From 78567dc913cea11f852e7c1a743a91ee260b8065 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 18 Nov 2023 21:08:19 +0800 Subject: [PATCH 01/29] feat: deserialized js class with schema info --- examples/package.json | 4 +- examples/src/status-deserialize-class.js | 164 +++++++++++++++++++++++ 2 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 examples/src/status-deserialize-class.js diff --git a/examples/package.json b/examples/package.json index 9aa43ea6..4f16b7d7 100644 --- a/examples/package.json +++ b/examples/package.json @@ -32,6 +32,7 @@ "build:state-migration": "run-s build:state-migration:*", "build:state-migration:original": "near-sdk-js build src/state-migration-original.ts build/state-migration-original.wasm", "build:state-migration:new": "near-sdk-js build src/state-migration-new.ts build/state-migration-new.wasm", + "build:status-deserialize-class": "near-sdk-js build src/status-deserialize-class.js build/status-deserialize-class.js.wasm", "test": "ava && pnpm test:counter-lowlevel && pnpm test:counter-ts", "test:nft": "ava __tests__/standard-nft/*", "test:ft": "ava __tests__/standard-ft/*", @@ -53,7 +54,8 @@ "test:nested-collections": "ava __tests__/test-nested-collections.ava.js", "test:status-message-borsh": "ava __tests__/test-status-message-borsh.ava.js", "test:status-message-serialize-err": "ava __tests__/test-status-message-serialize-err.ava.js", - "test:status-message-deserialize-err": "ava __tests__/test-status-message-deserialize-err.ava.js" + "test:status-message-deserialize-err": "ava __tests__/test-status-message-deserialize-err.ava.js", + "test:status-deserialize-class": "ava __tests__/status-deserialize-class.js" }, "author": "Near Inc ", "license": "Apache-2.0", diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js new file mode 100644 index 00000000..ed5a5174 --- /dev/null +++ b/examples/src/status-deserialize-class.js @@ -0,0 +1,164 @@ +import {NearBindgen, call, view, near, UnorderedMap, serialize, decode, deserialize} from "near-sdk-js"; +import lodash from "lodash-es"; + +function decode_obj2class(class_instance, obj) { + let key; + for (key in obj) { + // @ts-ignore + let value = obj[key]; + near.log("decodeNested filed, key: ", key, " value: ", value, "instance[", key, "]: ", class_instance[key]); + if (typeof value == 'object') { + near.log("object fields, object key: ", key, " value: ", value); + // @ts-ignore + let ty = class_instance.constructor.schema[key]; + if (ty !== undefined && ty.hasOwnProperty("map")) { + near.log("map type"); + for (let mkey in value) { + class_instance[key][mkey] = decode_obj2class(new ty["map"]["value"](), value[mkey]); + } + } else if (ty !== undefined && ty.hasOwnProperty("array")) { + near.log("vector type"); + for (let k in value) { + class_instance[key].push(decode_obj2class(new ty["array"]["value"](), value[k])); + } + } else if (ty !== undefined && ty.hasOwnProperty("unorder_map")) { + instance[key].constructor.schema = ty; + let subtype_value = ty["unorder_map"]["value"]; + instance[key].subtype = function () { + return subtype_value; + } + instance[key] = decodeNested2(instance[key], obj[key]); + } else if (ty !== undefined && ty.hasOwnProperty("vector")) { + // todo: imple + } else if (ty !== undefined && ty.hasOwnProperty("unorder_set")) { + // todo: imple + } else if (ty !== undefined && ty.hasOwnProperty("lookup_map")) { + // todo: impl + } else if (ty !== undefined && ty.hasOwnProperty("lookup_set")) { + // todo: impl + } else { + // normal case + class_instance[key].constructor.schema = class_instance.constructor.schema[key]; + class_instance[key] = decode_obj2class(class_instance[key], obj[key]); + } + near.log("instance[key] value", class_instance[key]); + } + } + const instance_tmp = lodash.cloneDeep(class_instance); + class_instance = Object.assign(class_instance, obj); + for (key in obj) { + if (typeof class_instance[key] == 'object') { + class_instance[key] = instance_tmp[key]; + } + } + near.log("current instance: ", class_instance); + return class_instance; +} + +class Car { + static schema = { + name: "string", + speed: "number", + }; + constructor() { + this.name = ""; + this.speed = 0; + } + + run() { + return this.name + " run with speed: " + this.speed.toString() + } +} + +class InnerStatusDeserializeClass { + static schema = { + records: {map: { key: 'string', value: 'string' }}, + car: Car, + efficient_recordes: {unorder_map: {value: 'string'}}, + nested_efficient_recordes: {unorder_map: {value: { unorder_map: {value: 'string'}}}} + }; + constructor() { + this.records = {}; + this.car = new Car(); + // account_id -> message + this.efficient_recordes = new UnorderedMap("a"); + // id -> account_id -> message + this.nested_efficient_recordes = new UnorderedMap("b"); + } +} + +@NearBindgen({}) +export class StatusDeserializeClass { + constructor() { + this.messages = ""; + } + + @call({}) + init_messages({ }) { + if (this.messages.length != 0) { + near.log(`message inited`); + return; + } + let account_id = near.signerAccountId(); + near.log(`${account_id} init_messages`); + let status = new InnerStatusDeserializeClass(); + let data = serialize(status) + this.messages = decode(data); + } + + @call({}) + set_record({ message }) { + let account_id = near.signerAccountId(); + near.log(`${account_id} set_status with message ${message}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + inst.records[account_id] = message; + let data = serialize(inst) + this.messages = decode(data); + } + + @view({}) + get_record({ account_id }) { + near.log(`get_status for account_id ${account_id}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.records[account_id] || null; + } + + @call({}) + set_efficient_recordes({ message, id }) { + let account_id = near.signerAccountId(); + near.log(`${account_id} set_efficient_recordes with message ${message},id ${id}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + inst.efficient_recordes.set(account_id, message); + const nestedMap = inst.nested_efficient_recordes.get(id, { + reconstructor: UnorderedMap.reconstruct, + defaultValue: new UnorderedMap("i_" + id + "_"), + }); + nestedMap.set(near.signerAccountId(), message); + inst.set(id, nestedMap); + + let data = serialize(inst) + this.messages = decode(data); + } + + @view({}) + get_efficient_recordes({ account_id }) { + near.log(`get_efficient_recordes for account_id ${account_id}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.efficient_recordes.get(account_id); + } + + @view({}) + get_nested_efficient_recordes({ account_id, id }) { + near.log(`get_nested_efficient_recordes for account_id ${account_id}, id ${id}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.nested_efficient_recordes.get(id, { + reconstructor: UnorderedMap.reconstruct, + defaultValue: new UnorderedMap("i_" + id + "_"), + }).get(account_id); + } +} From 2fa650cb8b8b9a191d0b5e1952f80c63e572de90 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Thu, 23 Nov 2023 00:41:29 +0800 Subject: [PATCH 02/29] feat: WIP add test of get_record --- .../test-status-deserialize-class.ava.js | 41 +++++++++++++++++++ examples/package.json | 4 +- examples/src/status-deserialize-class.js | 16 +++++--- 3 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 examples/__tests__/test-status-deserialize-class.ava.js diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js new file mode 100644 index 00000000..f1736a1e --- /dev/null +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -0,0 +1,41 @@ +import {BN, Worker} from "near-workspaces"; +import test from "ava"; + +test.before(async (t) => { + // Init the worker and start a Sandbox server + const worker = await Worker.init(); + + // Prepare sandbox for tests, create accounts, deploy contracts, etx. + const root = worker.rootAccount; + + // Deploy the contract. + const statusMessage = await root.devDeploy("./build/status-deserialize-class.wasm"); + + await root.call(statusMessage, "init_messages", {}, {gas: new BN(200 * 10**12)}); + const result = await statusMessage.view("is_message_inited", {}); + t.is(result, true); + // Create test users + const ali = await root.createSubAccount("ali"); + const bob = await root.createSubAccount("bob"); + const carl = await root.createSubAccount("carl"); + + // Save state for test runs + t.context.worker = worker; + t.context.accounts = { root, statusMessage, ali, bob, carl }; +}); + +test.after.always(async (t) => { + await t.context.worker.tearDown().catch((error) => { + console.log("Failed to tear down the worker:", error); + }); +}); + +test("Ali sets then gets status", async (t) => { + const { ali, statusMessage } = t.context.accounts; + await ali.call(statusMessage, "set_record", { message: "hello" }, {gas: new BN(200 * 10**12)}); + + t.is( + await statusMessage.view("get_record", { account_id: ali.accountId }), + "hello" + ); +}); diff --git a/examples/package.json b/examples/package.json index 4f16b7d7..be7e6309 100644 --- a/examples/package.json +++ b/examples/package.json @@ -32,7 +32,7 @@ "build:state-migration": "run-s build:state-migration:*", "build:state-migration:original": "near-sdk-js build src/state-migration-original.ts build/state-migration-original.wasm", "build:state-migration:new": "near-sdk-js build src/state-migration-new.ts build/state-migration-new.wasm", - "build:status-deserialize-class": "near-sdk-js build src/status-deserialize-class.js build/status-deserialize-class.js.wasm", + "build:status-deserialize-class": "near-sdk-js build src/status-deserialize-class.js build/status-deserialize-class.wasm", "test": "ava && pnpm test:counter-lowlevel && pnpm test:counter-ts", "test:nft": "ava __tests__/standard-nft/*", "test:ft": "ava __tests__/standard-ft/*", @@ -55,7 +55,7 @@ "test:status-message-borsh": "ava __tests__/test-status-message-borsh.ava.js", "test:status-message-serialize-err": "ava __tests__/test-status-message-serialize-err.ava.js", "test:status-message-deserialize-err": "ava __tests__/test-status-message-deserialize-err.ava.js", - "test:status-deserialize-class": "ava __tests__/status-deserialize-class.js" + "test:status-deserialize-class": "ava __tests__/test-status-deserialize-class.ava.js" }, "author": "Near Inc ", "license": "Apache-2.0", diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index ed5a5174..cb8de20a 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -1,4 +1,4 @@ -import {NearBindgen, call, view, near, UnorderedMap, serialize, decode, deserialize} from "near-sdk-js"; +import {NearBindgen, call, view, near, UnorderedMap, serialize, decode, deserialize, encode} from "near-sdk-js"; import lodash from "lodash-es"; function decode_obj2class(class_instance, obj) { @@ -22,12 +22,12 @@ function decode_obj2class(class_instance, obj) { class_instance[key].push(decode_obj2class(new ty["array"]["value"](), value[k])); } } else if (ty !== undefined && ty.hasOwnProperty("unorder_map")) { - instance[key].constructor.schema = ty; + class_instance[key].constructor.schema = ty; let subtype_value = ty["unorder_map"]["value"]; - instance[key].subtype = function () { + class_instance[key].subtype = function () { return subtype_value; } - instance[key] = decodeNested2(instance[key], obj[key]); + class_instance[key] = decode_obj2class(class_instance[key], obj[key]); } else if (ty !== undefined && ty.hasOwnProperty("vector")) { // todo: imple } else if (ty !== undefined && ty.hasOwnProperty("unorder_set")) { @@ -106,6 +106,12 @@ export class StatusDeserializeClass { this.messages = decode(data); } + @view({}) + is_message_inited({}) { + near.log(`query is_message_inited`); + return this.messages.length != 0; + } + @call({}) set_record({ message }) { let account_id = near.signerAccountId(); @@ -119,7 +125,7 @@ export class StatusDeserializeClass { @view({}) get_record({ account_id }) { - near.log(`get_status for account_id ${account_id}`); + near.log(`get_record for account_id ${account_id}`); let obj = deserialize(encode(this.messages)); let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); return inst.records[account_id] || null; From 2d65f4d85c222cfcee62e50911a9d60e69cc0414 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 25 Nov 2023 17:00:40 +0800 Subject: [PATCH 03/29] test: test decode class get record --- examples/src/status-deserialize-class.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index cb8de20a..faa37011 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -14,12 +14,20 @@ function decode_obj2class(class_instance, obj) { if (ty !== undefined && ty.hasOwnProperty("map")) { near.log("map type"); for (let mkey in value) { - class_instance[key][mkey] = decode_obj2class(new ty["map"]["value"](), value[mkey]); + if (ty["map"]["value"]==='string') { + class_instance[key][mkey] = value[mkey]; + } else { + class_instance[key][mkey] = decode_obj2class(new ty["map"]["value"](), value[mkey]); + } } } else if (ty !== undefined && ty.hasOwnProperty("array")) { near.log("vector type"); for (let k in value) { - class_instance[key].push(decode_obj2class(new ty["array"]["value"](), value[k])); + if (ty["array"]["value"]==='string') { + class_instance[key].push(value[k]); + } else { + class_instance[key].push(decode_obj2class(new ty["array"]["value"](), value[k])); + } } } else if (ty !== undefined && ty.hasOwnProperty("unorder_map")) { class_instance[key].constructor.schema = ty; @@ -129,6 +137,8 @@ export class StatusDeserializeClass { let obj = deserialize(encode(this.messages)); let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); return inst.records[account_id] || null; + near.log("obj", obj); + return true; } @call({}) From 4b9201cc1cd7d8abd5dc299d292c3a7a441f7cdb Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 25 Nov 2023 17:16:50 +0800 Subject: [PATCH 04/29] test: test call inner class's method --- .../test-status-deserialize-class.ava.js | 12 +++++++++ examples/src/status-deserialize-class.js | 27 ++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index f1736a1e..130c9f90 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -39,3 +39,15 @@ test("Ali sets then gets status", async (t) => { "hello" ); }); + +test("Ali set_car_info and get_car_info", async (t) => { + const { ali, statusMessage } = t.context.accounts; + let carName = "Mercedes-Benz"; + let speed = 240; + await ali.call(statusMessage, "set_car_info", { name: carName, speed: speed }, {gas: new BN(200 * 10**12)}); + + t.is( + await statusMessage.view("get_car_info", { }), + carName + " run with speed " + speed + ); +}); \ No newline at end of file diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index faa37011..51b3fefb 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -73,8 +73,8 @@ class Car { this.speed = 0; } - run() { - return this.name + " run with speed: " + this.speed.toString() + info() { + return this.name + " run with speed " + this.speed.toString() } } @@ -137,8 +137,27 @@ export class StatusDeserializeClass { let obj = deserialize(encode(this.messages)); let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); return inst.records[account_id] || null; - near.log("obj", obj); - return true; + } + + + @call({}) + set_car_info({ name, speed }) { + let account_id = near.signerAccountId(); + near.log(`${account_id} set_car_info name ${name}, speed ${speed}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + inst.car.name = name; + inst.car.speed = speed; + let data = serialize(inst) + this.messages = decode(data); + } + + @view({}) + get_car_info({ }) { + near.log(`get_car_info`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.car.info(); } @call({}) From a7230bcc68494d832c9e54b0cb5d5f1562307668 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 25 Nov 2023 18:01:12 +0800 Subject: [PATCH 05/29] test: test decode class with inner array --- .../test-status-deserialize-class.ava.js | 13 ++++++++++++ examples/src/status-deserialize-class.js | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index 130c9f90..0965114f 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -50,4 +50,17 @@ test("Ali set_car_info and get_car_info", async (t) => { await statusMessage.view("get_car_info", { }), carName + " run with speed " + speed ); +}); + +test("Ali push_message and get_messages", async (t) => { + const { ali, statusMessage } = t.context.accounts; + let message1 = 'Hello'; + let message2 = 'World'; + await ali.call(statusMessage, "push_message", { message: message1 }, {gas: new BN(200 * 10**12)}); + await ali.call(statusMessage, "push_message", { message: message2 }, {gas: new BN(200 * 10**12)}); + + t.is( + await statusMessage.view("get_messages", { }), + 'Hello,World' + ); }); \ No newline at end of file diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 51b3fefb..09dd96c3 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -82,12 +82,14 @@ class InnerStatusDeserializeClass { static schema = { records: {map: { key: 'string', value: 'string' }}, car: Car, + messages: {array: {value: 'string'}}, efficient_recordes: {unorder_map: {value: 'string'}}, nested_efficient_recordes: {unorder_map: {value: { unorder_map: {value: 'string'}}}} }; constructor() { this.records = {}; this.car = new Car(); + this.messages = []; // account_id -> message this.efficient_recordes = new UnorderedMap("a"); // id -> account_id -> message @@ -160,6 +162,25 @@ export class StatusDeserializeClass { return inst.car.info(); } + @call({}) + push_message({ message }) { + let account_id = near.signerAccountId(); + near.log(`${account_id} push_message message ${message}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + inst.messages.push(message); + let data = serialize(inst) + this.messages = decode(data); + } + + @view({}) + get_messages({ }) { + near.log(`get_messages`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.messages.join(','); + } + @call({}) set_efficient_recordes({ message, id }) { let account_id = near.signerAccountId(); From f3e01c94fbd4e170b7f9f9b6e86d1ce1bdf0ceaf Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 25 Nov 2023 22:40:42 +0800 Subject: [PATCH 06/29] test: test decode UnorderMap and nested collections --- .../test-status-deserialize-class.ava.js | 22 +++++++++++++++++++ examples/src/status-deserialize-class.js | 12 +++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index 0965114f..d167b379 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -63,4 +63,26 @@ test("Ali push_message and get_messages", async (t) => { await statusMessage.view("get_messages", { }), 'Hello,World' ); +}); + +test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text", async (t) => { + const { ali, bob, statusMessage } = t.context.accounts; + await ali.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, {gas: new BN(200 * 10**12)}); + await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, {gas: new BN(200 * 10**12)}); + await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "2", message: "world" }, {gas: new BN(200 * 10**12)}); + + t.is( + await statusMessage.view("get_efficient_recordes", { account_id: ali.accountId }), + "hello" + ); + + t.is( + await statusMessage.view("get_nested_efficient_recordes", { id: "1", account_id: bob.accountId }), + "hello" + ); + + t.is( + await statusMessage.view("get_nested_efficient_recordes", { id: "2", account_id: bob.accountId }), + "world" + ); }); \ No newline at end of file diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 09dd96c3..46e75b46 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -35,7 +35,7 @@ function decode_obj2class(class_instance, obj) { class_instance[key].subtype = function () { return subtype_value; } - class_instance[key] = decode_obj2class(class_instance[key], obj[key]); + // class_instance[key] = decode_obj2class(class_instance[key], obj[key]); } else if (ty !== undefined && ty.hasOwnProperty("vector")) { // todo: imple } else if (ty !== undefined && ty.hasOwnProperty("unorder_set")) { @@ -182,18 +182,18 @@ export class StatusDeserializeClass { } @call({}) - set_efficient_recordes({ message, id }) { + set_nested_efficient_recordes({ message, id }) { let account_id = near.signerAccountId(); - near.log(`${account_id} set_efficient_recordes with message ${message},id ${id}`); + near.log(`${account_id} set_nested_efficient_recordes with message ${message},id ${id}`); let obj = deserialize(encode(this.messages)); let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); inst.efficient_recordes.set(account_id, message); const nestedMap = inst.nested_efficient_recordes.get(id, { reconstructor: UnorderedMap.reconstruct, - defaultValue: new UnorderedMap("i_" + id + "_"), + defaultValue: new UnorderedMap("i_" + id + "_"), }); nestedMap.set(near.signerAccountId(), message); - inst.set(id, nestedMap); + inst.nested_efficient_recordes.set(id, nestedMap); let data = serialize(inst) this.messages = decode(data); @@ -214,7 +214,7 @@ export class StatusDeserializeClass { let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); return inst.nested_efficient_recordes.get(id, { reconstructor: UnorderedMap.reconstruct, - defaultValue: new UnorderedMap("i_" + id + "_"), + defaultValue: new UnorderedMap("i_" + id + "_"), }).get(account_id); } } From 8cb2bd0f1110f44b495731cb0ed9114a4f41e11f Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 25 Nov 2023 23:00:27 +0800 Subject: [PATCH 07/29] test: get subtype of UnorderedMap and nested UnorderedMap --- .../test-status-deserialize-class.ava.js | 18 ++++++++++++++++++ examples/src/status-deserialize-class.js | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index d167b379..5ed673de 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -85,4 +85,22 @@ test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text" await statusMessage.view("get_nested_efficient_recordes", { id: "2", account_id: bob.accountId }), "world" ); +}); + +test("View get_subtype_of_efficient_recordes", async (t) => { + const { statusMessage } = t.context.accounts; + + t.is( + await statusMessage.view("get_subtype_of_efficient_recordes", { }), + 'string' + ); +}); + +test("View get_subtype_of_nested_efficient_recordes", async (t) => { + const { statusMessage } = t.context.accounts; + + t.is( + JSON.stringify(await statusMessage.view("get_subtype_of_nested_efficient_recordes", { })), + '{"unorder_map":{"value":"string"}}' + ); }); \ No newline at end of file diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 46e75b46..f3b077ce 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -217,4 +217,20 @@ export class StatusDeserializeClass { defaultValue: new UnorderedMap("i_" + id + "_"), }).get(account_id); } + + @view({}) + get_subtype_of_efficient_recordes({ }) { + near.log(`get_subtype_of_efficient_recordes`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.efficient_recordes.subtype(); + } + + @view({}) + get_subtype_of_nested_efficient_recordes({ }) { + near.log(`get_subtype_of_nested_efficient_recordes`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.nested_efficient_recordes.subtype(); + } } From 7c2cfe676b090199c8eb1315431dc625c69b7bac Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 25 Nov 2023 23:54:25 +0800 Subject: [PATCH 08/29] feat: UnorderedMap remove reconstructor by add subtype and dynamic check subtype --- examples/src/status-deserialize-class.js | 2 -- .../near-sdk-js/lib/collections/unordered-map.d.ts | 1 + .../near-sdk-js/lib/collections/unordered-map.js | 10 ++++++++++ .../near-sdk-js/src/collections/unordered-map.ts | 12 ++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index f3b077ce..3d4ae4d0 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -189,7 +189,6 @@ export class StatusDeserializeClass { let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); inst.efficient_recordes.set(account_id, message); const nestedMap = inst.nested_efficient_recordes.get(id, { - reconstructor: UnorderedMap.reconstruct, defaultValue: new UnorderedMap("i_" + id + "_"), }); nestedMap.set(near.signerAccountId(), message); @@ -213,7 +212,6 @@ export class StatusDeserializeClass { let obj = deserialize(encode(this.messages)); let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); return inst.nested_efficient_recordes.get(id, { - reconstructor: UnorderedMap.reconstruct, defaultValue: new UnorderedMap("i_" + id + "_"), }).get(account_id); } diff --git a/packages/near-sdk-js/lib/collections/unordered-map.d.ts b/packages/near-sdk-js/lib/collections/unordered-map.d.ts index af50e1ae..c0d36760 100644 --- a/packages/near-sdk-js/lib/collections/unordered-map.d.ts +++ b/packages/near-sdk-js/lib/collections/unordered-map.d.ts @@ -21,6 +21,7 @@ export declare class UnorderedMap { * Checks whether the collection is empty. */ isEmpty(): boolean; + subtype(): any; /** * Get the data stored at the provided key. * diff --git a/packages/near-sdk-js/lib/collections/unordered-map.js b/packages/near-sdk-js/lib/collections/unordered-map.js index f155eae2..237e59eb 100644 --- a/packages/near-sdk-js/lib/collections/unordered-map.js +++ b/packages/near-sdk-js/lib/collections/unordered-map.js @@ -25,6 +25,9 @@ export class UnorderedMap { isEmpty() { return this._keys.isEmpty(); } + // eslint-disable-next-line @typescript-eslint/no-empty-function + subtype() { + } /** * Get the data stored at the provided key. * @@ -36,6 +39,13 @@ export class UnorderedMap { if (valueAndIndex === null) { return options?.defaultValue ?? null; } + if (options == undefined || (options.reconstructor == undefined)) { + // eslint-disable-next-line no-prototype-builtins + if (this.subtype() != undefined && this.subtype().hasOwnProperty("unorder_map")) { + // @ts-ignore + options.reconstructor = UnorderedMap.reconstruct; + } + } const [value] = valueAndIndex; return getValueWithOptions(encode(value), options); } diff --git a/packages/near-sdk-js/src/collections/unordered-map.ts b/packages/near-sdk-js/src/collections/unordered-map.ts index 810e1630..47f56cc8 100644 --- a/packages/near-sdk-js/src/collections/unordered-map.ts +++ b/packages/near-sdk-js/src/collections/unordered-map.ts @@ -42,6 +42,11 @@ export class UnorderedMap { return this._keys.isEmpty(); } + // eslint-disable-next-line @typescript-eslint/no-empty-function + subtype(): any { + + } + /** * Get the data stored at the provided key. * @@ -57,6 +62,13 @@ export class UnorderedMap { if (valueAndIndex === null) { return options?.defaultValue ?? null; } + if (options == undefined || (options.reconstructor == undefined)) { + // eslint-disable-next-line no-prototype-builtins + if (this.subtype() != undefined && this.subtype().hasOwnProperty("unorder_map")) { + // @ts-ignore + options.reconstructor = UnorderedMap.reconstruct; + } + } const [value] = valueAndIndex; From 707054ec05071520949f526ab0d5245171737b8e Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Mon, 27 Nov 2023 01:05:43 +0800 Subject: [PATCH 09/29] feat: auto reconstruct for LookupMap --- .../test-status-deserialize-class.ava.js | 10 ++++ examples/src/status-deserialize-class.js | 48 +++++++++++++++++-- .../lib/collections/lookup-map.d.ts | 1 + .../near-sdk-js/lib/collections/lookup-map.js | 12 +++++ .../lib/collections/unordered-map.js | 14 ++++-- .../near-sdk-js/src/collections/lookup-map.ts | 14 ++++++ .../src/collections/unordered-map.ts | 13 +++-- 7 files changed, 103 insertions(+), 9 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index 5ed673de..9f304f50 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -85,6 +85,16 @@ test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text" await statusMessage.view("get_nested_efficient_recordes", { id: "2", account_id: bob.accountId }), "world" ); + + t.is( + await statusMessage.view("get_nested_lookup_recordes", { id: "1", account_id: bob.accountId }), + "hello" + ); + + t.is( + await statusMessage.view("get_nested_lookup_recordes", { id: "2", account_id: bob.accountId }), + "world" + ); }); test("View get_subtype_of_efficient_recordes", async (t) => { diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 3d4ae4d0..064c6760 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -1,4 +1,15 @@ -import {NearBindgen, call, view, near, UnorderedMap, serialize, decode, deserialize, encode} from "near-sdk-js"; +import { + NearBindgen, + call, + view, + near, + UnorderedMap, + serialize, + decode, + deserialize, + encode, + LookupMap +} from "near-sdk-js"; import lodash from "lodash-es"; function decode_obj2class(class_instance, obj) { @@ -41,7 +52,11 @@ function decode_obj2class(class_instance, obj) { } else if (ty !== undefined && ty.hasOwnProperty("unorder_set")) { // todo: imple } else if (ty !== undefined && ty.hasOwnProperty("lookup_map")) { - // todo: impl + class_instance[key].constructor.schema = ty; + let subtype_value = ty["lookup_map"]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + } } else if (ty !== undefined && ty.hasOwnProperty("lookup_set")) { // todo: impl } else { @@ -84,7 +99,8 @@ class InnerStatusDeserializeClass { car: Car, messages: {array: {value: 'string'}}, efficient_recordes: {unorder_map: {value: 'string'}}, - nested_efficient_recordes: {unorder_map: {value: { unorder_map: {value: 'string'}}}} + nested_efficient_recordes: {unorder_map: {value: { unorder_map: {value: 'string'}}}}, + nested_lookup_recordes: {unorder_map: {value: { lookup_map: {value: 'string'}}}}, }; constructor() { this.records = {}; @@ -94,6 +110,8 @@ class InnerStatusDeserializeClass { this.efficient_recordes = new UnorderedMap("a"); // id -> account_id -> message this.nested_efficient_recordes = new UnorderedMap("b"); + // id -> account_id -> message + this.nested_lookup_recordes = new UnorderedMap("c"); } } @@ -194,6 +212,12 @@ export class StatusDeserializeClass { nestedMap.set(near.signerAccountId(), message); inst.nested_efficient_recordes.set(id, nestedMap); + const nestedLookup = inst.nested_lookup_recordes.get(id, { + defaultValue: new LookupMap("li_" + id + "_"), + }); + nestedLookup.set(near.signerAccountId(), message); + inst.nested_lookup_recordes.set(id, nestedLookup); + let data = serialize(inst) this.messages = decode(data); } @@ -216,6 +240,16 @@ export class StatusDeserializeClass { }).get(account_id); } + @view({}) + get_nested_lookup_recordes({ account_id, id }) { + near.log(`get_nested_lookup_recordes for account_id ${account_id}, id ${id}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.nested_lookup_recordes.get(id, { + defaultValue: new LookupMap("li_" + id + "_"), + }).get(account_id); + } + @view({}) get_subtype_of_efficient_recordes({ }) { near.log(`get_subtype_of_efficient_recordes`); @@ -231,4 +265,12 @@ export class StatusDeserializeClass { let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); return inst.nested_efficient_recordes.subtype(); } + + @view({}) + get_subtype_of_nested_lookup_recordes({ }) { + near.log(`get_subtype_of_nested_lookup_recordes`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.nested_lookup_recordes.subtype(); + } } diff --git a/packages/near-sdk-js/lib/collections/lookup-map.d.ts b/packages/near-sdk-js/lib/collections/lookup-map.d.ts index 4bd45f5e..fff2958e 100644 --- a/packages/near-sdk-js/lib/collections/lookup-map.d.ts +++ b/packages/near-sdk-js/lib/collections/lookup-map.d.ts @@ -14,6 +14,7 @@ export declare class LookupMap { * @param key - The value for which to check the presence. */ containsKey(key: string): boolean; + subtype(): any; /** * Get the data stored at the provided key. * diff --git a/packages/near-sdk-js/lib/collections/lookup-map.js b/packages/near-sdk-js/lib/collections/lookup-map.js index 364049a6..ccecb636 100644 --- a/packages/near-sdk-js/lib/collections/lookup-map.js +++ b/packages/near-sdk-js/lib/collections/lookup-map.js @@ -19,6 +19,10 @@ export class LookupMap { const storageKey = this.keyPrefix + key; return near.storageHasKey(storageKey); } + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ + subtype() { + } /** * Get the data stored at the provided key. * @@ -28,6 +32,14 @@ export class LookupMap { get(key, options) { const storageKey = this.keyPrefix + key; const value = near.storageReadRaw(encode(storageKey)); + if (options == undefined || (options.reconstructor == undefined)) { + // eslint-disable-next-line no-prototype-builtins + if (this.subtype() != undefined && this.subtype().hasOwnProperty("lookup_map")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupMap.reconstruct; + } + } return getValueWithOptions(value, options); } /** diff --git a/packages/near-sdk-js/lib/collections/unordered-map.js b/packages/near-sdk-js/lib/collections/unordered-map.js index 237e59eb..472652b7 100644 --- a/packages/near-sdk-js/lib/collections/unordered-map.js +++ b/packages/near-sdk-js/lib/collections/unordered-map.js @@ -25,7 +25,8 @@ export class UnorderedMap { isEmpty() { return this._keys.isEmpty(); } - // eslint-disable-next-line @typescript-eslint/no-empty-function + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ subtype() { } /** @@ -39,11 +40,18 @@ export class UnorderedMap { if (valueAndIndex === null) { return options?.defaultValue ?? null; } - if (options == undefined || (options.reconstructor == undefined)) { + if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins - if (this.subtype() != undefined && this.subtype().hasOwnProperty("unorder_map")) { + if (this.subtype().hasOwnProperty("unorder_map")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore options.reconstructor = UnorderedMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty("lookup_map")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupMap.reconstruct; } } const [value] = valueAndIndex; diff --git a/packages/near-sdk-js/src/collections/lookup-map.ts b/packages/near-sdk-js/src/collections/lookup-map.ts index 8002449e..064d32ac 100644 --- a/packages/near-sdk-js/src/collections/lookup-map.ts +++ b/packages/near-sdk-js/src/collections/lookup-map.ts @@ -25,6 +25,12 @@ export class LookupMap { return near.storageHasKey(storageKey); } + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ + subtype(): any { + + } + /** * Get the data stored at the provided key. * @@ -37,6 +43,14 @@ export class LookupMap { ): DataType | null { const storageKey = this.keyPrefix + key; const value = near.storageReadRaw(encode(storageKey)); + if (options == undefined || (options.reconstructor == undefined)) { + // eslint-disable-next-line no-prototype-builtins + if (this.subtype() != undefined && this.subtype().hasOwnProperty("lookup_map")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupMap.reconstruct; + } + } return getValueWithOptions(value, options); } diff --git a/packages/near-sdk-js/src/collections/unordered-map.ts b/packages/near-sdk-js/src/collections/unordered-map.ts index 47f56cc8..eea9562e 100644 --- a/packages/near-sdk-js/src/collections/unordered-map.ts +++ b/packages/near-sdk-js/src/collections/unordered-map.ts @@ -42,7 +42,8 @@ export class UnorderedMap { return this._keys.isEmpty(); } - // eslint-disable-next-line @typescript-eslint/no-empty-function + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ subtype(): any { } @@ -62,11 +63,17 @@ export class UnorderedMap { if (valueAndIndex === null) { return options?.defaultValue ?? null; } - if (options == undefined || (options.reconstructor == undefined)) { + if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins - if (this.subtype() != undefined && this.subtype().hasOwnProperty("unorder_map")) { + if (this.subtype().hasOwnProperty("unorder_map")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore options.reconstructor = UnorderedMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty("lookup_map")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupMap.reconstruct; } } From 95e8e0ffaf33ad89beb0fa297793ddf450b47290 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Tue, 28 Nov 2023 22:55:30 +0800 Subject: [PATCH 10/29] feat: refact set_reconstructor and modulize --- .../test-status-deserialize-class.ava.js | 2 +- examples/src/status-deserialize-class.js | 22 ++++---- .../near-sdk-js/lib/collections/index.d.ts | 1 + packages/near-sdk-js/lib/collections/index.js | 1 + .../near-sdk-js/lib/collections/lookup-map.js | 33 +++++++++++- .../near-sdk-js/lib/collections/subtype.d.ts | 10 ++++ .../near-sdk-js/lib/collections/subtype.js | 51 ++++++++++++++++++ .../lib/collections/unordered-map.d.ts | 4 +- .../lib/collections/unordered-map.js | 23 ++------ .../near-sdk-js/lib/collections/vector.d.ts | 1 + .../near-sdk-js/lib/collections/vector.js | 41 +++++++++++++++ packages/near-sdk-js/src/collections/index.ts | 1 + .../near-sdk-js/src/collections/lookup-map.ts | 29 ++++++++++- .../near-sdk-js/src/collections/subtype.ts | 52 +++++++++++++++++++ .../src/collections/unordered-map.ts | 24 ++------- .../near-sdk-js/src/collections/vector.ts | 40 +++++++++++++- 16 files changed, 277 insertions(+), 58 deletions(-) create mode 100644 packages/near-sdk-js/lib/collections/subtype.d.ts create mode 100644 packages/near-sdk-js/lib/collections/subtype.js create mode 100644 packages/near-sdk-js/src/collections/subtype.ts diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index 9f304f50..0d92d31d 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -111,6 +111,6 @@ test("View get_subtype_of_nested_efficient_recordes", async (t) => { t.is( JSON.stringify(await statusMessage.view("get_subtype_of_nested_efficient_recordes", { })), - '{"unorder_map":{"value":"string"}}' + '{"unordered_map":{"value":"string"}}' ); }); \ No newline at end of file diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 064c6760..2e2e23af 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -8,7 +8,7 @@ import { decode, deserialize, encode, - LookupMap + LookupMap, UNORDERED_MAP_SCHE, VECTOR_SCHE, UNORDERED_SET_SCHE, LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE } from "near-sdk-js"; import lodash from "lodash-es"; @@ -40,24 +40,24 @@ function decode_obj2class(class_instance, obj) { class_instance[key].push(decode_obj2class(new ty["array"]["value"](), value[k])); } } - } else if (ty !== undefined && ty.hasOwnProperty("unorder_map")) { + } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_MAP_SCHE)) { class_instance[key].constructor.schema = ty; - let subtype_value = ty["unorder_map"]["value"]; + let subtype_value = ty[UNORDERED_MAP_SCHE]["value"]; class_instance[key].subtype = function () { return subtype_value; } // class_instance[key] = decode_obj2class(class_instance[key], obj[key]); - } else if (ty !== undefined && ty.hasOwnProperty("vector")) { + } else if (ty !== undefined && ty.hasOwnProperty(VECTOR_SCHE)) { // todo: imple - } else if (ty !== undefined && ty.hasOwnProperty("unorder_set")) { + } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_SET_SCHE)) { // todo: imple - } else if (ty !== undefined && ty.hasOwnProperty("lookup_map")) { + } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_MAP_SCHE)) { class_instance[key].constructor.schema = ty; - let subtype_value = ty["lookup_map"]["value"]; + let subtype_value = ty[LOOKUP_MAP_SCHE]["value"]; class_instance[key].subtype = function () { return subtype_value; } - } else if (ty !== undefined && ty.hasOwnProperty("lookup_set")) { + } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_SET_SCHE)) { // todo: impl } else { // normal case @@ -98,9 +98,9 @@ class InnerStatusDeserializeClass { records: {map: { key: 'string', value: 'string' }}, car: Car, messages: {array: {value: 'string'}}, - efficient_recordes: {unorder_map: {value: 'string'}}, - nested_efficient_recordes: {unorder_map: {value: { unorder_map: {value: 'string'}}}}, - nested_lookup_recordes: {unorder_map: {value: { lookup_map: {value: 'string'}}}}, + efficient_recordes: {unordered_map: {value: 'string'}}, + nested_efficient_recordes: {unordered_map: {value: { unordered_map: {value: 'string'}}}}, + nested_lookup_recordes: {unordered_map: {value: { lookup_map: {value: 'string'}}}}, }; constructor() { this.records = {}; diff --git a/packages/near-sdk-js/lib/collections/index.d.ts b/packages/near-sdk-js/lib/collections/index.d.ts index 75f12247..1e590180 100644 --- a/packages/near-sdk-js/lib/collections/index.d.ts +++ b/packages/near-sdk-js/lib/collections/index.d.ts @@ -3,3 +3,4 @@ export * from "./lookup-set"; export * from "./unordered-map"; export * from "./unordered-set"; export * from "./vector"; +export * from "./subtype"; diff --git a/packages/near-sdk-js/lib/collections/index.js b/packages/near-sdk-js/lib/collections/index.js index 75f12247..1e590180 100644 --- a/packages/near-sdk-js/lib/collections/index.js +++ b/packages/near-sdk-js/lib/collections/index.js @@ -3,3 +3,4 @@ export * from "./lookup-set"; export * from "./unordered-map"; export * from "./unordered-set"; export * from "./vector"; +export * from "./subtype"; diff --git a/packages/near-sdk-js/lib/collections/lookup-map.js b/packages/near-sdk-js/lib/collections/lookup-map.js index ccecb636..00418539 100644 --- a/packages/near-sdk-js/lib/collections/lookup-map.js +++ b/packages/near-sdk-js/lib/collections/lookup-map.js @@ -1,5 +1,10 @@ import * as near from "../api"; import { getValueWithOptions, serializeValueWithOptions, encode, } from "../utils"; +import { UnorderedMap } from "./unordered-map"; +import { LookupSet } from "./lookup-set"; +import { UnorderedSet } from "./unordered-set"; +import { Vector } from "./vector"; +import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE } from "./subtype"; /** * A lookup map that stores data in NEAR storage. */ @@ -32,12 +37,36 @@ export class LookupMap { get(key, options) { const storageKey = this.keyPrefix + key; const value = near.storageReadRaw(encode(storageKey)); - if (options == undefined || (options.reconstructor == undefined)) { + if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins - if (this.subtype() != undefined && this.subtype().hasOwnProperty("lookup_map")) { + if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore options.reconstructor = LookupMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = Vector.reconstruct; } } return getValueWithOptions(value, options); diff --git a/packages/near-sdk-js/lib/collections/subtype.d.ts b/packages/near-sdk-js/lib/collections/subtype.d.ts new file mode 100644 index 00000000..7f279c45 --- /dev/null +++ b/packages/near-sdk-js/lib/collections/subtype.d.ts @@ -0,0 +1,10 @@ +import { GetOptions } from "../types/collections"; +export declare const LOOKUP_MAP_SCHE = "lookup_map"; +export declare const LOOKUP_SET_SCHE = "lookup_set"; +export declare const UNORDERED_MAP_SCHE = "unordered_map"; +export declare const UNORDERED_SET_SCHE = "unordered_set"; +export declare const VECTOR_SCHE = "vector"; +export declare abstract class SubType { + subtype(): any; + set_reconstructor(options?: Omit, "serializer">): Omit, "serializer">; +} diff --git a/packages/near-sdk-js/lib/collections/subtype.js b/packages/near-sdk-js/lib/collections/subtype.js new file mode 100644 index 00000000..6083e1f2 --- /dev/null +++ b/packages/near-sdk-js/lib/collections/subtype.js @@ -0,0 +1,51 @@ +import { LookupMap } from "./lookup-map"; +import { LookupSet } from "./lookup-set"; +import { UnorderedSet } from "./unordered-set"; +import { Vector } from "./vector"; +import { UnorderedMap } from "./unordered-map"; +export const LOOKUP_MAP_SCHE = "lookup_map"; +export const LOOKUP_SET_SCHE = "lookup_set"; +export const UNORDERED_MAP_SCHE = "unordered_map"; +export const UNORDERED_SET_SCHE = "unordered_set"; +export const VECTOR_SCHE = "vector"; +export class SubType { + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ + subtype() { + } + set_reconstructor(options) { + if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + // eslint-disable-next-line no-prototype-builtins + if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = Vector.reconstruct; + } + } + return options; + } +} diff --git a/packages/near-sdk-js/lib/collections/unordered-map.d.ts b/packages/near-sdk-js/lib/collections/unordered-map.d.ts index c0d36760..f2a4e3fa 100644 --- a/packages/near-sdk-js/lib/collections/unordered-map.d.ts +++ b/packages/near-sdk-js/lib/collections/unordered-map.d.ts @@ -1,11 +1,12 @@ import { Vector } from "./vector"; import { LookupMap } from "./lookup-map"; import { GetOptions } from "../types/collections"; +import { SubType } from "./subtype"; declare type ValueAndIndex = [value: string, index: number]; /** * An unordered map that stores data in NEAR storage. */ -export declare class UnorderedMap { +export declare class UnorderedMap extends SubType { readonly prefix: string; readonly _keys: Vector; readonly values: LookupMap; @@ -21,7 +22,6 @@ export declare class UnorderedMap { * Checks whether the collection is empty. */ isEmpty(): boolean; - subtype(): any; /** * Get the data stored at the provided key. * diff --git a/packages/near-sdk-js/lib/collections/unordered-map.js b/packages/near-sdk-js/lib/collections/unordered-map.js index 472652b7..51bc48c7 100644 --- a/packages/near-sdk-js/lib/collections/unordered-map.js +++ b/packages/near-sdk-js/lib/collections/unordered-map.js @@ -1,14 +1,16 @@ import { assert, ERR_INCONSISTENT_STATE, getValueWithOptions, serializeValueWithOptions, encode, decode, } from "../utils"; import { Vector, VectorIterator } from "./vector"; import { LookupMap } from "./lookup-map"; +import { SubType } from "./subtype"; /** * An unordered map that stores data in NEAR storage. */ -export class UnorderedMap { +export class UnorderedMap extends SubType { /** * @param prefix - The byte prefix to use when storing elements inside this collection. */ constructor(prefix) { + super(); this.prefix = prefix; this._keys = new Vector(`${prefix}u`); // intentional different prefix with old UnorderedMap this.values = new LookupMap(`${prefix}m`); @@ -25,10 +27,6 @@ export class UnorderedMap { isEmpty() { return this._keys.isEmpty(); } - /* eslint-disable @typescript-eslint/no-explicit-any */ - /* eslint-disable @typescript-eslint/no-empty-function */ - subtype() { - } /** * Get the data stored at the provided key. * @@ -40,20 +38,7 @@ export class UnorderedMap { if (valueAndIndex === null) { return options?.defaultValue ?? null; } - if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { - // eslint-disable-next-line no-prototype-builtins - if (this.subtype().hasOwnProperty("unorder_map")) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty("lookup_map")) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupMap.reconstruct; - } - } + options = this.set_reconstructor(options); const [value] = valueAndIndex; return getValueWithOptions(encode(value), options); } diff --git a/packages/near-sdk-js/lib/collections/vector.d.ts b/packages/near-sdk-js/lib/collections/vector.d.ts index 5261f78d..046798a4 100644 --- a/packages/near-sdk-js/lib/collections/vector.d.ts +++ b/packages/near-sdk-js/lib/collections/vector.d.ts @@ -15,6 +15,7 @@ export declare class Vector { * Checks whether the collection is empty. */ isEmpty(): boolean; + subtype(): any; /** * Get the data stored at the provided index. * diff --git a/packages/near-sdk-js/lib/collections/vector.js b/packages/near-sdk-js/lib/collections/vector.js index 79bc8b85..10bc644c 100644 --- a/packages/near-sdk-js/lib/collections/vector.js +++ b/packages/near-sdk-js/lib/collections/vector.js @@ -1,5 +1,10 @@ import * as near from "../api"; import { assert, getValueWithOptions, serializeValueWithOptions, ERR_INCONSISTENT_STATE, ERR_INDEX_OUT_OF_BOUNDS, str, bytes, } from "../utils"; +import { UnorderedMap } from "./unordered-map"; +import { LookupMap } from "./lookup-map"; +import { LookupSet } from "./lookup-set"; +import { UnorderedSet } from "./unordered-set"; +import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE } from "./subtype"; function indexToKey(prefix, index) { const data = new Uint32Array([index]); const array = new Uint8Array(data.buffer); @@ -25,6 +30,10 @@ export class Vector { isEmpty() { return this.length === 0; } + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ + subtype() { + } /** * Get the data stored at the provided index. * @@ -37,6 +46,38 @@ export class Vector { } const storageKey = indexToKey(this.prefix, index); const value = near.storageReadRaw(bytes(storageKey)); + if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + // eslint-disable-next-line no-prototype-builtins + if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } + else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = Vector.reconstruct; + } + } return getValueWithOptions(value, options); } /** diff --git a/packages/near-sdk-js/src/collections/index.ts b/packages/near-sdk-js/src/collections/index.ts index 75f12247..1e590180 100644 --- a/packages/near-sdk-js/src/collections/index.ts +++ b/packages/near-sdk-js/src/collections/index.ts @@ -3,3 +3,4 @@ export * from "./lookup-set"; export * from "./unordered-map"; export * from "./unordered-set"; export * from "./vector"; +export * from "./subtype"; diff --git a/packages/near-sdk-js/src/collections/lookup-map.ts b/packages/near-sdk-js/src/collections/lookup-map.ts index 064d32ac..158d0380 100644 --- a/packages/near-sdk-js/src/collections/lookup-map.ts +++ b/packages/near-sdk-js/src/collections/lookup-map.ts @@ -5,6 +5,11 @@ import { serializeValueWithOptions, encode, } from "../utils"; +import {UnorderedMap} from "./unordered-map"; +import {LookupSet} from "./lookup-set"; +import {UnorderedSet} from "./unordered-set"; +import {Vector} from "./vector"; +import {LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE} from "./subtype"; /** * A lookup map that stores data in NEAR storage. @@ -43,12 +48,32 @@ export class LookupMap { ): DataType | null { const storageKey = this.keyPrefix + key; const value = near.storageReadRaw(encode(storageKey)); - if (options == undefined || (options.reconstructor == undefined)) { + if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins - if (this.subtype() != undefined && this.subtype().hasOwnProperty("lookup_map")) { + if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore options.reconstructor = LookupMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = Vector.reconstruct; } } diff --git a/packages/near-sdk-js/src/collections/subtype.ts b/packages/near-sdk-js/src/collections/subtype.ts new file mode 100644 index 00000000..53c69e28 --- /dev/null +++ b/packages/near-sdk-js/src/collections/subtype.ts @@ -0,0 +1,52 @@ +import {GetOptions} from "../types/collections"; +import {LookupMap} from "./lookup-map"; +import {LookupSet} from "./lookup-set"; +import {UnorderedSet} from "./unordered-set"; +import {Vector} from "./vector"; +import {UnorderedMap} from "./unordered-map"; + +export const LOOKUP_MAP_SCHE = "lookup_map"; +export const LOOKUP_SET_SCHE = "lookup_set"; +export const UNORDERED_MAP_SCHE = "unordered_map"; +export const UNORDERED_SET_SCHE = "unordered_set"; +export const VECTOR_SCHE = "vector"; + +export abstract class SubType { + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ + subtype(): any { + + } + + set_reconstructor(options?: Omit, "serializer">): Omit, "serializer"> { + if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + // eslint-disable-next-line no-prototype-builtins + if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = Vector.reconstruct; + } + } + return options; + } +} \ No newline at end of file diff --git a/packages/near-sdk-js/src/collections/unordered-map.ts b/packages/near-sdk-js/src/collections/unordered-map.ts index eea9562e..183d59da 100644 --- a/packages/near-sdk-js/src/collections/unordered-map.ts +++ b/packages/near-sdk-js/src/collections/unordered-map.ts @@ -10,13 +10,14 @@ import { import { Vector, VectorIterator } from "./vector"; import { LookupMap } from "./lookup-map"; import { GetOptions } from "../types/collections"; +import {SubType} from "./subtype"; type ValueAndIndex = [value: string, index: number]; /** * An unordered map that stores data in NEAR storage. */ -export class UnorderedMap { +export class UnorderedMap extends SubType { readonly _keys: Vector; readonly values: LookupMap; @@ -24,6 +25,7 @@ export class UnorderedMap { * @param prefix - The byte prefix to use when storing elements inside this collection. */ constructor(readonly prefix: string) { + super(); this._keys = new Vector(`${prefix}u`); // intentional different prefix with old UnorderedMap this.values = new LookupMap(`${prefix}m`); } @@ -42,12 +44,6 @@ export class UnorderedMap { return this._keys.isEmpty(); } - /* eslint-disable @typescript-eslint/no-explicit-any */ - /* eslint-disable @typescript-eslint/no-empty-function */ - subtype(): any { - - } - /** * Get the data stored at the provided key. * @@ -63,19 +59,7 @@ export class UnorderedMap { if (valueAndIndex === null) { return options?.defaultValue ?? null; } - if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { - // eslint-disable-next-line no-prototype-builtins - if (this.subtype().hasOwnProperty("unorder_map")) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty("lookup_map")) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupMap.reconstruct; - } - } + options = this.set_reconstructor(options); const [value] = valueAndIndex; diff --git a/packages/near-sdk-js/src/collections/vector.ts b/packages/near-sdk-js/src/collections/vector.ts index 698613a3..a0640027 100644 --- a/packages/near-sdk-js/src/collections/vector.ts +++ b/packages/near-sdk-js/src/collections/vector.ts @@ -9,6 +9,11 @@ import { bytes, } from "../utils"; import { GetOptions } from "../types/collections"; +import {UnorderedMap} from "./unordered-map"; +import {LookupMap} from "./lookup-map"; +import {LookupSet} from "./lookup-set"; +import {UnorderedSet} from "./unordered-set"; +import {LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE} from "./subtype"; function indexToKey(prefix: string, index: number): string { const data = new Uint32Array([index]); @@ -36,6 +41,12 @@ export class Vector { return this.length === 0; } + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ + subtype(): any { + + } + /** * Get the data stored at the provided index. * @@ -52,7 +63,34 @@ export class Vector { const storageKey = indexToKey(this.prefix, index); const value = near.storageReadRaw(bytes(storageKey)); - + if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + // eslint-disable-next-line no-prototype-builtins + if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = Vector.reconstruct; + } + } return getValueWithOptions(value, options); } From 10836816e32b513bb5e4340186bdf4a4b083a58d Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Wed, 29 Nov 2023 23:18:02 +0800 Subject: [PATCH 11/29] feat: vector nest other collections and test, decode and test UnorderedSet --- .../test-status-deserialize-class.ava.js | 15 ++++ examples/src/status-deserialize-class.js | 82 +++++++++++++++++-- .../near-sdk-js/lib/collections/lookup-map.js | 5 +- .../near-sdk-js/lib/collections/subtype.js | 5 +- .../near-sdk-js/lib/collections/vector.d.ts | 1 + .../near-sdk-js/lib/collections/vector.js | 33 +++++--- .../near-sdk-js/src/collections/lookup-map.ts | 5 +- .../near-sdk-js/src/collections/subtype.ts | 5 +- .../near-sdk-js/src/collections/vector.ts | 42 ++++++---- 9 files changed, 155 insertions(+), 38 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index 0d92d31d..aad7c3e5 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -95,6 +95,21 @@ test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text" await statusMessage.view("get_nested_lookup_recordes", { id: "2", account_id: bob.accountId }), "world" ); + + t.is( + await statusMessage.view("get_vector_nested_group", { idx: 0, account_id: bob.accountId }), + "world" + ); + + t.is( + await statusMessage.view("get_lookup_nested_vec", { account_id: bob.accountId, idx: 1 }), + "world" + ); + + t.is( + await statusMessage.view("get_is_contains_user", { account_id: bob.accountId}), + true + ); }); test("View get_subtype_of_efficient_recordes", async (t) => { diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 2e2e23af..ff36a52e 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -8,7 +8,14 @@ import { decode, deserialize, encode, - LookupMap, UNORDERED_MAP_SCHE, VECTOR_SCHE, UNORDERED_SET_SCHE, LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE + LookupMap, + UNORDERED_MAP_SCHE, + VECTOR_SCHE, + UNORDERED_SET_SCHE, + LOOKUP_MAP_SCHE, + LOOKUP_SET_SCHE, + Vector, + UnorderedSet } from "near-sdk-js"; import lodash from "lodash-es"; @@ -41,16 +48,26 @@ function decode_obj2class(class_instance, obj) { } } } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_MAP_SCHE)) { + class_instance[key]._keys.length = obj[key]._keys.length; class_instance[key].constructor.schema = ty; let subtype_value = ty[UNORDERED_MAP_SCHE]["value"]; class_instance[key].subtype = function () { return subtype_value; } - // class_instance[key] = decode_obj2class(class_instance[key], obj[key]); } else if (ty !== undefined && ty.hasOwnProperty(VECTOR_SCHE)) { - // todo: imple + class_instance[key].length = obj[key].length; + class_instance[key].constructor.schema = ty; + let subtype_value = ty[VECTOR_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + } } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_SET_SCHE)) { - // todo: imple + class_instance[key]._elements.length = obj[key]._elements.length; + class_instance[key].constructor.schema = ty; + let subtype_value = ty[UNORDERED_SET_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + } } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_MAP_SCHE)) { class_instance[key].constructor.schema = ty; let subtype_value = ty[LOOKUP_MAP_SCHE]["value"]; @@ -101,6 +118,9 @@ class InnerStatusDeserializeClass { efficient_recordes: {unordered_map: {value: 'string'}}, nested_efficient_recordes: {unordered_map: {value: { unordered_map: {value: 'string'}}}}, nested_lookup_recordes: {unordered_map: {value: { lookup_map: {value: 'string'}}}}, + vector_nested_group: {vector: {value: { lookup_map: {value: 'string'}}}}, + lookup_nest_vec: {lookup_map: {value: { vector: { value: 'string' }}}}, + unordered_set: {unordered_set: {value: 'string'}}, }; constructor() { this.records = {}; @@ -112,6 +132,11 @@ class InnerStatusDeserializeClass { this.nested_efficient_recordes = new UnorderedMap("b"); // id -> account_id -> message this.nested_lookup_recordes = new UnorderedMap("c"); + // index -> account_id -> message + this.vector_nested_group = new Vector("d"); + // account_id -> index -> message + this.lookup_nest_vec = new LookupMap("e"); + this.unordered_set = new UnorderedSet("f"); } } @@ -209,15 +234,34 @@ export class StatusDeserializeClass { const nestedMap = inst.nested_efficient_recordes.get(id, { defaultValue: new UnorderedMap("i_" + id + "_"), }); - nestedMap.set(near.signerAccountId(), message); + nestedMap.set(account_id, message); inst.nested_efficient_recordes.set(id, nestedMap); const nestedLookup = inst.nested_lookup_recordes.get(id, { defaultValue: new LookupMap("li_" + id + "_"), }); - nestedLookup.set(near.signerAccountId(), message); + nestedLookup.set(account_id, message); inst.nested_lookup_recordes.set(id, nestedLookup); + // vector_nested_group: {vector: {value: { lookup_map: {value: 'string'}}}}, + const vecNestedLookup = inst.vector_nested_group.get(0, { + defaultValue: new LookupMap("di_0_"), + }); + vecNestedLookup.set(account_id, message); + if (inst.vector_nested_group.isEmpty()) { + inst.vector_nested_group.push(vecNestedLookup); + } else { + inst.vector_nested_group.replace(0, vecNestedLookup); + } + + const lookupNestVec = inst.lookup_nest_vec.get(account_id, { + defaultValue: new Vector("ei_" + account_id + "_"), + }); + lookupNestVec.push(message); + inst.lookup_nest_vec.set(account_id, lookupNestVec); + + inst.unordered_set.set(account_id); + let data = serialize(inst) this.messages = decode(data); } @@ -243,6 +287,7 @@ export class StatusDeserializeClass { @view({}) get_nested_lookup_recordes({ account_id, id }) { near.log(`get_nested_lookup_recordes for account_id ${account_id}, id ${id}`); + near.log(this.messages); let obj = deserialize(encode(this.messages)); let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); return inst.nested_lookup_recordes.get(id, { @@ -250,6 +295,31 @@ export class StatusDeserializeClass { }).get(account_id); } + @view({}) + get_vector_nested_group({ idx, account_id }) { + near.log(`get_vector_nested_group for idx ${idx}, account_id ${account_id}`); + near.log(this.messages); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.vector_nested_group.get(idx).get(account_id); + } + + @view({}) + get_lookup_nested_vec({ account_id, idx }) { + near.log(`get_looup_nested_vec for account_id ${account_id}, idx ${idx}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.lookup_nest_vec.get(account_id).get(idx); + } + + @view({}) + get_is_contains_user({ account_id }) { + near.log(`get_is_contains_user for account_id ${account_id}`); + let obj = deserialize(encode(this.messages)); + let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + return inst.unordered_set.contains(account_id); + } + @view({}) get_subtype_of_efficient_recordes({ }) { near.log(`get_subtype_of_efficient_recordes`); diff --git a/packages/near-sdk-js/lib/collections/lookup-map.js b/packages/near-sdk-js/lib/collections/lookup-map.js index 00418539..05b73753 100644 --- a/packages/near-sdk-js/lib/collections/lookup-map.js +++ b/packages/near-sdk-js/lib/collections/lookup-map.js @@ -37,7 +37,10 @@ export class LookupMap { get(key, options) { const storageKey = this.keyPrefix + key; const value = near.storageReadRaw(encode(storageKey)); - if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + if (options == undefined) { + options = {}; + } + if (((options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/near-sdk-js/lib/collections/subtype.js b/packages/near-sdk-js/lib/collections/subtype.js index 6083e1f2..504dc052 100644 --- a/packages/near-sdk-js/lib/collections/subtype.js +++ b/packages/near-sdk-js/lib/collections/subtype.js @@ -14,7 +14,10 @@ export class SubType { subtype() { } set_reconstructor(options) { - if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + if (options == undefined) { + options = {}; + } + if (((options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/near-sdk-js/lib/collections/vector.d.ts b/packages/near-sdk-js/lib/collections/vector.d.ts index 046798a4..6998913c 100644 --- a/packages/near-sdk-js/lib/collections/vector.d.ts +++ b/packages/near-sdk-js/lib/collections/vector.d.ts @@ -16,6 +16,7 @@ export declare class Vector { */ isEmpty(): boolean; subtype(): any; + set_reconstructor(options?: Omit, "serializer">): Omit, "serializer">; /** * Get the data stored at the provided index. * diff --git a/packages/near-sdk-js/lib/collections/vector.js b/packages/near-sdk-js/lib/collections/vector.js index 10bc644c..7e1dc82a 100644 --- a/packages/near-sdk-js/lib/collections/vector.js +++ b/packages/near-sdk-js/lib/collections/vector.js @@ -34,19 +34,11 @@ export class Vector { /* eslint-disable @typescript-eslint/no-empty-function */ subtype() { } - /** - * Get the data stored at the provided index. - * - * @param index - The index at which to look for the data. - * @param options - Options for retrieving the data. - */ - get(index, options) { - if (index >= this.length) { - return options?.defaultValue ?? null; + set_reconstructor(options) { + if (options == undefined) { + options = {}; } - const storageKey = indexToKey(this.prefix, index); - const value = near.storageReadRaw(bytes(storageKey)); - if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + if (((options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -78,6 +70,21 @@ export class Vector { options.reconstructor = Vector.reconstruct; } } + return options; + } + /** + * Get the data stored at the provided index. + * + * @param index - The index at which to look for the data. + * @param options - Options for retrieving the data. + */ + get(index, options) { + if (index >= this.length) { + return options?.defaultValue ?? null; + } + const storageKey = indexToKey(this.prefix, index); + const value = near.storageReadRaw(bytes(storageKey)); + options = this.set_reconstructor(options); return getValueWithOptions(value, options); } /** @@ -97,6 +104,7 @@ export class Vector { const last = this.pop(options); assert(near.storageWriteRaw(bytes(key), serializeValueWithOptions(last, options)), ERR_INCONSISTENT_STATE); const value = near.storageGetEvictedRaw(); + options = this.set_reconstructor(options); return getValueWithOptions(value, options); } /** @@ -138,6 +146,7 @@ export class Vector { const key = indexToKey(this.prefix, index); assert(near.storageWriteRaw(bytes(key), serializeValueWithOptions(element, options)), ERR_INCONSISTENT_STATE); const value = near.storageGetEvictedRaw(); + options = this.set_reconstructor(options); return getValueWithOptions(value, options); } /** diff --git a/packages/near-sdk-js/src/collections/lookup-map.ts b/packages/near-sdk-js/src/collections/lookup-map.ts index 158d0380..45dfe5b6 100644 --- a/packages/near-sdk-js/src/collections/lookup-map.ts +++ b/packages/near-sdk-js/src/collections/lookup-map.ts @@ -48,7 +48,10 @@ export class LookupMap { ): DataType | null { const storageKey = this.keyPrefix + key; const value = near.storageReadRaw(encode(storageKey)); - if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + if (options == undefined) { + options = {}; + } + if (((options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/near-sdk-js/src/collections/subtype.ts b/packages/near-sdk-js/src/collections/subtype.ts index 53c69e28..7f93db70 100644 --- a/packages/near-sdk-js/src/collections/subtype.ts +++ b/packages/near-sdk-js/src/collections/subtype.ts @@ -19,7 +19,10 @@ export abstract class SubType { } set_reconstructor(options?: Omit, "serializer">): Omit, "serializer"> { - if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + if (options == undefined) { + options = {}; + } + if (((options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/near-sdk-js/src/collections/vector.ts b/packages/near-sdk-js/src/collections/vector.ts index a0640027..3258b329 100644 --- a/packages/near-sdk-js/src/collections/vector.ts +++ b/packages/near-sdk-js/src/collections/vector.ts @@ -47,23 +47,11 @@ export class Vector { } - /** - * Get the data stored at the provided index. - * - * @param index - The index at which to look for the data. - * @param options - Options for retrieving the data. - */ - get( - index: number, - options?: Omit, "serializer"> - ): DataType | null { - if (index >= this.length) { - return options?.defaultValue ?? null; + set_reconstructor(options?: Omit, "serializer">): Omit, "serializer"> { + if (options == undefined) { + options = {}; } - - const storageKey = indexToKey(this.prefix, index); - const value = near.storageReadRaw(bytes(storageKey)); - if ((options == undefined || (options.reconstructor == undefined)) && this.subtype() != undefined) { + if (((options.reconstructor == undefined)) && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -91,6 +79,26 @@ export class Vector { options.reconstructor = Vector.reconstruct; } } + return options; + } + + /** + * Get the data stored at the provided index. + * + * @param index - The index at which to look for the data. + * @param options - Options for retrieving the data. + */ + get( + index: number, + options?: Omit, "serializer"> + ): DataType | null { + if (index >= this.length) { + return options?.defaultValue ?? null; + } + + const storageKey = indexToKey(this.prefix, index); + const value = near.storageReadRaw(bytes(storageKey)); + options = this.set_reconstructor(options); return getValueWithOptions(value, options); } @@ -121,6 +129,7 @@ export class Vector { ); const value = near.storageGetEvictedRaw(); + options = this.set_reconstructor(options); return getValueWithOptions(value, options); } @@ -189,6 +198,7 @@ export class Vector { ); const value = near.storageGetEvictedRaw(); + options = this.set_reconstructor(options); return getValueWithOptions(value, options); } From ac40cd9e0c50b68e6fc8ccaab92ac9e1aaf4956a Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Wed, 29 Nov 2023 23:44:35 +0800 Subject: [PATCH 12/29] feat: add lookupset and remove useless log --- examples/src/status-deserialize-class.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index ff36a52e..fe18bf80 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -24,13 +24,10 @@ function decode_obj2class(class_instance, obj) { for (key in obj) { // @ts-ignore let value = obj[key]; - near.log("decodeNested filed, key: ", key, " value: ", value, "instance[", key, "]: ", class_instance[key]); if (typeof value == 'object') { - near.log("object fields, object key: ", key, " value: ", value); // @ts-ignore let ty = class_instance.constructor.schema[key]; if (ty !== undefined && ty.hasOwnProperty("map")) { - near.log("map type"); for (let mkey in value) { if (ty["map"]["value"]==='string') { class_instance[key][mkey] = value[mkey]; @@ -39,7 +36,6 @@ function decode_obj2class(class_instance, obj) { } } } else if (ty !== undefined && ty.hasOwnProperty("array")) { - near.log("vector type"); for (let k in value) { if (ty["array"]["value"]==='string') { class_instance[key].push(value[k]); @@ -75,13 +71,16 @@ function decode_obj2class(class_instance, obj) { return subtype_value; } } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_SET_SCHE)) { - // todo: impl + class_instance[key].constructor.schema = ty; + let subtype_value = ty[LOOKUP_SET_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + } } else { - // normal case + // normal class class_instance[key].constructor.schema = class_instance.constructor.schema[key]; class_instance[key] = decode_obj2class(class_instance[key], obj[key]); } - near.log("instance[key] value", class_instance[key]); } } const instance_tmp = lodash.cloneDeep(class_instance); From 482ce687d54f812425e3bdb77c1bbf09d56928d3 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Fri, 1 Dec 2023 01:19:43 +0800 Subject: [PATCH 13/29] feat: mv decodeObj2class to sdk --- packages/near-sdk-js/lib/utils.d.ts | 1 + packages/near-sdk-js/lib/utils.js | 91 +++++++++++++++++++++++++++++ packages/near-sdk-js/package.json | 2 + packages/near-sdk-js/src/utils.ts | 83 ++++++++++++++++++++++++++ pnpm-lock.yaml | 7 ++- 5 files changed, 183 insertions(+), 1 deletion(-) diff --git a/packages/near-sdk-js/lib/utils.d.ts b/packages/near-sdk-js/lib/utils.d.ts index 3c53e54d..cf55baba 100644 --- a/packages/near-sdk-js/lib/utils.d.ts +++ b/packages/near-sdk-js/lib/utils.d.ts @@ -43,6 +43,7 @@ export declare function getValueWithOptions(value: Uint8Array | null, export declare function serializeValueWithOptions(value: DataType, { serializer }?: Pick, "serializer">): Uint8Array; export declare function serialize(valueToSerialize: unknown): Uint8Array; export declare function deserialize(valueToDeserialize: Uint8Array): unknown; +export declare function decodeObj2class(class_instance: any, obj: any): any; /** * Validates the Account ID according to the NEAR protocol * [Account ID rules](https://nomicon.io/DataStructures/Account#account-id-rules). diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index fd810fc7..750a42b9 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -1,3 +1,5 @@ +import lodash from "lodash"; +import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE } from "./collections"; // make PromiseIndex a nominal typing var PromiseIndexBrand; (function (PromiseIndexBrand) { @@ -90,6 +92,95 @@ export function deserialize(valueToDeserialize) { return value; }); } +export function decodeObj2class(class_instance, obj) { + let key; + for (key in obj) { + // @ts-ignore + let value = obj[key]; + if (typeof value == 'object') { + // @ts-ignore + const ty = class_instance.constructor.schema[key]; + // eslint-disable-next-line no-prototype-builtins + if (ty !== undefined && ty.hasOwnProperty("map")) { + for (const mkey in value) { + if (ty["map"]["value"] === 'string') { + class_instance[key][mkey] = value[mkey]; + } + else { + class_instance[key][mkey] = decodeObj2class(new ty["map"]["value"](), value[mkey]); + } + } + // eslint-disable-next-line no-prototype-builtins + } + else if (ty !== undefined && ty.hasOwnProperty("array")) { + for (const k in value) { + if (ty["array"]["value"] === 'string') { + class_instance[key].push(value[k]); + } + else { + class_instance[key].push(decodeObj2class(new ty["array"]["value"](), value[k])); + } + } + // eslint-disable-next-line no-prototype-builtins + } + else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_MAP_SCHE)) { + class_instance[key]._keys.length = obj[key]._keys.length; + class_instance[key].constructor.schema = ty; + const subtype_value = ty[UNORDERED_MAP_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + }; + // eslint-disable-next-line no-prototype-builtins + } + else if (ty !== undefined && ty.hasOwnProperty(VECTOR_SCHE)) { + class_instance[key].length = obj[key].length; + class_instance[key].constructor.schema = ty; + const subtype_value = ty[VECTOR_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + }; + // eslint-disable-next-line no-prototype-builtins + } + else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_SET_SCHE)) { + class_instance[key]._elements.length = obj[key]._elements.length; + class_instance[key].constructor.schema = ty; + const subtype_value = ty[UNORDERED_SET_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + }; + // eslint-disable-next-line no-prototype-builtins + } + else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_MAP_SCHE)) { + class_instance[key].constructor.schema = ty; + const subtype_value = ty[LOOKUP_MAP_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + }; + // eslint-disable-next-line no-prototype-builtins + } + else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_SET_SCHE)) { + class_instance[key].constructor.schema = ty; + const subtype_value = ty[LOOKUP_SET_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + }; + } + else { + // normal class + class_instance[key].constructor.schema = class_instance.constructor.schema[key]; + class_instance[key] = decodeObj2class(class_instance[key], obj[key]); + } + } + } + const instance_tmp = lodash.cloneDeep(class_instance); + class_instance = Object.assign(class_instance, obj); + for (key in obj) { + if (typeof class_instance[key] == 'object') { + class_instance[key] = instance_tmp[key]; + } + } + return class_instance; +} /** * Validates the Account ID according to the NEAR protocol * [Account ID rules](https://nomicon.io/DataStructures/Account#account-id-rules). diff --git a/packages/near-sdk-js/package.json b/packages/near-sdk-js/package.json index 8cb25eae..424fc104 100644 --- a/packages/near-sdk-js/package.json +++ b/packages/near-sdk-js/package.json @@ -46,6 +46,7 @@ "commander": "^9.4.1", "eslint": "^8.20.0", "json-schema": "0.4.0", + "lodash": "^4.17.21", "near-abi": "^0.1.0", "near-typescript-json-schema": "0.55.0", "rollup": "^2.61.1", @@ -69,6 +70,7 @@ "@types/babel__core": "^7.1.19", "@types/babel__traverse": "^7.18.1", "@types/eslint": "^8.4.6", + "@types/lodash": "^4.14.202", "@types/node": "^17.0.38", "@types/rollup": "^0.54.0", "@types/signale": "^1.4.4", diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index ab5ebbe0..25f837ba 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -1,4 +1,6 @@ import { GetOptions } from "./types/collections"; +import lodash from "lodash"; +import {LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE} from "./collections"; export interface Env { uint8array_to_latin1_string(a: Uint8Array): string; @@ -147,6 +149,87 @@ export function deserialize(valueToDeserialize: Uint8Array): unknown { }); } +export function decodeObj2class(class_instance, obj) { + let key; + for (key in obj) { + // @ts-ignore + let value = obj[key]; + if (typeof value == 'object') { + // @ts-ignore + const ty = class_instance.constructor.schema[key]; + // eslint-disable-next-line no-prototype-builtins + if (ty !== undefined && ty.hasOwnProperty("map")) { + for (const mkey in value) { + if (ty["map"]["value"]==='string') { + class_instance[key][mkey] = value[mkey]; + } else { + class_instance[key][mkey] = decodeObj2class(new ty["map"]["value"](), value[mkey]); + } + } + // eslint-disable-next-line no-prototype-builtins + } else if (ty !== undefined && ty.hasOwnProperty("array")) { + for (const k in value) { + if (ty["array"]["value"]==='string') { + class_instance[key].push(value[k]); + } else { + class_instance[key].push(decodeObj2class(new ty["array"]["value"](), value[k])); + } + } + // eslint-disable-next-line no-prototype-builtins + } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_MAP_SCHE)) { + class_instance[key]._keys.length = obj[key]._keys.length; + class_instance[key].constructor.schema = ty; + const subtype_value = ty[UNORDERED_MAP_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + } + // eslint-disable-next-line no-prototype-builtins + } else if (ty !== undefined && ty.hasOwnProperty(VECTOR_SCHE)) { + class_instance[key].length = obj[key].length; + class_instance[key].constructor.schema = ty; + const subtype_value = ty[VECTOR_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + } + // eslint-disable-next-line no-prototype-builtins + } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_SET_SCHE)) { + class_instance[key]._elements.length = obj[key]._elements.length; + class_instance[key].constructor.schema = ty; + const subtype_value = ty[UNORDERED_SET_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + } + // eslint-disable-next-line no-prototype-builtins + } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_MAP_SCHE)) { + class_instance[key].constructor.schema = ty; + const subtype_value = ty[LOOKUP_MAP_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + } + // eslint-disable-next-line no-prototype-builtins + } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_SET_SCHE)) { + class_instance[key].constructor.schema = ty; + const subtype_value = ty[LOOKUP_SET_SCHE]["value"]; + class_instance[key].subtype = function () { + return subtype_value; + } + } else { + // normal class + class_instance[key].constructor.schema = class_instance.constructor.schema[key]; + class_instance[key] = decodeObj2class(class_instance[key], obj[key]); + } + } + } + const instance_tmp = lodash.cloneDeep(class_instance); + class_instance = Object.assign(class_instance, obj); + for (key in obj) { + if (typeof class_instance[key] == 'object') { + class_instance[key] = instance_tmp[key]; + } + } + return class_instance; +} + /** * Validates the Account ID according to the NEAR protocol * [Account ID rules](https://nomicon.io/DataStructures/Account#account-id-rules). diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a94e0a46..ab7cbcaf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,9 @@ importers: json-schema: specifier: 0.4.0 version: 0.4.0 + lodash: + specifier: ^4.17.21 + version: 4.17.21 near-abi: specifier: ^0.1.0 version: 0.1.1 @@ -155,6 +158,9 @@ importers: '@types/eslint': specifier: ^8.4.6 version: 8.44.7 + '@types/lodash': + specifier: ^4.14.202 + version: 4.14.202 '@types/node': specifier: ^17.0.38 version: 17.0.45 @@ -2570,7 +2576,6 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true /lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} From 564440ce1a561f51f30b8783faaa4891616a2c47 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sun, 3 Dec 2023 01:41:26 +0800 Subject: [PATCH 14/29] feat: decode subtype recursively --- examples/src/status-deserialize-class.js | 114 +++--------------- .../near-sdk-js/lib/collections/lookup-map.js | 6 +- .../lib/collections/unordered-map.d.ts | 1 + .../lib/collections/unordered-map.js | 13 +- .../near-sdk-js/lib/collections/vector.js | 8 +- packages/near-sdk-js/lib/utils.d.ts | 2 +- packages/near-sdk-js/lib/utils.js | 43 ++++++- packages/near-sdk-js/package.json | 1 + .../near-sdk-js/src/collections/lookup-map.ts | 6 +- .../src/collections/unordered-map.ts | 15 ++- .../near-sdk-js/src/collections/vector.ts | 8 +- packages/near-sdk-js/src/utils.ts | 42 ++++++- pnpm-lock.yaml | 3 + 13 files changed, 135 insertions(+), 127 deletions(-) diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index fe18bf80..c7223c55 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -9,91 +9,11 @@ import { deserialize, encode, LookupMap, - UNORDERED_MAP_SCHE, - VECTOR_SCHE, - UNORDERED_SET_SCHE, - LOOKUP_MAP_SCHE, - LOOKUP_SET_SCHE, Vector, - UnorderedSet + UnorderedSet, decodeObj2class } from "near-sdk-js"; import lodash from "lodash-es"; -function decode_obj2class(class_instance, obj) { - let key; - for (key in obj) { - // @ts-ignore - let value = obj[key]; - if (typeof value == 'object') { - // @ts-ignore - let ty = class_instance.constructor.schema[key]; - if (ty !== undefined && ty.hasOwnProperty("map")) { - for (let mkey in value) { - if (ty["map"]["value"]==='string') { - class_instance[key][mkey] = value[mkey]; - } else { - class_instance[key][mkey] = decode_obj2class(new ty["map"]["value"](), value[mkey]); - } - } - } else if (ty !== undefined && ty.hasOwnProperty("array")) { - for (let k in value) { - if (ty["array"]["value"]==='string') { - class_instance[key].push(value[k]); - } else { - class_instance[key].push(decode_obj2class(new ty["array"]["value"](), value[k])); - } - } - } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_MAP_SCHE)) { - class_instance[key]._keys.length = obj[key]._keys.length; - class_instance[key].constructor.schema = ty; - let subtype_value = ty[UNORDERED_MAP_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - } - } else if (ty !== undefined && ty.hasOwnProperty(VECTOR_SCHE)) { - class_instance[key].length = obj[key].length; - class_instance[key].constructor.schema = ty; - let subtype_value = ty[VECTOR_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - } - } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_SET_SCHE)) { - class_instance[key]._elements.length = obj[key]._elements.length; - class_instance[key].constructor.schema = ty; - let subtype_value = ty[UNORDERED_SET_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - } - } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_MAP_SCHE)) { - class_instance[key].constructor.schema = ty; - let subtype_value = ty[LOOKUP_MAP_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - } - } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_SET_SCHE)) { - class_instance[key].constructor.schema = ty; - let subtype_value = ty[LOOKUP_SET_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - } - } else { - // normal class - class_instance[key].constructor.schema = class_instance.constructor.schema[key]; - class_instance[key] = decode_obj2class(class_instance[key], obj[key]); - } - } - } - const instance_tmp = lodash.cloneDeep(class_instance); - class_instance = Object.assign(class_instance, obj); - for (key in obj) { - if (typeof class_instance[key] == 'object') { - class_instance[key] = instance_tmp[key]; - } - } - near.log("current instance: ", class_instance); - return class_instance; -} - class Car { static schema = { name: "string", @@ -169,7 +89,7 @@ export class StatusDeserializeClass { let account_id = near.signerAccountId(); near.log(`${account_id} set_status with message ${message}`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); inst.records[account_id] = message; let data = serialize(inst) this.messages = decode(data); @@ -179,7 +99,7 @@ export class StatusDeserializeClass { get_record({ account_id }) { near.log(`get_record for account_id ${account_id}`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.records[account_id] || null; } @@ -189,7 +109,7 @@ export class StatusDeserializeClass { let account_id = near.signerAccountId(); near.log(`${account_id} set_car_info name ${name}, speed ${speed}`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); inst.car.name = name; inst.car.speed = speed; let data = serialize(inst) @@ -200,7 +120,7 @@ export class StatusDeserializeClass { get_car_info({ }) { near.log(`get_car_info`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.car.info(); } @@ -209,7 +129,7 @@ export class StatusDeserializeClass { let account_id = near.signerAccountId(); near.log(`${account_id} push_message message ${message}`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); inst.messages.push(message); let data = serialize(inst) this.messages = decode(data); @@ -219,7 +139,7 @@ export class StatusDeserializeClass { get_messages({ }) { near.log(`get_messages`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.messages.join(','); } @@ -228,7 +148,7 @@ export class StatusDeserializeClass { let account_id = near.signerAccountId(); near.log(`${account_id} set_nested_efficient_recordes with message ${message},id ${id}`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); inst.efficient_recordes.set(account_id, message); const nestedMap = inst.nested_efficient_recordes.get(id, { defaultValue: new UnorderedMap("i_" + id + "_"), @@ -269,7 +189,7 @@ export class StatusDeserializeClass { get_efficient_recordes({ account_id }) { near.log(`get_efficient_recordes for account_id ${account_id}`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.efficient_recordes.get(account_id); } @@ -277,7 +197,7 @@ export class StatusDeserializeClass { get_nested_efficient_recordes({ account_id, id }) { near.log(`get_nested_efficient_recordes for account_id ${account_id}, id ${id}`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.nested_efficient_recordes.get(id, { defaultValue: new UnorderedMap("i_" + id + "_"), }).get(account_id); @@ -288,7 +208,7 @@ export class StatusDeserializeClass { near.log(`get_nested_lookup_recordes for account_id ${account_id}, id ${id}`); near.log(this.messages); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.nested_lookup_recordes.get(id, { defaultValue: new LookupMap("li_" + id + "_"), }).get(account_id); @@ -299,7 +219,7 @@ export class StatusDeserializeClass { near.log(`get_vector_nested_group for idx ${idx}, account_id ${account_id}`); near.log(this.messages); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.vector_nested_group.get(idx).get(account_id); } @@ -307,7 +227,7 @@ export class StatusDeserializeClass { get_lookup_nested_vec({ account_id, idx }) { near.log(`get_looup_nested_vec for account_id ${account_id}, idx ${idx}`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.lookup_nest_vec.get(account_id).get(idx); } @@ -315,7 +235,7 @@ export class StatusDeserializeClass { get_is_contains_user({ account_id }) { near.log(`get_is_contains_user for account_id ${account_id}`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.unordered_set.contains(account_id); } @@ -323,7 +243,7 @@ export class StatusDeserializeClass { get_subtype_of_efficient_recordes({ }) { near.log(`get_subtype_of_efficient_recordes`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.efficient_recordes.subtype(); } @@ -331,7 +251,7 @@ export class StatusDeserializeClass { get_subtype_of_nested_efficient_recordes({ }) { near.log(`get_subtype_of_nested_efficient_recordes`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.nested_efficient_recordes.subtype(); } @@ -339,7 +259,7 @@ export class StatusDeserializeClass { get_subtype_of_nested_lookup_recordes({ }) { near.log(`get_subtype_of_nested_lookup_recordes`); let obj = deserialize(encode(this.messages)); - let inst = decode_obj2class(new InnerStatusDeserializeClass(), obj); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); return inst.nested_lookup_recordes.subtype(); } } diff --git a/packages/near-sdk-js/lib/collections/lookup-map.js b/packages/near-sdk-js/lib/collections/lookup-map.js index 05b73753..f0ebb6ba 100644 --- a/packages/near-sdk-js/lib/collections/lookup-map.js +++ b/packages/near-sdk-js/lib/collections/lookup-map.js @@ -72,7 +72,7 @@ export class LookupMap { options.reconstructor = Vector.reconstruct; } } - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** * Removes and retrieves the element with the provided key. @@ -86,7 +86,7 @@ export class LookupMap { return options?.defaultValue ?? null; } const value = near.storageGetEvictedRaw(); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** * Store a new value at the provided key. @@ -102,7 +102,7 @@ export class LookupMap { return options?.defaultValue ?? null; } const value = near.storageGetEvictedRaw(); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** * Extends the current collection with the passed in array of key-value pairs. diff --git a/packages/near-sdk-js/lib/collections/unordered-map.d.ts b/packages/near-sdk-js/lib/collections/unordered-map.d.ts index f2a4e3fa..21e3293c 100644 --- a/packages/near-sdk-js/lib/collections/unordered-map.d.ts +++ b/packages/near-sdk-js/lib/collections/unordered-map.d.ts @@ -96,6 +96,7 @@ declare class UnorderedMapIterator { * @param options - Options for retrieving and storing data. */ constructor(unorderedMap: UnorderedMap, options?: GetOptions); + subtype(): any; next(): { value: [string | null, DataType | null]; done: boolean; diff --git a/packages/near-sdk-js/lib/collections/unordered-map.js b/packages/near-sdk-js/lib/collections/unordered-map.js index 51bc48c7..373b3db2 100644 --- a/packages/near-sdk-js/lib/collections/unordered-map.js +++ b/packages/near-sdk-js/lib/collections/unordered-map.js @@ -40,7 +40,7 @@ export class UnorderedMap extends SubType { } options = this.set_reconstructor(options); const [value] = valueAndIndex; - return getValueWithOptions(encode(value), options); + return getValueWithOptions(this.subtype(), encode(value), options); } /** * Store a new value at the provided key. @@ -60,7 +60,7 @@ export class UnorderedMap extends SubType { } const [oldValue, oldIndex] = valueAndIndex; this.values.set(key, [decode(serialized), oldIndex]); - return getValueWithOptions(encode(oldValue), options); + return getValueWithOptions(this.subtype(), encode(oldValue), options); } /** * Removes and retrieves the element with the provided key. @@ -83,7 +83,7 @@ export class UnorderedMap extends SubType { assert(swappedValueAndIndex !== null, ERR_INCONSISTENT_STATE); this.values.set(swappedKey, [swappedValueAndIndex[0], index]); } - return getValueWithOptions(encode(value), options); + return getValueWithOptions(this.subtype(), encode(value), options); } /** * Remove all of the elements stored within the collection. @@ -179,6 +179,11 @@ class UnorderedMapIterator { this.options = options; this.keys = new VectorIterator(unorderedMap._keys); this.map = unorderedMap.values; + this.subtype = unorderedMap.subtype; + } + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ + subtype() { } next() { const key = this.keys.next(); @@ -191,7 +196,7 @@ class UnorderedMapIterator { done: key.done, value: [ key.value, - getValueWithOptions(encode(valueAndIndex[0]), this.options), + getValueWithOptions(this.subtype(), encode(valueAndIndex[0]), this.options), ], }; } diff --git a/packages/near-sdk-js/lib/collections/vector.js b/packages/near-sdk-js/lib/collections/vector.js index 7e1dc82a..dca47b1e 100644 --- a/packages/near-sdk-js/lib/collections/vector.js +++ b/packages/near-sdk-js/lib/collections/vector.js @@ -85,7 +85,7 @@ export class Vector { const storageKey = indexToKey(this.prefix, index); const value = near.storageReadRaw(bytes(storageKey)); options = this.set_reconstructor(options); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** * Removes an element from the vector and returns it in serialized form. @@ -105,7 +105,7 @@ export class Vector { assert(near.storageWriteRaw(bytes(key), serializeValueWithOptions(last, options)), ERR_INCONSISTENT_STATE); const value = near.storageGetEvictedRaw(); options = this.set_reconstructor(options); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** * Adds data to the collection. @@ -132,7 +132,7 @@ export class Vector { this.length -= 1; assert(near.storageRemoveRaw(bytes(lastKey)), ERR_INCONSISTENT_STATE); const value = near.storageGetEvictedRaw(); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** * Replaces the data stored at the provided index with the provided data and returns the previously stored data. @@ -147,7 +147,7 @@ export class Vector { assert(near.storageWriteRaw(bytes(key), serializeValueWithOptions(element, options)), ERR_INCONSISTENT_STATE); const value = near.storageGetEvictedRaw(); options = this.set_reconstructor(options); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** * Extends the current collection with the passed in array of elements. diff --git a/packages/near-sdk-js/lib/utils.d.ts b/packages/near-sdk-js/lib/utils.d.ts index cf55baba..987c4cdf 100644 --- a/packages/near-sdk-js/lib/utils.d.ts +++ b/packages/near-sdk-js/lib/utils.d.ts @@ -39,7 +39,7 @@ export declare function assert(expression: unknown, message: string): asserts ex export declare type Mutable = { -readonly [P in keyof T]: T[P]; }; -export declare function getValueWithOptions(value: Uint8Array | null, options?: Omit, "serializer">): DataType | null; +export declare function getValueWithOptions(datatype: any, value: Uint8Array | null, options?: Omit, "serializer">): DataType | null; export declare function serializeValueWithOptions(value: DataType, { serializer }?: Pick, "serializer">): Uint8Array; export declare function serialize(valueToSerialize: unknown): Uint8Array; export declare function deserialize(valueToDeserialize: Uint8Array): unknown; diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index 750a42b9..bc7e3d27 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -1,5 +1,5 @@ -import lodash from "lodash"; import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE } from "./collections"; +import { cloneDeep } from "lodash-es"; // make PromiseIndex a nominal typing var PromiseIndexBrand; (function (PromiseIndexBrand) { @@ -37,19 +37,51 @@ export function assert(expression, message) { throw new Error("assertion failed: " + message); } } -export function getValueWithOptions(value, options = { +export function getValueWithOptions(datatype, value, options = { deserializer: deserialize, }) { if (value === null) { return options?.defaultValue ?? null; } - const deserialized = deserialize(value); + // 这里是一个obj + let deserialized = deserialize(value); if (deserialized === undefined || deserialized === null) { return options?.defaultValue ?? null; } if (options?.reconstructor) { return options.reconstructor(deserialized); } + if (datatype !== undefined) { + // subtype info is a class constructor + if (typeof datatype === "function") { + deserialized = decodeObj2class(new datatype(), deserialized); + } + else if (typeof datatype === "object") { + // normal collections of array, map; subtype will be: + // {map: { key: 'string', value: 'string' }} or {array: {value: 'string'}} .. + // eslint-disable-next-line no-prototype-builtins + if (datatype.hasOwnProperty("map")) { + // @ts-ignore + for (const mkey in deserialized) { + if (datatype["map"]["value"] !== 'string') { + deserialized[mkey] = decodeObj2class(new datatype["map"]["value"](), value[mkey]); + } + } + // eslint-disable-next-line no-prototype-builtins + } + else if (datatype.hasOwnProperty("array")) { + const new_vec = []; + // @ts-ignore + for (const k in deserialized) { + if (datatype["array"]["value"] !== 'string') { + new_vec.push(decodeObj2class(new datatype["array"]["value"](), value[k])); + } + } + deserialized = new_vec; + // eslint-disable-next-line no-prototype-builtins + } + } + } return deserialized; } export function serializeValueWithOptions(value, { serializer } = { @@ -93,6 +125,9 @@ export function deserialize(valueToDeserialize) { }); } export function decodeObj2class(class_instance, obj) { + if (typeof obj != 'object') { + return obj; + } let key; for (key in obj) { // @ts-ignore @@ -172,7 +207,7 @@ export function decodeObj2class(class_instance, obj) { } } } - const instance_tmp = lodash.cloneDeep(class_instance); + const instance_tmp = cloneDeep(class_instance); class_instance = Object.assign(class_instance, obj); for (key in obj) { if (typeof class_instance[key] == 'object') { diff --git a/packages/near-sdk-js/package.json b/packages/near-sdk-js/package.json index 424fc104..bb7e5a04 100644 --- a/packages/near-sdk-js/package.json +++ b/packages/near-sdk-js/package.json @@ -47,6 +47,7 @@ "eslint": "^8.20.0", "json-schema": "0.4.0", "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "near-abi": "^0.1.0", "near-typescript-json-schema": "0.55.0", "rollup": "^2.61.1", diff --git a/packages/near-sdk-js/src/collections/lookup-map.ts b/packages/near-sdk-js/src/collections/lookup-map.ts index 45dfe5b6..32fa6bbf 100644 --- a/packages/near-sdk-js/src/collections/lookup-map.ts +++ b/packages/near-sdk-js/src/collections/lookup-map.ts @@ -80,7 +80,7 @@ export class LookupMap { } } - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** @@ -101,7 +101,7 @@ export class LookupMap { const value = near.storageGetEvictedRaw(); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** @@ -125,7 +125,7 @@ export class LookupMap { const value = near.storageGetEvictedRaw(); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** diff --git a/packages/near-sdk-js/src/collections/unordered-map.ts b/packages/near-sdk-js/src/collections/unordered-map.ts index 183d59da..ca011455 100644 --- a/packages/near-sdk-js/src/collections/unordered-map.ts +++ b/packages/near-sdk-js/src/collections/unordered-map.ts @@ -63,7 +63,7 @@ export class UnorderedMap extends SubType { const [value] = valueAndIndex; - return getValueWithOptions(encode(value), options); + return getValueWithOptions(this.subtype(), encode(value), options); } /** @@ -93,7 +93,7 @@ export class UnorderedMap extends SubType { const [oldValue, oldIndex] = valueAndIndex; this.values.set(key, [decode(serialized), oldIndex]); - return getValueWithOptions(encode(oldValue), options); + return getValueWithOptions(this.subtype(),encode(oldValue), options); } /** @@ -127,7 +127,7 @@ export class UnorderedMap extends SubType { this.values.set(swappedKey, [swappedValueAndIndex[0], index]); } - return getValueWithOptions(encode(value), options); + return getValueWithOptions(this.subtype(),encode(value), options); } /** @@ -249,6 +249,13 @@ class UnorderedMapIterator { ) { this.keys = new VectorIterator(unorderedMap._keys); this.map = unorderedMap.values; + this.subtype = unorderedMap.subtype; + } + + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ + subtype(): any { + } next(): { value: [string | null, DataType | null]; done: boolean } { @@ -266,7 +273,7 @@ class UnorderedMapIterator { done: key.done, value: [ key.value, - getValueWithOptions(encode(valueAndIndex[0]), this.options), + getValueWithOptions(this.subtype(), encode(valueAndIndex[0]), this.options), ], }; } diff --git a/packages/near-sdk-js/src/collections/vector.ts b/packages/near-sdk-js/src/collections/vector.ts index 3258b329..1459c9c5 100644 --- a/packages/near-sdk-js/src/collections/vector.ts +++ b/packages/near-sdk-js/src/collections/vector.ts @@ -99,7 +99,7 @@ export class Vector { const storageKey = indexToKey(this.prefix, index); const value = near.storageReadRaw(bytes(storageKey)); options = this.set_reconstructor(options); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** @@ -131,7 +131,7 @@ export class Vector { const value = near.storageGetEvictedRaw(); options = this.set_reconstructor(options); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** @@ -171,7 +171,7 @@ export class Vector { const value = near.storageGetEvictedRaw(); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** @@ -200,7 +200,7 @@ export class Vector { const value = near.storageGetEvictedRaw(); options = this.set_reconstructor(options); - return getValueWithOptions(value, options); + return getValueWithOptions(this.subtype(), value, options); } /** diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 25f837ba..5ecf1b1a 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -1,6 +1,7 @@ import { GetOptions } from "./types/collections"; -import lodash from "lodash"; import {LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE} from "./collections"; +import {cloneDeep} from "lodash-es"; +// import lodash from 'lodash'; export interface Env { uint8array_to_latin1_string(a: Uint8Array): string; @@ -72,6 +73,7 @@ export function assert( export type Mutable = { -readonly [P in keyof T]: T[P] }; export function getValueWithOptions( + datatype: any, value: Uint8Array | null, options: Omit, "serializer"> = { deserializer: deserialize, @@ -81,7 +83,8 @@ export function getValueWithOptions( return options?.defaultValue ?? null; } - const deserialized = deserialize(value); + // 这里是一个obj + let deserialized = deserialize(value); if (deserialized === undefined || deserialized === null) { return options?.defaultValue ?? null; @@ -91,6 +94,36 @@ export function getValueWithOptions( return options.reconstructor(deserialized); } + if (datatype !== undefined) { + // subtype info is a class constructor + if (typeof datatype === "function") { + deserialized = decodeObj2class(new datatype(), deserialized); + } else if (typeof datatype === "object") { + // normal collections of array, map; subtype will be: + // {map: { key: 'string', value: 'string' }} or {array: {value: 'string'}} .. + // eslint-disable-next-line no-prototype-builtins + if (datatype.hasOwnProperty("map")) { + // @ts-ignore + for (const mkey in deserialized) { + if (datatype["map"]["value"] !=='string') { + deserialized[mkey] = decodeObj2class(new datatype["map"]["value"](), value[mkey]); + } + } + // eslint-disable-next-line no-prototype-builtins + } else if (datatype.hasOwnProperty("array")) { + const new_vec = []; + // @ts-ignore + for (const k in deserialized) { + if (datatype["array"]["value"] !=='string') { + new_vec.push(decodeObj2class(new datatype["array"]["value"](), value[k])); + } + } + deserialized = new_vec; + // eslint-disable-next-line no-prototype-builtins + } + } + } + return deserialized as DataType; } @@ -150,6 +183,9 @@ export function deserialize(valueToDeserialize: Uint8Array): unknown { } export function decodeObj2class(class_instance, obj) { + if (typeof obj != 'object') { + return obj; + } let key; for (key in obj) { // @ts-ignore @@ -220,7 +256,7 @@ export function decodeObj2class(class_instance, obj) { } } } - const instance_tmp = lodash.cloneDeep(class_instance); + const instance_tmp = cloneDeep(class_instance); class_instance = Object.assign(class_instance, obj); for (key in obj) { if (typeof class_instance[key] == 'object') { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab7cbcaf..b832ce51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,6 +124,9 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 near-abi: specifier: ^0.1.0 version: 0.1.1 From 203e139cb487ff699f3ce66332dd4c2aef461c2d Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sun, 3 Dec 2023 23:34:08 +0800 Subject: [PATCH 15/29] test: add test of nested class of UnorderedMap --- .../test-status-deserialize-class.ava.js | 5 +++++ examples/src/status-deserialize-class.js | 21 +++++++++++++++++-- packages/near-sdk-js/lib/utils.d.ts | 2 +- packages/near-sdk-js/lib/utils.js | 8 ++++++- packages/near-sdk-js/src/utils.ts | 10 +++++++-- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index aad7c3e5..2de46307 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -50,6 +50,11 @@ test("Ali set_car_info and get_car_info", async (t) => { await statusMessage.view("get_car_info", { }), carName + " run with speed " + speed ); + + t.is( + await statusMessage.view("get_user_car_info", { account_id: ali.accountId }), + carName + " run with speed " + speed + ); }); test("Ali push_message and get_messages", async (t) => { diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index c7223c55..04516d95 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -40,6 +40,7 @@ class InnerStatusDeserializeClass { vector_nested_group: {vector: {value: { lookup_map: {value: 'string'}}}}, lookup_nest_vec: {lookup_map: {value: { vector: { value: 'string' }}}}, unordered_set: {unordered_set: {value: 'string'}}, + user_car_map: {unordered_map: {value: Car }}, }; constructor() { this.records = {}; @@ -56,6 +57,7 @@ class InnerStatusDeserializeClass { // account_id -> index -> message this.lookup_nest_vec = new LookupMap("e"); this.unordered_set = new UnorderedSet("f"); + this.user_car_map = new UnorderedMap("g"); } } @@ -110,8 +112,11 @@ export class StatusDeserializeClass { near.log(`${account_id} set_car_info name ${name}, speed ${speed}`); let obj = deserialize(encode(this.messages)); let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - inst.car.name = name; - inst.car.speed = speed; + let car = new Car(); + car.name = name; + car.speed = speed; + inst.car = car; + inst.user_car_map.set(account_id, car); let data = serialize(inst) this.messages = decode(data); } @@ -124,6 +129,18 @@ export class StatusDeserializeClass { return inst.car.info(); } + @view({}) + get_user_car_info({ account_id }) { + near.log(`get_user_car_info for account_id ${account_id}`); + let obj = deserialize(encode(this.messages)); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); + let car = inst.user_car_map.get(account_id); + if (car == null) { + return null; + } + return inst.user_car_map.get(account_id).info(); + } + @call({}) push_message({ message }) { let account_id = near.signerAccountId(); diff --git a/packages/near-sdk-js/lib/utils.d.ts b/packages/near-sdk-js/lib/utils.d.ts index 987c4cdf..4b2a343f 100644 --- a/packages/near-sdk-js/lib/utils.d.ts +++ b/packages/near-sdk-js/lib/utils.d.ts @@ -39,7 +39,7 @@ export declare function assert(expression: unknown, message: string): asserts ex export declare type Mutable = { -readonly [P in keyof T]: T[P]; }; -export declare function getValueWithOptions(datatype: any, value: Uint8Array | null, options?: Omit, "serializer">): DataType | null; +export declare function getValueWithOptions(datatype: unknown, value: Uint8Array | null, options?: Omit, "serializer">): DataType | null; export declare function serializeValueWithOptions(value: DataType, { serializer }?: Pick, "serializer">): Uint8Array; export declare function serialize(valueToSerialize: unknown): Uint8Array; export declare function deserialize(valueToDeserialize: Uint8Array): unknown; diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index bc7e3d27..ca1c2441 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -54,6 +54,8 @@ export function getValueWithOptions(datatype, value, options = { if (datatype !== undefined) { // subtype info is a class constructor if (typeof datatype === "function") { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore deserialized = decodeObj2class(new datatype(), deserialized); } else if (typeof datatype === "object") { @@ -61,6 +63,7 @@ export function getValueWithOptions(datatype, value, options = { // {map: { key: 'string', value: 'string' }} or {array: {value: 'string'}} .. // eslint-disable-next-line no-prototype-builtins if (datatype.hasOwnProperty("map")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const mkey in deserialized) { if (datatype["map"]["value"] !== 'string') { @@ -71,6 +74,7 @@ export function getValueWithOptions(datatype, value, options = { } else if (datatype.hasOwnProperty("array")) { const new_vec = []; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const k in deserialized) { if (datatype["array"]["value"] !== 'string') { @@ -130,9 +134,11 @@ export function decodeObj2class(class_instance, obj) { } let key; for (key in obj) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - let value = obj[key]; + const value = obj[key]; if (typeof value == 'object') { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const ty = class_instance.constructor.schema[key]; // eslint-disable-next-line no-prototype-builtins diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 5ecf1b1a..d39984cb 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -73,7 +73,7 @@ export function assert( export type Mutable = { -readonly [P in keyof T]: T[P] }; export function getValueWithOptions( - datatype: any, + datatype: unknown, value: Uint8Array | null, options: Omit, "serializer"> = { deserializer: deserialize, @@ -97,12 +97,15 @@ export function getValueWithOptions( if (datatype !== undefined) { // subtype info is a class constructor if (typeof datatype === "function") { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore deserialized = decodeObj2class(new datatype(), deserialized); } else if (typeof datatype === "object") { // normal collections of array, map; subtype will be: // {map: { key: 'string', value: 'string' }} or {array: {value: 'string'}} .. // eslint-disable-next-line no-prototype-builtins if (datatype.hasOwnProperty("map")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const mkey in deserialized) { if (datatype["map"]["value"] !=='string') { @@ -112,6 +115,7 @@ export function getValueWithOptions( // eslint-disable-next-line no-prototype-builtins } else if (datatype.hasOwnProperty("array")) { const new_vec = []; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const k in deserialized) { if (datatype["array"]["value"] !=='string') { @@ -188,9 +192,11 @@ export function decodeObj2class(class_instance, obj) { } let key; for (key in obj) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - let value = obj[key]; + const value = obj[key]; if (typeof value == 'object') { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const ty = class_instance.constructor.schema[key]; // eslint-disable-next-line no-prototype-builtins From 15d5ae43c0b6bf96b3a987bdeb5a810dd242ed27 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Wed, 6 Dec 2023 09:46:24 +0800 Subject: [PATCH 16/29] feat: update dependencies --- packages/near-sdk-js/lib/utils.js | 2 +- packages/near-sdk-js/package.json | 1 - packages/near-sdk-js/src/utils.ts | 2 +- pnpm-lock.yaml | 11 ++++------- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index ca1c2441..c5b2f75e 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -43,7 +43,7 @@ export function getValueWithOptions(datatype, value, options = { if (value === null) { return options?.defaultValue ?? null; } - // 这里是一个obj + // here is an obj let deserialized = deserialize(value); if (deserialized === undefined || deserialized === null) { return options?.defaultValue ?? null; diff --git a/packages/near-sdk-js/package.json b/packages/near-sdk-js/package.json index bb7e5a04..ef11d16b 100644 --- a/packages/near-sdk-js/package.json +++ b/packages/near-sdk-js/package.json @@ -71,7 +71,6 @@ "@types/babel__core": "^7.1.19", "@types/babel__traverse": "^7.18.1", "@types/eslint": "^8.4.6", - "@types/lodash": "^4.14.202", "@types/node": "^17.0.38", "@types/rollup": "^0.54.0", "@types/signale": "^1.4.4", diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index d39984cb..4748b256 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -83,7 +83,7 @@ export function getValueWithOptions( return options?.defaultValue ?? null; } - // 这里是一个obj + // here is an obj let deserialized = deserialize(value); if (deserialized === undefined || deserialized === null) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b832ce51..cbbec4f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -161,9 +165,6 @@ importers: '@types/eslint': specifier: ^8.4.6 version: 8.44.7 - '@types/lodash': - specifier: ^4.14.202 - version: 4.14.202 '@types/node': specifier: ^17.0.38 version: 17.0.45 @@ -3957,7 +3958,3 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} dev: true - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false From 4ffc1a85ed080a4b1d6e615ca6b63ca32f999fb3 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 9 Dec 2023 01:33:10 +0800 Subject: [PATCH 17/29] feat: add test bigint and date --- .../test-status-deserialize-class.ava.js | 11 +++++++ examples/src/status-deserialize-class.js | 32 +++++++++++++++++++ packages/near-sdk-js/lib/utils.js | 2 +- packages/near-sdk-js/src/utils.ts | 2 +- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index 2de46307..f4b9ed29 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -117,6 +117,17 @@ test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text" ); }); +test("Ali set_big_num_and_date then gets", async (t) => { + const { ali, bob, statusMessage } = t.context.accounts; + await ali.call(statusMessage, "set_big_num_and_date", { bigint_num: `${10n}`, new_date: new Date('August 19, 2023 23:15:30 GMT+00:00') }, {gas: new BN(200 * 10**12)}); + + + const afterSetNum = await statusMessage.view("get_big_num", { }); + t.is(afterSetNum, `${10n}`); + const afterSetDate = await statusMessage.view("get_date", { }); + t.is(afterSetDate.toString(), '2023-08-19T23:15:30.000Z'); +}); + test("View get_subtype_of_efficient_recordes", async (t) => { const { statusMessage } = t.context.accounts; diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 04516d95..5255368f 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -41,6 +41,8 @@ class InnerStatusDeserializeClass { lookup_nest_vec: {lookup_map: {value: { vector: { value: 'string' }}}}, unordered_set: {unordered_set: {value: 'string'}}, user_car_map: {unordered_map: {value: Car }}, + big_num: 'bigint', + date: 'date' }; constructor() { this.records = {}; @@ -58,6 +60,8 @@ class InnerStatusDeserializeClass { this.lookup_nest_vec = new LookupMap("e"); this.unordered_set = new UnorderedSet("f"); this.user_car_map = new UnorderedMap("g"); + this.big_num = 1n; + this.date = new Date(); } } @@ -202,6 +206,34 @@ export class StatusDeserializeClass { this.messages = decode(data); } + @call({}) + set_big_num_and_date({ bigint_num, new_date }) { + let account_id = near.signerAccountId(); + near.log(`${account_id} set_bigint_and_date bigint_num ${bigint_num}, new_date: ${new_date}`); + let obj = deserialize(encode(this.messages)); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); + inst.big_num = bigint_num; + inst.date = new_date; + let data = serialize(inst) + this.messages = decode(data); + } + + @view({}) + get_big_num({ }) { + near.log(`get_big_num}`); + let obj = deserialize(encode(this.messages)); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); + return inst.big_num; + } + + @view({}) + get_date({ }) { + near.log(`get_date`); + let obj = deserialize(encode(this.messages)); + let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); + return inst.date; + } + @view({}) get_efficient_recordes({ account_id }) { near.log(`get_efficient_recordes for account_id ${account_id}`); diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index c5b2f75e..a676f665 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -216,7 +216,7 @@ export function decodeObj2class(class_instance, obj) { const instance_tmp = cloneDeep(class_instance); class_instance = Object.assign(class_instance, obj); for (key in obj) { - if (typeof class_instance[key] == 'object') { + if (typeof class_instance[key] == 'object' && !(class_instance[key] instanceof Date)) { class_instance[key] = instance_tmp[key]; } } diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 4748b256..82baa5c2 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -265,7 +265,7 @@ export function decodeObj2class(class_instance, obj) { const instance_tmp = cloneDeep(class_instance); class_instance = Object.assign(class_instance, obj); for (key in obj) { - if (typeof class_instance[key] == 'object') { + if (typeof class_instance[key] == 'object' && !(class_instance[key] instanceof Date)) { class_instance[key] = instance_tmp[key]; } } From 6e7a8258bd6f0cde8b10a24050eeafea3ae7c8d4 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 9 Dec 2023 15:23:54 +0800 Subject: [PATCH 18/29] feat: decodeObj2class if contains scheme info --- packages/near-sdk-js/lib/utils.js | 2 +- packages/near-sdk-js/src/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index a676f665..550da478 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -129,7 +129,7 @@ export function deserialize(valueToDeserialize) { }); } export function decodeObj2class(class_instance, obj) { - if (typeof obj != 'object') { + if (typeof obj != 'object' || class_instance.constructor.schema === undefined) { return obj; } let key; diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 82baa5c2..ef0853e5 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -187,7 +187,7 @@ export function deserialize(valueToDeserialize: Uint8Array): unknown { } export function decodeObj2class(class_instance, obj) { - if (typeof obj != 'object') { + if (typeof obj != 'object' || class_instance.constructor.schema === undefined) { return obj; } let key; From 1eb4e97026a09d823f7d0ff8a9f49a8be97f95f2 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 9 Dec 2023 17:21:48 +0800 Subject: [PATCH 19/29] feat: reconstruct with decodeObj2class if contains static schema and format --- .../test-status-deserialize-class.ava.js | 23 +-- examples/src/status-deserialize-class.js | 156 +++++------------- .../near-sdk-js/lib/collections/lookup-map.js | 7 +- .../near-sdk-js/lib/collections/subtype.js | 5 +- .../lib/collections/unordered-map.js | 3 +- .../near-sdk-js/lib/collections/vector.js | 7 +- packages/near-sdk-js/lib/near-bindgen.js | 19 ++- packages/near-sdk-js/lib/utils.js | 21 ++- .../near-sdk-js/src/collections/lookup-map.ts | 22 ++- .../near-sdk-js/src/collections/subtype.ts | 88 +++++----- .../src/collections/unordered-map.ts | 16 +- .../near-sdk-js/src/collections/vector.ts | 26 +-- packages/near-sdk-js/src/near-bindgen.ts | 28 +++- packages/near-sdk-js/src/utils.ts | 61 ++++--- 14 files changed, 234 insertions(+), 248 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index f4b9ed29..cfac7167 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -1,4 +1,4 @@ -import {BN, Worker} from "near-workspaces"; +import {Worker} from "near-workspaces"; import test from "ava"; test.before(async (t) => { @@ -11,9 +11,10 @@ test.before(async (t) => { // Deploy the contract. const statusMessage = await root.devDeploy("./build/status-deserialize-class.wasm"); - await root.call(statusMessage, "init_messages", {}, {gas: new BN(200 * 10**12)}); - const result = await statusMessage.view("is_message_inited", {}); + await root.call(statusMessage, "init_contract", {}); + const result = await statusMessage.view("is_contract_inited", {}); t.is(result, true); + // Create test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); @@ -32,7 +33,7 @@ test.after.always(async (t) => { test("Ali sets then gets status", async (t) => { const { ali, statusMessage } = t.context.accounts; - await ali.call(statusMessage, "set_record", { message: "hello" }, {gas: new BN(200 * 10**12)}); + await ali.call(statusMessage, "set_record", { message: "hello" }); t.is( await statusMessage.view("get_record", { account_id: ali.accountId }), @@ -44,7 +45,7 @@ test("Ali set_car_info and get_car_info", async (t) => { const { ali, statusMessage } = t.context.accounts; let carName = "Mercedes-Benz"; let speed = 240; - await ali.call(statusMessage, "set_car_info", { name: carName, speed: speed }, {gas: new BN(200 * 10**12)}); + await ali.call(statusMessage, "set_car_info", { name: carName, speed: speed }); t.is( await statusMessage.view("get_car_info", { }), @@ -61,8 +62,8 @@ test("Ali push_message and get_messages", async (t) => { const { ali, statusMessage } = t.context.accounts; let message1 = 'Hello'; let message2 = 'World'; - await ali.call(statusMessage, "push_message", { message: message1 }, {gas: new BN(200 * 10**12)}); - await ali.call(statusMessage, "push_message", { message: message2 }, {gas: new BN(200 * 10**12)}); + await ali.call(statusMessage, "push_message", { message: message1 }); + await ali.call(statusMessage, "push_message", { message: message2 }); t.is( await statusMessage.view("get_messages", { }), @@ -72,9 +73,9 @@ test("Ali push_message and get_messages", async (t) => { test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text", async (t) => { const { ali, bob, statusMessage } = t.context.accounts; - await ali.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, {gas: new BN(200 * 10**12)}); - await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, {gas: new BN(200 * 10**12)}); - await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "2", message: "world" }, {gas: new BN(200 * 10**12)}); + await ali.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }); + await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }); + await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "2", message: "world" }); t.is( await statusMessage.view("get_efficient_recordes", { account_id: ali.accountId }), @@ -119,7 +120,7 @@ test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text" test("Ali set_big_num_and_date then gets", async (t) => { const { ali, bob, statusMessage } = t.context.accounts; - await ali.call(statusMessage, "set_big_num_and_date", { bigint_num: `${10n}`, new_date: new Date('August 19, 2023 23:15:30 GMT+00:00') }, {gas: new BN(200 * 10**12)}); + await ali.call(statusMessage, "set_big_num_and_date", { bigint_num: `${10n}`, new_date: new Date('August 19, 2023 23:15:30 GMT+00:00') }); const afterSetNum = await statusMessage.view("get_big_num", { }); diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 5255368f..0f3e25eb 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -4,15 +4,10 @@ import { view, near, UnorderedMap, - serialize, - decode, - deserialize, - encode, LookupMap, Vector, - UnorderedSet, decodeObj2class + UnorderedSet, } from "near-sdk-js"; -import lodash from "lodash-es"; class Car { static schema = { @@ -29,8 +24,10 @@ class Car { } } -class InnerStatusDeserializeClass { +@NearBindgen({}) +export class StatusDeserializeClass { static schema = { + is_inited: "bool", records: {map: { key: 'string', value: 'string' }}, car: Car, messages: {array: {value: 'string'}}, @@ -45,6 +42,7 @@ class InnerStatusDeserializeClass { date: 'date' }; constructor() { + this.is_inited = false; this.records = {}; this.car = new Car(); this.messages = []; @@ -63,50 +61,33 @@ class InnerStatusDeserializeClass { this.big_num = 1n; this.date = new Date(); } -} - -@NearBindgen({}) -export class StatusDeserializeClass { - constructor() { - this.messages = ""; - } @call({}) - init_messages({ }) { - if (this.messages.length != 0) { + init_contract({ }) { + if (this.is_inited) { near.log(`message inited`); return; } - let account_id = near.signerAccountId(); - near.log(`${account_id} init_messages`); - let status = new InnerStatusDeserializeClass(); - let data = serialize(status) - this.messages = decode(data); + this.is_inited = true; } @view({}) - is_message_inited({}) { - near.log(`query is_message_inited`); - return this.messages.length != 0; + is_contract_inited({}) { + near.log(`query is_contract_inited`); + return this.is_inited; } @call({}) set_record({ message }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_status with message ${message}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - inst.records[account_id] = message; - let data = serialize(inst) - this.messages = decode(data); + this.records[account_id] = message; } @view({}) get_record({ account_id }) { near.log(`get_record for account_id ${account_id}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.records[account_id] || null; + return this.records[account_id] || null; } @@ -114,140 +95,109 @@ export class StatusDeserializeClass { set_car_info({ name, speed }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_car_info name ${name}, speed ${speed}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); let car = new Car(); car.name = name; car.speed = speed; - inst.car = car; - inst.user_car_map.set(account_id, car); - let data = serialize(inst) - this.messages = decode(data); + this.car = car; + this.user_car_map.set(account_id, car); } @view({}) get_car_info({ }) { near.log(`get_car_info`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.car.info(); + return this.car.info(); } @view({}) get_user_car_info({ account_id }) { near.log(`get_user_car_info for account_id ${account_id}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - let car = inst.user_car_map.get(account_id); + let car = this.user_car_map.get(account_id); if (car == null) { return null; } - return inst.user_car_map.get(account_id).info(); + return car.info(); } @call({}) push_message({ message }) { let account_id = near.signerAccountId(); near.log(`${account_id} push_message message ${message}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - inst.messages.push(message); - let data = serialize(inst) - this.messages = decode(data); + this.messages.push(message); } @view({}) get_messages({ }) { near.log(`get_messages`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.messages.join(','); + return this.messages.join(','); } @call({}) set_nested_efficient_recordes({ message, id }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_nested_efficient_recordes with message ${message},id ${id}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - inst.efficient_recordes.set(account_id, message); - const nestedMap = inst.nested_efficient_recordes.get(id, { + this.efficient_recordes.set(account_id, message); + const nestedMap = this.nested_efficient_recordes.get(id, { defaultValue: new UnorderedMap("i_" + id + "_"), }); nestedMap.set(account_id, message); - inst.nested_efficient_recordes.set(id, nestedMap); + this.nested_efficient_recordes.set(id, nestedMap); - const nestedLookup = inst.nested_lookup_recordes.get(id, { + const nestedLookup = this.nested_lookup_recordes.get(id, { defaultValue: new LookupMap("li_" + id + "_"), }); nestedLookup.set(account_id, message); - inst.nested_lookup_recordes.set(id, nestedLookup); + this.nested_lookup_recordes.set(id, nestedLookup); // vector_nested_group: {vector: {value: { lookup_map: {value: 'string'}}}}, - const vecNestedLookup = inst.vector_nested_group.get(0, { + const vecNestedLookup = this.vector_nested_group.get(0, { defaultValue: new LookupMap("di_0_"), }); vecNestedLookup.set(account_id, message); - if (inst.vector_nested_group.isEmpty()) { - inst.vector_nested_group.push(vecNestedLookup); + if (this.vector_nested_group.isEmpty()) { + this.vector_nested_group.push(vecNestedLookup); } else { - inst.vector_nested_group.replace(0, vecNestedLookup); + this.vector_nested_group.replace(0, vecNestedLookup); } - const lookupNestVec = inst.lookup_nest_vec.get(account_id, { + const lookupNestVec = this.lookup_nest_vec.get(account_id, { defaultValue: new Vector("ei_" + account_id + "_"), }); lookupNestVec.push(message); - inst.lookup_nest_vec.set(account_id, lookupNestVec); - - inst.unordered_set.set(account_id); + this.lookup_nest_vec.set(account_id, lookupNestVec); - let data = serialize(inst) - this.messages = decode(data); + this.unordered_set.set(account_id); } @call({}) set_big_num_and_date({ bigint_num, new_date }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_bigint_and_date bigint_num ${bigint_num}, new_date: ${new_date}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - inst.big_num = bigint_num; - inst.date = new_date; - let data = serialize(inst) - this.messages = decode(data); + this.big_num = bigint_num; + this.date = new_date; } @view({}) get_big_num({ }) { near.log(`get_big_num}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.big_num; + return this.big_num; } @view({}) get_date({ }) { near.log(`get_date`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.date; + return this.date; } @view({}) get_efficient_recordes({ account_id }) { near.log(`get_efficient_recordes for account_id ${account_id}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.efficient_recordes.get(account_id); + return this.efficient_recordes.get(account_id); } @view({}) get_nested_efficient_recordes({ account_id, id }) { near.log(`get_nested_efficient_recordes for account_id ${account_id}, id ${id}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.nested_efficient_recordes.get(id, { + return this.nested_efficient_recordes.get(id, { defaultValue: new UnorderedMap("i_" + id + "_"), }).get(account_id); } @@ -255,10 +205,7 @@ export class StatusDeserializeClass { @view({}) get_nested_lookup_recordes({ account_id, id }) { near.log(`get_nested_lookup_recordes for account_id ${account_id}, id ${id}`); - near.log(this.messages); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.nested_lookup_recordes.get(id, { + return this.nested_lookup_recordes.get(id, { defaultValue: new LookupMap("li_" + id + "_"), }).get(account_id); } @@ -266,49 +213,36 @@ export class StatusDeserializeClass { @view({}) get_vector_nested_group({ idx, account_id }) { near.log(`get_vector_nested_group for idx ${idx}, account_id ${account_id}`); - near.log(this.messages); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.vector_nested_group.get(idx).get(account_id); + return this.vector_nested_group.get(idx).get(account_id); } @view({}) get_lookup_nested_vec({ account_id, idx }) { near.log(`get_looup_nested_vec for account_id ${account_id}, idx ${idx}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.lookup_nest_vec.get(account_id).get(idx); + return this.lookup_nest_vec.get(account_id).get(idx); } @view({}) get_is_contains_user({ account_id }) { near.log(`get_is_contains_user for account_id ${account_id}`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.unordered_set.contains(account_id); + return this.unordered_set.contains(account_id); } @view({}) get_subtype_of_efficient_recordes({ }) { near.log(`get_subtype_of_efficient_recordes`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.efficient_recordes.subtype(); + return this.efficient_recordes.subtype(); } @view({}) get_subtype_of_nested_efficient_recordes({ }) { near.log(`get_subtype_of_nested_efficient_recordes`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.nested_efficient_recordes.subtype(); + return this.nested_efficient_recordes.subtype(); } @view({}) get_subtype_of_nested_lookup_recordes({ }) { near.log(`get_subtype_of_nested_lookup_recordes`); - let obj = deserialize(encode(this.messages)); - let inst = decodeObj2class(new InnerStatusDeserializeClass(), obj); - return inst.nested_lookup_recordes.subtype(); + return this.nested_lookup_recordes.subtype(); } } diff --git a/packages/near-sdk-js/lib/collections/lookup-map.js b/packages/near-sdk-js/lib/collections/lookup-map.js index f0ebb6ba..5ffa5f96 100644 --- a/packages/near-sdk-js/lib/collections/lookup-map.js +++ b/packages/near-sdk-js/lib/collections/lookup-map.js @@ -4,7 +4,7 @@ import { UnorderedMap } from "./unordered-map"; import { LookupSet } from "./lookup-set"; import { UnorderedSet } from "./unordered-set"; import { Vector } from "./vector"; -import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE } from "./subtype"; +import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE, } from "./subtype"; /** * A lookup map that stores data in NEAR storage. */ @@ -26,8 +26,7 @@ export class LookupMap { } /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ - subtype() { - } + subtype() { } /** * Get the data stored at the provided key. * @@ -40,7 +39,7 @@ export class LookupMap { if (options == undefined) { options = {}; } - if (((options.reconstructor == undefined)) && this.subtype() != undefined) { + if (options.reconstructor == undefined && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/near-sdk-js/lib/collections/subtype.js b/packages/near-sdk-js/lib/collections/subtype.js index 504dc052..56106f19 100644 --- a/packages/near-sdk-js/lib/collections/subtype.js +++ b/packages/near-sdk-js/lib/collections/subtype.js @@ -11,13 +11,12 @@ export const VECTOR_SCHE = "vector"; export class SubType { /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ - subtype() { - } + subtype() { } set_reconstructor(options) { if (options == undefined) { options = {}; } - if (((options.reconstructor == undefined)) && this.subtype() != undefined) { + if (options.reconstructor == undefined && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/near-sdk-js/lib/collections/unordered-map.js b/packages/near-sdk-js/lib/collections/unordered-map.js index 373b3db2..19a57b77 100644 --- a/packages/near-sdk-js/lib/collections/unordered-map.js +++ b/packages/near-sdk-js/lib/collections/unordered-map.js @@ -183,8 +183,7 @@ class UnorderedMapIterator { } /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ - subtype() { - } + subtype() { } next() { const key = this.keys.next(); if (key.done) { diff --git a/packages/near-sdk-js/lib/collections/vector.js b/packages/near-sdk-js/lib/collections/vector.js index dca47b1e..f6f35055 100644 --- a/packages/near-sdk-js/lib/collections/vector.js +++ b/packages/near-sdk-js/lib/collections/vector.js @@ -4,7 +4,7 @@ import { UnorderedMap } from "./unordered-map"; import { LookupMap } from "./lookup-map"; import { LookupSet } from "./lookup-set"; import { UnorderedSet } from "./unordered-set"; -import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE } from "./subtype"; +import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE, } from "./subtype"; function indexToKey(prefix, index) { const data = new Uint32Array([index]); const array = new Uint8Array(data.buffer); @@ -32,13 +32,12 @@ export class Vector { } /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ - subtype() { - } + subtype() { } set_reconstructor(options) { if (options == undefined) { options = {}; } - if (((options.reconstructor == undefined)) && this.subtype() != undefined) { + if (options.reconstructor == undefined && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/near-sdk-js/lib/near-bindgen.js b/packages/near-sdk-js/lib/near-bindgen.js index a7b4792f..2cf8fc53 100644 --- a/packages/near-sdk-js/lib/near-bindgen.js +++ b/packages/near-sdk-js/lib/near-bindgen.js @@ -1,5 +1,5 @@ import * as near from "./api"; -import { deserialize, serialize, bytes, encode } from "./utils"; +import { deserialize, serialize, bytes, encode, decodeObj2class, } from "./utils"; /** * Tells the SDK to use this function as the migration function of the contract. * The migration function will ignore te existing state. @@ -101,13 +101,18 @@ export function NearBindgen({ requireInit = false, serializer = serialize, deser return deserializer(value); } static _reconstruct(classObject, plainObject) { - for (const item in classObject) { - const reconstructor = classObject[item].constructor?.reconstruct; - classObject[item] = reconstructor - ? reconstructor(plainObject[item]) - : plainObject[item]; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (classObject.constructor.schema === undefined) { + for (const item in classObject) { + const reconstructor = classObject[item].constructor?.reconstruct; + classObject[item] = reconstructor + ? reconstructor(plainObject[item]) + : plainObject[item]; + } + return classObject; } - return classObject; + return decodeObj2class(classObject, plainObject); } static _requireInit() { return requireInit; diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index 550da478..958a8a77 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -1,4 +1,4 @@ -import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE } from "./collections"; +import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE, } from "./collections"; import { cloneDeep } from "lodash-es"; // make PromiseIndex a nominal typing var PromiseIndexBrand; @@ -66,7 +66,7 @@ export function getValueWithOptions(datatype, value, options = { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const mkey in deserialized) { - if (datatype["map"]["value"] !== 'string') { + if (datatype["map"]["value"] !== "string") { deserialized[mkey] = decodeObj2class(new datatype["map"]["value"](), value[mkey]); } } @@ -77,7 +77,7 @@ export function getValueWithOptions(datatype, value, options = { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const k in deserialized) { - if (datatype["array"]["value"] !== 'string') { + if (datatype["array"]["value"] !== "string") { new_vec.push(decodeObj2class(new datatype["array"]["value"](), value[k])); } } @@ -129,7 +129,8 @@ export function deserialize(valueToDeserialize) { }); } export function decodeObj2class(class_instance, obj) { - if (typeof obj != 'object' || class_instance.constructor.schema === undefined) { + if (typeof obj != "object" || + class_instance.constructor.schema === undefined) { return obj; } let key; @@ -137,14 +138,14 @@ export function decodeObj2class(class_instance, obj) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const value = obj[key]; - if (typeof value == 'object') { + if (typeof value == "object") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const ty = class_instance.constructor.schema[key]; // eslint-disable-next-line no-prototype-builtins if (ty !== undefined && ty.hasOwnProperty("map")) { for (const mkey in value) { - if (ty["map"]["value"] === 'string') { + if (ty["map"]["value"] === "string") { class_instance[key][mkey] = value[mkey]; } else { @@ -155,7 +156,7 @@ export function decodeObj2class(class_instance, obj) { } else if (ty !== undefined && ty.hasOwnProperty("array")) { for (const k in value) { - if (ty["array"]["value"] === 'string') { + if (ty["array"]["value"] === "string") { class_instance[key].push(value[k]); } else { @@ -208,7 +209,8 @@ export function decodeObj2class(class_instance, obj) { } else { // normal class - class_instance[key].constructor.schema = class_instance.constructor.schema[key]; + class_instance[key].constructor.schema = + class_instance.constructor.schema[key]; class_instance[key] = decodeObj2class(class_instance[key], obj[key]); } } @@ -216,7 +218,8 @@ export function decodeObj2class(class_instance, obj) { const instance_tmp = cloneDeep(class_instance); class_instance = Object.assign(class_instance, obj); for (key in obj) { - if (typeof class_instance[key] == 'object' && !(class_instance[key] instanceof Date)) { + if (typeof class_instance[key] == "object" && + !(class_instance[key] instanceof Date)) { class_instance[key] = instance_tmp[key]; } } diff --git a/packages/near-sdk-js/src/collections/lookup-map.ts b/packages/near-sdk-js/src/collections/lookup-map.ts index 32fa6bbf..0e9b7c82 100644 --- a/packages/near-sdk-js/src/collections/lookup-map.ts +++ b/packages/near-sdk-js/src/collections/lookup-map.ts @@ -5,11 +5,17 @@ import { serializeValueWithOptions, encode, } from "../utils"; -import {UnorderedMap} from "./unordered-map"; -import {LookupSet} from "./lookup-set"; -import {UnorderedSet} from "./unordered-set"; -import {Vector} from "./vector"; -import {LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE} from "./subtype"; +import { UnorderedMap } from "./unordered-map"; +import { LookupSet } from "./lookup-set"; +import { UnorderedSet } from "./unordered-set"; +import { Vector } from "./vector"; +import { + LOOKUP_MAP_SCHE, + LOOKUP_SET_SCHE, + UNORDERED_MAP_SCHE, + UNORDERED_SET_SCHE, + VECTOR_SCHE, +} from "./subtype"; /** * A lookup map that stores data in NEAR storage. @@ -32,9 +38,7 @@ export class LookupMap { /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ - subtype(): any { - - } + subtype(): any {} /** * Get the data stored at the provided key. @@ -51,7 +55,7 @@ export class LookupMap { if (options == undefined) { options = {}; } - if (((options.reconstructor == undefined)) && this.subtype() != undefined) { + if (options.reconstructor == undefined && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/near-sdk-js/src/collections/subtype.ts b/packages/near-sdk-js/src/collections/subtype.ts index 7f93db70..741e35e2 100644 --- a/packages/near-sdk-js/src/collections/subtype.ts +++ b/packages/near-sdk-js/src/collections/subtype.ts @@ -1,9 +1,9 @@ -import {GetOptions} from "../types/collections"; -import {LookupMap} from "./lookup-map"; -import {LookupSet} from "./lookup-set"; -import {UnorderedSet} from "./unordered-set"; -import {Vector} from "./vector"; -import {UnorderedMap} from "./unordered-map"; +import { GetOptions } from "../types/collections"; +import { LookupMap } from "./lookup-map"; +import { LookupSet } from "./lookup-set"; +import { UnorderedSet } from "./unordered-set"; +import { Vector } from "./vector"; +import { UnorderedMap } from "./unordered-map"; export const LOOKUP_MAP_SCHE = "lookup_map"; export const LOOKUP_SET_SCHE = "lookup_set"; @@ -12,44 +12,44 @@ export const UNORDERED_SET_SCHE = "unordered_set"; export const VECTOR_SCHE = "vector"; export abstract class SubType { - /* eslint-disable @typescript-eslint/no-explicit-any */ - /* eslint-disable @typescript-eslint/no-empty-function */ - subtype(): any { + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-empty-function */ + subtype(): any {} + set_reconstructor( + options?: Omit, "serializer"> + ): Omit, "serializer"> { + if (options == undefined) { + options = {}; } - - set_reconstructor(options?: Omit, "serializer">): Omit, "serializer"> { - if (options == undefined) { - options = {}; - } - if (((options.reconstructor == undefined)) && this.subtype() != undefined) { - // eslint-disable-next-line no-prototype-builtins - if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = Vector.reconstruct; - } - } - return options; + if (options.reconstructor == undefined && this.subtype() != undefined) { + // eslint-disable-next-line no-prototype-builtins + if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupMap.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = LookupSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = UnorderedSet.reconstruct; + // eslint-disable-next-line no-prototype-builtins + } else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = Vector.reconstruct; + } } -} \ No newline at end of file + return options; + } +} diff --git a/packages/near-sdk-js/src/collections/unordered-map.ts b/packages/near-sdk-js/src/collections/unordered-map.ts index ca011455..b482164a 100644 --- a/packages/near-sdk-js/src/collections/unordered-map.ts +++ b/packages/near-sdk-js/src/collections/unordered-map.ts @@ -10,7 +10,7 @@ import { import { Vector, VectorIterator } from "./vector"; import { LookupMap } from "./lookup-map"; import { GetOptions } from "../types/collections"; -import {SubType} from "./subtype"; +import { SubType } from "./subtype"; type ValueAndIndex = [value: string, index: number]; @@ -93,7 +93,7 @@ export class UnorderedMap extends SubType { const [oldValue, oldIndex] = valueAndIndex; this.values.set(key, [decode(serialized), oldIndex]); - return getValueWithOptions(this.subtype(),encode(oldValue), options); + return getValueWithOptions(this.subtype(), encode(oldValue), options); } /** @@ -127,7 +127,7 @@ export class UnorderedMap extends SubType { this.values.set(swappedKey, [swappedValueAndIndex[0], index]); } - return getValueWithOptions(this.subtype(),encode(value), options); + return getValueWithOptions(this.subtype(), encode(value), options); } /** @@ -254,9 +254,7 @@ class UnorderedMapIterator { /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ - subtype(): any { - - } + subtype(): any {} next(): { value: [string | null, DataType | null]; done: boolean } { const key = this.keys.next(); @@ -273,7 +271,11 @@ class UnorderedMapIterator { done: key.done, value: [ key.value, - getValueWithOptions(this.subtype(), encode(valueAndIndex[0]), this.options), + getValueWithOptions( + this.subtype(), + encode(valueAndIndex[0]), + this.options + ), ], }; } diff --git a/packages/near-sdk-js/src/collections/vector.ts b/packages/near-sdk-js/src/collections/vector.ts index 1459c9c5..887d3ba2 100644 --- a/packages/near-sdk-js/src/collections/vector.ts +++ b/packages/near-sdk-js/src/collections/vector.ts @@ -9,11 +9,17 @@ import { bytes, } from "../utils"; import { GetOptions } from "../types/collections"; -import {UnorderedMap} from "./unordered-map"; -import {LookupMap} from "./lookup-map"; -import {LookupSet} from "./lookup-set"; -import {UnorderedSet} from "./unordered-set"; -import {LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE} from "./subtype"; +import { UnorderedMap } from "./unordered-map"; +import { LookupMap } from "./lookup-map"; +import { LookupSet } from "./lookup-set"; +import { UnorderedSet } from "./unordered-set"; +import { + LOOKUP_MAP_SCHE, + LOOKUP_SET_SCHE, + UNORDERED_MAP_SCHE, + UNORDERED_SET_SCHE, + VECTOR_SCHE, +} from "./subtype"; function indexToKey(prefix: string, index: number): string { const data = new Uint32Array([index]); @@ -43,15 +49,15 @@ export class Vector { /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ - subtype(): any { - - } + subtype(): any {} - set_reconstructor(options?: Omit, "serializer">): Omit, "serializer"> { + set_reconstructor( + options?: Omit, "serializer"> + ): Omit, "serializer"> { if (options == undefined) { options = {}; } - if (((options.reconstructor == undefined)) && this.subtype() != undefined) { + if (options.reconstructor == undefined && this.subtype() != undefined) { // eslint-disable-next-line no-prototype-builtins if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/near-sdk-js/src/near-bindgen.ts b/packages/near-sdk-js/src/near-bindgen.ts index 614f62b9..df1e04a2 100644 --- a/packages/near-sdk-js/src/near-bindgen.ts +++ b/packages/near-sdk-js/src/near-bindgen.ts @@ -1,5 +1,11 @@ import * as near from "./api"; -import { deserialize, serialize, bytes, encode } from "./utils"; +import { + deserialize, + serialize, + bytes, + encode, + decodeObj2class, +} from "./utils"; type EmptyParameterObject = Record; type AnyObject = Record; @@ -210,15 +216,21 @@ export function NearBindgen({ } static _reconstruct(classObject: object, plainObject: AnyObject): object { - for (const item in classObject) { - const reconstructor = classObject[item].constructor?.reconstruct; - - classObject[item] = reconstructor - ? reconstructor(plainObject[item]) - : plainObject[item]; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (classObject.constructor.schema === undefined) { + for (const item in classObject) { + const reconstructor = classObject[item].constructor?.reconstruct; + + classObject[item] = reconstructor + ? reconstructor(plainObject[item]) + : plainObject[item]; + } + + return classObject; } - return classObject; + return decodeObj2class(classObject, plainObject); } static _requireInit(): boolean { diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index ef0853e5..68d03ba4 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -1,6 +1,12 @@ import { GetOptions } from "./types/collections"; -import {LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE} from "./collections"; -import {cloneDeep} from "lodash-es"; +import { + LOOKUP_MAP_SCHE, + LOOKUP_SET_SCHE, + UNORDERED_MAP_SCHE, + UNORDERED_SET_SCHE, + VECTOR_SCHE, +} from "./collections"; +import { cloneDeep } from "lodash-es"; // import lodash from 'lodash'; export interface Env { @@ -108,8 +114,11 @@ export function getValueWithOptions( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const mkey in deserialized) { - if (datatype["map"]["value"] !=='string') { - deserialized[mkey] = decodeObj2class(new datatype["map"]["value"](), value[mkey]); + if (datatype["map"]["value"] !== "string") { + deserialized[mkey] = decodeObj2class( + new datatype["map"]["value"](), + value[mkey] + ); } } // eslint-disable-next-line no-prototype-builtins @@ -118,8 +127,10 @@ export function getValueWithOptions( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const k in deserialized) { - if (datatype["array"]["value"] !=='string') { - new_vec.push(decodeObj2class(new datatype["array"]["value"](), value[k])); + if (datatype["array"]["value"] !== "string") { + new_vec.push( + decodeObj2class(new datatype["array"]["value"](), value[k]) + ); } } deserialized = new_vec; @@ -187,7 +198,10 @@ export function deserialize(valueToDeserialize: Uint8Array): unknown { } export function decodeObj2class(class_instance, obj) { - if (typeof obj != 'object' || class_instance.constructor.schema === undefined) { + if ( + typeof obj != "object" || + class_instance.constructor.schema === undefined + ) { return obj; } let key; @@ -195,26 +209,31 @@ export function decodeObj2class(class_instance, obj) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const value = obj[key]; - if (typeof value == 'object') { + if (typeof value == "object") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const ty = class_instance.constructor.schema[key]; // eslint-disable-next-line no-prototype-builtins if (ty !== undefined && ty.hasOwnProperty("map")) { for (const mkey in value) { - if (ty["map"]["value"]==='string') { + if (ty["map"]["value"] === "string") { class_instance[key][mkey] = value[mkey]; } else { - class_instance[key][mkey] = decodeObj2class(new ty["map"]["value"](), value[mkey]); + class_instance[key][mkey] = decodeObj2class( + new ty["map"]["value"](), + value[mkey] + ); } } // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty("array")) { for (const k in value) { - if (ty["array"]["value"]==='string') { + if (ty["array"]["value"] === "string") { class_instance[key].push(value[k]); } else { - class_instance[key].push(decodeObj2class(new ty["array"]["value"](), value[k])); + class_instance[key].push( + decodeObj2class(new ty["array"]["value"](), value[k]) + ); } } // eslint-disable-next-line no-prototype-builtins @@ -224,7 +243,7 @@ export function decodeObj2class(class_instance, obj) { const subtype_value = ty[UNORDERED_MAP_SCHE]["value"]; class_instance[key].subtype = function () { return subtype_value; - } + }; // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty(VECTOR_SCHE)) { class_instance[key].length = obj[key].length; @@ -232,7 +251,7 @@ export function decodeObj2class(class_instance, obj) { const subtype_value = ty[VECTOR_SCHE]["value"]; class_instance[key].subtype = function () { return subtype_value; - } + }; // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_SET_SCHE)) { class_instance[key]._elements.length = obj[key]._elements.length; @@ -240,24 +259,25 @@ export function decodeObj2class(class_instance, obj) { const subtype_value = ty[UNORDERED_SET_SCHE]["value"]; class_instance[key].subtype = function () { return subtype_value; - } + }; // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_MAP_SCHE)) { class_instance[key].constructor.schema = ty; const subtype_value = ty[LOOKUP_MAP_SCHE]["value"]; class_instance[key].subtype = function () { return subtype_value; - } + }; // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_SET_SCHE)) { class_instance[key].constructor.schema = ty; const subtype_value = ty[LOOKUP_SET_SCHE]["value"]; class_instance[key].subtype = function () { return subtype_value; - } + }; } else { // normal class - class_instance[key].constructor.schema = class_instance.constructor.schema[key]; + class_instance[key].constructor.schema = + class_instance.constructor.schema[key]; class_instance[key] = decodeObj2class(class_instance[key], obj[key]); } } @@ -265,7 +285,10 @@ export function decodeObj2class(class_instance, obj) { const instance_tmp = cloneDeep(class_instance); class_instance = Object.assign(class_instance, obj); for (key in obj) { - if (typeof class_instance[key] == 'object' && !(class_instance[key] instanceof Date)) { + if ( + typeof class_instance[key] == "object" && + !(class_instance[key] instanceof Date) + ) { class_instance[key] = instance_tmp[key]; } } From 868f663c37ad820b25f229ce04c96839e941cdb9 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sun, 10 Dec 2023 14:47:02 +0800 Subject: [PATCH 20/29] optimize: optimize data clone inner decodeObj2class --- packages/near-sdk-js/lib/utils.js | 4 +++- packages/near-sdk-js/src/utils.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index 958a8a77..29d1141a 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -214,9 +214,11 @@ export function decodeObj2class(class_instance, obj) { class_instance[key] = decodeObj2class(class_instance[key], obj[key]); } } + else { + class_instance[key] = obj[key]; + } } const instance_tmp = cloneDeep(class_instance); - class_instance = Object.assign(class_instance, obj); for (key in obj) { if (typeof class_instance[key] == "object" && !(class_instance[key] instanceof Date)) { diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 68d03ba4..73214524 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -280,10 +280,11 @@ export function decodeObj2class(class_instance, obj) { class_instance.constructor.schema[key]; class_instance[key] = decodeObj2class(class_instance[key], obj[key]); } + } else { + class_instance[key] = obj[key]; } } const instance_tmp = cloneDeep(class_instance); - class_instance = Object.assign(class_instance, obj); for (key in obj) { if ( typeof class_instance[key] == "object" && From c43ab160fc8de2c1034c5381a40798958bd25cbf Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sun, 10 Dec 2023 21:16:28 +0800 Subject: [PATCH 21/29] chore: add description for auto reconstruct by json schema --- AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md | 154 +++++++++++++++++++++++ examples/src/status-deserialize-class.js | 2 +- pnpm-lock.yaml | 1 + 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md diff --git a/AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md b/AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md new file mode 100644 index 00000000..7cefc14a --- /dev/null +++ b/AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md @@ -0,0 +1,154 @@ +# Auto reconstruct by json schema +## Problem Solved: Could not decode contract state to class instance in early version of sdk +JS SDK decode contract as utf-8 and parse it as JSON, results in a JS Object. +One thing not intuitive is objects are recovered as Object, not class instance. For example, Assume an instance of this class is stored in contract state: +```typescript +Class Car { + name: string; + speed: number; + + run() { + // ... + } +} +``` +When load it back, the SDK gives us something like: +```json +{"name": "Audi", "speed": 200} +``` +However this is a JS Object, not an instance of Car Class, and therefore you cannot call run method on it. +This also applies to when user passes a JSON argument to a contract method. If the contract is written in TypeScript, although it may look like: +```typescript +add_a_car(car: Car) { + car.run(); // doesn't work + this.some_collection.set(car.name, car); +} +``` +But car.run() doesn't work, because SDK only know how to deserialize it as a plain object, not a Car instance. +This problem is particularly painful when class is nested, for example collection class instance LookupMap containing Car class instance. Currently SDK mitigate this problem by requires user to manually reconstruct the JS object to an instance of the original class. +## A method to decode string to class instance by json schema file +we just need to add static member in the class type. +```typescript +Class Car { + static schema = { + name: "string", + speed: "number", + }; + name: string; + speed: number; + + run() { + // ... + } +} +``` +After we add static member in the class type in our smart contract, it will auto reconstruct smart contract and it's member to class instance recursive by sdk. +And we can call class's functions directly after it deserialized. +```js +add_a_car(car: Car) { + car.run(); // it works! + this.some_collection.set(car.name, car); +} +``` +### The schema format +#### We support multiple type in schema: +* build-in non object types: `string`, `number`, `boolean` +* build-in object types: `date`, `bigint` +* build-in collection types: `array`, `map` + * for `array` type, we need to declare it in the format of `{array: {value: valueType}}` + * for `map` type, we need to declare it in the format of `{map: {key: 'KeyType', value: 'valueType'}}` +* Custom Class types: `Car` or any class types +* Near collection types: `Vector`, `LookupMap`, `LookupSet`, `UnorderedMap`, `UnorderedSet` + * +We have a test example which contains all those types in one schema: [status-deserialize-class.js](./examples/src/status-deserialize-class.js) +```js +class StatusDeserializeClass { + static schema = { + is_inited: "boolean", + records: {map: {key: 'string', value: 'string'}}, + car: Car, + messages: {array: {value: 'string'}}, + efficient_recordes: {unordered_map: {value: 'string'}}, + nested_efficient_recordes: {unordered_map: {value: {unordered_map: {value: 'string'}}}}, + nested_lookup_recordes: {unordered_map: {value: {lookup_map: {value: 'string'}}}}, + vector_nested_group: {vector: {value: {lookup_map: {value: 'string'}}}}, + lookup_nest_vec: {lookup_map: {value: {vector: {value: 'string'}}}}, + unordered_set: {unordered_set: {value: 'string'}}, + user_car_map: {unordered_map: {value: Car}}, + big_num: 'bigint', + date: 'date' + }; + + constructor() { + this.is_inited = false; + this.records = {}; + this.car = new Car(); + this.messages = []; + // account_id -> message + this.efficient_recordes = new UnorderedMap("a"); + // id -> account_id -> message + this.nested_efficient_recordes = new UnorderedMap("b"); + // id -> account_id -> message + this.nested_lookup_recordes = new UnorderedMap("c"); + // index -> account_id -> message + this.vector_nested_group = new Vector("d"); + // account_id -> index -> message + this.lookup_nest_vec = new LookupMap("e"); + this.unordered_set = new UnorderedSet("f"); + this.user_car_map = new UnorderedMap("g"); + this.big_num = 1n; + this.date = new Date(); + } + // other methods +} +``` +#### no need to announce GetOptions.reconstructor in decoding nested collections +In this other hand, after we set schema for the Near collections with nested collections, we don't need to announce `reconstructor` when we need to get and decode a nested collections because the data type info in the schema will tell sdk what the nested data type. +Before we set schema if we need to get a nested collection we need to set `reconstructor` in `GetOptions`: +```typescript +@NearBindgen({}) +export class Contract { + outerMap: UnorderedMap>; + + constructor() { + this.outerMap = new UnorderedMap("o"); + } + + @view({}) + get({id, accountId}: { id: string; accountId: string }) { + const innerMap = this.outerMap.get(id, { + reconstructor: UnorderedMap.reconstruct, // we need to announce reconstructor explicit + }); + if (innerMap === null) { + return null; + } + return innerMap.get(accountId); + } +} +``` +After we set schema info we don't need to set `reconstructor` in `GetOptions`, sdk can infer which reconstructor should be took by the schema: +```typescript +@NearBindgen({}) +export class Contract { + static schema = { + outerMap: {unordered_map: {value: { unordered_map: {value: 'string'}}}} + }; + + outerMap: UnorderedMap>; + + constructor() { + this.outerMap = new UnorderedMap("o"); + } + + @view({}) + get({id, accountId}: { id: string; accountId: string }) { + const innerMap = this.outerMap.get(id, { + reconstructor: UnorderedMap.reconstruct, // we need to announce reconstructor explicit, reconstructor can be infered from static schema + }); + if (innerMap === null) { + return null; + } + return innerMap.get(accountId); + } +} +``` diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 0f3e25eb..93ca1dff 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -27,7 +27,7 @@ class Car { @NearBindgen({}) export class StatusDeserializeClass { static schema = { - is_inited: "bool", + is_inited: "boolean", records: {map: { key: 'string', value: 'string' }}, car: Car, messages: {array: {value: 'string'}}, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbbec4f8..80eba14a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2580,6 +2580,7 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: true /lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} From 8e67878536255c63b05f27f9f1ef7c8e941cdfa9 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Mon, 18 Dec 2023 23:35:10 +0800 Subject: [PATCH 22/29] fix: fix gas use out in nft contract --- .../near-contract-standards/lib/non_fungible_token/impl.js | 4 ++-- .../near-contract-standards/src/non_fungible_token/impl.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/near-contract-standards/lib/non_fungible_token/impl.js b/packages/near-contract-standards/lib/non_fungible_token/impl.js index 5f2634e1..043d6ce8 100644 --- a/packages/near-contract-standards/lib/non_fungible_token/impl.js +++ b/packages/near-contract-standards/lib/non_fungible_token/impl.js @@ -3,9 +3,9 @@ import { TokenMetadata } from "./metadata"; import { refund_storage_deposit, refund_deposit, refund_deposit_to_account, assert_at_least_one_yocto, assert_one_yocto, } from "./utils"; import { NftMint, NftTransfer } from "./events"; import { Token } from "./token"; -const GAS_FOR_RESOLVE_TRANSFER = 15000000000000n; +const GAS_FOR_RESOLVE_TRANSFER = 16000000000000n; const GAS_FOR_NFT_TRANSFER_CALL = 30000000000000n + GAS_FOR_RESOLVE_TRANSFER; -const GAS_FOR_NFT_APPROVE = 20000000000000n; +const GAS_FOR_NFT_APPROVE = 21000000000000n; function repeat(str, n) { return Array(n + 1).join(str); } diff --git a/packages/near-contract-standards/src/non_fungible_token/impl.ts b/packages/near-contract-standards/src/non_fungible_token/impl.ts index c66b7835..20302b7f 100644 --- a/packages/near-contract-standards/src/non_fungible_token/impl.ts +++ b/packages/near-contract-standards/src/non_fungible_token/impl.ts @@ -27,10 +27,10 @@ import { NonFungibleTokenCore } from "./core"; import { NonFungibleTokenApproval } from "./approval"; import { NonFungibleTokenEnumeration } from "./enumeration"; -const GAS_FOR_RESOLVE_TRANSFER = 15_000_000_000_000n; +const GAS_FOR_RESOLVE_TRANSFER = 16_000_000_000_000n; const GAS_FOR_NFT_TRANSFER_CALL = 30_000_000_000_000n + GAS_FOR_RESOLVE_TRANSFER; -const GAS_FOR_NFT_APPROVE = 20_000_000_000_000n; +const GAS_FOR_NFT_APPROVE = 21_000_000_000_000n; function repeat(str: string, n: number) { return Array(n + 1).join(str); From feff2889b9ca992c22c5cada0e9517e6b874ba34 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Fri, 22 Dec 2023 00:44:57 +0800 Subject: [PATCH 23/29] fix: fix handle normal case with nested Class and add testcase --- .../test-status-deserialize-class.ava.js | 11 +++-- examples/src/status-deserialize-class.js | 44 +++++++++++++++---- packages/near-sdk-js/lib/utils.js | 4 +- packages/near-sdk-js/src/utils.ts | 4 +- 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index cfac7167..a7b291ed 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -41,15 +41,18 @@ test("Ali sets then gets status", async (t) => { ); }); -test("Ali set_car_info and get_car_info", async (t) => { +test.only("Ali set_truck_info and get_truck_info", async (t) => { const { ali, statusMessage } = t.context.accounts; let carName = "Mercedes-Benz"; let speed = 240; - await ali.call(statusMessage, "set_car_info", { name: carName, speed: speed }); + await ali.call(statusMessage, "set_truck_info", { name: carName, speed: speed }); + + await ali.call(statusMessage, "add_truck_load", { name: "alice", load: "a box" }); + await ali.call(statusMessage, "add_truck_load", { name: "bob", load: "a packet" }); t.is( - await statusMessage.view("get_car_info", { }), - carName + " run with speed " + speed + await statusMessage.view("get_truck_info", { }), + carName + " run with speed " + speed + " with loads length: 2" ); t.is( diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 93ca1dff..d6e248aa 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -24,12 +24,29 @@ class Car { } } +class Truck { + static schema = { + name: "string", + speed: "number", + loads: {unordered_map: {value: 'string'}} + }; + constructor() { + this.name = ""; + this.speed = 0; + this.loads = new UnorderedMap("tra"); + } + + info() { + return this.name + " run with speed " + this.speed.toString() + " with loads length: " + this.loads.toArray().length; + } +} + @NearBindgen({}) export class StatusDeserializeClass { static schema = { is_inited: "boolean", records: {map: { key: 'string', value: 'string' }}, - car: Car, + truck: Truck, messages: {array: {value: 'string'}}, efficient_recordes: {unordered_map: {value: 'string'}}, nested_efficient_recordes: {unordered_map: {value: { unordered_map: {value: 'string'}}}}, @@ -44,7 +61,7 @@ export class StatusDeserializeClass { constructor() { this.is_inited = false; this.records = {}; - this.car = new Car(); + this.truck = new Truck(); this.messages = []; // account_id -> message this.efficient_recordes = new UnorderedMap("a"); @@ -92,20 +109,31 @@ export class StatusDeserializeClass { @call({}) - set_car_info({ name, speed }) { + set_truck_info({ name, speed }) { let account_id = near.signerAccountId(); - near.log(`${account_id} set_car_info name ${name}, speed ${speed}`); + near.log(`${account_id} set_truck_info name ${name}, speed ${speed}`); + let truck = new Truck(); + truck.name = name; + truck.speed = speed; + truck.loads = this.truck.loads; + this.truck = truck; let car = new Car(); car.name = name; car.speed = speed; - this.car = car; this.user_car_map.set(account_id, car); } + @call({}) + add_truck_load({ name, load }) { + let account_id = near.signerAccountId(); + near.log(`${account_id} add_truck_load name ${name}, load ${load}`); + this.truck.loads.set(name, load); + } + @view({}) - get_car_info({ }) { - near.log(`get_car_info`); - return this.car.info(); + get_truck_info({ }) { + near.log(`get_truck_info`); + return this.truck.info(); } @view({}) diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index 29d1141a..64a988a4 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -208,9 +208,7 @@ export function decodeObj2class(class_instance, obj) { }; } else { - // normal class - class_instance[key].constructor.schema = - class_instance.constructor.schema[key]; + // normal case with nested Class, such as field is truck: Truck, class_instance[key] = decodeObj2class(class_instance[key], obj[key]); } } diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 73214524..67ba35af 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -275,9 +275,7 @@ export function decodeObj2class(class_instance, obj) { return subtype_value; }; } else { - // normal class - class_instance[key].constructor.schema = - class_instance.constructor.schema[key]; + // normal case with nested Class, such as field is truck: Truck, class_instance[key] = decodeObj2class(class_instance[key], obj[key]); } } else { From 24f4e5e7c8d01d899e7a478175d50fab1f2ab879 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 23 Dec 2023 17:08:41 +0800 Subject: [PATCH 24/29] test: add test of schema fields skipped --- AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md | 26 ++++++++++++-- .../test-status-deserialize-class.ava.js | 20 ++++++++++- examples/src/status-deserialize-class.js | 36 +++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md b/AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md index 7cefc14a..b5a9d97c 100644 --- a/AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md +++ b/AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md @@ -53,13 +53,12 @@ add_a_car(car: Car) { ### The schema format #### We support multiple type in schema: * build-in non object types: `string`, `number`, `boolean` -* build-in object types: `date`, `bigint` +* build-in object types: `Date`, `BigInt`. And we can skip those two build-in object types in schema info * build-in collection types: `array`, `map` * for `array` type, we need to declare it in the format of `{array: {value: valueType}}` * for `map` type, we need to declare it in the format of `{map: {key: 'KeyType', value: 'valueType'}}` * Custom Class types: `Car` or any class types -* Near collection types: `Vector`, `LookupMap`, `LookupSet`, `UnorderedMap`, `UnorderedSet` - * +* Near collection types: `Vector`, `LookupMap`, `LookupSet`, `UnorderedMap`, `UnorderedSet` We have a test example which contains all those types in one schema: [status-deserialize-class.js](./examples/src/status-deserialize-class.js) ```js class StatusDeserializeClass { @@ -102,6 +101,27 @@ class StatusDeserializeClass { // other methods } ``` +#### Logic of auto reconstruct by json schema +The `_reconstruct` method in [near-bindgen.ts](./packages/near-sdk-js/src/near-bindgen.ts) will check whether there exit a schema in smart contract class, if there exist a static schema info, it will be decoded to class by invoking `decodeObj2class`, or it will fallback to previous behavior: +```typescript + static _reconstruct(classObject: object, plainObject: AnyObject): object { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (classObject.constructor.schema === undefined) { + for (const item in classObject) { + const reconstructor = classObject[item].constructor?.reconstruct; + + classObject[item] = reconstructor + ? reconstructor(plainObject[item]) + : plainObject[item]; + } + + return classObject; + } + + return decodeObj2class(classObject, plainObject); + } +``` #### no need to announce GetOptions.reconstructor in decoding nested collections In this other hand, after we set schema for the Near collections with nested collections, we don't need to announce `reconstructor` when we need to get and decode a nested collections because the data type info in the schema will tell sdk what the nested data type. Before we set schema if we need to get a nested collection we need to set `reconstructor` in `GetOptions`: diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index a7b291ed..ffcc581f 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -41,7 +41,7 @@ test("Ali sets then gets status", async (t) => { ); }); -test.only("Ali set_truck_info and get_truck_info", async (t) => { +test("Ali set_truck_info and get_truck_info", async (t) => { const { ali, statusMessage } = t.context.accounts; let carName = "Mercedes-Benz"; let speed = 240; @@ -132,6 +132,24 @@ test("Ali set_big_num_and_date then gets", async (t) => { t.is(afterSetDate.toString(), '2023-08-19T23:15:30.000Z'); }); +test("Ali set_extra_data without schema defined then gets", async (t) => { + const { ali, statusMessage } = t.context.accounts; + await ali.call(statusMessage, "set_extra_data", { message: "Hello world!", number: 100 }); + + const messageWithoutSchemaDefined = await statusMessage.view("get_extra_msg", { }); + t.is(messageWithoutSchemaDefined, "Hello world!"); + const numberWithoutSchemaDefined = await statusMessage.view("get_extra_number", { }); + t.is(numberWithoutSchemaDefined, 100); +}); + +test("Ali set_extra_record without schema defined then gets", async (t) => { + const { ali, statusMessage } = t.context.accounts; + await ali.call(statusMessage, "set_extra_record", { message: "Hello world!"}); + + const recordWithoutSchemaDefined = await statusMessage.view("get_extra_record", { account_id: ali.accountId }); + t.is(recordWithoutSchemaDefined, "Hello world!"); +}); + test("View get_subtype_of_efficient_recordes", async (t) => { const { statusMessage } = t.context.accounts; diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index d6e248aa..396cbfc6 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -77,6 +77,9 @@ export class StatusDeserializeClass { this.user_car_map = new UnorderedMap("g"); this.big_num = 1n; this.date = new Date(); + this.message_without_schema_defined = ""; + this.number_without_schema_defined = 0; + this.records_without_schema_defined = {}; } @call({}) @@ -256,6 +259,39 @@ export class StatusDeserializeClass { return this.unordered_set.contains(account_id); } + @call({}) + set_extra_data({ message, number }) { + let account_id = near.signerAccountId(); + near.log(`${account_id} set_extra_data message ${message}, number: ${number}`); + this.message_without_schema_defined = message; + this.number_without_schema_defined = number; + } + + @view({}) + get_extra_msg({ }) { + near.log(`get_extra_msg`); + return this.message_without_schema_defined; + } + + @view({}) + get_extra_number({ }) { + near.log(`get_extra_number`); + return this.number_without_schema_defined; + } + + @call({}) + set_extra_record({ message }) { + let account_id = near.signerAccountId(); + near.log(`${account_id} set_extra_record with message ${message}`); + this.records_without_schema_defined[account_id] = message; + } + + @view({}) + get_extra_record({ account_id }) { + near.log(`get_extra_record for account_id ${account_id}`); + return this.records_without_schema_defined[account_id] || null; + } + @view({}) get_subtype_of_efficient_recordes({ }) { near.log(`get_subtype_of_efficient_recordes`); From 4a0a219f90abb0a38e6e13768e049dfc96043634 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 23 Dec 2023 19:11:34 +0800 Subject: [PATCH 25/29] feat: Vector inherit from SubType and fix gas Exceeded in ubuntu --- .../test-status-deserialize-class.ava.js | 6 +- examples/src/status-deserialize-class.js | 2 +- .../near-sdk-js/lib/collections/vector.d.ts | 5 +- .../near-sdk-js/lib/collections/vector.js | 50 +--------------- .../near-sdk-js/src/collections/vector.ts | 59 ++----------------- 5 files changed, 14 insertions(+), 108 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index ffcc581f..feb56b25 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -76,9 +76,9 @@ test("Ali push_message and get_messages", async (t) => { test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text", async (t) => { const { ali, bob, statusMessage } = t.context.accounts; - await ali.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }); - await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }); - await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "2", message: "world" }); + await ali.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, { gas: 35_000_000_000_000n }); + await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, { gas: 35_000_000_000_000n }); + await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "2", message: "world" }, { gas: 35_000_000_000_000n }); t.is( await statusMessage.view("get_efficient_recordes", { account_id: ali.accountId }), diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 396cbfc6..119c030d 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -209,7 +209,7 @@ export class StatusDeserializeClass { @view({}) get_big_num({ }) { - near.log(`get_big_num}`); + near.log(`get_big_num`); return this.big_num; } diff --git a/packages/near-sdk-js/lib/collections/vector.d.ts b/packages/near-sdk-js/lib/collections/vector.d.ts index 6998913c..ade162e6 100644 --- a/packages/near-sdk-js/lib/collections/vector.d.ts +++ b/packages/near-sdk-js/lib/collections/vector.d.ts @@ -1,9 +1,10 @@ import { GetOptions } from "../types/collections"; +import { SubType } from "./subtype"; /** * An iterable implementation of vector that stores its content on the trie. * Uses the following map: index -> element */ -export declare class Vector { +export declare class Vector extends SubType { readonly prefix: string; length: number; /** @@ -15,8 +16,6 @@ export declare class Vector { * Checks whether the collection is empty. */ isEmpty(): boolean; - subtype(): any; - set_reconstructor(options?: Omit, "serializer">): Omit, "serializer">; /** * Get the data stored at the provided index. * diff --git a/packages/near-sdk-js/lib/collections/vector.js b/packages/near-sdk-js/lib/collections/vector.js index f6f35055..80eb9a54 100644 --- a/packages/near-sdk-js/lib/collections/vector.js +++ b/packages/near-sdk-js/lib/collections/vector.js @@ -1,10 +1,6 @@ import * as near from "../api"; import { assert, getValueWithOptions, serializeValueWithOptions, ERR_INCONSISTENT_STATE, ERR_INDEX_OUT_OF_BOUNDS, str, bytes, } from "../utils"; -import { UnorderedMap } from "./unordered-map"; -import { LookupMap } from "./lookup-map"; -import { LookupSet } from "./lookup-set"; -import { UnorderedSet } from "./unordered-set"; -import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE, } from "./subtype"; +import { SubType } from "./subtype"; function indexToKey(prefix, index) { const data = new Uint32Array([index]); const array = new Uint8Array(data.buffer); @@ -15,12 +11,13 @@ function indexToKey(prefix, index) { * An iterable implementation of vector that stores its content on the trie. * Uses the following map: index -> element */ -export class Vector { +export class Vector extends SubType { /** * @param prefix - The byte prefix to use when storing elements inside this collection. * @param length - The initial length of the collection. By default 0. */ constructor(prefix, length = 0) { + super(); this.prefix = prefix; this.length = length; } @@ -30,47 +27,6 @@ export class Vector { isEmpty() { return this.length === 0; } - /* eslint-disable @typescript-eslint/no-explicit-any */ - /* eslint-disable @typescript-eslint/no-empty-function */ - subtype() { } - set_reconstructor(options) { - if (options == undefined) { - options = {}; - } - if (options.reconstructor == undefined && this.subtype() != undefined) { - // eslint-disable-next-line no-prototype-builtins - if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = Vector.reconstruct; - } - } - return options; - } /** * Get the data stored at the provided index. * diff --git a/packages/near-sdk-js/src/collections/vector.ts b/packages/near-sdk-js/src/collections/vector.ts index 887d3ba2..9b8ca4ac 100644 --- a/packages/near-sdk-js/src/collections/vector.ts +++ b/packages/near-sdk-js/src/collections/vector.ts @@ -9,17 +9,7 @@ import { bytes, } from "../utils"; import { GetOptions } from "../types/collections"; -import { UnorderedMap } from "./unordered-map"; -import { LookupMap } from "./lookup-map"; -import { LookupSet } from "./lookup-set"; -import { UnorderedSet } from "./unordered-set"; -import { - LOOKUP_MAP_SCHE, - LOOKUP_SET_SCHE, - UNORDERED_MAP_SCHE, - UNORDERED_SET_SCHE, - VECTOR_SCHE, -} from "./subtype"; +import { SubType } from "./subtype"; function indexToKey(prefix: string, index: number): string { const data = new Uint32Array([index]); @@ -33,12 +23,14 @@ function indexToKey(prefix: string, index: number): string { * An iterable implementation of vector that stores its content on the trie. * Uses the following map: index -> element */ -export class Vector { +export class Vector extends SubType { /** * @param prefix - The byte prefix to use when storing elements inside this collection. * @param length - The initial length of the collection. By default 0. */ - constructor(readonly prefix: string, public length = 0) {} + constructor(readonly prefix: string, public length = 0) { + super(); + } /** * Checks whether the collection is empty. @@ -47,47 +39,6 @@ export class Vector { return this.length === 0; } - /* eslint-disable @typescript-eslint/no-explicit-any */ - /* eslint-disable @typescript-eslint/no-empty-function */ - subtype(): any {} - - set_reconstructor( - options?: Omit, "serializer"> - ): Omit, "serializer"> { - if (options == undefined) { - options = {}; - } - if (options.reconstructor == undefined && this.subtype() != undefined) { - // eslint-disable-next-line no-prototype-builtins - if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = Vector.reconstruct; - } - } - return options; - } - /** * Get the data stored at the provided index. * From 1b4ec32a526a588fbb02cf6c5be9a4463636c8a1 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Sat, 23 Dec 2023 19:26:30 +0800 Subject: [PATCH 26/29] chore: remove unused lodash --- packages/near-sdk-js/package.json | 1 - packages/near-sdk-js/src/utils.ts | 1 - pnpm-lock.yaml | 3 --- 3 files changed, 5 deletions(-) diff --git a/packages/near-sdk-js/package.json b/packages/near-sdk-js/package.json index ef11d16b..3443c5df 100644 --- a/packages/near-sdk-js/package.json +++ b/packages/near-sdk-js/package.json @@ -46,7 +46,6 @@ "commander": "^9.4.1", "eslint": "^8.20.0", "json-schema": "0.4.0", - "lodash": "^4.17.21", "lodash-es": "^4.17.21", "near-abi": "^0.1.0", "near-typescript-json-schema": "0.55.0", diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 67ba35af..0b244a4c 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -7,7 +7,6 @@ import { VECTOR_SCHE, } from "./collections"; import { cloneDeep } from "lodash-es"; -// import lodash from 'lodash'; export interface Env { uint8array_to_latin1_string(a: Uint8Array): string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80eba14a..744add28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,9 +125,6 @@ importers: json-schema: specifier: 0.4.0 version: 0.4.0 - lodash: - specifier: ^4.17.21 - version: 4.17.21 lodash-es: specifier: ^4.17.21 version: 4.17.21 From f4b8967079abfeed2d220fc7b71c48b219523c80 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Tue, 26 Dec 2023 00:10:48 +0800 Subject: [PATCH 27/29] feat: add reconstructor in schema and update different collection types to colletion --- .../test-status-deserialize-class.ava.js | 2 +- examples/src/status-deserialize-class.js | 16 +++---- packages/near-sdk-js/lib/utils.js | 40 ++---------------- packages/near-sdk-js/src/utils.ts | 42 ++----------------- 4 files changed, 15 insertions(+), 85 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index feb56b25..f52d92d2 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -164,6 +164,6 @@ test("View get_subtype_of_nested_efficient_recordes", async (t) => { t.is( JSON.stringify(await statusMessage.view("get_subtype_of_nested_efficient_recordes", { })), - '{"unordered_map":{"value":"string"}}' + '{"collection":{"value":"string"}}' ); }); \ No newline at end of file diff --git a/examples/src/status-deserialize-class.js b/examples/src/status-deserialize-class.js index 119c030d..270cd1b8 100644 --- a/examples/src/status-deserialize-class.js +++ b/examples/src/status-deserialize-class.js @@ -28,7 +28,7 @@ class Truck { static schema = { name: "string", speed: "number", - loads: {unordered_map: {value: 'string'}} + loads: {collection: {reconstructor: UnorderedMap.reconstruct, value: 'string'}} }; constructor() { this.name = ""; @@ -48,13 +48,13 @@ export class StatusDeserializeClass { records: {map: { key: 'string', value: 'string' }}, truck: Truck, messages: {array: {value: 'string'}}, - efficient_recordes: {unordered_map: {value: 'string'}}, - nested_efficient_recordes: {unordered_map: {value: { unordered_map: {value: 'string'}}}}, - nested_lookup_recordes: {unordered_map: {value: { lookup_map: {value: 'string'}}}}, - vector_nested_group: {vector: {value: { lookup_map: {value: 'string'}}}}, - lookup_nest_vec: {lookup_map: {value: { vector: { value: 'string' }}}}, - unordered_set: {unordered_set: {value: 'string'}}, - user_car_map: {unordered_map: {value: Car }}, + efficient_recordes: {collection: {reconstructor: UnorderedMap.reconstruct, value: 'string'}}, + nested_efficient_recordes: {collection: {reconstructor: UnorderedMap.reconstruct, value: { collection: {reconstructor: UnorderedMap.reconstruct, value: 'string'}}}}, + nested_lookup_recordes: {collection: {reconstructor: UnorderedMap.reconstruct, value: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}}}}, + vector_nested_group: {collection: {reconstructor: Vector.reconstruct, value: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}}}}, + lookup_nest_vec: {collection: {reconstructor: LookupMap.reconstruct, value: { collection: { reconstructor: Vector.reconstruct, value: 'string' }}}}, + unordered_set: {collection: {reconstructor: UnorderedSet.reconstruct, value: 'string'}}, + user_car_map: {collection: {reconstructor: UnorderedMap.reconstruct, value: Car }}, big_num: 'bigint', date: 'date' }; diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index 64a988a4..5d95f187 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -1,4 +1,3 @@ -import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE, } from "./collections"; import { cloneDeep } from "lodash-es"; // make PromiseIndex a nominal typing var PromiseIndexBrand; @@ -165,48 +164,15 @@ export function decodeObj2class(class_instance, obj) { } // eslint-disable-next-line no-prototype-builtins } - else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_MAP_SCHE)) { - class_instance[key]._keys.length = obj[key]._keys.length; + else if (ty !== undefined && ty.hasOwnProperty("collection")) { + class_instance[key] = ty["collection"]["reconstructor"](obj[key]); class_instance[key].constructor.schema = ty; - const subtype_value = ty[UNORDERED_MAP_SCHE]["value"]; + const subtype_value = ty["collection"]["value"]; class_instance[key].subtype = function () { return subtype_value; }; // eslint-disable-next-line no-prototype-builtins } - else if (ty !== undefined && ty.hasOwnProperty(VECTOR_SCHE)) { - class_instance[key].length = obj[key].length; - class_instance[key].constructor.schema = ty; - const subtype_value = ty[VECTOR_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - }; - // eslint-disable-next-line no-prototype-builtins - } - else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_SET_SCHE)) { - class_instance[key]._elements.length = obj[key]._elements.length; - class_instance[key].constructor.schema = ty; - const subtype_value = ty[UNORDERED_SET_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - }; - // eslint-disable-next-line no-prototype-builtins - } - else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_MAP_SCHE)) { - class_instance[key].constructor.schema = ty; - const subtype_value = ty[LOOKUP_MAP_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - }; - // eslint-disable-next-line no-prototype-builtins - } - else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_SET_SCHE)) { - class_instance[key].constructor.schema = ty; - const subtype_value = ty[LOOKUP_SET_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - }; - } else { // normal case with nested Class, such as field is truck: Truck, class_instance[key] = decodeObj2class(class_instance[key], obj[key]); diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 0b244a4c..7666cc2a 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -1,11 +1,4 @@ import { GetOptions } from "./types/collections"; -import { - LOOKUP_MAP_SCHE, - LOOKUP_SET_SCHE, - UNORDERED_MAP_SCHE, - UNORDERED_SET_SCHE, - VECTOR_SCHE, -} from "./collections"; import { cloneDeep } from "lodash-es"; export interface Env { @@ -236,43 +229,14 @@ export function decodeObj2class(class_instance, obj) { } } // eslint-disable-next-line no-prototype-builtins - } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_MAP_SCHE)) { - class_instance[key]._keys.length = obj[key]._keys.length; + } else if (ty !== undefined && ty.hasOwnProperty("collection")) { + class_instance[key] = ty["collection"]["reconstructor"](obj[key]); class_instance[key].constructor.schema = ty; - const subtype_value = ty[UNORDERED_MAP_SCHE]["value"]; + const subtype_value = ty["collection"]["value"]; class_instance[key].subtype = function () { return subtype_value; }; // eslint-disable-next-line no-prototype-builtins - } else if (ty !== undefined && ty.hasOwnProperty(VECTOR_SCHE)) { - class_instance[key].length = obj[key].length; - class_instance[key].constructor.schema = ty; - const subtype_value = ty[VECTOR_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - }; - // eslint-disable-next-line no-prototype-builtins - } else if (ty !== undefined && ty.hasOwnProperty(UNORDERED_SET_SCHE)) { - class_instance[key]._elements.length = obj[key]._elements.length; - class_instance[key].constructor.schema = ty; - const subtype_value = ty[UNORDERED_SET_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - }; - // eslint-disable-next-line no-prototype-builtins - } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_MAP_SCHE)) { - class_instance[key].constructor.schema = ty; - const subtype_value = ty[LOOKUP_MAP_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - }; - // eslint-disable-next-line no-prototype-builtins - } else if (ty !== undefined && ty.hasOwnProperty(LOOKUP_SET_SCHE)) { - class_instance[key].constructor.schema = ty; - const subtype_value = ty[LOOKUP_SET_SCHE]["value"]; - class_instance[key].subtype = function () { - return subtype_value; - }; } else { // normal case with nested Class, such as field is truck: Truck, class_instance[key] = decodeObj2class(class_instance[key], obj[key]); From 884cc826c7747972f06619af662093ced3a63974 Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Wed, 27 Dec 2023 00:06:10 +0800 Subject: [PATCH 28/29] feat: update check subtype logic --- .../test-status-deserialize-class.ava.js | 2 +- .../near-sdk-js/lib/collections/subtype.js | 42 +++------------- packages/near-sdk-js/lib/utils.d.ts | 2 +- packages/near-sdk-js/lib/utils.js | 44 +++++++++++------ .../near-sdk-js/src/collections/subtype.ts | 42 ++++------------ packages/near-sdk-js/src/utils.ts | 48 +++++++++++++------ 6 files changed, 82 insertions(+), 98 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index f52d92d2..0d249f99 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -74,7 +74,7 @@ test("Ali push_message and get_messages", async (t) => { ); }); -test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text", async (t) => { +test.only("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text", async (t) => { const { ali, bob, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, { gas: 35_000_000_000_000n }); await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, { gas: 35_000_000_000_000n }); diff --git a/packages/near-sdk-js/lib/collections/subtype.js b/packages/near-sdk-js/lib/collections/subtype.js index 56106f19..28667e0b 100644 --- a/packages/near-sdk-js/lib/collections/subtype.js +++ b/packages/near-sdk-js/lib/collections/subtype.js @@ -1,8 +1,3 @@ -import { LookupMap } from "./lookup-map"; -import { LookupSet } from "./lookup-set"; -import { UnorderedSet } from "./unordered-set"; -import { Vector } from "./vector"; -import { UnorderedMap } from "./unordered-map"; export const LOOKUP_MAP_SCHE = "lookup_map"; export const LOOKUP_SET_SCHE = "lookup_set"; export const UNORDERED_MAP_SCHE = "unordered_map"; @@ -16,37 +11,12 @@ export class SubType { if (options == undefined) { options = {}; } - if (options.reconstructor == undefined && this.subtype() != undefined) { - // eslint-disable-next-line no-prototype-builtins - if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = Vector.reconstruct; - } + // eslint-disable-next-line no-prototype-builtins + if (options.reconstructor == undefined && this.subtype() != undefined && this.subtype().hasOwnProperty("collection")) { + // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = this.subtype()["reconstructor"]; } return options; } diff --git a/packages/near-sdk-js/lib/utils.d.ts b/packages/near-sdk-js/lib/utils.d.ts index 4b2a343f..805f92b7 100644 --- a/packages/near-sdk-js/lib/utils.d.ts +++ b/packages/near-sdk-js/lib/utils.d.ts @@ -39,7 +39,7 @@ export declare function assert(expression: unknown, message: string): asserts ex export declare type Mutable = { -readonly [P in keyof T]: T[P]; }; -export declare function getValueWithOptions(datatype: unknown, value: Uint8Array | null, options?: Omit, "serializer">): DataType | null; +export declare function getValueWithOptions(subDatatype: unknown, value: Uint8Array | null, options?: Omit, "serializer">): DataType | null; export declare function serializeValueWithOptions(value: DataType, { serializer }?: Pick, "serializer">): Uint8Array; export declare function serialize(valueToSerialize: unknown): Uint8Array; export declare function deserialize(valueToDeserialize: Uint8Array): unknown; diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index 5d95f187..d734297b 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -1,4 +1,5 @@ import { cloneDeep } from "lodash-es"; +import * as near from "./api"; // make PromiseIndex a nominal typing var PromiseIndexBrand; (function (PromiseIndexBrand) { @@ -36,7 +37,7 @@ export function assert(expression, message) { throw new Error("assertion failed: " + message); } } -export function getValueWithOptions(datatype, value, options = { +export function getValueWithOptions(subDatatype, value, options = { deserializer: deserialize, }) { if (value === null) { @@ -48,36 +49,49 @@ export function getValueWithOptions(datatype, value, options = { return options?.defaultValue ?? null; } if (options?.reconstructor) { - return options.reconstructor(deserialized); + near.log(deserialized); + // example: // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} + const collection = options.reconstructor(deserialized); + // eslint-disable-next-line no-prototype-builtins + if (subDatatype !== undefined && subDatatype.hasOwnProperty("collection") && subDatatype["collection"].hasOwnProperty("value")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + collection.subtype = function () { + // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} + return subDatatype["collection"]["value"]; + }; + } + return collection; } - if (datatype !== undefined) { - // subtype info is a class constructor - if (typeof datatype === "function") { + // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} + if (subDatatype !== undefined) { + // subtype info is a class constructor, Such as Car + if (typeof subDatatype === "function") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - deserialized = decodeObj2class(new datatype(), deserialized); + deserialized = decodeObj2class(new subDatatype(), deserialized); } - else if (typeof datatype === "object") { + else if (typeof subDatatype === "object") { // normal collections of array, map; subtype will be: // {map: { key: 'string', value: 'string' }} or {array: {value: 'string'}} .. // eslint-disable-next-line no-prototype-builtins - if (datatype.hasOwnProperty("map")) { + if (subDatatype.hasOwnProperty("map")) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const mkey in deserialized) { - if (datatype["map"]["value"] !== "string") { - deserialized[mkey] = decodeObj2class(new datatype["map"]["value"](), value[mkey]); + if (subDatatype["map"]["value"] !== "string") { + deserialized[mkey] = decodeObj2class(new subDatatype["map"]["value"](), value[mkey]); } } // eslint-disable-next-line no-prototype-builtins } - else if (datatype.hasOwnProperty("array")) { + else if (subDatatype.hasOwnProperty("array")) { const new_vec = []; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const k in deserialized) { - if (datatype["array"]["value"] !== "string") { - new_vec.push(decodeObj2class(new datatype["array"]["value"](), value[k])); + if (subDatatype["array"]["value"] !== "string") { + new_vec.push(decodeObj2class(new subDatatype["array"]["value"](), value[k])); } } deserialized = new_vec; @@ -165,10 +179,12 @@ export function decodeObj2class(class_instance, obj) { // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty("collection")) { + // nested_lookup_recordes: {collection: {reconstructor: UnorderedMap.reconstruct, value: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}}}}, + // {collection: {reconstructor: class_instance[key] = ty["collection"]["reconstructor"](obj[key]); - class_instance[key].constructor.schema = ty; const subtype_value = ty["collection"]["value"]; class_instance[key].subtype = function () { + // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} return subtype_value; }; // eslint-disable-next-line no-prototype-builtins diff --git a/packages/near-sdk-js/src/collections/subtype.ts b/packages/near-sdk-js/src/collections/subtype.ts index 741e35e2..9b95228d 100644 --- a/packages/near-sdk-js/src/collections/subtype.ts +++ b/packages/near-sdk-js/src/collections/subtype.ts @@ -1,9 +1,4 @@ import { GetOptions } from "../types/collections"; -import { LookupMap } from "./lookup-map"; -import { LookupSet } from "./lookup-set"; -import { UnorderedSet } from "./unordered-set"; -import { Vector } from "./vector"; -import { UnorderedMap } from "./unordered-map"; export const LOOKUP_MAP_SCHE = "lookup_map"; export const LOOKUP_SET_SCHE = "lookup_set"; @@ -22,33 +17,16 @@ export abstract class SubType { if (options == undefined) { options = {}; } - if (options.reconstructor == undefined && this.subtype() != undefined) { - // eslint-disable-next-line no-prototype-builtins - if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = Vector.reconstruct; - } + // eslint-disable-next-line no-prototype-builtins + if ( + options.reconstructor == undefined && + this.subtype() != undefined && + this.subtype().hasOwnProperty("collection") + ) { + // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + options.reconstructor = this.subtype()["reconstructor"]; } return options; } diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 7666cc2a..07b367e8 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -1,5 +1,6 @@ import { GetOptions } from "./types/collections"; import { cloneDeep } from "lodash-es"; +import * as near from "./api"; export interface Env { uint8array_to_latin1_string(a: Uint8Array): string; @@ -71,7 +72,7 @@ export function assert( export type Mutable = { -readonly [P in keyof T]: T[P] }; export function getValueWithOptions( - datatype: unknown, + subDatatype: unknown, value: Uint8Array | null, options: Omit, "serializer"> = { deserializer: deserialize, @@ -89,39 +90,56 @@ export function getValueWithOptions( } if (options?.reconstructor) { - return options.reconstructor(deserialized); + near.log(deserialized); + // example: // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} + const collection = options.reconstructor(deserialized); + // eslint-disable-next-line no-prototype-builtins + if ( + subDatatype !== undefined && + subDatatype.hasOwnProperty("collection") && + subDatatype["collection"].hasOwnProperty("value") + ) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + collection.subtype = function () { + // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} + return subDatatype["collection"]["value"]; + }; + } + return collection; } - if (datatype !== undefined) { - // subtype info is a class constructor - if (typeof datatype === "function") { + // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} + if (subDatatype !== undefined) { + // subtype info is a class constructor, Such as Car + if (typeof subDatatype === "function") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - deserialized = decodeObj2class(new datatype(), deserialized); - } else if (typeof datatype === "object") { + deserialized = decodeObj2class(new subDatatype(), deserialized); + } else if (typeof subDatatype === "object") { // normal collections of array, map; subtype will be: // {map: { key: 'string', value: 'string' }} or {array: {value: 'string'}} .. // eslint-disable-next-line no-prototype-builtins - if (datatype.hasOwnProperty("map")) { + if (subDatatype.hasOwnProperty("map")) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const mkey in deserialized) { - if (datatype["map"]["value"] !== "string") { + if (subDatatype["map"]["value"] !== "string") { deserialized[mkey] = decodeObj2class( - new datatype["map"]["value"](), + new subDatatype["map"]["value"](), value[mkey] ); } } // eslint-disable-next-line no-prototype-builtins - } else if (datatype.hasOwnProperty("array")) { + } else if (subDatatype.hasOwnProperty("array")) { const new_vec = []; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const k in deserialized) { - if (datatype["array"]["value"] !== "string") { + if (subDatatype["array"]["value"] !== "string") { new_vec.push( - decodeObj2class(new datatype["array"]["value"](), value[k]) + decodeObj2class(new subDatatype["array"]["value"](), value[k]) ); } } @@ -230,10 +248,12 @@ export function decodeObj2class(class_instance, obj) { } // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty("collection")) { + // nested_lookup_recordes: {collection: {reconstructor: UnorderedMap.reconstruct, value: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}}}}, + // {collection: {reconstructor: class_instance[key] = ty["collection"]["reconstructor"](obj[key]); - class_instance[key].constructor.schema = ty; const subtype_value = ty["collection"]["value"]; class_instance[key].subtype = function () { + // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} return subtype_value; }; // eslint-disable-next-line no-prototype-builtins From c8baf76af5169101daea23c8405e15d455d0499f Mon Sep 17 00:00:00 2001 From: Spring Chiu Date: Thu, 28 Dec 2023 00:49:03 +0800 Subject: [PATCH 29/29] fix: fix reconstructor undefine error --- .../test-status-deserialize-class.ava.js | 2 +- .../lib/collections/lookup-map.d.ts | 4 +- .../near-sdk-js/lib/collections/lookup-map.js | 45 ++-------------- .../near-sdk-js/lib/collections/subtype.d.ts | 5 -- .../near-sdk-js/lib/collections/subtype.js | 15 +++--- packages/near-sdk-js/lib/utils.js | 10 ++-- .../near-sdk-js/src/collections/lookup-map.ts | 51 +++---------------- .../near-sdk-js/src/collections/subtype.ts | 16 +++--- packages/near-sdk-js/src/utils.ts | 6 +-- 9 files changed, 33 insertions(+), 121 deletions(-) diff --git a/examples/__tests__/test-status-deserialize-class.ava.js b/examples/__tests__/test-status-deserialize-class.ava.js index 0d249f99..f52d92d2 100644 --- a/examples/__tests__/test-status-deserialize-class.ava.js +++ b/examples/__tests__/test-status-deserialize-class.ava.js @@ -74,7 +74,7 @@ test("Ali push_message and get_messages", async (t) => { ); }); -test.only("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text", async (t) => { +test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text", async (t) => { const { ali, bob, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, { gas: 35_000_000_000_000n }); await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, { gas: 35_000_000_000_000n }); diff --git a/packages/near-sdk-js/lib/collections/lookup-map.d.ts b/packages/near-sdk-js/lib/collections/lookup-map.d.ts index fff2958e..36be477a 100644 --- a/packages/near-sdk-js/lib/collections/lookup-map.d.ts +++ b/packages/near-sdk-js/lib/collections/lookup-map.d.ts @@ -1,8 +1,9 @@ import { GetOptions } from "../types/collections"; +import { SubType } from "./subtype"; /** * A lookup map that stores data in NEAR storage. */ -export declare class LookupMap { +export declare class LookupMap extends SubType { readonly keyPrefix: string; /** * @param keyPrefix - The byte prefix to use when storing elements inside this collection. @@ -14,7 +15,6 @@ export declare class LookupMap { * @param key - The value for which to check the presence. */ containsKey(key: string): boolean; - subtype(): any; /** * Get the data stored at the provided key. * diff --git a/packages/near-sdk-js/lib/collections/lookup-map.js b/packages/near-sdk-js/lib/collections/lookup-map.js index 5ffa5f96..0a5548eb 100644 --- a/packages/near-sdk-js/lib/collections/lookup-map.js +++ b/packages/near-sdk-js/lib/collections/lookup-map.js @@ -1,18 +1,15 @@ import * as near from "../api"; import { getValueWithOptions, serializeValueWithOptions, encode, } from "../utils"; -import { UnorderedMap } from "./unordered-map"; -import { LookupSet } from "./lookup-set"; -import { UnorderedSet } from "./unordered-set"; -import { Vector } from "./vector"; -import { LOOKUP_MAP_SCHE, LOOKUP_SET_SCHE, UNORDERED_MAP_SCHE, UNORDERED_SET_SCHE, VECTOR_SCHE, } from "./subtype"; +import { SubType } from "./subtype"; /** * A lookup map that stores data in NEAR storage. */ -export class LookupMap { +export class LookupMap extends SubType { /** * @param keyPrefix - The byte prefix to use when storing elements inside this collection. */ constructor(keyPrefix) { + super(); this.keyPrefix = keyPrefix; } /** @@ -24,9 +21,6 @@ export class LookupMap { const storageKey = this.keyPrefix + key; return near.storageHasKey(storageKey); } - /* eslint-disable @typescript-eslint/no-explicit-any */ - /* eslint-disable @typescript-eslint/no-empty-function */ - subtype() { } /** * Get the data stored at the provided key. * @@ -39,38 +33,7 @@ export class LookupMap { if (options == undefined) { options = {}; } - if (options.reconstructor == undefined && this.subtype() != undefined) { - // eslint-disable-next-line no-prototype-builtins - if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } - else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = Vector.reconstruct; - } - } + options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } /** diff --git a/packages/near-sdk-js/lib/collections/subtype.d.ts b/packages/near-sdk-js/lib/collections/subtype.d.ts index 7f279c45..b43bf1cf 100644 --- a/packages/near-sdk-js/lib/collections/subtype.d.ts +++ b/packages/near-sdk-js/lib/collections/subtype.d.ts @@ -1,9 +1,4 @@ import { GetOptions } from "../types/collections"; -export declare const LOOKUP_MAP_SCHE = "lookup_map"; -export declare const LOOKUP_SET_SCHE = "lookup_set"; -export declare const UNORDERED_MAP_SCHE = "unordered_map"; -export declare const UNORDERED_SET_SCHE = "unordered_set"; -export declare const VECTOR_SCHE = "vector"; export declare abstract class SubType { subtype(): any; set_reconstructor(options?: Omit, "serializer">): Omit, "serializer">; diff --git a/packages/near-sdk-js/lib/collections/subtype.js b/packages/near-sdk-js/lib/collections/subtype.js index 28667e0b..af87b68c 100644 --- a/packages/near-sdk-js/lib/collections/subtype.js +++ b/packages/near-sdk-js/lib/collections/subtype.js @@ -1,8 +1,3 @@ -export const LOOKUP_MAP_SCHE = "lookup_map"; -export const LOOKUP_SET_SCHE = "lookup_set"; -export const UNORDERED_MAP_SCHE = "unordered_map"; -export const UNORDERED_SET_SCHE = "unordered_set"; -export const VECTOR_SCHE = "vector"; export class SubType { /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ @@ -11,12 +6,16 @@ export class SubType { if (options == undefined) { options = {}; } - // eslint-disable-next-line no-prototype-builtins - if (options.reconstructor == undefined && this.subtype() != undefined && this.subtype().hasOwnProperty("collection")) { + const subtype = this.subtype(); + if (options.reconstructor == undefined && + subtype != undefined && + // eslint-disable-next-line no-prototype-builtins + subtype.hasOwnProperty("collection") && + typeof this.subtype().collection.reconstructor === "function") { // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - options.reconstructor = this.subtype()["reconstructor"]; + options.reconstructor = this.subtype().collection.reconstructor; } return options; } diff --git a/packages/near-sdk-js/lib/utils.js b/packages/near-sdk-js/lib/utils.js index d734297b..45e34e83 100644 --- a/packages/near-sdk-js/lib/utils.js +++ b/packages/near-sdk-js/lib/utils.js @@ -1,5 +1,4 @@ import { cloneDeep } from "lodash-es"; -import * as near from "./api"; // make PromiseIndex a nominal typing var PromiseIndexBrand; (function (PromiseIndexBrand) { @@ -49,11 +48,13 @@ export function getValueWithOptions(subDatatype, value, options = { return options?.defaultValue ?? null; } if (options?.reconstructor) { - near.log(deserialized); // example: // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} const collection = options.reconstructor(deserialized); - // eslint-disable-next-line no-prototype-builtins - if (subDatatype !== undefined && subDatatype.hasOwnProperty("collection") && subDatatype["collection"].hasOwnProperty("value")) { + if (subDatatype !== undefined && + // eslint-disable-next-line no-prototype-builtins + subDatatype.hasOwnProperty("collection") && + // eslint-disable-next-line no-prototype-builtins + subDatatype["collection"].hasOwnProperty("value")) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore collection.subtype = function () { @@ -187,7 +188,6 @@ export function decodeObj2class(class_instance, obj) { // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} return subtype_value; }; - // eslint-disable-next-line no-prototype-builtins } else { // normal case with nested Class, such as field is truck: Truck, diff --git a/packages/near-sdk-js/src/collections/lookup-map.ts b/packages/near-sdk-js/src/collections/lookup-map.ts index 0e9b7c82..4bb653e3 100644 --- a/packages/near-sdk-js/src/collections/lookup-map.ts +++ b/packages/near-sdk-js/src/collections/lookup-map.ts @@ -5,26 +5,18 @@ import { serializeValueWithOptions, encode, } from "../utils"; -import { UnorderedMap } from "./unordered-map"; -import { LookupSet } from "./lookup-set"; -import { UnorderedSet } from "./unordered-set"; -import { Vector } from "./vector"; -import { - LOOKUP_MAP_SCHE, - LOOKUP_SET_SCHE, - UNORDERED_MAP_SCHE, - UNORDERED_SET_SCHE, - VECTOR_SCHE, -} from "./subtype"; +import { SubType } from "./subtype"; /** * A lookup map that stores data in NEAR storage. */ -export class LookupMap { +export class LookupMap extends SubType { /** * @param keyPrefix - The byte prefix to use when storing elements inside this collection. */ - constructor(readonly keyPrefix: string) {} + constructor(readonly keyPrefix: string) { + super(); + } /** * Checks whether the collection contains the value. @@ -36,10 +28,6 @@ export class LookupMap { return near.storageHasKey(storageKey); } - /* eslint-disable @typescript-eslint/no-explicit-any */ - /* eslint-disable @typescript-eslint/no-empty-function */ - subtype(): any {} - /** * Get the data stored at the provided key. * @@ -55,34 +43,7 @@ export class LookupMap { if (options == undefined) { options = {}; } - if (options.reconstructor == undefined && this.subtype() != undefined) { - // eslint-disable-next-line no-prototype-builtins - if (this.subtype().hasOwnProperty(UNORDERED_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(LOOKUP_MAP_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupMap.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(LOOKUP_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = LookupSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(UNORDERED_SET_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = UnorderedSet.reconstruct; - // eslint-disable-next-line no-prototype-builtins - } else if (this.subtype().hasOwnProperty(VECTOR_SCHE)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - options.reconstructor = Vector.reconstruct; - } - } + options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } diff --git a/packages/near-sdk-js/src/collections/subtype.ts b/packages/near-sdk-js/src/collections/subtype.ts index 9b95228d..1b180162 100644 --- a/packages/near-sdk-js/src/collections/subtype.ts +++ b/packages/near-sdk-js/src/collections/subtype.ts @@ -1,11 +1,5 @@ import { GetOptions } from "../types/collections"; -export const LOOKUP_MAP_SCHE = "lookup_map"; -export const LOOKUP_SET_SCHE = "lookup_set"; -export const UNORDERED_MAP_SCHE = "unordered_map"; -export const UNORDERED_SET_SCHE = "unordered_set"; -export const VECTOR_SCHE = "vector"; - export abstract class SubType { /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ @@ -17,16 +11,18 @@ export abstract class SubType { if (options == undefined) { options = {}; } - // eslint-disable-next-line no-prototype-builtins + const subtype = this.subtype(); if ( options.reconstructor == undefined && - this.subtype() != undefined && - this.subtype().hasOwnProperty("collection") + subtype != undefined && + // eslint-disable-next-line no-prototype-builtins + subtype.hasOwnProperty("collection") && + typeof this.subtype().collection.reconstructor === "function" ) { // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - options.reconstructor = this.subtype()["reconstructor"]; + options.reconstructor = this.subtype().collection.reconstructor; } return options; } diff --git a/packages/near-sdk-js/src/utils.ts b/packages/near-sdk-js/src/utils.ts index 07b367e8..f611e0d2 100644 --- a/packages/near-sdk-js/src/utils.ts +++ b/packages/near-sdk-js/src/utils.ts @@ -1,6 +1,5 @@ import { GetOptions } from "./types/collections"; import { cloneDeep } from "lodash-es"; -import * as near from "./api"; export interface Env { uint8array_to_latin1_string(a: Uint8Array): string; @@ -90,13 +89,13 @@ export function getValueWithOptions( } if (options?.reconstructor) { - near.log(deserialized); // example: // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} const collection = options.reconstructor(deserialized); - // eslint-disable-next-line no-prototype-builtins if ( subDatatype !== undefined && + // eslint-disable-next-line no-prototype-builtins subDatatype.hasOwnProperty("collection") && + // eslint-disable-next-line no-prototype-builtins subDatatype["collection"].hasOwnProperty("value") ) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -256,7 +255,6 @@ export function decodeObj2class(class_instance, obj) { // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} return subtype_value; }; - // eslint-disable-next-line no-prototype-builtins } else { // normal case with nested Class, such as field is truck: Truck, class_instance[key] = decodeObj2class(class_instance[key], obj[key]);