UNPKG

@uprtcl/cortex

Version:

A new way to build web-applications, by recognizing the patterns that generic objects implement

264 lines (246 loc) 9.16 kB
import 'reflect-metadata'; import { injectable } from 'inversify'; import { MicroModule } from '@uprtcl/micro-orchestrator'; import { GraphQlSchemaModule } from '@uprtcl/graphql'; import gql from 'graphql-tag'; import merge from 'lodash-es/merge'; /** * A pattern is a behaviour that a certain kind of object implements */ class Pattern { constructor(behaviourCreators) { this.behaviourCreators = behaviourCreators; this.behaviours = []; } } function recognizeEntity(object) { const entity = object; return (typeof object === 'object' && entity.id !== undefined && typeof entity.id === 'string' && entity.object !== null); } /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } let PatternRecognizer = class PatternRecognizer { /** * Recognizes which registered patterns match the given object * @param object */ recognize(object) { if (!object) { throw new Error('The given object was not defined'); } const recognizedPatterns = this.patterns .filter((pattern) => pattern.recognize(object)) .map((p) => ({ ...p })); return recognizedPatterns; } /** * Recognizes all behaviours for the given object, flattening the array * * @param object object for which to recognize the behaviour */ recognizeBehaviours(object) { const patterns = this.recognize(object); const behaviours = patterns.map((p) => p.behaviours); return [].concat(...behaviours); } /** * Gets all the behaviours that the pattern with the given type implements * * @param type type of the pattern of which to return the behaviours */ getTypeBehaviours(type) { const patterns = this.patterns.filter((pattern) => pattern.type === type); const behaviours = patterns.map((p) => p.behaviours); return [].concat(...behaviours); } /** * Recognizes the type of the given entity * * @param entity to recognize the type for * @throws error if no pattern recognized the given entity * @throws error if two patterns with different types recognized the given entity */ recognizeType(entity) { const patterns = this.recognize(entity); const types = patterns.map((p) => p.type).filter((t) => !!t); if (types.length === 0) { throw new Error(`No entity found to recognize object ${JSON.stringify(entity)}`); } const abmiguousError = types.length > 1 && !types.every((t) => types[0]); if (abmiguousError) { throw new Error(`Ambiguous error recognizing entity: ${parent.toString()}. These two types recognized the object ${types.toString()}`); } return types[0]; } }; PatternRecognizer = __decorate([ injectable() ], PatternRecognizer); const CortexBindings = { Recognizer: 'pattern-recognizer', Pattern: 'pattern', }; const cortexSchema = gql ` scalar JSON interface Entity { id: ID! _context: EntityContext! } type EntityContext { patterns: Patterns! object: JSON! } type Patterns { links: [Entity!] @discover } `; const cortexResolvers = { Entity: { id(parent) { return parent.id ? parent.id : parent; }, _context(parent) { return parent; }, async __resolveType(parent, { container }, info) { const recognizer = container.get(CortexBindings.Recognizer); return recognizer.recognizeType(entityFromGraphQlObject(parent)); }, }, EntityContext: { object(parent) { return entityFromGraphQlObject(parent).object; }, async patterns(parent, args, { container }, info) { const entity = entityFromGraphQlObject(parent); const isGraphQlField = (key) => key !== 'accessControl' && Object.keys(info.returnType.ofType._fields).includes(key); const recognizer = container.get(CortexBindings.Recognizer); const behaviours = recognizer.recognizeBehaviours(entity); const applyedPatterns = behaviours.map((pattern) => { const applyedPattern = {}; for (const key of Object.keys(pattern)) { if (isGraphQlField(key)) { applyedPattern[key] = pattern[key](entity); } } return applyedPattern; }); const accPatterns = {}; merge(accPatterns, ...applyedPatterns, { __entity: entity }); return substituteFunction(accPatterns); }, }, }; function entityFromGraphQlObject(parent) { if (parent.id && parent.object && typeof parent.casID === 'string') return parent; const id = parent.id; let object = {}; for (const key of Object.keys(parent)) { if (key !== 'id') object[key] = parent[key]; } return { id, object, }; } function substituteFunction(object) { for (const key of Object.keys(object)) { try { if (Array.isArray(object[key])) object[key] = object[key].map((o) => substituteFunction(o)); else if (typeof object[key] === 'object') object[key] = substituteFunction(object[key]); else if (typeof object[key] === 'function') { const f = object[key]; object[key] = () => f; } } catch (e) { } } return object; } class CortexModule extends MicroModule { async onLoad(container) { let recognizer = undefined; container .bind(CortexModule.bindings.Recognizer) .toDynamicValue((ctx) => { if (recognizer) return recognizer; recognizer = new PatternRecognizer(); const patterns = ctx.container.getAll(CortexModule.bindings.Pattern); recognizer.patterns = patterns; return recognizer; }); } get submodules() { return [new GraphQlSchemaModule(cortexSchema, cortexResolvers)]; } } CortexModule.id = 'cortex-module'; CortexModule.bindings = CortexBindings; /** * This is a convenience MicroModule class that depends on `CortexModule` and registers the given set of patterns * to be used by Cortex * * Example usage: * * ```ts * class EveesModule extends MicroModule { * * ... * * get submodules() { * return [new PatternsModule([new PerspectivePattern()])]; * } * } * ``` */ class PatternsModule extends MicroModule { constructor(patterns) { super(); this.patterns = patterns; this.dependencies = [CortexModule.id]; } async onLoad(container) { // Initialize all the patterns for (const pattern of this.patterns) { const dynamicCreator = (ctx) => { const behaviours = pattern.behaviourCreators.map((prop) => ctx.container.resolve(prop)); return { ...pattern, recognize: pattern.recognize, behaviours, }; }; container.bind(CortexModule.bindings.Pattern).toDynamicValue(dynamicCreator); const type = pattern.type; if (type) { container.bind(type).toDynamicValue(dynamicCreator); } } } } export { CortexModule, Pattern, PatternRecognizer, PatternsModule, entityFromGraphQlObject, recognizeEntity }; //# sourceMappingURL=uprtcl-cortex.es5.js.map