UNPKG

zetabasejs

Version:

A NoSQL database for quick deployment.

255 lines (227 loc) 8.75 kB
const fs = require("fs"); const DataNode = require("./libs/dataStructures/DataNode"); const Stack = require("./libs/dataStructures/Stack"); const Deprecator = require("./libs/Deprecator"); const Query = require("./libs/Query"); const Observer = require("./libs/Observer"); const { Model } = require("./orm"); const WRITE_BUFFER_TIME = 10; class Zetabase { constructor(path, option = {}) { this.path = path; this.memory = new DataNode("/", null); this.observer = new Observer(); this.writeService = null; this.writeBufferTime = WRITE_BUFFER_TIME; this._start(); Model.setDBCONN(option.orm ? this : null) } setWriteBufferTime(duration) { this.writeBufferTime = duration; } monitor(key, callback) { this.observer.observe(key, callback); } write(key, value) { if (key === "/") { this.memory.data = value; return this.invalidate(); } let { childNodeKey, nodePtr } = this.traverse(key, true); nodePtr.cur.get(childNodeKey).data = value; this.invalidate(); this.observer.check(key, value, "WRITE"); } query(key, filters = {}) { if (key.indexOf(":") === -1 && key.indexOf("@") === -1) throw "Please provide variable(s) for query or use read function instead." let result = this._varTraverse(key, filters) return Query.reduce(result) } read(key) { if (key.indexOf(":") !== -1 || key.indexOf("@") !== -1) throw "read function does not support variable(s), please use query function instead." if (key === "/") return this.memory.data; return this.traverse(key).nodePtr.next.data; } list(key, callback) { if (key === "/") return this.memory.list(callback); let { childNodeKey, nodePtr } = this.traverse(key); nodePtr.cur.get(childNodeKey).list(callback) } append(key, value) { if (key === "/") { if (!this.memory.has(key)) this.memory.childNodes[key] = new DataNode(key); let id = this.memory.childNodes[key].push(value); this.invalidate(); return this.id; } let { childNodeKey, nodePtr } = this.traverse(key, true); let id = nodePtr.cur.get(childNodeKey).push(value); this.invalidate(); if (typeof (value) === 'object') value.key = id; this.observer.check(key, value, "APPEND"); return id; } containsKey(key) { if (key === "/") return true; try { let { childNodeKey, nodePtr } = this.traverse(key); return nodePtr.cur.has(childNodeKey); } catch (e) { return false; } } wipe(key) { if (key === "/") return this.memory = new DataNode(); let { childNodeKey, nodePtr, backStack } = this.traverse(key); let data = nodePtr.next.data; nodePtr.cur.remove(childNodeKey) this.purge(nodePtr, backStack); this.observer.check(key, data, "WIPE", true); } purge(nodePtr, backStack) { if (nodePtr.cur.isEmpty()) { let parentNodePtr = backStack.pop(); parentNodePtr.cur.remove(nodePtr.cur.id) this.invalidate(); if (!backStack.isEmpty()) this.purge(parentNodePtr, backStack); } this.invalidate(); } traverse(strPath, createIfMissing = false) { return strPath.indexOf(":") !== -1 || strPath.indexOf("@") !== -1 ? this._varTraverse(strPath) : this._traverse(strPath, createIfMissing) } _traverse(strPath, createIfMissing = false) { var ptr = this.memory; let backStack = new Stack(); let nodePtr = null; let childNodeKey = null; let dirs = this.tokenize(strPath); for (let i in dirs) { if (!createIfMissing && !ptr.has(dirs[i])) throw "The provided path " + dirs[i] + " does not exist."; if (!ptr.has(dirs[i]) && createIfMissing) ptr.add(dirs[i]) nodePtr = ptr.traverse(dirs[i]) backStack.push(nodePtr); ptr = nodePtr.next; childNodeKey = dirs[i]; } return { childNodeKey, nodePtr, backStack }; } _varTraverse(strPath, filters = {}) { let answers = null; let queries = this.tokenize(strPath) answers = this._queryByLevel(queries, filters); return answers } _objectReader(obj, path) { let value = obj, tmp = null, i = 0; let tokens = path.split("."); for (i = 0; i < tokens.length; i++) { tmp = value[tokens[i]] value = tmp; } return { key: tokens[i - 1], value: value } } _filter(filters, obj) { try { let keys = Object.keys(filters) for (let i = 0; i < keys.length; i++) if (!filters[keys[i]](obj[keys[i]])) return false return true } catch (err) { console.error(err) return false } } _queryByLevel(queries, filters = {}, ptr = this.memory, level = 0) { let query = queries[level]; let answers = {}, subAns = null; if (!queries[level]) return answers; let queryKey = ((query.indexOf(":") !== -1 || query.indexOf("@") !== -1) ? this._getQueryKey(query) : query).replace(new RegExp(' ', 'g'), ''); if (query[0] === "@") { if (!this._filter(filters, ptr.data)) return null if (/\{|\}/.test(queryKey)) { let props = queryKey.match(/{(.+)}/) if (props) { props[1].split(",").map(prop => { let objVal = this._objectReader(ptr.data, prop) answers[prop] = objVal.value }) } else answers = ptr.data; } else { if (!queryKey) throw "Please provide variable(s) for query or use read function instead." let objVal = this._objectReader(ptr.data, queryKey) answers[objVal.key] = ptr.data ? objVal.value : null; } } else if (query[0] === ":") { let keys = [...ptr.childKeys()] answers[queryKey] = {} keys.forEach(key => { let nextPtr = ptr.traverse(key).next let result = this._queryByLevel(queries, filters, nextPtr, level + 1, key) if (result) answers[queryKey][key] = Object.assign({}, result) }) } else answers[queryKey] = this._queryByLevel(queries, filters, ptr.traverse(queryKey).next, level + 1, queryKey) if (!Object.keys(answers[queryKey] || queryKey).length) delete answers[queryKey] return answers; } _getQueryKey(query) { return query.substring(1, query.length); } tokenize(strPath) { return this.trim(strPath).split("/"); } trim(strPath) { if (strPath[0] == "/") strPath = strPath.substr(1, strPath.length); if (strPath[strPath.length - 1] == "/") strPath = strPath.substr(0, strPath.length - 1); return strPath.trim(new RegExp(" ", 'g'), ""); } invalidate(immediate = false) { if (immediate) return this._writeToFile(); if (this.writeService !== null) clearTimeout(this.writeService); this.writeService = setTimeout(() => { this._writeToFile(); }, this.writeBufferTime); } _writeToFile() { fs.writeFileSync(this.path, JSON.stringify(this.memory)); } _start() { if (fs.existsSync(this.path)) this._resume(); else this.invalidate(true); } _resume() { this.memory = DataNode.fromObject(JSON.parse(fs.readFileSync(this.path))); } /* For ORM module */ _key() { return DataNode.key() } /* The below functions are deprecated. */ children(key) { Deprecator.deprecated("children(key)", "list(key)"); if (key === "/") return this.memory.childNodes; let { childNodeKey, nodePtr } = this.traverse(key); return nodePtr.cur.childNodes; } iterate(key, callback) { Deprecator.deprecated("iterate(key, callback)", "list(key, callback)"); if (key === "/") { if (!this.memory.has(key)) this.memory.childNodes[key] = new DataNode(key); this.memory.childNodes[key].iterate(callback); return this.invalidate(); } let { childNodeKey, nodePtr } = this.traverse(key); nodePtr.cur.get(childNodeKey).iterate(callback); this.invalidate(); } } module.exports = Zetabase;