import _ = require("lodash"); /** * A tree database that you can use to centrally store your application state in. Application * develops are encouraged not to use the SkeletosDb API directly, but rather build on top * of AbstractSkeletosState instead. * * ---------------------- * AbstractSkeletosState * ---------------------- * SkeletosCursor * ---------------------- * SkeletosDb <--- We are here * ---------------------- * * Internal details are provided in the constructor documentation. */ export declare class SkeletosDb { /** * This is the root node in the tree. */ private _rootNode; /** * Whenever we need to fetch a node from the tree, we need to do it from the root. However, this * results in O(lg n) access. By using a random access cache however allows us to do it in O(1) time. * * However, this cache would be reset with every set because calculating when needs to be removed * after a set can be very expensive (think about the nested children, parents, referencing, etc. and * you will see it is an O(n) traversal, which we cannot afford on a simple set). * * @type {{}} * @private */ private _nodeCache; /** * The listeners that need to be notified after a set takes place. * * @type {Array} * @private */ private _listeners; /** * The timestamp of when we triggered listeners last. Helps in dirty node hash recalculation. * * @type {number} * @private */ private _lastListenerTriggerTimestamp; /** * This value gets incremented every time we generate a new hash value. It acts as a seed to * the unique value generation. * * @type {number} * @private */ private _hashSeedCounter; /** * These are paths that had set called upon them. We need to use these paths to walk up the * parent hierarchy, as well as items referencing the dirty paths and the parent hierarchy * of those referencing items, to update the hashes of all nodes affected. * * @type {{}} * @private */ private _dirtyPathIndex; /** * Whether we should run the update job to figure out, given all the dirty paths in the * dirty path index, what paths require their hashes to be updated. * updated. * * @type {boolean} * @private */ private _shouldRunUpdate; /** * A fast and efficient tree database that supports cross node references (behaves like a directed * graph), as well as support for transactions and unique hash values. * * Full list of features: * * 1. Every node has a unique hash value assigned. When you set a value on a node, the hash value of * that node gets updated. In addition, all the hash values from that node up to the root (path too root) * are changed. This is perfect when you want to build UI applications with a technology like React as * each node can represent a Node, and updating a value will allow all components to the root to get updated. * * 2. A node can be a reference to another node. A referencing node will always take up the same value * as the node it references. However, the hash value of a referencing node and a referenced node will be * different. * * 3. Given (1) and (2), when a node is updated, it is not just the path to root that gets a new hash value, * but all referencing nodes, and all their paths to root, are also updated. If you are building nested UI * components, this is invaluable as it allows you to make mutations in one place and have UI updated across * a different hierarcy of components. For example, if you are building a new errorMessage counter in the top * navbar, and you also display new errorMessage counter in the sidebar, you can update both those fields simply by * mutating one node. * * 4. Hash values for all dirty nodes are changed in the next event loop. If each node is a prop for a React * component, this allows for batched UI updates. * * 5. All gets and sets are O(1) at best and O(lg n) at worst, where n is the total number of nodes * in the path supplied. * * 6. Each non-leaf node can also have a value set. * * 7. Values can only be of primitive immutable types (string, number, boolean, Date). * * 8. Collections (lists, dictionaries) are not part of this tree database. */ constructor(); /** * Gets the value at the given path. * * @param path * @returns {any} */ get(path: string[]): any; /** * Gets an ITreeNode at the given path. This is the raw database value. * * @param path * @returns {any} */ getNode(path: string[] | string): ITreeNode; /** * Sets a value for a node at the given path, where the path is represented as an array. Note that you do not need * to have the entire parent hierarchy already set. You can set a value at the path a/b/c/d even though * a/b/c may not exist. * * If you supply a null or undefined as the value, then the path will be deleted. This also means * that if you supply a null or undefined at a parent path, then all children of that path will be deleted. * * You can also supply a SkeletosTransaction that will record the mutation in case a rollback needs to be done. * * @param path * @param value * @param transaction */ set(path: string[], value: TreeNodeValueType | object, transaction?: ISkeletosDbTransaction, options?: SkeletosDbSetterOptions): void; /** * Sets a reference from the supplied path to the node at the toPath. After doing so, doing a get() * on the referencing path will always return the value at toPath. Note that you do not need * to have the entire parent hierarchy already set. You can set a value at the path a/b/c/d even though * a/b/c may not exist. * * !!Careful!! If the toPath does not exist, then we will warn you. Because you may think, "hey my * reference node exists, but when I do a get() on the reference, it returns null...wth??". That's because * you are pointing to something that does not exist. * * If you want to remove the existing reference, supply a null or undefined value. * * Supply a Transaction object to record this modification. * * The needs * @param path * @param toPath * @param transaction */ setReference(path: string[], toPath: string[], transaction?: ISkeletosDbTransaction, options?: SkeletosDbSetterOptions): void; /** * Given a path for a node X that points to a node Y, returns the path for node Y. * * @param referencingPath * @returns {string[]} */ getReferencedPathFromReferencingPath(referencingPath: string[]): string[]; /** * Returns a hash of the value at the node on the given path. See constructor() for more details * on how this works. * * @param path */ getNodeHash(path: string[]): any; /** * Add a new database listener. Will be called whenever anything changes. * * Note: the exact specifics of what changed will not be supplied. * * @param listener */ addListener(listener: ISkeletosDbListener): void; /** * Removes the previously added listener. See #addListener(..). * * @param listener */ removeListener(listener: ISkeletosDbListener): void; /** * Serializes the contents of the tree and returns a string that can be transported and called with deserialize(..) * * @returns {string} */ serialize(pathToStartFrom?: string[]): string; /** * Deserializes the output from serialize(..) and populates the tree. * * @param payload */ deserialize(payload: string | object, resetEverything?: boolean, deserializeAtPath?: string[], transaction?: ISkeletosDbTransaction): void; private escapeSpecialJsonCharacters; private unescapeSpecialJsonCharacters; /** * Creates a new node on the given path. * * If it is a reference path, then the path value is not set to the supplied path. * * @param pathVal * @returns {ITreeNode} */ private createNode; /** * Gets an existing or creates a new ITreeNode at the given path. * * If it is a reference path, then the path value is not set to the supplied path. * * @param path */ private getOrCreateNode; /** * Ensures there is no forward slash (/) in the path. * * @param path * @returns {boolean} */ private checkPath; /** * Generates a new hash. A hash is not specific to a value type, but rather just * a globally unique, never before used value. This is done not just for performance * reasons -- it is almost impossible to generate a unique hash for a node and all its children * everytime a child in the hierarchy changes. * * @returns {number} */ private generateUniqueHash; /** * Invoke listeners. */ private fireListeners; private addToDirtyPathIndex; private removeFromDirtyPathIndex; private addToCache; private clearCache; private removeFromCache; private getFromCache; private updateDirtyPathIndex; /** * Updates all the paths up the parent hierachy. * * @param path * @param memoizedIndex -- the memoized paths */ private updateDirtyFlagForPath; } /** * Any value that is set for a tree node can be only of the following types. * * Note: arrays are not supported. * * Also, I may add support for TypedArrays in the future to support binary data, * but for now because of arrays not being supported, TypedArrays are not supported. */ export declare type TreeNodeValueType = number | string | boolean | Date; /** * Represents a node that is stored in the tree. */ export interface ITreeNode { /** * The actual value stored by the client. This could be anything but we limit the API * for set to be TreeNodeValueType. */ value: any; /** * Only used when serializing/deserializing */ isDate?: boolean; /** * The path from the root this node is stored at. * * Each node in the path is separated with a forward slash separator. */ path: string; /** * This is a list of all the other paths that point to this node. */ referencingPaths: string[]; /** * In a strict tree structure, the children of those node are those nodes that are wholly * contained by this node. That is, deleting this node will delete all the children. */ children: ITreeNodeIndex; /** * Whenever the value of this node changes, a new hash is assigned. Note that two identical values * over time may not have the same hash unlike in other systems for the purpose of performance. * The hash will be unique in the system though, and a value never before assigned. */ hash: string; /** * Hashes are calculated lazily. This is a flag that indicates whether to update the hash. */ dirty: boolean; } /** * Helps maintain an index/cache of path to nodes. */ export interface ITreeNodeIndex { /** * Key here can be full paths from the root or partial paths to indicate, e.g., direct children. */ [path: string]: ITreeNode; } /** * An index of a path in single string form to a Boolean value. */ export interface IPathBooleanIndex { /** * Key is the full path with forward slashes. */ [path: string]: boolean; } /** * Setter options when calling the .set(..) API on the database. */ export declare class SkeletosDbSetterOptions { static DO_NOT_VERIFY_VALUE_TYPE: SkeletosDbSetterOptions; doNotVerifyValueType: boolean; doNotDeleteOnNullValue: boolean; suppressWarnings: boolean; /** * * @param doNotVerifyValueType skips checking for the type of value at debug time. */ constructor(doNotVerifyValueType?: boolean, doNotDeleteOnNullValue?: boolean, suppressWarnings?: boolean); } /** * Interface that represents a recorder for modifications that happen in the database (a Transaction). * * This interface is only meant to be used by SkeletosTransaction. The only reason it exists is to avoid * circular dependencies between SkeletosDb and SkeletosTransaction. */ export interface ISkeletosDbTransaction { /** * Records the value of the set call to the database * * @param path * @param newValue * @param oldValue */ recordSet: (path: string[], newValue: TreeNodeValueType, oldValue: TreeNodeValueType) => void; /** * Records the value of the setReference call to the database. * * @param path * @param newValue * @param oldValue */ recordSetReference: (path: string[], newValue: string[], oldValue: string[]) => void; /** * Records an unset of a node */ recordUnset: (path: string[], oldNode: ITreeNode) => void; /** * Rolls back all the modifications made to the database made so far as part of this transaction. * * @param {string} reason */ rollback: (reason?: string | Error) => void; } export declare type ISkeletosDbListener = () => void; /** * When nodes are serialized to be sent across the wire */ export interface ISerializedNode { /** * Maps to value */ __v?: TreeNodeValueType; /** * Maps to isDate */ __d?: boolean; /** * Maps to referencingPaths */ __r?: string[]; } /** * Keys that are disallowed in the database because they are reserved by the database. * * @type {{__v: boolean; __d: boolean; __r: boolean}} */ export declare const RESERVED_KEYWORDS: _.Dictionary; export declare const RESERVED_KEYWORDS_ARRAY: string[]; /** * Figures out if this value is a ITreeNode. Note we can't do an instanceof check because * it is just an interface. * * @param value * @returns {boolean} */ export declare function isITreeNode(value: any): boolean;