UNPKG

@react-native-ohos/realm

Version:

Realm by MongoDB is an offline-first mobile database: an alternative to SQLite and key-value stores

368 lines 15.8 kB
"use strict"; //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// Object.defineProperty(exports, "__esModule", { value: true }); exports.extractGeneric = exports.normalizePropertySchema = exports.normalizeObjectSchema = exports.normalizeRealmSchema = void 0; const assert_1 = require("../assert"); const flags_1 = require("../flags"); const errors_1 = require("../errors"); const PRIMITIVE_TYPES = new Set([ "bool", "int", "float", "double", "decimal128", "objectId", "string", "data", "date", "mixed", "uuid", ]); const COLLECTION_TYPES = new Set(["list", "dictionary", "set"]); const COLLECTION_SHORTHAND_TO_NAME = { "[]": "list", "{}": "dictionary", "<>": "set", }; const COLLECTION_SUFFIX_LENGTH = "[]".length; const PRESENTATION_TYPES = new Set(["counter"]); const PRESENTATION_TO_REALM_TYPE = { counter: "int", }; const OPTIONAL_MARKER = "?"; function isPrimitive(type) { return PRIMITIVE_TYPES.has(type); } function isCollection(type) { return COLLECTION_TYPES.has(type); } function isPresentationType(type) { return PRESENTATION_TYPES.has(type); } function isUserDefined(type) { return !!type && !(isPrimitive(type) || isCollection(type) || type === "object" || type === "linkingObjects"); } /** * Transform a validated user-provided Realm schema into its canonical form. */ function normalizeRealmSchema(realmSchema) { return realmSchema.map(normalizeObjectSchema); } exports.normalizeRealmSchema = normalizeRealmSchema; /** * Transform a validated user-provided object schema into its canonical form. */ function normalizeObjectSchema(arg) { if (typeof arg === "function") { (0, assert_1.assert)(arg.schema, () => new errors_1.SchemaParseError("A static schema must be specified on this class.")); const objectSchema = normalizeObjectSchema(arg.schema); objectSchema.ctor = arg; return objectSchema; } // ---- THIS IF BLOCK HAS NOT YET BEEN REWRITTEN ---- // TODO: Determine if we still want to support this (should show warning to users of future deprecation) if (Array.isArray(arg.properties)) { if (flags_1.flags.ALLOW_VALUES_ARRAYS) { return normalizeObjectSchema({ ...arg, // Build the PropertiesTypes object properties: Object.fromEntries(arg.properties.map(({ name, ...rest }) => [name, rest])), }); } throw new Error("Array of properties are no longer supported. Use an object instead."); } // -------------------------------------------------- const { name, primaryKey, asymmetric, embedded, properties } = arg; (0, assert_1.assert)(name.length > 0, objectError("", "'name' must be specified.")); const primaryKeyFieldIsMissing = primaryKey && !Object.prototype.hasOwnProperty.call(properties, primaryKey); (0, assert_1.assert)(!primaryKeyFieldIsMissing, objectError(name, `'${primaryKey}' is set as the primary key field but was not found in 'properties'.`)); return { name, primaryKey, asymmetric: !!asymmetric, embedded: !!embedded, properties: normalizePropertySchemas(name, properties, primaryKey), }; } exports.normalizeObjectSchema = normalizeObjectSchema; /** * Transform user-provided property schemas into their canonical forms. */ function normalizePropertySchemas(objectName, propertiesSchemas, primaryKey) { const normalizedSchemas = {}; for (const propertyName in propertiesSchemas) { normalizedSchemas[propertyName] = normalizePropertySchema({ objectName, propertyName, propertySchema: propertiesSchemas[propertyName], isPrimaryKey: primaryKey === propertyName, }); } return normalizedSchemas; } /** * Transform a user-provided property schema into its canonical form. */ function normalizePropertySchema(info) { const isUsingShorthand = typeof info.propertySchema === "string"; const normalizedSchema = isUsingShorthand ? normalizePropertySchemaShorthand(info) : normalizePropertySchemaObject(info); return normalizedSchema; } exports.normalizePropertySchema = normalizePropertySchema; /** * Transform a validated user-provided property schema that is using * the shorthand string notation into its canonical form. */ function normalizePropertySchemaShorthand(info) { let { propertySchema } = info; (0, assert_1.assert)(propertySchema.length > 0, propError(info, "The type must be specified.")); let type = ""; let objectType; let presentation; let optional; if (hasCollectionSuffix(propertySchema)) { const suffix = propertySchema.substring(propertySchema.length - COLLECTION_SUFFIX_LENGTH); type = COLLECTION_SHORTHAND_TO_NAME[suffix]; propertySchema = propertySchema.substring(0, propertySchema.length - COLLECTION_SUFFIX_LENGTH); (0, assert_1.assert)(propertySchema.length > 0, propError(info, `The element type must be specified (Example: 'int${suffix}')`)); const isNestedCollection = hasCollectionSuffix(propertySchema); (0, assert_1.assert)(!isNestedCollection, propError(info, "Nested collections are not supported.")); } if (propertySchema.endsWith(OPTIONAL_MARKER)) { optional = true; propertySchema = propertySchema.substring(0, propertySchema.length - OPTIONAL_MARKER.length); (0, assert_1.assert)(propertySchema.length > 0, propError(info, "The type must be specified. (Examples: 'int?' and 'int?[]')")); const usingOptionalOnCollection = hasCollectionSuffix(propertySchema); (0, assert_1.assert)(!usingOptionalOnCollection, propError(info, "Collections cannot be optional. To allow elements of the collection to be optional, use '?' after the element type. (Examples: 'int?[]', 'int?{}', and 'int?<>')")); } if (isPresentationType(propertySchema)) { presentation = propertySchema; propertySchema = PRESENTATION_TO_REALM_TYPE[propertySchema]; } if (isPrimitive(propertySchema)) { if (isCollection(type)) { objectType = propertySchema; } else { type = propertySchema; } } else if (isCollection(propertySchema)) { throw new errors_1.PropertySchemaParseError(`Cannot use the collection name ${propertySchema}. (Examples: 'int[]' (list), 'int{}' (dictionary), and 'int<>' (set))`, info); } else if (propertySchema === "object") { throw new errors_1.PropertySchemaParseError("To define a relationship, use either 'MyObjectType' or { type: 'object', objectType: 'MyObjectType' }", info); } else if (propertySchema === "linkingObjects") { throw new errors_1.PropertySchemaParseError("To define an inverse relationship, use { type: 'linkingObjects', objectType: 'MyObjectType', property: 'myObjectTypesProperty' }", info); } else { // User-defined types objectType = propertySchema; if (!isCollection(type)) { type = "object"; } } switch (presentation) { case "counter": // If `type` is not an int at this point, a collection shorthand is used. (0, assert_1.assert)(type === "int", propError(info, "Counters cannot be used in collections.")); break; default: break; } if (isAlwaysOptional(type, objectType)) { optional = true; } else if (isNeverOptional(type, objectType)) { (0, assert_1.assert)(!optional, propError(info, "User-defined types in lists and sets are always non-optional and cannot be made optional. Remove '?' or change the type.")); optional = false; } const normalizedSchema = { name: info.propertyName, type: type, optional: !!optional, indexed: !!info.isPrimaryKey, mapTo: info.propertyName, }; // Add optional properties only if defined (tests expect no 'undefined' properties) if (objectType !== undefined) normalizedSchema.objectType = objectType; if (presentation !== undefined) normalizedSchema.presentation = presentation; return normalizedSchema; } /** * Transform a validated user-provided property schema that is using * the relaxed object notation into its canonical form. */ function normalizePropertySchemaObject(info) { const { propertySchema } = info; const { type, objectType, presentation, property, default: defaultValue } = propertySchema; let { optional, indexed } = propertySchema; (0, assert_1.assert)(type.length > 0, propError(info, "'type' must be specified.")); assertNotUsingShorthand(type, info); assertNotUsingShorthand(objectType, info); if (isPrimitive(type)) { (0, assert_1.assert)(objectType === undefined, propError(info, `'objectType' cannot be defined when 'type' is '${type}'.`)); } else if (isCollection(type)) { (0, assert_1.assert)(isPrimitive(objectType) || isUserDefined(objectType), propError(info, `A ${type} must contain only primitive or user-defined types specified through 'objectType'.`)); } else if (type === "object") { (0, assert_1.assert)(isUserDefined(objectType), propError(info, "A user-defined type must be specified through 'objectType'.")); } else if (type === "linkingObjects") { (0, assert_1.assert)(isUserDefined(objectType), propError(info, "A user-defined type must be specified through 'objectType'.")); (0, assert_1.assert)(!!property, propError(info, "The linking object's property name must be specified through 'property'.")); } else { // 'type' is a user-defined type throw new errors_1.PropertySchemaParseError(`If you meant to define a relationship, use { type: 'object', objectType: '${type}' } or { type: 'linkingObjects', objectType: '${type}', property: 'The ${type} property' }`, info); } if (type !== "linkingObjects") { (0, assert_1.assert)(property === undefined, propError(info, "'property' can only be specified if 'type' is 'linkingObjects'.")); } switch (presentation) { case "counter": (0, assert_1.assert)(type === "int", propError(info, "Counters can only be used when 'type' is 'int'.")); break; default: break; } if (isAlwaysOptional(type, objectType)) { const displayed = type === "mixed" || objectType === "mixed" ? "'mixed' types" : "User-defined types as standalone objects and in dictionaries"; (0, assert_1.assert)(optional !== false, propError(info, `${displayed} are always optional and cannot be made non-optional.`)); optional = true; } else if (isNeverOptional(type, objectType)) { (0, assert_1.assert)(optional !== true, propError(info, "User-defined types in lists and sets are always non-optional and cannot be made optional.")); optional = false; } if (info.isPrimaryKey) { (0, assert_1.assert)(indexed !== false, propError(info, "Primary keys must always be indexed.")); (0, assert_1.assert)(indexed !== "full-text", propError(info, "Primary keys cannot be full-text indexed.")); (0, assert_1.assert)(presentation !== "counter", propError(info, "Counters cannot be primary keys.")); indexed = true; } const normalizedSchema = { name: info.propertyName, type: type, optional: !!optional, indexed: indexed !== undefined ? indexed : false, mapTo: propertySchema.mapTo || info.propertyName, }; // Add optional properties only if defined (tests expect no 'undefined' properties) if (objectType !== undefined) normalizedSchema.objectType = objectType; if (presentation !== undefined) normalizedSchema.presentation = presentation; if (property !== undefined) normalizedSchema.property = property; if (defaultValue !== undefined) normalizedSchema.default = defaultValue; return normalizedSchema; } /** * Determine whether a property always is implicitly optional (nullable). */ function isAlwaysOptional(type, objectType) { return (type === "mixed" || objectType === "mixed" || type === "object" || (type === "dictionary" && isUserDefined(objectType))); } /** * Determine whether a property always is implicitly non-optional (non-nullable). */ function isNeverOptional(type, objectType) { return (type === "list" || type === "set" || type === "linkingObjects") && isUserDefined(objectType); } /** * Determine whether a string ends with a shorthand collection ('[]' or '{}' or '<>'). */ function hasCollectionSuffix(input) { const suffix = input.substring(input.length - COLLECTION_SUFFIX_LENGTH); return !!COLLECTION_SHORTHAND_TO_NAME[suffix]; } /** * Assert that shorthand notation is not being used. */ function assertNotUsingShorthand(input, info) { if (!input) { return; } const shorthands = extractShorthands(input); let message = `Cannot use shorthand '${shorthands.all.join("' and '")}' in 'type' ` + "or 'objectType' when defining property objects."; if (shorthands.presentationType) { message += ` To use presentation types such as '${shorthands.presentationType}', use the field 'presentation'.`; } (0, assert_1.assert)(shorthands.all.length === 0, propError(info, message)); } /** * Extract the shorthand markers used in the input. */ function extractShorthands(input) { const shorthands = { all: [] }; if (hasCollectionSuffix(input)) { shorthands.all.push(input.substring(input.length - COLLECTION_SUFFIX_LENGTH)); input = input.substring(0, input.length - COLLECTION_SUFFIX_LENGTH); } if (input.endsWith(OPTIONAL_MARKER)) { shorthands.all.push(OPTIONAL_MARKER); input = input.substring(0, input.length - OPTIONAL_MARKER.length); } if (isPresentationType(input)) { shorthands.all.push(input); shorthands.presentationType = input; } return shorthands; } /** * Generate an error caused by an invalid property schema. * (Returning a function rather than the Error itself in order * for the Error to only be created if needed.) */ function propError(info, message) { return () => new errors_1.PropertySchemaParseError(message, info); } /** * Generate an error caused by an invalid object schema. */ function objectError(objectName, message) { return () => new errors_1.ObjectSchemaParseError(message, { objectName }); } /** * Extract the base type and the type argument from a generic string notation. */ function extractGeneric(type) { const bracketStart = type.indexOf("<"); const bracketEnd = type.indexOf(">", bracketStart); if (bracketStart === -1) { return { typeBase: type }; } return { typeBase: type.substring(0, bracketStart), typeArgument: type.substring(bracketStart + 1, bracketEnd) }; } exports.extractGeneric = extractGeneric; //# sourceMappingURL=normalize.js.map