baqend
Version:
Baqend JavaScript SDK
1,224 lines (1,218 loc) • 98 kB
JavaScript
"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