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().without('subtree') * ``` */ export abstract class TreeModel> extends Model { /** 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 { const ctor = this.constructor as typeof TreeModel return this.originalSourceRow?.[ctor.leftTreeField] } /** Get the right tree number for this model. */ public rightTreeNum(): Maybe { 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 { const ctor = this.constructor as typeof TreeModel return this.make>(HasSubtree, this, ctor.leftTreeField) } /** Get the immediate children of this model. */ public children(): Collection { return this.subtree().getValue() } }