From 5dad9eed70f9ed697c221a6039387e7f452e1d90 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 1 Jul 2024 09:46:47 +0200 Subject: [PATCH 01/14] core --- packages/realm/bindgen/vendor/realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/realm/bindgen/vendor/realm-core b/packages/realm/bindgen/vendor/realm-core index 6bebc40a03..7cefce7d15 160000 --- a/packages/realm/bindgen/vendor/realm-core +++ b/packages/realm/bindgen/vendor/realm-core @@ -1 +1 @@ -Subproject commit 6bebc40a03ca4144050bc672a6cd86c2286caa32 +Subproject commit 7cefce7d159e1904a9fcc2957a3f4a4ccb500849 From 83b739a61c2be41aa0b321c2ad81b3cf5282e4ad Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 5 Jul 2024 11:44:31 +0200 Subject: [PATCH 02/14] Configuration --- packages/realm/bindgen/js_opt_in_spec.yml | 1 + packages/realm/src/Configuration.ts | 10 ++++++++++ packages/realm/src/Realm.ts | 3 ++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/realm/bindgen/js_opt_in_spec.yml b/packages/realm/bindgen/js_opt_in_spec.yml index ef80c1274e..d9d7e57bb1 100644 --- a/packages/realm/bindgen/js_opt_in_spec.yml +++ b/packages/realm/bindgen/js_opt_in_spec.yml @@ -44,6 +44,7 @@ records: - schema - schema_version - schema_mode + - flexible_schema - disable_format_upgrade - sync_config - force_sync_history diff --git a/packages/realm/src/Configuration.ts b/packages/realm/src/Configuration.ts index 55bc7c0fa2..0d3902cee9 100644 --- a/packages/realm/src/Configuration.ts +++ b/packages/realm/src/Configuration.ts @@ -101,6 +101,12 @@ export type BaseConfiguration = { * @since 2.23.0 */ fifoFilesFallbackPath?: string; + /** + * Opening a Realm with relaxed schema means that you can dynamically add, update, and remove properties + * at runtime. These additional properties will always have the type Mixed. + * @default false + */ + relaxedSchema?: boolean; sync?: SyncConfiguration; /** @internal */ openSyncedRealmLocally?: true; @@ -184,6 +190,7 @@ export function validateConfiguration(config: unknown): asserts config is Config path, schema, schemaVersion, + relaxedSchema, inMemory, readOnly, fifoFilesFallbackPath, @@ -211,6 +218,9 @@ export function validateConfiguration(config: unknown): asserts config is Config "'schemaVersion' on realm configuration must be 0 or a positive integer.", ); } + if (relaxedSchema !== undefined) { + assert.boolean(relaxedSchema, "'relaxedSchema' on realm configuration"); + } if (inMemory !== undefined) { assert.boolean(inMemory, "'inMemory' on realm configuration"); } diff --git a/packages/realm/src/Realm.ts b/packages/realm/src/Realm.ts index 4e33af4f27..f968100c23 100644 --- a/packages/realm/src/Realm.ts +++ b/packages/realm/src/Realm.ts @@ -430,7 +430,7 @@ export class Realm { const normalizedSchema = config.schema && normalizeRealmSchema(config.schema); const schemaExtras = Realm.extractRealmSchemaExtras(normalizedSchema || []); const path = Realm.determinePath(config); - const { fifoFilesFallbackPath, shouldCompact, inMemory } = config; + const { fifoFilesFallbackPath, shouldCompact, inMemory, relaxedSchema } = config; const bindingSchema = normalizedSchema && toBindingSchema(normalizedSchema); return { schemaExtras, @@ -444,6 +444,7 @@ export class Realm { schemaVersion: config.schema ? binding.Int64.numToInt(typeof config.schemaVersion === "number" ? config.schemaVersion : 0) : undefined, + flexibleSchema: relaxedSchema === true, migrationFunction: config.onMigration ? Realm.wrapMigration(schemaExtras, config.onMigration) : undefined, shouldCompactOnLaunchFunction: shouldCompact ? (totalBytes, usedBytes) => { From 14bec5234c60eb864e6438f97be277a9c8b377e2 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 5 Jul 2024 11:57:48 +0200 Subject: [PATCH 03/14] Add test suite --- integration-tests/tests/src/tests.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-tests/tests/src/tests.ts b/integration-tests/tests/src/tests.ts index 6bd005fd52..be3b5d4791 100644 --- a/integration-tests/tests/src/tests.ts +++ b/integration-tests/tests/src/tests.ts @@ -63,6 +63,7 @@ import "./tests/objects"; import "./tests/observable"; import "./tests/queries"; import "./tests/realm-constructor"; +import "./tests/relaxed-schema"; import "./tests/results"; import "./tests/schema"; import "./tests/serialization"; From d5ca61b419c3da11ab86b71d7a8801d0f8b1fd80 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 5 Jul 2024 12:06:20 +0200 Subject: [PATCH 04/14] Using feature branch in core --- packages/realm/bindgen/vendor/realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/realm/bindgen/vendor/realm-core b/packages/realm/bindgen/vendor/realm-core index 7cefce7d15..5b76afde38 160000 --- a/packages/realm/bindgen/vendor/realm-core +++ b/packages/realm/bindgen/vendor/realm-core @@ -1 +1 @@ -Subproject commit 7cefce7d159e1904a9fcc2957a3f4a4ccb500849 +Subproject commit 5b76afde387c5b428e1e8565867c6d7f12611ebd From c58d6ff43a7ddd9fbad611a54683e017ae30bfab Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 17 Jul 2024 15:16:55 +0200 Subject: [PATCH 05/14] wip --- .../tests/src/tests/relaxed-schema.ts | 81 +++++++++++++++++++ packages/realm/bindgen/js_opt_in_spec.yml | 3 + packages/realm/src/ClassMap.ts | 2 +- packages/realm/src/Object.ts | 35 +++++++- 4 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 integration-tests/tests/src/tests/relaxed-schema.ts diff --git a/integration-tests/tests/src/tests/relaxed-schema.ts b/integration-tests/tests/src/tests/relaxed-schema.ts new file mode 100644 index 0000000000..45ef74d4be --- /dev/null +++ b/integration-tests/tests/src/tests/relaxed-schema.ts @@ -0,0 +1,81 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 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. +// +//////////////////////////////////////////////////////////////////////////// +import { expect } from "chai"; +import Realm from "realm"; + +import { PersonSchema } from "../schemas/person-and-dogs"; +import { openRealmBeforeEach } from "../hooks"; + + +describe.only("Relaxed schema", () => { + openRealmBeforeEach({ relaxedSchema: true, schema: [PersonSchema] }); + + it("can open a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { + expect(this.realm).not.null; + }); + + it("can add an object to a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { + this.realm.write(() => { + this.realm.create(PersonSchema.name, { + name: "Joe", + age: 19, + }); + }); + + expect(this.realm.objects(PersonSchema.name).length).equals(1); + }); + + it.only("can modify a property of an object in a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { + this.realm.write(() => { + this.realm.create(PersonSchema.name, { + name: "Joe", + age: 19, + }); + }); + + this.realm.write(() => { + const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + expect(joe).not.null; + console.log("FISK 1"); + joe.age = 25; + console.log("FISK 2"); + }); + + const olderJoe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + expect(olderJoe.age).equals(25); + }); + + it.only("can add a new property", function (this: Mocha.Context & RealmContext) { + this.realm.write(() => { + this.realm.create(PersonSchema.name, { + name: "Joe", + age: 19, + }); + }); + + this.realm.write(() => { + const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + expect(joe).not.null; + joe.realName = "Johannes"; + }); + const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + expect(joe).not.null; + expect(joe.name).equals("Joe"); + expect(joe.realName).equals("Johannes"); + }); +}); diff --git a/packages/realm/bindgen/js_opt_in_spec.yml b/packages/realm/bindgen/js_opt_in_spec.yml index d9d7e57bb1..c1be7ffd4a 100644 --- a/packages/realm/bindgen/js_opt_in_spec.yml +++ b/packages/realm/bindgen/js_opt_in_spec.yml @@ -299,6 +299,7 @@ classes: - get_link_target - clear - get_primary_key_column + - get_column_key Obj: methods: @@ -306,7 +307,9 @@ classes: - get_table - get_key - get_any + - get_any_by_name - set_any + - set_any_by_name - set_collection - add_int - get_linked_object diff --git a/packages/realm/src/ClassMap.ts b/packages/realm/src/ClassMap.ts index 3153561167..a617201323 100644 --- a/packages/realm/src/ClassMap.ts +++ b/packages/realm/src/ClassMap.ts @@ -130,7 +130,7 @@ export class ClassMap { properties, wrapObject(obj) { if (obj.isValid) { - return RealmObject.createWrapper(obj, constructor); + return RealmObject.createWrapper(obj, constructor, realm.internal.config.flexibleSchema); } else { return null; } diff --git a/packages/realm/src/Object.ts b/packages/realm/src/Object.ts index 4e96620f0e..bb473f3ebc 100644 --- a/packages/realm/src/Object.ts +++ b/packages/realm/src/Object.ts @@ -42,6 +42,8 @@ import { createResultsAccessor, flags, getTypeName, + mixedFromBinding, + mixedToBinding, } from "./internal"; /** @@ -111,6 +113,31 @@ const PROXY_HANDLER: ProxyHandler> = { }, }; +const PROXY_HANDLER_RELAXED: ProxyHandler> = { + get(target, prop) { + return target[INTERNAL].getAnyByName(prop as string); + }, + + set(target, prop, value) { + console.log("FISK 20", { target, prop, value }); + console.log("FISK 21", mixedToBinding(target[REALM].internal, value)); + target[INTERNAL].setAnyByName(prop as string, mixedToBinding(target[REALM].internal, value)); + console.log("FISK 22"); + return true; + }, + + deleteProperty(target, prop) { + return true; + }, + + getOwnPropertyDescriptor(_) { + return { + enumerable: true, + configurable: true, + }; + }, +}; + /** * Base class for a Realm Object. * @example @@ -308,7 +335,7 @@ export class RealmObject(internal: binding.Obj, constructor: Constructor): RealmObject & T { + public static createWrapper(internal: binding.Obj, constructor: Constructor, relaxedSchema: boolean): RealmObject & T { const result = Object.create(constructor.prototype); result[INTERNAL] = internal; // Initializing INTERNAL_LISTENERS here rather than letting it just be implicitly undefined since JS engines @@ -316,7 +343,11 @@ export class RealmObject Date: Thu, 18 Jul 2024 17:15:31 +0200 Subject: [PATCH 06/14] Initial implementation --- .../tests/src/tests/relaxed-schema.ts | 10 +++--- packages/realm/bindgen/js_opt_in_spec.yml | 1 + packages/realm/bindgen/vendor/realm-core | 2 +- packages/realm/src/Object.ts | 32 ++++++++++++++++--- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/integration-tests/tests/src/tests/relaxed-schema.ts b/integration-tests/tests/src/tests/relaxed-schema.ts index 45ef74d4be..1935864bb2 100644 --- a/integration-tests/tests/src/tests/relaxed-schema.ts +++ b/integration-tests/tests/src/tests/relaxed-schema.ts @@ -22,7 +22,7 @@ import { PersonSchema } from "../schemas/person-and-dogs"; import { openRealmBeforeEach } from "../hooks"; -describe.only("Relaxed schema", () => { +describe("Relaxed schema", () => { openRealmBeforeEach({ relaxedSchema: true, schema: [PersonSchema] }); it("can open a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { @@ -40,7 +40,7 @@ describe.only("Relaxed schema", () => { expect(this.realm.objects(PersonSchema.name).length).equals(1); }); - it.only("can modify a property of an object in a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { + it("can modify a property of an object in a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { this.realm.write(() => { this.realm.create(PersonSchema.name, { name: "Joe", @@ -51,16 +51,14 @@ describe.only("Relaxed schema", () => { this.realm.write(() => { const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); expect(joe).not.null; - console.log("FISK 1"); joe.age = 25; - console.log("FISK 2"); }); const olderJoe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); - expect(olderJoe.age).equals(25); + expect(olderJoe.age).equals(25n); // TODO: why BigInt and not Number? }); - it.only("can add a new property", function (this: Mocha.Context & RealmContext) { + it("can add a new property", function (this: Mocha.Context & RealmContext) { this.realm.write(() => { this.realm.create(PersonSchema.name, { name: "Joe", diff --git a/packages/realm/bindgen/js_opt_in_spec.yml b/packages/realm/bindgen/js_opt_in_spec.yml index c1be7ffd4a..55d470e3c6 100644 --- a/packages/realm/bindgen/js_opt_in_spec.yml +++ b/packages/realm/bindgen/js_opt_in_spec.yml @@ -316,6 +316,7 @@ classes: - get_backlink_count - get_backlink_view - create_and_set_linked_object + - has_schema_property Timestamp: methods: diff --git a/packages/realm/bindgen/vendor/realm-core b/packages/realm/bindgen/vendor/realm-core index 5b76afde38..7844c56823 160000 --- a/packages/realm/bindgen/vendor/realm-core +++ b/packages/realm/bindgen/vendor/realm-core @@ -1 +1 @@ -Subproject commit 5b76afde387c5b428e1e8565867c6d7f12611ebd +Subproject commit 7844c56823e7b8e0aeabfaf140c14d41bd41ead5 diff --git a/packages/realm/src/Object.ts b/packages/realm/src/Object.ts index bb473f3ebc..4b23f00e86 100644 --- a/packages/realm/src/Object.ts +++ b/packages/realm/src/Object.ts @@ -42,8 +42,12 @@ import { createResultsAccessor, flags, getTypeName, - mixedFromBinding, mixedToBinding, + getTypeHelpers, + getClassHelpers, + TypeOptions, + MappableTypeHelpers, + fromBindingSyncError, } from "./internal"; /** @@ -115,14 +119,32 @@ const PROXY_HANDLER: ProxyHandler> = { const PROXY_HANDLER_RELAXED: ProxyHandler> = { get(target, prop) { + // TODO: add type helper here too return target[INTERNAL].getAnyByName(prop as string); }, set(target, prop, value) { - console.log("FISK 20", { target, prop, value }); - console.log("FISK 21", mixedToBinding(target[REALM].internal, value)); - target[INTERNAL].setAnyByName(prop as string, mixedToBinding(target[REALM].internal, value)); - console.log("FISK 22"); + const obj = target[INTERNAL]; + const propName = prop as string; + + if (obj.hasSchemaProperty(propName)) { + const colKey = obj.table.getColumnKey(propName); + const options: TypeOptions = { + realm: target[REALM], + name: propName, + optional: false, + objectSchemaName: obj.table.name, + objectType: undefined, + getClassHelpers: function (nameOrTableKey: string | binding.TableKey): ClassHelpers { + throw new Error("Function not implemented."); + } + }; + const typ = obj.table.getColumnType(colKey); + const typeHelper = getTypeHelpers(typ as unknown as MappableTypeHelpers, options); + obj.setAny(colKey, typeHelper.toBinding(value)); + } else { + obj.setAnyByName(propName, value); + } return true; }, From a7bc1908da9f80bfc98f6698733132516de286bd Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Wed, 24 Jul 2024 11:21:49 +0200 Subject: [PATCH 07/14] Nicer tests --- .../tests/src/tests/relaxed-schema.ts | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/integration-tests/tests/src/tests/relaxed-schema.ts b/integration-tests/tests/src/tests/relaxed-schema.ts index 1935864bb2..9fced7f404 100644 --- a/integration-tests/tests/src/tests/relaxed-schema.ts +++ b/integration-tests/tests/src/tests/relaxed-schema.ts @@ -16,13 +16,10 @@ // //////////////////////////////////////////////////////////////////////////// import { expect } from "chai"; -import Realm from "realm"; - import { PersonSchema } from "../schemas/person-and-dogs"; import { openRealmBeforeEach } from "../hooks"; - -describe("Relaxed schema", () => { +describe("35", () => { openRealmBeforeEach({ relaxedSchema: true, schema: [PersonSchema] }); it("can open a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { @@ -40,7 +37,8 @@ describe("Relaxed schema", () => { expect(this.realm.objects(PersonSchema.name).length).equals(1); }); - it("can modify a property of an object in a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { + it("can modify a property of an object in a Realm with a relaxed schema", function (this: Mocha.Context & + RealmContext) { this.realm.write(() => { this.realm.create(PersonSchema.name, { name: "Joe", @@ -49,12 +47,14 @@ describe("Relaxed schema", () => { }); this.realm.write(() => { - const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; expect(joe).not.null; joe.age = 25; }); - const olderJoe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const olderJoe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; expect(olderJoe.age).equals(25n); // TODO: why BigInt and not Number? }); @@ -67,13 +67,23 @@ describe("Relaxed schema", () => { }); this.realm.write(() => { - const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; expect(joe).not.null; joe.realName = "Johannes"; }); - const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + let joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; expect(joe).not.null; expect(joe.name).equals("Joe"); expect(joe.realName).equals("Johannes"); + + this.realm.write(() => { + joe.realName = "Not Johannes"; + }); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; + expect(joe.realName).equals("Not Johannes"); }); }); From bd0d34d1c7a59796e31e323fb7080dd81a1d9040 Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Wed, 24 Jul 2024 12:21:21 +0200 Subject: [PATCH 08/14] Check gpg --- integration-tests/tests/src/tests/relaxed-schema.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-tests/tests/src/tests/relaxed-schema.ts b/integration-tests/tests/src/tests/relaxed-schema.ts index 9fced7f404..8936aad890 100644 --- a/integration-tests/tests/src/tests/relaxed-schema.ts +++ b/integration-tests/tests/src/tests/relaxed-schema.ts @@ -81,7 +81,6 @@ describe("35", () => { this.realm.write(() => { joe.realName = "Not Johannes"; }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; expect(joe.realName).equals("Not Johannes"); From 4faf63016d44184914a406bedc329ea827fec311 Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Wed, 24 Jul 2024 12:23:02 +0200 Subject: [PATCH 09/14] Fix GPG --- integration-tests/tests/src/tests/relaxed-schema.ts | 1 + packages/realm/bindgen/vendor/realm-core | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/integration-tests/tests/src/tests/relaxed-schema.ts b/integration-tests/tests/src/tests/relaxed-schema.ts index 8936aad890..9fced7f404 100644 --- a/integration-tests/tests/src/tests/relaxed-schema.ts +++ b/integration-tests/tests/src/tests/relaxed-schema.ts @@ -81,6 +81,7 @@ describe("35", () => { this.realm.write(() => { joe.realName = "Not Johannes"; }); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; expect(joe.realName).equals("Not Johannes"); diff --git a/packages/realm/bindgen/vendor/realm-core b/packages/realm/bindgen/vendor/realm-core index 7844c56823..9e8e654a3e 160000 --- a/packages/realm/bindgen/vendor/realm-core +++ b/packages/realm/bindgen/vendor/realm-core @@ -1 +1 @@ -Subproject commit 7844c56823e7b8e0aeabfaf140c14d41bd41ead5 +Subproject commit 9e8e654a3ef588eefaca3e32a9a9e61e22709c2f From e65ac8a5bacecdd659d5080abda30d21db4dc737 Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Wed, 24 Jul 2024 22:41:07 +0200 Subject: [PATCH 10/14] Add property deletion --- .../tests/src/tests/relaxed-schema.ts | 125 ++++++++++++++---- packages/realm/bindgen/js_opt_in_spec.yml | 1 + packages/realm/src/Object.ts | 6 +- 3 files changed, 105 insertions(+), 27 deletions(-) diff --git a/integration-tests/tests/src/tests/relaxed-schema.ts b/integration-tests/tests/src/tests/relaxed-schema.ts index 9fced7f404..8c73242766 100644 --- a/integration-tests/tests/src/tests/relaxed-schema.ts +++ b/integration-tests/tests/src/tests/relaxed-schema.ts @@ -15,11 +15,15 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////// + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + import { expect } from "chai"; -import { PersonSchema } from "../schemas/person-and-dogs"; +import { PersonSchema, Person } from "../schemas/person-and-dogs"; import { openRealmBeforeEach } from "../hooks"; +import { BSON } from "realm"; -describe("35", () => { +describe("Relaxed schema", () => { openRealmBeforeEach({ relaxedSchema: true, schema: [PersonSchema] }); it("can open a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { @@ -37,7 +41,7 @@ describe("35", () => { expect(this.realm.objects(PersonSchema.name).length).equals(1); }); - it("can modify a property of an object in a Realm with a relaxed schema", function (this: Mocha.Context & + it("can modify an existing property of an object in a Realm with a relaxed schema", function (this: Mocha.Context & RealmContext) { this.realm.write(() => { this.realm.create(PersonSchema.name, { @@ -58,32 +62,101 @@ describe("35", () => { expect(olderJoe.age).equals(25n); // TODO: why BigInt and not Number? }); - it("can add a new property", function (this: Mocha.Context & RealmContext) { - this.realm.write(() => { - this.realm.create(PersonSchema.name, { - name: "Joe", - age: 19, + [ + ["primitive", 1234], + ["data", new ArrayBuffer(10)], + ["decimal128", 12n], + ["objectId", new BSON.ObjectID()], + ["uuid", new BSON.UUID()], + // ["linkingObjects", "linkingObjects"], + // ["list", ["123", "123", "12"]], + // [ + // "dictionary", + // { + // dictionary: { + // windows: 3, + // apples: 3, + // }, + // }, + // ], + ].forEach(([typeName, valueToSet]) => { + describe(`with ${typeName}`, () => { + let setValue: any; + + beforeEach(function (this: Mocha.Context & RealmContext) { + if (valueToSet == "linkingObjects") { + this.realm.write(() => { + setValue = this.realm.create(PersonSchema.name, { + name: "Different Joe", + age: 81, + }); + }); + } else { + setValue = valueToSet; + } }); - }); - this.realm.write(() => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; - expect(joe).not.null; - joe.realName = "Johannes"; - }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - let joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; - expect(joe).not.null; - expect(joe.name).equals("Joe"); - expect(joe.realName).equals("Johannes"); + it("can add a new property", function (this: Mocha.Context & RealmContext) { + this.realm.write(() => { + this.realm.create(PersonSchema.name, { + name: "Joe", + age: 19, + }); + }); - this.realm.write(() => { - joe.realName = "Not Johannes"; - }); + this.realm.write(() => { + const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; + expect(joe).not.null; + joe.customProperty = setValue; + }); + const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; + expect(joe).not.null; + expect(joe.name).equals("Joe"); + expect(joe.customProperty).deep.equals(setValue); + }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; - expect(joe.realName).equals("Not Johannes"); + it("can add a new property", function (this: Mocha.Context & RealmContext) { + this.realm.write(() => { + this.realm.create(PersonSchema.name, { + name: "Joe", + age: 19, + }); + }); + let joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; + expect(() => joe.customProperty).throws("Property 'Person.customProperty' does not exist"); + + this.realm.write(() => { + joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; + expect(joe).not.null; + joe.customProperty = setValue; + }); + + joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; + expect(joe).not.null; + expect(joe.name).equals("Joe"); + + expect(joe.customProperty).deep.equals(setValue); + }); + + it("can delete a property", function () { + let joe: any; + this.realm.write(() => { + joe = this.realm.create(PersonSchema.name, { + name: "Joe", + age: 19, + }); + }); + this.realm.write(() => { + joe.customProperty = setValue; + }); + expect(() => joe.customProperty).does.not.throw(); + + this.realm.write(() => { + delete joe.customProperty; + }); + joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe")!; + expect(() => joe.customProperty).throws("Property 'Person.customProperty' does not exist"); + }); + }); }); }); diff --git a/packages/realm/bindgen/js_opt_in_spec.yml b/packages/realm/bindgen/js_opt_in_spec.yml index 55d470e3c6..a4f1166f89 100644 --- a/packages/realm/bindgen/js_opt_in_spec.yml +++ b/packages/realm/bindgen/js_opt_in_spec.yml @@ -317,6 +317,7 @@ classes: - get_backlink_view - create_and_set_linked_object - has_schema_property + - erase_additional_prop Timestamp: methods: diff --git a/packages/realm/src/Object.ts b/packages/realm/src/Object.ts index 4b23f00e86..23c86c5523 100644 --- a/packages/realm/src/Object.ts +++ b/packages/realm/src/Object.ts @@ -357,7 +357,11 @@ export class RealmObject(internal: binding.Obj, constructor: Constructor, relaxedSchema: boolean): RealmObject & T { + public static createWrapper( + internal: binding.Obj, + constructor: Constructor, + relaxedSchema: boolean, + ): RealmObject & T { const result = Object.create(constructor.prototype); result[INTERNAL] = internal; // Initializing INTERNAL_LISTENERS here rather than letting it just be implicitly undefined since JS engines From 3102077b57e640275d6712c232fdd251af6c7e0a Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Wed, 24 Jul 2024 22:42:46 +0200 Subject: [PATCH 11/14] Add deleteProperty change --- packages/realm/src/Object.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/realm/src/Object.ts b/packages/realm/src/Object.ts index 23c86c5523..6055864b44 100644 --- a/packages/realm/src/Object.ts +++ b/packages/realm/src/Object.ts @@ -149,6 +149,15 @@ const PROXY_HANDLER_RELAXED: ProxyHandler> = { }, deleteProperty(target, prop) { + const obj = target[INTERNAL]; + const propName = prop as string; + + if (obj.hasSchemaProperty(propName)) { + // TODO: Discuss + throw new Error("Unsupported"); + } else { + obj.eraseAdditionalProp(propName); + } return true; }, From 08a5e14e15615636af25d12606e857b3968a25b9 Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Wed, 24 Jul 2024 22:46:36 +0200 Subject: [PATCH 12/14] Add realm-core checkout --- packages/realm/bindgen/vendor/realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/realm/bindgen/vendor/realm-core b/packages/realm/bindgen/vendor/realm-core index 9e8e654a3e..0d90bf0209 160000 --- a/packages/realm/bindgen/vendor/realm-core +++ b/packages/realm/bindgen/vendor/realm-core @@ -1 +1 @@ -Subproject commit 9e8e654a3ef588eefaca3e32a9a9e61e22709c2f +Subproject commit 0d90bf0209e612ffe146d250ef8cc98bfa778273 From 0d1fe379b8ee9f994e9163d7c509091ad5177769 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 6 Aug 2024 12:58:48 +0200 Subject: [PATCH 13/14] Add support for Object.keys(), Object.values(), and Object.entries() --- .../tests/src/tests/relaxed-schema.ts | 28 +++++++++++++++++++ packages/realm/bindgen/js_opt_in_spec.yml | 3 +- packages/realm/bindgen/vendor/realm-core | 2 +- packages/realm/src/Object.ts | 10 ++++++- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/integration-tests/tests/src/tests/relaxed-schema.ts b/integration-tests/tests/src/tests/relaxed-schema.ts index 8c73242766..66c7c3fbea 100644 --- a/integration-tests/tests/src/tests/relaxed-schema.ts +++ b/integration-tests/tests/src/tests/relaxed-schema.ts @@ -159,4 +159,32 @@ describe("Relaxed schema", () => { }); }); }); + + it("Object.keys(), Object.values(), and Object.entries()", function () { + this.realm.write(() => { + this.realm.create(PersonSchema.name, { + name: "Joe", + age: 19, + }); + }); + + let joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + let keys = Object.keys(joe); + expect(keys.length).equal(3); // 3 (from schema) + 0 (additional property) + expect(keys).to.have.deep.members(["age", "friends", "name"]); + expect(Object.entries(joe).length).equal(3); + expect(Object.values(joe).length).equal(3); + + this.realm.write(() => { + const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + joe.nickname = "Johannes"; + }); + + joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + keys = Object.keys(joe); + expect(keys.length).equal(4); // 3 (from schema) + 1 (additional property) + expect(keys).to.have.deep.members(["age", "friends", "name", "nickname"]); + expect(Object.entries(joe).length).equal(4); + expect(Object.values(joe).length).equal(4); + }); }); diff --git a/packages/realm/bindgen/js_opt_in_spec.yml b/packages/realm/bindgen/js_opt_in_spec.yml index a4f1166f89..fd810d4078 100644 --- a/packages/realm/bindgen/js_opt_in_spec.yml +++ b/packages/realm/bindgen/js_opt_in_spec.yml @@ -317,7 +317,8 @@ classes: - get_backlink_view - create_and_set_linked_object - has_schema_property - - erase_additional_prop + - erase_additional_prop + - get_additional_properties Timestamp: methods: diff --git a/packages/realm/bindgen/vendor/realm-core b/packages/realm/bindgen/vendor/realm-core index 0d90bf0209..9e422bda40 160000 --- a/packages/realm/bindgen/vendor/realm-core +++ b/packages/realm/bindgen/vendor/realm-core @@ -1 +1 @@ -Subproject commit 0d90bf0209e612ffe146d250ef8cc98bfa778273 +Subproject commit 9e422bda404ebd719f46ab759c2879c16c68b611 diff --git a/packages/realm/src/Object.ts b/packages/realm/src/Object.ts index 6055864b44..f96901da4b 100644 --- a/packages/realm/src/Object.ts +++ b/packages/realm/src/Object.ts @@ -126,7 +126,7 @@ const PROXY_HANDLER_RELAXED: ProxyHandler> = { set(target, prop, value) { const obj = target[INTERNAL]; const propName = prop as string; - + if (obj.hasSchemaProperty(propName)) { const colKey = obj.table.getColumnKey(propName); const options: TypeOptions = { @@ -161,6 +161,14 @@ const PROXY_HANDLER_RELAXED: ProxyHandler> = { return true; }, + ownKeys(target) { + const obj = target[INTERNAL]; + const schema = (target as Realm.Object).objectSchema(); + let keys = Object.keys(schema.properties); + let additionalProperties = obj.getAdditionalProperties(); + return [...keys, ...additionalProperties]; + }, + getOwnPropertyDescriptor(_) { return { enumerable: true, From 0da018fdc572dc058fc401b72d5f41b83de81710 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 6 Aug 2024 13:22:23 +0200 Subject: [PATCH 14/14] Test spread operator --- .../tests/src/tests/relaxed-schema.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/integration-tests/tests/src/tests/relaxed-schema.ts b/integration-tests/tests/src/tests/relaxed-schema.ts index 66c7c3fbea..3a2ecc7e71 100644 --- a/integration-tests/tests/src/tests/relaxed-schema.ts +++ b/integration-tests/tests/src/tests/relaxed-schema.ts @@ -187,4 +187,27 @@ describe("Relaxed schema", () => { expect(Object.entries(joe).length).equal(4); expect(Object.values(joe).length).equal(4); }); + + it("spread operator", function () { + this.realm.write(() => { + this.realm.create(PersonSchema.name, { + name: "Joe", + age: 19, + }); + }); + + const joe1 = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + const plainJoe1 = {...joe1}; + expect(plainJoe1 instanceof Object).to.be.true; + expect(Object.keys(plainJoe1)).to.have.deep.members(["age", "friends", "name"]); + + this.realm.write(() => { + const joe = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + joe.nickname = "Johannes"; + }); + + const joe2 = this.realm.objectForPrimaryKey(PersonSchema.name, "Joe"); + const plainJoe2 = {...joe2}; + expect(Object.keys(plainJoe2)).to.have.deep.members(["age", "friends", "name", "nickname"]); + }); });