You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/orm/model/TreeModel.ts

89 lines
2.9 KiB

import {Model} from './Model'
import {Collection, Maybe} from '../../util'
import {HasSubtree} from './relation/HasSubtree'
import {Related} from './relation/decorators'
/**
* Model implementation with helpers for querying tree-structured data.
*
* This works by using a modified pre-order traversal to number the tree nodes
* with a left- and right-side numbers. For example:
*
* ```txt
* (1) A (14)
* |
* (2) B (9) (10) C (11) (12) D (14)
* |
* (3) E (6) (7) G (8)
* |
* (4) F (5)
* ```
*
* These numbers are stored, by default, in `left_num` and `right_num` columns.
* The `subtree()` method returns a `HasSubtree` relation which loads the subtree
* of a model and recursively nests the nodes.
*
* You can use the `children()` helper method to get a collection of the immediate
* children of this node, which also have the subtree set.
*
* To query the model without loading the entire subtree, use the `without()`
* method on the `ModelBuilder`. For example:
*
* ```ts
* MyModel.query<MyModel>().without('subtree')
* ```
*/
export abstract class TreeModel<T extends TreeModel<T>> extends Model<T> {
/** The table column where the left tree number is stored. */
public static readonly leftTreeField = 'left_num'
/** The table column where the right tree number is stored. */
public static readonly rightTreeField = 'right_num'
/**
* @override to eager-load the subtree by default
* @protected
*/
protected with: (keyof T)[] = ['subtree']
/** Get the left tree number for this model. */
public leftTreeNum(): Maybe<number> {
const ctor = this.constructor as typeof TreeModel
return this.originalSourceRow?.[ctor.leftTreeField]
}
/** Get the right tree number for this model. */
public rightTreeNum(): Maybe<number> {
const ctor = this.constructor as typeof TreeModel
return this.originalSourceRow?.[ctor.rightTreeField]
}
/** Returns true if this node has no children. */
public isLeaf(): boolean {
const left = this.leftTreeNum()
const right = this.rightTreeNum()
return Boolean(left && right && (right - left === 1))
}
/** Returns true if the given `node` exists within the subtree of this node. */
public contains(node: this): boolean {
const num = node.leftTreeNum()
const left = this.leftTreeNum()
const right = this.rightTreeNum()
return Boolean(num && left && right && (left < num && right > num))
}
/** The subtree nodes of this model, recursively nested. */
@Related()
public subtree(): HasSubtree<T> {
const ctor = this.constructor as typeof TreeModel
return this.make<HasSubtree<T>>(HasSubtree, this, ctor.leftTreeField)
}
/** Get the immediate children of this model. */
public children(): Collection<T> {
return this.subtree().getValue()
}
}