type-arango
Version:
ArangoDB Foxx decorators and utilities for TypeScript
503 lines • 22.7 kB
JavaScript
"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