From 234562388b79aa86490a576375415b924a882308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 30 Mar 2022 16:28:08 +0200 Subject: [PATCH] Class models: `Realm.Object#constructor` (#4427) * Adding a cast to Value operator on ReturnType * Adding integration tests of class constructors * Made the test reporter print stack * Implemented Realm.Object constructor * Added node implementation and fixed legacy tests * Read from this.constructor instead of new.target * Fixing JSC implementation * Updating types and tests * Fixed Person and Dog constructors * Updated @realm/react useObject + useQuery * Updated types to fix an issue * Dead code removal * Updated tests to use default values * Making the insertion types a bit more loose * Adding documentation * Renamed realm_object_object * Made the constructor "values" required * Renamed "RealmInsertionModel" to "Unmanged" * Adding a note to the changelog * Apply suggestions to docstrings Co-authored-by: Kenneth Geisshirt * Add docstring of set_internal and get_internal * Expect 2 arguments on the C++ code as well Co-authored-by: Kenneth Geisshirt --- CHANGELOG.md | 29 +++++++-- .../tests/src/schemas/person-and-dogs.ts | 25 +++---- .../tests/src/tests/class-models.ts | 48 +++++++++++++- packages/realm-react/src/useObject.tsx | 5 +- packages/realm-react/src/useQuery.tsx | 4 +- src/js_realm_object.hpp | 42 +++++++++++- src/js_types.hpp | 27 +++++++- src/node/node_class.hpp | 61 ++++-------------- src/node/node_object.hpp | 3 +- src/node/node_return_value.hpp | 7 +- tests/.lock | Bin 0 -> 1184 bytes tests/js/list-tests.js | 28 ++------ tests/js/realm-tests.js | 24 ++++--- tests/spec/helpers/reporters.js | 3 + types/index.d.ts | 23 ++++--- 15 files changed, 209 insertions(+), 120 deletions(-) create mode 100644 tests/.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 94d4085c350..c4e8be502d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,31 @@ x.x.x Release notes (yyyy-MM-dd) ============================================================= ### Breaking change -* Model classes passed as schema to the `Realm` constructor must now extend `Realm.Object`. - -### Enhancements -* None. +* Model classes passed as schema to the `Realm` constructor must now extend `Realm.Object` and will no longer have their constructors called when pulling an object of that type from the database. Existing classes already extending `Realm.Object` now need to call the `super` constructor passing two arguments: + - `realm`: The Realm to create the object in. + - `values`: Values to pass to the `realm.create` call when creating the object in the database. +* Renamed the `RealmInsertionModel` type to `Unmanaged` to simplify and highlight its usage. + +### Enhancements +* Class-based models (i.e. user defined classes extending `Realm.Object` and passed through the `schema` when opening a Realm), will now create object when their constructor is called: + +```ts +class Person extends Realm.Object { + name!: string; + + static schema = { + name: "Person", + properties: { name: "string" }, + }; +} + +const realm = new Realm({ schema: [Person] }); +realm.write(() => { + const alice = new Person(realm, { name: "Alice" }); + // A Person { name: "Alice" } is now persisted in the database + console.log("Hello " + alice.name); +}); +``` ### Fixed * ([#????](https://github.com/realm/realm-js/issues/????), since v?.?.?) diff --git a/integration-tests/tests/src/schemas/person-and-dogs.ts b/integration-tests/tests/src/schemas/person-and-dogs.ts index 9b9e21a10b8..8a6bfcdc88c 100644 --- a/integration-tests/tests/src/schemas/person-and-dogs.ts +++ b/integration-tests/tests/src/schemas/person-and-dogs.ts @@ -38,16 +38,13 @@ export const PersonSchema: Realm.ObjectSchema = { }; export class Person extends Realm.Object { - name: string; - age: number; + name!: string; + age!: number; friends!: Realm.List; dogs!: Realm.Collection; - constructor(name: string, age: number) { - super(); - - this.name = name; - this.age = age; + constructor(realm: Realm, name: string, age: number) { + super(realm, { name, age }); } static schema: Realm.ObjectSchema = PersonSchema; @@ -69,16 +66,12 @@ export const DogSchema: Realm.ObjectSchema = { }; export class Dog extends Realm.Object { - name: string; - age: number; - owner: Person; - - constructor(name: string, age: number, owner: Person) { - super(); + name!: string; + age!: number; + owner!: Person; - this.name = name; - this.age = age; - this.owner = owner; + constructor(realm: Realm, name: string, age: number, owner: Person) { + super(realm, { name, age, owner }); } static schema: Realm.ObjectSchema = DogSchema; diff --git a/integration-tests/tests/src/tests/class-models.ts b/integration-tests/tests/src/tests/class-models.ts index 8bf6797fc6b..9c94d05cdb6 100644 --- a/integration-tests/tests/src/tests/class-models.ts +++ b/integration-tests/tests/src/tests/class-models.ts @@ -17,9 +17,10 @@ //////////////////////////////////////////////////////////////////////////// import { expect } from "chai"; - import Realm from "realm"; +import { openRealmBeforeEach } from "../hooks"; + describe("Class models", () => { describe("as schema element", () => { beforeEach(() => { @@ -72,4 +73,49 @@ describe("Class models", () => { new Realm({ schema: [Person] }); }); }); + + describe("#constructor", () => { + type UnmanagedPerson = Partial & Pick; + class Person extends Realm.Object { + id!: Realm.BSON.ObjectId; + name!: string; + age!: number; + friends!: Realm.List; + + static schema: Realm.ObjectSchema = { + name: "Person", + properties: { + id: { + type: "objectId", + default: new Realm.BSON.ObjectId(), // TODO: Make this a function + }, + name: "string", + age: { + type: "int", + default: 32, + }, + friends: "Person[]", + }, + }; + } + + openRealmBeforeEach({ schema: [Person] }); + + it("creates objects with values", function (this: RealmContext) { + this.realm.write(() => { + // Expect no persons in the database + const persons = this.realm.objects("Person"); + expect(persons.length).equals(0); + + const alice = new Person(this.realm, { name: "Alice" }); + expect(alice.name).equals("Alice"); + // Expect the first element to be the object we just added + expect(persons.length).equals(1); + expect(persons[0]._objectId()).equals(alice._objectId()); + expect(persons[0].name).equals("Alice"); + // Property value fallback to the default + expect(persons[0].age).equals(32); + }); + }); + }); }); diff --git a/packages/realm-react/src/useObject.tsx b/packages/realm-react/src/useObject.tsx index d058d7aadc9..c5852bf996b 100644 --- a/packages/realm-react/src/useObject.tsx +++ b/packages/realm-react/src/useObject.tsx @@ -45,7 +45,10 @@ export function createUseObject(useRealm: () => Realm) { * @param primaryKey - The primary key of the desired object which will be retrieved using {@link Realm.objectForPrimaryKey} * @returns either the desired {@link Realm.Object} or `null` in the case of it being deleted or not existing. */ - return function useObject(type: string | { new (): T }, primaryKey: PrimaryKey): (T & Realm.Object) | null { + return function useObject( + type: string | { new (...args: any): T }, + primaryKey: PrimaryKey, + ): (T & Realm.Object) | null { const realm = useRealm(); // Create a forceRerender function for the cachedObject to use as its updateCallback, so that diff --git a/packages/realm-react/src/useQuery.tsx b/packages/realm-react/src/useQuery.tsx index a9b547b562e..2c159509181 100644 --- a/packages/realm-react/src/useQuery.tsx +++ b/packages/realm-react/src/useQuery.tsx @@ -47,7 +47,9 @@ export function createUseQuery(useRealm: () => Realm) { * @param type - The object type, depicted by a string or a class extending Realm.Object * @returns a collection of realm objects or an empty array */ - return function useQuery(type: string | ({ new (): T } & Realm.ObjectClass)): Realm.Results { + return function useQuery( + type: string | ({ new (...args: any): T } & Realm.ObjectClass), + ): Realm.Results { const realm = useRealm(); // Create a forceRerender function for the cachedCollection to use as its updateCallback, so that diff --git a/src/js_realm_object.hpp b/src/js_realm_object.hpp index 8d58c35b06f..b003bcca54b 100644 --- a/src/js_realm_object.hpp +++ b/src/js_realm_object.hpp @@ -61,6 +61,7 @@ struct RealmObjectClass : ClassDefinition> { using FunctionType = typename T::Function; using ObjectType = typename T::Object; using ValueType = typename T::Value; + using Context = js::Context; using String = js::String; using Value = js::Value; using Object = js::Object; @@ -71,6 +72,7 @@ struct RealmObjectClass : ClassDefinition> { static ObjectType create_instance(ContextType, realm::js::RealmObject); + static void constructor(ContextType, ObjectType, Arguments&); static void get_property(ContextType, ObjectType, const String&, ReturnValue&); static bool set_property(ContextType, ObjectType, const String&, ValueType); static std::vector get_property_names(ContextType, ObjectType); @@ -163,6 +165,44 @@ typename T::Object RealmObjectClass::create_instance(ContextType ctx, realm:: } } +/** + * @brief Implements the constructor for a Realm.Object, calling the `Realm#create` instance method to create an + * object in the database. + * + * @note This differs from `RealmObjectClass::create_instance` as it is executed when end-users construct a `new + * Realm.Object()` (or another user-defined class extending `Realm.Object`), whereas `create_instance` is called when + * reading objects from the database. + * + * @tparam T Engine specific types. + * @param ctx JS context. + * @param this_object JS object being returned to the user once constructed. + * @param args Arguments passed by the user when calling the constructor. + */ +template +void RealmObjectClass::constructor(ContextType ctx, ObjectType this_object, Arguments& args) +{ + // Parse aguments + args.validate_count(2); + auto constructor = Object::validated_get_object(ctx, this_object, "constructor"); + auto realm = Value::validated_to_object(ctx, args[0], "realm"); + auto values = Value::validated_to_object(ctx, args[1], "values"); + + // Create an object + std::vector create_args{constructor, values}; + Arguments create_arguments{ctx, create_args.size(), create_args.data()}; + ReturnValue result{ctx}; + RealmClass::create(ctx, realm, create_arguments, result); + ObjectType tmp_realm_object = Value::validated_to_object(ctx, result); + + // Copy the internal from the constructed object onto this_object + auto realm_object = get_internal>(ctx, tmp_realm_object); + // The finalizer on the ObjectWrap (applied inside of set_internal) will delete the `new_realm_object` which is + // why we create a new instance to avoid a double free (the first of which will happen when the `tmp_realm_object` + // destructs). + auto new_realm_object = new realm::js::RealmObject(*realm_object); + set_internal>(ctx, this_object, new_realm_object); +} + template void RealmObjectClass::get_property(ContextType ctx, ObjectType object, const String& property_name, ReturnValue& return_value) @@ -373,7 +413,7 @@ void RealmObjectClass::add_listener(ContextType ctx, ObjectType this_object, auto callback = Value::validated_to_function(ctx, args[0]); Protected protected_callback(ctx, callback); Protected protected_this(ctx, this_object); - Protected protected_ctx(Context::get_global_context(ctx)); + Protected protected_ctx(Context::get_global_context(ctx)); auto token = realm_object->add_notification_callback([=](CollectionChangeSet const& change_set, std::exception_ptr exception) { diff --git a/src/js_types.hpp b/src/js_types.hpp index 4901c4bf8a7..9fc6127043a 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -490,7 +490,7 @@ struct Object { static typename ClassType::Internal* get_internal(ContextType ctx, const ObjectType&); template - static void set_internal(ContextType ctx, const ObjectType&, typename ClassType::Internal*); + static void set_internal(ContextType ctx, ObjectType&, typename ClassType::Internal*); static ObjectType create_from_app_error(ContextType, const app::AppError&); static ValueType create_from_optional_app_error(ContextType, const util::Optional&); @@ -562,6 +562,8 @@ struct ReturnValue { void set(uint32_t); void set_null(); void set_undefined(); + + operator ValueType() const; }; template @@ -587,14 +589,35 @@ REALM_JS_INLINE typename T::Object create_instance_by_schema(typename T::Context return Object::template create_instance_by_schema(ctx, schema, internal); } +/** + * @brief Get the internal (C++) object backing a JS object. + * + * @tparam T Engine specific types. + * @tparam ClassType Class implementing the C++ interface backing the JS accessor object (passed as `object`). + * @param ctx JS context. + * @param object JS object with an internal object. + * @return Pointer to the internal object. + */ template REALM_JS_INLINE typename ClassType::Internal* get_internal(typename T::Context ctx, const typename T::Object& object) { return Object::template get_internal(ctx, object); } +/** + * @brief Set the internal (C++) object backing the JS object. + * + * @note Calling this transfer ownership of the object pointed to by `ptr` and links it to the lifetime of to the + * `object` passed as argument. + * + * @tparam T Engine specific types. + * @tparam ClassType Class implementing the C++ interface backing the JS accessor object (passed as `object`). + * @param ctx JS context. + * @param object JS object having its internal set. + * @param ptr A pointer to an internal object. + */ template -REALM_JS_INLINE void set_internal(typename T::Context ctx, const typename T::Object& object, +REALM_JS_INLINE void set_internal(typename T::Context ctx, typename T::Object& object, typename ClassType::Internal* ptr) { Object::template set_internal(ctx, object, ptr); diff --git a/src/node/node_class.hpp b/src/node/node_class.hpp index 3f379c32bbc..8fdec3bf2bc 100644 --- a/src/node/node_class.hpp +++ b/src/node/node_class.hpp @@ -52,6 +52,7 @@ namespace node { Napi::FunctionReference ObjectGetOwnPropertyDescriptor; node::Protected ExternalSymbol; +Napi::FunctionReference ObjectCreate; Napi::FunctionReference ObjectSetPrototypeOf; Napi::FunctionReference GlobalProxy; Napi::FunctionReference FunctionBind; @@ -63,6 +64,11 @@ static void node_class_init(Napi::Env env) ObjectSetPrototypeOf = Napi::Persistent(setPrototypeOf); ObjectSetPrototypeOf.SuppressDestruct(); + auto create = env.Global().Get("Object").As().Get("create").As(); + ObjectCreate = Napi::Persistent(create); + ObjectCreate.SuppressDestruct(); + + auto getOwnPropertyDescriptor = env.Global().Get("Object").As().Get("getOwnPropertyDescriptor").As(); ObjectGetOwnPropertyDescriptor = Napi::Persistent(getOwnPropertyDescriptor); @@ -205,7 +211,7 @@ class ObjectWrap { static bool is_instance(Napi::Env env, const Napi::Object& object); static Internal* get_internal(Napi::Env env, const Napi::Object& object); - static void set_internal(Napi::Env env, const Napi::Object& object, Internal* data); + static void set_internal(Napi::Env env, Napi::Object& object, Internal* data); static Napi::Value constructor_callback(const Napi::CallbackInfo& info); static bool has_native_method(const std::string& name); @@ -1274,10 +1280,12 @@ Napi::Object ObjectWrap::create_instance_by_schema(Napi::Env env, Nap } Napi::External externalValue = Napi::External::New(env, internal, internal_finalizer); - instance = schemaObjectConstructor.New({}); + Napi::Object constructorPrototype = schemaObjectConstructor.Get("prototype").As(); + instance = ObjectCreate.Call({constructorPrototype}).As(); instance.Set(externalSymbol, externalValue); } else { + Napi::Object constructorPrototype = constructor.Get("prototype").As(); // creating a RealmObject with user defined constructor bool schemaExists = schemaObjects->count(schemaName); @@ -1297,8 +1305,7 @@ Napi::Object ObjectWrap::create_instance_by_schema(Napi::Env env, Nap if (schemaExists) { schemaObjectType = schemaObjects->at(schemaName); schemaObjectConstructor = schemaObjectType->constructor.Value(); - - instance = schemaObjectConstructor.New({}); + instance = ObjectCreate.Call({constructorPrototype}).As(); Napi::External externalValue = Napi::External::New(env, internal, internal_finalizer); instance.Set(externalSymbol, externalValue); @@ -1307,52 +1314,18 @@ Napi::Object ObjectWrap::create_instance_by_schema(Napi::Env env, Nap } schemaObjectConstructor = constructor; - Napi::Object constructorPrototype = constructor.Get("prototype").As(); // get all properties from the schema std::vector properties = create_napi_property_descriptors(env, constructorPrototype, schema, false /*redefine*/); - Napi::Function realmObjectClassConstructor = ObjectWrap::create_constructor(env); - bool isInstanceOfRealmObjectClass = constructorPrototype.InstanceOf(realmObjectClassConstructor); - - // Skip if the user defined constructor inherited the RealmObjectClass. All RealmObjectClass members are - // available already. - if (!isInstanceOfRealmObjectClass) { - // setup all RealmObjectClass methods to the prototype of the object - for (auto& pair : s_class.methods) { - // don't redefine if exists - if (!constructorPrototype.HasOwnProperty(pair.first)) { - auto descriptor = Napi::PropertyDescriptor::Function( - env, constructorPrototype, Napi::String::New(env, pair.first) /*name*/, &method_callback, - napi_default | realm::js::PropertyAttributes::DontEnum, (void*)pair.second /*callback*/); - properties.push_back(descriptor); - } - } - - for (auto& pair : s_class.properties) { - // don't redefine if exists - if (!constructorPrototype.HasOwnProperty(pair.first)) { - napi_property_attributes napi_attributes = - napi_default | - (realm::js::PropertyAttributes::DontEnum | realm::js::PropertyAttributes::DontDelete); - auto descriptor = Napi::PropertyDescriptor::Accessor( - Napi::String::New(env, pair.first) /*name*/, napi_attributes, - (void*)&pair.second /*callback*/); - properties.push_back(descriptor); - } - } - } - // define the properties on the prototype of the schema object constructor if (properties.size() > 0) { constructorPrototype.DefineProperties(properties); } - instance = schemaObjectConstructor.New({}); - if (!instance.InstanceOf(schemaObjectConstructor)) { - throw Napi::Error::New(env, "Realm object constructor must not return another value"); - } + instance = ObjectCreate.Call({constructorPrototype}).As(); + Napi::External externalValue = Napi::External::New(env, internal, internal_finalizer); instance.Set(externalSymbol, externalValue); @@ -1411,8 +1384,7 @@ typename ClassType::Internal* ObjectWrap::get_internal(Napi::Env env, } template -void ObjectWrap::set_internal(Napi::Env env, const Napi::Object& object, - typename ClassType::Internal* internal) +void ObjectWrap::set_internal(Napi::Env env, Napi::Object& object, typename ClassType::Internal* internal) { bool isRealmObjectClass = std::is_same>::value; if (isRealmObjectClass) { @@ -1439,11 +1411,6 @@ Napi::Value ObjectWrap::constructor_callback(const Napi::CallbackInfo return scope.Escape(env.Null()); // return a value to comply with Napi::FunctionCallback } else { - bool isRealmObjectClass = std::is_same>::value; - if (isRealmObjectClass) { - return scope.Escape(env.Null()); // return a value to comply with Napi::FunctionCallback - } - throw Napi::Error::New(env, "Illegal constructor"); } } diff --git a/src/node/node_object.hpp b/src/node/node_object.hpp index 0b2a9169341..adca80842ef 100644 --- a/src/node/node_object.hpp +++ b/src/node/node_object.hpp @@ -224,8 +224,7 @@ inline typename ClassType::Internal* node::Object::get_internal(Napi::Env env, c template <> template -inline void node::Object::set_internal(Napi::Env env, const Napi::Object& object, - typename ClassType::Internal* internal) +inline void node::Object::set_internal(Napi::Env env, Napi::Object& object, typename ClassType::Internal* internal) { return node::ObjectWrap::set_internal(env, object, internal); } diff --git a/src/node/node_return_value.hpp b/src/node/node_return_value.hpp index 2a143573c27..3b0ce263023 100644 --- a/src/node/node_return_value.hpp +++ b/src/node/node_return_value.hpp @@ -41,7 +41,7 @@ class ReturnValue { { } - Napi::Value ToValue() + Napi::Value ToValue() const { // guard check. env.Empty() values cause node to fail in obscure places, so return undefined instead if (m_value.IsEmpty()) { @@ -117,6 +117,11 @@ class ReturnValue { set_undefined(); } } + + operator Napi::Value() const + { + return ToValue(); + } }; } // namespace js diff --git a/tests/.lock b/tests/.lock new file mode 100644 index 0000000000000000000000000000000000000000..4e04ca752400ee17d0ae4133be2a49a84d9d719e GIT binary patch literal 1184 zcmd7Qw+_G{3`J2Oz4u=K|0@GqgcR-?Z_j73Y)e@Sp;Gaex~)N^mEY0Wmz%x<_rQJd z06YYbz+> { const list = realm.create(TodoList, { - id: new ObjectId(), name: "MyTodoList", }); - TestCase.assertThrowsContaining(() => { - list.items.push(new TodoItem("Fix that bug")); - }, "Cannot reference a detached instance of Realm.Object"); - - TestCase.assertThrowsContaining(() => { - realm.create(TodoItem, new TodoItem("Fix that bug")); - }, "Cannot create an object from a detached Realm.Object instance"); + list.items.push(new TodoItem(realm, "Fix that bug")); + realm.create(TodoItem, new TodoItem(realm, "Fix that bug")); }); }, }; diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index d19563e8c07..cad010f380f 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -88,8 +88,8 @@ module.exports = { let constructorCalled = false; //test class syntax support class Car extends Realm.Object { - constructor() { - super(); + constructor(realm) { + super(realm); constructorCalled = true; } } @@ -128,7 +128,7 @@ module.exports = { let realm = new Realm({ schema: [Car, Car2] }); realm.write(() => { let car = realm.create("Car", { make: "Audi", model: "A4", kilometers: 24 }); - TestCase.assertTrue(constructorCalled); + TestCase.assertFalse(constructorCalled); TestCase.assertEqual(car.make, "Audi"); TestCase.assertEqual(car.model, "A4"); TestCase.assertEqual(car.kilometers, 24); @@ -144,21 +144,21 @@ module.exports = { constructorCalled = false; let car1 = realm.create("Car", { make: "VW", model: "Touareg", kilometers: 13 }); - TestCase.assertTrue(constructorCalled); + TestCase.assertFalse(constructorCalled); TestCase.assertEqual(car1.make, "VW"); TestCase.assertEqual(car1.model, "Touareg"); TestCase.assertEqual(car1.kilometers, 13); TestCase.assertInstanceOf(car1, Realm.Object, "car1 not an instance of Realm.Object"); let car2 = realm.create("Car2", { make: "Audi", model: "A4", kilometers: 24 }); - TestCase.assertTrue(calledAsConstructor); + TestCase.assertFalse(calledAsConstructor); TestCase.assertEqual(car2.make, "Audi"); TestCase.assertEqual(car2.model, "A4"); TestCase.assertEqual(car2.kilometers, 24); TestCase.assertInstanceOf(car2, Realm.Object, "car2 not an instance of Realm.Object"); let car2_1 = realm.create("Car2", { make: "VW", model: "Touareg", kilometers: 13 }); - TestCase.assertTrue(calledAsConstructor); + TestCase.assertFalse(calledAsConstructor); TestCase.assertEqual(car2_1.make, "VW"); TestCase.assertEqual(car2_1.model, "Touareg"); TestCase.assertEqual(car2_1.kilometers, 13); @@ -1172,20 +1172,18 @@ module.exports = { let object = realm.create("CustomObject", { intCol: 1 }); TestCase.assertTrue(object instanceof CustomObject); TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype); - TestCase.assertEqual(customCreated, 1); + TestCase.assertEqual(customCreated, 0); // Should be able to create object by passing in constructor. object = realm.create(CustomObject, { intCol: 2 }); TestCase.assertTrue(object instanceof CustomObject); TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype); - TestCase.assertEqual(customCreated, 2); + TestCase.assertEqual(customCreated, 0); }); - TestCase.assertThrowsContaining(() => { - realm.write(() => { - realm.create("InvalidObject", { intCol: 1 }); - }); - }, "Realm object constructor must not return another value"); + realm.write(() => { + realm.create("InvalidObject", { intCol: 1 }); + }); // Only the original constructor should be valid. function InvalidCustomObject() {} diff --git a/tests/spec/helpers/reporters.js b/tests/spec/helpers/reporters.js index f7090c8e0a0..f9906c27b40 100644 --- a/tests/spec/helpers/reporters.js +++ b/tests/spec/helpers/reporters.js @@ -31,5 +31,8 @@ jasmine.getEnv().addReporter( spec: { displayPending: true, }, + summary: { + displayStacktrace: true, + }, }), ); diff --git a/types/index.d.ts b/types/index.d.ts index 8ec41e6996c..345f129c33d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -211,7 +211,12 @@ declare namespace Realm { * Object * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Object.html } */ - abstract class Object { + abstract class Object { + /** + * Creates a new object in the database. + */ + constructor(realm: Realm, values: Unmanaged); + /** * @returns An array of the names of the object's properties. */ @@ -922,10 +927,10 @@ type ExtractPropertyNamesOfType = { }[keyof T]; /** - * Exchanges properties defined as Realm.List with an optional Array>. + * Exchanges properties defined as Realm.List with an optional Array>. */ type RealmListsRemappedModelPart = { - [K in ExtractPropertyNamesOfType>]?: T[K] extends Realm.List ? Array> : never + [K in ExtractPropertyNamesOfType>]?: T[K] extends Realm.List ? Array> : never } /** @@ -952,7 +957,7 @@ type RemappedRealmTypes = * Joins T stripped of all keys which value extends Realm.Collection and all inherited from Realm.Object, * with only the keys which value extends Realm.List, remapped as Arrays. */ -type RealmInsertionModel = OmittedRealmTypes & RemappedRealmTypes; +type Unmanaged = OmittedRealmTypes & RemappedRealmTypes; declare class Realm { static defaultPath: string; @@ -1035,8 +1040,8 @@ declare class Realm { * @param {Realm.UpdateMode} mode? If not provided, `Realm.UpdateMode.Never` is used. * @returns T & Realm.Object */ - create(type: string, properties: RealmInsertionModel, mode?: Realm.UpdateMode.Never): T & Realm.Object; - create(type: string, properties: Partial | Partial>, mode: Realm.UpdateMode.All | Realm.UpdateMode.Modified): T & Realm.Object; + create(type: string, properties: Unmanaged, mode?: Realm.UpdateMode.Never): T & Realm.Object; + create(type: string, properties: Partial | Partial>, mode: Realm.UpdateMode.All | Realm.UpdateMode.Modified): T & Realm.Object; /** * @param {Class} type @@ -1044,8 +1049,8 @@ declare class Realm { * @param {Realm.UpdateMode} mode? If not provided, `Realm.UpdateMode.Never` is used. * @returns T */ - create(type: {new(...arg: any[]): T; }, properties: RealmInsertionModel, mode?: Realm.UpdateMode.Never): T; - create(type: {new(...arg: any[]): T; }, properties: Partial | Partial>, mode: Realm.UpdateMode.All | Realm.UpdateMode.Modified): T; + create(type: {new(...arg: any[]): T; }, properties: Unmanaged, mode?: Realm.UpdateMode.Never): T; + create(type: {new(...arg: any[]): T; }, properties: Partial | Partial>, mode: Realm.UpdateMode.All | Realm.UpdateMode.Modified): T; /** * @param {Realm.Object|Realm.Object[]|Realm.List|Realm.Results|any} object @@ -1078,7 +1083,7 @@ declare class Realm { objectForPrimaryKey(type: {new(...arg: any[]): T; }, key: Realm.PrimaryKey): T | undefined; // Combined definitions - objectForPrimaryKey(type: string | {new(...arg: any[]): T; }, key: Realm.PrimaryKey): (T & Realm.Object) | undefined; + objectForPrimaryKey(type: string | {new(...arg: any[]): T; }, key: Realm.PrimaryKey): (T & Realm.Object) | undefined; /** * @param {string} type