mirror of
https://github.com/tcgdex/javascript-sdk.git
synced 2025-04-22 02:42:08 +00:00
feat: Allow a user to filters elements on the API (#275)
This commit is contained in:
parent
f2621890e1
commit
bf54ab3809
@ -17,7 +17,7 @@
|
||||
<img src="https://img.shields.io/github/stars/tcgdex/javascript-sdk?style=flat-square" alt="Github stars">
|
||||
</a>
|
||||
<a href="https://github.com/tcgdex/javascript-sdk/actions/workflows/build.yml">
|
||||
<img src="https://img.shields.io/github/workflow/status/tcgdex/javascript-sdk/Build%20&%20Test?style=flat-square" alt="the TCGdex JAvascript SDK is released under the MIT license." />
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/tcgdex/javascript-sdk/build.yml?style=flat-square" alt="the TCGdex JAvascript SDK is released under the MIT license." />
|
||||
</a>
|
||||
<a href="https://discord.gg/NehYTAhsZE">
|
||||
<img src="https://img.shields.io/discord/857231041261076491?color=%235865F2&label=Discord&style=flat-square" alt="Discord Link">
|
||||
|
@ -1,5 +1,7 @@
|
||||
const TCGdex = require("../src/tcgdex").default
|
||||
const fetch = require('node-fetch')
|
||||
/// <reference types="jest" />
|
||||
|
||||
const { default: TCGdex, Query } = require("../src/tcgdex")
|
||||
import fetch from 'node-fetch'
|
||||
|
||||
const fakeFetch = (response, status = 200) => jest.fn(() =>
|
||||
Promise.resolve({
|
||||
@ -8,59 +10,131 @@ const fakeFetch = (response, status = 200) => jest.fn(() =>
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
|
||||
test('Basic test', async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fakeFetch({ok: true})
|
||||
TCGdex.fetch = fakeFetch({ ok: true })
|
||||
const res = await tcgdex.fetch('cards', 'basic-test')
|
||||
expect(res).toEqual({ok: true})
|
||||
expect(res).toEqual({ ok: true })
|
||||
expect(TCGdex.fetch).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('Cache test', async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fakeFetch({ok: 'a'})
|
||||
const res1 = await tcgdex.fetch('cards', 'cache-test')
|
||||
expect(res1).toEqual({ok: 'a'})
|
||||
TCGdex.fetch = fakeFetch({ok: 'b'})
|
||||
const res2 = await tcgdex.fetch('cards', 'cache-test')
|
||||
expect(res2).toEqual({ok: 'a'})
|
||||
})
|
||||
|
||||
test('endpoint errors', async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fakeFetch({ok: 'a'})
|
||||
TCGdex.fetch = fakeFetch({ ok: 'a' })
|
||||
await expect(tcgdex.fetch('non existing endpoint')).rejects.toThrow()
|
||||
await expect(tcgdex.fetch()).rejects.toThrow()
|
||||
})
|
||||
|
||||
test('404 test', async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fakeFetch(undefined, 404)
|
||||
expect(
|
||||
await tcgdex.fetch('cards', '404-test')
|
||||
).not.toBeDefined()
|
||||
})
|
||||
|
||||
test('test real endpoints', async () => {
|
||||
test(`404 error`, async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
const endpoints = [
|
||||
{endpoint: 'fetchCard', params: ['swsh1-1']},
|
||||
{endpoint: 'fetchCard', params: ['1', 'Sword & Shield']},
|
||||
{endpoint: 'fetchCards', params: ['swsh1']},
|
||||
{endpoint: 'fetchCards', params: []},
|
||||
{endpoint: 'fetchSet', params: ['swsh1']},
|
||||
{endpoint: 'fetchSets', params: ['swsh']},
|
||||
{endpoint: 'fetchSets', params: []},
|
||||
{endpoint: 'fetchSeries', params: []},
|
||||
{endpoint: 'fetchSerie', params: ['swsh']},
|
||||
]
|
||||
|
||||
for await (const item of endpoints) {
|
||||
expect(
|
||||
await tcgdex[item.endpoint](...item.params)
|
||||
).toBeDefined()
|
||||
}
|
||||
expect(
|
||||
await tcgdex.card.get('404-error')
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
test(`test getting full set from list`, async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
|
||||
expect(
|
||||
await (await tcgdex.set.list())[0].getSet()
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test(`test getting full serie from list`, async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
|
||||
expect(
|
||||
await (await tcgdex.serie.list())[0].getSerie()
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test(`test getting full card from list`, async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
|
||||
expect(
|
||||
await (await tcgdex.card.list())[0].getCard()
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
|
||||
test(`test get set from card`, async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
|
||||
expect(
|
||||
await (await tcgdex.card.get('swsh1-136')).getSet()
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test(`test get serie from set`, async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
|
||||
expect(
|
||||
await (await tcgdex.set.get('swsh1')).getSerie()
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test(`advanced query system`, async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
|
||||
expect(
|
||||
(await tcgdex.card.list(
|
||||
Query.create()
|
||||
.equal('name', 'Pikachu')
|
||||
.greaterOrEqualThan('hp', 60)
|
||||
.lesserThan('hp', 70)
|
||||
.contains('localId', '5')
|
||||
.not.contains('localId', 'tg')
|
||||
.not.equal('id', 'cel25-5')
|
||||
.sort('localId', 'ASC')
|
||||
.paginate(3, 2)
|
||||
)).length
|
||||
).toBe(2)
|
||||
})
|
||||
|
||||
const endpoints = [
|
||||
{ endpoint: 'card', params: ['swsh1-136'] },
|
||||
{ endpoint: 'set', params: ['swsh1'] },
|
||||
{ endpoint: 'serie', params: ['swsh'] },
|
||||
{ endpoint: 'type', params: ['fire'] },
|
||||
{ endpoint: 'retreat', params: ['1'] },
|
||||
{ endpoint: 'rarity', params: ['common'] },
|
||||
{ endpoint: 'illustrator', params: [''] },
|
||||
{ endpoint: 'hp', params: ['30'] },
|
||||
{ endpoint: 'categorie', params: ['pokemon'] },
|
||||
{ endpoint: 'dexID', params: ['1'] },
|
||||
{ endpoint: 'energyType', params: ['normal'] },
|
||||
{ endpoint: 'regulationMark', params: ['f'] },
|
||||
{ endpoint: 'stage', params: ['basic'] },
|
||||
{ endpoint: 'suffixe', params: ['ex'] },
|
||||
{ endpoint: 'trainerType', params: ['item'] },
|
||||
{ endpoint: 'variant', params: ['normal'] },
|
||||
]
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
test(`test real ${endpoint.endpoint} endpoint list`, async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
|
||||
expect(
|
||||
await (tcgdex[endpoint.endpoint]).list()
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test(`test real ${endpoint.endpoint} endpoint item`, async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
|
||||
expect(
|
||||
await (tcgdex[endpoint.endpoint]).get(endpoint.params[0])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
}
|
||||
|
54
__tests__/cache.test.js
Normal file
54
__tests__/cache.test.js
Normal file
@ -0,0 +1,54 @@
|
||||
/// <reference types="jest" />
|
||||
|
||||
const { default: MemoryCache } = require("../src/Psr/SimpleCache/MemoryCache")
|
||||
|
||||
const TCGdex = require("../src/tcgdex").default
|
||||
|
||||
test('that cache store and get one element', async () => {
|
||||
const cache = new MemoryCache()
|
||||
cache.set('a', 'b')
|
||||
expect(cache.get('a')).toBe('b')
|
||||
})
|
||||
|
||||
test('that cache store and get multiple elements', async () => {
|
||||
const cache = new MemoryCache()
|
||||
cache.setMultiple({
|
||||
'a': 'b',
|
||||
'c': 'd'
|
||||
})
|
||||
expect(cache.getMultiple(['a', 'c'])).toStrictEqual({
|
||||
a: 'b',
|
||||
c: 'd'
|
||||
})
|
||||
})
|
||||
|
||||
test('cache expiration', async () => {
|
||||
const cache = new MemoryCache()
|
||||
cache.set('a', 'b', 1)
|
||||
// wait 2 secs
|
||||
await new Promise((res) => setTimeout(res, 2000))
|
||||
expect(cache.get('a')).toBeUndefined()
|
||||
})
|
||||
|
||||
test('cache deletion', async () => {
|
||||
const cache = new MemoryCache()
|
||||
cache.set('a', 'b')
|
||||
expect(cache.get('a')).toBe('b')
|
||||
cache.delete('a')
|
||||
expect(cache.get('a')).toBeUndefined()
|
||||
})
|
||||
|
||||
test('cache cleared', async () => {
|
||||
const cache = new MemoryCache()
|
||||
cache.set('a', 'b')
|
||||
expect(cache.get('a')).toBe('b')
|
||||
cache.clear()
|
||||
expect(cache.get('a')).toBeUndefined()
|
||||
})
|
||||
|
||||
test('cache exists', async () => {
|
||||
const cache = new MemoryCache()
|
||||
expect(cache.has('a')).toBe(false)
|
||||
cache.set('a', 'b')
|
||||
expect(cache.has('a')).toBe(true)
|
||||
})
|
66
__tests__/deprecated.test.js
Normal file
66
__tests__/deprecated.test.js
Normal file
@ -0,0 +1,66 @@
|
||||
const TCGdex = require("../src/tcgdex").default
|
||||
const fetch = require('node-fetch')
|
||||
|
||||
const fakeFetch = (response, status = 200) => jest.fn(() =>
|
||||
Promise.resolve({
|
||||
status: status,
|
||||
json: () => Promise.resolve(response),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
|
||||
test('Basic test', async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fakeFetch({ok: true})
|
||||
const res = await tcgdex.fetch('cards', 'basic-test')
|
||||
expect(res).toEqual({ok: true})
|
||||
expect(TCGdex.fetch).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('Cache test', async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fakeFetch({ok: 'a'})
|
||||
const res1 = await tcgdex.fetch('cards', 'cache-test')
|
||||
expect(res1).toEqual({ok: 'a'})
|
||||
TCGdex.fetch = fakeFetch({ok: 'b'})
|
||||
const res2 = await tcgdex.fetch('cards', 'cache-test')
|
||||
expect(res2).toEqual({ok: 'a'})
|
||||
})
|
||||
|
||||
test('endpoint errors', async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fakeFetch({ok: 'a'})
|
||||
await expect(tcgdex.fetch('non existing endpoint')).rejects.toThrow()
|
||||
await expect(tcgdex.fetch()).rejects.toThrow()
|
||||
})
|
||||
|
||||
test('404 test', async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fakeFetch(undefined, 404)
|
||||
expect(
|
||||
await tcgdex.fetch('cards', '404-test')
|
||||
).not.toBeDefined()
|
||||
})
|
||||
|
||||
test('test real endpoints', async () => {
|
||||
const tcgdex = new TCGdex('en')
|
||||
TCGdex.fetch = fetch
|
||||
const endpoints = [
|
||||
{endpoint: 'fetchCard', params: ['swsh1-1']},
|
||||
{endpoint: 'fetchCard', params: ['1', 'Sword & Shield']},
|
||||
{endpoint: 'fetchCards', params: ['swsh1']},
|
||||
{endpoint: 'fetchCards', params: []},
|
||||
{endpoint: 'fetchSet', params: ['swsh1']},
|
||||
{endpoint: 'fetchSets', params: ['swsh']},
|
||||
{endpoint: 'fetchSets', params: []},
|
||||
{endpoint: 'fetchSeries', params: []},
|
||||
{endpoint: 'fetchSerie', params: ['swsh']},
|
||||
]
|
||||
|
||||
for await (const item of endpoints) {
|
||||
expect(
|
||||
await tcgdex[item.endpoint](...item.params)
|
||||
).toBeDefined()
|
||||
}
|
||||
})
|
58
package-lock.json
generated
58
package-lock.json
generated
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@tcgdex/sdk",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.0-beta.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@tcgdex/sdk",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.0-beta.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"isomorphic-unfetch": "^3",
|
||||
"unfetch": "^4"
|
||||
"@dzeio/object-util": "^1",
|
||||
"isomorphic-unfetch": "^3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7",
|
||||
@ -1750,11 +1750,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@dzeio/object-util": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@dzeio/object-util/-/object-util-1.4.5.tgz",
|
||||
"integrity": "sha512-V04GE77lipF2qnzMuA+T3blyPVo+ABKQLrmEteerXecA7G+TCisyQKIVMewFvF9qNsJ1LOVTckWW9wnRPyAwoQ==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@dzeio/object-util/-/object-util-1.8.3.tgz",
|
||||
"integrity": "sha512-/d0ezut7EGrEKedcD8K2Jb2NAMSFfhxNj4rpUBlGzmmakJjJCXAgXvSDLjUwYrgHuabxbxlAn90Wo727MCzWLA=="
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "1.4.1",
|
||||
@ -2768,10 +2766,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.11.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
|
||||
"dev": true
|
||||
"version": "20.12.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz",
|
||||
"integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.2",
|
||||
@ -8889,6 +8890,12 @@
|
||||
"typpy": "^2.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/unfetch": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
|
||||
@ -10387,11 +10394,9 @@
|
||||
}
|
||||
},
|
||||
"@dzeio/object-util": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@dzeio/object-util/-/object-util-1.4.5.tgz",
|
||||
"integrity": "sha512-V04GE77lipF2qnzMuA+T3blyPVo+ABKQLrmEteerXecA7G+TCisyQKIVMewFvF9qNsJ1LOVTckWW9wnRPyAwoQ==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@dzeio/object-util/-/object-util-1.8.3.tgz",
|
||||
"integrity": "sha512-/d0ezut7EGrEKedcD8K2Jb2NAMSFfhxNj4rpUBlGzmmakJjJCXAgXvSDLjUwYrgHuabxbxlAn90Wo727MCzWLA=="
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "1.4.1",
|
||||
@ -11183,10 +11188,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.11.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
|
||||
"dev": true
|
||||
"version": "20.12.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz",
|
||||
"integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "2.6.2",
|
||||
@ -15647,6 +15655,12 @@
|
||||
"typpy": "^2.3.4"
|
||||
}
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"unfetch": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@tcgdex/sdk",
|
||||
"version": "2.5.1",
|
||||
"version": "2.6.0-beta.1",
|
||||
"main": "./dist/tcgdex.node.js",
|
||||
"module": "./dist/tcgdex.node.mjs",
|
||||
"types": "./dist/tcgdex.node.d.ts",
|
||||
@ -53,8 +53,8 @@
|
||||
"node": ">=12"
|
||||
},
|
||||
"dependencies": {
|
||||
"isomorphic-unfetch": "^3",
|
||||
"unfetch": "^4"
|
||||
"@dzeio/object-util": "^1",
|
||||
"isomorphic-unfetch": "^3"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "node scripts/export-version-number.js",
|
||||
|
38
src/Psr/SimpleCache/CacheAbstract.ts
Normal file
38
src/Psr/SimpleCache/CacheAbstract.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import type CacheInterface from './CacheInterface'
|
||||
|
||||
export default abstract class CacheAsbract implements CacheInterface {
|
||||
|
||||
public getMultiple<T>(keys: Array<string>, defaultValues?: Array<T> | undefined): Record<string, T> {
|
||||
const res: Record<string, T> = {}
|
||||
for (let idx = 0; idx < keys.length; idx++) {
|
||||
const key = keys[idx] as string
|
||||
const value = this.get(key, defaultValues?.[idx]) as T | undefined
|
||||
if (typeof value === 'undefined') {
|
||||
continue
|
||||
}
|
||||
res[key] = value
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
public setMultiple<T>(values: Record<string, T>, ttl?: number | undefined): boolean {
|
||||
objectLoop(values, (v, k) => {
|
||||
this.set(k, v, ttl)
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
public deleteMultiple(keys: Array<string>): boolean {
|
||||
for (const key of keys) {
|
||||
this.delete(key)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public abstract get<T>(key: string, defaultValue?: T): T | undefined
|
||||
public abstract set<T>(key: string, value: T, ttl?: number): boolean
|
||||
public abstract delete(key: string): boolean
|
||||
public abstract clear(): boolean
|
||||
public abstract has(key: string): boolean
|
||||
}
|
109
src/Psr/SimpleCache/CacheInterface.d.ts
vendored
Normal file
109
src/Psr/SimpleCache/CacheInterface.d.ts
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
export default interface CacheInterface {
|
||||
/**
|
||||
* Fetches a value from the cache.
|
||||
*
|
||||
* @param key The unique key of this item in the cache.
|
||||
* @param defaultValue Default value to return if the key does not exist.
|
||||
*
|
||||
* @return T The value of the item from the cache, or $default in case of cache miss.
|
||||
*
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
* MUST be thrown if the $key string is not a legal value.
|
||||
*/
|
||||
get<T>(key: string, defaultValue?: T): T | undefined
|
||||
|
||||
/**
|
||||
* Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
|
||||
*
|
||||
* @param key The key of the item to store.
|
||||
* @param value The value of the item to store. Must be serializable.
|
||||
* @param {null|number} ttl The TTL value of this item. If no value is sent and
|
||||
* the driver supports TTL then the library may set a default value
|
||||
* for it or let the driver take care of that.
|
||||
*
|
||||
* @return bool True on success and false on failure.
|
||||
*
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
* MUST be thrown if the $key string is not a legal value.
|
||||
*/
|
||||
set<T>(key: string, value: T, ttl?: number): boolean
|
||||
|
||||
/**
|
||||
* Delete an item from the cache by its unique key.
|
||||
*
|
||||
* @param key The unique cache key of the item to delete.
|
||||
*
|
||||
* @return True if the item was successfully removed. False if there was an error.
|
||||
*
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
* MUST be thrown if the $key string is not a legal value.
|
||||
*/
|
||||
delete(key: string): boolean
|
||||
|
||||
/**
|
||||
* Wipes clean the entire cache's keys.
|
||||
*
|
||||
* @return boolean True on success and false on failure.
|
||||
*/
|
||||
clear(): boolean
|
||||
|
||||
/**
|
||||
* Obtains multiple cache items by their unique keys.
|
||||
*
|
||||
* @param keys A list of keys that can obtained in a single operation.
|
||||
* @param defaultValues $default Default value to return for keys that do not exist.
|
||||
*
|
||||
* @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
|
||||
*
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
* MUST be thrown if $keys is neither an array nor a Traversable,
|
||||
* or if any of the $keys are not a legal value.
|
||||
*/
|
||||
getMultiple<T>(keys: Array<string>, defaultValues?: Array<T>): Record<string, T>
|
||||
|
||||
/**
|
||||
* Persists a set of key => value pairs in the cache, with an optional TTL.
|
||||
*
|
||||
* @param values A list of key => value pairs for a multiple-set operation.
|
||||
* @param ttl Optional. The TTL value of this item. If no value is sent and
|
||||
* the driver supports TTL then the library may set a default value
|
||||
* for it or let the driver take care of that.
|
||||
*
|
||||
* @return bool True on success and false on failure.
|
||||
*
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
* MUST be thrown if $values is neither an array nor a Traversable,
|
||||
* or if any of the $values are not a legal value.
|
||||
*/
|
||||
setMultiple<T>(values: Record<string, T>, ttl?: number): boolean
|
||||
|
||||
/**
|
||||
* Deletes multiple cache items in a single operation.
|
||||
*
|
||||
* @param keys A list of string-based keys to be deleted.
|
||||
*
|
||||
* @return bool True if the items were successfully removed. False if there was an error.
|
||||
*
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
* MUST be thrown if $keys is neither an array nor a Traversable,
|
||||
* or if any of the $keys are not a legal value.
|
||||
*/
|
||||
deleteMultiple(keys: Array<string>): boolean
|
||||
|
||||
/**
|
||||
* Determines whether an item is present in the cache.
|
||||
*
|
||||
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
|
||||
* and not to be used within your live applications operations for get/set, as this method
|
||||
* is subject to a race condition where your has() will return true and immediately after,
|
||||
* another script can remove it, making the state of your app out of date.
|
||||
*
|
||||
* @param key The cache item key.
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
* MUST be thrown if the $key string is not a legal value.
|
||||
*/
|
||||
has(key: string): boolean
|
||||
}
|
91
src/Psr/SimpleCache/LocalStorageCache.ts
Normal file
91
src/Psr/SimpleCache/LocalStorageCache.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import CacheAsbract from './CacheAbstract'
|
||||
|
||||
interface CacheItem<T> {
|
||||
data: T
|
||||
expire?: number | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* A cache implementation that uses browser storage.
|
||||
*
|
||||
* This class extends `CacheAsbract` and provides a concrete implementation
|
||||
* of the caching interface. It stores cached items in browser storage,
|
||||
* which is suitable for storing small amounts of data.
|
||||
*/
|
||||
export default class BrowserStorageCache extends CacheAsbract {
|
||||
private storage: Storage
|
||||
public constructor(private readonly prefix?: string, session = false) {
|
||||
super()
|
||||
if (session) {
|
||||
this.storage = window.sessionStorage
|
||||
} else {
|
||||
this.storage = window.localStorage
|
||||
}
|
||||
}
|
||||
|
||||
public get<T>(key: string, defaultValue?: T | undefined): T | undefined {
|
||||
const raw = this.storage.getItem(this.getFinalKey(key))
|
||||
|
||||
if (!raw) {
|
||||
return defaultValue ?? undefined
|
||||
}
|
||||
|
||||
const item: CacheItem<T> = JSON.parse(raw)
|
||||
|
||||
if (item.expire && item.expire < new Date().getTime()) {
|
||||
this.delete(key)
|
||||
return defaultValue ?? undefined
|
||||
}
|
||||
|
||||
return item.data
|
||||
}
|
||||
|
||||
public set<T>(key: string, value: T, ttl?: number | undefined): boolean {
|
||||
let expire = undefined
|
||||
if (ttl) {
|
||||
expire = (new Date()).getTime() + (ttl * 1000)
|
||||
}
|
||||
const data: CacheItem<unknown> = {
|
||||
data: value,
|
||||
expire: expire
|
||||
}
|
||||
this.storage.setItem(this.getFinalKey(key), JSON.stringify(data))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public delete(key: string): boolean {
|
||||
this.storage.removeItem(this.getFinalKey(key))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public clear(): boolean {
|
||||
const keys = this.keys()
|
||||
return this.deleteMultiple(keys)
|
||||
}
|
||||
|
||||
public has(key: string): boolean {
|
||||
return !!this.storage.getItem(this.getFinalKey(key))
|
||||
}
|
||||
|
||||
private keys(): Array<string> {
|
||||
const list: Array<string> = []
|
||||
for (let idx = 0; idx < this.storage.length; idx++) {
|
||||
const key = this.storage.key(idx)
|
||||
if (!key || this.prefix && !key?.startsWith(`${this.prefix}/`)) {
|
||||
continue
|
||||
}
|
||||
list.push(key)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
private getFinalKey(key: string): string {
|
||||
if (!this.prefix) {
|
||||
return key
|
||||
}
|
||||
return `${this.prefix}/${key}`
|
||||
}
|
||||
}
|
58
src/Psr/SimpleCache/MemoryCache.ts
Normal file
58
src/Psr/SimpleCache/MemoryCache.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import CacheAsbract from './CacheAbstract'
|
||||
|
||||
interface CacheItem<T> {
|
||||
data: T
|
||||
expire?: number | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory cache implementation that stores cached items in memory.
|
||||
* This class extends the abstract `CacheAbstract` and provides a basic in-memory caching mechanism.
|
||||
*
|
||||
* @class MemoryCache
|
||||
*/
|
||||
export default class MemoryCache extends CacheAsbract {
|
||||
private cache: Map<string, CacheItem<unknown>> = new Map()
|
||||
|
||||
public get<T>(key: string, defaultValue?: T | undefined): T | undefined {
|
||||
const item = this.cache.get(key)
|
||||
|
||||
if (!item) {
|
||||
return defaultValue ?? undefined
|
||||
}
|
||||
|
||||
if (item.expire && item.expire < new Date().getTime()) {
|
||||
this.delete(key)
|
||||
return defaultValue ?? undefined
|
||||
}
|
||||
|
||||
return item.data as T | undefined
|
||||
}
|
||||
|
||||
public set<T>(key: string, value: T, ttl?: number | undefined): boolean {
|
||||
let expire: number | undefined
|
||||
if (ttl) {
|
||||
expire = new Date().getTime() + ttl * 1000
|
||||
}
|
||||
this.cache.set(key, {
|
||||
data: value,
|
||||
expire: expire
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public delete(key: string): boolean {
|
||||
this.cache.delete(key)
|
||||
return true
|
||||
}
|
||||
|
||||
public clear(): boolean {
|
||||
this.cache.clear()
|
||||
return true
|
||||
}
|
||||
|
||||
public has(key: string): boolean {
|
||||
return this.cache.has(key)
|
||||
}
|
||||
}
|
88
src/Query.ts
Normal file
88
src/Query.ts
Normal file
@ -0,0 +1,88 @@
|
||||
export default class Query {
|
||||
public params: Array<{ key: string, value: string | number | boolean }> = []
|
||||
|
||||
public not: {
|
||||
equal: (key: string, value: string) => Query
|
||||
contains: (key: string, value: string) => Query
|
||||
includes: (key: string, value: string) => Query
|
||||
like: (key: string, value: string) => Query
|
||||
isNull: (key: string) => Query
|
||||
} = {
|
||||
equal: (key: string, value: string) => {
|
||||
this.params.push({ key: key, value: `neq:${value}` })
|
||||
return this
|
||||
},
|
||||
contains: (key: string, value: string) => {
|
||||
this.params.push({ key: key, value: `not:${value}` })
|
||||
return this
|
||||
},
|
||||
includes: (key: string, value: string) => this.not.contains(key, value),
|
||||
like: (key: string, value: string) => this.not.contains(key, value),
|
||||
isNull: (key: string) => {
|
||||
this.params.push({ key: key, value: 'notnull:' })
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
public static create(): Query {
|
||||
return new Query()
|
||||
}
|
||||
|
||||
public includes(key: string, value: string): this {
|
||||
return this.contains(key, value)
|
||||
}
|
||||
|
||||
public like(key: string, value: string): this {
|
||||
return this.contains(key, value)
|
||||
}
|
||||
|
||||
public contains(key: string, value: string): this {
|
||||
this.params.push({ key: key, value: value })
|
||||
return this
|
||||
}
|
||||
|
||||
public equal(key: string, value: string): this {
|
||||
this.params.push({ key: key, value: `eq:${value}` })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public sort(key: string, order: 'ASC' | 'DESC'): this {
|
||||
this.params.push({ key: 'sort:field', value: key })
|
||||
this.params.push({ key: 'sort:order', value: order })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public greaterOrEqualThan(key: string, value: number) {
|
||||
this.params.push({ key: key, value: `gte:${value}` })
|
||||
return this
|
||||
}
|
||||
|
||||
public lesserOrEqualThan(key: string, value: number) {
|
||||
this.params.push({ key: key, value: `lte:${value}` })
|
||||
return this
|
||||
}
|
||||
|
||||
public greaterThan(key: string, value: number) {
|
||||
this.params.push({ key: key, value: `gt:${value}` })
|
||||
return this
|
||||
}
|
||||
|
||||
public lesserThan(key: string, value: number) {
|
||||
this.params.push({ key: key, value: `lt:${value}` })
|
||||
return this
|
||||
}
|
||||
|
||||
public isNull(key: string) {
|
||||
this.params.push({ key: key, value: 'null:' })
|
||||
return this
|
||||
}
|
||||
|
||||
public paginate(page: number, itemsPerPage: number): this {
|
||||
this.params.push({ key: 'pagination:page', value: page })
|
||||
this.params.push({ key: 'pagination:itemsPerPage', value: itemsPerPage })
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import TCGdex from './tcgdex'
|
||||
import { version } from './version'
|
||||
|
||||
export default class Request {
|
||||
|
||||
// 1 hour of TTL by default
|
||||
public static ttl = 1000 * 60 * 60
|
||||
|
||||
private static cache: Record<string, {response: any, time: number}> = {}
|
||||
|
||||
public static async fetch<T>(url: string): Promise<T | undefined> {
|
||||
let request = this.cache[url]
|
||||
const now = new Date().getTime()
|
||||
if (!request || now - request.time > this.ttl) {
|
||||
const unfetch = TCGdex.fetch
|
||||
const resp = await unfetch(url, {
|
||||
headers: {
|
||||
'user-agent': `@tcgdex/javascript-sdk/${version}`
|
||||
}
|
||||
})
|
||||
if (resp.status !== 200) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
this.cache[url] = { response: await resp.json(), time: now }
|
||||
request = this.cache[url]
|
||||
}
|
||||
return request.response
|
||||
}
|
||||
|
||||
}
|
26
src/endpoints/Endpoint.ts
Normal file
26
src/endpoints/Endpoint.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { Endpoints } from '../interfaces'
|
||||
import Model from '../models/Model'
|
||||
import type Query from '../Query'
|
||||
import type TCGdex from '../tcgdex'
|
||||
|
||||
export default class Endpoint<Item extends Model, List extends Model> {
|
||||
public constructor(
|
||||
protected readonly tcgdex: TCGdex,
|
||||
protected readonly itemModel: new (sdk: TCGdex) => Item,
|
||||
protected readonly listModel: new (sdk: TCGdex) => List,
|
||||
protected readonly endpoint: Endpoints
|
||||
) { }
|
||||
|
||||
public async get(id: string | number): Promise<Item | null> {
|
||||
const res = await this.tcgdex.fetch(this.endpoint as 'cards', id as string)
|
||||
if (!res) {
|
||||
return null
|
||||
}
|
||||
return Model.build(new this.itemModel(this.tcgdex), res)
|
||||
}
|
||||
|
||||
public async list(query?: Query): Promise<Array<List>> {
|
||||
const res = await this.tcgdex.fetchWithQuery([this.endpoint], query?.params)
|
||||
return (res as Array<object> ?? []).map((it) => Model.build(new this.listModel(this.tcgdex), it))
|
||||
}
|
||||
}
|
24
src/endpoints/SimpleEndpoint.ts
Normal file
24
src/endpoints/SimpleEndpoint.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { Endpoints } from '../interfaces'
|
||||
import Model from '../models/Model'
|
||||
import type Query from '../Query'
|
||||
import type TCGdex from '../tcgdex'
|
||||
|
||||
export default class SimpleEndpoint<Item extends Model, List extends string | number> {
|
||||
public constructor(
|
||||
protected readonly tcgdex: TCGdex,
|
||||
protected readonly itemModel: new (sdk: TCGdex) => Item,
|
||||
protected readonly endpoint: Endpoints
|
||||
) {}
|
||||
|
||||
public async get(id: string | number): Promise<Item | null> {
|
||||
const res = await this.tcgdex.fetch(this.endpoint as 'cards', id as string)
|
||||
if (!res) {
|
||||
return null
|
||||
}
|
||||
return Model.build(new this.itemModel(this.tcgdex), res)
|
||||
}
|
||||
|
||||
public async list(query?: Query): Promise<Array<List>> {
|
||||
return await this.tcgdex.fetchWithQuery([this.endpoint], query?.params) ?? []
|
||||
}
|
||||
}
|
9
src/interfaces.ts → src/interfaces.d.ts
vendored
9
src/interfaces.ts → src/interfaces.d.ts
vendored
@ -310,3 +310,12 @@ export interface StringEndpoint {
|
||||
name: string
|
||||
cards: Array<CardResume>
|
||||
}
|
||||
|
||||
export type Quality = 'low' | 'high'
|
||||
|
||||
export type Extension = 'jpg' | 'webp' | 'png'
|
||||
|
||||
export type Endpoints = 'cards' | 'categories' | 'dex-ids' | 'energy-types' |
|
||||
'hp' | 'illustrators' | 'rarities' | 'regulation-marks' |
|
||||
'retreats' | 'series' | 'sets' | 'stages' | 'suffixes' |
|
||||
'trainer-types' | 'types' | 'variants'
|
198
src/models/Card.ts
Normal file
198
src/models/Card.ts
Normal file
@ -0,0 +1,198 @@
|
||||
import CardResume from './CardResume'
|
||||
import type { Variants } from './Other'
|
||||
import type TCGdexSet from './Set'
|
||||
import type SetResume from './SetResume'
|
||||
|
||||
// TODO: sort elements by alphabetical order
|
||||
export default class Card extends CardResume {
|
||||
/**
|
||||
* Card illustrator
|
||||
*/
|
||||
public illustrator?: string
|
||||
|
||||
/**
|
||||
* Card Rarity
|
||||
*
|
||||
* - None https://www.tcgdex.net/database/sm/smp/SM01
|
||||
* - Common https://www.tcgdex.net/database/xy/xy9/1
|
||||
* - Uncommon https://www.tcgdex.net/database/xy/xy9/2
|
||||
* - Rare https://www.tcgdex.net/database/xy/xy9/3
|
||||
* - Ultra Rare
|
||||
* - Secret Rare
|
||||
*/
|
||||
public rarity!: string
|
||||
|
||||
/**
|
||||
* Card Category
|
||||
*
|
||||
* - Pokemon
|
||||
* - Trainer
|
||||
* - Energy
|
||||
*/
|
||||
public category!: string
|
||||
|
||||
/**
|
||||
* Card Variants (Override Set Variants)
|
||||
*/
|
||||
public variants?: Variants
|
||||
|
||||
/**
|
||||
* Card Set
|
||||
*/
|
||||
public set!: SetResume
|
||||
|
||||
/**
|
||||
* Pokemon only elements
|
||||
*/
|
||||
|
||||
/**
|
||||
* Pokemon Pokedex ID
|
||||
*/
|
||||
public dexId?: Array<number>
|
||||
|
||||
/**
|
||||
* Pokemon HP
|
||||
*/
|
||||
public hp?: number
|
||||
|
||||
/**
|
||||
* Pokemon Types
|
||||
* ex for multiple https://www.tcgdex.net/database/ex/ex13/17
|
||||
*/
|
||||
public types?: Array<string>
|
||||
|
||||
/**
|
||||
* Pokemon Sub Evolution
|
||||
*/
|
||||
public evolveFrom?: string
|
||||
|
||||
/**
|
||||
* Pokemon Weight
|
||||
*/
|
||||
public weight?: string
|
||||
|
||||
/**
|
||||
* Pokemon Description
|
||||
*/
|
||||
public description?: string
|
||||
|
||||
/**
|
||||
* Level of the Pokemon
|
||||
*
|
||||
* NOTE: can be equal to 'X' when the pokemon is a LEVEL-UP one
|
||||
*/
|
||||
public level?: number | string
|
||||
|
||||
/**
|
||||
* Pokemon Stage
|
||||
*
|
||||
* - Basic https://www.tcgdex.net/database/xy/xy9/1
|
||||
* - BREAK https://www.tcgdex.net/database/xy/xy9/18
|
||||
* - LEVEL-UP https://www.tcgdex.net/database/dp/dp1/121
|
||||
* - MEGA https://www.tcgdex.net/database/xy/xy1/2
|
||||
* - RESTORED https://www.tcgdex.net/database/bw/bw5/53
|
||||
* - Stage1 https://www.tcgdex.net/database/xy/xy9/2
|
||||
* - Stage2 https://www.tcgdex.net/database/xy/xy9/3
|
||||
* - VMAX https://www.tcgdex.net/database/swsh/swsh1/50
|
||||
*/
|
||||
public stage?: string
|
||||
|
||||
/**
|
||||
* Card Suffix
|
||||
*
|
||||
* - EX https://www.tcgdex.net/database/ex/ex2/94
|
||||
* - GX https://www.tcgdex.net/database/sm/sm12/4
|
||||
* - V https://www.tcgdex.net/database/swsh/swsh1/1
|
||||
* - Legend https://www.tcgdex.net/database/hgss/hgss1/114
|
||||
* - Prime https://www.tcgdex.net/database/hgss/hgss2/85
|
||||
* - SP https://www.tcgdex.net/database/pl/pl1/7
|
||||
* - TAG TEAM-GX https://www.tcgdex.net/database/sm/sm12/226
|
||||
*/
|
||||
public suffix?: string
|
||||
|
||||
/**
|
||||
* Pokemon Held Item
|
||||
*
|
||||
* ex https://www.tcgdex.net/database/dp/dp2/75
|
||||
*/
|
||||
public item?: {
|
||||
name: string
|
||||
effect: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Pokemon Abilities
|
||||
*
|
||||
* multi abilities ex https://www.tcgdex.net/database/ex/ex15/10
|
||||
*/
|
||||
public abilities?: Array<{
|
||||
type: string
|
||||
name: string
|
||||
effect: string
|
||||
}>
|
||||
|
||||
/**
|
||||
* Pokemon Attacks
|
||||
*/
|
||||
public attacks?: Array<{
|
||||
cost?: Array<string>
|
||||
name: string
|
||||
effect?: string
|
||||
damage?: string | number
|
||||
}>
|
||||
|
||||
/**
|
||||
* Pokemon Weaknesses
|
||||
*/
|
||||
public weaknesses?: Array<{
|
||||
type: string
|
||||
value?: string
|
||||
}>
|
||||
|
||||
public resistances?: Array<{
|
||||
type: string
|
||||
value?: string
|
||||
}>
|
||||
|
||||
public retreat?: number
|
||||
|
||||
// Trainer/Energy
|
||||
public effect?: string
|
||||
|
||||
// Trainer Only
|
||||
public trainerType?: string
|
||||
|
||||
// Energy Only
|
||||
public energyType?: string
|
||||
|
||||
/**
|
||||
* Define the rotation mark on cards >= Sword & Shield
|
||||
*/
|
||||
public regulationMark?: string
|
||||
|
||||
/**
|
||||
* Card ability to be played in official tournaments
|
||||
*
|
||||
* Note: all cards are avaialable to play in unlimited tournaments
|
||||
*/
|
||||
public legal!: {
|
||||
|
||||
/**
|
||||
* Ability to play in standard tournaments
|
||||
*/
|
||||
standard: boolean
|
||||
|
||||
/**
|
||||
* Ability to play in expanded tournaments
|
||||
*/
|
||||
expanded: boolean
|
||||
}
|
||||
|
||||
public override async getCard(): Promise<Card> {
|
||||
return this
|
||||
}
|
||||
|
||||
public async getSet(): Promise<TCGdexSet> {
|
||||
return (await this.sdk.set.get(this.set.id))!
|
||||
}
|
||||
}
|
47
src/models/CardResume.ts
Normal file
47
src/models/CardResume.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import type { Extension, Quality } from '../interfaces'
|
||||
import type Card from './Card'
|
||||
import Model from './Model'
|
||||
|
||||
export default class CardResume extends Model {
|
||||
/**
|
||||
* Globally unique card ID based on the set ID and the cards ID within the set
|
||||
*/
|
||||
public id!: string
|
||||
|
||||
/**
|
||||
* Card image url without the extension and quality
|
||||
*
|
||||
* @see {@link getImageURL}
|
||||
*/
|
||||
public image?: string
|
||||
|
||||
/**
|
||||
* ID indexing this card within its set, usually just its number
|
||||
*/
|
||||
public localId!: string
|
||||
|
||||
/**
|
||||
* Card Name (Including the suffix if next to card name)
|
||||
*/
|
||||
public name!: string
|
||||
|
||||
/**
|
||||
* the the Card Image full URL
|
||||
*
|
||||
* @param {Quality} quality the quality you want your image to be in
|
||||
* @param {Extension} extension extension you want you image to be
|
||||
* @return the full card URL
|
||||
*/
|
||||
public getImageURL(quality: Quality = 'high', extension: Extension = 'png'): string {
|
||||
return `${this.image}/${quality}.${extension}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full Card
|
||||
*
|
||||
* @return the full card if available
|
||||
*/
|
||||
public async getCard(): Promise<Card> {
|
||||
return (await this.sdk.card.get(this.id))!
|
||||
}
|
||||
}
|
28
src/models/Model.ts
Normal file
28
src/models/Model.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import type TCGdex from '../tcgdex'
|
||||
|
||||
export default abstract class Model {
|
||||
|
||||
public constructor(
|
||||
protected readonly sdk: TCGdex
|
||||
) { }
|
||||
|
||||
/**
|
||||
* build a model depending on the data given
|
||||
* @param model the model to build
|
||||
* @param data the data to fill it with
|
||||
*/
|
||||
public static build<T extends Model>(model: T, data?: object): T {
|
||||
if (!data) {
|
||||
throw new Error('data is necessary.')
|
||||
}
|
||||
model.fill(data)
|
||||
return model
|
||||
}
|
||||
|
||||
protected fill(obj: object) {
|
||||
objectLoop(obj, (value, key) => {
|
||||
(this as object)[key] = value
|
||||
})
|
||||
}
|
||||
}
|
6
src/models/Other.d.ts
vendored
Normal file
6
src/models/Other.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
export interface Variants {
|
||||
normal?: boolean
|
||||
reverse?: boolean
|
||||
holo?: boolean
|
||||
firstEdition?: boolean
|
||||
}
|
21
src/models/Serie.ts
Normal file
21
src/models/Serie.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import Model from './Model'
|
||||
import SerieResume from './SerieResume'
|
||||
import SetResume from './SetResume'
|
||||
|
||||
export default class Serie extends SerieResume {
|
||||
public sets!: Array<SetResume>
|
||||
|
||||
protected fill(obj: object): void {
|
||||
objectLoop(obj, (value, key) => {
|
||||
switch (key) {
|
||||
case 'sets':
|
||||
this.sets = (value as Array<any>).map((it) => Model.build(new SetResume(this.sdk), it))
|
||||
break
|
||||
default:
|
||||
this[key] = value
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
24
src/models/SerieResume.ts
Normal file
24
src/models/SerieResume.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { Extension } from '../interfaces'
|
||||
import Model from './Model'
|
||||
import type Serie from './Serie'
|
||||
|
||||
export default class SerieResume extends Model {
|
||||
public id!: string
|
||||
public name!: string
|
||||
public logo?: string
|
||||
|
||||
/**
|
||||
* the the Card Image full URL
|
||||
*
|
||||
* @param {Quality} quality the quality you want your image to be in
|
||||
* @param {Extension} extension extension you want you image to be
|
||||
* @return the full card URL
|
||||
*/
|
||||
public getImageURL(extension: Extension = 'png'): string {
|
||||
return `${this.logo}.${extension}`
|
||||
}
|
||||
|
||||
public async getSerie(): Promise<Serie> {
|
||||
return (await this.sdk.serie.get(this.id))!
|
||||
}
|
||||
}
|
89
src/models/Set.ts
Normal file
89
src/models/Set.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import CardResume from './CardResume'
|
||||
import Model from './Model'
|
||||
import type { Variants } from './Other'
|
||||
import type SerieResume from './SerieResume'
|
||||
|
||||
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
||||
export default class Set extends Model {
|
||||
public id!: string
|
||||
public name!: string
|
||||
public logo?: string
|
||||
public symbol?: string
|
||||
public serie!: SerieResume
|
||||
public tcgOnline?: string
|
||||
public variants?: Variants
|
||||
|
||||
public releaseDate!: string
|
||||
|
||||
/**
|
||||
* Designate if the set is usable in tournaments
|
||||
*
|
||||
* Note: this is specific to the set and if a
|
||||
* card is banned from the set it will still be true
|
||||
*/
|
||||
public legal!: {
|
||||
|
||||
/**
|
||||
* Ability to play in standard tournaments
|
||||
*/
|
||||
standard: boolean
|
||||
|
||||
/**
|
||||
* Ability to play in expanded tournaments
|
||||
*/
|
||||
expanded: boolean
|
||||
}
|
||||
|
||||
public cardCount!: {
|
||||
|
||||
/**
|
||||
* total of number of cards
|
||||
*/
|
||||
total: number
|
||||
|
||||
/**
|
||||
* number of cards officialy (on the bottom of each cards)
|
||||
*/
|
||||
official: number
|
||||
|
||||
/**
|
||||
* number of cards having a normal version
|
||||
*/
|
||||
normal: number
|
||||
|
||||
/**
|
||||
* number of cards having an reverse version
|
||||
*/
|
||||
reverse: number
|
||||
|
||||
/**
|
||||
* number of cards having an holo version
|
||||
*/
|
||||
holo: number
|
||||
|
||||
/**
|
||||
* Number of possible cards
|
||||
*/
|
||||
firstEd?: number
|
||||
}
|
||||
|
||||
public cards!: Array<CardResume>
|
||||
|
||||
public async getSerie() {
|
||||
return this.sdk.serie.get(this.serie.id)
|
||||
}
|
||||
|
||||
protected fill(obj: object): void {
|
||||
objectLoop(obj, (value, key) => {
|
||||
switch (key) {
|
||||
case 'cards':
|
||||
this.cards = (value as Array<any>).map((it) => Model.build(new CardResume(this.sdk), it))
|
||||
break
|
||||
default:
|
||||
this[key] = value
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
25
src/models/SetResume.ts
Normal file
25
src/models/SetResume.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import Model from './Model'
|
||||
import type TCGdexSet from './Set'
|
||||
|
||||
export default class SetResume extends Model {
|
||||
public id!: string
|
||||
public name!: string
|
||||
public logo?: string
|
||||
public symbol?: string
|
||||
public cardCount!: {
|
||||
|
||||
/**
|
||||
* total of number of cards
|
||||
*/
|
||||
total: number
|
||||
|
||||
/**
|
||||
* number of cards officialy (on the bottom of each cards)
|
||||
*/
|
||||
official: number
|
||||
}
|
||||
|
||||
public async getSet(): Promise<TCGdexSet> {
|
||||
return (await this.sdk.set.get(this.id))!
|
||||
}
|
||||
}
|
21
src/models/StringEndpoint.ts
Normal file
21
src/models/StringEndpoint.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import CardResume from './CardResume'
|
||||
import Model from './Model'
|
||||
|
||||
export default class StringEndpoint extends Model {
|
||||
public name!: string
|
||||
public cards!: Array<CardResume>
|
||||
|
||||
protected fill(obj: object): void {
|
||||
objectLoop(obj, (value, key) => {
|
||||
switch (key) {
|
||||
case 'cards':
|
||||
this.cards = (value as Array<any>).map((it) => Model.build(new CardResume(this.sdk), it))
|
||||
break
|
||||
default:
|
||||
this[key] = value
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import unfetch from 'unfetch'
|
||||
import TCGdex from './tcgdex'
|
||||
|
||||
TCGdex.fetch = window.fetch ?? unfetch as any
|
||||
TCGdex.fetch = window.fetch
|
||||
|
||||
export default TCGdex
|
||||
|
245
src/tcgdex.ts
245
src/tcgdex.ts
@ -1,24 +1,144 @@
|
||||
import RequestWrapper from './Request'
|
||||
import { Serie, Set, Card, CardResume, SerieList, SetList, SupportedLanguages, StringEndpoint } from './interfaces'
|
||||
type Endpoint = 'cards' | 'categories' | 'dex-ids' | 'energy-types' | 'hp' | 'illustrators' | 'rarities' | 'regulation-marks' | 'retreats' | 'series' | 'sets' | 'stages' | 'suffixes' | 'trainer-types' | 'types' | 'variants'
|
||||
import type CacheInterface from './Psr/SimpleCache/CacheInterface'
|
||||
import LocalStorageCache from './Psr/SimpleCache/LocalStorageCache'
|
||||
import MemoryCache from './Psr/SimpleCache/MemoryCache'
|
||||
import Query from './Query'
|
||||
import Endpoint from './endpoints/Endpoint'
|
||||
import SimpleEndpoint from './endpoints/SimpleEndpoint'
|
||||
import type {
|
||||
Card,
|
||||
CardResume,
|
||||
Endpoints,
|
||||
Serie,
|
||||
SerieList,
|
||||
SetList,
|
||||
StringEndpoint,
|
||||
SupportedLanguages,
|
||||
Set as TCGdexSet
|
||||
} from './interfaces'
|
||||
import CardModel from './models/Card'
|
||||
import CardResumeModel from './models/CardResume'
|
||||
import SerieModel from './models/Serie'
|
||||
import SerieResume from './models/SerieResume'
|
||||
import SetModel from './models/Set'
|
||||
import SetResumeModel from './models/SetResume'
|
||||
import StringEndpointModel from './models/StringEndpoint'
|
||||
import { ENDPOINTS, detectContext } from './utils'
|
||||
import { version } from './version'
|
||||
|
||||
const ENDPOINTS: Array<Endpoint> = ['cards', 'categories', 'dex-ids', 'energy-types', 'hp', 'illustrators', 'rarities', 'regulation-marks', 'retreats', 'series', 'sets', 'stages', 'suffixes', 'trainer-types', 'types', 'variants']
|
||||
const BASE_URL = 'https://api.tcgdex.net/v2'
|
||||
export default class TCGdex {
|
||||
|
||||
public static fetch: typeof fetch
|
||||
/**
|
||||
* How the remote data is going to be fetched
|
||||
*/
|
||||
public static fetch: typeof fetch = fetch
|
||||
|
||||
/**
|
||||
* @deprecated to change the lang use `this.lang`
|
||||
* @deprecated to change the lang use {@link TCGdex.getLang} and {@link TCGdex.setLang}
|
||||
*/
|
||||
public static defaultLang: SupportedLanguages = 'en'
|
||||
|
||||
public constructor(public lang?: SupportedLanguages) {}
|
||||
/**
|
||||
* the previously hidden caching system used by TCGdex to not kill the API
|
||||
*/
|
||||
public cache: CacheInterface =
|
||||
detectContext() === 'browser' ? new LocalStorageCache('tcgdex-cache') : new MemoryCache()
|
||||
|
||||
/**
|
||||
* the default cache TTL, only subsequent requests will have their ttl changed
|
||||
*/
|
||||
public cacheTTL = 60 * 60
|
||||
|
||||
public readonly card = new Endpoint(this, CardModel, CardResumeModel, 'cards')
|
||||
public readonly set = new Endpoint(this, SetModel, SetResumeModel, 'sets')
|
||||
public readonly serie = new Endpoint(this, SerieModel, SerieResume, 'series')
|
||||
|
||||
public readonly type = new SimpleEndpoint(this, StringEndpointModel, 'types')
|
||||
public readonly retreat = new SimpleEndpoint(this, StringEndpointModel, 'retreats')
|
||||
public readonly rarity = new SimpleEndpoint(this, StringEndpointModel, 'rarities')
|
||||
public readonly illustrator = new SimpleEndpoint(this, StringEndpointModel, 'illustrators')
|
||||
public readonly hp = new SimpleEndpoint(this, StringEndpointModel, 'hp')
|
||||
public readonly categorie = new SimpleEndpoint(this, StringEndpointModel, 'categories')
|
||||
public readonly dexID = new SimpleEndpoint(this, StringEndpointModel, 'dex-ids')
|
||||
public readonly energyType = new SimpleEndpoint(this, StringEndpointModel, 'energy-types')
|
||||
public readonly regulationMark = new SimpleEndpoint(this, StringEndpointModel, 'regulation-marks')
|
||||
public readonly stage = new SimpleEndpoint(this, StringEndpointModel, 'stages')
|
||||
public readonly suffixe = new SimpleEndpoint(this, StringEndpointModel, 'suffixes')
|
||||
public readonly trainerType = new SimpleEndpoint(this, StringEndpointModel, 'trainer-types')
|
||||
public readonly variant = new SimpleEndpoint(this, StringEndpointModel, 'variants')
|
||||
|
||||
private lang: SupportedLanguages = 'en'
|
||||
private endpointURL = 'https://api.tcgdex.net/v2'
|
||||
|
||||
public constructor(lang: SupportedLanguages = 'en') {
|
||||
this.setLang(lang)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use the constructor parameter or {@link TCGdex.setLang} when in an instance
|
||||
*/
|
||||
public static setDefaultLang(lang: SupportedLanguages) {
|
||||
TCGdex.defaultLang = lang
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link TCGdex.setLang} when in an instance
|
||||
*/
|
||||
public static getDefaultLang(): SupportedLanguages {
|
||||
return TCGdex.defaultLang
|
||||
}
|
||||
|
||||
/**
|
||||
* the endpoint URL
|
||||
* ex: `https://api.tcgdex.net/v2`
|
||||
* @param endpoint the url
|
||||
*/
|
||||
public setEndpoint(endpoint: string) {
|
||||
this.endpointURL = endpoint
|
||||
}
|
||||
public getEndpoint(): string {
|
||||
return this.endpointURL
|
||||
}
|
||||
|
||||
/**
|
||||
* set the current cache methodology
|
||||
* @param cache the cache to use
|
||||
*/
|
||||
public setCache(cache: CacheInterface) {
|
||||
this.cache = cache
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current cache methodology
|
||||
* @param cache the cache to use
|
||||
*/
|
||||
public getCache(): CacheInterface {
|
||||
return this.cache
|
||||
}
|
||||
|
||||
/**
|
||||
* the endpoint URL
|
||||
* ex: `https://api.tcgdex.net/v2`
|
||||
* @param endpoint the url
|
||||
*/
|
||||
public setCacheTTL(seconds: number) {
|
||||
this.cacheTTL = seconds
|
||||
}
|
||||
/**
|
||||
* get the current useed cache ttl in seconds
|
||||
* @returns the cache ttl in seconds
|
||||
*/
|
||||
public getCacheTTL(): number {
|
||||
return this.cacheTTL
|
||||
}
|
||||
|
||||
public getLang(): SupportedLanguages {
|
||||
return this.lang ?? TCGdex.defaultLang ?? 'en'
|
||||
}
|
||||
|
||||
public setLang(lang: SupportedLanguages) {
|
||||
this.lang = lang
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to easily fetch a card using both it's global id and it's local ID
|
||||
* @param id the card global/local ID
|
||||
@ -47,7 +167,7 @@ export default class TCGdex {
|
||||
/**
|
||||
* @deprecated use `this.fetch('sets', set)`
|
||||
*/
|
||||
public async fetchSet(set: string): Promise<Set | undefined> {
|
||||
public async fetchSet(set: string): Promise<TCGdexSet | undefined> {
|
||||
return this.fetch('sets', set)
|
||||
}
|
||||
|
||||
@ -104,7 +224,7 @@ export default class TCGdex {
|
||||
* @param endpoint_0 'sets'
|
||||
* @param endpoint_1 {string} the set name or ID
|
||||
*/
|
||||
public async fetch(...endpoint: ['sets', string]): Promise<Set | undefined>
|
||||
public async fetch(...endpoint: ['sets', string]): Promise<TCGdexSet | undefined>
|
||||
|
||||
/**
|
||||
* Fetch every sets
|
||||
@ -148,7 +268,7 @@ export default class TCGdex {
|
||||
* @param endpoint_1 {string} (Optionnal) some details to go from the index file to the item file (mostly the ID/name)
|
||||
* @param endpoint_2 {string} (Optionnal) only for sets the card local ID to fetch the card through the set
|
||||
*/
|
||||
public async fetch(...endpoint: Array<Endpoint | string>): Promise<any | undefined> {
|
||||
public async fetch<T = object>(...endpoint: Array<Endpoints | string>): Promise<T | undefined> {
|
||||
if (endpoint.length === 0) {
|
||||
throw new Error('endpoint to fetch is empty!')
|
||||
}
|
||||
@ -157,29 +277,112 @@ export default class TCGdex {
|
||||
if (!ENDPOINTS.includes(baseEndpoint)) {
|
||||
throw new Error(`unknown endpoint to fetch! (${baseEndpoint})`)
|
||||
}
|
||||
return this.makeRequest(baseEndpoint, ...endpoint)
|
||||
return this.actualFetch<T>(this.getFullURL([baseEndpoint, ...endpoint]))
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to make the request and normalize the whole path
|
||||
* @param endpoint the endpoint to fetch
|
||||
* @param query the query
|
||||
*/
|
||||
private makeRequest<T = any>(...url: Array<string | number>) {
|
||||
public async fetchWithQuery<T = object>(
|
||||
endpoint: [Endpoints, ...Array<string>],
|
||||
query?: Array<{ key: string, value: string | number | boolean }>
|
||||
): Promise<T | undefined> {
|
||||
if (endpoint.length === 0) {
|
||||
throw new Error('endpoint to fetch is empty!')
|
||||
}
|
||||
const baseEndpoint = endpoint[0].toLowerCase() as Endpoints
|
||||
if (!ENDPOINTS.includes(baseEndpoint)) {
|
||||
throw new Error(`unknown endpoint to fetch! (${baseEndpoint})`)
|
||||
}
|
||||
return this.actualFetch<T>(this.getFullURL(endpoint, query))
|
||||
}
|
||||
|
||||
/**
|
||||
* format the final URL
|
||||
*/
|
||||
private getFullURL(
|
||||
url: Array<string | number>,
|
||||
searchParams?: Array<{ key: string, value: string | number | boolean }>
|
||||
): string {
|
||||
// Normalize path
|
||||
const path = url.map((subPath) => encodeURI(
|
||||
subPath
|
||||
let path = url.map(this.encode).join('/')
|
||||
|
||||
// handle the Search Params
|
||||
if (searchParams) {
|
||||
path += '?'
|
||||
for (let idx = 0; idx < searchParams.length; idx++) {
|
||||
const param = searchParams[idx]
|
||||
if (idx !== 0) {
|
||||
path += '&'
|
||||
}
|
||||
path += `${this.encode(param.key)}=${this.encode(param.value)}`
|
||||
}
|
||||
}
|
||||
|
||||
// return with the endpoint and all the shit
|
||||
return `${this.getEndpoint()}/${this.getLang()}/${path}`
|
||||
}
|
||||
|
||||
private async actualFetch<T = object>(path: string): Promise<T | undefined> {
|
||||
// get and return the cached value if available
|
||||
const cached = this.cache.get(path)
|
||||
if (cached) {
|
||||
return cached as T
|
||||
}
|
||||
|
||||
// the actual Fetch :D
|
||||
const resp = await TCGdex.fetch(path, {
|
||||
headers: {
|
||||
'user-agent': `@tcgdex/javascript-sdk/${version}`
|
||||
}
|
||||
})
|
||||
|
||||
// throw if a server-side error is occured
|
||||
if (resp.status >= 500) {
|
||||
try {
|
||||
const json = JSON.stringify(await resp.json())
|
||||
throw new Error(json)
|
||||
} catch {
|
||||
throw new Error('TCGdex Server responded with an invalid error :(')
|
||||
}
|
||||
}
|
||||
|
||||
// response is not valid :O
|
||||
if (resp.status !== 200) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// parse, put to cache and return
|
||||
const json = await resp.json()
|
||||
this.cache.set(path, json, this.cacheTTL)
|
||||
return json as T
|
||||
}
|
||||
|
||||
/**
|
||||
* encode a string to be used in an url
|
||||
* @param str the string to encode to URL
|
||||
* @returns the encoded string
|
||||
*/
|
||||
private encode(str: string | number | boolean): string {
|
||||
return encodeURI(
|
||||
str
|
||||
// Transform numbers to string
|
||||
.toString()
|
||||
// replace this special character with an escaped one
|
||||
.replace('?', '%3F')
|
||||
// normalize the string
|
||||
.normalize('NFC')
|
||||
// remove some special chars by nothing
|
||||
// remove some special chars
|
||||
// eslint-disable-next-line no-misleading-character-class
|
||||
.replace(/["'\u0300-\u036f]/gu, '')
|
||||
)).join('/')
|
||||
return RequestWrapper.fetch<T>(`${BASE_URL}/${this.getLang()}/${path}`)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export * from './interfaces'
|
||||
|
||||
export * from './models/Card'
|
||||
export {
|
||||
Query
|
||||
}
|
||||
|
||||
|
20
src/utils.ts
Normal file
20
src/utils.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { Endpoints } from './interfaces'
|
||||
|
||||
/**
|
||||
* detect the current running context ofthe program
|
||||
*/
|
||||
export function detectContext(): 'browser' | 'server' {
|
||||
try {
|
||||
const isBrowser = !!window
|
||||
return isBrowser ? 'browser' : 'server'
|
||||
} catch {
|
||||
return 'server'
|
||||
}
|
||||
}
|
||||
|
||||
export const ENDPOINTS: Array<Endpoints> = [
|
||||
'cards', 'categories', 'dex-ids', 'energy-types',
|
||||
'hp', 'illustrators', 'rarities', 'regulation-marks',
|
||||
'retreats', 'series', 'sets', 'stages', 'suffixes',
|
||||
'trainer-types', 'types', 'variants'
|
||||
] as const
|
@ -1,8 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@dzeio/config/tsconfig.base",
|
||||
"include": ["./src/tcgdex.node.ts"],
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"rootDir": "./src",
|
||||
}
|
||||
"outDir": "dist",
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"exclude": [
|
||||
"__tests__"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user