@allgemein/schema-api
Version:
Library for schema api
579 lines • 23.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultNamespacedRegistry = void 0;
/**
* Handler for metadata
*/
const lodash_1 = require("lodash");
const Constants_1 = require("./../Constants");
const IEntityRef_1 = require("../../api/IEntityRef");
const IClassRef_1 = require("../../api/IClassRef");
const MetadataRegistry_1 = require("./MetadataRegistry");
const DefaultPropertyRef_1 = require("./DefaultPropertyRef");
const ClassRef_1 = require("../ClassRef");
const DefaultEntityRef_1 = require("./DefaultEntityRef");
const JsonSchema7_1 = require("../json-schema/JsonSchema7");
const base_1 = require("@allgemein/base");
const JsonSchema_1 = require("../json-schema/JsonSchema");
const SchemaRef_1 = require("../SchemaRef");
const AbstractRegistry_1 = require("./AbstractRegistry");
/**
* Registry which handles by default class, schema, embeddalbes and properties, which are not handled by a specialized registry.
*
* This is a space for entities and their relations, which are grouped by a give "namespace" context.
*
* How entities are passed to the registry:
*
* 1. by annotations at startup (event from metadata registry)
* - properties are appended first by annotation then entities follow
* 2. by call for getEntityRefFor
* 3. append during runtime by event fired through metadata registry
* 4. append through reload method
*
*/
class DefaultNamespacedRegistry extends AbstractRegistry_1.AbstractRegistry {
constructor(namespace, options) {
super(namespace, (0, lodash_1.defaults)(options || {}, { detectUnannotatedProperties: true }));
this.drained = false;
}
/**
* Initialize events for metadata changes on runtime
*/
prepare() {
// apply listener
this.drainAlreadyAdded();
MetadataRegistry_1.MetadataRegistry.$().on(Constants_1.C_EVENT_ADD, this.onMetadataAdd.bind(this));
MetadataRegistry_1.MetadataRegistry.$().on(Constants_1.C_EVENT_REMOVE, this.onMetadataRemove.bind(this));
MetadataRegistry_1.MetadataRegistry.$().on(Constants_1.C_EVENT_UPDATE, this.onMetadataUpdate.bind(this));
}
ready(timeout) {
return super.ready(timeout).then(() => {
if (this.drained) {
return Promise.resolve(this.drained);
}
else {
return new Promise((resolve, reject) => {
const t = setTimeout(() => {
reject();
}, timeout ? timeout : 10000);
MetadataRegistry_1.MetadataRegistry.$().once(Constants_1.C_EVENT_DRAIN_FINISHED + this.namespace, () => {
resolve(true);
});
});
}
});
}
/**
* Apply already added entries to the metadata registry can be added to this registry
*/
drainAlreadyAdded() {
this.drained = false;
const alreadyFired = MetadataRegistry_1.MetadataRegistry.$()
.getMetadata()
.filter(x => {
const v = Object.getOwnPropertyDescriptor(x, Constants_1.K_TRIGGERED);
return v && v.value;
});
for (const event of alreadyFired) {
const cloneEvent = (0, lodash_1.cloneDeep)(event);
try {
this.onAdd(cloneEvent.metaType, cloneEvent);
}
catch (e) {
}
}
this.drained = true;
MetadataRegistry_1.MetadataRegistry.$().emit([Constants_1.C_EVENT_DRAIN_FINISHED, this.namespace].join('_'), true);
}
async onMetadataAdd(context, options) {
await this.lock.acquire();
this.onAdd(context, options);
this.lock.release();
}
async onMetadataUpdate(context, options) {
if (!this.validNamespace(options)) {
return;
}
await this.lock.acquire();
this.onUpdate(context, options);
this.lock.release();
}
async onMetadataRemove(context, options) {
if (!this.validNamespace(options)) {
return;
}
await this.lock.acquire();
this.onRemove(context, options);
this.lock.release();
}
/**
* Check if object has the correct namespace for this registry handle
*
* @param options
*/
validNamespace(options) {
if (options.namespace) {
if (options.namespace !== this.namespace) {
// skip not my namespace
return false;
}
}
else {
// if namespace not present skipping
if (options.metaType !== Constants_1.METATYPE_PROPERTY) {
// only properties should be pass and check if entity already exists
const namespace = MetadataRegistry_1.MetadataRegistry.$().find(Constants_1.METATYPE_NAMESPACE, (x) => x.target === options.target);
if (namespace && namespace.attributes.namespace === this.namespace) {
options.namespace = namespace.attributes.namespace;
}
else if (this.namespace !== Constants_1.DEFAULT_NAMESPACE) {
// passing to default namespace if nothing
return false;
}
}
}
return true;
}
/**
* react on dynamically added context
*
* @param context
* @param entries
*/
onAdd(context, options) {
if (!this.validNamespace(options)) {
return;
}
if (context === Constants_1.METATYPE_PROPERTY) {
const sourceEntry = this.find(Constants_1.METATYPE_CLASS_REF, (ref) => ref.getClass() === options.target);
if (sourceEntry) {
const find = this.find(context, (c) => c.getClassRef().getClass() === options.target &&
c.name === options.propertyName);
if (!find) {
// update
this.create(context, options);
}
}
}
else if (context === Constants_1.METATYPE_ENTITY) {
const find = this.find(context, (c) => c.getClassRef().getClass() === options.target);
if (!find) {
const entityRef = this.create(context, options);
const properties = this.find(Constants_1.METATYPE_PROPERTY, (c) => c.getClassRef().getClass() === options.target);
if ((0, lodash_1.isEmpty)(properties)) {
// create properties only when non exists
this.createPropertiesForRef(entityRef.getClassRef());
}
}
}
else if (context === Constants_1.METATYPE_EMBEDDABLE) {
const find = this.find(Constants_1.METATYPE_CLASS_REF, (c) => c.getClass() === options.target);
if (!find) {
this.create(context, options);
}
else {
const refOptions = find.getOptions();
(0, lodash_1.defaults)(refOptions, options);
}
}
else if (context === Constants_1.METATYPE_SCHEMA) {
let find = this.getOrCreateSchemaRefByName(options);
if (options.target) {
let entityRef = this.find(Constants_1.METATYPE_ENTITY, (c) => c.getClass() === options.target);
if (find && entityRef) {
this.addSchemaToEntityRef(find, entityRef);
}
}
}
}
getOrCreateSchemaRefByName(options) {
let find = this.find(Constants_1.METATYPE_SCHEMA, (c) => c.name === options.name);
if (!find) {
find = this.createSchemaForOptions(options);
}
return find;
}
addSchemaToEntityRef(schemaRef, entityRef, options = {}) {
if ((0, lodash_1.isString)(schemaRef)) {
schemaRef = this.getOrCreateSchemaRefByName({
metaType: Constants_1.METATYPE_SCHEMA,
name: schemaRef,
namespace: this.namespace,
target: entityRef.getClass()
});
}
const name = schemaRef.name;
let entry = entityRef.getOptions(Constants_1.METATYPE_SCHEMA, []);
if (!entry.includes(name)) {
if (entry) {
if ((0, lodash_1.isArray)(entry)) {
entry.push(name);
}
else {
entry = [entry, name];
}
}
else {
entry = [name];
}
if ((0, lodash_1.get)(options, 'override', false)) {
if ((0, lodash_1.get)(options, 'onlyDefault', false)) {
(0, lodash_1.remove)(entry, x => x === base_1.C_DEFAULT);
}
else {
entry = [name];
}
}
entityRef.setOption(Constants_1.METATYPE_SCHEMA, (0, lodash_1.uniq)(entry));
}
}
/**
* react on dynamically removed context
*
* @param context
* @param entries
*/
onRemove(context, entries) {
if (context === Constants_1.METATYPE_ENTITY || context === Constants_1.METATYPE_CLASS_REF) {
const targets = entries.map(x => x.target);
this.getLookupRegistry().remove(Constants_1.METATYPE_CLASS_REF, (x) => targets.includes(x.target));
this.getLookupRegistry().remove(Constants_1.METATYPE_ENTITY, (x) => targets.includes(x.target));
this.getLookupRegistry().remove(Constants_1.METATYPE_PROPERTY, (x) => targets.includes(x.target));
}
else if (context === Constants_1.METATYPE_PROPERTY) {
const targets = entries.map(x => [x.target, x.propertyName]);
this.getLookupRegistry().remove(context, (x) => targets.includes([x.target, x.name]));
}
else if (context === Constants_1.METATYPE_SCHEMA) {
// TODO remove schema ref
// TODO remove schema options entries
}
}
/**
* react on dynamically update context
*
* @param context
* @param entries
*/
onUpdate(context, options) {
}
/**
* Return all registered schema references
*
* @param ref
* @return ISchemaRef[]
*/
getSchemaRefs(filter) {
return this.filter(Constants_1.METATYPE_SCHEMA, filter);
}
getSchemaRefsFor(ref) {
let lookup = [];
if ((0, IEntityRef_1.isEntityRef)(ref) || (0, IClassRef_1.isClassRef)(ref)) {
const schemas = ref.getOptions(Constants_1.METATYPE_SCHEMA, []);
for (const schema of schemas) {
let schemaRef = this.find(Constants_1.METATYPE_SCHEMA, (x) => x.name === schema);
if (!schemaRef) {
schemaRef = this.create(Constants_1.METATYPE_SCHEMA, { name: schema, target: ref.getClass() });
}
lookup.push(schemaRef);
}
lookup = this.getSchemaRefs((x) => schemas.includes(x.name));
}
else if ((0, lodash_1.isString)(ref)) {
let schemaRef = this.find(Constants_1.METATYPE_SCHEMA, (x) => x.name === ref);
if (!schemaRef) {
schemaRef = this.create(Constants_1.METATYPE_SCHEMA, { name: ref });
}
return schemaRef;
}
else {
throw new base_1.NotSupportedError('passed value is not supported');
}
return lookup ? (0, lodash_1.uniq)(lookup) : null;
}
/**
* TODO
*
* @param filter
*/
getEntities(filter) {
return this.getLookupRegistry().filter(Constants_1.METATYPE_ENTITY, filter);
}
/**
* Can get get entity ref for function.
*
* @param fn
*/
getEntityRefFor(fn, skipNsCheck = true) {
let lookup = fn;
if (!(0, lodash_1.isFunction)(fn) && !(0, lodash_1.isString)(fn) && (0, lodash_1.isObjectLike)(fn)) {
lookup = base_1.ClassUtils.getFunction(fn);
}
let lookupFn = (x) => x.getClassRef().getClass() === lookup;
if ((0, lodash_1.isString)(fn)) {
lookupFn = (x) => x.getClassRef().name === lookup || x.name === lookup;
}
const entityRefExists = this.find(Constants_1.METATYPE_ENTITY, lookupFn);
if (entityRefExists) {
return entityRefExists;
}
return this.create(Constants_1.METATYPE_ENTITY, {
target: lookup,
namespace: this.namespace,
skipNsCheck: skipNsCheck
});
}
/**
* Returns property by name for a given class or entity ref
*
* @param filter
*/
getPropertyRef(ref, name) {
return this.getPropertyRefs(ref).find(x => (0, lodash_1.snakeCase)(x.name) === (0, lodash_1.snakeCase)(name));
}
/**
* Returns all properties for given class or entity ref
*
* @param ref
*/
getPropertyRefs(ref) {
const clsRef = (0, IEntityRef_1.isEntityRef)(ref) ? ref.getClassRef() : ref;
// Return existing
const clsRefExists = this.find(Constants_1.METATYPE_CLASS_REF, (x) => x === clsRef);
let properties = [];
if (clsRefExists) {
properties = this.filter(Constants_1.METATYPE_PROPERTY, (x) => x.getClassRef() === clsRefExists);
}
else {
properties = this.createPropertiesForRef(clsRef);
}
return properties;
}
/**
* Create properties for class or entity ref.
*/
createPropertiesForRef(clsRef) {
const cls = clsRef.getClass(true);
const propOptions = [];
const metaPropOptions = MetadataRegistry_1.MetadataRegistry.$().getByContextAndTarget(Constants_1.METATYPE_PROPERTY, cls);
if (this.getOptions().detectUnannotatedProperties) {
const jsonSchema = JsonSchema_1.JsonSchema.serialize(cls, { appendTarget: true });
if ((0, JsonSchema7_1.hasClassPropertiesInDefinition)(clsRef.name, jsonSchema)) {
const properties = jsonSchema.definitions[clsRef.name].properties;
for (const k of (0, lodash_1.keys)(properties)) {
const property = properties[k];
const opts = {
metaType: Constants_1.METATYPE_PROPERTY,
namespace: clsRef.getNamespace(),
type: property.$target ? property.$target : property.type,
propertyName: k,
target: cls
};
if (property.default) {
opts.default = property.default;
}
const propMetadata = (0, lodash_1.remove)(metaPropOptions, x => x.propertyName === k);
if (!(0, lodash_1.isEmpty)(propMetadata)) {
for (const p of propMetadata) {
// clean namespace
const copy = (0, lodash_1.clone)(p);
delete copy.namespace;
(0, lodash_1.assign)(opts, copy);
}
}
propOptions.push(opts);
}
}
}
const propertyOptions = propOptions.concat(metaPropOptions)
// .filter(x => !clsRef.getRegistry().find(METATYPE_PROPERTY,
// (z: IPropertyRef) => z.name === x.propertyName && z.getClassRef().getClass() === x.target)
// )
// .filter(x => !this.processing.find((z: IPropertyOptions) => z.propertyName === x.propertyName && z.target === x.target))
.map(p => {
// change namespace of properties namespace
const copy = (0, lodash_1.clone)(p);
copy.namespace = clsRef.getNamespace();
copy.skipNsCheck = true;
return copy;
});
return propertyOptions.map(opts => clsRef.getRegistry().create(Constants_1.METATYPE_PROPERTY, opts));
}
/**
* Create default property reference
*
* @param options
*/
createPropertyForOptions(options) {
if ((0, lodash_1.keys)(options).length === 0) {
throw new Error('can\'t create property for empty options');
}
options.namespace = this.namespace;
const prop = new DefaultPropertyRef_1.DefaultPropertyRef(options);
return this.add(prop.metaType, prop);
}
/**
* Create default entity reference
*
* @param options
*/
createEntityForOptions(options) {
options.namespace = this.namespace;
if (!options.name) {
options.name = ClassRef_1.ClassRef.getClassName(options.target);
}
const entityRef = new DefaultEntityRef_1.DefaultEntityRef(options);
const retRef = this.add(entityRef.metaType, entityRef);
const metaSchemaOptionsForEntity = MetadataRegistry_1.MetadataRegistry.$()
.getByContextAndTarget(Constants_1.METATYPE_SCHEMA, entityRef.getClass());
if (metaSchemaOptionsForEntity.length > 0) {
for (const schemaOptions of metaSchemaOptionsForEntity) {
this.addSchemaToEntityRef(schemaOptions.name, entityRef);
}
}
return retRef;
}
/**
* Create default entity reference
*
* @param options
*/
createEmbeddableForOptions(options) {
// options.namespace = this.namespace;
// if (!options.name) {
// options.name = ClassRef.getClassName(options.target);
// }
options = (0, lodash_1.defaults)(options || {}, {});
const classRef = this.getClassRefFor(options.target, Constants_1.METATYPE_CLASS_REF);
classRef.setOptions(options);
return classRef;
}
/**
* Create default schema reference
*
* @param options
*/
createSchemaForOptions(options) {
options.namespace = this.namespace;
const schemaRef = new SchemaRef_1.SchemaRef(options);
return this.add(schemaRef.metaType, schemaRef);
}
/**
* Return metadata collected in the MetadataRegistry through annotation or explizit attached data.
*
* @param context
* @param target
* @return IAbstractOptions
*/
getMetadata(context, target, propertyName) {
let metadataOptionList = MetadataRegistry_1.MetadataRegistry.$()
.getByContextAndTarget(context, target, 'merge', propertyName);
let metadataOptions = {};
if (metadataOptionList.length > 1) {
metadataOptions = (0, lodash_1.assign)(metadataOptions, ...metadataOptionList.map(x => (0, lodash_1.cloneDeep)(x)));
}
else if (metadataOptionList.length === 1) {
metadataOptions = (0, lodash_1.cloneDeep)(metadataOptionList.shift());
}
if (propertyName) {
// remove namespace for properties
delete metadataOptions.namespace;
}
return metadataOptions;
}
getPropertyRefsFor(fn) {
const clsName = ClassRef_1.ClassRef.getClassName(fn);
const clsRef = ClassRef_1.ClassRef.get(clsName, this.namespace);
return this.getPropertyRefs(clsRef);
}
create(context, options) {
const skipNsCheck = (0, lodash_1.get)(options, 'skipNsCheck', false);
const failedNsCheck = (0, lodash_1.get)(options, 'failedNsCheckMode', 'throw');
delete options.skipNsCheck;
const metadata = this.getMetadata(context, options.target, options.propertyName ? options.propertyName : null);
if (metadata) {
if (metadata.namespace && metadata.namespace !== this.namespace) {
if (!skipNsCheck) {
if (context !== Constants_1.METATYPE_PROPERTY) {
if (failedNsCheck === 'ignore') {
return null;
}
else {
throw new base_1.NotSupportedError('namespace for ' + context + ' is ' + metadata.namespace +
' the namespace of this registry is ' + this.namespace);
}
}
}
}
if (!(0, lodash_1.isEmpty)(metadata) && (0, lodash_1.keys)(metadata).length > 0) {
(0, lodash_1.merge)(options, (0, lodash_1.assign)(metadata, { namespace: this.namespace }));
}
}
options.metaType = context;
switch (context) {
case Constants_1.METATYPE_CLASS_REF:
break;
case Constants_1.METATYPE_EMBEDDABLE:
return this.createEmbeddableForOptions(options);
case Constants_1.METATYPE_ENTITY:
return this.createEntityForOptions(options);
case Constants_1.METATYPE_PROPERTY:
return this.createPropertyForOptions(options);
case Constants_1.METATYPE_SCHEMA:
return this.createSchemaForOptions(options);
}
return null;
}
/**
* TODO
*/
add(context, entry) {
if (context === Constants_1.METATYPE_CLASS_REF) {
return this.addClassRef(entry);
}
return this.getLookupRegistry().add(context, entry);
}
/**
* Create default entity reference
*
* @param options
*/
addClassRef(ref) {
const addedClassRef = this.getLookupRegistry().add(Constants_1.METATYPE_CLASS_REF, ref);
this.createPropertiesForRef(addedClassRef);
return addedClassRef;
}
getClassRefFor(object, type) {
let ref = null;
if ((0, lodash_1.isString)(object)) {
ref = this.find(Constants_1.METATYPE_CLASS_REF, (x) => x.name === object);
}
else if ((0, lodash_1.isFunction)(object)) {
ref = this.find(Constants_1.METATYPE_CLASS_REF, (x) => x.getClass(true) === object);
}
else {
ref = this.find(Constants_1.METATYPE_CLASS_REF, (x) => x === object);
}
if (!ref) {
ref = ClassRef_1.ClassRef.get(object, this.namespace, {
resolve: type === Constants_1.METATYPE_PROPERTY,
checkNamespace: true
});
const metadata = this.getMetadata(Constants_1.METATYPE_EMBEDDABLE, object);
if (!(0, lodash_1.isEmpty)(metadata) && (0, lodash_1.keys)(metadata).length > 0) {
const refOptions = ref.getOptions();
(0, lodash_1.merge)(refOptions, metadata);
}
// this.createPropertiesForRef(ref);
}
return ref;
}
reset() {
super.reset();
MetadataRegistry_1.MetadataRegistry.$().off(Constants_1.C_EVENT_ADD, this.onAdd.bind(this));
MetadataRegistry_1.MetadataRegistry.$().off(Constants_1.C_EVENT_REMOVE, this.onRemove.bind(this));
MetadataRegistry_1.MetadataRegistry.$().off(Constants_1.C_EVENT_UPDATE, this.onUpdate.bind(this));
}
}
exports.DefaultNamespacedRegistry = DefaultNamespacedRegistry;
//# sourceMappingURL=DefaultNamespacedRegistry.js.map