UNPKG

@allgemein/schema-api

Version:
579 lines 23.2 kB
"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