Skip to content

Commit

Permalink
feat: name records cache interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Aysnine committed Mar 23, 2022
1 parent acd2853 commit 6a14ab6
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 3 deletions.
46 changes: 44 additions & 2 deletions src/client.ts
Expand Up @@ -97,8 +97,50 @@ export class IcNamingClient extends IcNamingClientBase {

// --- Resolver ---

async getRecordsOfName(name: string) {
return await throwable(() => this.resolver.get_record_value(name));
async getRecordsOfName(name: string): Promise<Array<[string, string]>> {
let cachedRecords: Array<[string, string]> | undefined;

await this.dispatchNameRecordsCache(async (store) => {
const target = await store.getRecordsByName(name);

if (target) {
if (Date.now() >= target.expired_at) {
const { ttl } = await this.getRegistryOfName(name);

await store.setRecordsByName(name, {
name,
expired_at: new Date(Date.now() + Number(ttl) * 1000).getTime(),
});
} else {
if (target.records) {
cachedRecords = target.records.map((i) => [i.key, i.value]);
}
}
} else {
const { ttl } = await this.getRegistryOfName(name);
await store.setRecordsByName(name, {
name,
expired_at: new Date(Date.now() + Number(ttl) * 1000).getTime(),
});
}
});

if (cachedRecords) return cachedRecords;

const result = await throwable(() => this.resolver.get_record_value(name));

await this.dispatchNameRecordsCache(async (store) => {
const target = await store.getRecordsByName(name);

if (target) {
await store.setRecordsByName(name, {
...target,
records: result.map(([key, value]) => ({ key, value })),
});
}
});

return result;
}

// --- Favorites ---
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
@@ -1,2 +1,3 @@
export * from "./internal/interfaces";
export * from "./internal/errors";
export * from "./client";
14 changes: 14 additions & 0 deletions src/internal/base.ts
Expand Up @@ -17,11 +17,17 @@ import {
MAINNET_CANISTER_ID_GROUP,
TICP_CANISTER_ID_GROUP,
} from "./config";
import { NameRecordsCacheStore } from "./interfaces";

export interface IcNamingClientInitOptions {
net: "MAINNET" | "TICP";
mode: "production" | "local";
httpAgentOptions?: HttpAgentOptions;
nameRecordsCacheStore?: NameRecordsCacheStore;
}

export interface InternalStores {
nameRecordsCacheStore?: NameRecordsCacheStore;
}

export class IcNamingClientBase {
Expand Down Expand Up @@ -91,6 +97,14 @@ export class IcNamingClientBase {
canisterId,
}) as ActorSubclass<ServiceType>;
}

public async dispatchNameRecordsCache(
fn: (store: NameRecordsCacheStore) => Promise<void>
) {
if (this._options.nameRecordsCacheStore) {
await fn(this._options.nameRecordsCacheStore);
}
}
}

const toDidModuleType = <T>(module: T) => {
Expand Down
10 changes: 10 additions & 0 deletions src/internal/interfaces.ts
@@ -0,0 +1,10 @@
export interface NameRecordsValue {
name: string;
expired_at: number;
records?: Array<{ key: string; value: string }>;
}

export interface NameRecordsCacheStore {
getRecordsByName: (name: string) => Promise<null | NameRecordsValue>;
setRecordsByName: (name: string, data: NameRecordsValue) => Promise<void>;
}
88 changes: 87 additions & 1 deletion test/client.test.ts
@@ -1,5 +1,9 @@
import { Principal } from "@dfinity/principal";
import { IcNamingClient } from "../src";
import {
IcNamingClient,
NameRecordsCacheStore,
NameRecordsValue,
} from "../src";

jest.mock("../src/internal/base");

Expand Down Expand Up @@ -202,6 +206,88 @@ describe("IcNamingClient", () => {
expect(client["resolver"].get_record_value).toBeCalledTimes(1);
});

it("should return records of name with cache store", async () => {
const simpleMemoryNameRecordsCacheStore = {
map: {} as Record<string, NameRecordsValue>,
async getRecordsByName(name: string) {
return this.map[name];
},
async setRecordsByName(name: string, value: NameRecordsValue) {
this.map[name] = value;
},
};
const spyStoreGet = jest.spyOn(
simpleMemoryNameRecordsCacheStore,
"getRecordsByName"
);
const spyStoreSet = jest.spyOn(
simpleMemoryNameRecordsCacheStore,
"setRecordsByName"
);

const client = new IcNamingClient({
net: "MAINNET",
mode: "local",
nameRecordsCacheStore: simpleMemoryNameRecordsCacheStore,
});

client["resolver"] = { get_record_value: () => {} } as any;
client["dispatchNameRecordsCache"] = jest
.fn()
.mockImplementation(async (fn) => {
await fn(simpleMemoryNameRecordsCacheStore);
});
client["getRegistryOfName"] = jest.fn().mockResolvedValue({
ttl: BigInt(600),
resolver: dummyPrincipal,
owner: dummyPrincipal,
name: "name",
});

jest.spyOn(client["resolver"], "get_record_value").mockResolvedValue({
Ok: [
["key1", "value1"],
["key2", "value2"],
["key3", "value3"],
],
});

await expect(client.getRecordsOfName("name")).resolves.toMatchObject([
["key1", "value1"],
["key2", "value2"],
["key3", "value3"],
]);
expect(client["dispatchNameRecordsCache"]).toHaveBeenCalledTimes(2);
expect(client["resolver"].get_record_value).toBeCalledTimes(1);
expect(client["getRegistryOfName"]).toBeCalledTimes(1);
expect(spyStoreGet).toBeCalledTimes(2);
expect(spyStoreSet).toBeCalledTimes(2);

await expect(client.getRecordsOfName("name")).resolves.toMatchObject([
["key1", "value1"],
["key2", "value2"],
["key3", "value3"],
]);
expect(client["dispatchNameRecordsCache"]).toHaveBeenCalledTimes(2 + 1);
expect(client["resolver"].get_record_value).toBeCalledTimes(1 + 0);
expect(client["getRegistryOfName"]).toBeCalledTimes(1 + 0);
expect(spyStoreGet).toBeCalledTimes(2 + 1);
expect(spyStoreSet).toBeCalledTimes(2 + 0);

simpleMemoryNameRecordsCacheStore.map["name"].expired_at = Date.now();

await expect(client.getRecordsOfName("name")).resolves.toMatchObject([
["key1", "value1"],
["key2", "value2"],
["key3", "value3"],
]);
expect(client["dispatchNameRecordsCache"]).toHaveBeenCalledTimes(2 + 1 + 2);
expect(client["resolver"].get_record_value).toBeCalledTimes(1 + 0 + 1);
expect(client["getRegistryOfName"]).toBeCalledTimes(1 + 0 + 1);
expect(spyStoreGet).toBeCalledTimes(2 + 1 + 2);
expect(spyStoreSet).toBeCalledTimes(2 + 0 + 2);
});

it("should return registry of name", async () => {
const client = new IcNamingClient({
net: "MAINNET",
Expand Down

0 comments on commit 6a14ab6

Please sign in to comment.