ani-client 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -236,7 +236,13 @@ Full API reference, configuration options, and guides (caching, pagination, hook
236
236
 
237
237
  ## Contributing
238
238
 
239
- See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and PR guidelines.
239
+ Contributions are welcome.
240
+
241
+ Before opening an issue or a pull request, please read:
242
+ - [CONTRIBUTING.md](CONTRIBUTING.md)
243
+ - [SECURITY.md](SECURITY.md)
244
+
245
+ This repository also includes GitHub issue templates and a pull request template to help keep reports and contributions consistent.
240
246
 
241
247
  ## License
242
248
 
@@ -0,0 +1 @@
1
+ export { f as RedisCache, g as RedisCacheOptions, h as RedisLikeClient } from '../redis-AFbnh0Xa.mjs';
@@ -0,0 +1 @@
1
+ export { f as RedisCache, g as RedisCacheOptions, h as RedisLikeClient } from '../redis-AFbnh0Xa.js';
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ // src/cache/redis.ts
4
+ var RedisCache = class {
5
+ client;
6
+ prefix;
7
+ ttl;
8
+ constructor(options) {
9
+ this.client = options.client;
10
+ this.prefix = options.prefix ?? "ani:";
11
+ this.ttl = options.ttl ?? 86400;
12
+ }
13
+ prefixedKey(key) {
14
+ return `${this.prefix}${key}`;
15
+ }
16
+ async get(key) {
17
+ const raw = await this.client.get(this.prefixedKey(key));
18
+ if (raw === null) return void 0;
19
+ try {
20
+ return JSON.parse(raw);
21
+ } catch {
22
+ return void 0;
23
+ }
24
+ }
25
+ async set(key, data) {
26
+ await this.client.set(this.prefixedKey(key), JSON.stringify(data), "EX", this.ttl);
27
+ }
28
+ async delete(key) {
29
+ const count = await this.client.del(this.prefixedKey(key));
30
+ return count > 0;
31
+ }
32
+ /**
33
+ * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
34
+ *
35
+ * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
36
+ * Provide a client with `scanIterator` support for production use.
37
+ * @internal
38
+ */
39
+ async collectKeys(pattern) {
40
+ if (this.client.scanIterator) {
41
+ const keys = [];
42
+ for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {
43
+ keys.push(key);
44
+ }
45
+ return keys;
46
+ }
47
+ return this.client.keys(pattern);
48
+ }
49
+ async clear() {
50
+ const keys = await this.collectKeys(`${this.prefix}*`);
51
+ if (keys.length > 0) {
52
+ await this.client.del(...keys);
53
+ }
54
+ }
55
+ /**
56
+ * Get the actual number of keys with this prefix in Redis.
57
+ */
58
+ get size() {
59
+ return this.getSize();
60
+ }
61
+ /** @internal */
62
+ async getSize() {
63
+ const keys = await this.collectKeys(`${this.prefix}*`);
64
+ return keys.length;
65
+ }
66
+ async keys() {
67
+ const raw = await this.collectKeys(`${this.prefix}*`);
68
+ return raw.map((k) => k.slice(this.prefix.length));
69
+ }
70
+ /**
71
+ * Remove all entries whose key matches the given pattern.
72
+ *
73
+ * - **String**: treated as a substring match (e.g. `"Media"` removes all keys containing `"Media"`).
74
+ * - **RegExp**: tested against each key directly.
75
+ *
76
+ * @param pattern — A string (substring match) or RegExp.
77
+ * @returns Number of entries removed.
78
+ */
79
+ async invalidate(pattern) {
80
+ if (typeof pattern === "string") {
81
+ const keys = await this.collectKeys(`${this.prefix}*${pattern}*`);
82
+ if (keys.length === 0) return 0;
83
+ return this.client.del(...keys);
84
+ }
85
+ const allKeys = await this.collectKeys(`${this.prefix}*`);
86
+ const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));
87
+ if (matching.length === 0) return 0;
88
+ return this.client.del(...matching);
89
+ }
90
+ };
91
+
92
+ exports.RedisCache = RedisCache;
93
+ //# sourceMappingURL=redis.js.map
94
+ //# sourceMappingURL=redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cache/redis.ts"],"names":[],"mappings":";;;AAsCO,IAAM,aAAN,MAAyC;AAAA,EAC7B,MAAA;AAAA,EACA,MAAA;AAAA,EACA,GAAA;AAAA,EAEjB,YAAY,OAAA,EAA4B;AACtC,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,MAAA,GAAS,QAAQ,MAAA,IAAU,MAAA;AAChC,IAAA,IAAA,CAAK,GAAA,GAAM,QAAQ,GAAA,IAAO,KAAA;AAAA,EAC5B;AAAA,EAEQ,YAAY,GAAA,EAAqB;AACvC,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,EAAG,GAAG,CAAA,CAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,IAAO,GAAA,EAAqC;AAChD,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,MAAA,CAAO,IAAI,IAAA,CAAK,WAAA,CAAY,GAAG,CAAC,CAAA;AACvD,IAAA,IAAI,GAAA,KAAQ,MAAM,OAAO,MAAA;AACzB,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,IACvB,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,GAAA,CAAO,GAAA,EAAa,IAAA,EAAwB;AAChD,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,IAAA,CAAK,WAAA,CAAY,GAAG,CAAA,EAAG,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,EAAG,IAAA,EAAM,KAAK,GAAG,CAAA;AAAA,EACnF;AAAA,EAEA,MAAM,OAAO,GAAA,EAA+B;AAC1C,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,MAAA,CAAO,IAAI,IAAA,CAAK,WAAA,CAAY,GAAG,CAAC,CAAA;AACzD,IAAA,OAAO,KAAA,GAAQ,CAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,YAAY,OAAA,EAAoC;AAC5D,IAAA,IAAI,IAAA,CAAK,OAAO,YAAA,EAAc;AAC5B,MAAA,MAAM,OAAiB,EAAC;AACxB,MAAA,WAAA,MAAiB,GAAA,IAAO,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,EAAE,OAAO,OAAA,EAAS,KAAA,EAAO,GAAA,EAAK,CAAA,EAAG;AAChF,QAAA,IAAA,CAAK,KAAK,GAAG,CAAA;AAAA,MACf;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAAA,EACjC;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,MAAM,OAAO,MAAM,IAAA,CAAK,YAAY,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,CAAA,CAAG,CAAA;AACrD,IAAA,IAAI,IAAA,CAAK,SAAS,CAAA,EAAG;AACnB,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAG,IAAI,CAAA;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,IAAA,GAAwB;AAC1B,IAAA,OAAO,KAAK,OAAA,EAAQ;AAAA,EACtB;AAAA;AAAA,EAGA,MAAc,OAAA,GAA2B;AACvC,IAAA,MAAM,OAAO,MAAM,IAAA,CAAK,YAAY,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,CAAA,CAAG,CAAA;AACrD,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA,EAEA,MAAM,IAAA,GAA0B;AAC9B,IAAA,MAAM,MAAM,MAAM,IAAA,CAAK,YAAY,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,CAAA,CAAG,CAAA;AACpD,IAAA,OAAO,GAAA,CAAI,IAAI,CAAC,CAAA,KAAM,EAAE,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,MAAM,CAAC,CAAA;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,WAAW,OAAA,EAA2C;AAC1D,IAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,WAAA,CAAY,GAAG,IAAA,CAAK,MAAM,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,CAAG,CAAA;AAChE,MAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,CAAA;AAC9B,MAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAG,IAAI,CAAA;AAAA,IAChC;AAEA,IAAA,MAAM,UAAU,MAAM,IAAA,CAAK,YAAY,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,CAAA,CAAG,CAAA;AACxD,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,OAAA,CAAQ,IAAA,CAAK,CAAA,CAAE,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,MAAM,CAAC,CAAC,CAAA;AAChF,IAAA,IAAI,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG,OAAO,CAAA;AAClC,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAG,QAAQ,CAAA;AAAA,EACpC;AACF","file":"redis.js","sourcesContent":["import type { CacheAdapter } from \"../types\";\n\n/**\n * Minimal interface representing a Redis client.\n * Compatible with both `ioredis` and `redis` (node-redis v4+).\n */\nexport interface RedisLikeClient {\n get(key: string): Promise<string | null>;\n set(key: string, value: string, ...args: unknown[]): Promise<unknown>;\n del(...keys: (string | string[])[]): Promise<number>;\n keys(pattern: string): Promise<string[]>;\n /** Optional SCAN-based iteration — used when available to avoid blocking the server. */\n scanIterator?(options: { MATCH: string; COUNT?: number }): AsyncIterable<string>;\n}\n\nexport interface RedisCacheOptions {\n /** A Redis client instance (ioredis or node-redis). */\n client: RedisLikeClient;\n /** Key prefix to namespace ani-client entries (default: `\"ani:\"`) */\n prefix?: string;\n /** TTL in seconds (default: 86 400 = 24 h) */\n ttl?: number;\n}\n\n/**\n * Redis-backed cache adapter for AniListClient.\n *\n * @example\n * ```ts\n * import Redis from \"ioredis\";\n * import { AniListClient, RedisCache } from \"ani-client\";\n *\n * const redis = new Redis();\n * const client = new AniListClient({\n * cacheAdapter: new RedisCache({ client: redis }),\n * });\n * ```\n */\nexport class RedisCache implements CacheAdapter {\n private readonly client: RedisLikeClient;\n private readonly prefix: string;\n private readonly ttl: number;\n\n constructor(options: RedisCacheOptions) {\n this.client = options.client;\n this.prefix = options.prefix ?? \"ani:\";\n this.ttl = options.ttl ?? 86_400;\n }\n\n private prefixedKey(key: string): string {\n return `${this.prefix}${key}`;\n }\n\n async get<T>(key: string): Promise<T | undefined> {\n const raw = await this.client.get(this.prefixedKey(key));\n if (raw === null) return undefined;\n try {\n return JSON.parse(raw) as T;\n } catch {\n return undefined;\n }\n }\n\n async set<T>(key: string, data: T): Promise<void> {\n await this.client.set(this.prefixedKey(key), JSON.stringify(data), \"EX\", this.ttl);\n }\n\n async delete(key: string): Promise<boolean> {\n const count = await this.client.del(this.prefixedKey(key));\n return count > 0;\n }\n\n /**\n * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.\n *\n * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.\n * Provide a client with `scanIterator` support for production use.\n * @internal\n */\n private async collectKeys(pattern: string): Promise<string[]> {\n if (this.client.scanIterator) {\n const keys: string[] = [];\n for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {\n keys.push(key);\n }\n return keys;\n }\n return this.client.keys(pattern);\n }\n\n async clear(): Promise<void> {\n const keys = await this.collectKeys(`${this.prefix}*`);\n if (keys.length > 0) {\n await this.client.del(...keys);\n }\n }\n\n /**\n * Get the actual number of keys with this prefix in Redis.\n */\n get size(): Promise<number> {\n return this.getSize();\n }\n\n /** @internal */\n private async getSize(): Promise<number> {\n const keys = await this.collectKeys(`${this.prefix}*`);\n return keys.length;\n }\n\n async keys(): Promise<string[]> {\n const raw = await this.collectKeys(`${this.prefix}*`);\n return raw.map((k) => k.slice(this.prefix.length));\n }\n\n /**\n * Remove all entries whose key matches the given pattern.\n *\n * - **String**: treated as a substring match (e.g. `\"Media\"` removes all keys containing `\"Media\"`).\n * - **RegExp**: tested against each key directly.\n *\n * @param pattern — A string (substring match) or RegExp.\n * @returns Number of entries removed.\n */\n async invalidate(pattern: string | RegExp): Promise<number> {\n if (typeof pattern === \"string\") {\n const keys = await this.collectKeys(`${this.prefix}*${pattern}*`);\n if (keys.length === 0) return 0;\n return this.client.del(...keys);\n }\n\n const allKeys = await this.collectKeys(`${this.prefix}*`);\n const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));\n if (matching.length === 0) return 0;\n return this.client.del(...matching);\n }\n}\n"]}
@@ -0,0 +1,92 @@
1
+ // src/cache/redis.ts
2
+ var RedisCache = class {
3
+ client;
4
+ prefix;
5
+ ttl;
6
+ constructor(options) {
7
+ this.client = options.client;
8
+ this.prefix = options.prefix ?? "ani:";
9
+ this.ttl = options.ttl ?? 86400;
10
+ }
11
+ prefixedKey(key) {
12
+ return `${this.prefix}${key}`;
13
+ }
14
+ async get(key) {
15
+ const raw = await this.client.get(this.prefixedKey(key));
16
+ if (raw === null) return void 0;
17
+ try {
18
+ return JSON.parse(raw);
19
+ } catch {
20
+ return void 0;
21
+ }
22
+ }
23
+ async set(key, data) {
24
+ await this.client.set(this.prefixedKey(key), JSON.stringify(data), "EX", this.ttl);
25
+ }
26
+ async delete(key) {
27
+ const count = await this.client.del(this.prefixedKey(key));
28
+ return count > 0;
29
+ }
30
+ /**
31
+ * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
32
+ *
33
+ * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
34
+ * Provide a client with `scanIterator` support for production use.
35
+ * @internal
36
+ */
37
+ async collectKeys(pattern) {
38
+ if (this.client.scanIterator) {
39
+ const keys = [];
40
+ for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {
41
+ keys.push(key);
42
+ }
43
+ return keys;
44
+ }
45
+ return this.client.keys(pattern);
46
+ }
47
+ async clear() {
48
+ const keys = await this.collectKeys(`${this.prefix}*`);
49
+ if (keys.length > 0) {
50
+ await this.client.del(...keys);
51
+ }
52
+ }
53
+ /**
54
+ * Get the actual number of keys with this prefix in Redis.
55
+ */
56
+ get size() {
57
+ return this.getSize();
58
+ }
59
+ /** @internal */
60
+ async getSize() {
61
+ const keys = await this.collectKeys(`${this.prefix}*`);
62
+ return keys.length;
63
+ }
64
+ async keys() {
65
+ const raw = await this.collectKeys(`${this.prefix}*`);
66
+ return raw.map((k) => k.slice(this.prefix.length));
67
+ }
68
+ /**
69
+ * Remove all entries whose key matches the given pattern.
70
+ *
71
+ * - **String**: treated as a substring match (e.g. `"Media"` removes all keys containing `"Media"`).
72
+ * - **RegExp**: tested against each key directly.
73
+ *
74
+ * @param pattern — A string (substring match) or RegExp.
75
+ * @returns Number of entries removed.
76
+ */
77
+ async invalidate(pattern) {
78
+ if (typeof pattern === "string") {
79
+ const keys = await this.collectKeys(`${this.prefix}*${pattern}*`);
80
+ if (keys.length === 0) return 0;
81
+ return this.client.del(...keys);
82
+ }
83
+ const allKeys = await this.collectKeys(`${this.prefix}*`);
84
+ const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));
85
+ if (matching.length === 0) return 0;
86
+ return this.client.del(...matching);
87
+ }
88
+ };
89
+
90
+ export { RedisCache };
91
+ //# sourceMappingURL=redis.mjs.map
92
+ //# sourceMappingURL=redis.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cache/redis.ts"],"names":[],"mappings":"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","file":"redis.mjs","sourcesContent":["import type { CacheAdapter } from \"../types\";\n\n/**\n * Minimal interface representing a Redis client.\n * Compatible with both `ioredis` and `redis` (node-redis v4+).\n */\nexport interface RedisLikeClient {\n get(key: string): Promise<string | null>;\n set(key: string, value: string, ...args: unknown[]): Promise<unknown>;\n del(...keys: (string | string[])[]): Promise<number>;\n keys(pattern: string): Promise<string[]>;\n /** Optional SCAN-based iteration — used when available to avoid blocking the server. */\n scanIterator?(options: { MATCH: string; COUNT?: number }): AsyncIterable<string>;\n}\n\nexport interface RedisCacheOptions {\n /** A Redis client instance (ioredis or node-redis). */\n client: RedisLikeClient;\n /** Key prefix to namespace ani-client entries (default: `\"ani:\"`) */\n prefix?: string;\n /** TTL in seconds (default: 86 400 = 24 h) */\n ttl?: number;\n}\n\n/**\n * Redis-backed cache adapter for AniListClient.\n *\n * @example\n * ```ts\n * import Redis from \"ioredis\";\n * import { AniListClient, RedisCache } from \"ani-client\";\n *\n * const redis = new Redis();\n * const client = new AniListClient({\n * cacheAdapter: new RedisCache({ client: redis }),\n * });\n * ```\n */\nexport class RedisCache implements CacheAdapter {\n private readonly client: RedisLikeClient;\n private readonly prefix: string;\n private readonly ttl: number;\n\n constructor(options: RedisCacheOptions) {\n this.client = options.client;\n this.prefix = options.prefix ?? \"ani:\";\n this.ttl = options.ttl ?? 86_400;\n }\n\n private prefixedKey(key: string): string {\n return `${this.prefix}${key}`;\n }\n\n async get<T>(key: string): Promise<T | undefined> {\n const raw = await this.client.get(this.prefixedKey(key));\n if (raw === null) return undefined;\n try {\n return JSON.parse(raw) as T;\n } catch {\n return undefined;\n }\n }\n\n async set<T>(key: string, data: T): Promise<void> {\n await this.client.set(this.prefixedKey(key), JSON.stringify(data), \"EX\", this.ttl);\n }\n\n async delete(key: string): Promise<boolean> {\n const count = await this.client.del(this.prefixedKey(key));\n return count > 0;\n }\n\n /**\n * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.\n *\n * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.\n * Provide a client with `scanIterator` support for production use.\n * @internal\n */\n private async collectKeys(pattern: string): Promise<string[]> {\n if (this.client.scanIterator) {\n const keys: string[] = [];\n for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {\n keys.push(key);\n }\n return keys;\n }\n return this.client.keys(pattern);\n }\n\n async clear(): Promise<void> {\n const keys = await this.collectKeys(`${this.prefix}*`);\n if (keys.length > 0) {\n await this.client.del(...keys);\n }\n }\n\n /**\n * Get the actual number of keys with this prefix in Redis.\n */\n get size(): Promise<number> {\n return this.getSize();\n }\n\n /** @internal */\n private async getSize(): Promise<number> {\n const keys = await this.collectKeys(`${this.prefix}*`);\n return keys.length;\n }\n\n async keys(): Promise<string[]> {\n const raw = await this.collectKeys(`${this.prefix}*`);\n return raw.map((k) => k.slice(this.prefix.length));\n }\n\n /**\n * Remove all entries whose key matches the given pattern.\n *\n * - **String**: treated as a substring match (e.g. `\"Media\"` removes all keys containing `\"Media\"`).\n * - **RegExp**: tested against each key directly.\n *\n * @param pattern — A string (substring match) or RegExp.\n * @returns Number of entries removed.\n */\n async invalidate(pattern: string | RegExp): Promise<number> {\n if (typeof pattern === \"string\") {\n const keys = await this.collectKeys(`${this.prefix}*${pattern}*`);\n if (keys.length === 0) return 0;\n return this.client.del(...keys);\n }\n\n const allKeys = await this.collectKeys(`${this.prefix}*`);\n const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));\n if (matching.length === 0) return 0;\n return this.client.del(...matching);\n }\n}\n"]}
package/dist/index.d.mts CHANGED
@@ -1,159 +1,5 @@
1
- interface PageInfo {
2
- total: number | null;
3
- perPage: number;
4
- currentPage: number;
5
- lastPage: number;
6
- hasNextPage: boolean;
7
- }
8
- interface PagedResult<T> {
9
- pageInfo: PageInfo;
10
- results: T[];
11
- }
12
- interface FuzzyDate {
13
- year: number | null;
14
- month: number | null;
15
- day: number | null;
16
- }
17
- interface ExternalLink {
18
- id: number;
19
- url: string | null;
20
- site: string;
21
- type: string | null;
22
- icon: string | null;
23
- color: string | null;
24
- }
25
- /**
26
- * Interface that all cache adapters must implement.
27
- * Methods may return sync values or Promises — the client awaits all calls.
28
- */
29
- interface CacheAdapter {
30
- /** Retrieve a cached value, or `undefined` if missing / expired. */
31
- get<T>(key: string): T | undefined | Promise<T | undefined>;
32
- /** Store a value in the cache. */
33
- set<T>(key: string, data: T): void | Promise<void>;
34
- /** Remove a specific entry. Returns `true` if the key existed. */
35
- delete(key: string): boolean | Promise<boolean>;
36
- /** Clear the entire cache. */
37
- clear(): void | Promise<void>;
38
- /** Number of entries currently stored. */
39
- readonly size: number | Promise<number>;
40
- /** Return all cache keys. */
41
- keys(): string[] | Promise<string[]>;
42
- /** Bulk-remove entries matching a pattern. Optional — the client provides a fallback. */
43
- invalidate?(pattern: string | RegExp): number | Promise<number>;
44
- }
45
- /** Cache configuration options. */
46
- interface CacheOptions {
47
- /** Time-to-live in milliseconds (default: 86 400 000 = 24h) */
48
- ttl?: number;
49
- /** Maximum number of cached entries (default: 500, 0 = unlimited) */
50
- maxSize?: number;
51
- /** Set to false to disable caching entirely */
52
- enabled?: boolean;
53
- /**
54
- * Stale-while-revalidate grace period in milliseconds (default: 0 = disabled).
55
- * When set, expired entries are still returned within the grace window,
56
- * allowing the caller to refresh in the background.
57
- */
58
- staleWhileRevalidateMs?: number;
59
- }
60
- /** Rate limiter configuration options. */
61
- interface RateLimitOptions {
62
- /** Max requests per window (default: 25) */
63
- maxRequests?: number;
64
- /** Window size in ms (default: 60 000) */
65
- windowMs?: number;
66
- /** Max retries on 429 (default: 3) */
67
- maxRetries?: number;
68
- /** Retry delay in ms when Retry-After header is absent (default: 2000) */
69
- retryDelayMs?: number;
70
- /** Set to false to disable rate limiting entirely */
71
- enabled?: boolean;
72
- /** Timeout per request in ms (default: 30 000). 0 = no timeout. */
73
- timeoutMs?: number;
74
- /** Retry on network errors like ECONNRESET / ETIMEDOUT (default: true) */
75
- retryOnNetworkError?: boolean;
76
- /**
77
- * Custom retry delay strategy. Receives the attempt number (0-based) and the base delay,
78
- * and should return the delay in ms before retrying.
79
- * When omitted, the default exponential backoff with jitter is used.
80
- *
81
- * @example
82
- * // Linear backoff: 1s, 2s, 3s, ...
83
- * retryStrategy: (attempt) => (attempt + 1) * 1000
84
- */
85
- retryStrategy?: (attempt: number, baseDelayMs: number) => number;
86
- }
87
- /** Event hooks for logging, debugging, and monitoring. */
88
- interface AniListHooks {
89
- /** Called before every API request. */
90
- onRequest?: (query: string, variables: Record<string, unknown>) => void;
91
- /** Called when a response is served from cache. */
92
- onCacheHit?: (key: string) => void;
93
- /** Called when the rate limiter enforces a wait (429 received). */
94
- onRateLimit?: (retryAfterMs: number) => void;
95
- /** Called when a request is retried (429 or network error). */
96
- onRetry?: (attempt: number, reason: string, delayMs: number) => void;
97
- /** Called when a request completes. */
98
- onResponse?: (query: string, durationMs: number, fromCache: boolean, rateLimitInfo?: RateLimitInfo) => void;
99
- /** Called when a request fails with an error. */
100
- onError?: (error: Error, query: string, variables: Record<string, unknown>) => void;
101
- }
102
- /** Rate limit information parsed from AniList API response headers. */
103
- interface RateLimitInfo {
104
- /** Maximum number of requests allowed per window. */
105
- limit: number;
106
- /** Remaining requests in the current window. */
107
- remaining: number;
108
- /** UNIX timestamp (seconds) when the rate limit window resets. */
109
- reset: number;
110
- }
111
- /** Metadata about the last request, useful for debugging and monitoring. */
112
- interface ResponseMeta {
113
- /** Duration of the request in milliseconds. */
114
- durationMs: number;
115
- /** Whether the response was served from cache. */
116
- fromCache: boolean;
117
- /** Rate limit information from the API response headers (not present for cached responses). */
118
- rateLimitInfo?: RateLimitInfo;
119
- }
120
- /**
121
- * Minimal logger interface for structured log output.
122
- * Compatible with `console`, `pino`, `winston`, etc.
123
- */
124
- interface Logger {
125
- debug(message: string, ...args: unknown[]): void;
126
- info(message: string, ...args: unknown[]): void;
127
- warn(message: string, ...args: unknown[]): void;
128
- error(message: string, ...args: unknown[]): void;
129
- }
130
- interface AniListClientOptions {
131
- /** Optional AniList OAuth token for authenticated requests */
132
- token?: string;
133
- /** Custom API endpoint (defaults to https://graphql.anilist.co) */
134
- apiUrl?: string;
135
- /** Cache configuration (enabled by default, 24h TTL) */
136
- cache?: CacheOptions;
137
- /** Custom cache adapter (e.g. RedisCache). Takes precedence over `cache`. */
138
- cacheAdapter?: CacheAdapter;
139
- /** Rate limiter configuration (enabled by default, 85 req/min) */
140
- rateLimit?: RateLimitOptions;
141
- /** Event hooks for logging, debugging, and monitoring */
142
- hooks?: AniListHooks;
143
- /** Optional AbortSignal to cancel all requests made by this client */
144
- signal?: AbortSignal;
145
- /**
146
- * Optional logger for structured log output.
147
- * Accepts any object with `debug`, `info`, `warn`, `error` methods.
148
- * Compatible with `console`, `pino`, `winston`, etc.
149
- *
150
- * @example
151
- * ```ts
152
- * const client = new AniListClient({ logger: console });
153
- * ```
154
- */
155
- logger?: Logger;
156
- }
1
+ import { F as FuzzyDate, P as PageInfo, E as ExternalLink, C as CacheAdapter, a as CacheOptions, b as PagedResult, A as AniListClientOptions, R as RateLimitInfo, c as ResponseMeta, d as RateLimitOptions } from './redis-AFbnh0Xa.mjs';
2
+ export { e as AniListHooks, L as Logger, f as RedisCache, g as RedisCacheOptions, h as RedisLikeClient } from './redis-AFbnh0Xa.mjs';
157
3
 
158
4
  declare enum MediaListStatus {
159
5
  CURRENT = "CURRENT",
@@ -1070,6 +916,52 @@ interface SearchThreadOptions {
1070
916
  perPage?: number;
1071
917
  }
1072
918
 
919
+ /**
920
+ * Normalized Cache Adapter for AniListClient.
921
+ *
922
+ * This cache intercepts GraphQL responses, extracts objects with `__typename` and `id`,
923
+ * and stores them flat in an entity store.
924
+ * This ensures data consistency across all queries (e.g., `getMedia(1)` and `searchMedia`
925
+ * share the exact same `Media` object).
926
+ */
927
+ declare class NormalizedCache implements CacheAdapter {
928
+ private readonly ttl;
929
+ private readonly maxSize;
930
+ private readonly enabled;
931
+ private readonly swrMs;
932
+ private readonly queryStore;
933
+ private readonly entityStore;
934
+ private _hits;
935
+ private _misses;
936
+ private _stales;
937
+ constructor(options?: CacheOptions);
938
+ static key(query: string, variables: Record<string, unknown>): string;
939
+ /** Normalizes a GraphQL response, extracting entities and returning a tree of references. */
940
+ private normalize;
941
+ /** Reconstructs a GraphQL response from references. */
942
+ private denormalize;
943
+ getWithMeta<T>(key: string): {
944
+ data: T;
945
+ stale: boolean;
946
+ } | undefined;
947
+ get<T>(key: string): T | undefined;
948
+ set<T>(key: string, data: T): void;
949
+ delete(key: string): boolean;
950
+ clear(): void;
951
+ get size(): number;
952
+ keys(): string[];
953
+ invalidate(pattern: string | RegExp): number;
954
+ get stats(): CacheStats & {
955
+ entitiesCount: number;
956
+ };
957
+ resetStats(): void;
958
+ /**
959
+ * Garbage-collect orphaned entities that are no longer referenced by any query.
960
+ * Called automatically on LRU eviction to prevent unbounded entity store growth.
961
+ */
962
+ gc(): number;
963
+ }
964
+
1073
965
  /** Cache performance statistics. */
1074
966
  interface CacheStats {
1075
967
  /** Total cache hits. */
@@ -1094,10 +986,14 @@ declare class MemoryCache implements CacheAdapter {
1094
986
  /** Build a deterministic cache key from a query + variables pair. */
1095
987
  static key(query: string, variables: Record<string, unknown>): string;
1096
988
  /**
1097
- * Retrieve a cached value, or `undefined` if missing / expired.
989
+ * Retrieve a cached value and its stale status.
1098
990
  * With stale-while-revalidate enabled, returns stale data within the grace window
1099
991
  * and flags it so the caller can refresh in the background.
1100
992
  */
993
+ getWithMeta<T>(key: string): {
994
+ data: T;
995
+ stale: boolean;
996
+ } | undefined;
1101
997
  get<T>(key: string): T | undefined;
1102
998
  /** Store a value in the cache. */
1103
999
  set<T>(key: string, data: T): void;
@@ -1136,77 +1032,15 @@ declare class MemoryCache implements CacheAdapter {
1136
1032
  }
1137
1033
 
1138
1034
  /**
1139
- * Minimal interface representing a Redis client.
1140
- * Compatible with both `ioredis` and `redis` (node-redis v4+).
1141
- */
1142
- interface RedisLikeClient {
1143
- get(key: string): Promise<string | null>;
1144
- set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
1145
- del(...keys: (string | string[])[]): Promise<number>;
1146
- keys(pattern: string): Promise<string[]>;
1147
- /** Optional SCAN-based iteration — used when available to avoid blocking the server. */
1148
- scanIterator?(options: {
1149
- MATCH: string;
1150
- COUNT?: number;
1151
- }): AsyncIterable<string>;
1152
- }
1153
- interface RedisCacheOptions {
1154
- /** A Redis client instance (ioredis or node-redis). */
1155
- client: RedisLikeClient;
1156
- /** Key prefix to namespace ani-client entries (default: `"ani:"`) */
1157
- prefix?: string;
1158
- /** TTL in seconds (default: 86 400 = 24 h) */
1159
- ttl?: number;
1160
- }
1161
- /**
1162
- * Redis-backed cache adapter for AniListClient.
1035
+ * Base interface for the client context shared by all domain method modules.
1036
+ * Exposes only the internal request methods that domain functions need.
1163
1037
  *
1164
- * @example
1165
- * ```ts
1166
- * import Redis from "ioredis";
1167
- * import { AniListClient, RedisCache } from "ani-client";
1168
- *
1169
- * const redis = new Redis();
1170
- * const client = new AniListClient({
1171
- * cacheAdapter: new RedisCache({ client: redis }),
1172
- * });
1173
- * ```
1038
+ * @internal
1174
1039
  */
1175
- declare class RedisCache implements CacheAdapter {
1176
- private readonly client;
1177
- private readonly prefix;
1178
- private readonly ttl;
1179
- constructor(options: RedisCacheOptions);
1180
- private prefixedKey;
1181
- get<T>(key: string): Promise<T | undefined>;
1182
- set<T>(key: string, data: T): Promise<void>;
1183
- delete(key: string): Promise<boolean>;
1184
- /**
1185
- * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
1186
- *
1187
- * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
1188
- * Provide a client with `scanIterator` support for production use.
1189
- * @internal
1190
- */
1191
- private collectKeys;
1192
- clear(): Promise<void>;
1193
- /**
1194
- * Get the actual number of keys with this prefix in Redis.
1195
- */
1196
- get size(): Promise<number>;
1197
- /** @internal */
1198
- private getSize;
1199
- keys(): Promise<string[]>;
1200
- /**
1201
- * Remove all entries whose key matches the given pattern.
1202
- *
1203
- * - **String**: treated as a substring match (e.g. `"Media"` removes all keys containing `"Media"`).
1204
- * - **RegExp**: tested against each key directly.
1205
- *
1206
- * @param pattern — A string (substring match) or RegExp.
1207
- * @returns Number of entries removed.
1208
- */
1209
- invalidate(pattern: string | RegExp): Promise<number>;
1040
+ interface ClientBase {
1041
+ request<T>(query: string, variables?: Record<string, unknown>): Promise<T>;
1042
+ pagedRequest<T>(query: string, variables: Record<string, unknown>, field: string): Promise<PagedResult<T>>;
1043
+ paginate<T>(fetchPage: (page: number) => Promise<PagedResult<T>>, maxPages?: number): AsyncGenerator<T, void, undefined>;
1210
1044
  }
1211
1045
 
1212
1046
  /**
@@ -1227,7 +1061,7 @@ declare class RedisCache implements CacheAdapter {
1227
1061
  * });
1228
1062
  * ```
1229
1063
  */
1230
- declare class AniListClient {
1064
+ declare class AniListClient implements ClientBase {
1231
1065
  private readonly apiUrl;
1232
1066
  private readonly headers;
1233
1067
  private readonly cacheAdapter;
@@ -1299,7 +1133,7 @@ declare class AniListClient {
1299
1133
  */
1300
1134
  getMediaByMalId(malId: number, type?: MediaType): Promise<Media>;
1301
1135
  /** Get the detailed schedule for the current week, sorted by day. */
1302
- getWeeklySchedule(date?: Date): Promise<WeeklySchedule>;
1136
+ getWeeklySchedule(date?: Date, idNotIn?: number[]): Promise<WeeklySchedule>;
1303
1137
  /** Get upcoming (not yet released) media. */
1304
1138
  getPlanning(options?: GetPlanningOptions): Promise<PagedResult<Media>>;
1305
1139
  /** Get recommendations for a specific media. */
@@ -1473,4 +1307,4 @@ declare class RateLimiter {
1473
1307
 
1474
1308
  declare function parseAniListMarkdown(text: string): string;
1475
1309
 
1476
- export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type CacheStats, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, type ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, type FuzzyDate, type GetAiringOptions, type GetMediaCharactersOptions, type GetMediaStaffOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Logger, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, type PageInfo, type PagedResult, type RateLimitInfo, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ResponseMeta, type Review, ReviewSort, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchReviewOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, type StudioIncludeOptions, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, type UserFavoritesOptions, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };
1310
+ export { type AiringSchedule, AiringSort, AniListClient, AniListClientOptions, AniListError, CacheAdapter, CacheOptions, type CacheStats, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, FuzzyDate, type GetAiringOptions, type GetMediaCharactersOptions, type GetMediaStaffOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, NormalizedCache, PageInfo, PagedResult, RateLimitInfo, RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, ResponseMeta, type Review, ReviewSort, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchReviewOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, type StudioIncludeOptions, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, type UserFavoritesOptions, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };