UNPKG

skypager-project

Version:
252 lines (206 loc) 7.03 kB
/* * @name Model * @description The Model helper is used to define patterns which can turn a certain * document or set of documents into an ActiveRecord like object which * can be queried or acted on, and which can have relationships with other * documents in the project. */ import Helper from '../helper' import isArray from 'lodash/isArray' import isFunction from 'lodash/isArray' import flatten from 'lodash/flatten' import testRule from 'skypager-util/lib/path-matcher' import castArray from 'lodash/castArray' import bindAll from 'lodash/bindAll' import { mixinPropertyUtils } from 'skypager-util/lib/properties' /** * A Model is a way of taking any document and treating it as a thing with attributes, * a thing with a status, relationships, context, etc. These things can be derived by * analyzing the contents, or through explicit annotations made by their author. * * For example, a javascript application has many screens. these screens have multiple components, * style sheets, sources of content, images and assets. By turning the file which defines this screens * as a model we can use these relationships in many ways. */ export class Model extends Helper { static isCacheable = false static dirname = __dirname // A Model returns a set of documents get documentInstances() { return this.chain .get('routeResults') .map(result => { const doc = result.doc const data = { ...result.meta, ...doc.get('data', {}), } doc.set('data', data) return doc }) .value() } get resultIds() { return this.get('routeResults').map(result => result.id) } buildId(fromDocumentId) { return this.idBuilder(fromDocumentId) } createContextRegistry(options = {}, filter) { return this.createRequireContext({ stringify: true, ...options, }, filter) } createModuleMap(options = {}, filter) { return this.chain.get('documentsMap') .pickBy((v,k) => !filter || filter.call(this, v,k)) .mapValues((v) => v.get(options.pathAttribute || 'baseRelativePath')) .mapValues(v => `${options.prepend || ''}${options.resolve ? this.project.resolve(v) : v}${options.append || ''}` ) .value() } createRequireContext(options = {}, filter) { const moduleMap = options.moduleMap || this.createModuleMap(options, filter) return this.project.select('require-context', { moduleMap, ...options, }) } get include() { return flatten([ ...(castArray(this.result('options.include', []))), ...(castArray(this.result('provider.include', []))), ]) } get exclude() { return flatten([ ...(castArray(this.result('options.exclude', []))), ...(castArray(this.result('provider.exclude', []))), ]) } get matchPattern() { return this.tryGet('matchPattern') } get routeResults() { const { include, exclude } = this if (this.matchPattern) { include.unshift(this.matchPattern) } exclude.push( ...this.project.chain.get('paths').pick('bundles','ast','logs','temp','library','cache','output') ) const filterBy = this.tryGet('filterBy') return this.chain .get('routes') .map(route => this.project.route(route)) .flatten() .filter((item, ...args) => { if (!item) { return false } if (exclude.length > 0 && exclude.find(rule => testRule(rule, item.file.path))) { return false } if (include.length > 0 && !include.find(rule => testRule(rule, item.file.path))) { return false } if (isFunction(filterBy)) { return filterBy.call(this, item, ...args) } return true }) .compact() .value() } get resultsMappedByRoute() { return this.routeResultsMap } get routeResultsMap() { return this.chain .get('routes') .keyBy(i => i) .mapValues((route) => this.project.route(route)) .value() } get idBuilder() { if (this.tryGet('idBuilder')) { return this.tryGet('idBuilder').bind(this) } return (r) => r.id || r } get idAttribute() { return this.tryGet('idAttribute', 'id') } get routes() { const cfg = this.tryResult('routes', []) if (!isArray(cfg)) { return flatten(Object.values(cfg)) } return cfg.map(o => typeof o === 'string' ? o : o.pattern || o.route) } get instanceMethods() { return this.chain.invoke('tryResult', 'instanceMethods', {}) .pickBy(v => typeof v === 'function') .value() } get collectionMethods() { return this.chain.invoke('tryResult', 'collectionMethods', {}) .pickBy(v => typeof v === 'function') .mapValues(v => v.bind(this)) .value() } get documentsMap() { return this.chain.get('documentInstances').keyBy(this.idBuilder).value() } initialize() { this.applyMixin(this.collectionMethods, this.context, this) this.lazy('instances', () => this.createInstances(this.instanceMethods)) this.hideGetter('instanceChain', () => this.chain.get('instances')) this.hideGetter('refreshed', () => this.refresh()) } refresh() { delete(this.instances) this.lazy('instances', () => this.createInstances(this.instanceMethods)) return this } createInstanceObjects(...args) { return this.createInstances(...args) } createInstances(instanceMethods = {}, stayChained = false) { const instanceOptions = this.tryResult('instanceOptions', ({ propUtils: true, lodash: true, chain: true, })) const selector = this.tryGet('selector', (this.tryGet('active') ? 'active-entity' : 'entity')) const propUtils = !!instanceOptions && instanceOptions.propUtils const mixinLodash = !!instanceOptions && instanceOptions.lodash const applyChain = !!instanceOptions && instanceOptions.chain const chain = this.chain .get('documentsMap', {}) .mapValues(doc => { const e = doc.select(selector, instanceMethods) const instance = bindAll(Object.assign(e, instanceMethods)) return propUtils ? mixinPropertyUtils(instance, mixinLodash, applyChain) : instance }) return stayChained ? chain : chain.value() } static attach(project, options = {}) { return Helper.attach(project, Model, { registryProp: 'models', lookupProp: 'model', registry: options.registry || Helper.createContextRegistry('models', { context: require.context('../models', true, /\.js$/), }), ...options, }) } } export default Model export function validate (providerModule = {}) { const report = { pass: true, errors: [], warnings: [] } if (!has(providerModule, 'routes')) { report.pass = false report.errors.push(`Model modules must export a routes object`) } return report }