diff --git a/ValueFormatter.js b/ValueFormatter.js index 55276a1..62fa167 100644 --- a/ValueFormatter.js +++ b/ValueFormatter.js @@ -10,6 +10,7 @@ const { AsyncSeriesEventEmitter } = require('@themost/events'); const { round } = require('@themost/query'); const MD5 = require('crypto-js/md5'); const { DataAttributeResolver } = require('./data-attribute-resolver'); +const { firstValueFrom } = require('rxjs'); const testFieldRegex = /^\$\w+(\.\w+)*$/g; @@ -166,15 +167,14 @@ class ValueDialect { * @param {string=} property * @returns Promise */ - $user(property) { + async $user(property) { const selectAttribute = property || 'id'; - let name = this.context.user && this.context.user.name; - if (Object.prototype.hasOwnProperty.call(this.context, 'interactiveUser') === true) { - name = this.context.interactiveUser && this.context.interactiveUser.name; + const source$ = this.context.interactiveUser ? this.context.interactiveUser$ : this.context.user$; + const user = await firstValueFrom(source$); + if (user == null) { + return null; } - return this.context.model('User').asQueryable().where((x, username) => { - return x.name === username && x.name != null && x.name != 'anonymous'; - }, name).select(selectAttribute).value(); + return getProperty(user, selectAttribute); } /** * A shorthand for $user method diff --git a/data-context.d.ts b/data-context.d.ts index bac9b69..c89cc45 100644 --- a/data-context.d.ts +++ b/data-context.d.ts @@ -1,8 +1,12 @@ // MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved +import { DataModel } from "data-model"; import {DataAdapter, DataContext} from "./types"; -import { DataAdapterBase } from '@themost/common'; +import { ConfigurationBase, DataAdapterBase } from '@themost/common'; export declare class DefaultDataContext extends DataContext { + model(name: any): DataModel; + getConfiguration(): ConfigurationBase; + finalize(callback?: (err?: Error) => void): void; constructor(); readonly name: string; getDb(): DataAdapterBase; @@ -10,6 +14,9 @@ export declare class DefaultDataContext extends DataContext { } export declare class NamedDataContext extends DataContext { + model(name: any): DataModel; + getConfiguration(): ConfigurationBase; + finalize(callback?: (err?: Error) => void): void; constructor(name: string); readonly name: string; getName(): string diff --git a/data-context.js b/data-context.js index 1700133..8938802 100644 --- a/data-context.js +++ b/data-context.js @@ -1,388 +1,279 @@ -// MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved -var _ = require('lodash'); -var {TraceUtils} = require('@themost/common'); -var {LangUtils} = require('@themost/common'); -var {DataContext} = require('./types'); -var {DataConfigurationStrategy} = require('./data-configuration'); -var cfg = require('./data-configuration'); -var Symbol = require('symbol'); -var nameProperty = Symbol('name'); -var { DataModel } = require('./data-model'); +/*eslint no-var: "error"*/ +const {DataError} = require('@themost/common'); +const {DataContext} = require('./types'); +const {DataConfigurationStrategy} = require('./data-configuration'); +const cfg = require('./data-configuration'); +const { DataModel } = require('./data-model'); -/** - * @classdesc Represents the default data context of MOST Data Applications. - * The default data context uses the adapter which is registered as the default adapter in application configuration. - * @description - ``` - adapters: [ - ... - { "name":"development", "invariantName":"...", "default":false, - "options": { - "server":"localhost", - "user":"user", - "password":"password", - "database":"test" - } -}, - { "name":"development_with_pool", "invariantName":"pool", "default":true, - "options": { - "adapter":"development" +class DefaultDataContext extends DataContext { + constructor() { + super(); + Object.defineProperty(this, '_db', { + writable: true, + enumerable: false, + configurable: true, + value: null + }); + Object.defineProperty(this, 'name', { + writable: true, + enumerable: false, + configurable: true, + value: 'default' + }); } -} - ... - ] - ``` - * @class - * @constructor - * @augments {DataContext} - * @property {DataAdapter} db - Gets a data adapter based on the current configuration settings. - */ -function DefaultDataContext() -{ - /** - * @type {import('./types').DataAdapter} - */ - var _db= null; - /** - * @name DataAdapter#hasConfiguration - * @type {Function} - * @param {Function} getConfigurationFunc - */ + /** - * @private + * Gets the current data adapter instance + * @returns {import('@themost/common').DataAdapterBase} */ - this._finalize = function(cb) { - if (_db) { - return _db.close(function(err) { - // destroy db context - _db = null; - return cb(err); - }); - } - return cb(); - }; - var self = this; - // set data context name with respect to DataContext implementation - var _name = 'default'; - Object.defineProperty(this, 'name', { - enumerable: false, - configurable: true, - get: function() { - return _name; + get db() { + if (this._db) { + return this._db; } - }); - - self.getDb = function() { - - if (_db) - return _db; - var er; - //otherwise load database options from configuration - var strategy = self.getConfiguration().getStrategy(DataConfigurationStrategy); - var adapter = _.find(strategy.adapters, function(x) { - return x['default']; + // otherwise, load database options from configuration + const strategy = this.getConfiguration().getStrategy(DataConfigurationStrategy); + const adapter = strategy.adapters.find((x) => { + return x.default === true; }); - if (_.isNil(adapter)) { - er = new Error('The default data adapter is missing.'); er.code = 'EADAPTER'; - throw er; + if (adapter == null) { + throw new DataError('ERR_ADAPTER', 'The default data adapter is missing.'); } /** * @type {*} */ - var adapterType = strategy.adapterTypes.get(adapter.invariantName); + const adapterType = strategy.adapterTypes instanceof Map ? strategy.adapterTypes.get(adapter.invariantName) : strategy.adapterTypes[adapter.invariantName]; //validate data adapter type if (adapterType == null) { - er = new Error('Invalid adapter type.'); er.code = 'ERR_ADAPTER_TYPE'; - throw er; + throw new DataError('ERR_ADAPTER_TYPE', 'Invalid adapter type.'); } // get data adapter instance - var createInstance = adapterType.createInstance; + const createInstance = adapterType.createInstance; // get adapter type constructor if any - var AdapterTypeCtor = adapterType.type; + const AdapterTypeCtor = adapterType.type; // create adapter instance - if (typeof AdapterTypeCtor === 'function') { - _db = new AdapterTypeCtor(adapter.options); + if (typeof AdapterTypeCtor === 'function') { + this._db = new AdapterTypeCtor(adapter.options); } else if (typeof createInstance === 'function') { - _db = createInstance(adapter.options); + this._db = createInstance(adapter.options); } else { // throw error - var err = new Error('The given adapter type is invalid. Adapter type constructor is undefined.'); - err.code = 'ERR_ADAPTER_TYPE'; - throw err; + throw new DataError('ERR_ADAPTER_TYPE', 'The given adapter type is invalid. Adapter type constructor is undefined.'); } - if (typeof _db.hasConfiguration === 'function') { - _db.hasConfiguration(function() { - return self.getConfiguration(); + if (typeof this._db.hasConfiguration === 'function') { + this._db.hasConfiguration(() => { + return this.getConfiguration(); }); } - return _db; - }; + return this._db; + } - self.setDb = function(value) { - /** - * @type {DataAdapter|*} - */ - _db = value; - if (_db) { - if (typeof _db.hasConfiguration === 'function') { - _db.hasConfiguration(function() { - return self.getConfiguration(); + /** + * Sets a data adapter instance + * @param {import('@themost/common').DataAdapterBase} value + */ + set db(value) { + this._db = value; + if (this._db) { + if (typeof this._db.hasConfiguration === 'function') { + this._db.hasConfiguration(() => { + return this.getConfiguration(); }); } } - }; - - delete self.db; - - Object.defineProperty(self, 'db', { - get: function() { - return self.getDb(); - }, - set: function(value) { - self.setDb(value); - }, - configurable: true, - enumerable:false - }); - -} - -LangUtils.inherits(DefaultDataContext, DataContext); - -/** - * Gets an instance of DataConfiguration class which is associated with this data context - * @returns {import('@themost/common').ConfigurationBase} - */ -DefaultDataContext.prototype.getConfiguration = function() { - return cfg.getCurrent(); -}; + } -/** - * Gets an instance of DataModel class based on the given name. - * @param {*} name - A variable that represents the model name. - * @returns {DataModel} - An instance of DataModel class associated with this data context. - */ -DefaultDataContext.prototype.model = function(name) { - var self = this; - if (name == null) { - return null; + /** + * Gets an instance of DataConfiguration class which is associated with this data context + * @returns {import('@themost/common').ConfigurationBase} + */ + getConfiguration() { + return cfg.getCurrent(); } - var modelName = name; - // if model is a function (is a constructor) - if (typeof name === 'function') { - // try to get EdmMapping.entityType() decorator - if (Object.prototype.hasOwnProperty.call(name, 'entityTypeDecorator')) { - // if entityTypeDecorator is string - if (typeof name.entityTypeDecorator === 'string') { - // get model name - modelName = name.entityTypeDecorator; + /** + * Gets an instance of DataModel class based on the given name. + * @param {*} name - A variable that represents the model name. + * @returns {DataModel} - An instance of DataModel class associated with this data context. + */ + model(name) { + const self = this; + if (name == null) { + return null; + } + let modelName = name; + // if model is a function (is a constructor) + if (typeof name === 'function') { + // try to get EdmMapping.entityType() decorator + if (Object.prototype.hasOwnProperty.call(name, 'entityTypeDecorator')) { + // if entityTypeDecorator is string + if (typeof name.entityTypeDecorator === 'string') { + // get model name + modelName = name.entityTypeDecorator; + } + } else { + // get function name as the requested model + modelName = name.name; } - } else { - // get function name as the requested model - modelName = name.name; } + const obj = this.getConfiguration().getStrategy(DataConfigurationStrategy).model(modelName); + if (obj == null) + return null; + const model = new DataModel(obj); + //set model context + model.context = self; + //return model + return model; } - var obj = self.getConfiguration().getStrategy(DataConfigurationStrategy).model(modelName); - if (_.isNil(obj)) - return null; - var model = new DataModel(obj); - //set model context - model.context = self; - //return model - return model; -}; -/** - * Finalizes the current data context - * @param {Function} cb - A callback function where the first argument will contain the Error object if an error occurred, or null otherwise. - */ -DefaultDataContext.prototype.finalize = function(cb) { - cb = cb || function () {}; - void this._finalize(function(err) { - if (err) { - TraceUtils.error('An error occurred while finalizing the underlying database context.'); - TraceUtils.error(err); - } - return cb(); - }); -}; - - -/** - * @classdesc Represents a data context based on a data adapter's name. - * The specified adapter name must be registered in application configuration. - * @class - * @constructor - * @augments DataContext - * @property {DataAdapter} db - Gets a data adapter based on the given adapter's name. - */ -function NamedDataContext(name) -{ - NamedDataContext.super_.bind(this)(); - /** - * @type {DataAdapter} - * @private - */ - var _db; /** - * @private + * Finalizes the current data context + * @param {Function} cb - A callback function where the first argument will contain the Error object if an error occurred, or null otherwise. */ - this._finalize = function(cb) { - if (_db) { - return _db.close(function(err) { - // destroy db context - _db = null; + finalize(cb) { + cb = cb || function () { }; + if (this._db) { + return this._db.close((err) => { + this._db = null; return cb(err); }); } return cb(); - }; - var self = this; - self[nameProperty] = name; + } + +} + +class NamedDataContext extends DataContext { + /** + * @type {string} name + */ + constructor(name) { + + super(); - self.getDb = function() { - if (_db) - return _db; - var strategy = self.getConfiguration().getStrategy(DataConfigurationStrategy); + Object.defineProperty(this, '_db', { + writable: true, + enumerable: false, + configurable: true, + value: null + }); + + Object.defineProperty(this, 'name', { + writable: false, + enumerable: false, + configurable: true, + value: name + }); + + } + + get db() { + if (this._db) { + return this._db; + } + const strategy = this.getConfiguration().getStrategy(DataConfigurationStrategy); //otherwise load database options from configuration - var adapter = strategy.adapters.find(function(x) { - return x.name === self[nameProperty]; + const adapter = strategy.adapters.find((x) => { + return x.name === this.name; }); - var er; - if (typeof adapter ==='undefined' || adapter===null) { - er = new Error('The specified data adapter is missing.'); er.code = 'EADAPTER'; - throw er; + if (adapter == null) { + throw new DataError('ERR_ADAPTER', 'The specified data adapter is missing.'); } //get data adapter type - var adapterType = strategy.adapterTypes.get(adapter.invariantName); + const adapterType = strategy.adapterTypes instanceof Map ? strategy.adapterTypes.get(adapter.invariantName) : strategy.adapterTypes[adapter.invariantName]; // get data adapter instance - var createInstance = adapterType.createInstance; + const createInstance = adapterType.createInstance; // get adapter type constructor if any - var AdapterTypeCtor = adapterType.type; + const AdapterTypeCtor = adapterType.type; // create adapter instance - if (typeof AdapterTypeCtor === 'function') { - _db = new AdapterTypeCtor(adapter.options); + if (typeof AdapterTypeCtor === 'function') { + this._db = new AdapterTypeCtor(adapter.options); } else if (typeof createInstance === 'function') { - _db = createInstance(adapter.options); + this._db = createInstance(adapter.options); } else { // throw error - var err = new Error('The given adapter type is invalid. Adapter type constructor is undefined.'); - err.code = 'ERR_ADAPTER_TYPE'; - throw err; + throw new DataError('ERR_ADAPTER_TYPE', 'The given adapter type is invalid. Adapter type constructor is undefined.'); } - if (typeof _db.hasConfiguration === 'function') { - _db.hasConfiguration(function() { - return self.getConfiguration(); + if (typeof this._db.hasConfiguration === 'function') { + this._db.hasConfiguration(() => { + return this.getConfiguration(); }); } - return _db; - }; + return this._db; + } - /** - * @param {DataAdapter|*} value - */ - self.setDb = function(value) { - _db = value; - if (_db) { - if (typeof _db.hasConfiguration === 'function') { - _db.hasConfiguration(function() { - return self.getConfiguration(); + set db(value) { + this._db = value; + if (this._db) { + if (typeof this._db.hasConfiguration === 'function') { + this._db.hasConfiguration(() => { + return this.getConfiguration(); }); } } - - }; + } /** - * @name NamedDataContext#db - * @type {DataAdapter} + * Gets a string which represents the name of this context + * @returns {string} */ - - Object.defineProperty(self, 'db', { - get : function() { - return self.getDb(); - }, - set : function(value) { - self.setDb(value); - }, - configurable : true, - enumerable:false }); - + getName() { + return this.name; + } /** - * @name NamedDataContext#name - * @type {string} + * Gets an instance of DataConfiguration class which is associated with this data context + * @returns {DataConfiguration} */ - Object.defineProperty(self, 'name', { - get: function () { - return self[nameProperty]; - } - }); - -} -LangUtils.inherits(NamedDataContext, DataContext); - -/** - * Gets a string which represents the name of this context - * @returns {string} - */ -NamedDataContext.prototype.getName = function() { - return this[nameProperty]; -}; - -/** - * Gets an instance of DataConfiguration class which is associated with this data context - * @returns {DataConfiguration} - */ -NamedDataContext.prototype.getConfiguration = function() { - return cfg.getNamedConfiguration(this.name); -}; -/** - * Gets an instance of DataModel class based on the given name. - * @param name {string} - A string that represents the model name. - * @returns {DataModel} - An instance of DataModel class associated with this data context. - */ -NamedDataContext.prototype.model = function(name) { - var self = this; - if (name == null) { - return null; + getConfiguration() { + return cfg.getNamedConfiguration(this.name); } - var modelName = name; - // if model is a function (is a constructor) - if (typeof name === 'function') { - // try to get EdmMapping.entityType() decorator - if (Object.prototype.hasOwnProperty.call(name, 'entityTypeDecorator')) { - // if entityTypeDecorator is string - if (typeof name.entityTypeDecorator === 'string') { - // get model name - modelName = name.entityTypeDecorator; + /** + * Gets an instance of DataModel class based on the given name. + * @param name {string} - A string that represents the model name. + * @returns {DataModel} - An instance of DataModel class associated with this data context. + */ + model(name) { + const self = this; + if (name == null) { + return null; + } + let modelName = name; + // if model is a function (is a constructor) + if (typeof name === 'function') { + // try to get EdmMapping.entityType() decorator + if (Object.prototype.hasOwnProperty.call(name, 'entityTypeDecorator')) { + // if entityTypeDecorator is string + if (typeof name.entityTypeDecorator === 'string') { + // get model name + modelName = name.entityTypeDecorator; + } + } else { + // get function name as the requested model + modelName = name.name; } - } else { - // get function name as the requested model - modelName = name.name; } + const obj = this.getConfiguration().getStrategy(DataConfigurationStrategy).model(modelName); + if (obj == null) + return null; + const model = new DataModel(obj); + //set model context + model.context = self; + //return model + return model; + } - var obj = self.getConfiguration().getStrategy(DataConfigurationStrategy).model(modelName); - if (_.isNil(obj)) - return null; - var model = new DataModel(obj); - //set model context - model.context = self; - //return model - return model; -}; -NamedDataContext.prototype.finalize = function(cb) { - cb = cb || function () {}; - this._finalize(function(err) { - if (err) { - TraceUtils.error('An error occurred while finalizing the underlying database context.'); - TraceUtils.error(err); + finalize(cb) { + cb = cb || function () { }; + if (this._db) { + return this._db.close((err) => { + this._db = null; + return cb(err); + }); } - cb(); - }); -}; + return cb(); + } +} module.exports = { DefaultDataContext, NamedDataContext -} +} \ No newline at end of file diff --git a/data-permission.js b/data-permission.js index 71bab52..3313009 100644 --- a/data-permission.js +++ b/data-permission.js @@ -8,12 +8,11 @@ var async = require('async'); var { AccessDeniedError } = require('@themost/common'); var { DataConfigurationStrategy } = require('./data-configuration'); var _ = require('lodash'); -var { DataCacheStrategy } = require('./data-cache'); var Q = require('q'); var { hasOwnProperty } = require('./has-own-property'); var { at } = require('lodash'); var { DataModelFilterParser } = require('./data-model-filter.parser'); - +var { firstValueFrom } = require('rxjs'); /** * @class * @constructor @@ -319,7 +318,9 @@ DataPermissionEventListener.prototype.validate = function (event, callback) { //validate throwError if (typeof event.throwError === 'undefined') event.throwError = true; - context.user = context.user || { name: 'anonymous', authenticationType: 'None' }; + if (context.user == null) { + context.setUser({ name:'anonymous',authenticationType:'None' }); + } //description: Use unattended execution account as an escape permission check account var authSettings = context.getConfiguration().getStrategy(DataConfigurationStrategy).getAuthSettings(); if (authSettings) { @@ -634,122 +635,39 @@ DataPermissionEventListener.prototype.validate = function (event, callback) { }); }; -/** - * @private - * @type {string} - */ -var ANONYMOUS_USER_CACHE_PATH = '/User/anonymous'; -/** - * @param {DataContext} context - * @param {function(Error=,*=)} callback - * @private - */ -function anonymousUser(context, callback) { - queryUser(context, 'anonymous', function (err, result) { - if (err) { - callback(err); - } - else { - callback(null, result || { id: null, name: 'anonymous', groups: [], enabled: false }); - } - }); -} -/** - * - * @param {DataContext} context - * @param {string} username - * @param {function(Error=,*=)} callback - * @private - */ -function queryUser(context, username, callback) { - try { - if (_.isNil(context)) { - return callback(); - } - var users = context.model('User'); - if (_.isNil(users)) { - return callback(); - } - users.where('name').equal(username).silent().select('id', 'name').expand('groups').getTypedItem().then(function (result) { - return callback(null, result); - }).catch(function (err) { - return callback(err); - }); - } - catch (err) { - callback(err); - } -} + /** * @param {DataContext} context * @param {function(Error=,Array=)} callback * @private */ function effectiveAccounts(context, callback) { - if (_.isNil(context)) { - //push no account - return callback(null, [{ id: null }]); + var accounts = [ { id: null } ]; + if (context == null) { + //push empty accounts + return callback(null, accounts); } - - /** - * @type {DataCacheStrategy} - */ - var cache = context.getConfiguration().getStrategy(DataCacheStrategy); - /** - * Gets or sets an object that represents the user of the current data context. - * @property {*|{name: string, authenticationType: string}} - * @name DataContext#user - * @memberof DataContext - */ - context.user = context.user || { name: 'anonymous', authenticationType: 'None' }; - context.user.name = context.user.name || 'anonymous'; - //if the current user is anonymous - if (context.user.name === 'anonymous') { - //get anonymous user data - cache.getOrDefault(ANONYMOUS_USER_CACHE_PATH, function () { - return Q.nfbind(anonymousUser)(context); - }).then(function (result) { - var arr = []; - if (result) { - arr.push({ 'id': result.id, 'name': result.name }); - result.groups = result.groups || []; - result.groups.forEach(function (x) { arr.push({ 'id': x.id, 'name': x.name }); }); - } - if (arr.length === 0) - arr.push({ id: null }); - return callback(null, arr); - }).catch(function (err) { - return callback(err); - }); + // validate context user + if (context.user == null) { + context.setUser({ name:'anonymous',authenticationType:'None' }); } - else { - //try to get data from cache - var USER_CACHE_PATH = '/User/' + context.user.name; - - cache.getOrDefault(USER_CACHE_PATH, function () { - return Q.nfbind(queryUser)(context, context.user.name); - }).then(function (user) { - return cache.getOrDefault(ANONYMOUS_USER_CACHE_PATH, function () { - return Q.nfbind(anonymousUser)(context); - }).then(function (anonymous) { - var arr = []; - if (user) { - arr.push({ 'id': user.id, 'name': user.name }); - if (_.isArray(user.groups)) - user.groups.forEach(function (x) { arr.push({ 'id': x.id, 'name': x.name }); }); - } - if (anonymous) { - arr.push({ 'id': anonymous.id, 'name': 'anonymous' }); - if (_.isArray(anonymous.groups)) - anonymous.groups.forEach(function (x) { arr.push({ 'id': x.id, 'name': x.name }); }); + try { + var source$ = context.user.name === 'anonymous' ? context.anonymousUser$ : context.user$; + void firstValueFrom(source$).then(function(user) { + if (user) { + accounts = [ + { id: user.id, name: user.name } + ]; + if (Array.isArray(user.groups)) { + accounts.push.apply(accounts, user.groups.map(function(x) { return { id: x.id, name: x.name }; })); } - if (arr.length === 0) - arr.push({ id: null }); - return callback(null, arr); - }); + } + return callback(null, accounts); }).catch(function (err) { return callback(err); }); + } catch (err) { + return callback(err); } } @@ -811,7 +729,9 @@ DataPermissionEventListener.prototype.beforeExecute = function (event, callback) } } //ensure context user - context.user = context.user || { name: 'anonymous', authenticationType: 'None' }; + if (context.user == null) { + context.setUser({ name:'anonymous',authenticationType:'None' }); + } //change: 2-May 2015 //description: Use unattended execution account as an escape permission check account var authSettings = context.getConfiguration().getStrategy(DataConfigurationStrategy).getAuthSettings(); diff --git a/functions.js b/functions.js index 220548c..c0106aa 100644 --- a/functions.js +++ b/functions.js @@ -7,6 +7,7 @@ var {TraceUtils} = require('@themost/common'); // eslint-disable-next-line no-unused-vars var moment = require('moment'); var _ = require('lodash'); +const { firstValueFrom } = require('rxjs'); /** * @class @@ -246,51 +247,18 @@ FunctionContext.prototype.password = function(length) { /** * @returns {Promise} */ -FunctionContext.prototype.user = function() { - var self = this; +FunctionContext.prototype.user = async function() { // backward compatibility issue (get FunctionContext.model.context first) - var context = (self.model && self.model.context) || self.context; - var user = context.interactiveUser || context.user || { }; - return new Promise(function(resolve) { - if (typeof user.id !== 'undefined') { - return resolve(user.id); - } - var userModel = context.model('User'); - var parser; - var undefinedUser = null; - userModel.where('name').equal(user.name).silent().select('id').first(function(err, result) { - if (err) { - TraceUtils.log(err); - //try to get undefined user - parser = TypeParser.hasParser(userModel.field('id').type); - if (typeof parser === 'function') - undefinedUser = parser(null); - //set id for next calls - user.id = undefinedUser; - if (context.user == null) { - context.user = user; - } - return resolve(undefinedUser); - } - else if (result == null) { - //try to get undefined user - parser = TypeParser.hasParser(userModel.field('id').type); - if (typeof parser === 'function') - undefinedUser = parser(null); - //set id for next calls - user.id = undefinedUser; - if (context.user == null) { - context.user = user; - } - return resolve(undefinedUser); - } - else { - //set id for next calls - user.id = result.id; - return resolve(result.id); - } - }); - }); + var context = (this.model && this.model.context) || this.context; + if (context.interactiveUser) { + const user = await firstValueFrom(context.interactiveUser$) + return user && user.id; + } + if (context.user) { + const user = await firstValueFrom(context.user$) + return user && user.id; + } + return null; }; /** * @returns {Promise|*} diff --git a/odata.js b/odata.js index 5f2499b..2d48c1c 100644 --- a/odata.js +++ b/odata.js @@ -1635,31 +1635,30 @@ ODataModelBuilder.prototype.hasJsonFormatter = function(jsonFormatterFunc) { * @augments DataContext * @extends DataContext */ -function EntityDataContext(configuration) { - EntityDataContext.super_.bind(this)(); - /** - * @returns {ConfigurationBase} - */ - this.getConfiguration = function() { - return configuration; - }; -} -LangUtils.inherits(EntityDataContext, DataContext); - -EntityDataContext.prototype.model = function(name) { - var strategy = this.getConfiguration().getStrategy(DataConfigurationStrategy); - if (hasOwnProperty(strategy.dataTypes, name)) { - return; +class EntityDataContext extends DataContext { + constructor(configuration) { + super(); + /** + * @returns {ConfigurationBase} + */ + this.getConfiguration = function () { + return configuration; + }; } - var definition = strategy.model(name); - if (_.isNil(definition)) { - return; + model(name) { + var strategy = this.getConfiguration().getStrategy(DataConfigurationStrategy); + if (hasOwnProperty(strategy.dataTypes, name)) { + return; + } + var definition = strategy.model(name); + if (_.isNil(definition)) { + return; + } + var res = new DataModel(definition); + res.context = this; + return res; } - var res = new DataModel(definition); - res.context = this; - return res; -}; - +} /** * @class diff --git a/package-lock.json b/package-lock.json index db1a90d..4d4156f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "node-cache": "^1.1.0", "pluralize": "^7.0.0", "q": "^1.4.1", + "rxjs": "^7.8.2", "sprintf-js": "^1.1.2", "symbol": "^0.3.1", "uuid": "^10.0.0" @@ -30,7 +31,7 @@ "@themost/common": "^2.10.4", "@themost/peers": "^1.0.2", "@themost/query": "^2.14.7", - "@themost/sqlite": "^2.8.4", + "@themost/sqlite": "^2.10.0", "@themost/xml": "^2.5.2", "@types/core-js": "^2.5.0", "@types/crypto-js": "^4.2.2", @@ -74,12 +75,15 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -430,19 +434,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -472,38 +478,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", - "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/types": "^7.27.0" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz", - "integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -1721,26 +1717,28 @@ } }, "node_modules/@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "dev": true, + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" @@ -1767,19 +1765,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/traverse/node_modules/@babel/generator": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", @@ -1841,78 +1826,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/traverse/node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -1938,14 +1851,14 @@ } }, "node_modules/@babel/types": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz", - "integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2919,9 +2832,10 @@ } }, "node_modules/@themost/events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@themost/events/-/events-1.0.5.tgz", - "integrity": "sha512-4G5OcgMXL0z7MdMLvxdvGnt5oXFKOwH3Bv1U4Mn39raO+oRXzT3oF7uip/nRtbXEQLyLgwfyKlOCU97656YLHg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@themost/events/-/events-1.5.0.tgz", + "integrity": "sha512-R+q764cVNDPW4CYKr6Le1LCty1YYieKHbh9mJwv3gDo9fG2wtoN23T85ABnsQpTOkkJOsxk0+Tl3tPM3dblEjQ==", + "license": "BSD-3-Clause" }, "node_modules/@themost/peers": { "version": "1.0.2", @@ -2960,13 +2874,16 @@ "dev": true }, "node_modules/@themost/sqlite": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/@themost/sqlite/-/sqlite-2.8.4.tgz", - "integrity": "sha512-pmkf18CUqnJ1dqZwVdCZtS9LU9xsgVvKitjuTh9xX0Ym2L02iBTPWcPx7uemZCwUOaXi9cw5mikdpi2pPsMIlA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@themost/sqlite/-/sqlite-2.10.0.tgz", + "integrity": "sha512-R0VVO9kDHmeIm96osW3+RS3TZ2hP8LO+dPjui83G4iXVJepH2lNSxnZvFrK5xFXmCjvxja4jZ4Nx0XFF26gL5g==", "dev": true, "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { + "@themost/events": "^1.5.0", "async": "^2.6.4", + "lodash": "^4.17.21", "sprintf-js": "^1.1.2", "sqlite3": "^5.1.7", "unzipper": "^0.12.3" @@ -3309,18 +3226,6 @@ "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -3831,20 +3736,6 @@ } ] }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -3912,21 +3803,6 @@ "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -4375,15 +4251,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/escodegen": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", @@ -5271,15 +5138,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", @@ -8615,10 +8473,11 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" }, "node_modules/regenerator-transform": { "version": "0.15.0", @@ -8815,6 +8674,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -9169,18 +9037,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/supports-hyperlinks": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", @@ -9256,10 +9112,11 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", "dev": true, + "license": "MIT", "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -9346,15 +9203,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -9420,6 +9268,12 @@ "typescript": ">=2.7" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index 0292f4a..2e64475 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "node-cache": "^1.1.0", "pluralize": "^7.0.0", "q": "^1.4.1", + "rxjs": "^7.8.2", "sprintf-js": "^1.1.2", "symbol": "^0.3.1", "uuid": "^10.0.0" @@ -47,7 +48,7 @@ "@themost/common": "^2.10.4", "@themost/peers": "^1.0.2", "@themost/query": "^2.14.7", - "@themost/sqlite": "^2.8.4", + "@themost/sqlite": "^2.10.0", "@themost/xml": "^2.5.2", "@types/core-js": "^2.5.0", "@types/crypto-js": "^4.2.2", diff --git a/spec/DataContext.spec.ts b/spec/DataContext.spec.ts new file mode 100644 index 0000000..514718b --- /dev/null +++ b/spec/DataContext.spec.ts @@ -0,0 +1,94 @@ +import { resolve } from 'path'; +import { DataContext } from '../index'; +import { TestApplication, TestApplication2 } from './TestApplication'; +import { firstValueFrom } from 'rxjs'; + +describe('DataContext', () => { + let app: TestApplication2; + let context: DataContext; + beforeAll(async () => { + app = new TestApplication2(); + context = app.createContext(); + }); + afterAll(async () => { + await context.finalizeAsync(); + await app.finalize(); + }) + + + it('should get context user', async () => { + const name = 'alexis.rees@example.com' + context.user = { + name + }; + const spyOnGetUser = jest.spyOn(context, 'getUser') + const user = await firstValueFrom(context.user$); + expect(spyOnGetUser).toHaveBeenCalled(); + spyOnGetUser.mockClear(); + expect(user).toBeTruthy(); + expect(user.name).toBe(name); + await firstValueFrom(context.user$); + expect(spyOnGetUser).not.toHaveBeenCalled(); + }); + + it('should switch context user', async () => { + let name = 'alexis.rees@example.com'; + context.user = { + name + }; + const spyOnGetUser = jest.spyOn(context, 'getUser') + const user = await firstValueFrom(context.user$); + expect(spyOnGetUser).toHaveBeenCalled(); + spyOnGetUser.mockClear(); + expect(user).toBeTruthy(); + expect(user.name).toBe(name); + name = 'luis.nash@example.com'; + context.switchUser({ + name + }); + const user2 = await firstValueFrom(context.user$); + expect(spyOnGetUser).toHaveBeenCalled(); + spyOnGetUser.mockClear(); + expect(user2).toBeTruthy(); + expect(user2.name).toBe(name); + }); + + it('should switch context user with assignment', async () => { + let name = 'alexis.rees@example.com'; + context.user = { + name + }; + const spyOnGetUser = jest.spyOn(context, 'getUser') + const user = await firstValueFrom(context.user$); + expect(spyOnGetUser).toHaveBeenCalled(); + spyOnGetUser.mockClear(); + expect(user).toBeTruthy(); + expect(user.name).toBe(name); + name = 'luis.nash@example.com'; + Object.assign(context, { + user: { + name + } + }); + const user2 = await firstValueFrom(context.user$); + expect(spyOnGetUser).toHaveBeenCalled(); + spyOnGetUser.mockClear(); + expect(user2).toBeTruthy(); + expect(user2.name).toBe(name); + }); + + it('should switch get context interactive user', async () => { + const name = 'alexis.rees@example.com' + context.user = { + name + }; + const spyOnGetInteractiveUser = jest.spyOn(context, 'getInteractiveUser') + const interactiveUser = await firstValueFrom(context.interactiveUser$); + expect(spyOnGetInteractiveUser).toHaveBeenCalled(); + spyOnGetInteractiveUser.mockClear(); + expect(interactiveUser).toBeFalsy(); + await firstValueFrom(context.interactiveUser$); + expect(spyOnGetInteractiveUser).not.toHaveBeenCalled(); + }); + +}); diff --git a/types.d.ts b/types.d.ts index 2f6129b..91c79c0 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,7 +1,8 @@ // MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved import {DataModel} from "./data-model"; -import {ConfigurationBase, SequentialEventEmitter, DataAdapterBase, DataAdapterMigration, DataContextBase, ContextUserBase} from "@themost/common"; +import {ConfigurationBase, SequentialEventEmitter, DataAdapterBase, DataAdapterMigration, DataContextBase, ContextUserBase, ApplicationBase} from "@themost/common"; import {DataAssociationMappingBase, DataFieldBase} from '@themost/common'; +import { Observable } from "rxjs"; export declare function DataAdapterCallback(err?:Error, result?:any): void; @@ -75,23 +76,118 @@ export declare interface ContextUser extends ContextUserBase { } -export declare class DataContext extends SequentialEventEmitter implements DataContextBase { +export declare abstract class DataContext extends SequentialEventEmitter implements DataContextBase { + /** + * Optional property representing the use of the current context. + * + * @type {ContextUser} + */ user?: ContextUser; + /** + * Optional property representing the interactive user of the current context. + * The interactive user is the original user who initiated the current context. + */ interactiveUser?: ContextUser; + + /** + * An observable stream that emits user-related data. + * + * @type {Observable} + */ + user$: Observable; + + /** + * An observable stream that emits interactive user-related data. + * + * @type {Observable} + */ + interactiveUser$: Observable; - model(name:any): DataModel; - + /** + * The database adapter instance used for interacting with the database. + * This property provides the necessary methods and properties to perform + * database operations such as querying, inserting, updating, and deleting records. + */ db: DataAdapterBase; - getConfiguration(): ConfigurationBase; + /** + * Returns an instance of the data model with the specified name. + * @param {*} name + */ + abstract model(name:any): DataModel; + + /** + * Returns the configuration service of the parent application. + * @returns {ConfigurationBase} + */ + abstract getConfiguration(): ConfigurationBase; - finalize(callback?:(err?:Error) => void): void; + /** + * Finalizes the current context and releases all resources. + * @param {(err?: Error) => void} callback + */ + abstract finalize(callback?:(err?:Error) => void): void; + /** + * Finalizes the current context and releases all resources. + * @returns {Promise} + */ finalizeAsync(): Promise; + /** + * Executes the specified function within a transaction. + * A transaction is a set of operations that are executed as a single unit of work. + * @param func + */ executeInTransactionAsync(func: () => Promise): Promise; + + /** + * Switches the current user of the context. + * @param {ContextUser} user + */ + switchUser(user?: ContextUser): void; + + /** + * An alternative method to switch the current user of the context. + * @param {ContextUser} user + */ + setUser(user?: ContextUser): void; + + /** + * Switches the interactive user of the context. The interactive user is the original user who initiated the current context. + * @param {ContextUser} user + */ + switchInteractiveUser(user?: ContextUser): void; + + /** + * An alternative method to switch the interactive user of the context. + * @param {ContextUser} user + */ + setInteractiveUser(user?: ContextUser): void; + + /** + * Sets the application of the current context. The application is the parent application that created the current context. + * @param {ApplicationBase} application + */ + setApplication(application: ApplicationBase): void; + + /** + * Returns the application of the current context. + * + */ + getApplication(): ApplicationBase; + + /** + * Refreshes the state of the current context including the state of the current user and the interactive user. + */ + protected refreshState(): void; + + getUser(): Observable; + + getInteractiveUser(): Observable; + } export declare class DataContextEmitter { diff --git a/types.js b/types.js index 620ddc1..41b45cc 100644 --- a/types.js +++ b/types.js @@ -1,7 +1,7 @@ // MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved var _ = require('lodash'); -var {SequentialEventEmitter, LangUtils, AbstractClassError, AbstractMethodError} = require('@themost/common'); - +var {SequentialEventEmitter, AbstractClassError, AbstractMethodError} = require('@themost/common'); +var { shareReplay, Observable, switchMap, defer } = require('rxjs'); /** * @classdesc Represents an abstract data connector to a database * @description @@ -282,110 +282,6 @@ function DataEventArgs() { // } -/** - * @classdesc Represents the main data context. - * @class - * @augments SequentialEventEmitter - * @constructor - * @abstract - */ -function DataContext() { - DataContext.super_.bind(this)(); - //throw abstract class error - if (this.constructor === DataContext.prototype.constructor) { - throw new AbstractClassError(); - } - /** - * @property db - * @description Gets the current database adapter - * @type {DataAdapter} - * @memberOf DataContext# - */ - Object.defineProperty(this, 'db', { - get : function() { - return null; - }, - configurable : true, - enumerable:false }); -} -// noinspection JSUnusedLocalSymbols -/** - * Gets a data model based on the given data context - * @param name {string} A string that represents the model to be loaded. - * @returns {DataModel} - * @abstract - */ -// eslint-disable-next-line no-unused-vars -DataContext.prototype.model = function(name) { - throw new AbstractMethodError(); -}; - -/** - * Gets an instance of DataConfiguration class which is associated with this data context - * @returns {ConfigurationBase} - * @abstract - */ -DataContext.prototype.getConfiguration = function() { - throw new AbstractMethodError(); -}; -// noinspection JSUnusedLocalSymbols -/** - * @param {Function} callback - * @abstract - */ -// eslint-disable-next-line no-unused-vars -DataContext.prototype.finalize = function(callback) { - throw new AbstractMethodError(); -}; -/** - * Finalizes data context - * @returns {Promise} - */ -DataContext.prototype.finalizeAsync = function() { - const self = this; - return new Promise(function(resolve, reject) { - return self.finalize(function(err) { - if (err) { - return reject(err); - } - return resolve(); - }); - }); -} -/** - * - * @param {function():Promise} func - * @returns {Promise} - */ -DataContext.prototype.executeInTransactionAsync = function(func) { - const self = this; - return new Promise((resolve, reject) => { - // start transaction - return self.db.executeInTransaction(function(cb) { - try { - func().then(function() { - // commit - return cb(); - }).catch( function(err) { - // rollback - return cb(err); - }); - } - catch (err) { - return cb(err); - } - }, function(err) { - if (err) { - return reject(err); - } - // end transaction - return resolve(); - }); - }); -} - -LangUtils.inherits(DataContext, SequentialEventEmitter); - /** * @classdesc Represents a data model's listener * @class @@ -943,6 +839,249 @@ class TypeParser { // backward compatibility issue (this constant should be removed in next version) var parsers = TypeParser; +/** + * @classdesc Represents the main data context. + * @class + * @augments SequentialEventEmitter + * @abstract + */ +class DataContext extends SequentialEventEmitter { + constructor() { + super(); + if (this.constructor === DataContext.prototype.constructor) { + throw new AbstractClassError(); + } + + this.user$ = defer(() => this.getUser()).pipe(shareReplay()); + + this.interactiveUser$ = defer(() => this.getInteractiveUser()).pipe(shareReplay()); + + var _user = null; + Object.defineProperty(this, 'user', { + get: function () { + return _user; + }, + set: function (value) { + _user = value; + this.refreshState(); + }, + configurable: true, + enumerable: false + }); + + var _interactiveUser = null; + Object.defineProperty(this, 'interactiveUser', { + get: function () { + return _interactiveUser; + }, + set: function (value) { + _interactiveUser = value; + this.refreshState(); + }, + configurable: true, + enumerable: false + }); + + this.anonymousUser$ = new Observable(observer => observer.next()).pipe(switchMap(() => { + const application = this.getApplication(); + if (application) { + const userService = application.getService(function UserService() {}); + if (userService) { + return userService.anonymousUser$; + } + } + return new Observable((observer) => { + void this.model('User').where('name').equal('anonymous').expand('groups').silent().getItem().then((result) => { + return observer.next(result); + }).catch((err) => { + return observer.error(err); + }); + }); + }), shareReplay(1)); + + } + // noinspection JSUnusedLocalSymbols + /** + * Gets a data model based on the given data context + * @param name {string} A string that represents the model to be loaded. + * @returns {DataModel} + * @abstract + */ + // eslint-disable-next-line no-unused-vars + model(name) { + throw new AbstractMethodError(); + } + /** + * + * @returns {Observable} + */ + getUser() { + return new Observable((observer) => { + if ((this.user && this.user.name) == null) { + return observer.next(null); + } + + // get current application + const application = this.getApplication(); + if (application != null) { + // get user service + const userService = application.getService(function UserService() {}); + // check if user service is available + if (userService != null) { + // get user + return userService.getUser(this, this.user.name).then((result) => { + return observer.next(result); + }).catch((err) => { + return observer.error(err); + }); + } + } + // otherwise get user from data context + void this.model('User').where('name').equal(this.user.name).expand('groups').silent().getItem().then((result) => { + return observer.next(result); + }).catch((err) => { + return observer.error(err); + }); + }); + } + switchUser(user) { + this.user = user; + } + setUser(user) { + this.user = user; + } + /** + * @protected + */ + refreshState() { + this.user$ = defer(() => this.getUser()).pipe(shareReplay()); + this.interactiveUser$ = defer(() => this.getInteractiveUser()).pipe(shareReplay()); + } + /** + * + * @returns {Observable} + */ + getInteractiveUser() { + return new Observable((observer) => { + let username = this.interactiveUser && this.interactiveUser.name; + if (username == null) { + return observer.next(null); + } + // get current application + const application = this.getApplication(); + if (application != null) { + // get user service + const userService = application.getService(function UserService() {}); + // check if user service is available + if (userService != null) { + // get user + return userService.getUser(this, username).then((result) => { + return observer.next(result); + }).catch((err) => { + return observer.error(err); + }); + } + } + // otherwise get user from data context + void this.model('User').where('name').equal(username).expand('groups').silent().getItem().then((result) => { + return observer.next(result); + }).catch((err) => { + return observer.error(err); + }); + }); + } + switchInteractiveUser(user) { + this.interactiveUser = user; + } + setInteractiveUser(user) { + this.interactiveUser = user; + } + /** + * Gets an instance of DataConfiguration class which is associated with this data context + * @returns {ConfigurationBase} + * @abstract + */ + getConfiguration() { + throw new AbstractMethodError(); + } + // noinspection JSUnusedLocalSymbols + /** + * @param {Function} callback + * @abstract + */ + // eslint-disable-next-line no-unused-vars + finalize(callback) { + throw new AbstractMethodError(); + } + /** + * Finalizes data context + * @returns {Promise} + */ + finalizeAsync() { + const self = this; + return new Promise(function (resolve, reject) { + return self.finalize(function (err) { + if (err) { + return reject(err); + } + return resolve(); + }); + }); + } + /** + * + * @param {function():Promise} func + * @returns {Promise} + */ + executeInTransactionAsync(func) { + const self = this; + return new Promise((resolve, reject) => { + // start transaction + return self.db.executeInTransaction(function (cb) { + try { + func().then(function () { + // commit + return cb(); + }).catch(function (err) { + // rollback + return cb(err); + }); + } + catch (err) { + return cb(err); + } + }, function (err) { + if (err) { + return reject(err); + } + // end transaction + return resolve(); + }); + }); + } + /** + * Sets the application that is associated with this data context + * @param {import('@themost/common').ApplicationBase} application + */ + setApplication(application) { + Object.defineProperty(this, 'application', { + get: function () { + return application; + }, + configurable: true, + enumerable: false + }); + } + /** + * Returns the application that is associated with this data context + * @returns {import('@themost/common').ApplicationBase} + */ + getApplication() { + return this.application; + } +} + + module.exports = { TypeParser, parsers,