diff --git a/OnJsonAttribute.js b/OnJsonAttribute.js new file mode 100644 index 0000000..4c24635 --- /dev/null +++ b/OnJsonAttribute.js @@ -0,0 +1,315 @@ +const {DataObjectState} = require('./types'); +const {eachSeries} = require('async'); +const {DataConfigurationStrategy} = require('./data-configuration'); +const {DataError} = require('@themost/common'); + + +function edmTypeToJsonType(edmType) { + switch (edmType) { + case 'Edm.String': + return 'string'; + case 'Edm.Boolean': + return 'boolean'; + case 'Edm.Byte': + case 'Edm.SByte': + case 'Edm.Int16': + case 'Edm.Int32': + case 'Edm.Int64': + return 'integer'; + case 'Edm.Decimal': + case 'Edm.Double': + return 'number'; + case 'Edm.DateTime': + case 'Edm.EdmDateTimeOffset': + case 'Edm.Duration': + return 'string'; + case 'Edm.Guid': + return 'string'; + case 'Edm.Binary': + case 'Edm.Stream': + return 'string'; + default: + return 'string'; + } +} + +class OnJsonAttribute { + + /** + * @param {import('./data-model').DataModel} model + */ + static getJsonSchema(model) { + const { context } = model; + const {dataTypes} = context.getConfiguration().getStrategy(DataConfigurationStrategy); + const additionalProperties = false; + const properties = model.attributes.reduce((prev, attr) => { + /** + * @type {{edmtype: string,type:string}} + */ + const dataType = dataTypes[attr.type]; + let type = 'object'; + let assign = {}; + if (dataType != null) { + type = edmTypeToJsonType(dataType.edmtype); + assign = { + [attr.name]: { + type + } + } + } else { + // try to get related model + let relatedModel = attr.additionalType != null ? context.model(attr.additionalType) : context.model(attr.type); + // if related model exists + if (relatedModel) { + // get json schema for related model + assign = { + [attr.name]: Object.assign(OnJsonAttribute.getJsonSchema(relatedModel), { + type + }) + } + } else { + // if related model does not exist + assign = { + [attr.name]: { + type + } + }; + } + } + // set property + Object.assign(prev, assign); + return prev; + }, {}); + const required = model.attributes.filter((attr) => { + const primary = attr.primary === true; + const many = attr.many === true; + return attr.nullable === false && many === false && primary === false; + }).map((attr) => attr.name); + return { + properties, + required, + additionalProperties + } + } + + /** + * @param {import('./types').DataEventArgs} event + * @param {function(err?:Error)} callback + * @returns {Promise | Promise} + */ + beforeSave(event, callback) { + try { + // get json attributes if any + const attributes= event.model.attributes.filter((attr) => { + const editable = attr.editable !== false; + const insertable = attr.insertable !== false; + const include = event.state === DataObjectState.Insert ? insertable : editable; + return include && attr.type === 'Json' && attr.additionalType != null && attr.model === event.model.name; + }).filter((attr) => { + return Object.prototype.hasOwnProperty.call(event.target, attr.name); + }); + // exit if there are no json attributes + if (attributes.length === 0) { + return callback(); + } + // iterate over json attributes + void eachSeries(attributes, (attr, cb) => { + // get attribute name + const {name} = attr; + const {[name]: value} = event.target; + if (value == null) { + return cb(); + } + try { + const targetModel = event.model.context.model(attr.additionalType); + if (targetModel == null) { + return cb(new DataError('ERR_INVALID_MODEL', 'Property additional type cannot be determined.', 'The target model cannot be found.', event.model.name, attr.name)); + } + // execute beforeSave event + // this operation will add calculated values and validate the object against the current state of the model + void targetModel.emit('before.save', { + target: value, + state: event.state, + model: targetModel + }, (err) => { + if (err) { + return cb(err); + } + // get object properties + const properties = Object.getOwnPropertyNames(value); + // get target model attributes + const attributes = targetModel.attributeNames; + // check if all properties are defined in the target model + const additionalProperty = properties.find((prop) => attributes.indexOf(prop) < 0); + if (additionalProperty != null) { + return cb(new DataError('ERR_INVALID_PROPERTY', `The given structured value seems to be invalid. The property '${additionalProperty}' is not defined in the target model.`, null, event.model.name, attr.name)); + } + return cb(); + }); + + } catch(err) { + return cb(err); + } + }, (err) => { + if (err) { + return callback(err); + } + return callback(); + }); + } catch (err) { + return callback(err); + } + } + + /** + * @protected + * @param {{model: DataModel, result: any, emitter?: import('./data-queryable').DataQueryable}} event + * @param {function(err?:Error): void} callback + * @returns void + */ + static afterSelect(event, callback) { + const jsonAttributes = event.model.attributes.filter((attr) => { + return attr.type === 'Json' && attr.additionalType != null && attr.model === event.model.name; + }).map((attr) => { + return attr.name + }); + if (jsonAttributes.length === 0) { + return callback(); + } + let select = []; + const { viewAdapter: entity } = event.model; + if (event.emitter && event.emitter.query && event.emitter.query.$select) { + const querySelect = event.emitter.query.$select[entity] || []; + select.push(...querySelect); + } + let attributes = select.reduce((prev, element) => { + // if select element is a typical query field with $name property + if (element && typeof element.$name === 'string') { + // split $name property by dot + const matches = element.$name.split('.'); + // if there are more than one parts + if (matches && matches.length > 1) { + // get last part + if (jsonAttributes.indexOf(matches[1]) >= 0) { + prev.push(matches.pop()); + } + } + } else { + // try to get first property which should be attribute alias + const [key] = Object.keys(element); + if (Object.hasOwnProperty.call(element, key)) { + /** + * @type {{$jsonGet?: any[]}} + */ + const selectField = element[key]; + // if select field has $jsonGet property + if (selectField.$jsonGet) { + const [jsonGet] = selectField.$jsonGet; + // if jsonGet has $name property + if (jsonGet && typeof jsonGet.$name === 'string') { + // split $name property by dot + const matches = jsonGet.$name.split('.'); + if (matches && matches.length > 1) { + if (jsonAttributes.indexOf(matches[1]) >= 0) { + // get json schema + if (matches.length > 2) { + // get attribute + const attribute = event.model.getAttribute(matches[1]); + if (attribute) { + // get additional model + const additionalModel = event.model.context.model(attribute.additionalType) + // and its json schema + let jsonSchema = OnJsonAttribute.getJsonSchema(additionalModel); + let index = 2; + // iterate over matches + while(index < matches.length) { + // get property + const property = jsonSchema.properties[matches[index]]; + // if property is an object + if (property && property.type === 'object') { + if (index + 1 === matches.length) { + prev.push(matches[index]); + break; + } + if (Array.isArray(property.properties)) { + jsonSchema = property.properties; + } else { + prev.push(matches[index]); + break; + } + } + index++; + } + } + } else { + prev.push(matches[2]); + } + } + } + } + } + } + } + return prev + }, []); + if (select.length === 0) { + attributes = jsonAttributes; + } + // define json converter + const parseJson = (item) => { + attributes.forEach((name) => { + if (Object.prototype.hasOwnProperty.call(item, name)) { + const value = item[name]; + if (typeof value === 'string') { + item[name] = JSON.parse(value); + } + } + }); + }; + // iterate over result + const {result} = event; + if (result == null) { + return callback(); + } + if (Array.isArray(result)) { + result.forEach((item) => parseJson(item)); + } else { + // or parse json for single item + parseJson(result) + } + return callback(); + } + + /** + * @param {import('./types').DataEventArgs} event + * @param {function(err?:Error): void} callback + * @returns void + */ + afterSave(event, callback) { + return OnJsonAttribute.afterSelect({ + model: event.model, + result: event.target + }, callback); + } + + /** + * @param {import('./types').DataEventArgs} event + * @param {function(err?:Error): void} callback + * @returns void + */ + afterExecute(event, callback) { + try { + if (event.emitter && event.emitter.query && event.emitter.query.$select && event.result) { + return OnJsonAttribute.afterSelect(event, callback); + } + return callback(); + } catch (err) { + return callback(err); + } + } + +} + +module.exports = { + OnJsonAttribute +} \ No newline at end of file diff --git a/data-attribute-resolver.js b/data-attribute-resolver.js index 7720e98..3ce2567 100644 --- a/data-attribute-resolver.js +++ b/data-attribute-resolver.js @@ -1,4 +1,4 @@ -var {QueryField, QueryEntity, QueryUtils} = require('@themost/query'); +var {QueryField, QueryEntity, QueryUtils, MethodCallExpression, MemberExpression, Expression} = require('@themost/query'); var {sprintf} = require('sprintf-js'); var _ = require('lodash'); var {DataError} = require('@themost/common'); @@ -90,6 +90,11 @@ DataAttributeResolver.prototype.resolveNestedAttribute = function(attr) { }; // and pass member expression expr = new DataAttributeResolver().resolveNestedAttributeJoin.call(self.model, memberExpr); + // if expr.$select is an instance of Expression then return it + // important note: this operation is very important in cases where a json object is selected + if (expr && expr.$select instanceof Expression) { + return expr.$select; + } //select field if (member.length>2) { if (memberExpr.name !== attr) { @@ -179,6 +184,17 @@ DataAttributeResolver.prototype.resolveNestedAttributeJoin = function(memberExpr //search for field mapping var mapping = self.inferMapping(arrMember[0]); if (_.isNil(mapping)) { + if (attrMember.type === 'Json') { + var collection = self[aliasProperty] || self.viewAdapter; + var objectPath = arrMember.join('.'); + var objectGet = new MethodCallExpression('jsonGet', [ + new MemberExpression(collection + '.' + objectPath) + ]); + return { + $select: objectGet, + $expand: [] + } + } throw new Error(sprintf('The target model does not have an association defined for attribute named %s',arrMember[0])); } if (mapping.childModel===self.name && mapping.associationType==='association') { diff --git a/data-filter-resolver.js b/data-filter-resolver.js index a76ca66..910b96e 100644 --- a/data-filter-resolver.js +++ b/data-filter-resolver.js @@ -14,6 +14,9 @@ function DataFilterResolver() { } DataFilterResolver.prototype.resolveMember = function(member, callback) { + if (typeof member !== 'string') { + return callback(null, member); + } if (/\//.test(member)) { var arr = member.split('/'); return callback(null, arr.slice(arr.length-2).join('.')); diff --git a/data-model-filter.parser.js b/data-model-filter.parser.js index d987c29..630a894 100644 --- a/data-model-filter.parser.js +++ b/data-model-filter.parser.js @@ -1,5 +1,5 @@ const { AsyncSeriesEventEmitter } = require('@themost/events'); -const { OpenDataParser } = require('@themost/query'); +const { OpenDataParser, Expression} = require('@themost/query'); const { DataAttributeResolver } = require('./data-attribute-resolver'); const { DataFilterResolver } = require('./data-filter-resolver'); @@ -55,7 +55,10 @@ class DataModelFilterParser { } else { expr = DataAttributeResolver.prototype.resolveNestedAttributeJoin.call(thisModel, member); - if (expr.$select) { + if (expr && expr.$select instanceof Expression) { + // // use it as select expression after converting it to query field + return cb(null, expr.$select); + } else if (expr.$select) { member = expr.$select.$name.replace(/\./g,'/'); } } diff --git a/data-model.js b/data-model.js index 5d0a0ee..108123a 100644 --- a/data-model.js +++ b/data-model.js @@ -41,6 +41,8 @@ var {hasOwnProperty} = require('./has-own-property'); var {SyncSeriesEventEmitter} = require('@themost/events'); require('@themost/promise-sequence'); var DataObjectState = types.DataObjectState; +var { OnJsonAttribute } = require('./OnJsonAttribute'); +var { isObjectDeep } = require('./is-object'); /** * @this DataModel * @param {DataField} field @@ -638,6 +640,11 @@ function unregisterContextListeners() { this.on('after.upgrade',DataModelCreateViewListener.prototype.afterUpgrade); this.on('after.upgrade',DataModelSeedListener.prototype.afterUpgrade); + // json listener + this.on('after.save', OnJsonAttribute.prototype.afterSave); + this.on('after.execute', OnJsonAttribute.prototype.afterExecute); + this.on('before.save', OnJsonAttribute.prototype.beforeSave); + //get module loader /** * @type {ModuleLoader|*} @@ -1489,15 +1496,20 @@ function cast_(obj, state) { if (hasOwnProperty(obj, name)) { var mapping = self.inferMapping(name); - //if mapping is empty and a super model is defined + //if mapping is empty and super model is defined if (_.isNil(mapping)) { if (superModel && x.type === 'Object') { //try to find if superModel has a mapping for this attribute mapping = superModel.inferMapping(name); } } - if (_.isNil(mapping)) { - result[x.name] = obj[name]; + if (mapping == null) { + var {[name]: value} = obj; + if (x.type === 'Json') { + result[x.name] = isObjectDeep(value) ? JSON.stringify(value) : null; + } else { + result[x.name] = value; + } } else if (mapping.associationType==='association') { if (typeof obj[name] === 'object' && obj[name] !== null) diff --git a/data-pluralize.d.ts b/data-pluralize.d.ts new file mode 100644 index 0000000..e4ce093 --- /dev/null +++ b/data-pluralize.d.ts @@ -0,0 +1 @@ +export declare function setSingularRules(pluralize: any): void; \ No newline at end of file diff --git a/data-pluralize.js b/data-pluralize.js new file mode 100644 index 0000000..7ba75d6 --- /dev/null +++ b/data-pluralize.js @@ -0,0 +1,20 @@ + +const singularRules = [ + [ + /data$/, + 'data' + ] +] + +/** + * @param {*} pluralize + */ +function setSingularRules(pluralize) { + singularRules.forEach(rule => { + pluralize.addSingularRule(rule[0], rule[1]) + }); +} + +export { + setSingularRules +} \ No newline at end of file diff --git a/data-queryable.js b/data-queryable.js index af3f3d7..3d11aa3 100644 --- a/data-queryable.js +++ b/data-queryable.js @@ -7,8 +7,8 @@ var {TextUtils} = require('@themost/common'); var {DataMappingExtender} = require('./data-mapping-extensions'); var {DataAssociationMapping} = require('./types'); var {DataError, Args} = require('@themost/common'); -var {QueryField} = require('@themost/query'); -var {QueryEntity} = require('@themost/query'); +var {QueryField, MethodCallExpression, MemberExpression} = require('@themost/query'); +var {QueryEntity, Expression} = require('@themost/query'); var {QueryUtils} = require('@themost/query'); var Q = require('q'); var aliasProperty = Symbol('alias'); @@ -43,6 +43,11 @@ DataValueResolver.prototype.resolve = function(value) { } } } + // if value is an instance of Expression e.g. an instance if MemberExpression + if (value instanceof Expression) { + // return the expression + return value; + } if (isObjectDeep(value)) { // try to get in-process left operand // noinspection JSUnresolvedReference @@ -243,24 +248,37 @@ DataAttributeResolver.prototype.resolveNestedAttribute = function(attr) { }; // and pass member expression expr = DataAttributeResolver.prototype.resolveNestedAttributeJoin.call(self.model, memberExpr); - //select field - if (member.length>2) { - if (memberExpr.name !== attr) { - // get member segments again because they have been modified - member = memberExpr.name.split('/'); - } - select = QueryField.select(member[member.length-1]).from(member[member.length-2]); - } - else { - if (memberExpr.name !== attr) { - // get member segments again because they have been modified - member = memberExpr.name.split('/'); + // if the returned expression is an instance of query expression + if (expr && expr.$select instanceof Expression) { + // use it as select expression after converting it to query field + select = new QueryField({ + // important note: use $value query property to set the expression + $value: expr.$select.exprOf() + }); + } else { + // select field + if (member.length > 2) { + if (memberExpr.name !== attr) { + // get member segments again because they have been modified + member = memberExpr.name.split('/'); + } + select = QueryField.select(member[member.length - 1]).from(member[member.length - 2]); + } else { + if (memberExpr.name !== attr) { + // get member segments again because they have been modified + member = memberExpr.name.split('/'); + } + // and create query field expression + select = QueryField.select(member[1]).from(member[0]); } - // and create query field expression - select = QueryField.select(member[1]).from(member[0]); } } if (expr) { + // if expand expression is an empty array + if (Array.isArray(expr.$expand) && expr.$expand.length === 0) { + // do nothing and return select expression + return select; + } if (_.isNil(self.query.$expand)) { self.query.$expand = expr; } @@ -320,6 +338,18 @@ DataAttributeResolver.prototype.resolveNestedAttributeJoin = function(memberExpr //search for field mapping var mapping = self.inferMapping(arrMember[0]); if (_.isNil(mapping)) { + // add support for json objects + if (attrMember.type === 'Json') { + var collection = self[aliasProperty] || self.viewAdapter; + var objectPath = arrMember.join('.'); + var objectGet = new MethodCallExpression('jsonGet', [ + new MemberExpression(collection + '.' + objectPath) + ]); + return { + $select: objectGet, + $expand: [] + } + } throw new Error(sprintf('The target model does not have an association defined for attribute named %s',arrMember[0])); } if (mapping.childModel===self.name && mapping.associationType==='association') { diff --git a/jest.setup.js b/jest.setup.js index 3a8e49a..cecd238 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -6,4 +6,4 @@ TraceUtils.useLogger(new JsonLogger({ /* env */ process.env.NODE_ENV = 'development'; /* global jest */ -jest.setTimeout(30000); +jest.setTimeout(60000); diff --git a/model-schema.json b/model-schema.json index 972238e..47a3db5 100644 --- a/model-schema.json +++ b/model-schema.json @@ -9,7 +9,7 @@ "Time", "URL", "Language", "Model", "Guid", "Object", "NegativeInteger", "NegativeNumber", "NonNegativeInteger", "NonNegativeNumber", "NonPositiveInteger", "NonPositiveNumber", "PositiveInteger", - "PositiveNumber", "Email", "AbsoluteURI", "RelativeURI" + "PositiveNumber", "Email", "AbsoluteURI", "RelativeURI", "Json" ] }, "unknownTypes": { @@ -173,6 +173,10 @@ "type": "boolean", "description": "A boolean which indicates whether this attribute defines an association between two models where child objects are always treated as a part of the parent object." }, + "additionalType": { + "type": "string", + "description": "A string which represents the additional type of this property e.g. Person, Order etc. This attribute is used for properties marked as \"Json\" where a specific type should be defined for validating entities." + }, "mapping": { "type": "object", "properties": { diff --git a/package-lock.json b/package-lock.json index b15447e..2366ee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "@themost/data", - "version": "2.6.63", + "version": "2.6.70", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@themost/data", - "version": "2.6.63", + "version": "2.6.70", "license": "BSD-3-Clause", "dependencies": { "@themost/events": "^1.3.0", "@themost/promise-sequence": "^1.0.1", + "ajv": "^8.17.1", "async": "^2.6.4", "lodash": "^4.17.21", "node-cache": "^5.1.2", @@ -26,13 +27,12 @@ "@themost/common": "^2.5.11", "@themost/json-logger": "^1.1.0", "@themost/peers": "^1.0.2", - "@themost/query": "^2.5.27", - "@themost/sqlite": "^2.6.16", + "@themost/query": "^2.6.0", + "@themost/sqlite": "^2.8.4", "@themost/xml": "^2.5.2", "@types/core-js": "^2.5.0", "@types/jest": "^27.4.1", "@types/lodash": "^4.14.149", - "@types/moment": "^2.13.0", "@types/node": "^10.12.0", "@types/pluralize": "0.0.29", "@types/q": "^1.5.1", @@ -53,7 +53,7 @@ }, "peerDependencies": { "@themost/common": "^2.5.11", - "@themost/query": "^2.5.27", + "@themost/query": "^2.6.0", "@themost/xml": "^2.5.2" } }, @@ -4225,6 +4225,23 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.13.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", @@ -4240,6 +4257,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -5194,31 +5218,39 @@ "integrity": "sha512-wlMYRsNWaz5EJ7AwCIA3yw2kHy4p36a4VTz8XmyYTYDYaKgmXjTi6IJPB/+di0mt/rf6NfFfsYwrmytoLseiIg==" }, "node_modules/@themost/query": { - "version": "2.5.27", - "resolved": "https://registry.npmjs.org/@themost/query/-/query-2.5.27.tgz", - "integrity": "sha512-3c8kzEEYlkxZblzqgi+Ky9anVMRgcyMCAW/WC5uIEaNIeWaE7SgNIaIi8/ksq6+r6IMQI8hcThFLwHRU30AP6w==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@themost/query/-/query-2.6.0.tgz", + "integrity": "sha512-kS+m0nSBKgz/sa5Fq2o4litgyTe5V8HImnmnJc+mFtqBWnteKd3+6xqtWJZv0TlTILz4t7pdq3JM21VBNBMXcA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@themost/events": "^1.0.5", "async": "^2.6.4", "esprima": "^4.0.1", "lodash": "^4.17.15", + "package-lock-only": "^0.0.4", "sprintf-js": "^1.1.2", "symbol": "^0.3.1" }, + "engines": { + "node": ">=14" + }, "peerDependencies": { "@themost/common": "^2.5.0" } }, "node_modules/@themost/sqlite": { - "version": "2.6.16", - "resolved": "https://registry.npmjs.org/@themost/sqlite/-/sqlite-2.6.16.tgz", - "integrity": "sha512-6Mgj7516YC9+IINGCgBl9IpIMXVO+ZRWYKsrA/PdTo2cs1yYYgaoYNVifO42PZionW+WRMYETVAJuPKpEYhG2A==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@themost/sqlite/-/sqlite-2.8.4.tgz", + "integrity": "sha512-pmkf18CUqnJ1dqZwVdCZtS9LU9xsgVvKitjuTh9xX0Ym2L02iBTPWcPx7uemZCwUOaXi9cw5mikdpi2pPsMIlA==", "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { "async": "^2.6.4", "sprintf-js": "^1.1.2", - "sqlite3": "^5.1.6" + "sqlite3": "^5.1.7", + "unzipper": "^0.12.3" }, "engines": { "node": ">=12" @@ -5404,16 +5436,6 @@ "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", "dev": true }, - "node_modules/@types/moment": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", - "integrity": "sha512-DyuyYGpV6r+4Z1bUznLi/Y7HpGn4iQ4IVcGn8zrr1P4KotKLdH0sbK1TFR6RGyX6B+G8u83wCzL+bpawKU/hdQ==", - "deprecated": "This is a stub types definition for Moment (https://github.com/moment/moment). Moment provides its own type definitions, so you don't need @types/moment installed!", - "dev": true, - "dependencies": { - "moment": "*" - } - }, "node_modules/@types/node": { "version": "10.17.60", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", @@ -5538,15 +5560,15 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -5905,6 +5927,13 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, "node_modules/blueimp-md5": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", @@ -6260,6 +6289,13 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -6512,6 +6548,49 @@ "node": ">=12" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.625", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.625.tgz", @@ -6793,6 +6872,23 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6878,6 +6974,13 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7190,8 +7293,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -7205,6 +7307,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -7282,6 +7390,21 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -7778,6 +7901,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -10115,10 +10245,10 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -10138,6 +10268,19 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -10833,6 +10976,17 @@ "node": ">=6" } }, + "node_modules/package-lock-only": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/package-lock-only/-/package-lock-only-0.0.4.tgz", + "integrity": "sha512-fV1YHeTMWH5LKmdVqfWskm2/SG0iF2IrxJn3ziaPVx9CnpecGJzt8xXtLV+CYINENZwPFMtbxO5qupz0asNz1A==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "chalk": "^2.4.1" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11023,6 +11177,13 @@ "node": ">= 0.6.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -11068,10 +11229,11 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -11237,6 +11399,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -11941,6 +12112,30 @@ "imurmurhash": "^0.1.4" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -11976,6 +12171,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -12983,7 +13179,8 @@ "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true + "dev": true, + "requires": {} }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -15272,6 +15469,18 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "globals": { "version": "13.13.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", @@ -15281,6 +15490,12 @@ "type-fest": "^0.20.2" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -16036,28 +16251,30 @@ "integrity": "sha512-wlMYRsNWaz5EJ7AwCIA3yw2kHy4p36a4VTz8XmyYTYDYaKgmXjTi6IJPB/+di0mt/rf6NfFfsYwrmytoLseiIg==" }, "@themost/query": { - "version": "2.5.27", - "resolved": "https://registry.npmjs.org/@themost/query/-/query-2.5.27.tgz", - "integrity": "sha512-3c8kzEEYlkxZblzqgi+Ky9anVMRgcyMCAW/WC5uIEaNIeWaE7SgNIaIi8/ksq6+r6IMQI8hcThFLwHRU30AP6w==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@themost/query/-/query-2.6.0.tgz", + "integrity": "sha512-kS+m0nSBKgz/sa5Fq2o4litgyTe5V8HImnmnJc+mFtqBWnteKd3+6xqtWJZv0TlTILz4t7pdq3JM21VBNBMXcA==", "dev": true, "requires": { "@themost/events": "^1.0.5", "async": "^2.6.4", "esprima": "^4.0.1", "lodash": "^4.17.15", + "package-lock-only": "^0.0.4", "sprintf-js": "^1.1.2", "symbol": "^0.3.1" } }, "@themost/sqlite": { - "version": "2.6.16", - "resolved": "https://registry.npmjs.org/@themost/sqlite/-/sqlite-2.6.16.tgz", - "integrity": "sha512-6Mgj7516YC9+IINGCgBl9IpIMXVO+ZRWYKsrA/PdTo2cs1yYYgaoYNVifO42PZionW+WRMYETVAJuPKpEYhG2A==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@themost/sqlite/-/sqlite-2.8.4.tgz", + "integrity": "sha512-pmkf18CUqnJ1dqZwVdCZtS9LU9xsgVvKitjuTh9xX0Ym2L02iBTPWcPx7uemZCwUOaXi9cw5mikdpi2pPsMIlA==", "dev": true, "requires": { "async": "^2.6.4", "sprintf-js": "^1.1.2", - "sqlite3": "^5.1.6" + "sqlite3": "^5.1.7", + "unzipper": "^0.12.3" } }, "@themost/xml": { @@ -16219,15 +16436,6 @@ "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", "dev": true }, - "@types/moment": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", - "integrity": "sha512-DyuyYGpV6r+4Z1bUznLi/Y7HpGn4iQ4IVcGn8zrr1P4KotKLdH0sbK1TFR6RGyX6B+G8u83wCzL+bpawKU/hdQ==", - "dev": true, - "requires": { - "moment": "*" - } - }, "@types/node": { "version": "10.17.60", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", @@ -16300,7 +16508,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "agent-base": { "version": "6.0.2", @@ -16334,15 +16543,14 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } }, "ansi-escapes": { @@ -16615,6 +16823,12 @@ "readable-stream": "^3.4.0" } }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "blueimp-md5": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", @@ -16867,6 +17081,12 @@ "browserslist": "^4.22.2" } }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, "create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -16978,7 +17198,8 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", - "dev": true + "dev": true, + "requires": {} }, "deep-extend": { "version": "0.6.0", @@ -17044,6 +17265,47 @@ "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==", "dev": true }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "electron-to-chromium": { "version": "1.4.625", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.625.tgz", @@ -17165,6 +17427,18 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -17220,6 +17494,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -17538,8 +17818,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-json-stable-stringify": { "version": "2.1.0", @@ -17553,6 +17832,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==" + }, "fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -17618,6 +17902,17 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true }, + "fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -17987,6 +18282,12 @@ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -18871,7 +19172,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "29.6.3", @@ -19743,10 +20045,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -19760,6 +20061,16 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -20304,6 +20615,15 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "package-lock-only": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/package-lock-only/-/package-lock-only-0.0.4.tgz", + "integrity": "sha512-fV1YHeTMWH5LKmdVqfWskm2/SG0iF2IrxJn3ziaPVx9CnpecGJzt8xXtLV+CYINENZwPFMtbxO5qupz0asNz1A==", + "dev": true, + "requires": { + "chalk": "^2.4.1" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -20442,6 +20762,12 @@ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -20481,9 +20807,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "pure-rand": { @@ -20607,6 +20933,11 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -21110,6 +21441,25 @@ "imurmurhash": "^0.1.4" } }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + }, + "unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", + "dev": true, + "requires": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, "update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", diff --git a/package.json b/package.json index 91fe77a..bdb22c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@themost/data", - "version": "2.6.63", + "version": "2.6.70", "description": "MOST Web Framework Codename Blueshift - Data module", "main": "index.js", "types": "index.d.ts", @@ -9,7 +9,7 @@ }, "peerDependencies": { "@themost/common": "^2.5.11", - "@themost/query": "^2.5.27", + "@themost/query": "^2.6.0", "@themost/xml": "^2.5.2" }, "engines": { @@ -28,6 +28,7 @@ "dependencies": { "@themost/events": "^1.3.0", "@themost/promise-sequence": "^1.0.1", + "ajv": "^8.17.1", "async": "^2.6.4", "lodash": "^4.17.21", "node-cache": "^5.1.2", @@ -43,13 +44,12 @@ "@themost/common": "^2.5.11", "@themost/json-logger": "^1.1.0", "@themost/peers": "^1.0.2", - "@themost/query": "^2.5.27", - "@themost/sqlite": "^2.6.16", + "@themost/query": "^2.6.0", + "@themost/sqlite": "^2.8.4", "@themost/xml": "^2.5.2", "@types/core-js": "^2.5.0", "@types/jest": "^27.4.1", "@types/lodash": "^4.14.149", - "@types/moment": "^2.13.0", "@types/node": "^10.12.0", "@types/pluralize": "0.0.29", "@types/q": "^1.5.1", diff --git a/spec/DataModelFilterParser.spec.ts b/spec/DataModelFilterParser.spec.ts index 0833b9e..8f73492 100644 --- a/spec/DataModelFilterParser.spec.ts +++ b/spec/DataModelFilterParser.spec.ts @@ -2,6 +2,7 @@ import {DataModelFilterParser} from '../data-model-filter.parser'; import {TestApplication} from './TestApplication'; import {DataContext} from '../types'; import {resolve} from 'path'; +import { TestUtils } from "./adapter/TestUtils"; describe('DataModelFilterParser', () => { @@ -95,4 +96,21 @@ describe('DataModelFilterParser', () => { } }); + it('should parse filter with json attributes', async () => { + await TestUtils.executeInTransaction(context, async () => { + const Products = context.model('Product').silent(); + const resolver = new DataModelFilterParser(Products); + const { $where, $expand } = await resolver.parseAsync( + `metadata/color eq 'silver'` + ); + const q = Products.asQueryable(); + Object.assign(q.query, { + $where, + $expand + }); + const items: any[] = await q.take(25).getItems(); + expect(items).toBeTruthy(); + }); + }); + }); diff --git a/spec/JsonAttribute.spec.ts b/spec/JsonAttribute.spec.ts new file mode 100644 index 0000000..fab9712 --- /dev/null +++ b/spec/JsonAttribute.spec.ts @@ -0,0 +1,162 @@ +import {TestApplication} from './TestApplication'; +import {DataContext} from '../types'; +import {resolve} from 'path'; +import { TestUtils } from "./adapter/TestUtils"; +import pluralize from "pluralize"; +import { setSingularRules } from '../data-pluralize'; + +describe('JsonAttribute', () => { + + let app: TestApplication; + let context: DataContext; + beforeAll((done) => { + app = new TestApplication(resolve(__dirname, 'test2')); + context = app.createContext(); + return done(); + }); + afterAll(async () => { + await context.finalizeAsync(); + await app.finalize(); + }); + + it('should select json attribute', async () => { + const Products = context.model('Product').silent(); + const items = await Products.asQueryable().select( + 'id', 'name', 'metadata/color as color' + ).take(10).getItems(); + expect(items).toBeTruthy(); + expect(items.length).toBeGreaterThan(0); + }); + + it('should update json attribute', async () => { + await TestUtils.executeInTransaction(context, async () => { + const Products = context.model('Product').silent(); + let item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)').getItem() + expect(item).toBeTruthy(); + item.metadata = { + color: 'silver' + } + await Products.save(item); + expect(item.dateCreated).toBeTruthy(); + item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)') + .select('metadata/color as color','metadata/dateCreated as dateCreated').getItem(); + expect(item).toBeTruthy(); + expect(item.color).toBe('silver'); + expect(item.dateCreated).toBeTruthy(); + item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)').getItem(); + expect(item).toBeTruthy(); + expect(item.metadata).toBeTruthy(); + }); + }); + + it('should update json structured value', async () => { + await TestUtils.executeInTransaction(context, async () => { + const Products = context.model('Product').silent(); + let item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)').getItem() + expect(item).toBeTruthy(); + item.metadata = { + color: 'silver', + audience: { + name: 'New customers', + description: 'New customers who have never purchased before', + audienceType: 'B2C', + geographicArea: 'Worldwide' + } + } + await Products.save(item); + item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)').getItem(); + expect(item).toBeTruthy(); + expect(item.metadata).toBeTruthy(); + expect(item.metadata.audience).toBeTruthy(); + item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)') + .select('metadata/audience/name as audienceName').getItem(); + expect(item).toBeTruthy(); + expect(item.audienceName).toBe('New customers'); + }); + }); + + it('should select json nested value', async () => { + await TestUtils.executeInTransaction(context, async () => { + const Products = context.model('Product').silent(); + let item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)').getItem() + expect(item).toBeTruthy(); + item.metadata = { + color: 'silver', + audience: { + name: 'New customers', + description: 'New customers who have never purchased before', + audienceType: 'B2C', + geographicArea: 'Worldwide' + } + } + await Products.save(item); + item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)').getItem(); + expect(item).toBeTruthy(); + expect(item.metadata).toBeTruthy(); + expect(item.metadata.audience).toBeTruthy(); + item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)') + .select('metadata/audience as audience').getItem(); + expect(item).toBeTruthy(); + expect(item.audience.name).toBe('New customers'); + }); + }); + + it('should throw error on invalid json', async () => { + await TestUtils.executeInTransaction(context, async () => { + const Products = context.model('Product').silent(); + let item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)').getItem() + expect(item).toBeTruthy(); + item.metadata = { + color: 'silver', + message : 'hello' + } + await expect(Products.save(item)).rejects.toThrow('The given structured value seems to be invalid. The property \'message\' is not defined in the target model.'); + }); + }); + + it('should use pluralize rules', async () => { + expect(pluralize.isSingular('metadata')).toBeFalsy(); + setSingularRules(pluralize); + expect(pluralize.isSingular('metadata')).toBeTruthy(); + }); + + it('should select json attribute from $select', async () => { + const Products = context.model('Product').silent(); + const options = { + $select: 'id,name,metadata/color as color', + $top: 10 + } + const q = await Products.filterAsync(options); + const items = await q.getItems(); + expect(items).toBeTruthy(); + expect(items.length).toBeGreaterThan(0); + }); + + it('should use json attribute in aggregate functions', async () => { + await TestUtils.executeInTransaction(context, async () => { + const Products = context.model('Product').silent(); + let items = await Products.asQueryable().select('id', 'metadata').where('category').equal('Laptops') + //.and('id').equal(19) + //.take(20) + .getItems(); + expect(items).toBeTruthy(); + const colors = ['silver', 'black', 'white', 'red', 'blue']; + items.forEach(item => { + item.metadata = { + color: colors[Math.floor(Math.random() * colors.length)] + } + }); + for (const item of items) { + await Products.save(item); + } + items = await Products.asQueryable().select( + 'count(metadata/color) as total', + 'metadata/color as color' + ).groupBy( + 'metadata/color' + ).getItems(); + expect(items).toBeTruthy(); + }); + }); + +}); diff --git a/spec/test2/config/models/Audience.json b/spec/test2/config/models/Audience.json new file mode 100644 index 0000000..68f23ca --- /dev/null +++ b/spec/test2/config/models/Audience.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://themost-framework.github.io/themost/models/2018/2/schema.json", + "@id": "https://schema.org/Audience", + "name": "Audience", + "description": "Intended audience for an item, i.e. the group for whom the item was created.", + "title": "Audience", + "abstract": false, + "sealed": false, + "implements": "Intangible", + "version": "1.0", + "fields": [ + { + "name": "audienceType", + "title": "Audience Type", + "description": "The target group associated with a given audience (e.g. veterans, car owners, musicians, etc.).", + "type": "Text" + }, + { + "name": "geographicArea", + "title": "Geographic Area", + "description": "The geographic area associated with the audience.", + "type": "Text" + } + ], + "privileges": [ + { + "mask": 15, + "type": "global" + }, + { + "mask": 15, + "type": "global", + "account": "Administrators" + } + ] +} diff --git a/spec/test2/config/models/Product.json b/spec/test2/config/models/Product.json index f98878b..9c57073 100644 --- a/spec/test2/config/models/Product.json +++ b/spec/test2/config/models/Product.json @@ -1,253 +1,262 @@ { - "$schema": "https://themost-framework.github.io/themost/models/2018/2/schema.json", - "name": "Product", - "title": "Products", - "hidden": false, - "sealed": false, - "abstract": false, - "version": "2.1", - "inherits": "Thing", - "caching":"conditional", - "fields": [ - { - "name": "category", - "title": "Category", - "description": "A category related to this product.", - "type": "Text" - }, - { - "name": "discontinued", - "title": "Discontinued", - "description": "Indicates whether this product is discontinued or not.", - "type": "Boolean" - }, - { - "name": "price", - "title": "Price", - "description": "The price of the product.", - "type": "Number" - }, - { - "name": "isRelatedTo", - "title": "Is Related to", - "description": "A pointer to another, somehow related product (or multiple products).", - "type": "Product" - }, - { - "name": "isSimilarTo", - "title": "Is Similar to", - "description": "A pointer to another, functionally similar product (or multiple products).", - "type": "Product" - }, - { - "name": "model", - "title": "Model", - "description": "The model of the product. Use with the URL of a ProductModel or a textual representation of the model identifier. The URL of the ProductModel can be from an external source. It is recommended to additionally provide strong product identifiers via the gtin8/gtin13/gtin14 and mpn properties.", - "type": "Text" - }, - { - "name": "productID", - "title": "Product ID", - "description": "The product identifier, such as ISBN. For example: <meta itemprop='productID' content='isbn:123-456-789'/>.", - "type": "Text" - }, - { - "name": "releaseDate", - "title": "Release Date", - "description": "The release date of a product or product model. This can be used to distinguish the exact variant of a product.", - "type": "Date" - }, - { - "name": "tags", - "title": "Tag", - "many": true, - "type": "Text", - "mapping": { - "associationAdapter": "ProductTags", - "associationType": "junction", - "cascade": "delete", - "associationObjectField": "product", - "associationValueField": "value", - "privileges": [ - { - "mask": 15, - "type": "global" - }, - { - "mask": 15, - "type": "global", - "account": "Administrators" - }, - { - "mask": 15, - "type": "global", - "account": "Contributors" - } - ] - } - }, - { - "name": "keywords", - "title": "Keyword", - "description": "The release date of a product or product model. This can be used to distinguish the exact variant of a product.", - "many": true, - "type": "Text", - "mapping": { - "associationAdapter": "ProductKeywords", - "associationType": "junction", - "cascade": "delete", - "associationObjectField": "product", - "associationValueField": "value", - "privileges": [ - { - "mask": 1, - "type": "global", - "account": "*" - }, - { - "mask": 15, - "type": "global", - "account": "Administrators" - } - ] - } - }, - { - "name": "productDimensions", - "type": "ProductDimension", - "nested": true, - "expandable": true, - "multiplicity": "ZeroOrOne", - "mapping": { - "parentModel": "Product", - "parentField": "id", - "childModel": "ProductDimension", - "childField": "product", - "associationType": "association", - "cascade": "delete" - } - }, - { - "name": "specialOffers", - "type": "SpecialOffer", - "nested": true, - "expandable": true, - "many": true, - "mapping": { - "parentModel": "Product", - "parentField": "id", - "childModel": "SpecialOffer", - "childField": "itemOffered", - "associationType": "association", - "cascade": "delete" - } - }, - { - "name": "orders", - "type": "Order", - "expandable": false, - "many": true, - "mapping": { - "parentModel": "Product", - "parentField": "id", - "childModel": "Order", - "childField": "orderedItem", - "associationType": "association", - "cascade": "delete" - } - }, - { - "name": "productImage", - "type": "ImageObject", - "nullable": true, - "nested": true, - "expandable": true, - "many": false - }, - { - "name": "madeIn", - "type": "Country", - "nullable": true, - "many": true, - "multiplicity": "ZeroOrOne", - "mapping": { - "associationType": "junction", - "associationObjectField": "product", - "associationValueField": "country", - "parentModel": "Product", - "parentField": "id", - "childModel": "Country", - "childField": "id", - "privileges": [ - { - "mask": 1, - "type": "global", - "account": "*" - }, - { - "mask": 15, - "type": "global" - }, - { - "mask": 15, - "type": "global", - "account": "Administrators" - } - ] - } - }, - { - "name": "productDescription", - "type": "ProductDescription", - "readonly": true, - "many": true, - "multiplicity": "ZeroOrOne", - "mapping": { - "parentModel": "Product", - "parentField": "id", - "childModel": "ProductDescription", - "childField": "product", - "associationType": "association", - "options": { - "$filter" : "inLanguage eq lang()" - } - } - }, - { - "name": "productDescriptions", - "type": "ProductDescription", - "nested": true, - "mapping": { - "parentModel": "Product", - "parentField": "id", - "childModel": "ProductDescription", - "childField": "product", - "associationType": "association", - "cascade": "delete" - } - } - ], - "constraints": [ - { - "type": "unique", - "description": "The model number must be unique across different records.", - "fields": ["model"] - } - ], - "privileges": [ - { + "$schema": "https://themost-framework.github.io/themost/models/2018/2/schema.json", + "name": "Product", + "title": "Products", + "hidden": false, + "sealed": false, + "abstract": false, + "version": "2.2.1", + "inherits": "Thing", + "caching": "conditional", + "fields": [ + { + "name": "category", + "title": "Category", + "description": "A category related to this product.", + "type": "Text" + }, + { + "name": "discontinued", + "title": "Discontinued", + "description": "Indicates whether this product is discontinued or not.", + "type": "Boolean" + }, + { + "name": "price", + "title": "Price", + "description": "The price of the product.", + "type": "Number" + }, + { + "name": "isRelatedTo", + "title": "Is Related to", + "description": "A pointer to another, somehow related product (or multiple products).", + "type": "Product" + }, + { + "name": "isSimilarTo", + "title": "Is Similar to", + "description": "A pointer to another, functionally similar product (or multiple products).", + "type": "Product" + }, + { + "name": "model", + "title": "Model", + "description": "The model of the product. Use with the URL of a ProductModel or a textual representation of the model identifier. The URL of the ProductModel can be from an external source. It is recommended to additionally provide strong product identifiers via the gtin8/gtin13/gtin14 and mpn properties.", + "type": "Text" + }, + { + "name": "productID", + "title": "Product ID", + "description": "The product identifier, such as ISBN. For example: <meta itemprop='productID' content='isbn:123-456-789'/>.", + "type": "Text" + }, + { + "name": "releaseDate", + "title": "Release Date", + "description": "The release date of a product or product model. This can be used to distinguish the exact variant of a product.", + "type": "Date" + }, + { + "name": "tags", + "title": "Tag", + "many": true, + "type": "Text", + "mapping": { + "associationAdapter": "ProductTags", + "associationType": "junction", + "cascade": "delete", + "associationObjectField": "product", + "associationValueField": "value", + "privileges": [ + { + "mask": 15, + "type": "global" + }, + { + "mask": 15, + "type": "global", + "account": "Administrators" + }, + { + "mask": 15, + "type": "global", + "account": "Contributors" + } + ] + } + }, + { + "name": "keywords", + "title": "Keyword", + "description": "The release date of a product or product model. This can be used to distinguish the exact variant of a product.", + "many": true, + "type": "Text", + "mapping": { + "associationAdapter": "ProductKeywords", + "associationType": "junction", + "cascade": "delete", + "associationObjectField": "product", + "associationValueField": "value", + "privileges": [ + { + "mask": 1, + "type": "global", + "account": "*" + }, + { + "mask": 15, + "type": "global", + "account": "Administrators" + } + ] + } + }, + { + "name": "productDimensions", + "type": "ProductDimension", + "nested": true, + "expandable": true, + "multiplicity": "ZeroOrOne", + "mapping": { + "parentModel": "Product", + "parentField": "id", + "childModel": "ProductDimension", + "childField": "product", + "associationType": "association", + "cascade": "delete" + } + }, + { + "name": "specialOffers", + "type": "SpecialOffer", + "nested": true, + "expandable": true, + "many": true, + "mapping": { + "parentModel": "Product", + "parentField": "id", + "childModel": "SpecialOffer", + "childField": "itemOffered", + "associationType": "association", + "cascade": "delete" + } + }, + { + "name": "orders", + "type": "Order", + "expandable": false, + "many": true, + "mapping": { + "parentModel": "Product", + "parentField": "id", + "childModel": "Order", + "childField": "orderedItem", + "associationType": "association", + "cascade": "delete" + } + }, + { + "name": "productImage", + "type": "ImageObject", + "nullable": true, + "nested": true, + "expandable": true, + "many": false + }, + { + "name": "madeIn", + "type": "Country", + "nullable": true, + "many": true, + "multiplicity": "ZeroOrOne", + "mapping": { + "associationType": "junction", + "associationObjectField": "product", + "associationValueField": "country", + "parentModel": "Product", + "parentField": "id", + "childModel": "Country", + "childField": "id", + "privileges": [ + { "mask": 1, "type": "global", "account": "*" - }, - { + }, + { "mask": 15, "type": "global" - }, - { + }, + { "mask": 15, "type": "global", "account": "Administrators" + } + ] + } + }, + { + "name": "productDescription", + "type": "ProductDescription", + "readonly": true, + "many": true, + "multiplicity": "ZeroOrOne", + "mapping": { + "parentModel": "Product", + "parentField": "id", + "childModel": "ProductDescription", + "childField": "product", + "associationType": "association", + "options": { + "$filter": "inLanguage eq lang()" } - ] + } + }, + { + "name": "productDescriptions", + "type": "ProductDescription", + "nested": true, + "mapping": { + "parentModel": "Product", + "parentField": "id", + "childModel": "ProductDescription", + "childField": "product", + "associationType": "association", + "cascade": "delete" + } + }, + { + "name": "metadata", + "many": false, + "type": "Json", + "indexed": true, + "additionalType": "ProductMetadata" + } + ], + "constraints": [ + { + "type": "unique", + "description": "The model number must be unique across different records.", + "fields": [ + "model" + ] + } + ], + "privileges": [ + { + "mask": 1, + "type": "global", + "account": "*" + }, + { + "mask": 15, + "type": "global" + }, + { + "mask": 15, + "type": "global", + "account": "Administrators" + } + ] } diff --git a/spec/test2/config/models/ProductMetadata.json b/spec/test2/config/models/ProductMetadata.json new file mode 100644 index 0000000..2de0e53 --- /dev/null +++ b/spec/test2/config/models/ProductMetadata.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://themost-framework.github.io/themost/models/2018/2/schema.json", + "title": "Product Metadata", + "name": "ProductMetadata", + "abstract": true, + "version": "1.0.0", + "fields": [ + { + "name": "color", + "title": "Color", + "description": "A color related to the product.", + "type": "Text" + }, + { + "name": "mpn", + "title": "Manufacturer Part Number", + "description": "The manufacturer part number (MPN) of the product.", + "type": "Text" + }, + { + "name": "slogan", + "title": "Slogan", + "description": "A slogan or tagline associated with the product.", + "type": "Text" + }, + { + "@id": "https://themost.io/schemas/dateCreated", + "name": "dateCreated", + "title": "dateCreated", + "description": "The date on which this item was created.", + "type": "DateTime", + "readonly": true, + "value": "javascript:return new Date();", + "calculation": "javascript:return (new Date());" + }, + { + "@id": "https://themost.io/schemas/dateModified", + "name": "dateModified", + "title": "dateModified", + "description": "The date on which this item was most recently modified.", + "type": "DateTime", + "readonly": true, + "value": "javascript:return (new Date());", + "calculation": "javascript:return (new Date());" + }, + { + "@id": "https://schema.org/Audience", + "name": "audience", + "title": "Audience", + "description": "An intended audience associated with the product.", + "type": "Json", + "additionalType": "Audience" + } + ], + "privileges": [ + { + "mask": 1, + "type": "global", + "account": "*" + }, + { + "mask": 15, + "type": "global" + }, + { + "mask": 15, + "type": "global", + "account": "Administrators" + } + ] +} \ No newline at end of file diff --git a/spec/test2/db/local.db b/spec/test2/db/local.db index 50d7032..de3f14c 100644 Binary files a/spec/test2/db/local.db and b/spec/test2/db/local.db differ diff --git a/tsconfig.json b/tsconfig.json index b271c14..23cdefa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "target": "esnext", "module": "commonjs", "moduleResolution": "node", + "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "sourceMap": true, "noImplicitAny": true, diff --git a/types.d.ts b/types.d.ts index 62c2dcd..3509d5c 100644 --- a/types.d.ts +++ b/types.d.ts @@ -179,6 +179,8 @@ export declare class DataField { calculation?: string; readonly?: boolean; editable?: boolean; + insertable? : boolean; + additionalType?: string; mapping?: DataAssociationMapping; expandable?: boolean; nested?: boolean; @@ -200,6 +202,7 @@ export declare class DataEventArgs { query?: any; previous?: any; throwError?: boolean; + result?: unknown; } export declare interface BeforeSaveEventListener {