diff --git a/libs/executor.cjs b/libs/executor.cjs index 380ebb6..0b67312 100644 --- a/libs/executor.cjs +++ b/libs/executor.cjs @@ -3,13 +3,41 @@ const executeQuery = require("./executeQuery.cjs"); const arrayToObject = require("./arrayToObject.cjs"); const dataPath = require("./dataPath.cjs"); +/** + * Using waterfall structure for better readability + * @param {*} query + * @returns + */ +function waterfallParser(query) { + if (query.name && Array.isArray(query.computes)) { + const result = {}; + const current = result; + + query.computes.reduce((acc, item) => { + if (typeof item === "string") { + acc[item] = {}; + return acc[item]; + } else if (typeof item === "object" && item !== null) { + Object.assign(acc, item); + return acc; + } + }, current); + + return { + [query.name]: result, + }; + } else { + return query; + } +} + /** * Options for query execution. * * @typedef {object} QueryOptions * @property {object} methods - Methods configuration. * @property {object} config - Configuration settings. - * @property {string} dataUrl - Data url path. + * @property {string} [dataUrl] - Data url path. */ function postProcessing(options) { @@ -29,12 +57,45 @@ function postProcessing(options) { * @returns {Promise} A promise that resolves to the result object. */ const rq = (query, options) => { - return executeQuery(query, null, options) + const parseQuery = waterfallParser(query); + return executeQuery(parseQuery, null, options) .then(postProcessing(options)) .catch((error) => console.error("rql Error:", error.message)); }; +class RqExtender { + constructor() { + this.methods = {}; + } + compute(payload) { + return rq(payload, { + methods: this.methods, + config: (param) => this.getMethodsMap()[param], + }); + } + + getMethodsMap() { + const prototype = Object.getPrototypeOf(this); + const methodNames = Object.getOwnPropertyNames(prototype).filter( + (name) => + typeof this[name] === "function" && + name !== "constructor" && + name !== "compute" && + name !== "getMethodsMap", + ); + const methodsMap = {}; + for (const name of methodNames) { + methodsMap[name] = this[name].bind(this); + } + return methodsMap; + } +} + global.rq = rq; exports.rq = rq; module.exports = rq; module.exports.default = rq; + +global.RqExtender = RqExtender; +exports.RqExtender = RqExtender; +module.exports.RqExtender = RqExtender; diff --git a/package.json b/package.json index cbbe1a2..f7a2d3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "requrse", - "version": "0.3.4", + "version": "0.4.0", "type": "module", "description": "Lightweight driven query language", "main": "libs/executor.cjs", @@ -17,7 +17,9 @@ "test:array-string": "node test/arrayString.test.mjs", "test:duplicate": "node test/duplicateField.test.mjs", "test:array-index": "node test/arrayIndex.test.mjs", - "test": "npm run test:starwars && npm run test:mongoose && npm run test:mongoose-lookup && npm run test:redis && npm run test:requrse && npm run test:inflight-request-cancelation && npm run test:fantasy && npm run test:basic && npm run test:array && npm run test:array-string && npm run test:duplicate && npm run test:array-index", + "test:waterfall": "node test/waterfall.test.mjs", + "test:class": "node test/class.test.mjs", + "test": "npm run test:starwars && npm run test:mongoose && npm run test:mongoose-lookup && npm run test:redis && npm run test:requrse && npm run test:inflight-request-cancelation && npm run test:fantasy && npm run test:basic && npm run test:array && npm run test:array-string && npm run test:duplicate && npm run test:array-index && npm run test:waterfall && npm run test:class", "test:coverage": "c8 --exclude samples --exclude test npm run test", "test:lcov": "c8 --exclude samples --exclude test --reporter lcov npm run test", "coverage": "coveralls < coverage/lcov.info" diff --git a/test/class.test.mjs b/test/class.test.mjs new file mode 100644 index 0000000..2f2223a --- /dev/null +++ b/test/class.test.mjs @@ -0,0 +1,67 @@ +import assert from "assert"; +import { RqExtender } from "../libs/executor.cjs"; +import { test } from "./fixture/test.mjs"; + +await test("Classes config", () => { + class TestConfig extends RqExtender { + constructor() { + super(); + this.methods = { + area: "area", + occupation: "occupation", + person: "getPerson", + birth: "birth", + }; + } + area() { + return { city: "NY" }; + } + occupation() { + return { type: "CT0" }; + } + birth() { + return { year: "1981" }; + } + getPerson(name) { + return { name, age: 42 }; + } + } + + const test = new TestConfig(); + + const payload = { + Test: { + test: { + person: { + $params: { name: "Foo" }, + name: 1, + age: 1, + birth: { + year: 1, + area: { + city: 1, + }, + }, + occupation: { + type: 1, + }, + }, + }, + }, + }; + + test.compute(payload).then((result) => { + assert.deepEqual(result, { + Test: { + test: { + person: { + name: "Foo", + age: 42, + birth: { year: "1981", area: { city: "NY" } }, + occupation: { type: "CT0" }, + }, + }, + }, + }); + }); +}); diff --git a/test/waterfall.test.mjs b/test/waterfall.test.mjs new file mode 100644 index 0000000..ec7d5b4 --- /dev/null +++ b/test/waterfall.test.mjs @@ -0,0 +1,32 @@ +import assert from "assert"; +import rq from "../libs/executor.cjs"; +import { test } from "./fixture/test.mjs"; + +await test("Foo Bar", () => { + rq( + { + name: "Test", + computes: [ + "test", // placeholder + "foo", + { + bar: "*", + }, + ], + }, + { + methods: { + bar() { + return "another"; + }, + foo() { + return { + bar: "foobar", + }; + }, + }, + }, + ).then((result) => { + assert.deepEqual(result, { Test: { test: { foo: { bar: "another" } } } }); + }); +});