Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- Automatic retries
- TypeScript typings
- 100% endpoint coverage (incl. DDragon)
- Caching with custom ttls per endpoint
- Caching with custom TTLs per endpoint
- Request prioritization

## Installation
Expand All @@ -27,11 +27,28 @@ $ npm install @fightmegg/riot-api
import { RiotAPI, RiotAPITypes, PlatformId } from "@fightmegg/riot-api";

(async () => {
const rAPI = new RiotAPI("RGAPI-KEY");
const rAPI = new RiotAPI("RGAPI-KEY" {
cache: {
cacheType: "local",
ttls: {
byMethod: {
//TTLs are defined in milliseconds
[RiotAPITypes.METHOD_KEY.ACCOUNT.GET_BY_RIOT_ID]: 3_600_000, // 1 hour
[RiotAPITypes.METHOD_KEY.SUMMONER.GET_BY_PUUID]: 24 * 60 * 60 * 1000, // 24 hours
},
},
},
});

const account = await rAPI.account.getByRiotId({
cluster: PlatformId.ASIA,
gameName: "Hide on bush",
tagLine: "KR1",
});

const summoner = await rAPI.summoner.getBySummonerName({
region: PlatformId.EUW1,
summonerName: "Demos Kratos",
const summoner = await rAPI.summoner.getByPUUID({
region: PlatformId.KR,
puuid: account.puuid,
});
})();
```
Expand All @@ -42,7 +59,7 @@ import { RiotAPI, RiotAPITypes, PlatformId } from "@fightmegg/riot-api";
const config: RiotAPITypes.Config = {
debug: false,
cache: {
cacheType: "ioredis", // local or ioredis
cacheType: "ioredis", // local, ioredis or mongodb
client: "redis://localhost:6379", // leave null if client is local
ttls: {
byMethod: {
Expand All @@ -52,24 +69,24 @@ const config: RiotAPITypes.Config = {
},
};

const rAPI = new RiotAPI("RGAPI-TOKEN", config);
const rAPI = new RiotAPI("RGAPI-KEY", config);
```

## Error handling

If you use `Promises` then any error will reject the promise, this can either be an error value, or the response from the API.
If you use `Promises`, any error will reject the promise. This can either be an error value or the response from the API.

Same as above with `async/await`, where the error thrown will be the response from the API if the error occured at that level.
The same applies to `async/await`, where the error thrown will be the response from the API if the error occurred at that level.

## Caching

Caching is turned off by default, but with the cache property in the config you can enable it with various settings. For now we only support local (in memory) or [ioredis](https://github.com/luin/ioredis) caches, will potential support for custom caches in future.
Caching is turned off by default, but you can enable it with various settings using the `cache` property in the config. Currently, we support local (in-memory), [ioredis](https://github.com/redis/ioredis) and [mongodb](https://github.com/mongodb/node-mongodb-native) caches, with potential support for custom caches in the future.

When setting up the cache, you can change the `ttl` of each method / endpoint individually. This is done through the `METHOD_KEY` type which can be found in the [typings file](https://github.com/fightmegg/riot-api/blob/master/src/%40types/index.ts#L92).
When setting up the cache, you can change the `ttl` of each method/endpoint individually. This is done through the `METHOD_KEY` type, which can be found in the [typings file](https://github.com/fightmegg/riot-api/blob/master/src/%40types/index.ts#L92).

## DDragon

We also fully support [DataDragon](https://developer.riotgames.com/docs/lol#data-dragon) which can be accessed in two ways:
We fully support [DataDragon](https://developer.riotgames.com/docs/lol#data-dragon), which can be accessed in two ways:

```ts
// ...
Expand All @@ -79,7 +96,7 @@ const latestV = await rAPI.ddragon.versions.latest();
const champs = await rAPI.ddragon.champion.all();
```

If you want to just use static data only, then you can do the following:
If you want to use static data only, you can do the following:

```ts
import { DDragon } from "@fightmegg/riot-api";
Expand All @@ -92,7 +109,7 @@ Just like the main API, we have full TypeScript typings for DDragon endpoints. P

## regionToCluster

A helper method to make it easier to determing which cluster you want to hit based on the users region
A helper method that makes it easier to determine which cluster to target based on the user's region.

```ts
import { regionToCluster } from "@fightmegg/riot-api";
Expand All @@ -112,7 +129,7 @@ const summoner: RiotAPITypes.Summoner.SummonerDTO = await rAPI.summoner.getBySum

## Debugging

If you want to see want the rate-limiter is currently doing, we use the [debug](https://github.com/visionmedia/debug) module for logging. Simply run your app with:
If you want to see what the rate-limiter is currently doing, we use the [debug](https://github.com/visionmedia/debug) module for logging. Simply run your app with:

```shell
DEBUG=riotapi* node ...
Expand Down
34 changes: 34 additions & 0 deletions __mocks__/mongodb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const mockFindOne = jest.fn();
export const mockUpdateOne = jest.fn();
export const mockDeleteMany = jest.fn();
export const mockCreateIndex = jest.fn();
export const mockIndexes = jest.fn(() => Promise.resolve([]));
export const mockListCollections = jest.fn(() => ({
toArray: jest.fn(() => Promise.resolve([])),
}));
export const mockCreateCollection = jest.fn();
export const mockCollection = jest.fn(() => ({
findOne: mockFindOne,
updateOne: mockUpdateOne,
deleteMany: mockDeleteMany,
createIndex: mockCreateIndex,
indexes: mockIndexes,
}));
export const mockListDatabases = jest.fn(() =>
Promise.resolve({ databases: [] })
);
export const mockAdmin = jest.fn(() => ({
listDatabases: mockListDatabases,
}));
export const mockDb = jest.fn(() => ({
admin: mockAdmin,
listCollections: mockListCollections,
createCollection: mockCreateCollection,
collection: mockCollection,
}));
export const mockConnect = jest.fn(() => Promise.resolve());

export const MongoClient = jest.fn(() => ({
connect: mockConnect,
db: mockDb,
}));
95 changes: 94 additions & 1 deletion __tests__/unit/cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MemoryCache, RedisCache } from "../../src/cache";
import { MemoryCache, MongoCache, RedisCache } from "../../src/cache";
import { Redis } from "ioredis";

jest.mock("ioredis", () => ({
Expand All @@ -9,6 +9,20 @@ jest.mock("ioredis", () => ({
})),
}));

jest.mock("mongodb");

import {
mockConnect,
mockCreateCollection,
mockCreateIndex,
mockDeleteMany,
mockFindOne,
mockIndexes,
mockListCollections,
mockListDatabases,
mockUpdateOne,
} from "../../__mocks__/mongodb";

describe("Cache", () => {
describe("MemoryCache", () => {
let memCache: MemoryCache;
Expand Down Expand Up @@ -115,4 +129,83 @@ describe("Cache", () => {
expect(mockedRedisFlush).toHaveBeenCalled();
});
});

describe("MongoCache", () => {
let mongoCache: MongoCache;

beforeEach(() => {
jest.clearAllMocks();

mockConnect.mockResolvedValue(undefined);
mockListDatabases.mockResolvedValue({ databases: [] });
mockListCollections.mockReturnValue({
toArray: jest.fn().mockResolvedValue([]),
});
mockCreateCollection.mockResolvedValue(undefined);
mockIndexes.mockResolvedValue([]);
mockCreateIndex.mockResolvedValue("riot-api-cache-key-index");

mongoCache = new MongoCache("mongodb://localhost:27017");
});

test("initialises & creates new mongo client", () => {
expect(mongoCache.client).toBeTruthy();
expect(mongoCache.dbName).toEqual("riot-api");
expect(mongoCache.collectionName).toEqual("cache");
expect(mongoCache.keyIndexName).toEqual("riot-api-cache-key-index");
});

test("connects to mongodb on initialization", async () => {
await new Promise((resolve) => setTimeout(resolve, 10));

expect(mockConnect).toHaveBeenCalled();
});

test("set -> calls collection.updateOne with key, value and upsert", async () => {
mockUpdateOne.mockResolvedValue({ acknowledged: true });

await expect(mongoCache.set("key", { a: 1 }, 5000)).resolves.toEqual(
"OK"
);
expect(mockUpdateOne).toHaveBeenCalledWith(
{ key: "key" },
{
$set: expect.objectContaining({
value: { a: 1 },
key: "key",
expiresAt: expect.any(Date),
}),
},
{ upsert: true }
);
});

test("set -> returns Error when update is not acknowledged", async () => {
mockUpdateOne.mockResolvedValue({ acknowledged: false });

await expect(mongoCache.set("key", { a: 1 }, 5000)).resolves.toEqual(
"Error"
);
});

test("get -> returns NULL if value is not present in cache", async () => {
mockFindOne.mockResolvedValue(null);

await expect(mongoCache.get("key")).resolves.toBeNull();
expect(mockFindOne).toHaveBeenCalledWith({ key: "key" });
});

test("get -> returns value without key property if present in cache", async () => {
mockFindOne.mockResolvedValue({ key: "key", value: { a: 1, b: 2 } });

await expect(mongoCache.get("key")).resolves.toEqual({ a: 1, b: 2 });
});

test("flush -> calls collection.deleteMany and empties the cache", async () => {
mockDeleteMany.mockResolvedValue({ deletedCount: 5 });

await expect(mongoCache.flush()).resolves.toEqual("OK");
expect(mockDeleteMany).toHaveBeenCalledWith({});
});
});
});
26 changes: 15 additions & 11 deletions __tests__/unit/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ describe("RiotAPI", () => {
test.each([
[
"getByPUUID",
{ region: PlatformId.EUROPE, puuid: "1" },
{ cluster: PlatformId.EUROPE, puuid: "1" },
[
PlatformId.EUROPE,
RiotAPITypes.METHOD_KEY.ACCOUNT.GET_BY_PUUID,
Expand All @@ -280,7 +280,11 @@ describe("RiotAPI", () => {
],
[
"getByRiotId",
{ region: PlatformId.EUROPE, gameName: "Demos Kratos", tagLine: "EUW" },
{
cluster: PlatformId.EUROPE,
gameName: "Demos Kratos",
tagLine: "EUW",
},
[
PlatformId.EUROPE,
RiotAPITypes.METHOD_KEY.ACCOUNT.GET_BY_RIOT_ID,
Expand All @@ -290,7 +294,7 @@ describe("RiotAPI", () => {
],
[
"getByAccessToken",
{ region: PlatformId.EUROPE, accessToken: "12234" },
{ cluster: PlatformId.EUROPE, accessToken: "12234" },
[
PlatformId.EUROPE,
RiotAPITypes.METHOD_KEY.ACCOUNT.GET_BY_ACCESS_TOKEN,
Expand All @@ -303,7 +307,7 @@ describe("RiotAPI", () => {
],
[
"getActiveShardForPlayer",
{ region: PlatformId.EUROPE, game: "val", puuid: "1" },
{ cluster: PlatformId.EUROPE, game: "val", puuid: "1" },
[
PlatformId.EUROPE,
RiotAPITypes.METHOD_KEY.ACCOUNT.GET_ACTIVE_SHARD_FOR_PLAYER,
Expand Down Expand Up @@ -1209,7 +1213,7 @@ describe("RiotAPI", () => {
[
"getMatchIdsByPUUID",
{
region: PlatformId.EUROPE,
cluster: PlatformId.EUROPE,
puuid: "1",
params: { count: 10, start: 10, endTime: 131311, startTime: 1111 },
},
Expand All @@ -1226,7 +1230,7 @@ describe("RiotAPI", () => {
[
"getById",
{
region: PlatformId.EUROPE,
cluster: PlatformId.EUROPE,
matchId: "1",
},
[
Expand Down Expand Up @@ -1555,7 +1559,7 @@ describe("RiotAPI", () => {
[
"getContent",
{
region: PlatformId.EU,
cluster: PlatformId.EU,
params: { locale: "gb-en" },
},
[
Expand Down Expand Up @@ -1585,7 +1589,7 @@ describe("RiotAPI", () => {
[
"getById",
{
region: PlatformId.EU,
cluster: PlatformId.EU,
matchId: "1",
},
[
Expand All @@ -1600,7 +1604,7 @@ describe("RiotAPI", () => {
[
"getMatchlistByPUUID",
{
region: PlatformId.EU,
cluster: PlatformId.EU,
puuid: "1",
},
[
Expand All @@ -1615,7 +1619,7 @@ describe("RiotAPI", () => {
[
"getRecentMatchesByQueue",
{
region: PlatformId.EU,
cluster: PlatformId.EU,
queue: RiotAPITypes.VAL_QUEUE.COMPETITIVE,
},
[
Expand Down Expand Up @@ -1644,7 +1648,7 @@ describe("RiotAPI", () => {
[
"getLeaderboardByQueue",
{
region: PlatformId.EU,
cluster: PlatformId.EU,
queue: "1",
params: {
size: 200,
Expand Down
Loading