From 1b1365ebf1179024d351f0b5d69b9ac2ed8fd4c3 Mon Sep 17 00:00:00 2001 From: Jijun Leng Date: Wed, 14 Sep 2022 09:29:59 -0700 Subject: [PATCH] refactor(ts-sdk): get rid of typescript-memoize dep --- ecosystem/typescript/sdk/jest.config.js | 2 +- ecosystem/typescript/sdk/package.json | 3 +- ecosystem/typescript/sdk/src/aptos_account.ts | 2 +- .../typescript/sdk/src/aptos_client.test.ts | 2 +- ecosystem/typescript/sdk/src/aptos_client.ts | 3 +- .../typescript/sdk/src/coin_client.test.ts | 2 +- .../typescript/sdk/src/faucet_client.test.ts | 2 +- .../typescript/sdk/src/token_client.test.ts | 2 +- .../sdk/src/transaction_builder/builder.ts | 2 +- ecosystem/typescript/sdk/src/utils/index.ts | 2 + .../sdk/src/utils/memoize-decorator.ts | 148 ++++++++++++++++++ .../src/{util.test.ts => utils/misc.test.ts} | 12 +- .../sdk/src/{util.ts => utils/misc.ts} | 0 .../sdk/src/utils/test_helper.test.ts | 8 + 14 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 ecosystem/typescript/sdk/src/utils/index.ts create mode 100644 ecosystem/typescript/sdk/src/utils/memoize-decorator.ts rename ecosystem/typescript/sdk/src/{util.test.ts => utils/misc.test.ts} (59%) rename ecosystem/typescript/sdk/src/{util.ts => utils/misc.ts} (100%) create mode 100644 ecosystem/typescript/sdk/src/utils/test_helper.test.ts diff --git a/ecosystem/typescript/sdk/jest.config.js b/ecosystem/typescript/sdk/jest.config.js index e79dcb6de6709..46a62de826115 100644 --- a/ecosystem/typescript/sdk/jest.config.js +++ b/ecosystem/typescript/sdk/jest.config.js @@ -5,7 +5,7 @@ module.exports = { "^(\\.{1,2}/.*)\\.js$": "$1", }, testEnvironment: "node", - coveragePathIgnorePatterns: ["generated/*", "transaction_builder/aptos_types/*"], + coveragePathIgnorePatterns: ["generated/*", "transaction_builder/aptos_types/*", "utils/memoize-decorator.ts"], testPathIgnorePatterns: ["dist/*"], collectCoverage: true, setupFiles: ["dotenv/config"], diff --git a/ecosystem/typescript/sdk/package.json b/ecosystem/typescript/sdk/package.json index 6b7407efa4b5a..4415ec93eab42 100644 --- a/ecosystem/typescript/sdk/package.json +++ b/ecosystem/typescript/sdk/package.json @@ -46,8 +46,7 @@ "ed25519-hd-key": "1.2.0", "form-data": "4.0.0", "js-sha3": "0.8.0", - "tweetnacl": "1.0.3", - "typescript-memoize": "1.1.0" + "tweetnacl": "1.0.3" }, "devDependencies": { "@types/jest": "28.1.8", diff --git a/ecosystem/typescript/sdk/src/aptos_account.ts b/ecosystem/typescript/sdk/src/aptos_account.ts index 4c564a7e4b1cb..62a1ed929ca9b 100644 --- a/ecosystem/typescript/sdk/src/aptos_account.ts +++ b/ecosystem/typescript/sdk/src/aptos_account.ts @@ -5,10 +5,10 @@ import nacl from "tweetnacl"; import sha3 from "js-sha3"; import { derivePath } from "ed25519-hd-key"; import * as bip39 from "@scure/bip39"; -import { Memoize } from "typescript-memoize"; import { bytesToHex } from "./bytes_to_hex.js"; import { HexString, MaybeHexString } from "./hex_string"; import * as Gen from "./generated/index"; +import { Memoize } from "./utils"; const { sha3_256: sha3Hash } = sha3; diff --git a/ecosystem/typescript/sdk/src/aptos_client.test.ts b/ecosystem/typescript/sdk/src/aptos_client.test.ts index a51e52b9861c7..814d5f487414f 100644 --- a/ecosystem/typescript/sdk/src/aptos_client.test.ts +++ b/ecosystem/typescript/sdk/src/aptos_client.test.ts @@ -3,7 +3,6 @@ import { AptosClient } from "./aptos_client"; import * as Gen from "./generated/index"; -import { FAUCET_URL, NODE_URL } from "./util.test"; import { FaucetClient } from "./faucet_client"; import { AptosAccount } from "./aptos_account"; import { @@ -14,6 +13,7 @@ import { } from "./transaction_builder"; import { TokenClient } from "./token_client"; import { HexString } from "./hex_string"; +import { FAUCET_URL, NODE_URL } from "./utils/test_helper.test"; const account = "0x1::account::Account"; diff --git a/ecosystem/typescript/sdk/src/aptos_client.ts b/ecosystem/typescript/sdk/src/aptos_client.ts index 44f7587755b5f..d23b4867c7735 100644 --- a/ecosystem/typescript/sdk/src/aptos_client.ts +++ b/ecosystem/typescript/sdk/src/aptos_client.ts @@ -1,9 +1,8 @@ // Copyright (c) Aptos // SPDX-License-Identifier: Apache-2.0 -import { Memoize } from "typescript-memoize"; import { HexString, MaybeHexString } from "./hex_string"; -import { fixNodeUrl, sleep } from "./util"; +import { fixNodeUrl, Memoize, sleep } from "./utils"; import { AptosAccount } from "./aptos_account"; import * as Gen from "./generated/index"; import { diff --git a/ecosystem/typescript/sdk/src/coin_client.test.ts b/ecosystem/typescript/sdk/src/coin_client.test.ts index 9c0a57746b5ff..33d93a37226e4 100644 --- a/ecosystem/typescript/sdk/src/coin_client.test.ts +++ b/ecosystem/typescript/sdk/src/coin_client.test.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { AptosClient } from "./aptos_client"; -import { FAUCET_URL, NODE_URL } from "./util.test"; +import { FAUCET_URL, NODE_URL } from "./utils/test_helper.test"; import { FaucetClient } from "./faucet_client"; import { AptosAccount } from "./aptos_account"; import { CoinClient } from "./coin_client"; diff --git a/ecosystem/typescript/sdk/src/faucet_client.test.ts b/ecosystem/typescript/sdk/src/faucet_client.test.ts index 67dba81cb258a..5773562183894 100644 --- a/ecosystem/typescript/sdk/src/faucet_client.test.ts +++ b/ecosystem/typescript/sdk/src/faucet_client.test.ts @@ -7,7 +7,7 @@ import { AptosAccount } from "./aptos_account"; import { HexString } from "./hex_string"; import * as Gen from "./generated/index"; -import { NODE_URL, FAUCET_URL } from "./util.test"; +import { NODE_URL, FAUCET_URL } from "./utils/test_helper.test"; const aptosCoin = "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"; diff --git a/ecosystem/typescript/sdk/src/token_client.test.ts b/ecosystem/typescript/sdk/src/token_client.test.ts index 1c0b990a7c1bf..67cf50c5fe2e2 100644 --- a/ecosystem/typescript/sdk/src/token_client.test.ts +++ b/ecosystem/typescript/sdk/src/token_client.test.ts @@ -6,7 +6,7 @@ import { AptosAccount } from "./aptos_account"; import { AptosClient } from "./aptos_client"; import { TokenClient } from "./token_client"; -import { NODE_URL, FAUCET_URL } from "./util.test"; +import { FAUCET_URL, NODE_URL } from "./utils/test_helper.test"; test( "full tutorial nft token flow", diff --git a/ecosystem/typescript/sdk/src/transaction_builder/builder.ts b/ecosystem/typescript/sdk/src/transaction_builder/builder.ts index 0cee5b2171c13..711a0d2c356b5 100644 --- a/ecosystem/typescript/sdk/src/transaction_builder/builder.ts +++ b/ecosystem/typescript/sdk/src/transaction_builder/builder.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 import sha3 from "js-sha3"; -import { MemoizeExpiring } from "typescript-memoize"; import { Ed25519PublicKey, Ed25519Signature, @@ -30,6 +29,7 @@ import { ArgumentABI, EntryFunctionABI, ScriptABI, TransactionScriptABI, TypeArg import { HexString, MaybeHexString } from "../hex_string"; import { argToTransactionArgument, TypeTagParser, serializeArg } from "./builder_utils"; import * as Gen from "../generated/index"; +import { MemoizeExpiring } from "../utils"; export { TypeTagParser } from "./builder_utils.js"; diff --git a/ecosystem/typescript/sdk/src/utils/index.ts b/ecosystem/typescript/sdk/src/utils/index.ts new file mode 100644 index 0000000000000..80d7d2eb00e6c --- /dev/null +++ b/ecosystem/typescript/sdk/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from "./misc"; +export * from "./memoize-decorator"; diff --git a/ecosystem/typescript/sdk/src/utils/memoize-decorator.ts b/ecosystem/typescript/sdk/src/utils/memoize-decorator.ts new file mode 100644 index 0000000000000..e611918a41092 --- /dev/null +++ b/ecosystem/typescript/sdk/src/utils/memoize-decorator.ts @@ -0,0 +1,148 @@ +/** + * Credits to https://github.com/darrylhodgins/typescript-memoize + */ + +/* eslint-disable no-param-reassign */ +/* eslint-disable no-restricted-syntax */ + +interface MemoizeArgs { + // ttl in milliseconds for cached items. After `ttlMs`, cached items are evicted automatically. If no `ttlMs` + // is provided, cached items won't get auto-evicted. + ttlMs?: number; + // produces the cache key based on `args`. + hashFunction?: boolean | ((...args: any[]) => any); + // cached items can be taged with `tags`. `tags` can be used to evict cached items + tags?: string[]; +} + +export function Memoize(args?: MemoizeArgs | MemoizeArgs["hashFunction"]) { + let hashFunction: MemoizeArgs["hashFunction"]; + let ttlMs: MemoizeArgs["ttlMs"]; + let tags: MemoizeArgs["tags"]; + + if (typeof args === "object") { + hashFunction = args.hashFunction; + ttlMs = args.ttlMs; + tags = args.tags; + } else { + hashFunction = args; + } + + return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor) => { + if (descriptor.value != null) { + descriptor.value = getNewFunction(descriptor.value, hashFunction, ttlMs, tags); + } else if (descriptor.get != null) { + descriptor.get = getNewFunction(descriptor.get, hashFunction, ttlMs, tags); + } else { + throw new Error("Only put a Memoize() decorator on a method or get accessor."); + } + }; +} + +export function MemoizeExpiring(ttlMs: number, hashFunction?: MemoizeArgs["hashFunction"]) { + return Memoize({ + ttlMs, + hashFunction, + }); +} + +const clearCacheTagsMap: Map[]> = new Map(); + +export function clear(tags: string[]): number { + const cleared: Set> = new Set(); + for (const tag of tags) { + const maps = clearCacheTagsMap.get(tag); + if (maps) { + for (const mp of maps) { + if (!cleared.has(mp)) { + mp.clear(); + cleared.add(mp); + } + } + } + } + return cleared.size; +} + +function getNewFunction( + originalMethod: () => void, + hashFunction?: MemoizeArgs["hashFunction"], + ttlMs: number = 0, + tags?: MemoizeArgs["tags"], +) { + const propMapName = Symbol("__memoized_map__"); + + // The function returned here gets called instead of originalMethod. + // eslint-disable-next-line func-names + return function (...args: any[]) { + let returnedValue: any; + + // Get or create map + // eslint-disable-next-line no-prototype-builtins + if (!this.hasOwnProperty(propMapName)) { + Object.defineProperty(this, propMapName, { + configurable: false, + enumerable: false, + writable: false, + value: new Map(), + }); + } + const myMap: Map = this[propMapName]; + + if (Array.isArray(tags)) { + for (const tag of tags) { + if (clearCacheTagsMap.has(tag)) { + clearCacheTagsMap.get(tag).push(myMap); + } else { + clearCacheTagsMap.set(tag, [myMap]); + } + } + } + + if (hashFunction || args.length > 0 || ttlMs > 0) { + let hashKey: any; + + // If true is passed as first parameter, will automatically use every argument, passed to string + if (hashFunction === true) { + hashKey = args.map((a) => a.toString()).join("!"); + } else if (hashFunction) { + hashKey = hashFunction.apply(this, args); + } else { + // eslint-disable-next-line prefer-destructuring + hashKey = args[0]; + } + + const timestampKey = `${hashKey}__timestamp`; + let isExpired: boolean = false; + if (ttlMs > 0) { + if (!myMap.has(timestampKey)) { + // "Expired" since it was never called before + isExpired = true; + } else { + const timestamp = myMap.get(timestampKey); + isExpired = Date.now() - timestamp > ttlMs; + } + } + + if (myMap.has(hashKey) && !isExpired) { + returnedValue = myMap.get(hashKey); + } else { + returnedValue = originalMethod.apply(this, args); + myMap.set(hashKey, returnedValue); + if (ttlMs > 0) { + myMap.set(timestampKey, Date.now()); + } + } + } else { + const hashKey = this; + if (myMap.has(hashKey)) { + returnedValue = myMap.get(hashKey); + } else { + returnedValue = originalMethod.apply(this, args); + myMap.set(hashKey, returnedValue); + } + } + + return returnedValue; + }; +} diff --git a/ecosystem/typescript/sdk/src/util.test.ts b/ecosystem/typescript/sdk/src/utils/misc.test.ts similarity index 59% rename from ecosystem/typescript/sdk/src/util.test.ts rename to ecosystem/typescript/sdk/src/utils/misc.test.ts index 5ca67d0f5167f..f63fb3d823e44 100644 --- a/ecosystem/typescript/sdk/src/util.test.ts +++ b/ecosystem/typescript/sdk/src/utils/misc.test.ts @@ -1,17 +1,7 @@ // Copyright (c) Aptos // SPDX-License-Identifier: Apache-2.0 -import { AptosClient } from "./aptos_client"; - -export const NODE_URL = process.env.APTOS_NODE_URL; -export const FAUCET_URL = process.env.APTOS_FAUCET_URL; - -test("noop", () => { - // All TS files are compiled by default into the npm package - // Adding this empty test allows us to: - // 1. Guarantee that this test library won't get compiled - // 2. Prevent jest from exploding when it finds a file with no tests in it -}); +import { AptosClient } from "../aptos_client"; test("test fixNodeUrl", () => { expect(new AptosClient("https://test.com").client.request.config.BASE).toBe("https://test.com/v1"); diff --git a/ecosystem/typescript/sdk/src/util.ts b/ecosystem/typescript/sdk/src/utils/misc.ts similarity index 100% rename from ecosystem/typescript/sdk/src/util.ts rename to ecosystem/typescript/sdk/src/utils/misc.ts diff --git a/ecosystem/typescript/sdk/src/utils/test_helper.test.ts b/ecosystem/typescript/sdk/src/utils/test_helper.test.ts new file mode 100644 index 0000000000000..4a6b68e41c810 --- /dev/null +++ b/ecosystem/typescript/sdk/src/utils/test_helper.test.ts @@ -0,0 +1,8 @@ +export const NODE_URL = process.env.APTOS_NODE_URL; +export const FAUCET_URL = process.env.APTOS_FAUCET_URL; +test("noop", () => { + // All TS files are compiled by default into the npm package + // Adding this empty test allows us to: + // 1. Guarantee that this test library won't get compiled + // 2. Prevent jest from exploding when it finds a file with no tests in it +});