UNPKG

baqend

Version:

Baqend JavaScript SDK

1,224 lines (1,218 loc) 98 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); 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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EntityManager = void 0; var messages = __importStar(require("./message")); var binding_1 = require("./binding"); var util_1 = require("./util"); var connector_1 = require("./connector"); var caching_1 = require("./caching"); var GeoPoint_1 = require("./GeoPoint"); var metamodel_1 = require("./metamodel"); var query_1 = require("./query"); var error_1 = require("./error"); var intersection_1 = require("./intersection"); var Message_1 = require("./connector/Message"); var MFAError_1 = require("./error/MFAError"); var message = __importStar(require("./message")); var DB_PREFIX = '/db/'; var EntityManager = /** @class */ (function (_super) { __extends(EntityManager, _super); /** * @param entityManagerFactory The factory which of this entityManager instance */ function EntityManager(entityManagerFactory) { var _this = _super.call(this) || this; /** * Constructor for a new List collection */ _this.List = Array; /** * Constructor for a new Set collection */ _this.Set = Set; /** * Constructor for a new Map collection */ _this.Map = Map; /** * Constructor for a new GeoPoint */ _this.GeoPoint = GeoPoint_1.GeoPoint; /** * 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> */ _this.log = intersection_1.Logger.create(_this); /** * The connector used for requests */ _this.connection = null; /** * All managed and cached entity instances * @type Map<String,Entity> * @private */ _this.entities = {}; _this.modules = new intersection_1.Modules(_this); /** * The current logged in user object */ _this.me = null; /** * The current registered device object */ _this.deviceMe = null; /** * Returns the tokenStorage which will be used to authorize all requests. */ _this.tokenStorage = null; // is never null after em is ready /** * The bloom filter which contains staleness information of cached objects */ _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. */ _this.bloomFilterRefresh = 60; /** * Bloom filter refresh Promise */ _this.bloomFilterLock = new util_1.Lockable(); /** * A File factory for file objects. * The file factory can be called to create new instances for files. * The created instances implements the {@link File} interface */ _this.File = null; // is never null after the em is ready /** * the shared connection data if this EntityManager shares the credentials with the EntityManagerFactory * @private */ _this.connectData = null; _this.entityManagerFactory = entityManagerFactory; _this.metamodel = entityManagerFactory.metamodel; _this.code = entityManagerFactory.code; return _this; } Object.defineProperty(EntityManager.prototype, "isOpen", { /** * Determine whether the entity manager is open. * true until the entity manager has been closed */ get: function () { return !!this.connection; }, enumerable: false, configurable: true }); Object.defineProperty(EntityManager.prototype, "token", { /** * The authentication token if the user is logged in currently */ get: function () { return this.tokenStorage.token; }, /** * The authentication token if the user is logged in currently * @param value */ set: function (value) { this.tokenStorage.update(value); }, enumerable: false, configurable: true }); Object.defineProperty(EntityManager.prototype, "isCachingDisabled", { /** * Whether caching is disabled * @deprecated */ get: function () { return !this.isCachingEnabled(); }, enumerable: false, configurable: true }); /** * Whether caching is enabled */ EntityManager.prototype.isCachingEnabled = function () { return !!this.bloomFilter; }; Object.defineProperty(EntityManager.prototype, "isDeviceRegistered", { /** * Returns true if the device token is already registered, otherwise false. */ get: function () { return !!this.deviceMe; }, enumerable: false, configurable: true }); /** * Connects this entityManager, used for synchronous and asynchronous initialization * @param useSharedTokenStorage Indicates if the shared credentials should be used */ EntityManager.prototype.connected = function (useSharedTokenStorage) { var _a; this.connection = this.entityManagerFactory.connection; this.bloomFilterRefresh = this.entityManagerFactory.staleness; this.tokenStorage = useSharedTokenStorage ? this.entityManagerFactory.tokenStorage : new intersection_1.TokenStorage(this.connection.origin); this.connectData = useSharedTokenStorage ? this.entityManagerFactory.connectData : null; this.File = binding_1.FileFactory.create(this); this._createObjectFactory(this.metamodel.embeddables); this._createObjectFactory(this.metamodel.entities); if (this.connectData) { if (this.connectData.device) { this._updateDevice(this.connectData.device); } if (this.connectData.user) { this._updateUser(this.connectData.user, true); } } var bloomFilter = (_a = this.entityManagerFactory.connectData) === null || _a === void 0 ? void 0 : _a.bloomFilter; if (this.bloomFilterRefresh > 0 && bloomFilter && typeof util_1.atob !== 'undefined' && !util_1.isNode) { this._updateBloomFilter(bloomFilter); } }; /** * @param types * @return * @private */ EntityManager.prototype._createObjectFactory = function (types) { var values = Object.values(types); var _loop_1 = function (i) { var type = values[i]; var name_1 = type.name; if (this_1[name_1]) { type.typeConstructor = this_1[name_1]; Object.defineProperty(this_1, name_1, { value: type.createObjectFactory(this_1), }); } else { Object.defineProperty(this_1, name_1, { get: function () { Object.defineProperty(this, name_1, { value: type.createObjectFactory(this), }); return this[name_1]; }, set: function (typeConstructor) { type.typeConstructor = typeConstructor; }, configurable: true, }); } }; var this_1 = this; for (var i = 0; i < values.length; i += 1) { _loop_1(i); } }; EntityManager.prototype.send = function (message, ignoreCredentialError) { var _this = this; if (ignoreCredentialError === void 0) { ignoreCredentialError = true; } if (!this.connection) { throw new Error('This EntityManager is not connected.'); } var msg = message; msg.tokenStorage(this.tokenStorage); var result = this.connection.send(msg); if (!ignoreCredentialError) { result = result.catch(function (e) { if (e.status === connector_1.StatusCode.BAD_CREDENTIALS) { _this._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 entityClass * @param key * @return */ EntityManager.prototype.getReference = function (entityClass, key) { var id = null; var type; if (key) { var keyAsStr = key; type = this.metamodel.entity(entityClass); if (keyAsStr.indexOf(DB_PREFIX) === 0) { id = keyAsStr; } else { id = "".concat(type.ref, "/").concat(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.substring(0, keyIndex) : entityClass); } else { type = this.metamodel.entity(entityClass); } var entity = this.entities[id]; if (!entity) { entity = type.create(); var metadata = intersection_1.Metadata.get(entity); if (id) { metadata.id = id; } metadata.setUnavailable(); this._attach(entity); } return entity; }; /** * Creates an instance of {@link Builder<T>} for query creation and execution * * The query results are instances of the resultClass argument. * * @param resultClass - the type of the query result * @return A query builder to create one ore more queries for the specified class */ EntityManager.prototype.createQueryBuilder = function (resultClass) { return new query_1.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 */ EntityManager.prototype.clear = function () { 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 * and isOpen (which will return false). * * @return */ EntityManager.prototype.close = function () { this.connection = null; return this.clear(); }; /** * Check if the instance is a managed entity instance belonging to the current persistence context * * @param entity - entity instance * @return boolean indicating if entity is in persistence context */ EntityManager.prototype.contains = function (entity) { return !!entity && !!entity.id && this.entities[entity.id] === entity; }; /** * Check if an object with the id from the given entity is already attached * * @param entity - entity instance * @return boolean indicating if entity with same id is attached */ EntityManager.prototype.containsById = function (entity) { return !!(entity && entity.id && 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 entity The entity instance to detach. * @return */ EntityManager.prototype.detach = function (entity) { var _this = this; var state = intersection_1.Metadata.get(entity); return state.withLock(function () { _this.removeReference(entity); return Promise.resolve(entity); }); }; /** * Resolve the depth by loading the referenced objects of the given entity * * @param entity - entity instance * @param [options] The load options * @return */ EntityManager.prototype.resolveDepth = function (entity, options) { var _this = this; if (!options || !options.depth) { return Promise.resolve(entity); } var resolved = options.resolved || []; var promises = []; var subOptions = __assign(__assign({}, options), { resolved: resolved, depth: options.depth === true ? true : options.depth - 1 }); this.getSubEntities(entity, 1).forEach(function (subEntity) { if (subEntity !== null && resolved.indexOf(subEntity) === -1 && subEntity.id) { resolved.push(subEntity); promises.push(_this.load(subEntity.id, undefined, 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 entityClass - entity class * @param oid - Object ID * @param [options] The load options. * @return the loaded entity or null */ EntityManager.prototype.load = function (entityClass, oid, options) { var _this = this; var opt = options || {}; var entity = this.getReference(entityClass, oid); var state = intersection_1.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 && entity.version > response.entity.version) { opt.refresh = true; return _this.load(entityClass, oid, opt); } _this.addToWhiteList(response.entity.id); if (response.status !== connector_1.StatusCode.NOT_MODIFIED) { state.type.fromJsonValue(state, response.entity, entity, { persisting: true }); } return _this.resolveDepth(entity, opt); }, function (e) { if (e.status === connector_1.StatusCode.OBJECT_NOT_FOUND) { _this.removeReference(entity); state.setRemoved(); return null; } throw e; }); }; /** * @param entity * @param options * @return */ EntityManager.prototype.insert = function (entity, options) { var _this = this; var opt = options || {}; var isNew; return this._save(entity, opt, function (state, json) { if (state.version) { throw new error_1.PersistentError('Existing objects can\'t be inserted.'); } isNew = !state.id; return new messages.CreateObject(state.bucket, json); }).then(function (val) { if (isNew) { _this._attach(entity); } return val; }); }; /** * @param entity * @param options * @return */ EntityManager.prototype.update = function (entity, options) { var opt = options || {}; return this._save(entity, opt, function (state, json) { if (!state.version) { throw new error_1.PersistentError('New objects can\'t be inserted.'); } if (opt.force) { var version = json.version, jsonWithoutVersion = __rest(json, ["version"]); return new messages.ReplaceObject(state.bucket, state.key, jsonWithoutVersion) .ifMatch('*'); } return new messages.ReplaceObject(state.bucket, state.key, json) .ifMatch("".concat(state.version)); }); }; /** * @param entity * @param options The save options * @param withoutLock Set true to save the entity without locking * @return */ EntityManager.prototype.save = function (entity, options, withoutLock) { if (withoutLock === void 0) { withoutLock = false; } var opt = options || {}; var msgFactory = function (state, json) { if (opt.force) { if (!state.id) { throw new error_1.PersistentError('New special objects can\'t be forcefully saved.'); } var version = json.version, jsonWithoutVersion = __rest(json, ["version"]); return new messages.ReplaceObject(state.bucket, state.key, jsonWithoutVersion); } 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 entity * @param cb pre-safe callback * @return */ EntityManager.prototype.optimisticSave = function (entity, cb) { var _this = this; return intersection_1.Metadata.get(entity).withLock(function () { return _this._optimisticSave(entity, cb); }); }; /** * @param entity * @param cb pre-safe callback * @return * @private */ EntityManager.prototype._optimisticSave = function (entity, cb) { var _this = this; var abort = false; var abortFn = function () { abort = true; }; var promise = Promise.resolve(cb(entity, abortFn)); if (abort) { return Promise.resolve(entity); } return promise.then(function () { return (_this.save(entity, {}, true) .catch(function (e) { if (e.status === 412) { return _this.refresh(entity, {}) .then(function () { return _this._optimisticSave(entity, cb); }); } throw e; })); }); }; /** * Save the object state without locking * @param entity * @param options * @param msgFactory * @return * @private */ EntityManager.prototype._locklessSave = function (entity, options, msgFactory) { var _this = this; this.attach(entity); var state = intersection_1.Metadata.get(entity); var refPromises; var json; if (state.isAvailable) { // getting json will check all collections changes, therefore we must do it before proofing the dirty state json = state.type.toJsonValue(state, entity, { 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) { _this.removeReference(entity); state.id = response.entity.id; _this._attach(entity); } state.type.fromJsonValue(state, response.entity, entity, { persisting: options.refresh, onlyMetadata: !options.refresh, }); return entity; }, function (e) { if (e.status === connector_1.StatusCode.OBJECT_NOT_FOUND) { _this.removeReference(entity); state.setRemoved(); return null; } state.setDirty(); throw e; }); refPromises = [sendPromise]; } else { refPromises = [Promise.resolve(entity)]; } var subOptions = __assign({}, options); subOptions.depth = 0; this.getSubEntities(entity, options.depth).forEach(function (sub) { refPromises.push(_this._save(sub, subOptions, msgFactory)); }); return Promise.all(refPromises).then(function () { return entity; }); }; /** * Save and lock the object state * @param entity * @param options * @param msgFactory * @return * @private */ EntityManager.prototype._save = function (entity, options, msgFactory) { var _this = this; this.ensureBloomFilterFreshness(); var state = intersection_1.Metadata.get(entity); if (state.version) { this.addToBlackList(entity.id); } return state.withLock(function () { return _this._locklessSave(entity, options, msgFactory); }); }; /** * Returns all referenced sub entities for the given depth and root entity * @param entity * @param depth * @param [resolved] * @param initialEntity * @return */ EntityManager.prototype.getSubEntities = function (entity, depth, resolved, initialEntity) { if (resolved === void 0) { resolved = []; } if (!depth) { return resolved; } var obj = initialEntity || entity; var state = intersection_1.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 (resolved.indexOf(subEntity) === -1 && subEntity !== obj) { resolved.push(subEntity); this.getSubEntities(subEntity, depth === true ? depth : depth - 1, resolved, obj); } } } return resolved; }; /** * Returns all referenced one level sub entities for the given path * @param entity * @param path * @return */ EntityManager.prototype.getSubEntitiesByPath = function (entity, path) { var _this = this; var subEntities = [entity]; path.forEach(function (attributeName) { var tmpSubEntities = []; subEntities.forEach(function (subEntity) { var curEntity = subEntity[attributeName]; if (!curEntity) { return; } var attribute = _this.metamodel.managedType(subEntity.constructor).getAttribute(attributeName); if (attribute instanceof metamodel_1.PluralAttribute) { var iter = curEntity.entries(); for (var item = iter.next(); !item.done; item = iter.next()) { var entry = item.value; tmpSubEntities.push(entry[1]); if (attribute instanceof metamodel_1.MapAttribute && attribute.keyType.isEntity) { tmpSubEntities.push(entry[0]); } } } else { tmpSubEntities.push(curEntity); } }); subEntities = tmpSubEntities; }); return subEntities; }; /** * Delete the entity instance. * @param entity * @param options The delete options * @return */ EntityManager.prototype.delete = function (entity, options) { var _this = this; var opt = options || {}; this.attach(entity); var state = intersection_1.Metadata.get(entity); return state.withLock(function () { if (!state.version && !opt.force) { throw new error_1.IllegalEntityError(entity); } var msg = new messages.DeleteObject(state.bucket, state.key); _this.addToBlackList(entity.id); if (!opt.force) { msg.ifMatch("".concat(state.version)); } var refPromises = [_this.send(msg).then(function () { _this.removeReference(entity); state.setRemoved(); return entity; })]; var subOptions = __assign({}, opt); subOptions.depth = 0; _this.getSubEntities(entity, opt.depth).forEach(function (sub) { refPromises.push(_this.delete(sub, subOptions)); }); return Promise.all(refPromises).then(function () { return entity; }); }); }; /** * Synchronize the persistence context to the underlying database. * * @return */ EntityManager.prototype.flush = function () { throw new Error('Not implemented.'); }; /** * Make an instance managed and persistent. * @param entity - entity instance * @return */ EntityManager.prototype.persist = function (entity) { this.attach(entity); }; /** * Refresh the state of the instance from the database, overwriting changes made to the entity, if any. * @param entity - entity instance * @param options The refresh options * @return */ EntityManager.prototype.refresh = function (entity, options) { if (!entity.id) { // entity is not persisted so far return Promise.resolve(entity); } return this.load(entity.id, undefined, __assign(__assign({}, options), { refresh: true })); }; /** * Attach the instance to this database context, if it is not already attached * @param entity The entity to attach * @return */ EntityManager.prototype.attach = function (entity) { if (!this.contains(entity)) { var type = this.metamodel.entity(entity.constructor); if (!type) { throw new error_1.IllegalEntityError(entity); } if (this.containsById(entity)) { throw new error_1.EntityExistsError(entity); } this._attach(entity); } }; EntityManager.prototype._attach = function (entity) { var metadata = intersection_1.Metadata.get(entity); if (metadata.isAttached) { if (metadata.db !== this) { throw new error_1.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 = "".concat(DB_PREFIX + metadata.type.name, "/").concat((0, util_1.uuid)()); } } if (metadata.id) { this.entities[metadata.id] = entity; } }; EntityManager.prototype.removeReference = function (entity) { var state = intersection_1.Metadata.get(entity); if (!state || !state.id) { throw new error_1.IllegalEntityError(entity); } delete this.entities[state.id]; }; EntityManager.prototype.register = function (user, password, loginOption) { var _this = this; var login = loginOption > binding_1.LoginOption.NO_LOGIN; if (this.me && login) { throw new error_1.PersistentError('User is already logged in.'); } return this.withLock(function () { var msg = new messages.Register({ user: user, password: password, login: login }); return _this._userRequest(msg, loginOption); }); }; EntityManager.prototype.login = function (username, password, loginOption) { var _this = this; if (this.me) { throw new error_1.PersistentError('User is already logged in.'); } return this.withLock(function () { var msg = new messages.Login({ username: username, password: password }); return _this._userRequest(msg, loginOption); }); }; EntityManager.prototype.logout = function () { var _this = this; return this.withLock(function () { return _this.send(new messages.Logout()).then(_this._logout.bind(_this)); }); }; /** * Starts the MFA initiate process - note you must be logged in, to start the mfa setup process * * @returns A promise that resolves to an object with the following properties: * - qrCode: A Base64 representation of the QR code for MFA setup. * - keyUri: The URI for the MFA secret key. * @example * const { qrCode, keyUri } = await db.initMFA(); * const code = await setupMFADevice(qrCode, keyUri); * const user = await db.finishMFA(code); */ EntityManager.prototype.initMFA = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.send(new messages.MFAInitChallenge()).then(function (resp) { return { qrCode: resp.entity.qrCode, keyUri: resp.entity.keyUri }; })]; }); }); }; /** * Finishes the MFA (Multi-Factor Authentication) initiation process. * * @param code - The verification code for MFA. * @returns A promise that resolves with the user object of the logged-in user. */ EntityManager.prototype.finishMFA = function (code) { var _this = this; return this.send(new messages.MFAInitFinish({ code: code })).then(function (resp) { return _this.User.me; // to be here user is already logged in; }); }; /** * Submit a verification code after a login * * @param code - A 6 digit verification code * @param token - An MFA token obtained during the login process * @return The logged-in user object */ EntityManager.prototype.submitMFACode = function (code, token) { return __awaiter(this, void 0, void 0, function () { var loginType, msg; var _this = this; return __generator(this, function (_a) { loginType = this.tokenStorage.temporary ? binding_1.LoginOption.SESSION_LOGIN : binding_1.LoginOption.PERSIST_LOGIN; msg = new messages.MFAToken({ authToken: token, code: code, global: loginType === binding_1.LoginOption.PERSIST_LOGIN, }); return [2 /*return*/, this.withLock(function () { return _this._userRequest(msg, loginType); })]; }); }); }; /** * Disables Multi-Factor Authentication for the currently logged in user. * * @throws {PersistentError} - Thrown when the user is not logged in. * @return A promise that resolves when Multi-Factor Authentication is successfully disabled. */ EntityManager.prototype.disableMFA = function () { if (!this.User.me) throw new error_1.PersistentError('User not Logged in'); return this.send(new messages.MFADelete()); }; /** * Returns the current MFA status of the user * * @returns A promise that resolves to the MFA status of the user. * Possible values are 'ENABLED' if MFA is enabled, 'DISABLED' if MFA is * disabled, or 'PENDING' if MFA status is pending. */ EntityManager.prototype.getMFAStatus = function () { return this.send(new messages.MFAStatus()).then(function (resp) { return resp.entity; }); }; EntityManager.prototype.loginWithOAuth = function (provider, options) { if (!this.connection) { throw new Error('This EntityManager is not connected.'); } if (this.me) { throw new error_1.PersistentError('User is already logged in.'); } var opt = __assign({ title: "Login with ".concat(provider), timeout: 5 * 60 * 1000, state: {}, loginOption: true, oAuthVersion: 2, open: util_1.openWindow }, options); if (opt.deviceCode) { return this._loginOAuthDevice(provider.toLowerCase(), opt); } if (opt.oAuthVersion !== 1 && !opt.path && !opt.deviceCode) { throw new Error('No OAuth path is provided to start the OAuth flow.'); } if (opt.redirect) { Object.assign(opt.state, { redirect: opt.redirect, loginOption: opt.loginOption }); } var oAuthEndpoint = "".concat(this.connection.origin).concat(this.connection.basePath, "/db/User/OAuth/").concat(provider.toLowerCase()); var url = opt.oAuthVersion === 1 ? oAuthEndpoint : (0, Message_1.appendQueryParams)(opt.path, { client_id: opt.clientId, scope: opt.scope, state: JSON.stringify(opt.state), redirect_uri: oAuthEndpoint, }); var windowOptions = { title: opt.title, width: opt.width, height: opt.height, }; if (opt.redirect) { // use oauth via redirect by opening the login in the same window return opt.open(url, __assign({ target: '_self' }, windowOptions)) || url; } var req = this._userRequest(new connector_1.OAuthMessage(), opt.loginOption); if (!opt.open(url, windowOptions)) { throw new Error('The OAuth flow with a Pop-Up can only be issued in browsers. Add a redirect URL to the options to return to your app via that redirect after the OAuth flow succeed.'); } return new Promise(function (resolve, reject) { var timeout = setTimeout(function () { reject(new error_1.PersistentError('OAuth login timeout.')); }, opt.timeout); req.then(resolve, reject).then(function () { clearTimeout(timeout); }); }); }; EntityManager.prototype._loginOAuthDevice = function (provider, opt) { var _this = this; return this._userRequest(new messages.OAuth2(provider, opt.deviceCode), opt.loginOption) .catch(function () { return new Promise(function (resolve) { return setTimeout(resolve, 5000); }) .then(function () { return _this._loginOAuthDevice(provider, opt); }); }); }; EntityManager.prototype.renew = function (loginOption) { var _this = this; return this.withLock(function () { var msg = new messages.Me(); return _this._userRequest(msg, loginOption); }); }; EntityManager.prototype.newPassword = function (username, password, newPassword) { var _this = this; return this.withLock(function () { var msg = new messages.NewPassword({ username: username, password: password, newPassword: newPassword }); return _this.send(msg, true).then(function (response) { return _this._updateUser(response.entity); }); }); }; EntityManager.prototype.newPasswordWithToken = function (token, newPassword, loginOption) { var _this = this; return this.withLock(function () { return (_this._userRequest(new messages.NewPassword({ token: token, newPassword: newPassword }), loginOption)); }); }; EntityManager.prototype.resetPassword = function (username) { return this.send(new messages.ResetPassword({ username: username })); }; EntityManager.prototype.changeUsername = function (username, newUsername, password) { return this.send(new messages.ChangeUsername({ username: username, newUsername: newUsername, password: password })); }; EntityManager.prototype._updateUser = function (obj, updateMe) { if (updateMe === void 0) { updateMe = false; } var user = this.getReference(obj.id); var metadata = intersection_1.Metadata.get(user); metadata.type.fromJsonValue(metadata, obj, user, { persisting: true }); if (updateMe) { this.me = user; if (this.connectData) { this.connectData.user = obj; } } return user; }; EntityManager.prototype._logout = function () { this.me = null; this.token = null; if (this.connectData) { delete this.connectData.user; } }; EntityManager.prototype._userRequest = function (msg, loginOption) { var _this = this; var opt = loginOption === undefined ? true : loginOption; var login = opt > binding_1.LoginOption.NO_LOGIN; if (login) { this.tokenStorage.temporary = opt < binding_1.LoginOption.PERSIST_LOGIN; } return this.send(msg, !login) .then(function (response) { return response.entity ? _this._updateUser(response.entity, login) : null; }, function (e) { if (e.status === connector_1.StatusCode.OBJECT_NOT_FOUND) { if (login) { _this._logout(); } return null; } if (e.status === connector_1.StatusCode.FORBIDDEN) { var data = e.data; throw new MFAError_1.MFAError(data['baqend-mfa-auth-token']); // If MFA is required: throw an error containing the auth token } throw e; }); }; /** * @param deviceType The OS of the device (IOS/Android) * @param subscription WebPush subscription * @param device * @return */ EntityManager.prototype.registerDevice = function (deviceType, subscription, device) { var _this = this; var msg = new messages.DeviceRegister({ devicetype: deviceType, subscription: subscription, device: device }); msg.withCredentials = true; return this.send(msg) .then(function (response) { return _this._updateDevice(response.entity); }); }; EntityManager.prototype._updateDevice = function (obj) { var device = this.getReference(obj.id); var metadata = intersection_1.Metadata.get(device); metadata.type.fromJsonValue(metadata, obj, device, { persisting: true }); this.deviceMe = device; if (this.connectData) { this.connectData.device = obj; } return device; }; EntityManager.prototype.checkDeviceRegistration = function () { return this.send(new messages.DeviceRegistered()) .then(function () { return true; }, function (e) { if (e.status === connector_1.StatusCode.OBJECT_NOT_FOUND) { return false; } throw e; }); }; EntityManager.prototype.pushDevice = function (pushMessage) { return this.send(new messages.DevicePush(pushMessage.toJSON())); }; /** * The given entity will be checked by the validation code of the entity type. * * @param entity * @return result */ EntityManager.prototype.validate = function (entity) { var type = intersection_1.Metadata.get(entity).type; var result = new intersection_1.ValidationResult(); var iter = type.attributes(); for (var item = iter.next(); !item.done; item = iter.next()) { var validate = new intersection_1.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 objectId The id to add. * @return */ EntityManager.prototype.addToWhiteList = function (objectId) { if (this.isCachingEnabled()) { if (this.bloomFilter.contains(objectId) || this.cacheBlackList.has(objectId)) { this.cacheWhiteList.add(objectId); } } }; /** * Adds the given object id to the cacheBlackList if needed. * @param objectId The id to add. * @return */ EntityManager.prototype.addToBlackList = function (objectId) { if (this.isCachingEnabled() && objectId) { if (!this.bloomFilter.contains(objectId)) { this.cacheBlackList.add(objectId); } this.cacheWhiteList.delete(objectId); } }; EntityManager.prototype.refreshBloomFilter = function () { var _this = this; if (!this.isCachingEnabled()) { return Promise.resolve(null); } var msg = new messages.GetBloomFilter(); msg.noCache(); return this.send(msg).then(function (response) { _this._updateBloomFilter(response.entity); return _this.bloomFilter; }); }; EntityManager.prototype._updateBloomFilter = function (bloomFilter) { this.bloomFilter = new caching_1.BloomFilter(bloomFilter); this.cacheWhiteList = new Set(); this.c