UNPKG

type-arango

Version:

ArangoDB Foxx decorators and utilities for TypeScript

503 lines 22.7 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Document = exports.getDocumentForContainer = exports.findDocumentForContainer = void 0; var joi_1 = require("../joi"); var index_1 = require("../index"); var utils_1 = require("../utils"); var errors_1 = require("../errors"); var edgeAttributes = ['_from', '_to']; /** * Creates a new Document for a decorated class */ function createDocumentFromContainer(someClass) { var c = new Document(someClass); index_1.documents.push(c); return c; } /** * Finds a document instance for a decorated class */ function findDocumentForContainer(someClass) { return index_1.documents.find(function (c) { return someClass === c.Class; }); } exports.findDocumentForContainer = findDocumentForContainer; /** * Returns the respective document instance for a decorated class */ function getDocumentForContainer(someClass) { var doc = findDocumentForContainer(someClass); if (doc) return doc; return createDocumentFromContainer(someClass); } exports.getDocumentForContainer = getDocumentForContainer; function reduceMap(arg, doc, map) { if (doc[map[0]] === undefined) return doc; doc[map[0]] = map[1](doc[map[0]], arg); return doc; } var _id = joi_1.Joi.string(); var _key = joi_1.Joi.string(); var _from = joi_1.Joi.string(); var _to = joi_1.Joi.string(); var eventTypes = [ 'After.document', 'Before.document', 'After.insert', 'Before.insert', 'After.update', 'Before.update', 'After.modify', 'Before.modify', 'After.write', 'Before.write', 'After.replace', 'Before.replace', 'After.remove', 'Before.remove' ]; var eventNames = [ 'afterDocument', 'beforeDocument', 'afterInsert', 'beforeInsert', 'afterUpdate', 'beforeUpdate', 'afterModify', 'beforeModify', 'afterWrite', 'beforeWrite', 'afterReplace', 'beforeReplace', 'afterRemove', 'beforeRemove' ]; var Document = /** @class */ (function () { function Document(Class) { this.Class = Class; this.isEdge = false; this.options = {}; this.attribute = { _id: { attribute: '_id', metadata: String, schema: _id }, _key: { attribute: '_key', metadata: String, schema: _key } }; this.schema = { _id: _id, _key: _key }; this.relation = {}; this.roles = ['guest']; this.roleStripAttributes = {}; this.decorator = { Index: [] }; this.forClientMap = []; this.fromClientMap = []; this.name = Class.name; } Document.prototype.makeEdge = function () { this.isEdge = true; Object.assign(this.attribute, { _from: _from, _to: _to }); Object.assign(this.schema, { _from: _from, _to: _to }); }; Document.prototype.decorate = function (decorator, data) { return this.decorator[decorator] = __spreadArrays((this.decorator[decorator] || []), [__assign(__assign({}, data), { decorator: decorator })]); }; Object.defineProperty(Document.prototype, "attributeIdentifierObject", { get: function () { var o = {}; Object.keys(this.attribute).forEach(function (a) { return o[a] = a; }); return o; }, enumerable: false, configurable: true }); Object.defineProperty(Document.prototype, "indexes", { get: function () { return this.decorator.Index; }, enumerable: false, configurable: true }); Object.defineProperty(Document.prototype, "joi", { get: function () { return joi_1.Joi.object(this.schema); }, enumerable: false, configurable: true }); Document.prototype.forClient = function (doc, arg) { var forClient = this.options.forClient; doc = this.forClientMap.reduce(reduceMap.bind(null, arg), doc); return forClient ? forClient(doc, arg) : doc; }; Document.prototype.fromClient = function (doc, arg) { var fromClient = this.options.fromClient; doc = this.fromClientMap.reduce(reduceMap.bind(null, arg), doc); return fromClient ? fromClient(doc, arg) : doc; }; /** * Emit before event */ Document.prototype.emitBefore = function (method) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } // PropertyDecorator utils_1.toArray(this.decorator['Before.' + method + '.prop']) .forEach(function (_a) { var attribute = _a.attribute, resolver = _a.resolver; switch (method) { case 'document': resolver(args[0], { _key: args[0], attribute: attribute, method: method }); break; case 'insert': args[0][attribute] = resolver(args[0][attribute], { json: args[0], attribute: attribute, method: method }); break; case 'update': args[0][attribute] = resolver(args[0][attribute], { _key: args[1], json: args[0], attribute: attribute, method: method }); break; case 'replace': args[0][attribute] = resolver(args[0][attribute], { _key: args[1], json: args[0], attribute: attribute, method: method }); break; case 'remove': resolver(args[0], { _key: args[0], attribute: attribute, method: method }); break; } }); // ClassDecorator utils_1.toArray(this.decorator['Before.' + method + '.class']) .forEach(function (_a) { var resolver = _a.resolver; var v; switch (method) { case 'document': v = resolver(args[0], { _key: args[0], method: method }); break; case 'insert': v = resolver(args[0], { json: args[0], method: method }); break; case 'update': v = resolver(args[0], { _key: args[1], json: args[0], method: method }); break; case 'replace': v = resolver(args[0], { _key: args[1], json: args[0], method: method }); break; case 'remove': v = resolver(args[0], { _key: args[0], method: method }); break; } // Cancel if (v === false || typeof v === 'string') { throw new Error(v || 'bad-request'); } // Passive else if (v === true || v === undefined) { } // DocumentData else if (typeof args[0] === 'object') { args[0] = v; } }); return args[0]; }; /** * Emit after event */ Document.prototype.emitAfter = function (method) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } // PropertyDecorator utils_1.toArray(this.decorator['After.' + method + '.prop']) .forEach(function (_a) { var attribute = _a.attribute, resolver = _a.resolver; switch (method) { case 'document': args[0][attribute] = resolver(args[0][attribute], { _key: args[1], document: args[0], attribute: attribute, method: method }); break; case 'insert': args[0][attribute] = resolver(args[0][attribute], { _key: args[1], document: args[0], attribute: attribute, method: method }); break; case 'update': args[0][attribute] = resolver(args[0][attribute], { _key: args[1], document: args[0], attribute: attribute, method: method }); break; case 'replace': args[0][attribute] = resolver(args[0][attribute], { _key: args[1], document: args[0], attribute: attribute, method: method }); break; case 'remove': resolver(args[0], { _key: args[0], attribute: attribute, method: method }); break; } }); // ClassDecorator utils_1.toArray(this.decorator['After.' + method + '.class']) .forEach(function (_a) { var resolver = _a.resolver; var v; switch (method) { case 'document': v = resolver(args[0], { _key: args[0], method: method }); break; case 'insert': v = resolver(args[0], { _key: args[0]._key, document: args[0], method: method }); break; case 'update': v = resolver(args[1], { _key: args[1], document: args[0], method: method }); break; case 'replace': v = resolver(args[1], { _key: args[1], document: args[0], method: method }); break; case 'remove': v = resolver(args[0], { _key: args[0], method: method }); break; } // Cancel if (v === false || typeof v === 'string') { throw new Error(v || 'bad-request'); } // Passive else if (v === true || v === undefined) { } // DocumentData else if (typeof args[0] === 'object') { args[0] = v; } }); return args[0]; }; /** * Resolves one or more related entities */ Document.prototype.resolveRelation = function (data, attribute, arg) { var rel = this.relation[attribute]; var filter = {}; var merge = null; // related document is an edge, use CollectionName/ID if (rel.document.isEdge && edgeAttributes.includes(rel.attribute)) { filter[rel.attribute] = this.col.name + '/' + data._key; } else { if (data._key) filter[rel.attribute] = ['HAS', data._key]; } // relation key/s stored in document var ref = data[attribute]; if (ref) { if (utils_1.isObject(ref)) throw new Error("Invalid relation value of \"" + rel.document.name + "." + rel.attribute + "\": " + JSON.stringify(ref)); // remove CollectionName/ from relation id if (this.isEdge) { if (Array.isArray(ref)) ref = ref.map(function (r) { return r.replace(rel.document.col.name + '/', ''); }); else ref = ref.replace(rel.document.col.name + '/', ''); } // support tuples with key and value [[KEY, VALUE],...] if (Array.isArray(ref[0])) { merge = __spreadArrays(ref); ref = ref.map(function (r) { return r[0]; }); } filter = { _key: ref }; } if (!Object.keys(filter).length) throw new Error("Cannot resolve relation of \"" + rel.document.name + "." + rel.attribute + "\": empty filter"); var entities = rel.document.col.Class; if (arg === null) return data[attribute]; var opt = { filter: filter }; // attributes if (Array.isArray(arg)) { opt.keep = arg; } var res; // retrieve document/s switch (rel.type) { default: return null; case 'OneToOne': res = entities.find(opt); break; case 'OneToMany': res = entities.filter(opt); break; } // merge values back into result if (merge) res = res.map(function (res) { return (__assign(__assign({}, res), { _value: merge.find(function (r) { return r[0] === res._key; })[1] })); }); return res; }; /** * Creates an array of attributes that can't be read or written for every role used in the collection * this.roleStripAttributes = {user:{read:[],write:['readOnlyAttribute']} */ Document.prototype.buildAttributeRoles = function () { index_1.logger.debug('Building %s attribute roles cache', this.name); var attributes = Object.values(this.attribute); // every role of the entity for (var _i = 0, _a = this.roles; _i < _a.length; _i++) { var role = _a[_i]; var roles = { read: [], write: [] }; // every attribute of the entity for (var _b = 0, attributes_1 = attributes; _b < attributes_1.length; _b++) { var attr = attributes_1[_b]; if (!attr.roles) continue; var _c = attr.roles, readers = _c.readers, writers = _c.writers; if (readers && !readers.includes(role)) roles.read = utils_1.concatUnique(roles.read, attr.attribute); if (writers && !writers.includes(role)) roles.write = utils_1.concatUnique(roles.write, attr.attribute); } this.roleStripAttributes[role] = roles; } }; /** * Creates arrays of document attributes that can be read or written */ Document.prototype.stripAttributeList = function (providedRoles, method) { // collect attributes into temp object {key:count} var attributes = {}, sum = 0; for (var _i = 0, providedRoles_1 = providedRoles; _i < providedRoles_1.length; _i++) { var role = providedRoles_1[_i]; if (this.roleStripAttributes[role]) { sum++; for (var _a = 0, _b = this.roleStripAttributes[role][method]; _a < _b.length; _a++) { var r = _b[_a]; attributes[r] = (attributes[r] || 0) + 1; } } } var stripAttributes = []; Object.keys(attributes).forEach(function (k) { return attributes[k] === sum && stripAttributes.push(k); }); return stripAttributes; }; /** * Setup attribute names for doc.attributeIdentifierObject before finalizing */ Document.prototype.complete = function () { var _a = this.decorator, Authorized = _a.Authorized, Attribute = _a.Attribute; if (Attribute) for (var _i = 0, _b = __spreadArrays(Authorized || [], Attribute); _i < _b.length; _i++) { var attribute = _b[_i].attribute; if (attribute) this.attribute[attribute] = { attribute: attribute }; } }; Document.prototype.finalize = function () { var _this = this; var _a = this.decorator, Authorized = _a.Authorized, OneToOne = _a.OneToOne, OneToMany = _a.OneToMany; var metadata; // @OneToOne | @OneToMany if (OneToOne || OneToMany) for (var _i = 0, _b = (OneToOne || []).concat(OneToMany || []); _i < _b.length; _i++) { var _c = _b[_i], decorator = _c.decorator, prototype = _c.prototype, attribute = _c.attribute, type = _c.type, relation = _c.relation; metadata = attribute && Reflect.getMetadata('design:type', prototype, attribute); if (!type && ((metadata && ['Array', 'Function'].includes(metadata.name)) || (!type && !metadata))) throw new errors_1.MissingTypeError(this.name, attribute); type = type ? utils_1.argumentResolve(type) : metadata; var docName = this.name.toLowerCase(); var relationDoc = getDocumentForContainer(type); var attrObj = relationDoc.attributeIdentifierObject; var relationAttr = utils_1.argumentResolve(relation, attrObj); if (relation && !relationAttr) throw new errors_1.RelationNotFoundError(decorator, this.name, relation); else if (!relationAttr) relationAttr = attrObj[docName] ? docName : '_key'; this.relation[attribute] = { type: decorator, document: relationDoc, attribute: relationAttr }; } // @Authorized if (Authorized) { var _loop_1 = function (o) { var A = this_1.decorator.Attribute || []; var a = (A || []).find(function (a) { return a.attribute === o.attribute; }); if (!a) a = this_1.decorate('Attribute', o).find(function (attr) { return attr.attribute === o.attribute; }); else { a.readersArray = o.readersArray; a.writersArray = o.writersArray; } }; var this_1 = this; for (var _d = 0, Authorized_1 = Authorized; _d < Authorized_1.length; _d++) { var o = Authorized_1[_d]; _loop_1(o); } } var Attribute = this.decorator.Attribute; // @Attribute if (Attribute) { var _loop_2 = function (prototype, attribute, typeOrRequiredOrSchemaOrReaders, readersArray, writersArray) { metadata = Reflect.getMetadata('design:type', prototype, attribute); var rel = this_2.relation[attribute]; var joi = void 0; if (!rel) joi = utils_1.toJoi(metadata); else switch (rel.type) { case 'OneToOne': joi = rel.document.attribute[rel.attribute].schema || joi_1.Joi.string(); break; case 'OneToMany': joi = joi_1.Joi.array().items(rel.document.attribute[rel.attribute].schema || joi_1.Joi.string()); break; default: joi = utils_1.toJoi(metadata); break; } // if(!metadata) // throw new Error('Invalid design:type for "'+this.name+'.'+attribute+'"') var schema = utils_1.argumentResolve(typeOrRequiredOrSchemaOrReaders, joi, utils_1.enjoi) || joi; var readers = utils_1.argumentResolve(readersArray); var writers = utils_1.argumentResolve(writersArray); var roles = void 0; if (Array.isArray(schema)) { writers = readers; readers = schema; schema = joi; } if (readers) roles = utils_1.removeValues({ readers: readers, writers: writers || index_1.config.requiredWriterRolesFallback }, undefined); if (roles) { if (index_1.config.addAttributeWritersToReaders && roles.writers) { roles.readers = utils_1.concatUnique(roles.readers, writers || []); } this_2.roles = utils_1.concatUnique(this_2.roles, roles.readers, roles.writers); } if (schema) { this_2.schema[attribute] = schema === true ? joi.required() : schema; } var data = utils_1.removeValues({ attribute: attribute, roles: roles, schema: schema, metadata: metadata }, undefined); if (metadata && metadata._typeArango) { if (index_1.version < metadata._typeArango) { index_1.logger.error("Type." + metadata.name + " requires type-arango v" + metadata._typeArango); } else { if (metadata.forClient) this_2.forClientMap.push([attribute, metadata.forClient]); if (metadata.fromClient) this_2.fromClientMap.push([attribute, metadata.fromClient]); eventTypes.forEach(function (name) { var m = metadata[eventNames[eventTypes.indexOf(name)]]; if (!m) return; _this.decorate(name + '.prop', { prototype: prototype, attribute: attribute, resolver: m }); }); } } this_2.attribute[attribute] = data; index_1.logger.debug('Created attribute `%s.%s`', this_2.name, attribute); }; var this_2 = this; for (var _e = 0, Attribute_1 = Attribute; _e < Attribute_1.length; _e++) { var _f = Attribute_1[_e], prototype = _f.prototype, attribute = _f.attribute, typeOrRequiredOrSchemaOrReaders = _f.typeOrRequiredOrSchemaOrReaders, readersArray = _f.readersArray, writersArray = _f.writersArray; _loop_2(prototype, attribute, typeOrRequiredOrSchemaOrReaders, readersArray, writersArray); } } // create attribute roles this.buildAttributeRoles(); index_1.logger.info('Completed document "%s"', this.name); }; return Document; }()); exports.Document = Document; //# sourceMappingURL=Document.model.js.map