From 915c91a1dfbb7dfd5267003cd542891cb348bcd8 Mon Sep 17 00:00:00 2001 From: Stefan Bittmann Date: Sat, 20 Nov 2021 15:51:46 +0100 Subject: [PATCH] many improvements --- lib/CodeInterpreter.js | 280 ++++++++---------- lib/Database.js | 6 +- lib/Query.js | 21 +- lib/Query/Filter.js | 15 +- lib/Query/Map.js | 15 +- lib/Query/Reduce.js | 13 +- lib/Query/Sort.js | 20 +- lib/Table.js | 22 +- lib/TableIndex.js | 17 -- lib/Thread.js | 82 ++--- lib/ThreadPool.js | 8 +- lib/Virtual/VirtualDatabase.js | 30 ++ lib/Virtual/VirtualTable.js | 39 +++ lib/VirtualMaschine.js | 8 +- lib/utils.js | 16 + package-lock.json | 84 +++--- package.json | 4 +- test/001_Database.js | 58 ++-- test/003_Table.js | 75 ++++- test/004_Query.js | 27 +- test/006_VirtualDatabase.js | 43 +++ test/007_VirtualTable.js | 60 ++++ test/008_Utils.js | 14 + test/{005_Server.js => Plugins/001_Server.js} | 6 +- .../001_TestQuerys.js} | 15 +- test/utils.js | 13 + 26 files changed, 647 insertions(+), 344 deletions(-) delete mode 100644 lib/TableIndex.js create mode 100644 lib/Virtual/VirtualDatabase.js create mode 100644 lib/Virtual/VirtualTable.js create mode 100644 test/006_VirtualDatabase.js create mode 100644 test/007_VirtualTable.js create mode 100644 test/008_Utils.js rename test/{005_Server.js => Plugins/001_Server.js} (96%) rename test/{006_TestQuerys.js => Querys/001_TestQuerys.js} (95%) create mode 100644 test/utils.js diff --git a/lib/CodeInterpreter.js b/lib/CodeInterpreter.js index 21b9503..5a17766 100644 --- a/lib/CodeInterpreter.js +++ b/lib/CodeInterpreter.js @@ -1,25 +1,24 @@ import * as acorn from "acorn"; import VirtualMaschine from "./VirtualMaschine.js"; -import { Readable, Transform } from "stream" -import { tableKey, indexKey, dbValues } from "./utils.js" +import { tableKey, indexKey, dbValues, getProp } from "./utils.js" let scripts = {}; export default class CodeInterpreter { #code; #parsed; - #validator; + #vm; #context; #db; #meta; - constructor(code, context, db, meta) { + constructor(code, context, db, meta, virtualDB) { this.#code = code.toString(); this.#context = context; this.#db = db; this.#meta = meta; - this.#validator = new VirtualMaschine(code, context); + this.#vm = new VirtualMaschine(code, context, virtualDB); } parse() { this.#parsed = acorn.parse("let func = " + this.#code, { @@ -30,71 +29,66 @@ export default class CodeInterpreter { } interprete() { - let stream; + let ret; try { - stream = this.interpreteIndex(); - } catch { - let query = { - interpreterRows: 0, - interpreterNeeded: true, - indexes: {} + ret = this.interpreteIndex(); + } catch (e) { + if(e instanceof ReferenceError) { + throw e; } + } + if(!ret) { let db = this.#db; let dbKey = (id) => tableKey(this.#meta.name, id); - let validator = this.#validator; + let vm = this.#vm; - let recordset = { + ret = { *[Symbol.iterator]() { let range = db.getRange({ start: dbKey(dbValues.LO) ,end: dbKey(dbValues.HI) }) - for(let row of range) { - yield row.value + try { + for(let row of range) { + this.query.interpreterRows++; + if(vm.run(row.value) === true) { + yield row.value + } + } + return; + } catch(e) { + this.error = e; } - } + }, + query: { + indexes: {}, + interpreterNeeded: true, + interpreterRows: 0, + }, } - - stream = Readable.from(recordset).pipe(new Transform({ - readableObjectMode: true, - writableObjectMode: true, - autoDestroy: true, - transform(row, encoding, cb) { - query.interpreterRows++; - if(validator.run(row) === true) { - this.push(row); - } - cb(); - } - })) - stream.query = query; } - - - return stream + return ret } interpreteIndex() { let db = this.#db; let dbKey = (id) => tableKey(this.#meta.name, id);; - let validator = this.#validator + let vm = this.#vm let code = this.parse(); - let key; let value; - - if(Array.isArray(code)) { if (code[0].$return) { let values = Object.keys(code[0].$return); key = values[0]; value = code[0].$return[key]; - } else { - throw new Error("Codeblocks not valid") - } + }/* else { + //Seems this never gets thrown actually .... + throw new ReferenceError("Codeblocks not valid") + }*/ } else { key = Object.keys(code)[0]; value = code[key]; @@ -102,32 +96,32 @@ export default class CodeInterpreter { let result = this.queryCode(key, value); result.query.interpreterRows = 0; - let ret = Readable.from(result.recordset).pipe( - new Transform({ - readableObjectMode: true, - writableObjectMode: true, - autoDestroy: true, - transform(id, _, cb) { - let val = db.get(dbKey(id)); - - if(result.query.interpreterNeeded) { - result.query.interpreterRows++; - if(validator.run(val) === true) { - this.push(val); + + let recordset = { + *[Symbol.iterator]() { + try { + for(let id of result.recordset) { + let val = db.get(dbKey(id)); + if(result.recordset.count) { + this.query.indexes[key] = result.recordset.count; } - cb(); - return; - } - if(result.recordset.count) { - result.query.indexes[key] = result.recordset.count; + if(this.query.interpreterNeeded) { + this.query.interpreterRows++; + if(vm.run(val) === true) { + yield val; + continue; + } + } + yield val; } - this.push(val); - cb(); + } catch(e) { + this.error = e } - }) - ); - ret.query = result.query; - return ret; + }, + query: result.query + } + + return recordset; } queryCode(key, value) { @@ -144,47 +138,6 @@ export default class CodeInterpreter { } } } - if (key === "$and") { - let recordsetObj = new Map(); - let query = { - interpreterNeeded: false, - indexes: {} - } - - let noIndexesCount = 0 - - for(let i = 0; i < value.length; i++) { - let innerKey = Object.keys(value[i])[0]; - let innerValue = value[i][innerKey]; - - try { - let result = this.queryCode(innerKey, innerValue) - query.indexes[innerKey] = 0; - if(i - noIndexesCount === 0) { - for(let id of result.recordset) { - query.indexes[innerKey]++ - recordsetObj.set(id, 1) - } - continue; - } - for(let id of result.recordset) { - query.indexes[innerKey]++ - if(recordsetObj.has(id)) { - let val = recordsetObj.get(id)++ - recordsetObj.set(id, val) - } - } - } catch { - noIndexesCount++; - query.interpreterNeeded = true; - } - } - let recordset = Array.from(recordsetObj).filter((row) => { return row[1] == value.length - noIndexesCount; }).map(row => row[0]) - return { - recordset, - query - } - } if (key === "$or") { let recordset = new Set(); let indexes = { @@ -205,12 +158,51 @@ export default class CodeInterpreter { return { recordset: [... recordset], query: { + indexes: indexes, interpreterNeeded: false, - indexes: indexes } } } - throw new Error("Unkown Query Function") + //$and + let recordsetObj = new Map(); + let query = { + indexes: {}, + interpreterNeeded: false, + } + + let noIndexesCount = 0 + + for(let i = 0; i < value.length; i++) { + let innerKey = Object.keys(value[i])[0]; + let innerValue = value[i][innerKey]; + + try { + let result = this.queryCode(innerKey, innerValue) + query.indexes[innerKey] = 0; + if(i - noIndexesCount === 0) { + for(let id of result.recordset) { + query.indexes[innerKey]++ + recordsetObj.set(id, 1) + } + continue; + } + for(let id of result.recordset) { + query.indexes[innerKey]++ + if(recordsetObj.has(id)) { + let val = recordsetObj.get(id)++ + recordsetObj.set(id, val) + } + } + } catch { + noIndexesCount++; + query.interpreterNeeded = true; + } + } + let recordset = Array.from(recordsetObj).filter((row) => { return row[1] == value.length - noIndexesCount; }).map(row => row[0]); + return { + recordset, + query + } } queryIndex(key, value) { @@ -263,7 +255,6 @@ export default class CodeInterpreter { count: 0 } } - if(conditionKey === "$lte" || conditionKey === "$lt") { return { *[Symbol.iterator]() { @@ -280,8 +271,6 @@ export default class CodeInterpreter { count: 0 } } - - throw new Error(`Search value could not be a object / Unknown search type ${conditionKey}`) } return { @@ -299,18 +288,19 @@ export default class CodeInterpreter { count: 0 } } - throw new Error("IDX" + key + " not avaiable") + throw new Error("IDX " + key + " not avaiable") } } function interpreteStart(code, context) { - let name = code.body[0].declarations[0].init.params[0].name; + let expression = code.body[0].declarations[0].init; if (["ArrowFunctionExpression", "FunctionExpression"].includes(expression.type)) { + let name = code.body[0].declarations[0].init.params[0].name; let r = interpreteNode(expression.body, name, context); return r } - throw new Error("Not a function"); + throw new ReferenceError("Not a function"); } @@ -332,7 +322,6 @@ function interpreteNode(node, rowName, context, options = {}) { case "LogicalExpression": return LogicalExpression(node, rowName, context, options); default: - console.log("Unknown node type " + node.type, node); throw new Error("Unknown node type " + node.type); } } @@ -354,38 +343,24 @@ function ReturnStatement(node, rowName, context) { function BinaryExpression(node, rowName, context) { let left = interpreteNode(node.left, rowName, context); let right = interpreteNode(node.right, rowName, context); - if (["==", "==="].includes(node.operator)) { - let result = {}; - result[left] = right; - - return result; - } - if (["!==", "!="].includes(node.operator)) { - let result = {}; - result[left] = { $not: right }; - return result; + let result = {}; + switch(node.operator) { + case "==": + case "===": + result[left] = right; break; + case "!=": + case "!==": + result[left] = { $not: right }; break; + case ">=": + result[left] = { $gte: right }; break; + case "<=": + result[left] = { $lte: right }; break; + case ">": + result[left] = { $gt: right }; break; + case "<": + result[left] = { $lt: right }; break; } - if ([">="].includes(node.operator)) { - let result = {}; - result[left] = { $gte: right }; - return result; - } - if (["<="].includes(node.operator)) { - let result = {}; - result[left] = { $lte: right }; - return result; - } - if ([">"].includes(node.operator)) { - let result = {}; - result[left] = { $gt: right }; - return result; - } - if (["<"].includes(node.operator)) { - let result = {}; - result[left] = { $lt: right }; - return result; - } - throw new Error("Unkown operator:" + node.operator); + return result } function MemberExpression(node, rowName, context, { replace = true } = {}) { @@ -408,6 +383,9 @@ function MemberExpression(node, rowName, context, { replace = true } = {}) { function Identifier(node, rowName, context, { replace = true } = {}) { if (replace) { + if(context[node.name] == undefined) { + throw new ReferenceError(node.name + " is not defined"); + } return context[node.name]; } return node.name; @@ -421,15 +399,13 @@ function LogicalExpression(node, rowName, context) { let left = interpreteNode(node.left, rowName, context); let right = interpreteNode(node.right, rowName, context); - if (node.operator === "&&") { - return { - $and: [left, right], - }; - } - if (node.operator === "||") { + if(node.operator === "||") { return { $or: [left, right], }; } - throw new Error("unknown operator: " + node.operator); + //&& + return { + $and: [left, right], + } } diff --git a/lib/Database.js b/lib/Database.js index ce8a40a..2c86319 100644 --- a/lib/Database.js +++ b/lib/Database.js @@ -1,7 +1,7 @@ import Table from "./Table.js"; import Backup from "./Backup.js"; import Events from "./Events.js" -import { open } from 'lmdb-store' +import { open } from 'lmdb' import fs from "fs/promises"; import { fileURLToPath } from 'url'; import path from "path" @@ -48,7 +48,7 @@ export default class Database extends Events { return this._db.get("meta"); } - #loadedTables; + #loadedTables = {}; table(name) { if(!name) { throw new Error("no tableName provided"); @@ -165,8 +165,6 @@ export default class Database extends Events { for(let i = 0; i < this.#plugins.length; i++) { await this.#plugins[i].start(this); } - - this.#loadedTables = {}; } get backup() { diff --git a/lib/Query.js b/lib/Query.js index f08bd26..7d9e960 100644 --- a/lib/Query.js +++ b/lib/Query.js @@ -29,6 +29,9 @@ export default class Query extends Readable { this.#stream.on("data", (data) => { this.push(data); }) + this.#stream.on("error", (err) => { + this.destroy(err); + }) this.#stream.on("end", () => { this.push(null) }) @@ -55,22 +58,28 @@ export default class Query extends Readable { return this.#stream.query; } - async then(cb) { + async then(result, error) { let recordSet = []; recordSet.getQuery = () => { return this.#stream.query; } - for await (let row of this) { - recordSet.push(row); - } + try { + for await (let row of this) { + recordSet.push(row); + } + } catch (e) { + error(e) + return; + } + if(this.#reduced) { - cb(recordSet[0]) + result(recordSet[0]) } - cb(recordSet); + result(recordSet); } } \ No newline at end of file diff --git a/lib/Query/Filter.js b/lib/Query/Filter.js index 5bdc9cd..1d167ef 100644 --- a/lib/Query/Filter.js +++ b/lib/Query/Filter.js @@ -4,15 +4,20 @@ import VirtualMaschine from "../VirtualMaschine.js"; export default class Filter extends Transform { #vm; - constructor(query, context) { + constructor(query, context, virtualDB) { super({readableObjectMode: true, writableObjectMode: true}); - this.#vm = new VirtualMaschine(query, context) + this.#vm = new VirtualMaschine(query, context, virtualDB) } _transform(data, _, cb) { - let result = this.#vm.run(data); - if(result) { - this.push(data) + try{ + let result = this.#vm.run(data); + if(result) { + this.push(data) + } + } + catch(e) { + this.destroy(e) } cb(); diff --git a/lib/Query/Map.js b/lib/Query/Map.js index e01a2f4..283fa91 100644 --- a/lib/Query/Map.js +++ b/lib/Query/Map.js @@ -5,15 +5,20 @@ export default class Map extends Transform { #vm; #index = 0; - constructor(query, context) { + constructor(query, context, virtualDB) { super({readableObjectMode: true, writableObjectMode: true}); - this.#vm = new VirtualMaschine(query, context) + this.#vm = new VirtualMaschine(query, context, virtualDB) } _transform(data, _, cb) { - this.push(this.#vm.run(data, this.#index)) - this.#index++; - cb(); + try { + this.push(this.#vm.run(data, this.#index)) + this.#index++; + cb(); + } + catch(e) { + this.destroy(e) + } } _flush(cb) { diff --git a/lib/Query/Reduce.js b/lib/Query/Reduce.js index 7744449..e5f470f 100644 --- a/lib/Query/Reduce.js +++ b/lib/Query/Reduce.js @@ -5,16 +5,21 @@ export default class Reduce extends Transform { #vm #val; - constructor(query, context, initVal) { + constructor(query, context, initVal, virtualDB) { super({readableObjectMode: true, writableObjectMode: true}); - this.#vm = new VirtualMaschine(query, context); + this.#vm = new VirtualMaschine(query, context, virtualDB); this.#val = initVal; } _transform(data, _, cb) { - this.#val = this.#vm.run(this.#val, data) - cb(); + try { + this.#val = this.#vm.run(this.#val, data) + cb(); + } catch(e) { + this.destroy(e) + } + } _flush(cb) { diff --git a/lib/Query/Sort.js b/lib/Query/Sort.js index a8d4a70..d7096b8 100644 --- a/lib/Query/Sort.js +++ b/lib/Query/Sort.js @@ -6,9 +6,9 @@ export default class Sort extends Transform { #array = []; - constructor(query, context) { + constructor(query, context, virtualDB) { super({readableObjectMode: true, writableObjectMode: true}); - this.#vm = new VirtualMaschine(query, context); + this.#vm = new VirtualMaschine(query, context, virtualDB); } _transform(data, _, cb) { @@ -17,12 +17,16 @@ export default class Sort extends Transform { } _flush(cb) { - let sortedArray = this.#array.sort((a, b) => this.#vm.run(a, b)); - let length = sortedArray.length; - for (let i = 0; i < length; i++) { - this.push(sortedArray[i]); + try { + let sortedArray = this.#array.sort((a, b) => this.#vm.run(a, b)); + let length = sortedArray.length; + for (let i = 0; i < length; i++) { + this.push(sortedArray[i]); + } + this.push(null); + cb(); + } catch(e) { + this.destroy(e) } - this.push(null); - cb(); } } \ No newline at end of file diff --git a/lib/Table.js b/lib/Table.js index fdffec2..bc959e6 100644 --- a/lib/Table.js +++ b/lib/Table.js @@ -1,5 +1,5 @@ import { nanoid } from "nanoid/async"; -import * as utils from "./utils.js" +import { tableKey, indexKey } from "./utils.js" import Events from "./Events.js" import Query from "./Query.js" @@ -29,19 +29,19 @@ export default class Table extends Events { return this.#meta; } - #databaseKey(id) { - return utils.tableKey(this.#meta.name, id) + #tableKey(id) { + return tableKey(this.#meta.name, id) } #indexKey(indexName, value, id) { - return utils.indexKey(this.#meta.name, indexName, value, id) + return indexKey(this.#meta.name, indexName, value, id) } async get(id) { let before = await this.emitBefore("get", {id}) - let data = await this.#db.get(this.#databaseKey(id)); + let data = await this.#db.get(this.#tableKey(id)); let after = await this.emitAfter("get", {id, data}) - return data || null; + return data; } async ensureIndex(name) { @@ -81,7 +81,7 @@ export default class Table extends Events { dbname: this.#dbname, cache: this.#cache, }); - + let result = null; for await (let row of query) { query.destroy(); @@ -121,7 +121,7 @@ export default class Table extends Events { if(data._id) { hadOld = true; - old = this.#db.get(this.#databaseKey(data._id)); + old = this.#db.get(this.#tableKey(data._id)); } for(let prop in this.meta.indexes) { @@ -143,7 +143,7 @@ export default class Table extends Events { } } - this.#db.put(this.#databaseKey(id), data) + this.#db.put(this.#tableKey(id), data) return id; }) let after = await this.emitAfter("save", {id, data: {_id, ...data}}) @@ -154,7 +154,7 @@ export default class Table extends Events { let before = await this.emitBefore("remove", {id}) let old; await this.#db.transactionAsync(() => { - old = this.#db.get(this.#databaseKey(id)); + old = this.#db.get(this.#tableKey(id)); if(old) { for(let prop in this.meta.indexes) { let val = old[prop]; @@ -163,7 +163,7 @@ export default class Table extends Events { } } } - this.#db.remove(this.#databaseKey(id)); + this.#db.remove(this.#tableKey(id)); }) let after = await this.emitAfter("remove", { id, data: old }) } diff --git a/lib/TableIndex.js b/lib/TableIndex.js deleted file mode 100644 index e4710d9..0000000 --- a/lib/TableIndex.js +++ /dev/null @@ -1,17 +0,0 @@ - - -function getProp(obj, prop) { - if (!obj) { - return; - } - let path = prop.split("."); - while (path.length > 0) { - let actProp = path.shift(); - if (obj[actProp] !== undefined) { - obj = obj[actProp]; - } else { - return; - } - } - return obj; -} \ No newline at end of file diff --git a/lib/Thread.js b/lib/Thread.js index d901042..f2b70dd 100644 --- a/lib/Thread.js +++ b/lib/Thread.js @@ -1,54 +1,69 @@ import { expose } from "threads/worker" import { Observable } from "observable-fns" -import CodeInterpreter from "./CodeInterpreter.js" -import { open } from 'lmdb-store' + +import { open } from 'lmdb' import path from "path" +import { Readable } from "stream" +import CodeInterpreter from "./CodeInterpreter.js" import * as utils from "./utils.js" import Filter from "./Query/Filter.js" import Sort from "./Query/Sort.js" import aMap from "./Query/Map.js" import Reduce from "./Query/Reduce.js" +import VirtualDatabase from "./Virtual/VirtualDatabase.js" let db; +let virtualDB; expose({ query({query, context, meta, path, dbname, cache, actions = []}) { startDb(path, dbname, cache); return new Observable((observer) => { - let cp = new CodeInterpreter(query, context, db, meta); - - let result = cp.interprete(); - for(let i = 0; i < actions.length; i++) { - switch (actions[i].type) { - case utils.actionTypes.FILTER: - result = result.pipe(new Filter(actions[i].data.query, actions[i].data.context)) - break; - case utils.actionTypes.SORT: - result = result.pipe(new Sort(actions[i].data.query, actions[i].data.context)) - break; - case utils.actionTypes.MAP: - result = result.pipe(new aMap(actions[i].data.query, actions[i].data.context)) - break; - case utils.actionTypes.REDUCE: - result = result.pipe(new Reduce(actions[i].data.query, actions[i].data.context, actions[i].data.initVal)) - break; + try { + let cp = new CodeInterpreter(query, context, db, meta, virtualDB); + let cpInt = cp.interprete(); + let result = Readable.from(cpInt); + result.query = cpInt.query; + + for(let i = 0; i < actions.length; i++) { + switch (actions[i].type) { + case utils.actionTypes.FILTER: + result = result.pipe(new Filter(actions[i].data.query, actions[i].data.context, virtualDB)) + break; + case utils.actionTypes.SORT: + result = result.pipe(new Sort(actions[i].data.query, actions[i].data.context, virtualDB)) + break; + case utils.actionTypes.MAP: + result = result.pipe(new aMap(actions[i].data.query, actions[i].data.context, virtualDB)) + break; + case utils.actionTypes.REDUCE: + result = result.pipe(new Reduce(actions[i].data.query, actions[i].data.context, actions[i].data.initVal, virtualDB)) + break; + } } - } + result.on("error", (e) => { + observer.next({error: { message: e.message }}); + }) + result.on("data", (data) => { + if(data === Object(data)) { + observer.next({data: {... data}, query: result.query}); + return; + } + observer.next({data: data, query: result.query}); + + }); + result.on("end", () => { + if(cpInt.error) { + observer.next({error: { message: cpInt.error.message }}); + } + observer.complete(); + }) - result.on("data", (data) => { - if(data === Object(data)) { - observer.next({data: {... data}, query: result.query}); - return; + return () => { + result.close(); } - observer.next({data: data, query: result.query}); - - }); - result.on("end", () => { - observer.complete(); - }) - - return () => { - result.close(); + } catch (e) { + observer.next({error: { message: e.message }}); } }) }, @@ -90,5 +105,6 @@ function startDb(dbpath, name, cache) { path: path.join(dbpath, name), cache: cache }); + virtualDB = new VirtualDatabase(db); } } \ No newline at end of file diff --git a/lib/ThreadPool.js b/lib/ThreadPool.js index f5ebfe4..ebd6933 100644 --- a/lib/ThreadPool.js +++ b/lib/ThreadPool.js @@ -8,7 +8,7 @@ export default class ThreadPool { async start() { if(!this.#started) { this.#pool = Pool(() => spawn(new Worker("./Thread.js")), { - //concurrency: 20, + concurrency: 20, }) this.#started = true; } @@ -21,9 +21,11 @@ export default class ThreadPool { this.#pool.queue((worker) => { let s = worker.query(options); - let subscribtion = s.subscribe(val => { - //console.log(val) + if(val.error) { + stream.destroy(new Error(val.error.message)); + subscribtion.unsubscribe(); + } stream.push(val.data); stream.query = val.query; }) diff --git a/lib/Virtual/VirtualDatabase.js b/lib/Virtual/VirtualDatabase.js new file mode 100644 index 0000000..57ac6a4 --- /dev/null +++ b/lib/Virtual/VirtualDatabase.js @@ -0,0 +1,30 @@ +import VirtualTable from "./VirtualTable.js" + +export default class VirtualDatabase { + _db + + constructor(db) { + this._db = db; + } + + get meta() { + return this._db.get("meta"); + } + + #loadedTables = {}; + table(name) { + if(!name) { + throw new Error("no tableName provided"); + } + + if (this.#loadedTables[name] === undefined) { + let tableMeta = this.meta.tables[name]; + this.#loadedTables[name] = new VirtualTable( + tableMeta, + this._db, + this + ); + } + return this.#loadedTables[name]; + } +} \ No newline at end of file diff --git a/lib/Virtual/VirtualTable.js b/lib/Virtual/VirtualTable.js new file mode 100644 index 0000000..9995e82 --- /dev/null +++ b/lib/Virtual/VirtualTable.js @@ -0,0 +1,39 @@ +import { tableKey } from "../utils.js" +import CodeInterpreter from "../CodeInterpreter.js" + +export default class VirtualTable { + #db + #meta + #virtualDB + constructor(meta, db, virtualDB) { + + this.#meta = meta + this.#db = db; + this.#virtualDB = virtualDB; + } + + get meta() { + return this.#meta; + } + + get(id) { + return this.#db.get(this.#tableKey(id)); + } + + #tableKey(id) { + return tableKey(this.#meta.name, id) + } + + find(query, context = {}) { + let cp = new CodeInterpreter(query, context, this.#db, this.#meta, this.#virtualDB); + for(let row of cp.interprete()) { + return row; + } + return null; + } + + filter(query, context = {}) { + let cp = new CodeInterpreter(query, context, this.#db, this.#meta, this.#virtualDB); + return Array.from(cp.interprete()); + } +} \ No newline at end of file diff --git a/lib/VirtualMaschine.js b/lib/VirtualMaschine.js index 9e847c2..504ef85 100644 --- a/lib/VirtualMaschine.js +++ b/lib/VirtualMaschine.js @@ -6,7 +6,7 @@ export default class VirtualMaschine { #code #context #vm - constructor(code, context = {}) { + constructor(code, context = {}, virtualDB) { this.#code = code; this.#context = context; @@ -14,10 +14,12 @@ export default class VirtualMaschine { scripts[this.#code]= new VMScript("module.exports = (" + this.#code + ")"), scripts[this.#code].compile(); } - this.#vm = new NodeVM({ + let vm = new NodeVM({ sandbox: this.#context, eval: false, - }).run(scripts[this.#code]); + }); + vm.freeze(virtualDB, 'db'); + this.#vm = vm.run(scripts[this.#code]); } run(...args) { diff --git a/lib/utils.js b/lib/utils.js index 3197168..b8dd5b9 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -16,4 +16,20 @@ export let actionTypes = { SORT: 1, MAP: 2, REDUCE: 3, +} + +export function getProp(obj, prop) { + if (!obj) { + return; + } + let path = prop.split("."); + while (path.length > 0) { + let actProp = path.shift(); + if (obj[actProp] !== undefined) { + obj = obj[actProp]; + } else { + return; + } + } + return obj; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1694773..e3427cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "acorn": "^8.6.0", "axios": "^0.24.0", "fastify": "^3.24.0", - "lmdb-store": "^1.6.13", + "lmdb": "^2.0.0", "minimist": "^1.2.5", "nanoid": "^3.1.30", "threads": "^1.7.0", @@ -25,7 +25,7 @@ "codecov": "^3.8.3", "faker": "^5.5.3", "mocha": "^9.1.3", - "np": "^7.5.0", + "np": "^7.6.0", "prettier": "^2.4.1" }, "engines": { @@ -3374,19 +3374,17 @@ "node": ">=6" } }, - "node_modules/lmdb-store": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/lmdb-store/-/lmdb-store-1.6.13.tgz", - "integrity": "sha512-WJPNfzSZXD6anGFdIEK/wq/HzAU5kfi7+LSUSzQ2Qo9uV9REeIYPGqWX+FKl/QCb6qK4ie1D4f44aEvvv7M7rw==", + "node_modules/lmdb": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.0.0.tgz", + "integrity": "sha512-Ab8dyyrvY6gHCVN4mzPSna6s1MeNZ1PM0GGkDNcIkH+bkGzYzBYdYdFaCDtLT0I401UV1jF4KxfWoKeqebMSCg==", "hasInstallScript": true, "dependencies": { + "msgpackr": "^1.5.0", "nan": "^2.14.2", "node-gyp-build": "^4.2.3", - "ordered-binary": "^1.0.0", - "weak-lru-cache": "^1.0.0" - }, - "optionalDependencies": { - "msgpackr": "^1.4.7" + "ordered-binary": "^1.1.0", + "weak-lru-cache": "^1.1.0" } }, "node_modules/locate-path": { @@ -3801,18 +3799,17 @@ "dev": true }, "node_modules/msgpackr": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.4.7.tgz", - "integrity": "sha512-bhC8Ed1au3L3oHaR/fe4lk4w7PLGFcWQ5XY/Tk9N6tzDRz8YndjCG68TD8zcvYZoxNtw767eF/7VpaTpU9kf9w==", - "optional": true, + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.5.1.tgz", + "integrity": "sha512-I1CXFG8BYYSeIhtDlHpUVMsdDiyvP9JAh1d9QoBnkPx3ETPeH/1lR14hweM9GETs09wCWlaOyhtXxIc9boxAAA==", "optionalDependencies": { "msgpackr-extract": "^1.0.14" } }, "node_modules/msgpackr-extract": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-1.0.14.tgz", - "integrity": "sha512-t8neMf53jNZRF+f0H9VvEUVvtjGZ21odSBRmFfjZiyxr9lKYY0mpY3kSWZAIc7YWXtCZGOvDQVx2oqcgGiRBrw==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-1.0.15.tgz", + "integrity": "sha512-vgJgzFva0/4/mt84wXf3CRCDPHKqiqk5t7/kVSjk/V2IvwSjoStHhxyq/b2+VrWcch3sxiNQOJEWXgI86Fm7AQ==", "hasInstallScript": true, "optional": true, "dependencies": { @@ -3943,9 +3940,9 @@ } }, "node_modules/np": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/np/-/np-7.5.0.tgz", - "integrity": "sha512-CdpgqtO6JpDKJjQ2gueY0jnbz6APWA9wFXSwPv5bXg4seSBibHqQ8JyWxYlS8YRfVbpeDtj582wcAWTlfy5qNA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/np/-/np-7.6.0.tgz", + "integrity": "sha512-WWGZtfNkE6MEkI7LE8NtG7poTqzTHj/tssBzcPnBAdMVPXkXDtX2wk0ptrj8YZ3u4TFmGSqioSohdud86aJxSg==", "dev": true, "dependencies": { "@samverschueren/stream-to-observable": "^0.3.1", @@ -5861,9 +5858,9 @@ } }, "node_modules/weak-lru-cache": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.1.2.tgz", - "integrity": "sha512-Bi5ae8Bev3YulgtLTafpmHmvl3vGbanRkv+qqA2AX8c3qj/MUdvSuaHq7ukDYBcMDINIaRPTPEkXSNCqqWivuA==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.1.3.tgz", + "integrity": "sha512-5LDIv+sr6uzT94Hhcq7Qv7gt3jxol4iMWUqOgJSLYbB5oO7bTSMqIBtKsytm8N2BufYOdJw86/qu+SDfbo/wKQ==" }, "node_modules/which": { "version": "2.0.2", @@ -8730,16 +8727,16 @@ } } }, - "lmdb-store": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/lmdb-store/-/lmdb-store-1.6.13.tgz", - "integrity": "sha512-WJPNfzSZXD6anGFdIEK/wq/HzAU5kfi7+LSUSzQ2Qo9uV9REeIYPGqWX+FKl/QCb6qK4ie1D4f44aEvvv7M7rw==", + "lmdb": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.0.0.tgz", + "integrity": "sha512-Ab8dyyrvY6gHCVN4mzPSna6s1MeNZ1PM0GGkDNcIkH+bkGzYzBYdYdFaCDtLT0I401UV1jF4KxfWoKeqebMSCg==", "requires": { - "msgpackr": "^1.4.7", + "msgpackr": "^1.5.0", "nan": "^2.14.2", "node-gyp-build": "^4.2.3", - "ordered-binary": "^1.0.0", - "weak-lru-cache": "^1.0.0" + "ordered-binary": "^1.1.0", + "weak-lru-cache": "^1.1.0" } }, "locate-path": { @@ -9045,18 +9042,17 @@ "dev": true }, "msgpackr": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.4.7.tgz", - "integrity": "sha512-bhC8Ed1au3L3oHaR/fe4lk4w7PLGFcWQ5XY/Tk9N6tzDRz8YndjCG68TD8zcvYZoxNtw767eF/7VpaTpU9kf9w==", - "optional": true, + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.5.1.tgz", + "integrity": "sha512-I1CXFG8BYYSeIhtDlHpUVMsdDiyvP9JAh1d9QoBnkPx3ETPeH/1lR14hweM9GETs09wCWlaOyhtXxIc9boxAAA==", "requires": { "msgpackr-extract": "^1.0.14" } }, "msgpackr-extract": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-1.0.14.tgz", - "integrity": "sha512-t8neMf53jNZRF+f0H9VvEUVvtjGZ21odSBRmFfjZiyxr9lKYY0mpY3kSWZAIc7YWXtCZGOvDQVx2oqcgGiRBrw==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-1.0.15.tgz", + "integrity": "sha512-vgJgzFva0/4/mt84wXf3CRCDPHKqiqk5t7/kVSjk/V2IvwSjoStHhxyq/b2+VrWcch3sxiNQOJEWXgI86Fm7AQ==", "optional": true, "requires": { "nan": "^2.14.2", @@ -9152,9 +9148,9 @@ "dev": true }, "np": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/np/-/np-7.5.0.tgz", - "integrity": "sha512-CdpgqtO6JpDKJjQ2gueY0jnbz6APWA9wFXSwPv5bXg4seSBibHqQ8JyWxYlS8YRfVbpeDtj582wcAWTlfy5qNA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/np/-/np-7.6.0.tgz", + "integrity": "sha512-WWGZtfNkE6MEkI7LE8NtG7poTqzTHj/tssBzcPnBAdMVPXkXDtX2wk0ptrj8YZ3u4TFmGSqioSohdud86aJxSg==", "dev": true, "requires": { "@samverschueren/stream-to-observable": "^0.3.1", @@ -10589,9 +10585,9 @@ "integrity": "sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng==" }, "weak-lru-cache": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.1.2.tgz", - "integrity": "sha512-Bi5ae8Bev3YulgtLTafpmHmvl3vGbanRkv+qqA2AX8c3qj/MUdvSuaHq7ukDYBcMDINIaRPTPEkXSNCqqWivuA==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.1.3.tgz", + "integrity": "sha512-5LDIv+sr6uzT94Hhcq7Qv7gt3jxol4iMWUqOgJSLYbB5oO7bTSMqIBtKsytm8N2BufYOdJw86/qu+SDfbo/wKQ==" }, "which": { "version": "2.0.2", diff --git a/package.json b/package.json index 7663fe8..45017ae 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "acorn": "^8.6.0", "axios": "^0.24.0", "fastify": "^3.24.0", - "lmdb-store": "^1.6.13", + "lmdb": "^2.0.0", "minimist": "^1.2.5", "nanoid": "^3.1.30", "threads": "^1.7.0", @@ -44,7 +44,7 @@ "codecov": "^3.8.3", "faker": "^5.5.3", "mocha": "^9.1.3", - "np": "^7.5.0", + "np": "^7.6.0", "prettier": "^2.4.1" }, "engines": { diff --git a/test/001_Database.js b/test/001_Database.js index 32d6022..6654807 100644 --- a/test/001_Database.js +++ b/test/001_Database.js @@ -2,7 +2,10 @@ import Database from "../lib/Database.js"; import Table from "../lib/Table.js"; import Backup from "../lib/Backup.js"; import fs from "fs/promises"; +import path from "path"; +import { fileURLToPath } from 'url'; import Server from "../plugins/Server.js"; +import {shouldThrow } from "./utils.js" import { expect } from "chai"; let dbname = "databaseTest"; @@ -58,9 +61,20 @@ describe("Database (class)", () => { }); }); describe(".meta", () => { - it("should return correct meta info", async () => { - let meta = db.meta - expect(meta.name).to.be.equal(dbname); + let __dirname = path.dirname(fileURLToPath(import.meta.url)); + let pack; + before(async () => { + pack = JSON.parse(await fs.readFile(path.join(__dirname, "..", "package.json"), "utf8")); + }) + + it("should return correct name", async () => { + expect(db.meta.name).to.be.equal(dbname); + }); + it("should return correct version", async () => { + expect(db.meta.version).to.be.equal(pack.version); + }); + it("should have table object", async () => { + expect(db.meta.tables).to.be.a("object"); }); }); @@ -83,39 +97,19 @@ describe("Database (class)", () => { before(async () => { await db.delete(); }); - it("should throw on get meta data", (next) => { - let thrown = false; - try { + it("should throw on get meta data", async () => { + await shouldThrow(() => { let meta = db.meta; - } catch { - thrown = true - } - - if(thrown) { - next(); - return - } - throw Error('should error'); + }) }); - it("should throw on table", (next) => { - let thrown = false - try { + it("should throw on table", async () => { + await shouldThrow(() => { let table = db.table("test"); - } catch { - thrown = true - } - - if(thrown) { - next(); - return - } - throw Error('should error'); + }) }); - it("should delete database files", (next) => { - fs.readdir(`./storage/${dbname}`).then(() => { - throw Error('should error'); - }).catch(() => { - next(); + it("should delete database files", async () => { + await shouldThrow(async () => { + await fs.readdir(`./storage/${dbname}`) }) }); }); diff --git a/test/003_Table.js b/test/003_Table.js index a4b0056..3d603f9 100644 --- a/test/003_Table.js +++ b/test/003_Table.js @@ -1,5 +1,6 @@ import Database from "../lib/Database.js"; import fs from "fs/promises"; +import { shouldThrow } from "./utils.js" import { expect } from "chai"; let dbname = "tableTest"; @@ -69,7 +70,7 @@ describe("Table (class)", () => { expect(updateId).to.be.equal(id); }); }); - describe(".get(value)", () => { + describe(".get(id)", () => { it("should return object by id", async () => { let id = "testId123456789" await db.table(tableName).save({_id: id, test: true}) @@ -83,10 +84,10 @@ describe("Table (class)", () => { let id = await db.table(tableName).save({test: true}); await db.table(tableName).remove(id); let val = await db.table(tableName).get(id); - expect(val).to.be.equal(null); + expect(val).to.be.equal(undefined); }); }); - describe(".find(id)", () => { + describe(".find(query, context)", () => { it("should find inserted object", async () => { let name = "Max Mustermann"; let id = await db.table(tableName).save({name: name}); @@ -166,6 +167,72 @@ describe("Table (class)", () => { expect(result).to.be.a("object"); expect(result.notIndexed).to.be.equal(notIndexed); expect(result._id).to.be.equal(id); - }) + }); + + it("should work with Object Property", async () => { + let name = "Max Musterfrau" + let id = await db.table(tableName).save({name}); + + let result = await db.table(tableName).find((l) => { + return l.name === obj.test; + }, { + obj: { + test: name + } + }); + + expect(result).to.be.a("object"); + expect(result.name).to.be.equal(name); + expect(result._id).to.be.equal(id); + }); + + it("should work on complex Query without index usage", async () => { + let id = await db.table(tableName).save({complexQuery: true}); + + let result = await db.table(tableName).find((l) => { + if(l.complexQuery === true) { + return true + } + return false + }); + + expect(result).to.be.a("object"); + expect(result.complexQuery).to.be.equal(true); + expect(result._id).to.be.equal(id); + }); + + + it("should throw Error on unkown var", async () => { + await shouldThrow(async () => { + let result = await db.table(tableName).find((l) => { return name }) + }) + }); + + it("should throw Error on interpreter Error", async () => { + await shouldThrow(async () => { + let result = await db.table(tableName).find(`(l) => { return l.nested.nested.not.there}`) + }) + }); + + it("should throw Error on interpreter Error with index", async () => { + await db.table(tableName).ensureIndex("throwTest"); + await db.table(tableName).save({throwTest: true}); + + await shouldThrow(async () => { + let result = await db.table(tableName).find(`(l) => { return l.throwTest === true && l.nested.nested.not.there === false}`); + }) + }); + + it("should throw Error on not correct JS-code", async () => { + await shouldThrow(async () => { + let result = await db.table(tableName).find(`(l => { return l.name }`) + }) + }); + + it("should throw Error on Query not a function", async () => { + await shouldThrow(async () => { + let result = await db.table(tableName).find(`{test: true}`) + }) + }); }); }); diff --git a/test/004_Query.js b/test/004_Query.js index 05064b1..e8557a0 100644 --- a/test/004_Query.js +++ b/test/004_Query.js @@ -1,6 +1,7 @@ import Database from "../lib/Database.js"; import Query from "../lib/Query.js"; import fs from "fs/promises"; +import { shouldThrow } from "./utils.js" import { expect } from "chai"; let dbname = "queryTest"; @@ -16,11 +17,15 @@ describe("Query (class)", () => { } catch {} db = await new Database(dbname); + await db.table("test").ensureIndex("test") + let p = [] for(let i = 0; i < tableLength; i++) { p.push(db.table("test").save({index: i, test: true})); } + p.push(db.table("test").save({index: -1, test: false})); await Promise.all(p); + }); after(async () => { await db.delete(); @@ -59,6 +64,11 @@ describe("Query (class)", () => { it("should have same length", async () => { expect(dbSort.length).to.be.equal(arraySort.length); }); + it("should throw on incorret query", async () => { + await shouldThrow(async () => { + await db.table("test").filter((row) => { return row.index <= 10 }).sort(`(a,b) => { return name }`); + }) ; + }); }); describe(".map(mapFunction)", () => { @@ -85,6 +95,11 @@ describe("Query (class)", () => { it("should have same length", async () => { expect(dbMap.length).to.be.equal(arrayMap.length); }); + it("should throw on incorret query", async () => { + await shouldThrow(async () => { + await db.table("test").filter((row) => { return row.test === false }).map(`(row) => { return name }`); + }); + }); }); describe(".reduce(reduceFunction)", () => { @@ -106,6 +121,11 @@ describe("Query (class)", () => { it("should reduce", async () => { expect(arrayReduce).to.be.equal(dbReduce); }); + it("should throw on incorret query", async () => { + await shouldThrow(async () => { + await db.table("test").filter((row) => { return row.test === false }).reduce(`(row) => { return name }`); + }); + }); }); describe(".filter(filterFunction)", () => { @@ -124,10 +144,15 @@ describe("Query (class)", () => { let reduced = db.table("test").filter(filter).filter(filter2) expect(reduced).to.be.instanceOf(Query); }); - it("should reduce", async () => { + it("should filter", async () => { for(let i = 0; i < tableLength; i++) { expect(dbFilter[i]).to.be.eql(arrayFilter[i]); } }); + it("should throw on incorret query", async () => { + await shouldThrow(async () => { + await db.table("test").filter((row) => { return row.test === false }).filter(`(row) => { return name }`); + }); + }); }); }); diff --git a/test/006_VirtualDatabase.js b/test/006_VirtualDatabase.js new file mode 100644 index 0000000..62e73bc --- /dev/null +++ b/test/006_VirtualDatabase.js @@ -0,0 +1,43 @@ +import Database from "../lib/Database.js"; +import fs from "fs/promises"; +import { expect } from "chai"; +import { shouldThrow } from "./utils.js" + +let dbname = "vmTest"; + +describe("VirtualDatabase (class)", () => { + let db; + before(async () => { + try { + await fs.rm(`./storage/${dbname}`, { + recursive: true, + }); + } catch {} + + db = await new Database(dbname); + await db.table("test").ensureIndex("test"); + await db.table("test").save({test: true}); + }); + after(async () => { + await db.delete(); + }); + + describe(".meta", () => { + it("should return same as non virtual Database", async () => { + let meta = await db.table("test").filter((row) => { return row.test === true }).map(`(row) => { return db.meta }`); + expect(meta[0]).to.be.eql(db.meta); + }); + }) + + describe(".table(name)", () => { + it("should not exit virtual space", async () => { + let table = await db.table("test").filter((row) => { return row.test === true }).map(`(row) => { return db.table("test") }`); + expect(table[0]).to.be.eql({}); + }); + it("should not allow empty name", async () => { + await shouldThrow(async () => { + await db.table("test").filter((row) => { return row.test === true }).map(`(row) => { return db.table() }`); + }) + }); + }) +}); \ No newline at end of file diff --git a/test/007_VirtualTable.js b/test/007_VirtualTable.js new file mode 100644 index 0000000..cb4cda0 --- /dev/null +++ b/test/007_VirtualTable.js @@ -0,0 +1,60 @@ +import Database from "../lib/Database.js"; +import fs from "fs/promises"; +import { expect } from "chai"; + +let dbname = "vmTableTest"; + +describe("VirtualTable (class)", () => { + let db; + let entry = {_id: "testId", test: true} + before(async () => { + try { + await fs.rm(`./storage/${dbname}`, { + recursive: true, + }); + } catch {} + + db = await new Database(dbname); + await db.table("test").ensureIndex("test"); + await db.table("test").save(entry); + }); + after(async () => { + await db.delete(); + }); + + describe(".meta", () => { + it("should return same as no virtual Table", async () => { + let meta = await db.table("test").filter((row) => { return row.test === true }).map(`(row) => { return db.table("test").meta }`); + expect(meta[0]).to.be.eql(db.table("test").meta); + }); + }) + + describe(".get(id)", () => { + it("should return entry by id", async () => { + let get = await db.table("test").filter((row) => { return row.test === true }).map(`(row) => { return db.table("test").get(id) }`, {id: entry._id}); + expect(get[0]).to.be.eql(entry); + }); + it("should return undefined if id not avaiable", async () => { + let get = await db.table("test").filter((row) => { return row.test === true }).map(`(row) => { return db.table("test").get(id) }`, {id: 50}); + expect(get[0]).to.be.equal(undefined) + }); + }) + + describe(".find(query, context)", () => { + it("should return entry by query", async () => { + let get = await db.table("test").filter((row) => { return row.test === true }).map(`(row) => { return db.table("test").find((row) => row.test === true) }`); + expect(get[0]).to.be.eql(entry); + }); + it("should return undefined if id not avaiable", async () => { + let get = await db.table("test").filter((row) => { return row.test === true }).map(`(row) => { return db.table("test").find((row) => row.test === test, {test}) }`, {test: 50}); + expect(get[0]).to.be.equal(undefined) + }); + }) + + describe(".filter(query, context)", () => { + it("should return array from query", async () => { + let get = await db.table("test").filter((row) => { return row.test === true }).map(`(row) => { return db.table("test").filter((row) => row.test === true) }`); + expect(get[0][0]).to.be.eql(entry); + }); + }) +}); \ No newline at end of file diff --git a/test/008_Utils.js b/test/008_Utils.js new file mode 100644 index 0000000..e4647ab --- /dev/null +++ b/test/008_Utils.js @@ -0,0 +1,14 @@ +import { getProp } from "../lib/utils.js"; +import { expect } from "chai"; + +describe("Utils (module)", () => { + it("should return undefined if no Object provided", () => { + expect(getProp(undefined, "test")).to.be.equal(undefined) + }) + it("should return undefined if Object Property not there", () => { + expect(getProp({}, "test")).to.be.equal(undefined) + }) + it("should return Property on neseted input", () => { + expect(getProp({test: {value: true}}, "test.value")).to.be.equal(true) + }) +}) \ No newline at end of file diff --git a/test/005_Server.js b/test/Plugins/001_Server.js similarity index 96% rename from test/005_Server.js rename to test/Plugins/001_Server.js index 214eaad..168f3cb 100644 --- a/test/005_Server.js +++ b/test/Plugins/001_Server.js @@ -1,5 +1,5 @@ -import Database from "../lib/Database.js"; -import Server from "../plugins/Server.js"; +import Database from "../../lib/Database.js"; +import Server from "../../plugins/Server.js"; import fs from "fs/promises"; import axios from "axios" import { expect } from "chai"; @@ -63,7 +63,7 @@ describe("Server (plugin)", () => { await axios.delete(`http://localhost:${port}/table/persons/${id}`) - expect(await db.table("persons").get(id)).to.be.equal(null); + expect(await db.table("persons").get(id)).to.be.equal(undefined); }); }); diff --git a/test/006_TestQuerys.js b/test/Querys/001_TestQuerys.js similarity index 95% rename from test/006_TestQuerys.js rename to test/Querys/001_TestQuerys.js index effe216..8368544 100644 --- a/test/006_TestQuerys.js +++ b/test/Querys/001_TestQuerys.js @@ -1,4 +1,4 @@ -import Database from "../lib/Database.js"; +import Database from "../../lib/Database.js"; import fs from "fs/promises"; import { expect } from "chai"; @@ -27,6 +27,7 @@ describe("Querys", () => { await db.table("persons").save({_id: 3, name: "Maxi Mustermann", birthdate: "1976-02-01T00:00:00.000Z", activeSince: 2003}); await db.table("persons").save({_id: 4,name: "Max Mustermann", birthdate: "1976-02-01T00:00:00.000Z", activeSince: 2004}); + await db.table("persons").save({_id: 5,name: "Norman Mustermann", birthdate: "1976-02-01T00:00:00.000Z", activeSince: 2005}); maxMustermann = 4 }); @@ -126,8 +127,8 @@ describe("Querys", () => { }, { name }) }); - it("should find one entry", async () => { - expect(result.length).to.be.equal(1); + it("should find 2 entries", async () => { + expect(result.length).to.be.equal(2); }); it("should find entry with correct data", async () => { @@ -152,8 +153,8 @@ describe("Querys", () => { }, { activeSince }) }); - it("should find one entry", async () => { - expect(result.length).to.be.equal(2); + it("should find 3 entry", async () => { + expect(result.length).to.be.equal(3); }); it("should find entry with correct data", async () => { @@ -179,8 +180,8 @@ describe("Querys", () => { }, { activeSince }) }); - it("should find one entry", async () => { - expect(result.length).to.be.equal(3); + it("should find 4 entries", async () => { + expect(result.length).to.be.equal(4); }); it("should find entry with correct data", async () => { diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..233606d --- /dev/null +++ b/test/utils.js @@ -0,0 +1,13 @@ +export async function shouldThrow(fn) { + let thrown = false; + try { + await fn(); + } catch { + thrown = true + } + + if(thrown) { + return + } + throw Error('should Throw'); +} \ No newline at end of file