metal-orm 1.0.114 → 1.0.115
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 +11 -8
- package/dist/index.cjs +410 -202
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +405 -202
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dto/openapi/generators/tree.ts +276 -0
- package/src/dto/openapi/index.ts +6 -5
package/README.md
CHANGED
|
@@ -79,10 +79,12 @@ Full docs live in the `docs/` folder:
|
|
|
79
79
|
- [Introduction](https://github.com/celsowm/metal-orm/blob/main/docs/index.md)
|
|
80
80
|
- [Getting Started](https://github.com/celsowm/metal-orm/blob/main/docs/getting-started.md)
|
|
81
81
|
- [Level 3 Backend Tutorial](https://github.com/celsowm/metal-orm/blob/main/docs/level-3-backend-tutorial.md)
|
|
82
|
-
- [Schema Definition](https://github.com/celsowm/metal-orm/blob/main/docs/schema-definition.md)
|
|
83
|
-
- [Query Builder](https://github.com/celsowm/metal-orm/blob/main/docs/query-builder.md)
|
|
84
|
-
- [Tree Behavior (Nested Set/MPTT)](https://github.com/celsowm/metal-orm/blob/main/docs/tree.md)
|
|
85
|
-
- [
|
|
82
|
+
- [Schema Definition](https://github.com/celsowm/metal-orm/blob/main/docs/schema-definition.md)
|
|
83
|
+
- [Query Builder](https://github.com/celsowm/metal-orm/blob/main/docs/query-builder.md)
|
|
84
|
+
- [Tree Behavior (Nested Set/MPTT)](https://github.com/celsowm/metal-orm/blob/main/docs/tree.md)
|
|
85
|
+
- [DTO (Data Transfer Objects)](https://github.com/celsowm/metal-orm/blob/main/docs/dto.md)
|
|
86
|
+
- [OpenAPI Schema Generation](https://github.com/celsowm/metal-orm/blob/main/docs/openapi.md)
|
|
87
|
+
- [DML Operations](https://github.com/celsowm/metal-orm/blob/main/docs/dml-operations.md)
|
|
86
88
|
- [Hydration & Entities](https://github.com/celsowm/metal-orm/blob/main/docs/hydration.md)
|
|
87
89
|
- [Runtime & Unit of Work](https://github.com/celsowm/metal-orm/blob/main/docs/runtime.md)
|
|
88
90
|
- [Save Graph](https://github.com/celsowm/metal-orm/blob/main/docs/save-graph.md)
|
|
@@ -124,10 +126,11 @@ On top of the query builder, MetalORM ships a focused runtime managed by `Orm` a
|
|
|
124
126
|
|
|
125
127
|
- **Entities inferred from your `TableDef`s** (no separate mapping file).
|
|
126
128
|
- **Lazy, batched relations**: `user.posts.load()`, `user.roles.syncByIds([...])`, etc.
|
|
127
|
-
- **Scoped transactions**: `session.transaction(async s => { ... })` wraps `begin/commit/rollback` on the existing executor; `Orm.transaction` remains available when you want a fresh transactional executor per call.
|
|
128
|
-
- **Identity map**: the same row becomes the same entity instance within a session (see the [Identity map pattern](https://en.wikipedia.org/wiki/Identity_map_pattern)).
|
|
129
|
-
- **Tree Behavior (Nested Set/MPTT)**: hierarchical data with `TreeManager`, `treeQuery()`, and `@Tree` decorators. Efficient O(log n) operations for moves, inserts, and deletes. Supports multi-tree scoping, recovery, and validation.
|
|
130
|
-
- **
|
|
129
|
+
- **Scoped transactions**: `session.transaction(async s => { ... })` wraps `begin/commit/rollback` on the existing executor; `Orm.transaction` remains available when you want a fresh transactional executor per call.
|
|
130
|
+
- **Identity map**: the same row becomes the same entity instance within a session (see the [Identity map pattern](https://en.wikipedia.org/wiki/Identity_map_pattern)).
|
|
131
|
+
- **Tree Behavior (Nested Set/MPTT)**: hierarchical data with `TreeManager`, `treeQuery()`, and `@Tree` decorators. Efficient O(log n) operations for moves, inserts, and deletes. Supports multi-tree scoping, recovery, and validation.
|
|
132
|
+
- **DTO/OpenAPI helpers**: the `metal-orm/dto` module generates DTOs and OpenAPI schemas, including tree schemas (`TreeNode`, `TreeNodeResult`, threaded trees).
|
|
133
|
+
- **Unit of Work (`OrmSession`)** tracking New/Dirty/Removed entities and relation changes, inspired by the classic [Unit of Work pattern](https://en.wikipedia.org/wiki/Unit_of_work).
|
|
131
134
|
- **Graph persistence**: mutate a whole object graph and flush once with `session.commit()`.
|
|
132
135
|
- **Partial updates**: use `session.patchGraph()` to update only specific fields of an entity and its relations (returns `null` if entity doesn't exist).
|
|
133
136
|
- **Relation change processor** that knows how to deal with has-many and many-to-many pivot tables.
|
package/dist/index.cjs
CHANGED
|
@@ -218,6 +218,7 @@ __export(index_exports, {
|
|
|
218
218
|
generateRelationComponents: () => generateRelationComponents,
|
|
219
219
|
generateSchemaSql: () => generateSchemaSql,
|
|
220
220
|
generateSchemaSqlFor: () => generateSchemaSqlFor,
|
|
221
|
+
generateTreeComponents: () => generateTreeComponents,
|
|
221
222
|
getColumn: () => getColumn,
|
|
222
223
|
getColumnMap: () => getColumnMap,
|
|
223
224
|
getColumnType: () => getColumnType,
|
|
@@ -381,6 +382,7 @@ __export(index_exports, {
|
|
|
381
382
|
tableRef: () => tableRef,
|
|
382
383
|
tan: () => tan,
|
|
383
384
|
threadResults: () => threadResults,
|
|
385
|
+
threadedNodeToOpenApiSchema: () => threadedNodeToOpenApiSchema,
|
|
384
386
|
toColumnRef: () => toColumnRef,
|
|
385
387
|
toPagedResponse: () => toPagedResponse,
|
|
386
388
|
toPagedResponseBuilder: () => toPagedResponseBuilder,
|
|
@@ -389,6 +391,9 @@ __export(index_exports, {
|
|
|
389
391
|
toResponseBuilder: () => toResponseBuilder,
|
|
390
392
|
toTableRef: () => toTableRef,
|
|
391
393
|
treeEntityRegistry: () => treeEntityRegistry,
|
|
394
|
+
treeListEntryToOpenApiSchema: () => treeListEntryToOpenApiSchema,
|
|
395
|
+
treeNodeResultToOpenApiSchema: () => treeNodeResultToOpenApiSchema,
|
|
396
|
+
treeNodeToOpenApiSchema: () => treeNodeToOpenApiSchema,
|
|
392
397
|
treeQuery: () => treeQuery,
|
|
393
398
|
trim: () => trim,
|
|
394
399
|
trunc: () => trunc,
|
|
@@ -15966,6 +15971,406 @@ function extractReusableSchemas(schema, existing = {}, prefix = "") {
|
|
|
15966
15971
|
return existing;
|
|
15967
15972
|
}
|
|
15968
15973
|
|
|
15974
|
+
// src/tree/tree-types.ts
|
|
15975
|
+
var DEFAULT_TREE_CONFIG = {
|
|
15976
|
+
parentKey: "parentId",
|
|
15977
|
+
leftKey: "lft",
|
|
15978
|
+
rightKey: "rght",
|
|
15979
|
+
cascadeCallbacks: false
|
|
15980
|
+
};
|
|
15981
|
+
function isTreeConfig(value) {
|
|
15982
|
+
if (typeof value !== "object" || value === null) return false;
|
|
15983
|
+
const obj = value;
|
|
15984
|
+
return typeof obj.parentKey === "string" && typeof obj.leftKey === "string" && typeof obj.rightKey === "string";
|
|
15985
|
+
}
|
|
15986
|
+
function resolveTreeConfig(partial) {
|
|
15987
|
+
return {
|
|
15988
|
+
parentKey: partial.parentKey ?? DEFAULT_TREE_CONFIG.parentKey,
|
|
15989
|
+
leftKey: partial.leftKey ?? DEFAULT_TREE_CONFIG.leftKey,
|
|
15990
|
+
rightKey: partial.rightKey ?? DEFAULT_TREE_CONFIG.rightKey,
|
|
15991
|
+
depthKey: partial.depthKey,
|
|
15992
|
+
scope: partial.scope,
|
|
15993
|
+
cascadeCallbacks: partial.cascadeCallbacks ?? DEFAULT_TREE_CONFIG.cascadeCallbacks,
|
|
15994
|
+
recoverOrder: partial.recoverOrder
|
|
15995
|
+
};
|
|
15996
|
+
}
|
|
15997
|
+
function validateTreeTable(table, config) {
|
|
15998
|
+
const missingColumns = [];
|
|
15999
|
+
const warnings = [];
|
|
16000
|
+
const requiredColumns = [config.parentKey, config.leftKey, config.rightKey];
|
|
16001
|
+
for (const col2 of requiredColumns) {
|
|
16002
|
+
if (!(col2 in table.columns)) {
|
|
16003
|
+
missingColumns.push(col2);
|
|
16004
|
+
}
|
|
16005
|
+
}
|
|
16006
|
+
if (config.depthKey && !(config.depthKey in table.columns)) {
|
|
16007
|
+
warnings.push(`Optional depth column '${config.depthKey}' not found in table '${table.name}'`);
|
|
16008
|
+
}
|
|
16009
|
+
if (config.scope) {
|
|
16010
|
+
for (const scopeCol of config.scope) {
|
|
16011
|
+
if (!(scopeCol in table.columns)) {
|
|
16012
|
+
missingColumns.push(scopeCol);
|
|
16013
|
+
}
|
|
16014
|
+
}
|
|
16015
|
+
}
|
|
16016
|
+
const leftCol = table.columns[config.leftKey];
|
|
16017
|
+
const rightCol = table.columns[config.rightKey];
|
|
16018
|
+
if (leftCol && leftCol.type !== "int" && leftCol.type !== "integer") {
|
|
16019
|
+
warnings.push(`Left column '${config.leftKey}' should be an integer type`);
|
|
16020
|
+
}
|
|
16021
|
+
if (rightCol && rightCol.type !== "int" && rightCol.type !== "integer") {
|
|
16022
|
+
warnings.push(`Right column '${config.rightKey}' should be an integer type`);
|
|
16023
|
+
}
|
|
16024
|
+
return {
|
|
16025
|
+
valid: missingColumns.length === 0,
|
|
16026
|
+
missingColumns,
|
|
16027
|
+
warnings
|
|
16028
|
+
};
|
|
16029
|
+
}
|
|
16030
|
+
function getTreeColumns(table, config) {
|
|
16031
|
+
const parentColumn = table.columns[config.parentKey];
|
|
16032
|
+
const leftColumn = table.columns[config.leftKey];
|
|
16033
|
+
const rightColumn = table.columns[config.rightKey];
|
|
16034
|
+
const depthColumn = config.depthKey ? table.columns[config.depthKey] : void 0;
|
|
16035
|
+
const scopeColumns = (config.scope ?? []).map((s) => table.columns[s]).filter(Boolean);
|
|
16036
|
+
if (!parentColumn || !leftColumn || !rightColumn) {
|
|
16037
|
+
throw new Error(
|
|
16038
|
+
`Table '${table.name}' is missing required tree columns. Required: ${config.parentKey}, ${config.leftKey}, ${config.rightKey}`
|
|
16039
|
+
);
|
|
16040
|
+
}
|
|
16041
|
+
return {
|
|
16042
|
+
parentColumn,
|
|
16043
|
+
leftColumn,
|
|
16044
|
+
rightColumn,
|
|
16045
|
+
depthColumn,
|
|
16046
|
+
scopeColumns
|
|
16047
|
+
};
|
|
16048
|
+
}
|
|
16049
|
+
|
|
16050
|
+
// src/tree/tree-decorator.ts
|
|
16051
|
+
var TREE_METADATA_KEY = /* @__PURE__ */ Symbol("metal-orm:tree");
|
|
16052
|
+
function Tree(options = {}) {
|
|
16053
|
+
return function(target) {
|
|
16054
|
+
const config = resolveTreeConfig(options);
|
|
16055
|
+
const metadata = { config };
|
|
16056
|
+
setTreeMetadataOnClass(target, metadata);
|
|
16057
|
+
return target;
|
|
16058
|
+
};
|
|
16059
|
+
}
|
|
16060
|
+
function TreeParent() {
|
|
16061
|
+
return function(_value, context) {
|
|
16062
|
+
const propertyName = String(context.name);
|
|
16063
|
+
context.addInitializer(function() {
|
|
16064
|
+
const constructor = this.constructor;
|
|
16065
|
+
const metadata = getTreeMetadata(constructor);
|
|
16066
|
+
if (metadata) {
|
|
16067
|
+
metadata.parentProperty = propertyName;
|
|
16068
|
+
setTreeMetadata(constructor, metadata);
|
|
16069
|
+
}
|
|
16070
|
+
});
|
|
16071
|
+
};
|
|
16072
|
+
}
|
|
16073
|
+
function TreeChildren() {
|
|
16074
|
+
return function(_value, context) {
|
|
16075
|
+
const propertyName = String(context.name);
|
|
16076
|
+
context.addInitializer(function() {
|
|
16077
|
+
const constructor = this.constructor;
|
|
16078
|
+
const metadata = getTreeMetadata(constructor);
|
|
16079
|
+
if (metadata) {
|
|
16080
|
+
metadata.childrenProperty = propertyName;
|
|
16081
|
+
setTreeMetadata(constructor, metadata);
|
|
16082
|
+
}
|
|
16083
|
+
});
|
|
16084
|
+
};
|
|
16085
|
+
}
|
|
16086
|
+
function getTreeMetadata(target) {
|
|
16087
|
+
return target[TREE_METADATA_KEY];
|
|
16088
|
+
}
|
|
16089
|
+
function setTreeMetadata(target, metadata) {
|
|
16090
|
+
target[TREE_METADATA_KEY] = metadata;
|
|
16091
|
+
}
|
|
16092
|
+
function setTreeMetadataOnClass(target, metadata) {
|
|
16093
|
+
target[TREE_METADATA_KEY] = metadata;
|
|
16094
|
+
}
|
|
16095
|
+
function hasTreeBehavior(target) {
|
|
16096
|
+
return getTreeMetadata(target) !== void 0;
|
|
16097
|
+
}
|
|
16098
|
+
function getTreeConfig(target) {
|
|
16099
|
+
return getTreeMetadata(target)?.config;
|
|
16100
|
+
}
|
|
16101
|
+
var TreeEntityRegistry = class {
|
|
16102
|
+
entities = /* @__PURE__ */ new Map();
|
|
16103
|
+
tableNames = /* @__PURE__ */ new Map();
|
|
16104
|
+
/**
|
|
16105
|
+
* Registers an entity class with its table name.
|
|
16106
|
+
*/
|
|
16107
|
+
register(entityClass, tableName) {
|
|
16108
|
+
this.entities.set(tableName, entityClass);
|
|
16109
|
+
this.tableNames.set(entityClass, tableName);
|
|
16110
|
+
}
|
|
16111
|
+
/**
|
|
16112
|
+
* Gets the entity class for a table name.
|
|
16113
|
+
*/
|
|
16114
|
+
getByTableName(tableName) {
|
|
16115
|
+
return this.entities.get(tableName);
|
|
16116
|
+
}
|
|
16117
|
+
/**
|
|
16118
|
+
* Gets the table name for an entity class.
|
|
16119
|
+
*/
|
|
16120
|
+
getTableName(entityClass) {
|
|
16121
|
+
return this.tableNames.get(entityClass);
|
|
16122
|
+
}
|
|
16123
|
+
/**
|
|
16124
|
+
* Checks if a table has tree behavior.
|
|
16125
|
+
*/
|
|
16126
|
+
isTreeTable(tableName) {
|
|
16127
|
+
const entity = this.entities.get(tableName);
|
|
16128
|
+
return entity ? hasTreeBehavior(entity) : false;
|
|
16129
|
+
}
|
|
16130
|
+
/**
|
|
16131
|
+
* Gets all registered tree entities.
|
|
16132
|
+
*/
|
|
16133
|
+
getAll() {
|
|
16134
|
+
const result = [];
|
|
16135
|
+
for (const [tableName, entityClass] of this.entities) {
|
|
16136
|
+
const metadata = getTreeMetadata(entityClass);
|
|
16137
|
+
if (metadata) {
|
|
16138
|
+
result.push({ entityClass, tableName, metadata });
|
|
16139
|
+
}
|
|
16140
|
+
}
|
|
16141
|
+
return result;
|
|
16142
|
+
}
|
|
16143
|
+
/**
|
|
16144
|
+
* Clears the registry.
|
|
16145
|
+
*/
|
|
16146
|
+
clear() {
|
|
16147
|
+
this.entities.clear();
|
|
16148
|
+
this.tableNames.clear();
|
|
16149
|
+
}
|
|
16150
|
+
};
|
|
16151
|
+
var treeEntityRegistry = new TreeEntityRegistry();
|
|
16152
|
+
function getTreeBounds(entity, config) {
|
|
16153
|
+
const data = entity;
|
|
16154
|
+
const lft = data[config.leftKey];
|
|
16155
|
+
const rght = data[config.rightKey];
|
|
16156
|
+
if (typeof lft !== "number" || typeof rght !== "number") {
|
|
16157
|
+
return null;
|
|
16158
|
+
}
|
|
16159
|
+
return { lft, rght };
|
|
16160
|
+
}
|
|
16161
|
+
function getTreeParentId(entity, config) {
|
|
16162
|
+
return entity[config.parentKey];
|
|
16163
|
+
}
|
|
16164
|
+
function setTreeBounds(entity, config, lft, rght, depth) {
|
|
16165
|
+
const data = entity;
|
|
16166
|
+
data[config.leftKey] = lft;
|
|
16167
|
+
data[config.rightKey] = rght;
|
|
16168
|
+
if (config.depthKey && depth !== void 0) {
|
|
16169
|
+
data[config.depthKey] = depth;
|
|
16170
|
+
}
|
|
16171
|
+
}
|
|
16172
|
+
function setTreeParentId(entity, config, parentId) {
|
|
16173
|
+
entity[config.parentKey] = parentId;
|
|
16174
|
+
}
|
|
16175
|
+
|
|
16176
|
+
// src/dto/openapi/generators/tree.ts
|
|
16177
|
+
function threadedNodeToOpenApiSchema(target, options) {
|
|
16178
|
+
const componentName = options?.componentName;
|
|
16179
|
+
const nodeSchema = entityToOpenApiSchema(target, options);
|
|
16180
|
+
const treeNodeSchema = {
|
|
16181
|
+
type: "object",
|
|
16182
|
+
properties: {
|
|
16183
|
+
node: nodeSchema,
|
|
16184
|
+
children: {
|
|
16185
|
+
type: "array",
|
|
16186
|
+
items: componentName ? { $ref: `#/components/schemas/${componentName}` } : {},
|
|
16187
|
+
description: "Child nodes in the tree hierarchy"
|
|
16188
|
+
}
|
|
16189
|
+
},
|
|
16190
|
+
required: ["node", "children"],
|
|
16191
|
+
description: "A node in a threaded tree structure with nested children"
|
|
16192
|
+
};
|
|
16193
|
+
return treeNodeSchema;
|
|
16194
|
+
}
|
|
16195
|
+
function treeNodeToOpenApiSchema(target, options) {
|
|
16196
|
+
const includeMetadata = options?.includeTreeMetadata ?? true;
|
|
16197
|
+
const entitySchema = entityToOpenApiSchema(target, options);
|
|
16198
|
+
const properties = {
|
|
16199
|
+
entity: entitySchema,
|
|
16200
|
+
lft: {
|
|
16201
|
+
type: "integer",
|
|
16202
|
+
description: "Left boundary value (nested set)"
|
|
16203
|
+
},
|
|
16204
|
+
rght: {
|
|
16205
|
+
type: "integer",
|
|
16206
|
+
description: "Right boundary value (nested set)"
|
|
16207
|
+
},
|
|
16208
|
+
isLeaf: {
|
|
16209
|
+
type: "boolean",
|
|
16210
|
+
description: "Whether this node has no children"
|
|
16211
|
+
},
|
|
16212
|
+
isRoot: {
|
|
16213
|
+
type: "boolean",
|
|
16214
|
+
description: "Whether this node has no parent"
|
|
16215
|
+
},
|
|
16216
|
+
childCount: {
|
|
16217
|
+
type: "integer",
|
|
16218
|
+
minimum: 0,
|
|
16219
|
+
description: "Number of descendants"
|
|
16220
|
+
}
|
|
16221
|
+
};
|
|
16222
|
+
if (includeMetadata) {
|
|
16223
|
+
properties.depth = {
|
|
16224
|
+
type: "integer",
|
|
16225
|
+
minimum: 0,
|
|
16226
|
+
description: "Depth level (0 = root)"
|
|
16227
|
+
};
|
|
16228
|
+
}
|
|
16229
|
+
return {
|
|
16230
|
+
type: "object",
|
|
16231
|
+
properties,
|
|
16232
|
+
required: ["entity", "lft", "rght", "isLeaf", "isRoot", "childCount"],
|
|
16233
|
+
description: "A tree node with nested set boundaries and metadata"
|
|
16234
|
+
};
|
|
16235
|
+
}
|
|
16236
|
+
function treeNodeResultToOpenApiSchema(target, options) {
|
|
16237
|
+
const includeMetadata = options?.includeTreeMetadata ?? true;
|
|
16238
|
+
const entitySchema = entityToOpenApiSchema(target, options);
|
|
16239
|
+
const parentKey = resolveParentKey(target, options);
|
|
16240
|
+
const parentSchema = resolveParentSchema(target, parentKey, options?.dialect);
|
|
16241
|
+
const parentIdSchema = {
|
|
16242
|
+
...parentSchema,
|
|
16243
|
+
description: parentSchema.description ?? "Parent identifier (null for roots)"
|
|
16244
|
+
};
|
|
16245
|
+
const properties = {
|
|
16246
|
+
data: entitySchema,
|
|
16247
|
+
lft: {
|
|
16248
|
+
type: "integer",
|
|
16249
|
+
description: "Left boundary value (nested set)"
|
|
16250
|
+
},
|
|
16251
|
+
rght: {
|
|
16252
|
+
type: "integer",
|
|
16253
|
+
description: "Right boundary value (nested set)"
|
|
16254
|
+
},
|
|
16255
|
+
parentId: parentIdSchema,
|
|
16256
|
+
isLeaf: {
|
|
16257
|
+
type: "boolean",
|
|
16258
|
+
description: "Whether this node has no children"
|
|
16259
|
+
},
|
|
16260
|
+
isRoot: {
|
|
16261
|
+
type: "boolean",
|
|
16262
|
+
description: "Whether this node has no parent"
|
|
16263
|
+
}
|
|
16264
|
+
};
|
|
16265
|
+
if (includeMetadata) {
|
|
16266
|
+
properties.depth = {
|
|
16267
|
+
type: "integer",
|
|
16268
|
+
minimum: 0,
|
|
16269
|
+
description: "Depth level (0 = root)"
|
|
16270
|
+
};
|
|
16271
|
+
}
|
|
16272
|
+
return {
|
|
16273
|
+
type: "object",
|
|
16274
|
+
properties,
|
|
16275
|
+
required: ["data", "lft", "rght", "parentId", "isLeaf", "isRoot"],
|
|
16276
|
+
description: "A tree node result with nested set boundaries and metadata"
|
|
16277
|
+
};
|
|
16278
|
+
}
|
|
16279
|
+
function treeListEntryToOpenApiSchema(options) {
|
|
16280
|
+
const keyType = options?.keyType ?? "integer";
|
|
16281
|
+
const valueType = options?.valueType ?? "string";
|
|
16282
|
+
return {
|
|
16283
|
+
type: "object",
|
|
16284
|
+
properties: {
|
|
16285
|
+
key: {
|
|
16286
|
+
type: keyType,
|
|
16287
|
+
description: "The key (usually primary key)"
|
|
16288
|
+
},
|
|
16289
|
+
value: {
|
|
16290
|
+
type: valueType,
|
|
16291
|
+
description: "The display value with depth prefix"
|
|
16292
|
+
},
|
|
16293
|
+
depth: {
|
|
16294
|
+
type: "integer",
|
|
16295
|
+
minimum: 0,
|
|
16296
|
+
description: "The depth level"
|
|
16297
|
+
}
|
|
16298
|
+
},
|
|
16299
|
+
required: ["key", "value", "depth"],
|
|
16300
|
+
description: "A tree list entry for dropdown/select rendering"
|
|
16301
|
+
};
|
|
16302
|
+
}
|
|
16303
|
+
function generateTreeComponents(target, baseName, options) {
|
|
16304
|
+
const threadedNodeName = `${baseName}TreeNode`;
|
|
16305
|
+
return {
|
|
16306
|
+
[baseName]: entityToOpenApiSchema(target, options),
|
|
16307
|
+
[`${baseName}Node`]: treeNodeToOpenApiSchema(target, options),
|
|
16308
|
+
[`${baseName}NodeResult`]: treeNodeResultToOpenApiSchema(target, options),
|
|
16309
|
+
[threadedNodeName]: threadedNodeToOpenApiSchema(target, {
|
|
16310
|
+
...options,
|
|
16311
|
+
componentName: threadedNodeName
|
|
16312
|
+
}),
|
|
16313
|
+
[`${baseName}TreeList`]: {
|
|
16314
|
+
type: "array",
|
|
16315
|
+
items: treeListEntryToOpenApiSchema(),
|
|
16316
|
+
description: `Flat list of ${baseName} tree entries for dropdown/select`
|
|
16317
|
+
},
|
|
16318
|
+
[`${baseName}ThreadedTree`]: {
|
|
16319
|
+
type: "array",
|
|
16320
|
+
items: { $ref: `#/components/schemas/${threadedNodeName}` },
|
|
16321
|
+
description: `Threaded tree structure of ${baseName} nodes`
|
|
16322
|
+
}
|
|
16323
|
+
};
|
|
16324
|
+
}
|
|
16325
|
+
function entityToOpenApiSchema(target, options) {
|
|
16326
|
+
const columns = getColumnMap(target);
|
|
16327
|
+
const properties = {};
|
|
16328
|
+
const required = [];
|
|
16329
|
+
const dialect = options?.dialect ?? "openapi-3.1";
|
|
16330
|
+
for (const [key, col2] of Object.entries(columns)) {
|
|
16331
|
+
if (options?.exclude?.includes(key)) {
|
|
16332
|
+
continue;
|
|
16333
|
+
}
|
|
16334
|
+
if (options?.include && !options.include.includes(key)) {
|
|
16335
|
+
continue;
|
|
16336
|
+
}
|
|
16337
|
+
properties[key] = columnToOpenApiSchema(col2, dialect);
|
|
16338
|
+
if (col2.notNull || col2.primary) {
|
|
16339
|
+
required.push(key);
|
|
16340
|
+
}
|
|
16341
|
+
}
|
|
16342
|
+
return {
|
|
16343
|
+
type: "object",
|
|
16344
|
+
properties,
|
|
16345
|
+
...required.length > 0 && { required }
|
|
16346
|
+
};
|
|
16347
|
+
}
|
|
16348
|
+
function resolveParentSchema(target, parentKey, dialect = "openapi-3.1") {
|
|
16349
|
+
const columns = getColumnMap(target);
|
|
16350
|
+
const parentColumn = columns[parentKey] ?? findColumnByName(columns, parentKey);
|
|
16351
|
+
if (parentColumn) {
|
|
16352
|
+
return columnToOpenApiSchema(parentColumn, dialect);
|
|
16353
|
+
}
|
|
16354
|
+
return {
|
|
16355
|
+
type: ["string", "null"]
|
|
16356
|
+
};
|
|
16357
|
+
}
|
|
16358
|
+
function resolveParentKey(target, options) {
|
|
16359
|
+
if (options?.parentKey) {
|
|
16360
|
+
return options.parentKey;
|
|
16361
|
+
}
|
|
16362
|
+
if (!isTableDef2(target)) {
|
|
16363
|
+
const config = getTreeConfig(target);
|
|
16364
|
+
if (config?.parentKey) {
|
|
16365
|
+
return config.parentKey;
|
|
16366
|
+
}
|
|
16367
|
+
}
|
|
16368
|
+
return "parentId";
|
|
16369
|
+
}
|
|
16370
|
+
function findColumnByName(columns, columnName) {
|
|
16371
|
+
return Object.values(columns).find((col2) => col2.name === columnName);
|
|
16372
|
+
}
|
|
16373
|
+
|
|
15969
16374
|
// src/dto/openapi/generators/pagination.ts
|
|
15970
16375
|
var paginationParamsSchema = {
|
|
15971
16376
|
type: "object",
|
|
@@ -16066,82 +16471,6 @@ function pagedResponseToOpenApiSchema(itemSchema) {
|
|
|
16066
16471
|
};
|
|
16067
16472
|
}
|
|
16068
16473
|
|
|
16069
|
-
// src/tree/tree-types.ts
|
|
16070
|
-
var DEFAULT_TREE_CONFIG = {
|
|
16071
|
-
parentKey: "parentId",
|
|
16072
|
-
leftKey: "lft",
|
|
16073
|
-
rightKey: "rght",
|
|
16074
|
-
cascadeCallbacks: false
|
|
16075
|
-
};
|
|
16076
|
-
function isTreeConfig(value) {
|
|
16077
|
-
if (typeof value !== "object" || value === null) return false;
|
|
16078
|
-
const obj = value;
|
|
16079
|
-
return typeof obj.parentKey === "string" && typeof obj.leftKey === "string" && typeof obj.rightKey === "string";
|
|
16080
|
-
}
|
|
16081
|
-
function resolveTreeConfig(partial) {
|
|
16082
|
-
return {
|
|
16083
|
-
parentKey: partial.parentKey ?? DEFAULT_TREE_CONFIG.parentKey,
|
|
16084
|
-
leftKey: partial.leftKey ?? DEFAULT_TREE_CONFIG.leftKey,
|
|
16085
|
-
rightKey: partial.rightKey ?? DEFAULT_TREE_CONFIG.rightKey,
|
|
16086
|
-
depthKey: partial.depthKey,
|
|
16087
|
-
scope: partial.scope,
|
|
16088
|
-
cascadeCallbacks: partial.cascadeCallbacks ?? DEFAULT_TREE_CONFIG.cascadeCallbacks,
|
|
16089
|
-
recoverOrder: partial.recoverOrder
|
|
16090
|
-
};
|
|
16091
|
-
}
|
|
16092
|
-
function validateTreeTable(table, config) {
|
|
16093
|
-
const missingColumns = [];
|
|
16094
|
-
const warnings = [];
|
|
16095
|
-
const requiredColumns = [config.parentKey, config.leftKey, config.rightKey];
|
|
16096
|
-
for (const col2 of requiredColumns) {
|
|
16097
|
-
if (!(col2 in table.columns)) {
|
|
16098
|
-
missingColumns.push(col2);
|
|
16099
|
-
}
|
|
16100
|
-
}
|
|
16101
|
-
if (config.depthKey && !(config.depthKey in table.columns)) {
|
|
16102
|
-
warnings.push(`Optional depth column '${config.depthKey}' not found in table '${table.name}'`);
|
|
16103
|
-
}
|
|
16104
|
-
if (config.scope) {
|
|
16105
|
-
for (const scopeCol of config.scope) {
|
|
16106
|
-
if (!(scopeCol in table.columns)) {
|
|
16107
|
-
missingColumns.push(scopeCol);
|
|
16108
|
-
}
|
|
16109
|
-
}
|
|
16110
|
-
}
|
|
16111
|
-
const leftCol = table.columns[config.leftKey];
|
|
16112
|
-
const rightCol = table.columns[config.rightKey];
|
|
16113
|
-
if (leftCol && leftCol.type !== "int" && leftCol.type !== "integer") {
|
|
16114
|
-
warnings.push(`Left column '${config.leftKey}' should be an integer type`);
|
|
16115
|
-
}
|
|
16116
|
-
if (rightCol && rightCol.type !== "int" && rightCol.type !== "integer") {
|
|
16117
|
-
warnings.push(`Right column '${config.rightKey}' should be an integer type`);
|
|
16118
|
-
}
|
|
16119
|
-
return {
|
|
16120
|
-
valid: missingColumns.length === 0,
|
|
16121
|
-
missingColumns,
|
|
16122
|
-
warnings
|
|
16123
|
-
};
|
|
16124
|
-
}
|
|
16125
|
-
function getTreeColumns(table, config) {
|
|
16126
|
-
const parentColumn = table.columns[config.parentKey];
|
|
16127
|
-
const leftColumn = table.columns[config.leftKey];
|
|
16128
|
-
const rightColumn = table.columns[config.rightKey];
|
|
16129
|
-
const depthColumn = config.depthKey ? table.columns[config.depthKey] : void 0;
|
|
16130
|
-
const scopeColumns = (config.scope ?? []).map((s) => table.columns[s]).filter(Boolean);
|
|
16131
|
-
if (!parentColumn || !leftColumn || !rightColumn) {
|
|
16132
|
-
throw new Error(
|
|
16133
|
-
`Table '${table.name}' is missing required tree columns. Required: ${config.parentKey}, ${config.leftKey}, ${config.rightKey}`
|
|
16134
|
-
);
|
|
16135
|
-
}
|
|
16136
|
-
return {
|
|
16137
|
-
parentColumn,
|
|
16138
|
-
leftColumn,
|
|
16139
|
-
rightColumn,
|
|
16140
|
-
depthColumn,
|
|
16141
|
-
scopeColumns
|
|
16142
|
-
};
|
|
16143
|
-
}
|
|
16144
|
-
|
|
16145
16474
|
// src/tree/nested-set-strategy.ts
|
|
16146
16475
|
var NestedSetStrategy = class {
|
|
16147
16476
|
/**
|
|
@@ -17207,132 +17536,6 @@ function queryResultsToRows(results) {
|
|
|
17207
17536
|
}
|
|
17208
17537
|
return rows;
|
|
17209
17538
|
}
|
|
17210
|
-
|
|
17211
|
-
// src/tree/tree-decorator.ts
|
|
17212
|
-
var TREE_METADATA_KEY = /* @__PURE__ */ Symbol("metal-orm:tree");
|
|
17213
|
-
function Tree(options = {}) {
|
|
17214
|
-
return function(target) {
|
|
17215
|
-
const config = resolveTreeConfig(options);
|
|
17216
|
-
const metadata = { config };
|
|
17217
|
-
setTreeMetadataOnClass(target, metadata);
|
|
17218
|
-
return target;
|
|
17219
|
-
};
|
|
17220
|
-
}
|
|
17221
|
-
function TreeParent() {
|
|
17222
|
-
return function(_value, context) {
|
|
17223
|
-
const propertyName = String(context.name);
|
|
17224
|
-
context.addInitializer(function() {
|
|
17225
|
-
const constructor = this.constructor;
|
|
17226
|
-
const metadata = getTreeMetadata(constructor);
|
|
17227
|
-
if (metadata) {
|
|
17228
|
-
metadata.parentProperty = propertyName;
|
|
17229
|
-
setTreeMetadata(constructor, metadata);
|
|
17230
|
-
}
|
|
17231
|
-
});
|
|
17232
|
-
};
|
|
17233
|
-
}
|
|
17234
|
-
function TreeChildren() {
|
|
17235
|
-
return function(_value, context) {
|
|
17236
|
-
const propertyName = String(context.name);
|
|
17237
|
-
context.addInitializer(function() {
|
|
17238
|
-
const constructor = this.constructor;
|
|
17239
|
-
const metadata = getTreeMetadata(constructor);
|
|
17240
|
-
if (metadata) {
|
|
17241
|
-
metadata.childrenProperty = propertyName;
|
|
17242
|
-
setTreeMetadata(constructor, metadata);
|
|
17243
|
-
}
|
|
17244
|
-
});
|
|
17245
|
-
};
|
|
17246
|
-
}
|
|
17247
|
-
function getTreeMetadata(target) {
|
|
17248
|
-
return target[TREE_METADATA_KEY];
|
|
17249
|
-
}
|
|
17250
|
-
function setTreeMetadata(target, metadata) {
|
|
17251
|
-
target[TREE_METADATA_KEY] = metadata;
|
|
17252
|
-
}
|
|
17253
|
-
function setTreeMetadataOnClass(target, metadata) {
|
|
17254
|
-
target[TREE_METADATA_KEY] = metadata;
|
|
17255
|
-
}
|
|
17256
|
-
function hasTreeBehavior(target) {
|
|
17257
|
-
return getTreeMetadata(target) !== void 0;
|
|
17258
|
-
}
|
|
17259
|
-
function getTreeConfig(target) {
|
|
17260
|
-
return getTreeMetadata(target)?.config;
|
|
17261
|
-
}
|
|
17262
|
-
var TreeEntityRegistry = class {
|
|
17263
|
-
entities = /* @__PURE__ */ new Map();
|
|
17264
|
-
tableNames = /* @__PURE__ */ new Map();
|
|
17265
|
-
/**
|
|
17266
|
-
* Registers an entity class with its table name.
|
|
17267
|
-
*/
|
|
17268
|
-
register(entityClass, tableName) {
|
|
17269
|
-
this.entities.set(tableName, entityClass);
|
|
17270
|
-
this.tableNames.set(entityClass, tableName);
|
|
17271
|
-
}
|
|
17272
|
-
/**
|
|
17273
|
-
* Gets the entity class for a table name.
|
|
17274
|
-
*/
|
|
17275
|
-
getByTableName(tableName) {
|
|
17276
|
-
return this.entities.get(tableName);
|
|
17277
|
-
}
|
|
17278
|
-
/**
|
|
17279
|
-
* Gets the table name for an entity class.
|
|
17280
|
-
*/
|
|
17281
|
-
getTableName(entityClass) {
|
|
17282
|
-
return this.tableNames.get(entityClass);
|
|
17283
|
-
}
|
|
17284
|
-
/**
|
|
17285
|
-
* Checks if a table has tree behavior.
|
|
17286
|
-
*/
|
|
17287
|
-
isTreeTable(tableName) {
|
|
17288
|
-
const entity = this.entities.get(tableName);
|
|
17289
|
-
return entity ? hasTreeBehavior(entity) : false;
|
|
17290
|
-
}
|
|
17291
|
-
/**
|
|
17292
|
-
* Gets all registered tree entities.
|
|
17293
|
-
*/
|
|
17294
|
-
getAll() {
|
|
17295
|
-
const result = [];
|
|
17296
|
-
for (const [tableName, entityClass] of this.entities) {
|
|
17297
|
-
const metadata = getTreeMetadata(entityClass);
|
|
17298
|
-
if (metadata) {
|
|
17299
|
-
result.push({ entityClass, tableName, metadata });
|
|
17300
|
-
}
|
|
17301
|
-
}
|
|
17302
|
-
return result;
|
|
17303
|
-
}
|
|
17304
|
-
/**
|
|
17305
|
-
* Clears the registry.
|
|
17306
|
-
*/
|
|
17307
|
-
clear() {
|
|
17308
|
-
this.entities.clear();
|
|
17309
|
-
this.tableNames.clear();
|
|
17310
|
-
}
|
|
17311
|
-
};
|
|
17312
|
-
var treeEntityRegistry = new TreeEntityRegistry();
|
|
17313
|
-
function getTreeBounds(entity, config) {
|
|
17314
|
-
const data = entity;
|
|
17315
|
-
const lft = data[config.leftKey];
|
|
17316
|
-
const rght = data[config.rightKey];
|
|
17317
|
-
if (typeof lft !== "number" || typeof rght !== "number") {
|
|
17318
|
-
return null;
|
|
17319
|
-
}
|
|
17320
|
-
return { lft, rght };
|
|
17321
|
-
}
|
|
17322
|
-
function getTreeParentId(entity, config) {
|
|
17323
|
-
return entity[config.parentKey];
|
|
17324
|
-
}
|
|
17325
|
-
function setTreeBounds(entity, config, lft, rght, depth) {
|
|
17326
|
-
const data = entity;
|
|
17327
|
-
data[config.leftKey] = lft;
|
|
17328
|
-
data[config.rightKey] = rght;
|
|
17329
|
-
if (config.depthKey && depth !== void 0) {
|
|
17330
|
-
data[config.depthKey] = depth;
|
|
17331
|
-
}
|
|
17332
|
-
}
|
|
17333
|
-
function setTreeParentId(entity, config, parentId) {
|
|
17334
|
-
entity[config.parentKey] = parentId;
|
|
17335
|
-
}
|
|
17336
17539
|
// Annotate the CommonJS export names for ESM import in node:
|
|
17337
17540
|
0 && (module.exports = {
|
|
17338
17541
|
Alphanumeric,
|
|
@@ -17510,6 +17713,7 @@ function setTreeParentId(entity, config, parentId) {
|
|
|
17510
17713
|
generateRelationComponents,
|
|
17511
17714
|
generateSchemaSql,
|
|
17512
17715
|
generateSchemaSqlFor,
|
|
17716
|
+
generateTreeComponents,
|
|
17513
17717
|
getColumn,
|
|
17514
17718
|
getColumnMap,
|
|
17515
17719
|
getColumnType,
|
|
@@ -17673,6 +17877,7 @@ function setTreeParentId(entity, config, parentId) {
|
|
|
17673
17877
|
tableRef,
|
|
17674
17878
|
tan,
|
|
17675
17879
|
threadResults,
|
|
17880
|
+
threadedNodeToOpenApiSchema,
|
|
17676
17881
|
toColumnRef,
|
|
17677
17882
|
toPagedResponse,
|
|
17678
17883
|
toPagedResponseBuilder,
|
|
@@ -17681,6 +17886,9 @@ function setTreeParentId(entity, config, parentId) {
|
|
|
17681
17886
|
toResponseBuilder,
|
|
17682
17887
|
toTableRef,
|
|
17683
17888
|
treeEntityRegistry,
|
|
17889
|
+
treeListEntryToOpenApiSchema,
|
|
17890
|
+
treeNodeResultToOpenApiSchema,
|
|
17891
|
+
treeNodeToOpenApiSchema,
|
|
17684
17892
|
treeQuery,
|
|
17685
17893
|
trim,
|
|
17686
17894
|
trunc,
|