"use strict"; const Promise = require("bluebird"); const objectFromEntries = require("object.fromentries"); const util = require("util"); function resolveFromDataSource(dataContext, dataSource, id) { if (dataContext[dataSource] != null) { return dataContext[dataSource].load(id); } else { throw new Error(`Specified data source '${dataSource}' does not exist`); } } function withProperty(dataSource, id, property) { return withData(dataSource, id, (value) => { return value[property]; }); } function withData(dataSource, id, callback) { return function (args, context) { let { data } = context; return Promise.try(() => { return resolveFromDataSource(data, dataSource, id); }).then((value) => { if (value != null) { // FIXME: Inject 'properties' return callback(value, args, context); } else { // QUESTION: Why do we disallow this again? throw new Error(`Got a null-ish value from data source '${dataSource}' for ID '${util.inspect(id)}'`); } }); }; } function withDynamicHandler(handler, object) { return function (args, context) { let { data } = context; function resolveProperty(property, fromObject = object) { if (typeof fromObject[property] !== "function") { throw new Error(`FIXME: Properties can apparently be non-functions`); } return fromObject[property](args, context); } let extendedContext = { ... context, resolveProperty: resolveProperty, resolveProperties: function (properties, fromObject) { return Promise.map(properties, (property) => { return Promise.try(() => { return resolveProperty(property, fromObject); }).then((value) => { return [ property, value ]; }); }).then((entries) => { return objectFromEntries(entries); }); }, resolvePropertyPath: function (propertyPath, fromObject) { let initialObject = fromObject ?? object; return Promise.reduce(propertyPath, (last, property) => { if (last != null) { return resolveProperty(property, last); } }, initialObject); }, resolveDataSource: function (dataSource, id) { return resolveFromDataSource(data, dataSource, id); } }; return handler(args, extendedContext); }; } let ID = Symbol("ID"); let LocalProperties = Symbol("LocalProperties"); let Dynamic = Symbol("Dynamic"); module.exports = { ID: ID, Dynamic: Dynamic, LocalProperties: LocalProperties, createDataObject: function createDataObject(mappings) { let object = {}; if (mappings[LocalProperties] != null) { Object.assign(object, mappings[LocalProperties]); } if (mappings[Dynamic] != null) { for (let [property, handler] of Object.entries(mappings[Dynamic])) { object[property] = withDynamicHandler(handler, object); } } for (let [dataSource, items] of Object.entries(mappings)) { if (items[ID] != null) { let id = items[ID]; for (let [property, source] of Object.entries(items)) { if (object[property] == null) { if (typeof source === "string") { object[property] = withProperty(dataSource, id, source); } else if (typeof source === "function") { object[property] = withData(dataSource, id, source); } /* FIXME: else */ } else { throw new Error(`Handler already defined for property '${property}' - maybe you specified it twice for different data sources?`); } } } else { throw new Error(`No object ID was provided for the '${dataSource}' data source`); } } return object; } };