Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: cap-js tests #11

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,10 +479,62 @@ console.log(userTxns);
### `capRoot.time([options])`
> ToDo

### Kyasshu Layer

### `capCache.get_all_user_transactions(userId, LastEvaluatedKey)`
> Return all of the user transactions for `userId`, if `LastEvaluatedKey` is returned, you must provide it in subsequent calls to query the rest of the data.

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| userId | [principal](#principal) | The user Id of the requested transactions |
| LastEvaluatedKey | [string?](#string?) | The optional `LastEvaluatedKey`, If LastEvaluatedKey is empty, then the "last page" of results has been processed and there is no more data to be retrieved. If LastEvaluatedKey is not empty, it does not necessarily mean that there is more data in the result set. The only way to know when you have reached the end of the result set is when LastEvaluatedKey is empty. |

#### Returns

| Type | Description |
| ---- | ----------- |
| `GetTransactionsResponseBorrowed` | An object returning an array of `data` as well as the page queried. If witness = `true` the certified response will be appended to the response |

#### Example

```JavaScript
const capCache = new CapCache();

let userTxns = await capCache.get_all_user_transactions({
user: Principal.from("zxt4e-ian3w-g4ll2-3n5mz-lfqkc-eyj7k-yg6jl-rsbud-f6sft-zdfq3-pae"),
});

userTxns = await capCache.get_all_user_transactions({
user: Principal.from("zxt4e-ian3w-g4ll2-3n5mz-lfqkc-eyj7k-yg6jl-rsbud-f6sft-zdfq3-pae"),
LastEvaluatedKey: userTxns.LastEvaluatedKey,
});

console.log(userTxns);
```
```bash
{
Items?: {
[key: string]: Event;
}[] | undefined;
LastEvaluatedKey?: {
[key: string]: any;
} | undefined;
}
```

#### Notes
- Unstable endpoint

### API

- ToDo

## Local Development

- ToDo

## Roadmap

- Cache every endpoitn with Kyasshu
Expand Down
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
roots: [
'<rootDir>/tests/',
],
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ["<rootDir>/tests/**/*.(spec|test).ts"],
setupFiles: ["<rootDir>/tests/setup-tests.ts"],
testTimeout: 30000,
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"lint": "eslint src --ext ts",
"lint:fix": "eslint src --fix --ext ts && prettier --write \"./src/**/*.{ts,tsx,js,jsx}\"",
"format": "prettier --write \"./src/**/*.{ts,tsx,js,jsx}\"",
"format:tests": "prettier --write \"./tests/**/*.{ts,tsx,js,jsx}\"",
"format:check": "prettier --check \"./src/**/*.{ts,tsx,js,jsx}\""
},
"husky": {
Expand All @@ -39,9 +40,11 @@
"@dfinity/agent": "^0.10.1",
"@dfinity/candid": "^0.10.1",
"@dfinity/principal": "^0.10.1",
"axios": "^0.23.0",
"cross-fetch": "^3.1.4"
},
"devDependencies": {
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"eslint": "^7.32.0",
Expand Down
4 changes: 3 additions & 1 deletion scripts/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ dfx canister call r7inp-6aaaa-aaaaa-aaabq-cai get_next_canisters "(record {witne
dfx canister call ic-history-router print_out "()"

# Contract root buckets
dfx canister call ic-history-router get_token_contract_root_bucket "(record { canister=(principal \"avesb-mgo2l-ds25i-g7kd4-3he5l-z7ary-3biiq-sojiw-xjgbk-ich5l-mae\"); witness=(false:bool)})"
dfx canister call ic-history-router get_token_contract_root_bucket "(record { canister=(principal \"avesb-mgo2l-ds25i-g7kd4-3he5l-z7ary-3biiq-sojiw-xjgbk-ich5l-mae\"); witness=(false:bool)})"

dfx canister --network ic call o3aoo-taaaa-aaaad-qayza-cai get_transactions "(record {page=(0:nat32); witness=(false:bool)})"
13 changes: 13 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { KyaStage } from "./types";

export default {};

//kyasshu
export const KyaUrl = (stage: KyaStage = "prod") => {
const resp: { [key: string]: string } = {
prod: "https://kyasshu-prod.fleek.co",
dev: "https://kyasshu-dev.fleek.co",
local: "http://localhost:3000/dev",
};

return resp[stage];
};

// To edit or maintain
export const DFX_JSON_HISTORY_ROUTER_KEY_NAME = "ic-history-router";
const IC_HISTORY_ROUTER_CANISTER_ID_MAINNET = "lj532-6iaaa-aaaah-qcc7a-cai";
Expand Down
1 change: 0 additions & 1 deletion src/declarations/cap/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export {
Witness,
} from "./root";


export {
default as Router,
GetIndexCanistersResponse,
Expand Down
126 changes: 77 additions & 49 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "@dfinity/agent";
import { Principal } from "@dfinity/principal";

import { KyaConnector } from "./kyasshu";
import _ROUTER_SERVICE from "./declarations/cap/router";
import _ROOT_SERVICE from "./declarations/cap/root";
import { routerFactory } from "./declarations/cap/router.did.js";
Expand Down Expand Up @@ -44,7 +45,12 @@ export {
GetUserRootBucketsResponse,
} from "./declarations/cap";

import { CanisterInfo, DFX_JSON_HISTORY_ROUTER_KEY_NAME } from "./config";
import {
CanisterInfo,
KyaUrl,
DFX_JSON_HISTORY_ROUTER_KEY_NAME,
} from "./config";
import { KyaStage } from "./types";

export { CanisterInfo };

Expand All @@ -61,15 +67,16 @@ export interface ActorParams {
idlFactory: IdlFactory;
}

type CreateActorFromTokenParams = {
tokenId: string;
router: CapRouter;
} |
{
tokenId: string;
routerHost?: string;
routerCanisterId?: string;
}
type CreateActorFromTokenParams =
| {
tokenId: string;
router: CapRouter;
}
| {
tokenId: string;
routerHost?: string;
routerCanisterId?: string;
};

interface CreateActorFromRootParams {
canisterId: string;
Expand All @@ -93,20 +100,24 @@ type InitRootParams = BaseInitParams &
export class CapBase<T> {
public actor: ActorSubclass<T>;

constructor(actor: ActorSubclass<T>) {
public cache: KyaConnector | undefined;

constructor(actor: ActorSubclass<T>, cache?: KyaConnector) {
this.actor = actor;
if (cache) {
this.cache = cache;
}
}

private static async createActor<T>({
private static createActor<T>({
host,
idlFactory,
...args
}: CreateActorParams): Promise<ActorSubclass<T>> {
}: CreateActorParams): ActorSubclass<T> {
const agent = new HttpAgent({
host,
fetch,
} as unknown as HttpAgentOptions);

if (process.env.NODE_ENV !== "production") {
try {
agent.fetchRootKey();
Expand All @@ -125,17 +136,9 @@ export class CapBase<T> {
});
}

const router = "router" in args ? args.router : await CapRouter.init({ host: args.routerHost, canisterId: args.routerCanisterId });

const { canister } = await router.get_token_contract_root_bucket({
tokenId: Principal.fromText(args.tokenId),
});

if (!canister?.[0]) throw Error(`Token ${args.tokenId} not in cap`);

return Actor.createActor(idlFactory, {
agent,
canisterId: canister[0],
canisterId: args.tokenId,
});
}

Expand All @@ -144,15 +147,13 @@ export class CapBase<T> {
idlFactory,
...args
}: CreateActorParams) {
return (async () => {
const actor = await CapBase.createActor<T>({
host,
idlFactory,
...args,
});
const actor = CapBase.createActor<T>({
host,
idlFactory,
...args,
});

return actor;
})();
return actor;
}
}

Expand All @@ -164,17 +165,15 @@ export class CapRouter extends CapBase<_ROUTER_SERVICE> {
host?: string;
canisterId?: string;
}) {
return (async () => {
const actor = await CapBase.inititalise<_ROUTER_SERVICE>({
host,
canisterId,
idlFactory: routerFactory,
});
const actor = CapBase.inititalise<_ROUTER_SERVICE>({
host,
canisterId,
idlFactory: routerFactory,
});

const cap = new CapRouter(actor);
const cap = new CapRouter(actor);

return cap;
})();
return cap;
}

public async get_index_canisters({
Expand Down Expand Up @@ -227,17 +226,17 @@ export class CapRouter extends CapBase<_ROUTER_SERVICE> {

export class CapRoot extends CapBase<_ROOT_SERVICE> {
public static init({ host = Hosts.mainnet, ...args }: InitRootParams) {
return (async () => {
const actor = await CapBase.inititalise<_ROOT_SERVICE>({
host,
idlFactory: rootFactory,
...args,
});
const actor = CapBase.inititalise<_ROOT_SERVICE>({
host,
idlFactory: rootFactory,
...args,
});

const cap = new CapRoot(actor);
// ToDo cache flag
const cache = new KyaConnector(KyaUrl("dev"));
const cap = new CapRoot(actor, cache);

return cap;
})();
return cap;
}

public async get_transaction(
Expand Down Expand Up @@ -293,3 +292,32 @@ export class CapRoot extends CapBase<_ROOT_SERVICE> {
});
}
}

export class CapCache extends CapBase<_ROOT_SERVICE> {
constructor(cacheStage?: KyaStage) {
const actor = CapBase.inititalise<_ROOT_SERVICE>({
host: Hosts.mainnet,
canisterId: CanisterInfo[DFX_JSON_HISTORY_ROUTER_KEY_NAME].mainnet,
idlFactory: rootFactory,
});

const cache = new KyaConnector(KyaUrl(cacheStage));

super(actor, cache);
}

public async get_all_user_transactions({
user,
LastEvaluatedKey,
}: {
user: Principal;
LastEvaluatedKey?: unknown;
}): Promise<unknown> {
return this.cache?.request({
path: `cap/user/txns/${user.toString()}`,
params: [{
LastEvaluatedKey
}],
});
}
}
65 changes: 65 additions & 0 deletions src/kyasshu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { KyaApi, RequestArgs } from "./types";
import { EventEmitter } from "events";
import axios, { AxiosResponse, AxiosRequestConfig } from "axios";

export type KyaOptions = { url: string };

export class KyaConnector extends EventEmitter implements KyaApi {
public url: string;

constructor(protected options: string | KyaOptions) {
super();
if (typeof options === "string") {
this.url = options;
} else {
this.url = options.url;
}
}

public async connect(): Promise<any> {
this.emit("connected");
}

public async disconnect(): Promise<any> {
this.emit("disconnected");
}

private get headers() {
return {
"Content-Type": "application/json",
// when authenticating to kya api
// ...(this.token ? { 'Authorization': `Bearer ${this.token}` } : {})
};
}

public async request(req: RequestArgs): Promise<unknown> {
const params = req.params;
const options: AxiosRequestConfig = {
method: "get",
url: `${this.url}/${req.path}`,
headers: this.headers,
...(params && { params: Object.assign({}, ...params) }),
};

let resp: AxiosResponse;

try {
resp = await axios(options);
} catch (error) {
throw new Error((error as Error).message);
}

if (resp.statusText !== "OK") {
throw new Error(`status: ${resp.status}, ${resp.statusText}`);
}

return resp.data;
}

public on(
event: "connected" | "disconnected",
listener: (...args: any[]) => void
): this {
return super.on(event, listener);
}
}
Loading