realm
Version:
Realm by MongoDB is an offline-first mobile database: an alternative to SQLite and key-value stores
296 lines • 13 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.createPropertyHelpers = void 0;
const internal_1 = require("./internal");
function getObj(results, index) {
return results.getObj(index);
}
function getAny(results, index) {
return results.getAny(index);
}
const defaultGet = ({ typeHelpers: { fromBinding }, columnKey }) => (obj) => {
try {
return fromBinding(obj.getAny(columnKey));
}
catch (err) {
internal_1.assert.isValid(obj);
throw err;
}
};
const defaultSet = ({ realm, typeHelpers: { toBinding }, columnKey }) => (obj, value) => {
internal_1.assert.inTransaction(realm);
try {
if (!realm.isInMigration && obj.table.getPrimaryKeyColumn() === columnKey) {
throw new Error(`Cannot change value of primary key outside migration function`);
}
obj.setAny(columnKey, toBinding(value));
}
catch (err) {
internal_1.assert.isValid(obj);
throw err;
}
};
function embeddedSet({ typeHelpers: { toBinding }, columnKey }) {
return (obj, value) => {
// Asking for the toBinding will create the object and link it to the parent in one operation.
// Thus, no need to actually set the value on the `obj` unless it's an optional null value.
const bindingValue = toBinding(value, { createObj: () => [obj.createAndSetLinkedObject(columnKey), true] });
// No need to destructure `optional` and check that it's `true` in this condition before setting
// it to null as objects are always optional. The condition is placed after the invocation of
// `toBinding()` in order to leave the type conversion responsibility to `toBinding()`.
if (bindingValue === null) {
obj.setAny(columnKey, bindingValue);
}
};
}
const ACCESSOR_FACTORIES = {
[7 /* binding.PropertyType.Object */](options) {
const { columnKey, typeHelpers: { fromBinding }, embedded, } = options;
(0, internal_1.assert)(options.optional, "Objects are always nullable");
return {
get(obj) {
return fromBinding(obj.getLinkedObject(columnKey));
},
set: embedded ? embeddedSet(options) : defaultSet(options),
};
},
[8 /* binding.PropertyType.LinkingObjects */]() {
return {
get() {
throw new Error("Getting linking objects happens through Array");
},
set() {
throw new Error("Setting linking objects happens through Array");
},
};
},
[128 /* binding.PropertyType.Array */]({ realm, type, name, columnKey, objectType, embedded, linkOriginPropertyName, getClassHelpers, optional, typeHelpers: { fromBinding }, }) {
const realmInternal = realm.internal;
const itemType = type & ~960 /* binding.PropertyType.Flags */;
const itemHelpers = (0, internal_1.getTypeHelpers)(itemType, {
realm,
name: `element of ${name}`,
optional,
getClassHelpers,
objectType,
objectSchemaName: undefined,
});
// Properties of items are only available on lists of objects
const isObjectItem = itemType === 7 /* binding.PropertyType.Object */ || itemType === 8 /* binding.PropertyType.LinkingObjects */;
const collectionHelpers = {
...itemHelpers,
get: isObjectItem ? getObj : getAny,
};
if (itemType === 8 /* binding.PropertyType.LinkingObjects */) {
// Locate the table of the targeted object
internal_1.assert.string(objectType, "object type");
(0, internal_1.assert)(objectType !== "", "Expected a non-empty string");
const targetClassHelpers = getClassHelpers(objectType);
const { objectSchema: { tableKey, persistedProperties }, } = targetClassHelpers;
// TODO: Check if we want to match with the `p.name` or `p.publicName` here
const targetProperty = persistedProperties.find((p) => p.name === linkOriginPropertyName);
(0, internal_1.assert)(targetProperty, `Expected a '${linkOriginPropertyName}' property on ${objectType}`);
const tableRef = internal_1.binding.Helpers.getTable(realmInternal, tableKey);
return {
get(obj) {
const tableView = obj.getBacklinkView(tableRef, targetProperty.columnKey);
const results = internal_1.binding.Results.fromTableView(realmInternal, tableView);
return new internal_1.Results(realm, results, collectionHelpers);
},
set() {
throw new Error("Not supported");
},
};
}
else {
const { toBinding: itemToBinding } = itemHelpers;
return {
collectionHelpers,
get(obj) {
const internal = internal_1.binding.List.make(realm.internal, obj, columnKey);
internal_1.assert.instanceOf(internal, internal_1.binding.List);
return fromBinding(internal);
},
set(obj, values) {
internal_1.assert.inTransaction(realm);
// Implements https://github.com/realm/realm-core/blob/v12.0.0/src/realm/object-store/list.hpp#L258-L286
internal_1.assert.iterable(values);
const bindingValues = [];
const internal = internal_1.binding.List.make(realm.internal, obj, columnKey);
// In case of embedded objects, they're added as they're transformed
// So we need to ensure an empty list before
if (embedded) {
internal.removeAll();
}
// Transform all values to mixed before inserting into the list
{
let index = 0;
for (const value of values) {
try {
if (embedded) {
itemToBinding(value, { createObj: () => [internal.insertEmbedded(index), true] });
}
else {
bindingValues.push(itemToBinding(value));
}
}
catch (err) {
if (err instanceof internal_1.TypeAssertionError) {
err.rename(`${name}[${index}]`);
}
throw err;
}
index++;
}
}
// Move values into the internal list - embedded objects are added as they're transformed
if (!embedded) {
internal.removeAll();
let index = 0;
for (const value of bindingValues) {
internal.insertAny(index++, value);
}
}
},
};
}
},
[512 /* binding.PropertyType.Dictionary */]({ columnKey, realm, name, type, optional, objectType, getClassHelpers, embedded }) {
const itemType = type & ~960 /* binding.PropertyType.Flags */;
const itemHelpers = (0, internal_1.getTypeHelpers)(itemType, {
realm,
name: `value in ${name}`,
getClassHelpers,
objectType,
optional,
objectSchemaName: undefined,
});
return {
get(obj) {
const internal = internal_1.binding.Dictionary.make(realm.internal, obj, columnKey);
return new internal_1.Dictionary(realm, internal, itemHelpers);
},
set(obj, value) {
const internal = internal_1.binding.Dictionary.make(realm.internal, obj, columnKey);
// Clear the dictionary before adding new values
internal.removeAll();
internal_1.assert.object(value, `values of ${name}`);
for (const [k, v] of Object.entries(value)) {
try {
if (embedded) {
itemHelpers.toBinding(v, { createObj: () => [internal.insertEmbedded(k), true] });
}
else {
internal.insertAny(k, itemHelpers.toBinding(v));
}
}
catch (err) {
if (err instanceof internal_1.TypeAssertionError) {
err.rename(`${name}["${k}"]`);
}
throw err;
}
}
},
};
},
[256 /* binding.PropertyType.Set */]({ columnKey, realm, name, type, optional, objectType, getClassHelpers }) {
const itemType = type & ~960 /* binding.PropertyType.Flags */;
const itemHelpers = (0, internal_1.getTypeHelpers)(itemType, {
realm,
name: `value in ${name}`,
getClassHelpers,
objectType,
optional,
objectSchemaName: undefined,
});
internal_1.assert.string(objectType);
const collectionHelpers = {
get: itemType === 7 /* binding.PropertyType.Object */ ? getObj : getAny,
fromBinding: itemHelpers.fromBinding,
toBinding: itemHelpers.toBinding,
};
return {
get(obj) {
const internal = internal_1.binding.Set.make(realm.internal, obj, columnKey);
return new internal_1.RealmSet(realm, internal, collectionHelpers);
},
set(obj, value) {
const internal = internal_1.binding.Set.make(realm.internal, obj, columnKey);
// Clear the set before adding new values
internal.removeAll();
internal_1.assert.array(value, "values");
for (const v of value) {
internal.insertAny(itemHelpers.toBinding(v));
}
},
};
},
};
function getPropertyHelpers(type, options) {
const { typeHelpers, columnKey, embedded, objectType } = options;
const accessorFactory = ACCESSOR_FACTORIES[type];
if (accessorFactory) {
const accessors = accessorFactory(options);
return { ...accessors, ...typeHelpers, type: options.type, columnKey, embedded, objectType };
}
else {
return {
get: defaultGet(options),
set: defaultSet(options),
...typeHelpers,
type: options.type,
columnKey,
embedded,
objectType,
};
}
}
/** @internal */
function createPropertyHelpers(property, options) {
const collectionType = property.type & 896 /* binding.PropertyType.Collection */;
const typeOptions = {
realm: options.realm,
name: property.publicName || property.name,
getClassHelpers: options.getClassHelpers,
objectType: property.objectType,
objectSchemaName: property.objectSchemaName,
optional: !!(property.type & 64 /* binding.PropertyType.Nullable */),
};
if (collectionType) {
return getPropertyHelpers(collectionType, {
...property,
...options,
...typeOptions,
typeHelpers: (0, internal_1.getTypeHelpers)(collectionType, typeOptions),
});
}
else {
const baseType = property.type & ~960 /* binding.PropertyType.Flags */;
return getPropertyHelpers(baseType, {
...property,
...options,
...typeOptions,
typeHelpers: (0, internal_1.getTypeHelpers)(baseType, typeOptions),
});
}
}
exports.createPropertyHelpers = createPropertyHelpers;
//# sourceMappingURL=PropertyHelpers.js.map