UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

480 lines (459 loc) 15.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkFieldNameRegex = checkFieldNameRegex; exports.checkPrimaryKey = checkPrimaryKey; exports.checkSchema = checkSchema; exports.validateFieldsDeep = validateFieldsDeep; var _rxError = require("../../rx-error.js"); var _rxSchemaHelper = require("../../rx-schema-helper.js"); var _index = require("../../plugins/utils/index.js"); var _entityProperties = require("./entity-properties.js"); /** * does additional checks over the schema-json * to ensure nothing is broken or not supported */ /** * checks if the fieldname is allowed * this makes sure that the fieldnames can be transformed into javascript-vars * and does not conquer the observe$ and populate_ fields * @throws {Error} */ function checkFieldNameRegex(fieldName) { if (fieldName === '_deleted') { return; } if (['properties'].includes(fieldName)) { throw (0, _rxError.newRxError)('SC23', { fieldName }); } var regexStr = '^[a-zA-Z](?:[[a-zA-Z0-9_]*]?[a-zA-Z0-9])?$'; var regex = new RegExp(regexStr); if ( /** * It must be allowed to set _id as primaryKey. * This makes it sometimes easier to work with RxDB+CouchDB * @link https://github.com/pubkey/rxdb/issues/681 */ fieldName !== '_id' && !fieldName.match(regex)) { throw (0, _rxError.newRxError)('SC1', { regex: regexStr, fieldName }); } } /** * validate that all schema-related things are ok */ function validateFieldsDeep(rxJsonSchema) { var primaryPath = (0, _rxSchemaHelper.getPrimaryFieldOfPrimaryKey)(rxJsonSchema.primaryKey); function checkField(fieldName, schemaObj, path) { if (typeof fieldName === 'string' && typeof schemaObj === 'object' && !Array.isArray(schemaObj) && path.split('.').pop() !== 'patternProperties') checkFieldNameRegex(fieldName); // 'item' only allowed it type=='array' if (Object.prototype.hasOwnProperty.call(schemaObj, 'item') && schemaObj.type !== 'array') { throw (0, _rxError.newRxError)('SC2', { fieldName }); } /** * required fields cannot be set via 'required: true', * but must be set via required: [] */ if (Object.prototype.hasOwnProperty.call(schemaObj, 'required') && typeof schemaObj.required === 'boolean') { throw (0, _rxError.newRxError)('SC24', { fieldName }); } // $ref is not allowed if (Object.prototype.hasOwnProperty.call(schemaObj, '$ref')) { throw (0, _rxError.newRxError)('SC40', { fieldName }); } // if ref given, must be type=='string', type=='array' with string-items or type==['string','null'] if (Object.prototype.hasOwnProperty.call(schemaObj, 'ref')) { if (Array.isArray(schemaObj.type)) { if (schemaObj.type.length > 2 || !schemaObj.type.includes('string') || !schemaObj.type.includes('null')) { throw (0, _rxError.newRxError)('SC4', { fieldName }); } } else { switch (schemaObj.type) { case 'string': break; case 'array': if (!schemaObj.items || !schemaObj.items.type || schemaObj.items.type !== 'string') { throw (0, _rxError.newRxError)('SC3', { fieldName }); } break; default: throw (0, _rxError.newRxError)('SC4', { fieldName }); } } } var isNested = path.split('.').length >= 2; // nested only if (isNested) { if (schemaObj.default) { throw (0, _rxError.newRxError)('SC7', { path }); } } // first level if (!isNested) { // if _id is used, it must be primaryKey if (fieldName === '_id' && primaryPath !== '_id') { throw (0, _rxError.newRxError)('COL2', { fieldName }); } // check underscore fields if (fieldName.charAt(0) === '_') { if ( // exceptional allow underscore on these fields. fieldName === '_id' || fieldName === '_deleted') { return; } throw (0, _rxError.newRxError)('SC8', { fieldName }); } } } function traverse(currentObj, currentPath) { if (!currentObj || typeof currentObj !== 'object') { return; } Object.keys(currentObj).forEach(attributeName => { var schemaObj = currentObj[attributeName]; if (!currentObj.properties && schemaObj && typeof schemaObj === 'object' && !Array.isArray(currentObj)) { checkField(attributeName, schemaObj, currentPath); } var nextPath = currentPath; if (attributeName !== 'properties') nextPath = nextPath + '.' + attributeName; traverse(schemaObj, nextPath); }); } traverse(rxJsonSchema, ''); return true; } function checkPrimaryKey(jsonSchema) { if (!jsonSchema.primaryKey) { throw (0, _rxError.newRxError)('SC30', { schema: jsonSchema }); } function validatePrimarySchemaPart(schemaPart) { if (!schemaPart) { throw (0, _rxError.newRxError)('SC33', { schema: jsonSchema }); } var type = schemaPart.type; if (!type || !['string', 'number', 'integer'].includes(type)) { throw (0, _rxError.newRxError)('SC32', { schema: jsonSchema, args: { schemaPart } }); } } if (typeof jsonSchema.primaryKey === 'string') { var key = jsonSchema.primaryKey; var schemaPart = jsonSchema.properties[key]; validatePrimarySchemaPart(schemaPart); } else { var compositePrimaryKey = jsonSchema.primaryKey; var keySchemaPart = (0, _rxSchemaHelper.getSchemaByObjectPath)(jsonSchema, compositePrimaryKey.key); validatePrimarySchemaPart(keySchemaPart); compositePrimaryKey.fields.forEach(field => { var schemaPart = (0, _rxSchemaHelper.getSchemaByObjectPath)(jsonSchema, field); validatePrimarySchemaPart(schemaPart); }); } /** * The primary key must have a maxLength set * which is required by some RxStorage implementations * to ensure we can craft custom index strings. */ var primaryPath = (0, _rxSchemaHelper.getPrimaryFieldOfPrimaryKey)(jsonSchema.primaryKey); var primaryPathSchemaPart = jsonSchema.properties[primaryPath]; if (!primaryPathSchemaPart.maxLength) { throw (0, _rxError.newRxError)('SC39', { schema: jsonSchema, args: { primaryPathSchemaPart } }); } else if (!isFinite(primaryPathSchemaPart.maxLength)) { throw (0, _rxError.newRxError)('SC41', { schema: jsonSchema, args: { primaryPathSchemaPart } }); } } /** * computes real path of the object path in the collection schema */ function getSchemaPropertyRealPath(shortPath) { var pathParts = shortPath.split('.'); var realPath = ''; for (var i = 0; i < pathParts.length; i += 1) { if (pathParts[i] !== '[]') { realPath = realPath.concat('.properties.'.concat(pathParts[i])); } else { realPath = realPath.concat('.items'); } } return (0, _index.trimDots)(realPath); } /** * does the checking * @throws {Error} if something is not ok */ function checkSchema(jsonSchema) { if (!jsonSchema.primaryKey) { throw (0, _rxError.newRxError)('SC30', { schema: jsonSchema }); } if (!Object.prototype.hasOwnProperty.call(jsonSchema, 'properties')) { throw (0, _rxError.newRxError)('SC29', { schema: jsonSchema }); } // _rev MUST NOT exist, it is added by RxDB if (jsonSchema.properties._rev) { throw (0, _rxError.newRxError)('SC10', { schema: jsonSchema }); } // check version if (!Object.prototype.hasOwnProperty.call(jsonSchema, 'version') || typeof jsonSchema.version !== 'number' || jsonSchema.version < 0) { throw (0, _rxError.newRxError)('SC11', { version: jsonSchema.version }); } validateFieldsDeep(jsonSchema); checkPrimaryKey(jsonSchema); Object.keys(jsonSchema.properties).forEach(key => { var value = jsonSchema.properties[key]; // check primary if (key === jsonSchema.primaryKey) { if (jsonSchema.indexes && jsonSchema.indexes.includes(key)) { throw (0, _rxError.newRxError)('SC13', { value, schema: jsonSchema }); } if (value.unique) { throw (0, _rxError.newRxError)('SC14', { value, schema: jsonSchema }); } if (jsonSchema.encrypted && jsonSchema.encrypted.includes(key)) { throw (0, _rxError.newRxError)('SC15', { value, schema: jsonSchema }); } if (value.type !== 'string') { throw (0, _rxError.newRxError)('SC16', { value, schema: jsonSchema }); } } // check if RxDocument-property if ((0, _entityProperties.rxDocumentProperties)().includes(key)) { throw (0, _rxError.newRxError)('SC17', { key, schema: jsonSchema }); } }); // check format of jsonSchema.indexes if (jsonSchema.indexes) { // should be an array if (!(0, _index.isMaybeReadonlyArray)(jsonSchema.indexes)) { throw (0, _rxError.newRxError)('SC18', { indexes: jsonSchema.indexes, schema: jsonSchema }); } jsonSchema.indexes.forEach(index => { // should contain strings or array of strings if (!(typeof index === 'string' || Array.isArray(index))) { throw (0, _rxError.newRxError)('SC19', { index, schema: jsonSchema }); } // if is a compound index it must contain strings if (Array.isArray(index)) { for (var i = 0; i < index.length; i += 1) { if (typeof index[i] !== 'string') { throw (0, _rxError.newRxError)('SC20', { index, schema: jsonSchema }); } } } /** * To be able to craft custom indexable string with compound fields, * we need to know the maximum fieldlength of the fields values * when they are transformed to strings. * Therefore we need to enforce some properties inside of the schema. */ var indexAsArray = (0, _index.isMaybeReadonlyArray)(index) ? index : [index]; indexAsArray.forEach(fieldName => { var schemaPart = (0, _rxSchemaHelper.getSchemaByObjectPath)(jsonSchema, fieldName); var type = schemaPart.type; switch (type) { case 'string': var maxLength = schemaPart.maxLength; if (!maxLength) { throw (0, _rxError.newRxError)('SC34', { index, field: fieldName, schema: jsonSchema }); } break; case 'number': case 'integer': var multipleOf = schemaPart.multipleOf; if (!multipleOf) { throw (0, _rxError.newRxError)('SC35', { index, field: fieldName, schema: jsonSchema }); } var maximum = schemaPart.maximum; var minimum = schemaPart.minimum; if (typeof maximum === 'undefined' || typeof minimum === 'undefined') { throw (0, _rxError.newRxError)('SC37', { index, field: fieldName, schema: jsonSchema }); } if (!isFinite(maximum) || !isFinite(minimum)) { throw (0, _rxError.newRxError)('SC41', { index, field: fieldName, schema: jsonSchema }); } break; case 'boolean': /** * If a boolean field is used as an index, * it must be required. */ var parentPath = ''; var lastPathPart = fieldName; if (fieldName.includes('.')) { var partParts = fieldName.split('.'); lastPathPart = partParts.pop(); parentPath = partParts.join('.'); } var parentSchemaPart = parentPath === '' ? jsonSchema : (0, _rxSchemaHelper.getSchemaByObjectPath)(jsonSchema, parentPath); if (!parentSchemaPart.required || !parentSchemaPart.required.includes(lastPathPart)) { throw (0, _rxError.newRxError)('SC38', { index, field: fieldName, schema: jsonSchema }); } break; default: throw (0, _rxError.newRxError)('SC36', { fieldName, type: schemaPart.type, schema: jsonSchema }); } }); }); } // remove backward-compatibility for index: true Object.keys((0, _index.flattenObject)(jsonSchema)).map(key => { // flattenObject returns only ending paths, we need all paths pointing to an object var split = key.split('.'); split.pop(); // all but last return split.join('.'); }).filter(key => key !== '').filter((elem, pos, arr) => arr.indexOf(elem) === pos) // unique .filter(key => { // check if this path defines an index var value = (0, _index.getProperty)(jsonSchema, key); return value && !!value.index; }).forEach(key => { // replace inner properties key = key.replace('properties.', ''); // first key = key.replace(/\.properties\./g, '.'); // middle throw (0, _rxError.newRxError)('SC26', { index: (0, _index.trimDots)(key), schema: jsonSchema }); }); /* check types of the indexes */ (jsonSchema.indexes || []).reduce((indexPaths, currentIndex) => { if ((0, _index.isMaybeReadonlyArray)(currentIndex)) { (0, _index.appendToArray)(indexPaths, currentIndex); } else { indexPaths.push(currentIndex); } return indexPaths; }, []).filter((elem, pos, arr) => arr.indexOf(elem) === pos) // from now on working only with unique indexes .map(indexPath => { var realPath = getSchemaPropertyRealPath(indexPath); // real path in the collection schema var schemaObj = (0, _index.getProperty)(jsonSchema, realPath); // get the schema of the indexed property if (!schemaObj || typeof schemaObj !== 'object') { throw (0, _rxError.newRxError)('SC21', { index: indexPath, schema: jsonSchema }); } return { indexPath, schemaObj }; }).filter(index => index.schemaObj.type !== 'string' && index.schemaObj.type !== 'integer' && index.schemaObj.type !== 'number' && index.schemaObj.type !== 'boolean').forEach(index => { throw (0, _rxError.newRxError)('SC22', { key: index.indexPath, type: index.schemaObj.type, schema: jsonSchema }); }); /* ensure encrypted fields exist in the schema */ if (jsonSchema.encrypted) { jsonSchema.encrypted.forEach(propPath => { // real path in the collection schema var realPath = getSchemaPropertyRealPath(propPath); // get the schema of the indexed property var schemaObj = (0, _index.getProperty)(jsonSchema, realPath); if (!schemaObj || typeof schemaObj !== 'object') { throw (0, _rxError.newRxError)('SC28', { field: propPath, schema: jsonSchema }); } }); } } //# sourceMappingURL=check-schema.js.map