diff --git a/src/engine-default-fact-decorators.js b/src/engine-default-fact-decorators.js new file mode 100644 index 0000000..a97dd9a --- /dev/null +++ b/src/engine-default-fact-decorators.js @@ -0,0 +1,64 @@ +import { JSONPath } from "jsonpath-plus"; +import FactDecorator from "./fact-decorator"; + +export const PathDecorator = new FactDecorator("path", (params, almanac, next) => { + if (Object.prototype.hasOwnProperty.call(params, 'path')) { + const path = params.path + const paramCopy = Object.assign({}, params) + delete paramCopy.path + return Promise.resolve(next(paramCopy, almanac)).then(factValue => { + if (factValue != null && typeof factValue === 'object') { + const pathValue = JSONPath({ json: factValue, path, wrap: false }) + debug('condition::evaluate extracting object', { property: path, received: pathValue }) + return pathValue + } else { + debug('condition::evaluate could not compute object path of non-object', { path, factValue, type: typeof factValue }) + return factValue + } + }) + + } else { + return next(params, almanac); + } +}) + +export const KeysOfDecorator = new FactDecorator("keysOf", (params, almanac, next) => { + const n = next(params, almanac) + if (n != null) { + if (Object.prototype.hasOwnProperty.call(n, 'keys') && typeof n.keys === 'function') { + return Array.from(n.keys()) + } + return Object.keys(n) + } + return n; +}) + +export const ValuesOfDecorator = new FactDecorator("valuesOf", (params, almanac, next) => { + const n = next(params,almanac) + if (n != null) { + if (Object.prototype.hasOwnProperty(n, 'values') && typeof n.values === 'function') { + return Array.from(n.values()) + } + return Object.values(n) + } + return n +}) + +export const SizeOfDecorator = new FactDecorator("sizeOf", (params, almanac, next) => { + const n = next(params, almanac) + if (n != null) { + if (Object.prototype.hasOwnProperty(n, 'length')) { + return n.length + } else if (Object.prototype.hasOwnProperty(n, 'size') && typeof n.size === 'function') { + return n.size() + } + } + return 1 +}) + +/** + * Options (arg 3) are merged onto fact options and override + * This allows us to do things like create a noCache version of a fact + * noCache:name for instance would access the name fact without hitting the cache + */ +export const NoCacheDecorator = new FactDecorator("noCache", (params, almanac, next) => next(params, almanac), { cache: false }) \ No newline at end of file diff --git a/src/fact-decorator.js b/src/fact-decorator.js new file mode 100644 index 0000000..ad19b5f --- /dev/null +++ b/src/fact-decorator.js @@ -0,0 +1,39 @@ +import Fact from "./fact"; + +class FactDecorator { + + /** + * + * @param {string} id + * @param {function} cb Function that computes the new fact value by invoking a 3rd argument + * that is a function to produce the next value + * @param {object} options options to override the defaults from the decorated fact + */ + constructor(id, cb, options) { + this.id = id + this.cb = cb + if (options) { + this.options = options + } else { + this.options = {} + } + } + + /** + * + * @param {Fact} fact to decorate + * @returns {Fact} the decorated fact + */ + decorate(fact) { + const next = fact.calculate.bind(fact); + return new Fact( + `${this.id}:${fact.id}`, + (params, almanac) => this.cb(params, almanac, next), + Object.assign({}, this.options, fact.options) + ) + } + +} + + +export default FactDecorator \ No newline at end of file