UNPKG

@harishreddym/baqend

Version:

Baqend JavaScript SDK

1,705 lines (1,407 loc) 545 kB
/*! * 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_(67); /** * 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; },{"67":67}],2:[function(_dereq_,module,exports){ 'use strict'; var messages = _dereq_(34); var error = _dereq_(33); var binding = _dereq_(19); var util = _dereq_(75); var query = _dereq_(61); var UserFactory = _dereq_(18); var Metadata = _dereq_(65); var Message = _dereq_(25); var BloomFilter = _dereq_(21); var deorecated = _dereq_(73); 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}