UNPKG

rxdb

Version:

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

295 lines (270 loc) 8.33 kB
import { newRxError } from "./rx-error.js"; import { appendToArray, ensureNotFalsy, flatClone, getProperty, isMaybeReadonlyArray, REGEX_ALL_DOTS, RX_META_LWT_MINIMUM, sortObject, trimDots } from "./plugins/utils/index.js"; /** * Helper function to create a valid RxJsonSchema * with a given version. */ export function getPseudoSchemaForVersion(version, primaryKey) { var pseudoSchema = fillWithDefaultSettings({ version, type: 'object', primaryKey: primaryKey, properties: { [primaryKey]: { type: 'string', maxLength: 100 }, value: { type: 'string' } }, indexes: [[primaryKey]], required: [primaryKey] }); return pseudoSchema; } /** * Returns the sub-schema for a given path */ export function getSchemaByObjectPath(rxJsonSchema, path) { var usePath = path; usePath = usePath.replace(REGEX_ALL_DOTS, '.properties.'); usePath = 'properties.' + usePath; usePath = trimDots(usePath); var ret = getProperty(rxJsonSchema, usePath); return ret; } export function fillPrimaryKey(primaryPath, jsonSchema, documentData) { // optimization shortcut. if (typeof jsonSchema.primaryKey === 'string') { return documentData; } var newPrimary = getComposedPrimaryKeyOfDocumentData(jsonSchema, documentData); var existingPrimary = documentData[primaryPath]; if (existingPrimary && existingPrimary !== newPrimary) { throw newRxError('DOC19', { args: { documentData, existingPrimary, newPrimary }, schema: jsonSchema }); } documentData[primaryPath] = newPrimary; return documentData; } export function getPrimaryFieldOfPrimaryKey(primaryKey) { if (typeof primaryKey === 'string') { return primaryKey; } else { return primaryKey.key; } } export function getLengthOfPrimaryKey(schema) { var primaryPath = getPrimaryFieldOfPrimaryKey(schema.primaryKey); var schemaPart = getSchemaByObjectPath(schema, primaryPath); return ensureNotFalsy(schemaPart.maxLength); } /** * Returns the composed primaryKey of a document by its data. */ export function getComposedPrimaryKeyOfDocumentData(jsonSchema, documentData) { if (typeof jsonSchema.primaryKey === 'string') { return documentData[jsonSchema.primaryKey]; } var compositePrimary = jsonSchema.primaryKey; return compositePrimary.fields.map(field => { var value = getProperty(documentData, field); if (typeof value === 'undefined') { throw newRxError('DOC18', { args: { field, documentData } }); } return value; }).join(compositePrimary.separator); } /** * Normalize the RxJsonSchema. * We need this to ensure everything is set up properly * and we have the same hash on schemas that represent the same value but * have different json. * * - Orders the schemas attributes by alphabetical order * - Adds the primaryKey to all indexes that do not contain the primaryKey * - We need this for deterministic sort order on all queries, which is required for event-reduce to work. * * @return RxJsonSchema - ordered and filled */ export function normalizeRxJsonSchema(jsonSchema) { var normalizedSchema = sortObject(jsonSchema, true); return normalizedSchema; } /** * If the schema does not specify any index, * we add this index so we at least can run RxQuery() * and only select non-deleted fields. */ export function getDefaultIndex(primaryPath) { return ['_deleted', primaryPath]; } /** * fills the schema-json with default-settings * @return cloned schemaObj */ export function fillWithDefaultSettings(schemaObj) { schemaObj = flatClone(schemaObj); var primaryPath = getPrimaryFieldOfPrimaryKey(schemaObj.primaryKey); schemaObj.properties = flatClone(schemaObj.properties); // additionalProperties is always false schemaObj.additionalProperties = false; // fill with key-compression-state () if (!Object.prototype.hasOwnProperty.call(schemaObj, 'keyCompression')) { schemaObj.keyCompression = false; } // indexes must be array schemaObj.indexes = schemaObj.indexes ? schemaObj.indexes.slice(0) : []; // required must be array schemaObj.required = schemaObj.required ? schemaObj.required.slice(0) : []; // encrypted must be array schemaObj.encrypted = schemaObj.encrypted ? schemaObj.encrypted.slice(0) : []; // add _rev schemaObj.properties._rev = { type: 'string', minLength: 1 }; // add attachments schemaObj.properties._attachments = { type: 'object' }; // add deleted flag schemaObj.properties._deleted = { type: 'boolean' }; // add meta property schemaObj.properties._meta = RX_META_SCHEMA; /** * meta fields are all required */ schemaObj.required = schemaObj.required ? schemaObj.required.slice(0) : []; schemaObj.required.push('_deleted'); schemaObj.required.push('_rev'); schemaObj.required.push('_meta'); schemaObj.required.push('_attachments'); // final fields are always required var finalFields = getFinalFields(schemaObj); appendToArray(schemaObj.required, finalFields); schemaObj.required = schemaObj.required.filter(field => !field.includes('.')).filter((elem, pos, arr) => arr.indexOf(elem) === pos); // unique; // version is 0 by default schemaObj.version = schemaObj.version || 0; var useIndexes = schemaObj.indexes.map(index => { var arIndex = isMaybeReadonlyArray(index) ? index.slice(0) : [index]; /** * Append primary key to indexes that do not contain the primaryKey. * All indexes must have the primaryKey to ensure a deterministic sort order. */ if (!arIndex.includes(primaryPath)) { arIndex.push(primaryPath); } // add _deleted flag to all indexes so we can query only non-deleted fields // in RxDB itself if (arIndex[0] !== '_deleted') { arIndex.unshift('_deleted'); } return arIndex; }); if (useIndexes.length === 0) { useIndexes.push(getDefaultIndex(primaryPath)); } // we need this index for the getChangedDocumentsSince() method useIndexes.push(['_meta.lwt', primaryPath]); // also add the internalIndexes if (schemaObj.internalIndexes) { schemaObj.internalIndexes.map(idx => { useIndexes.push(idx); }); } // make indexes unique var hasIndex = new Set(); useIndexes.filter(index => { var indexStr = index.join(','); if (hasIndex.has(indexStr)) { return false; } else { hasIndex.add(indexStr); return true; } }); schemaObj.indexes = useIndexes; return schemaObj; } export var RX_META_SCHEMA = { type: 'object', properties: { /** * The last-write time. * Unix time in milliseconds. */ lwt: { type: 'number', /** * We use 1 as minimum so that the value is never falsy. */ minimum: RX_META_LWT_MINIMUM, maximum: 1000000000000000, multipleOf: 0.01 } }, /** * Additional properties are allowed * and can be used by plugins to set various flags. */ additionalProperties: true, required: ['lwt'] }; /** * returns the final-fields of the schema * @return field-names of the final-fields */ export function getFinalFields(jsonSchema) { var ret = Object.keys(jsonSchema.properties).filter(key => jsonSchema.properties[key].final); // primary is also final var primaryPath = getPrimaryFieldOfPrimaryKey(jsonSchema.primaryKey); ret.push(primaryPath); // fields of composite primary are final if (typeof jsonSchema.primaryKey !== 'string') { jsonSchema.primaryKey.fields.forEach(field => ret.push(field)); } return ret; } /** * fills all unset fields with default-values if set * @hotPath */ export function fillObjectWithDefaults(rxSchema, obj) { var defaultKeys = Object.keys(rxSchema.defaultValues); for (var i = 0; i < defaultKeys.length; ++i) { var key = defaultKeys[i]; if (!Object.prototype.hasOwnProperty.call(obj, key) || typeof obj[key] === 'undefined') { obj[key] = rxSchema.defaultValues[key]; } } return obj; } export var DEFAULT_CHECKPOINT_SCHEMA = { type: 'object', properties: { id: { type: 'string' }, lwt: { type: 'number' } }, required: ['id', 'lwt'], additionalProperties: false }; //# sourceMappingURL=rx-schema-helper.js.map