diff --git a/README.md b/README.md index 0a84e6e..18cfb0b 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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, }); })(); ``` @@ -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: { @@ -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 // ... @@ -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"; @@ -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"; @@ -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 ... diff --git a/__mocks__/mongodb.ts b/__mocks__/mongodb.ts new file mode 100644 index 0000000..9989d81 --- /dev/null +++ b/__mocks__/mongodb.ts @@ -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, +})); diff --git a/__tests__/unit/cache.test.ts b/__tests__/unit/cache.test.ts index 36a792e..9216479 100644 --- a/__tests__/unit/cache.test.ts +++ b/__tests__/unit/cache.test.ts @@ -1,4 +1,4 @@ -import { MemoryCache, RedisCache } from "../../src/cache"; +import { MemoryCache, MongoCache, RedisCache } from "../../src/cache"; import { Redis } from "ioredis"; jest.mock("ioredis", () => ({ @@ -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; @@ -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({}); + }); + }); }); diff --git a/__tests__/unit/index.test.ts b/__tests__/unit/index.test.ts index 0d8d2af..6622aa7 100644 --- a/__tests__/unit/index.test.ts +++ b/__tests__/unit/index.test.ts @@ -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, @@ -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, @@ -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, @@ -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, @@ -1209,7 +1213,7 @@ describe("RiotAPI", () => { [ "getMatchIdsByPUUID", { - region: PlatformId.EUROPE, + cluster: PlatformId.EUROPE, puuid: "1", params: { count: 10, start: 10, endTime: 131311, startTime: 1111 }, }, @@ -1226,7 +1230,7 @@ describe("RiotAPI", () => { [ "getById", { - region: PlatformId.EUROPE, + cluster: PlatformId.EUROPE, matchId: "1", }, [ @@ -1555,7 +1559,7 @@ describe("RiotAPI", () => { [ "getContent", { - region: PlatformId.EU, + cluster: PlatformId.EU, params: { locale: "gb-en" }, }, [ @@ -1585,7 +1589,7 @@ describe("RiotAPI", () => { [ "getById", { - region: PlatformId.EU, + cluster: PlatformId.EU, matchId: "1", }, [ @@ -1600,7 +1604,7 @@ describe("RiotAPI", () => { [ "getMatchlistByPUUID", { - region: PlatformId.EU, + cluster: PlatformId.EU, puuid: "1", }, [ @@ -1615,7 +1619,7 @@ describe("RiotAPI", () => { [ "getRecentMatchesByQueue", { - region: PlatformId.EU, + cluster: PlatformId.EU, queue: RiotAPITypes.VAL_QUEUE.COMPETITIVE, }, [ @@ -1644,7 +1648,7 @@ describe("RiotAPI", () => { [ "getLeaderboardByQueue", { - region: PlatformId.EU, + cluster: PlatformId.EU, queue: "1", params: { size: 200, diff --git a/package-lock.json b/package-lock.json index 82faca8..758a890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@fightmegg/riot-rate-limiter": "^0.0.23", "debug": "^4.3.4", "ioredis": "^5.4.1", + "mongodb": "^7.1.0", "node-fetch": "^2.6.1", "path-to-regexp": "^6.2.2" }, @@ -59,89 +60,20 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { "version": "7.21.4", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", @@ -156,6 +88,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.21.4", @@ -353,19 +286,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -380,109 +315,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/types": "^7.29.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -668,14 +522,15 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -712,14 +567,14 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -905,10 +760,11 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1322,6 +1178,15 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.5.tgz", + "integrity": "sha512-k64Lbyb7ycCSXHSLzxVdb2xsKGPMvYZfCICXvDsI8Z65CeWQzTEKS4YmGbnqw+U9RBvLPTsB6UCmwkgsDTGWIw==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1531,6 +1396,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", "dev": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1551,6 +1417,21 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -1604,6 +1485,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.9.0.tgz", "integrity": "sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.9.0", "@typescript-eslint/types": "7.9.0", @@ -1713,10 +1595,11 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -1786,6 +1669,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2021,22 +1905,24 @@ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2057,6 +1943,7 @@ "url": "https://tidelift.com/funding/github/npm/browserslist" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001449", "electron-to-chromium": "^1.4.284", @@ -2091,12 +1978,35 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", + "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2289,10 +2199,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2374,10 +2285,11 @@ } }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -2415,6 +2327,21 @@ "node": ">=6.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.372", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.372.tgz", @@ -2448,6 +2375,55 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -2471,6 +2447,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2526,6 +2503,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -2812,14 +2790,15 @@ } }, "node_modules/fengari": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.4.tgz", - "integrity": "sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.5.tgz", + "integrity": "sha512-0DS4Nn4rV8qyFlQCpKK8brT61EUtswynrpfFTcgLErcilBIBskSMQ86fO2WVuybr14ywyKdRjv91FiRZwnEuvQ==", "dev": true, + "license": "MIT", "dependencies": { - "readline-sync": "^1.4.9", - "sprintf-js": "^1.1.1", - "tmp": "^0.0.33" + "readline-sync": "^1.4.10", + "sprintf-js": "^1.1.3", + "tmp": "^0.2.5" } }, "node_modules/fengari-interop": { @@ -2832,10 +2811,11 @@ } }, "node_modules/fengari/node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/file-entry-cache": { "version": "6.0.1", @@ -2850,10 +2830,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2897,13 +2878,16 @@ "dev": true }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -2916,6 +2900,31 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2934,6 +2943,31 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -2943,6 +2977,20 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -3022,6 +3070,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3052,6 +3113,48 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3140,6 +3243,7 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "peer": true, "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -3241,6 +3345,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -3352,6 +3457,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -3942,13 +4048,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -4102,6 +4210,22 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4118,12 +4242,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -4184,6 +4309,99 @@ "node": ">=10" } }, + "node_modules/mongodb": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.0.tgz", + "integrity": "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.1.1", + "mongodb-connection-string-url": "^7.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz", + "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4288,15 +4506,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4400,9 +4609,10 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -4414,10 +4624,11 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -4518,6 +4729,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -4583,7 +4795,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "engines": { "node": ">=6" } @@ -4844,6 +5055,15 @@ "source-map": "^0.6.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4997,15 +5217,13 @@ "dev": true }, "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, + "license": "MIT", "engines": { - "node": ">=0.6.0" + "node": ">=14.14" } }, "node_modules/tmpl": { @@ -5014,20 +5232,12 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -5100,6 +5310,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -5182,6 +5393,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 2dbcfe3..88521be 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@fightmegg/riot-rate-limiter": "^0.0.23", "debug": "^4.3.4", "ioredis": "^5.4.1", + "mongodb": "^7.1.0", "node-fetch": "^2.6.1", "path-to-regexp": "^6.2.2" }, diff --git a/src/@types/index.ts b/src/@types/index.ts index e57e3e2..cb4c1fa 100644 --- a/src/@types/index.ts +++ b/src/@types/index.ts @@ -1,5 +1,6 @@ import { PlatformId } from "@fightmegg/riot-rate-limiter"; import { RedisOptions } from "ioredis"; +import { MongoClientOptions } from "mongodb"; export type Leaves = T extends object ? { @@ -9,16 +10,36 @@ export type Leaves = T extends object }[keyof T] : never; +export type MongoOptionsExtended = { + url: string; + MongoClientOptions: MongoClientOptions | undefined; +}; + export namespace RiotAPITypes { + export type TTLConfig = { + byMethod: { [key: string]: number }; + }; + + export type CacheConfig = + | { + cacheType: "local"; + client?: null | undefined; + ttls?: TTLConfig; + } + | { + cacheType: "ioredis"; + client: RedisOptions | string; + ttls?: TTLConfig; + } + | { + cacheType: "mongodb"; + client: MongoOptionsExtended; + ttls?: TTLConfig; + }; + export interface Config { debug?: boolean; - cache?: { - cacheType: "local" | "ioredis"; - client?: RedisOptions | string; - ttls?: { - byMethod: { [key: string]: number }; - }; - }; + cache?: CacheConfig; } export interface RequestOptions { @@ -824,6 +845,7 @@ export namespace RiotAPITypes { riotIdName: string; riotIdTagline: string; role: string; + roleBoundItem: number; sightWardsBoughtInGame: number; spell1Casts: number; spell2Casts: number; diff --git a/src/cache.ts b/src/cache.ts index 852e0fd..240af34 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,6 +1,114 @@ +import { + Collection, + Db, + Document, + MongoClient, + MongoClientOptions, +} from "mongodb"; import { Redis, RedisOptions } from "ioredis"; -export class RedisCache { +export interface ICache { + get(key: string): Promise; + set(key: string, value: object, ttl: number): Promise; + flush(): Promise; +} + +export class MongoCache implements ICache { + readonly client: MongoClient; + readonly dbName: string = "riot-api"; + readonly collectionName: string = "cache"; + readonly keyIndexName: string = "riot-api-cache-key-index"; + readonly ttlIndexName: string = "riot-api-cache-ttl-index"; + + private _connectPromise: Promise; + private _db: Db | null = null; + private _collection: Collection | null = null; + + constructor(url: string, options?: MongoClientOptions | undefined) { + this.client = new MongoClient(url, options); + this._connectPromise = this.client + .connect() + .then(() => this.initDatabase()); + } + + private async initDatabase() { + const dbList = await this.client.db().admin().listDatabases(); + if (!dbList.databases.some((db) => db.name === this.dbName)) { + this.client.db(this.dbName); + } + + this._db = this.client.db(this.dbName); + + const collections = await this._db.listCollections().toArray(); + if (!collections.some((col) => col.name === this.collectionName)) { + await this._db.createCollection(this.collectionName); + } + + this._collection = this._db.collection(this.collectionName); + + const indexes = await this._collection.indexes(); + + if (!indexes.some((index) => index.name === this.keyIndexName)) { + await this._collection.createIndex( + { key: 1 }, + { unique: true, name: this.keyIndexName } + ); + } + + if (!indexes.some((index) => index.name === this.ttlIndexName)) { + await this._collection.createIndex( + { expiresAt: 1 }, + { expireAfterSeconds: 0, name: this.ttlIndexName } + ); + } + } + + async get(key: string): Promise { + await this._connectPromise; + + if (!this._collection) + throw new Error("MongoDB collection not initialized"); + + const document = (await this._collection.findOne({ key })) as + | (T & { key: string; value: object }) + | null; + if (!document) return null; + + return document.value as T; + } + + async set(key: string, value: object, ttl: number): Promise<"OK" | "Error"> { + await this._connectPromise; + + if (!this._collection) + throw new Error("MongoDB collection not initialized"); + + let expiresAt: Date | null = null; + if (ttl) { + expiresAt = new Date(Date.now() + ttl); + } + + const result = await this._collection.updateOne( + { key }, + { $set: { value, key, expiresAt } }, + { upsert: true } + ); + + return result.acknowledged ? "OK" : "Error"; + } + + async flush(): Promise { + await this._connectPromise; + + if (!this._collection) + throw new Error("MongoDB collection not initialized"); + + await this._collection.deleteMany({}); + return "OK"; + } +} + +export class RedisCache implements ICache { readonly client: Redis; readonly keyPrefix: string = "fm-riot-api-"; @@ -26,7 +134,7 @@ export class RedisCache { } } -export class MemoryCache { +export class MemoryCache implements ICache { cache: { [key: string]: { expires: number; value: object } }; constructor() { @@ -39,7 +147,7 @@ export class MemoryCache { delete this.cache[key]; return null; } - return this.cache[key].value as any; + return this.cache[key].value as T; } async set(key: string, value: object, ttl: number): Promise<"OK"> { diff --git a/src/ddragon.ts b/src/ddragon.ts index 125e85a..069963d 100644 --- a/src/ddragon.ts +++ b/src/ddragon.ts @@ -17,7 +17,7 @@ export class DDragon { }, }); - if (resp.ok) return resp.json(); + if (resp.ok) return resp.json() as Promise; throw resp; } diff --git a/src/index.ts b/src/index.ts index 0590f22..a529448 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ import { RedisOptions } from "ioredis"; import { compile } from "path-to-regexp"; import qs from "querystring"; import { Leaves, RiotAPITypes } from "./@types"; -import { MemoryCache, RedisCache } from "./cache"; +import { ICache, MemoryCache, MongoCache, RedisCache } from "./cache"; import { DDragon } from "./ddragon"; import { regionToCluster } from "./utils"; @@ -37,7 +37,7 @@ const getPath = (key: Leaves): string => { export { DDragon, PlatformId, RiotAPITypes, regionToCluster }; export class RiotAPI { - readonly cache?: MemoryCache | RedisCache; + readonly cache?: ICache; readonly riotRateLimiter: RiotRateLimiter; @@ -57,15 +57,26 @@ export class RiotAPI { this.riotRateLimiter = new RiotRateLimiter({ concurrency: 10, - datastore: this.config.cache?.cacheType || "local", + datastore: + this.config.cache?.cacheType === "ioredis" ? "ioredis" : "local", redis: this.config.cache?.client as Bottleneck.RedisConnectionOptions, }); this.ddragon = new DDragon(); - if (this.config.cache?.cacheType === "local") - this.cache = new MemoryCache(); - else if (this.config.cache?.cacheType === "ioredis") - this.cache = new RedisCache(this.config.cache?.client as RedisOptions); + switch (this.config.cache?.cacheType) { + case "local": + this.cache = new MemoryCache(); + break; + case "ioredis": + this.cache = new RedisCache(this.config.cache?.client as RedisOptions); + break; + case "mongodb": + this.cache = new MongoCache( + this.config.cache?.client.url, + this.config.cache?.client.MongoClientOptions + ); + break; + } } private getHeaders(headers?: { [key: string]: string }) { @@ -149,66 +160,66 @@ export class RiotAPI { get account() { return { getByPUUID: ({ - region, + cluster, puuid, }: { - region: Exclude; + cluster: Exclude; puuid: string; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.ACCOUNT.GET_BY_PUUID, { puuid }, - { id: `${region}.account.getByPUUID.${puuid}`, priority: 4 } + { id: `${cluster}.account.getByPUUID.${puuid}`, priority: 4 } ), getByRiotId: ({ - region, + cluster, gameName, tagLine, }: { - region: Exclude; + cluster: Exclude; gameName: string; tagLine: string; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.ACCOUNT.GET_BY_RIOT_ID, { gameName, tagLine }, { - id: `${region}.account.getByRiotId.${gameName}.${tagLine}`, + id: `${cluster}.account.getByRiotId.${gameName}.${tagLine}`, priority: 4, } ), getByAccessToken: ({ - region, + cluster, accessToken, }: { - region: Exclude; + cluster: Exclude; accessToken: string; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.ACCOUNT.GET_BY_ACCESS_TOKEN, {}, { - id: `${region}.account.getByAccessToken`, + id: `${cluster}.account.getByAccessToken`, headers: { Authorization: `Bearer ${accessToken}` }, } ), getActiveShardForPlayer: ({ - region, + cluster, game, puuid, }: { - region: Exclude; + cluster: Exclude; game: "val" | "lor"; puuid: string; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.ACCOUNT.GET_ACTIVE_SHARD_FOR_PLAYER, { game, puuid }, - { id: `${region}.account.getActiveShardForPlayer.${game}.${puuid}` } + { id: `${cluster}.account.getActiveShardForPlayer.${game}.${puuid}` } ), }; } @@ -985,11 +996,11 @@ export class RiotAPI { get tftMatch() { return { getMatchIdsByPUUID: ({ - region, + cluster, puuid, params, }: { - region: RiotAPITypes.TFTCluster; + cluster: RiotAPITypes.TFTCluster; puuid: string; params?: { start?: number; @@ -999,23 +1010,23 @@ export class RiotAPI { }; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.TFT_MATCH.GET_MATCH_IDS_BY_PUUID, { puuid }, - { id: `${region}.tftMatch.getMatchIdsByPUUID.${puuid}`, params } + { id: `${cluster}.tftMatch.getMatchIdsByPUUID.${puuid}`, params } ), getById: ({ - region, + cluster, matchId, }: { - region: RiotAPITypes.TFTCluster; + cluster: RiotAPITypes.TFTCluster; matchId: string; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.TFT_MATCH.GET_MATCH_BY_ID, { matchId }, - { id: `${region}.tftMatch.getById.${matchId}` } + { id: `${cluster}.tftMatch.getById.${matchId}` } ), }; } @@ -1209,7 +1220,7 @@ export class RiotAPI { }: { tournamentCode: string; body: RiotAPITypes.TournamentV5.TournamentCodeUpdateParametersV5DTO; - }): Promise => + }): Promise => this.request( PlatformId.AMERICAS, RiotAPITypes.METHOD_KEY.TOURNAMENT_V5.GET_TOURNAMENT_BY_CODE, @@ -1288,19 +1299,19 @@ export class RiotAPI { get valContent() { return { getContent: ({ - region, + cluster, params, }: { - region: RiotAPITypes.VALCluster; + cluster: RiotAPITypes.VALCluster; params?: { locale?: string; }; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.VAL_CONTENT.GET_CONTENT, {}, - { id: `${region}.valContent.getContent`, params } + { id: `${cluster}.valContent.getContent`, params } ), }; } @@ -1308,43 +1319,43 @@ export class RiotAPI { get valMatch() { return { getById: ({ - region, + cluster, matchId, }: { - region: RiotAPITypes.VALCluster; + cluster: RiotAPITypes.VALCluster; matchId: string; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.VAL_MATCH.GET_MATCH_BY_ID, { matchId }, - { id: `${region}.valMatch.getById.${matchId}` } + { id: `${cluster}.valMatch.getById.${matchId}` } ), getMatchlistByPUUID: ({ - region, + cluster, puuid, }: { - region: RiotAPITypes.VALCluster; + cluster: RiotAPITypes.VALCluster; puuid: string; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.VAL_MATCH.GET_MATCHLIST_BY_PUUID, { puuid }, - { id: `${region}.valMatch.getMatchlistByPUUID.${puuid}` } + { id: `${cluster}.valMatch.getMatchlistByPUUID.${puuid}` } ), getRecentMatchesByQueue: ({ - region, + cluster, queue, }: { - region: RiotAPITypes.VALCluster; + cluster: RiotAPITypes.VALCluster; queue: RiotAPITypes.VAL_QUEUE; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.VAL_MATCH.GET_RECENT_MATCHES_BY_QUEUE, { queue }, - { id: `${region}.valMatch.getRecentMatchesByQueue.${queue}` } + { id: `${cluster}.valMatch.getRecentMatchesByQueue.${queue}` } ), }; } @@ -1352,11 +1363,11 @@ export class RiotAPI { get valRanked() { return { getLeaderboardByQueue: ({ - region, + cluster, queue, params, }: { - region: Exclude; + cluster: Exclude; queue: string; params?: { size?: number; @@ -1364,10 +1375,10 @@ export class RiotAPI { }; }): Promise => this.request( - region, + cluster, RiotAPITypes.METHOD_KEY.VAL_RANKED.GET_LEADERBOARD_BY_QUEUE, { actId: queue }, - { id: `${region}.valRanked.getLeaderboardByQueue.${queue}`, params } + { id: `${cluster}.valRanked.getLeaderboardByQueue.${queue}`, params } ), }; }