realm
Version:
Realm by MongoDB is an offline-first mobile database: an alternative to SQLite and key-value stores
363 lines • 14.5 kB
JavaScript
;
////////////////////////////////////////////////////////////////////////////
//
// 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