UNPKG

realm

Version:

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

363 lines 14.5 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.getTypeHelpers = exports.mixedToBinding = exports.toArrayBuffer = void 0; const internal_1 = require("./internal"); const TYPED_ARRAY_CONSTRUCTORS = new Set([ DataView, Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, // These will not be present on old versions of JSC without BigInt support. internal_1.safeGlobalThis.BigInt64Array, internal_1.safeGlobalThis.BigUint64Array, ].filter((ctor) => ctor !== undefined)); function toArrayBuffer(value, stringToBase64 = true) { if (typeof value === "string" && stringToBase64) { return internal_1.binding.Helpers.base64Decode(value); } for (const TypedArray of TYPED_ARRAY_CONSTRUCTORS) { if (value instanceof TypedArray) { return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength); } } internal_1.assert.instanceOf(value, ArrayBuffer); return value; } exports.toArrayBuffer = toArrayBuffer; // TODO: Consider testing for expected object instance types and throw something similar to the legacy SDK: // "Only Realm instances are supported." (which should probably have been "RealmObject") // instead of relying on the binding to throw. /** @internal */ function mixedToBinding(realm, value) { if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) { // Fast track pass through for the most commonly used types return value; } else if (value === undefined) { return null; } else if (value instanceof Date) { return internal_1.binding.Timestamp.fromDate(value); } else if (value instanceof internal_1.RealmObject) { const otherRealm = value[internal_1.REALM].internal; internal_1.assert.isSameRealm(realm, otherRealm, "Realm object is from another Realm"); return value[internal_1.INTERNAL]; } else if (value instanceof internal_1.Collection) { throw new Error(`Using a ${value.constructor.name} as Mixed value, is not yet supported`); } else if (Array.isArray(value)) { throw new TypeError("A mixed property cannot contain an array of values."); } else { if (typeof value === "object" && value !== null) { if (isGeoCircle(value)) { return (0, internal_1.circleToBindingGeospatial)(value); } else if (isGeoBox(value)) { return (0, internal_1.boxToBindingGeospatial)(value); } else if (isGeoPolygon(value)) { return (0, internal_1.polygonToBindingGeospatial)(value); } } // Convert typed arrays to an `ArrayBuffer` for (const TypedArray of TYPED_ARRAY_CONSTRUCTORS) { if (value instanceof TypedArray) { return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength); } } // Rely on the binding for any other value return value; } } exports.mixedToBinding = mixedToBinding; function isGeoCircle(value) { return "distance" in value && "center" in value && typeof value["distance"] === "number"; } function isGeoBox(value) { return "bottomLeft" in value && "topRight" in value; } function isGeoPolygon(value) { return (("type" in value && value["type"] === "Polygon" && "coordinates" in value && Array.isArray(value["coordinates"])) || ("outerRing" in value && Array.isArray(value["outerRing"]))); } function defaultToBinding(value) { return value; } function defaultFromBinding(value) { return value; } /** * Adds a branch to a function, which checks for the argument to be null, in which case it returns early. */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any -- Using `unknown` here breaks type inference in `binding.PropertyType.Object` `toBinding` from for some reason */ function nullPassthrough(fn, enabled) { if (enabled) { return ((value, ...rest) => typeof value === "undefined" || value === null ? null : fn.call(this, value, ...rest)); } else { return fn; } } const TYPES_MAPPING = { [0 /* binding.PropertyType.Int */]({ optional }) { return { toBinding: nullPassthrough((value) => { if (typeof value === "number") { return internal_1.binding.Int64.numToInt(value); } else if (internal_1.binding.Int64.isInt(value)) { return value; } else { throw new internal_1.TypeAssertionError("a number or bigint", value); } }, optional), // TODO: Support returning bigints to end-users fromBinding: nullPassthrough((value) => Number(value), optional), }; }, [1 /* binding.PropertyType.Bool */]({ optional }) { return { toBinding: nullPassthrough((value) => { internal_1.assert.boolean(value); return value; }, optional), fromBinding: defaultFromBinding, }; }, [2 /* binding.PropertyType.String */]({ optional }) { return { toBinding: nullPassthrough((value) => { internal_1.assert.string(value); return value; }, optional), fromBinding: defaultFromBinding, }; }, [3 /* binding.PropertyType.Data */]({ optional }) { return { toBinding: nullPassthrough((value) => { return toArrayBuffer(value); }, optional), fromBinding: defaultFromBinding, }; }, [4 /* binding.PropertyType.Date */]({ optional }) { return { toBinding: nullPassthrough((value) => { if (typeof value === "string") { // TODO: Consider deprecating this undocumented type coercion return internal_1.binding.Timestamp.fromDate(new Date(value)); } else { internal_1.assert.instanceOf(value, Date); return internal_1.binding.Timestamp.fromDate(value); } }, optional), fromBinding: nullPassthrough((value) => { internal_1.assert.instanceOf(value, internal_1.binding.Timestamp); return value.toDate(); }, optional), }; }, [5 /* binding.PropertyType.Float */]({ optional }) { return { toBinding: nullPassthrough((value) => { internal_1.assert.number(value); return new internal_1.binding.Float(value); }, optional), fromBinding: nullPassthrough((value) => { internal_1.assert.instanceOf(value, internal_1.binding.Float); return value.value; }, optional), }; }, [6 /* binding.PropertyType.Double */]({ optional }) { return { toBinding: nullPassthrough((value) => { internal_1.assert.number(value); return value; }, optional), fromBinding: defaultFromBinding, }; }, [7 /* binding.PropertyType.Object */]({ realm, name, objectType, optional, getClassHelpers }) { (0, internal_1.assert)(objectType); const helpers = getClassHelpers(objectType); const { wrapObject } = helpers; return { toBinding: nullPassthrough((value, options) => { if (value instanceof internal_1.RealmObject && value.constructor.name === objectType && value[internal_1.REALM].internal.$addr === realm.internal.$addr) { return value[internal_1.INTERNAL]; } else { // TODO: Consider exposing a way for calling code to disable object creation internal_1.assert.object(value, name); // Use the update mode if set; otherwise, the object is assumed to be an // unmanaged object that the user wants to create. // TODO: Ideally use `options?.updateMode` instead of `realm.currentUpdateMode`. const createdObject = internal_1.RealmObject.create(realm, value, realm.currentUpdateMode ?? internal_1.UpdateMode.Never, { helpers, createObj: options?.createObj, }); return createdObject[internal_1.INTERNAL]; } }, optional), fromBinding: nullPassthrough((value) => { if (value instanceof internal_1.binding.ObjLink) { const table = internal_1.binding.Helpers.getTable(realm.internal, value.tableKey); const linkedObj = table.getObject(value.objKey); return wrapObject(linkedObj); } else { internal_1.assert.instanceOf(value, internal_1.binding.Obj); return wrapObject(value); } }, optional), }; }, [8 /* binding.PropertyType.LinkingObjects */]({ objectType, getClassHelpers }) { (0, internal_1.assert)(objectType); const { wrapObject } = getClassHelpers(objectType); return { toBinding: defaultToBinding, fromBinding(value) { internal_1.assert.instanceOf(value, internal_1.binding.Obj); return wrapObject(value); }, }; }, [9 /* binding.PropertyType.Mixed */]({ realm, getClassHelpers }) { return { toBinding: mixedToBinding.bind(null, realm.internal), fromBinding(value) { if (internal_1.binding.Int64.isInt(value)) { return internal_1.binding.Int64.intToNum(value); } else if (value instanceof internal_1.binding.Timestamp) { return value.toDate(); } else if (value instanceof internal_1.binding.Float) { return value.value; } else if (value instanceof internal_1.binding.ObjLink) { const table = internal_1.binding.Helpers.getTable(realm.internal, value.tableKey); const linkedObj = table.getObject(value.objKey); const { wrapObject } = getClassHelpers(value.tableKey); return wrapObject(linkedObj); } else { return value; } }, }; }, [10 /* binding.PropertyType.ObjectId */]({ optional }) { return { toBinding: nullPassthrough((value) => { internal_1.assert.instanceOf(value, internal_1.BSON.ObjectId); return value; }, optional), fromBinding: defaultFromBinding, }; }, [11 /* binding.PropertyType.Decimal */]({ optional }) { return { toBinding: nullPassthrough((value) => { internal_1.assert.instanceOf(value, internal_1.BSON.Decimal128); return value; }, optional), fromBinding: defaultFromBinding, }; }, [12 /* binding.PropertyType.Uuid */]({ optional }) { return { toBinding: nullPassthrough((value) => { internal_1.assert.instanceOf(value, internal_1.BSON.UUID); return value; }, optional), fromBinding: defaultFromBinding, }; }, [128 /* binding.PropertyType.Array */]({ realm, getClassHelpers, name, objectSchemaName }) { internal_1.assert.string(objectSchemaName, "objectSchemaName"); const classHelpers = getClassHelpers(objectSchemaName); return { fromBinding(value) { internal_1.assert.instanceOf(value, internal_1.binding.List); const propertyHelpers = classHelpers.properties.get(name); const collectionHelpers = propertyHelpers.collectionHelpers; internal_1.assert.object(collectionHelpers); return new internal_1.List(realm, value, collectionHelpers); }, toBinding() { throw new Error("Not supported"); }, }; }, [256 /* binding.PropertyType.Set */]() { return { fromBinding() { throw new Error("Not yet supported"); }, toBinding() { throw new Error("Not yet supported"); }, }; }, [512 /* binding.PropertyType.Dictionary */]() { return { fromBinding() { throw new Error("Not supported"); }, toBinding() { throw new Error("Not supported"); }, }; }, [64 /* binding.PropertyType.Nullable */]() { throw new Error("Not directly mappable"); }, [896 /* binding.PropertyType.Collection */]() { throw new Error("Not directly mappable"); }, [960 /* binding.PropertyType.Flags */]() { throw new Error("Not directly mappable"); }, }; /** @internal */ function getTypeHelpers(type, options) { const helpers = TYPES_MAPPING[type]; (0, internal_1.assert)(helpers, `Unexpected type ${type}`); return helpers(options); } exports.getTypeHelpers = getTypeHelpers; //# sourceMappingURL=TypeHelpers.js.map