@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
JavaScript
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