@harishreddym/baqend
Version:
Baqend JavaScript SDK
1,705 lines (1,407 loc) • 572 kB
JavaScript
/*!
* Baqend JavaScript SDK 2.14.1
* http://baqend.com
*
* Copyright (c) 2015 Baqend GmbH
*
* Includes:
* babel-helpers - https://babeljs.io/docs/plugins/external-helpers/
* Copyright (c) 2014-2016 Sebastian McKenzie <sebmck@gmail.com>
*
* core.js - https://github.com/zloirock/core-js
* Copyright (c) 2014-2016 Denis Pushkarev
*
* uuid - http://github.com/broofa/node-uuid
* Copyright (c) 2010-2016 Robert Kieffer and other contributors
*
* validator - http://github.com/chriso/validator.js
* Copyright (c) 2015 Chris O'Hara <cohara87@gmail.com>
*
* Released under the MIT license
*
* Date: Sun, 06 Jan 2019 04:59:54 GMT
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.DB = f()}})(function(){var define,module,exports;var babelHelpers = {};
babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
babelHelpers.classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
babelHelpers.createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
babelHelpers.defaults = function (obj, defaults) {
var keys = Object.getOwnPropertyNames(defaults);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = Object.getOwnPropertyDescriptor(defaults, key);
if (value && value.configurable && obj[key] === undefined) {
Object.defineProperty(obj, key, value);
}
}
return obj;
};
babelHelpers.inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : babelHelpers.defaults(subClass, superClass);
};
babelHelpers.possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(_dereq_,module,exports){
'use strict';
var Permission = _dereq_(68);
/**
* Creates a new Acl object, with an empty rule set for an object
*
*/
var Acl = function () {
/**
* @param {util.Metadata=} metadata the metadata of the object, null for files
*/
function Acl(metadata) {
babelHelpers.classCallCheck(this, Acl);
/**
* The read permission of the object
* @type util.Permission
* @readonly
*/
this.read = new Permission(metadata);
/**
* The write permission of the object
* @type util.Permission
* @readonly
*/
this.write = new Permission(metadata);
}
/**
* Removes all acl rules, read and write access is public afterwards
*
* @return {void}
*/
Acl.prototype.clear = function clear() {
this.read.clear();
this.write.clear();
};
/**
* Copies permissions from another ACL
*
* @param {Acl} acl The ACL to copy from
* @return {Acl}
*/
Acl.prototype.copy = function copy(acl) {
this.read.copy(acl.read);
this.write.copy(acl.write);
return this;
};
/**
* Gets whenever all users and roles have the permission to read the object
*
* @return {boolean} <code>true</code> If public access is allowed
*/
Acl.prototype.isPublicReadAllowed = function isPublicReadAllowed() {
return this.read.isPublicAllowed();
};
/**
* Sets whenever all users and roles should have the permission to read the object
*
* Note: All other allow read rules will be removed.
*
* @return {void}
*/
Acl.prototype.setPublicReadAllowed = function setPublicReadAllowed() {
return this.read.setPublicAllowed();
};
/**
* Checks whenever the user or role is explicit allowed to read the object
*
* @param {model.User|model.Role|string} userOrRole The user or role to check for
* @return {boolean} <code>true</code> if read access is explicitly allowed for the given user or role
*/
Acl.prototype.isReadAllowed = function isReadAllowed(userOrRole) {
return this.read.isAllowed(userOrRole);
};
/**
* Checks whenever the user or role is explicit denied to read the object
*
* @param {model.User|model.Role|string} userOrRole The user or role to check for
* @return {boolean} <code>true</code> if read access is explicitly denied for the given user or role
*/
Acl.prototype.isReadDenied = function isReadDenied(userOrRole) {
return this.read.isDenied(userOrRole);
};
/**
* Allows the given user or rule to read the object
*
* @param {...(model.User|model.Role|string)} userOrRole The user or role to allow
* @return {Acl} this acl object
*/
Acl.prototype.allowReadAccess = function allowReadAccess() /* ...userOrRole */{
Permission.prototype.allowAccess.apply(this.read, arguments);
return this;
};
/**
* Denies the given user or rule to read the object
*
* @param {...(model.User|model.Role|string)} userOrRole The user or role to deny
* @return {Acl} this acl object
*/
Acl.prototype.denyReadAccess = function denyReadAccess() /* ...userOrRole */{
Permission.prototype.denyAccess.apply(this.read, arguments);
return this;
};
/**
* Deletes any read allow/deny rule for the given user or role
*
* @param {...(model.User|model.Role|string)} userOrRole The user or role
* @return {Acl} this acl object
*/
Acl.prototype.deleteReadAccess = function deleteReadAccess() /* ...userOrRole */{
Permission.prototype.deleteAccess.apply(this.read, arguments);
return this;
};
/**
* Gets whenever all users and roles have the permission to write the object
*
* @return {boolean} <code>true</code> If public access is allowed
*/
Acl.prototype.isPublicWriteAllowed = function isPublicWriteAllowed() {
return this.write.isPublicAllowed();
};
/**
* Sets whenever all users and roles should have the permission to write the object
*
* Note: All other allow write rules will be removed.
*
* @return {void}
*/
Acl.prototype.setPublicWriteAllowed = function setPublicWriteAllowed() {
return this.write.setPublicAllowed();
};
/**
* Checks whenever the user or role is explicit allowed to write the object
*
* @param {model.User|model.Role|string} userOrRole The user or role to check for
* @return {boolean} <code>true</code> if write access is explicitly allowed for the given user or role
*/
Acl.prototype.isWriteAllowed = function isWriteAllowed(userOrRole) {
return this.write.isAllowed(userOrRole);
};
/**
* Checks whenever the user or role is explicit denied to write the object
*
* @param {model.User|model.Role|string} userOrRole The user or role to check for
* @return {boolean} <code>true</code> if write access is explicitly denied for the given user or role
*/
Acl.prototype.isWriteDenied = function isWriteDenied(userOrRole) {
return this.write.isDenied(userOrRole);
};
/**
* Allows the given user or rule to write the object
*
* @param {...(model.User|model.Role|string)} userOrRole The user or role to allow
* @return {Acl} this acl object
*/
Acl.prototype.allowWriteAccess = function allowWriteAccess() /* ...userOrRole */{
Permission.prototype.allowAccess.apply(this.write, arguments);
return this;
};
/**
* Denies the given user or rule to write the object
*
* @param {...(model.User|model.Role|string)} userOrRole The user or role to deny
* @return {Acl} this acl object
*/
Acl.prototype.denyWriteAccess = function denyWriteAccess() /* ...userOrRole */{
Permission.prototype.denyAccess.apply(this.write, arguments);
return this;
};
/**
* Deletes any write allow/deny rule for the given user or role
*
* @param {...(model.User|model.Role|string)} userOrRole The user or role
* @return {Acl} this acl object
*/
Acl.prototype.deleteWriteAccess = function deleteWriteAccess() /* ...userOrRole */{
Permission.prototype.deleteAccess.apply(this.write, arguments);
return this;
};
/**
* A JSON representation of the set of rules
*
* @return {json}
*/
Acl.prototype.toJSON = function toJSON() {
return {
read: this.read.toJSON(),
write: this.write.toJSON()
};
};
/**
* Sets the acl rules form JSON
*
* @param {json} json The json encoded acls
* @return {void}
*/
Acl.prototype.fromJSON = function fromJSON(json) {
this.read.fromJSON(json.read || {});
this.write.fromJSON(json.write || {});
};
return Acl;
}();
module.exports = Acl;
},{"68":68}],2:[function(_dereq_,module,exports){
'use strict';
var messages = _dereq_(35);
var error = _dereq_(33);
var binding = _dereq_(19);
var util = _dereq_(76);
var query = _dereq_(62);
var UserFactory = _dereq_(18);
var Metadata = _dereq_(66);
var Message = _dereq_(25);
var BloomFilter = _dereq_(21);
var deorecated = _dereq_(74);
var StatusCode = Message.StatusCode;
var DB_PREFIX = '/db/';
/**
* @alias EntityManager
* @extends util.Lockable
*/
var EntityManager = function (_util$Lockable) {
babelHelpers.inherits(EntityManager, _util$Lockable);
babelHelpers.createClass(EntityManager, [{
key: 'isOpen',
/**
* Determine whether the entity manager is open.
* true until the entity manager has been closed
* @type boolean
* @readonly
*/
get: function get() {
return !!this.connection;
}
/**
* The authentication token if the user is logged in currently
* @type string
*/
}, {
key: 'token',
get: function get() {
return this.tokenStorage.token;
}
/**
* Whether caching is disabled
* @type boolean
* @readonly
*/
, /**
* The authentication token if the user is logged in currently
* @param {string} value
*/
set: function set(value) {
this.tokenStorage.update(value);
}
/**
* @param {EntityManagerFactory} entityManagerFactory The factory which of this entityManager instance
*/
}, {
key: 'isCachingDisabled',
get: function get() {
return !this.bloomFilter;
}
/**
* Returns true if the device token is already registered, otherwise false.
* @type boolean
* @readonly
*/
}, {
key: 'isDeviceRegistered',
get: function get() {
return !!this.deviceMe;
}
}]);
function EntityManager(entityManagerFactory) {
babelHelpers.classCallCheck(this, EntityManager);
/**
* Log messages can created by calling log directly as function, with a specific log level or with the helper
* methods, which a members of the log method.
*
* Logs will be filtered by the client logger and the before they persisted. The default log level is
* 'info' therefore all log messages below the given message aren't persisted.
*
* Examples:
* <pre class="prettyprint">
// default log level ist info
db.log('test message %s', 'my string');
// info: test message my string
// pass a explicit log level as the first argument, one of ('trace', 'debug', 'info', 'warn', 'error')
db.log('warn', 'test message %d', 123);
// warn: test message 123
// debug log level will not be persisted by default, since the default logging level is info
db.log('debug', 'test message %j', {number: 123}, {});
// debug: test message {"number":123}
// data = {}
// One additional json object can be provided, which will be persisted together with the log entry
db.log('info', 'test message %s, %s', 'first', 'second', {number: 123});
// info: test message first, second
// data = {number: 123}
//use the log level helper
db.log.info('test message', 'first', 'second', {number: 123});
// info: test message first second
// data = {number: 123}
//change the default log level to trace, i.e. all log levels will be persisted, note that the log level can be
//additionally configured in the baqend
db.log.level = 'trace';
//trace will be persisted now
db.log.trace('test message', 'first', 'second', {number: 123});
// info: test message first second
// data = {number: 123}
* </pre>
*
* @type util.Logger
* @readonly
*/
var _this = babelHelpers.possibleConstructorReturn(this, _util$Lockable.call(this));
_this.log = util.Logger.create(_this);
/**
* The connector used for requests
* @type connector.Connector
* @private
*/
_this.connection = null;
/**
* All managed and cached entity instances
* @type Map<String,binding.Entity>
* @private
*/
_this.entities = null;
/**
* @type EntityManagerFactory
* @readonly
*/
_this.entityManagerFactory = entityManagerFactory;
/**
* @type metamodel.Metamodel
* @readonly
*/
_this.metamodel = entityManagerFactory.metamodel;
/**
* @type util.Code
* @readonly
*/
_this.code = entityManagerFactory.code;
/**
* @type util.Modules
* @readonly
*/
_this.modules = null;
/**
* The current logged in user object
* @type (model.User|null)
* @readonly
*/
_this.me = null;
/**
* The current registered device object
* @type (model.Device|null)
* @readonly
*/
_this.deviceMe = null;
/**
* Returns the tokenStorage which will be used to authorize all requests.
* @type {util.TokenStorage}
* @readonly
*/
_this.tokenStorage = null;
/**
* @type {caching.BloomFilter}
* @readonly
*/
_this.bloomFilter = null;
/**
* Set of object ids that were revalidated after the Bloom filter was loaded.
*/
_this.cacheWhiteList = null;
/**
* Set of object ids that were updated but are not yet included in the bloom filter.
* This set essentially implements revalidation by side effect which does not work in Chrome.
*/
_this.cacheBlackList = null;
/**
* Bloom filter refresh interval in seconds.
*
* @type {number}
* @readonly
*/
_this.bloomFilterRefresh = 60;
/**
* Bloom filter refresh Promise
*
*/
_this.bloomFilterLock = new util.Lockable();
return _this;
}
/**
* Connects this entityManager, used for synchronous and asynchronous initialization
* @param {connector.Connector} connector
* @param {Object} connectData
* @param {util.TokenStorage} tokenStorage The used tokenStorage for token persistence
* @return {void}
*/
EntityManager.prototype.connected = function connected(connector, connectData, tokenStorage) {
this.connection = connector;
this.tokenStorage = tokenStorage;
this.bloomFilterRefresh = this.entityManagerFactory.staleness;
this.entities = {};
this.File = binding.FileFactory.create(this);
this._createObjectFactory(this.metamodel.embeddables);
this._createObjectFactory(this.metamodel.entities);
this.transaction = {}; // TODO: implement this
this.modules = new util.Modules(this, connector);
if (connectData) {
if (connectData.device) {
this.updateDevice(connectData.device);
}
if (connectData.user && tokenStorage.token) {
this._updateUser(connectData.user, true);
}
if (this.bloomFilterRefresh > 0 && connectData.bloomFilter && util.atob && !util.isNode) {
this.updateBloomFilter(connectData.bloomFilter);
}
}
};
/**
* @param {metamodel.ManagedType[]} types
* @return {binding.ManagedFactory}
* @private
*/
EntityManager.prototype._createObjectFactory = function _createObjectFactory(types) {
var _this2 = this;
Object.keys(types).forEach(function (ref) {
var type = _this2.metamodel.managedType(ref);
var name = type.name;
if (_this2[name]) {
type.typeConstructor = _this2[name];
Object.defineProperty(_this2, name, {
value: type.createObjectFactory(_this2)
});
} else {
Object.defineProperty(_this2, name, {
get: function get() {
Object.defineProperty(this, name, {
value: type.createObjectFactory(this)
});
return this[name];
},
set: function set(typeConstructor) {
type.typeConstructor = typeConstructor;
},
configurable: true
});
}
}, this);
};
EntityManager.prototype.send = function send(mesage, ignoreCredentialError) {
var _this3 = this;
var msg = mesage;
msg.tokenStorage = this.tokenStorage;
var result = this.connection.send(msg);
if (!ignoreCredentialError) {
result = result.catch(function (e) {
if (e.status === StatusCode.BAD_CREDENTIALS) {
_this3._logout();
}
throw e;
});
}
return result;
};
/**
* Get an instance whose state may be lazily fetched
*
* If the requested instance does not exist in the database, the
* EntityNotFoundError is thrown when the instance state is first accessed.
* The application should not expect that the instance state will be available upon detachment,
* unless it was accessed by the application while the entity manager was open.
*
* @param {(Class<binding.Entity>|string)} entityClass
* @param {string=} key
* @return {binding.Entity}
*/
EntityManager.prototype.getReference = function getReference(entityClass, key) {
var id = void 0;
var type = void 0;
if (key) {
var keyAsStr = key;
type = this.metamodel.entity(entityClass);
if (keyAsStr.indexOf(DB_PREFIX) === 0) {
id = keyAsStr;
} else {
id = type.ref + '/' + encodeURIComponent(keyAsStr);
}
} else if (typeof entityClass === 'string') {
var keyIndex = entityClass.indexOf('/', DB_PREFIX.length); // skip /db/
if (keyIndex !== -1) {
id = entityClass;
}
type = this.metamodel.entity(keyIndex === -1 ? entityClass : id.substring(0, keyIndex));
} else {
type = this.metamodel.entity(entityClass);
}
var entity = this.entities[id];
if (!entity) {
entity = type.create();
var metadata = Metadata.get(entity);
if (id) {
metadata.id = id;
}
metadata.setUnavailable();
this._attach(entity);
}
return entity;
};
/**
* Creates an instance of {@link query.Builder<T>} for query creation and execution
*
* The query results are instances of the resultClass argument.
*
* @alias EntityManager.prototype.createQueryBuilder<T>
* @param {Class<T>=} resultClass - the type of the query result
* @return {query.Builder<T>} A query builder to create one ore more queries for the specified class
*/
EntityManager.prototype.createQueryBuilder = function createQueryBuilder(resultClass) {
return new query.Builder(this, resultClass);
};
/**
* Clear the persistence context, causing all managed entities to become detached
*
* Changes made to entities that have not been flushed to the database will not be persisted.
*
* @return {void}
*/
EntityManager.prototype.clear = function clear() {
this.entities = {};
};
/**
* Close an application-managed entity manager
*
* After the close method has been invoked, all methods on the EntityManager instance
* and any Query and TypedQuery objects obtained from it will throw the IllegalStateError
* except for transaction, and isOpen (which will return false). If this method
* is called when the entity manager is associated with an active transaction,
* the persistence context remains managed until the transaction completes.
*
* @return {void}
*/
EntityManager.prototype.close = function close() {
this.connection = null;
return this.clear();
};
/**
* Check if the instance is a managed entity instance belonging to the current persistence context
*
* @param {binding.Entity} entity - entity instance
* @return {boolean} boolean indicating if entity is in persistence context
*/
EntityManager.prototype.contains = function contains(entity) {
return !!entity && this.entities[entity.id] === entity;
};
/**
* Check if an object with the id from the given entity is already attached
*
* @param {binding.Entity} entity - entity instance
* @return {boolean} boolean indicating if entity with same id is attached
*/
EntityManager.prototype.containsById = function containsById(entity) {
return !!(entity && this.entities[entity.id]);
};
/**
* Remove the given entity from the persistence context, causing a managed entity to become detached
*
* Unflushed changes made to the entity if any (including removal of the entity),
* will not be synchronized to the database. Entities which previously referenced the detached entity will continue
* to reference it.
*
* @param {binding.Entity} entity The entity instance to detach.
* @return {Promise<binding.Entity>}
*/
EntityManager.prototype.detach = function detach(entity) {
var _this4 = this;
var state = Metadata.get(entity);
return state.withLock(function () {
_this4.removeReference(entity);
return Promise.resolve(entity);
});
};
/**
* Resolve the depth by loading the referenced objects of the given entity
*
* @param {binding.Entity} entity - entity instance
* @param {Object} [options] The load options
* @return {Promise<binding.Entity>}
*/
EntityManager.prototype.resolveDepth = function resolveDepth(entity, options) {
var _this5 = this;
if (!options || !options.depth) {
return Promise.resolve(entity);
}
options.resolved = options.resolved || [];
var promises = [];
var subOptions = Object.assign({}, options, {
depth: options.depth === true ? true : options.depth - 1
});
this.getSubEntities(entity, 1).forEach(function (subEntity) {
if (subEntity !== null && options.resolved.indexOf(subEntity) === -1) {
options.resolved.push(subEntity);
promises.push(_this5.load(subEntity.id, null, subOptions));
}
});
return Promise.all(promises).then(function () {
return entity;
});
};
/**
* Search for an entity of the specified oid
*
* If the entity instance is contained in the persistence context, it is returned from there.
*
* @param {(Class<binding.Entity>|string)} entityClass - entity class
* @param {String} oid - Object ID
* @param {Object} [options] The load options.
* @return {Promise<binding.Entity>} the loaded entity or null
*/
EntityManager.prototype.load = function load(entityClass, oid, options) {
var _this6 = this;
var opt = options || {};
var entity = this.getReference(entityClass, oid);
var state = Metadata.get(entity);
if (!opt.refresh && opt.local && state.isAvailable) {
return this.resolveDepth(entity, opt);
}
var msg = new messages.GetObject(state.bucket, state.key);
this.ensureCacheHeader(entity.id, msg, opt.refresh);
return this.send(msg).then(function (response) {
// refresh object if loaded older version from cache
// chrome doesn't using cache when ifNoneMatch is set
if (entity.version > response.entity.version) {
opt.refresh = true;
return _this6.load(entityClass, oid, opt);
}
_this6.addToWhiteList(response.entity.id);
if (response.status !== StatusCode.NOT_MODIFIED) {
state.setJson(response.entity, { persisting: true });
}
return _this6.resolveDepth(entity, opt);
}, function (e) {
if (e.status === StatusCode.OBJECT_NOT_FOUND) {
_this6.removeReference(entity);
state.setRemoved();
return null;
}
throw e;
});
};
/**
* @param {binding.Entity} entity
* @param {Object} options
* @return {Promise<binding.Entity>}
*/
EntityManager.prototype.insert = function insert(entity, options) {
var _this7 = this;
var opt = options || {};
var isNew = void 0;
return this._save(entity, opt, function (state, json) {
if (state.version) {
throw new error.PersistentError('Existing objects can\'t be inserted.');
}
isNew = !state.id;
return new messages.CreateObject(state.bucket, json);
}).then(function (val) {
if (isNew) {
_this7._attach(entity);
}
return val;
});
};
/**
* @param {binding.Entity} entity
* @param {Object} options
* @return {Promise<binding.Entity>}
*/
EntityManager.prototype.update = function update(entity, options) {
var opt = options || {};
return this._save(entity, opt, function (state, json) {
if (!state.version) {
throw new error.PersistentError('New objects can\'t be inserted.');
}
if (opt.force) {
delete json.version;
return new messages.ReplaceObject(state.bucket, state.key, json).ifMatch('*');
}
return new messages.ReplaceObject(state.bucket, state.key, json).ifMatch(state.version);
});
};
/**
* @param {binding.Entity} entity
* @param {Object} options The save options
* @param {boolean=} withoutLock Set true to save the entity without locking
* @return {Promise<binding.Entity>}
*/
EntityManager.prototype.save = function save(entity, options, withoutLock) {
var opt = options || {};
var msgFactory = function msgFactory(state, json) {
if (opt.force) {
if (!state.id) {
throw new error.PersistentError('New special objects can\'t be forcedly saved.');
}
delete json.version;
return new messages.ReplaceObject(state.bucket, state.key, json);
}
if (state.version) {
return new messages.ReplaceObject(state.bucket, state.key, json).ifMatch(state.version);
}
return new messages.CreateObject(state.bucket, json);
};
return withoutLock ? this._locklessSave(entity, opt, msgFactory) : this._save(entity, opt, msgFactory);
};
/**
* @param {binding.Entity} entity
* @param {Function} cb pre-safe callback
* @return {Promise<binding.Entity>}
*/
EntityManager.prototype.optimisticSave = function optimisticSave(entity, cb) {
var _this8 = this;
return Metadata.get(entity).withLock(function () {
return _this8._optimisticSave(entity, cb);
});
};
/**
* @param {binding.Entity} entity
* @param {Function} cb pre-safe callback
* @return {Promise<binding.Entity>}
* @private
*/
EntityManager.prototype._optimisticSave = function _optimisticSave(entity, cb) {
var _this9 = this;
var abort = false;
var abortFn = function abortFn() {
abort = true;
};
var promise = Promise.resolve(cb(entity, abortFn));
if (abort) {
return Promise.resolve(entity);
}
return promise.then(function () {
return _this9.save(entity, {}, true).catch(function (e) {
if (e.status === 412) {
return _this9.refresh(entity, {}).then(function () {
return _this9._optimisticSave(entity, cb);
});
}
throw e;
});
});
};
/**
* Save the object state without locking
* @param {binding.Entity} entity
* @param {Object} options
* @param {Function} msgFactory
* @return {Promise.<binding.Entity>}
* @private
*/
EntityManager.prototype._locklessSave = function _locklessSave(entity, options, msgFactory) {
var _this10 = this;
this.attach(entity);
var state = Metadata.get(entity);
var refPromises = void 0;
var json = void 0;
if (state.isAvailable) {
// getting json will check all collections changes, therefore we must do it before proofing the dirty state
json = state.getJson({
persisting: true
});
}
if (state.isDirty) {
if (!options.refresh) {
state.setPersistent();
}
var sendPromise = this.send(msgFactory(state, json)).then(function (response) {
if (state.id && state.id !== response.entity.id) {
_this10.removeReference(entity);
state.id = response.entity.id;
_this10._attach(entity);
}
state.setJson(response.entity, {
persisting: options.refresh,
onlyMetadata: !options.refresh
});
return entity;
}, function (e) {
if (e.status === StatusCode.OBJECT_NOT_FOUND) {
_this10.removeReference(entity);
state.setRemoved();
return null;
}
state.setDirty();
throw e;
});
refPromises = [sendPromise];
} else {
refPromises = [Promise.resolve(entity)];
}
var subOptions = Object.assign({}, options);
subOptions.depth = 0;
this.getSubEntities(entity, options.depth).forEach(function (sub) {
refPromises.push(_this10._save(sub, subOptions, msgFactory));
});
return Promise.all(refPromises).then(function () {
return entity;
});
};
/**
* Save and lock the object state
* @param {binding.Entity} entity
* @param {Object} options
* @param {Function} msgFactory
* @return {Promise.<binding.Entity>}
* @private
*/
EntityManager.prototype._save = function _save(entity, options, msgFactory) {
var _this11 = this;
this.ensureBloomFilterFreshness();
var state = Metadata.get(entity);
if (state.version) {
this.addToBlackList(entity.id);
}
return state.withLock(function () {
return _this11._locklessSave(entity, options, msgFactory);
});
};
/**
* Returns all referenced sub entities for the given depth and root entity
* @param {binding.Entity} entity
* @param {boolean|number} depth
* @param {binding.Entity[]} [resolved]
* @param {binding.Entity=} initialEntity
* @return {binding.Entity[]}
*/
EntityManager.prototype.getSubEntities = function getSubEntities(entity, depth, resolved, initialEntity) {
var resolv = resolved || [];
if (!depth) {
return resolv;
}
var obj = initialEntity || entity;
var state = Metadata.get(entity);
var iter = state.type.references();
for (var item = iter.next(); !item.done; item = iter.next()) {
var value = item.value;
var subEntities = this.getSubEntitiesByPath(entity, value.path);
for (var i = 0, len = subEntities.length; i < len; i += 1) {
var subEntity = subEntities[i];
if (resolv.indexOf(subEntity) === -1 && subEntity !== obj) {
resolv.push(subEntity);
resolv = this.getSubEntities(subEntity, depth === true ? depth : depth - 1, resolv, obj);
}
}
}
return resolv;
};
/**
* Returns all referenced one level sub entities for the given path
* @param {binding.Entity} entity
* @param {Array<string>} path
* @return {binding.Entity[]}
*/
EntityManager.prototype.getSubEntitiesByPath = function getSubEntitiesByPath(entity, path) {
var _this12 = this;
var subEntities = [entity];
path.forEach(function (attributeName) {
var tmpSubEntities = [];
subEntities.forEach(function (subEntity) {
var curEntity = subEntity[attributeName];
if (!curEntity) {
return;
}
var attribute = _this12.metamodel.managedType(subEntity.constructor).getAttribute(attributeName);
if (attribute.isCollection) {
var iter = curEntity.entries();
for (var item = iter.next(); !item.done; item = iter.next()) {
var entry = item.value;
tmpSubEntities.push(entry[1]);
if (attribute.keyType && attribute.keyType.isEntity) {
tmpSubEntities.push(entry[0]);
}
}
} else {
tmpSubEntities.push(curEntity);
}
});
subEntities = tmpSubEntities;
});
return subEntities;
};
/**
* Delete the entity instance.
* @param {binding.Entity} entity
* @param {Object} options The delete options
* @return {Promise<binding.Entity>}
*/
EntityManager.prototype['delete'] = function _delete(entity, options) {
var _this13 = this;
var opt = options || {};
this.attach(entity);
var state = Metadata.get(entity);
return state.withLock(function () {
if (!state.version && !opt.force) {
throw new error.IllegalEntityError(entity);
}
var msg = new messages.DeleteObject(state.bucket, state.key);
_this13.addToBlackList(entity.id);
if (!opt.force) {
msg.ifMatch(state.version);
}
var refPromises = [_this13.send(msg).then(function () {
_this13.removeReference(entity);
state.setRemoved();
return entity;
})];
var subOptions = Object.assign({}, opt);
subOptions.depth = 0;
_this13.getSubEntities(entity, opt.depth).forEach(function (sub) {
refPromises.push(_this13.delete(sub, subOptions));
});
return Promise.all(refPromises).then(function () {
return entity;
});
});
};
/**
* Synchronize the persistence context to the underlying database.
*
* @return {Promise<*>}
*/
EntityManager.prototype.flush = function flush() {}
// TODO: implement this
/**
* Make an instance managed and persistent.
* @param {binding.Entity} entity - entity instance
* @return {void}
*/
;
EntityManager.prototype.persist = function persist(entity) {
this.attach(entity);
};
/**
* Refresh the state of the instance from the database, overwriting changes made to the entity, if any.
* @param {binding.Entity} entity - entity instance
* @param {Object} options The refresh options
* @return {Promise<binding.Entity>}
*/
EntityManager.prototype.refresh = function refresh(entity, options) {
var opt = options || {};
opt.refresh = true;
return this.load(entity.id, null, opt);
};
/**
* Attach the instance to this database context, if it is not already attached
* @param {binding.Entity} entity The entity to attach
* @return {void}
*/
EntityManager.prototype.attach = function attach(entity) {
if (!this.contains(entity)) {
var type = this.metamodel.entity(entity.constructor);
if (!type) {
throw new error.IllegalEntityError(entity);
}
if (this.containsById(entity)) {
throw new error.EntityExistsError(entity);
}
this._attach(entity);
}
};
EntityManager.prototype._attach = function _attach(entity) {
var metadata = Metadata.get(entity);
if (metadata.isAttached) {
if (metadata.db !== this) {
throw new error.EntityExistsError(entity);
}
} else {
metadata.db = this;
}
if (!metadata.id) {
if (metadata.type.name !== 'User' && metadata.type.name !== 'Role' && metadata.type.name !== 'logs.AppLog') {
metadata.id = DB_PREFIX + metadata.type.name + '/' + util.uuid();
}
}
if (metadata.id) {
this.entities[metadata.id] = entity;
}
};
EntityManager.prototype.removeReference = function removeReference(entity) {
var state = Metadata.get(entity);
if (!state) {
throw new error.IllegalEntityError(entity);
}
delete this.entities[state.id];
};
EntityManager.prototype.register = function register(user, password, loginOption) {
var _this14 = this;
var login = loginOption > UserFactory.LoginOption.NO_LOGIN;
if (this.me && login) {
throw new error.PersistentError('User is already logged in.');
}
return this.withLock(function () {
var msg = new messages.Register({ user: user, password: password, login: login });
return _this14._userRequest(msg, loginOption);
});
};
EntityManager.prototype.login = function login(username, password, loginOption) {
var _this15 = this;
if (this.me) {
throw new error.PersistentError('User is already logged in.');
}
return this.withLock(function () {
var msg = new messages.Login({ username: username, password: password });
return _this15._userRequest(msg, loginOption);
});
};
EntityManager.prototype.logout = function logout() {
var _this16 = this;
return this.withLock(function () {
return _this16.send(new messages.Logout()).then(_this16._logout.bind(_this16));
});
};
EntityManager.prototype.loginWithOAuth = function loginWithOAuth(provider, clientID, options) {
if (this.me) {
throw new error.PersistentError('User is already logged in.');
}
var opt = Object.assign({
title: 'Login with ' + provider,
timeout: 5 * 60 * 1000,
state: {},
loginOption: true
}, options);
if (opt.redirect) {
Object.assign(opt.state, { redirect: opt.redirect, loginOption: opt.loginOption });
}
var msg = void 0;
if (Message[provider + 'OAuth']) {
msg = new Message[provider + 'OAuth'](clientID, opt.scope, JSON.stringify(opt.state));
msg.addRedirectOrigin(this.connection.origin + this.connection.basePath);
} else {
throw new Error('OAuth provider ' + provider + ' not supported.');
}
var windowOptions = { width: opt.width, height: opt.height };
if (opt.redirect) {
// use oauth via redirect by opening the login in the same window
// for app wrappers we need to open the system browser
var isBrowser = document.URL.indexOf('http://') !== -1 || document.URL.indexOf('https://') !== -1;
this.openOAuthWindow(msg.request.path, isBrowser ? '_self' : '_system', windowOptions);
return new Promise(function () {});
}
var req = this._userRequest(msg, opt.loginOption);
this.openOAuthWindow(msg.request.path, opt.title, windowOptions);
return new Promise(function (resolve, reject) {
var timeout = setTimeout(function () {
reject(new error.PersistentError('OAuth login timeout.'));
}, opt.timeout);
req.then(resolve, reject).then(function () {
clearTimeout(timeout);
});
});
};
/**
* Opens a new window use for OAuth logins
* @param {string} url The url to open
* @param {string} targetOrTitle The target of the window, or the title of the popup
* @param {object} options Additional window options
* @return {void}
*/
EntityManager.prototype.openOAuthWindow = function openOAuthWindow(url, targetOrTitle, options) {
var str = Object.keys(options).filter(function (key) {
return options[key] !== undefined;
}).map(function (key) {
return key + '=' + options[key];
}).join(',');
open(url, targetOrTitle, str); // eslint-disable-line no-restricted-globals
};
EntityManager.prototype.renew = function renew(loginOption) {
var _this17 = this;
return this.withLock(function () {
var msg = new messages.Me();
return _this17._userRequest(msg, loginOption);
});
};
EntityManager.prototype.newPassword = function newPassword(username, password, _newPassword) {
var _this18 = this;
return this.withLock(function () {
var msg = new messages.NewPassword({ username: username, password: password, newPassword: _newPassword });
return _this18.send(msg, true).then(function (response) {
return _this18._updateUser(response.entity);
});
});
};
EntityManager.prototype.newPasswordWithToken = function newPasswordWithToken(token, newPassword, loginOption) {
var _this19 = this;
return this.withLock(function () {
return _this19._userRequest(new messages.NewPassword({ token: token, newPassword: newPassword }), loginOption);
});
};
EntityManager.prototype.resetPassword = function resetPassword(username) {
return this.send(new messages.ResetPassword({ username: username }));
};
EntityManager.prototype.changeUsername = function changeUsername(username, newUsername, password) {
return this.send(new messages.ChangeUsername({ username: username, newUsername: newUsername, password: password }));
};
EntityManager.prototype._updateUser = function _updateUser(obj, updateMe) {
var user = this.getReference(obj.id);
var metadata = Metadata.get(user);
metadata.setJson(obj, { persisting: true });
if (updateMe) {
this.me = user;
}
return user;
};
EntityManager.prototype._logout = function _logout() {
this.me = null;
this.token = null;
};
EntityManager.prototype._userRequest = function _userRequest(msg, loginOption) {
var _this20 = this;
var opt = loginOption === undefined ? true : loginOption;
var login = opt > UserFactory.LoginOption.NO_LOGIN;
if (login) {
this.tokenStorage.temporary = opt < UserFactory.LoginOption.PERSIST_LOGIN;
}
return this.send(msg, !login).then(function (response) {
return response.entity ? _this20._updateUser(response.entity, login) : null;
}, function (e) {
if (e.status === StatusCode.OBJECT_NOT_FOUND) {
if (login) {
_this20._logout();
}
return null;
}
throw e;
});
};
/**
* @param {string} devicetype The OS of the device (IOS/Android)
* @param {object} subscription WebPush subscription
* @param {model.Device} device
* @return {Promise<model.Device>}
*/
EntityManager.prototype.registerDevice = function registerDevice(devicetype, subscription, device) {
var _this21 = this;
var msg = new messages.DeviceRegister({ devicetype: devicetype, subscription: subscription, device: device });
msg.withCredentials = true;
return this.send(msg).then(function (response) {
return _this21.updateDevice(response.entity);
});
};
EntityManager.prototype.updateDevice = function updateDevice(obj) {
var device = this.getReference(obj.id);
var metadata = Metadata.get(device);
metadata.setJson(obj, { persisting: true });
this.deviceMe = device;
return device;
};
EntityManager.prototype.checkDeviceRegistration = function checkDeviceRegistration() {
var _this22 = this;
return this.send(new messages.DeviceRegistered()).then(function () {
_this22.isDeviceRegistered = true;
return true;
}, function (e) {
if (e.status === StatusCode.OBJECT_NOT_FOUND) {
_this22.isDeviceRegistered = false;
return false;
}
throw e;
});
};
EntityManager.prototype.pushDevice = function pushDevice(pushMessage) {
return this.send(new messages.DevicePush(pushMessage));
};
/**
* The given entity will be checked by the validation code of the entity type.
*
* @param {binding.Entity} entity
* @return {util.ValidationResult} result
*/
EntityManager.prototype.validate = function validate(entity) {
var type = Metadata.get(entity).type;
var result = new util.ValidationResult();
var iter = type.attributes();
for (var item = iter.next(); !item.done; item = iter.next()) {
var validate = new util.Validator(item.value.name, entity);
result.fields[validate.key] = validate;
}
var validationCode = type.validationCode;
if (validationCode) {
validationCode(result.fields);
}
return result;
};
/**
* Adds the given object id to the cacheWhiteList if needed.
* @param {string} objectId The id to add.
* @return {void}
*/
EntityManager.prototype.addToWhiteList = function addToWhiteList(objectId) {
if (!this.isCachingDisabled) {
if (this.bloomFilter.contains(objectId)) {
this.cacheWhiteList.add(objectId);
}
this.cacheBlackList.delete(objectId);
}
};
/**
* Adds the given object id to the cacheBlackList if needed.
* @param {string} objectId The id to add.
* @return {void}
*/
EntityManager.prototype.addToBlackList = function addToBlackList(objectId) {
if (!this.isCachingDisabled) {
if (!this.bloomFilter.contains(objectId)) {
this.cacheBlackList.add(objectId);
}
this.cacheWhiteList.delete(objectId);
}
};
EntityManager.prototype.refreshBloomFilter = function refreshBloomFilter() {
var _this23 = this;
if (this.isCachingDisabled) {
return Promise.resolve();
}
var msg = new messages.GetBloomFilter();
msg.noCache();
return this.send(msg).then(function (response) {
_this23.updateBloomFilter(response.entity);
return _this23.bloomFilter;
});
};
EntityManager.prototype.updateBloomFilter = function updateBloomFilter(bloomFilter) {
this.bloomFilter = new BloomFilter(bloomFilter);
this.cacheWhiteList = new Set();
this.cacheBlackList = new Set();
};
/**
* Checks the freshness of the bloom filter and does a reload if necessary
* @return {void}
*/
EntityManager.prototype.ensureBloomFilterFreshness = function ensureBloomFilterFreshness() {
var _this24 = this;
if (this.isCachingDisabled) {
return;
}
var now = new Date().getTime();
var refreshRate = this.bloomFilterRefresh * 1000;
if (this.bloomFilterLock.isReady && now - this.bloomFilter.creation > refreshRate) {
this.bloomFilterLock.withLock(function () {
return _this24.refreshBloomFilter();
});
}
};
/**
* Checks for a given id, if revalidation is required, the resource is stale or caching was disabled
* @param {string} id The object id to check
* @return {boolean} Indicates if the resource must be revalidated
*/
EntityManager.prototype.mustRevalidate = function mustRevalidate(id) {
if (util.isNode) {
return false;
}
this.ensureBloomFilterFreshness();
var refresh = this.isCachingDisabled || !this.bloomFilterLock.isReady;
refresh = refresh || !this.cacheWhiteList.has(id) && (this.cacheBlackList.has(id) || this.bloomFilter.contains(id));
return refresh;
};
/**
* @param {string} id To check the bloom filter
* @param {connector.Message} message To attach the headers
* @param {boolean} refresh To force the reload headers
* @return {void}
*/
EntityManager.prototype.ensureCacheHeader = function ensureCacheHeader(id, message, refresh) {
var noCache = refresh || this.mustRevalidate(id);
if (noCache) {
message.noCache();
}
};
/**
* Creates a absolute url for the given relative one
* @param {string} relativePath the relative url
* @param {boolean=} authorize indicates if authorization credentials should be generated and be attached to the url
* @return {string} a absolute url wich is optionaly signed with a resource token which authenticates the currently
* logged in user
*/
EntityManager.prototype.createURL = function createURL(relativePath, authorize) {
var path = this.connection.basePath + relativePath;
var append = false;
if (authorize && this.me) {
path = this.tokenStorage.signPath(path);
append = true;
} else {
path = path.split('/').map(encodeURIComponent).join('/');
}
if (this.mustRevalidate(relativePath)) {
path = path + (append ? '&' : '?') + 'BCB';
}
return this.connection.origin + path;
};
/**
* Requests a perpetual token for the given user
*
* Only users with the admin role are allowed to request an API token.
*
* @param {(Class<binding.Entity>|Class<binding.Managed>)} entityClass
* @param {binding.User|String} user The user object or id of the user object
* @return {Promise<*>}
*/
EntityManager.prototype.requestAPIToken = function requestAPIToken(entityClass, user) {
var userObj = this._getUserReference(entityClass, user);
var msg = new messages.UserToken(userObj.key);
return this.send(msg).then(function (resp) {
return resp.entity;
});
};
/**
* Revoke all created tokens for the given user
*
* This method will revoke all previously issued tokens and the user must login again.
*
* @param {(Class<binding.Entity>|Class<binding.Managed>)} entityClass
* @param {binding.User|String}