@teamvelix/cache-redis 5.3.3 → 5.3.6
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/LICENSE +21 -0
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-2030 Velix Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
CHANGED
|
@@ -50,9 +50,15 @@ var RedisCacheAdapter = class {
|
|
|
50
50
|
await this.redis.set(k, serialized);
|
|
51
51
|
}
|
|
52
52
|
if (options.tags?.length) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const pipeline = this.redis.pipeline();
|
|
54
|
+
for (const tag of options.tags) {
|
|
55
|
+
const tKey = this.tagKey(tag);
|
|
56
|
+
pipeline.sadd(tKey, k);
|
|
57
|
+
if (ttlMs) {
|
|
58
|
+
pipeline.pexpire(tKey, ttlMs * 2);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
await pipeline.exec();
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
64
|
async delete(key) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/redis.adapter.ts"],"names":[],"mappings":";;;AA6BA,IAAM,iBAAA,GAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AA6BnB,IAAM,oBAAN,MAAiD;AAAA,EAC9C,KAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA;AAAA,EAER,WAAA,CAAY,OAAA,GAAoC,EAAC,EAAG;AAClD,IAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,MAAA,IAAU,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,wBAAwB,CAAA;AAChF,IAAA,IAAA,CAAK,MAAA,GAAS,QAAQ,SAAA,IAAa,QAAA;AACnC,IAAA,IAAA,CAAK,aAAa,OAAA,CAAQ,UAAA;AAAA,EAC5B;AAAA;AAAA,EAIQ,IAAI,CAAA,EAAmB;AAC7B,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,EAAG,CAAC,CAAA,CAAA;AAAA,EAC3B;AAAA,EAEQ,OAAO,GAAA,EAAqB;AAClC,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,IAAA,EAAO,GAAG,CAAA,CAAA;AAAA,EACjC;AAAA;AAAA,EAIA,MAAM,IAAO,GAAA,EAAgC;AAC3C,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,KAAA,CAAM,IAAI,IAAA,CAAK,GAAA,CAAI,GAAG,CAAC,CAAA;AAC9C,IAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC5B,MAAA,OAAO,KAAA,CAAM,KAAA;AAAA,IACf,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,GAAA,CAAO,GAAA,EAAa,KAAA,EAAU,OAAA,GAA2B,EAAC,EAAkB;AAChF,IAAA,MAAM,KAAA,GAAwB;AAAA,MAC5B,KAAA;AAAA,MACA,IAAA,EAAM,OAAA,CAAQ,IAAA,IAAQ;AAAC,KACzB;AACA,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AACvC,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA;AACtB,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,IAAO,IAAA,CAAK,UAAA;AAElC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAM,KAAK,KAAA,CAAM,GAAA,CAAI,CAAA,EAAG,UAAA,EAAY,MAAM,KAAK,CAAA;AAAA,IACjD,CAAA,MAAO;AACL,MAAA,MAAM,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,CAAA,EAAG,UAAU,CAAA;AAAA,IACpC;AAGA,IAAA,IAAI,OAAA,CAAQ,MAAM,MAAA,EAAQ;AACxB,MAAA,MAAM,
|
|
1
|
+
{"version":3,"sources":["../src/redis.adapter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import Redis from 'ioredis';\nimport type { RedisCacheAdapterOptions, StoredEntry } from './types.js';\n\n// ============================================================================\n// ICacheAdapter — inline pour éviter la dépendance circulaire et permettre\n// à @velix/cache-redis d'être utilisable sans velix-core installé\n// ============================================================================\n\nexport interface ICacheAdapter {\n get<T>(key: string): Promise<T | null>;\n set<T>(key: string, value: T, options?: CacheSetOptions): Promise<void>;\n delete(key: string): Promise<void>;\n deleteByTag(tag: string): Promise<void>;\n deleteByPrefix(prefix: string): Promise<void>;\n clear(): Promise<void>;\n has(key: string): Promise<boolean>;\n}\n\nexport type CacheSetOptions = {\n /** TTL en millisecondes */\n ttl?: number;\n /** Tags pour l'invalidation groupée */\n tags?: string[];\n};\n\n// ============================================================================\n// Script Lua inline pour deleteByTag — atomicité garantie\n// ============================================================================\n\nconst DELETE_BY_TAG_LUA = `\nlocal keys = redis.call('SMEMBERS', KEYS[1])\nif #keys > 0 then\n redis.call('DEL', unpack(keys))\nend\nredis.call('DEL', KEYS[1])\nreturn #keys\n`;\n\n// ============================================================================\n// RedisCacheAdapter\n// ============================================================================\n\n/**\n * Adaptateur de cache Redis pour Velix.\n * Implémente ICacheAdapter via ioredis.\n *\n * @example\n * ```ts\n * import { RedisCacheAdapter } from '@velix/cache-redis';\n * import { defineConfig } from 'velix';\n *\n * export default defineConfig({\n * cache: {\n * adapter: new RedisCacheAdapter({ url: process.env.REDIS_URL }),\n * }\n * });\n * ```\n */\nexport class RedisCacheAdapter implements ICacheAdapter {\n private redis: Redis;\n private prefix: string;\n private defaultTTL?: number;\n\n constructor(options: RedisCacheAdapterOptions = {}) {\n this.redis = options.client ?? new Redis(options.url ?? 'redis://localhost:6379');\n this.prefix = options.keyPrefix ?? 'velix:';\n this.defaultTTL = options.defaultTTL;\n }\n\n // ── Helpers ──────────────────────────────────────────────────────────────\n\n private key(k: string): string {\n return `${this.prefix}${k}`;\n }\n\n private tagKey(tag: string): string {\n return `${this.prefix}tag:${tag}`;\n }\n\n // ── ICacheAdapter ─────────────────────────────────────────────────────────\n\n async get<T>(key: string): Promise<T | null> {\n const raw = await this.redis.get(this.key(key));\n if (!raw) return null;\n try {\n const entry = JSON.parse(raw) as StoredEntry<T>;\n return entry.value;\n } catch {\n return null;\n }\n }\n\n async set<T>(key: string, value: T, options: CacheSetOptions = {}): Promise<void> {\n const entry: StoredEntry<T> = {\n value,\n tags: options.tags ?? [],\n };\n const serialized = JSON.stringify(entry);\n const k = this.key(key);\n const ttlMs = options.ttl ?? this.defaultTTL;\n\n if (ttlMs) {\n await this.redis.set(k, serialized, 'PX', ttlMs);\n } else {\n await this.redis.set(k, serialized);\n }\n\n // Indexer la clé dans chaque tag (Redis Set pour invalidation rapide)\n if (options.tags?.length) {\n const pipeline = this.redis.pipeline();\n for (const tag of options.tags) {\n const tKey = this.tagKey(tag);\n pipeline.sadd(tKey, k);\n if (ttlMs) {\n pipeline.pexpire(tKey, ttlMs * 2); // Expire tag set slightly after items\n }\n }\n await pipeline.exec();\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.redis.del(this.key(key));\n }\n\n async deleteByTag(tag: string): Promise<void> {\n // Script Lua atomique — évite les race conditions entre SMEMBERS et DEL\n await this.redis.eval(DELETE_BY_TAG_LUA, 1, this.tagKey(tag));\n }\n\n async deleteByPrefix(prefix: string): Promise<void> {\n // SCAN non-bloquant — ne jamais utiliser KEYS en production\n const fullPrefix = this.key(prefix);\n let cursor = '0';\n do {\n const [nextCursor, keys] = await this.redis.scan(\n cursor, 'MATCH', `${fullPrefix}*`, 'COUNT', 100\n );\n cursor = nextCursor;\n if (keys.length > 0) {\n await this.redis.del(...keys);\n }\n } while (cursor !== '0');\n }\n\n async clear(): Promise<void> {\n // SCAN + DELETE toutes les clés avec le préfixe Velix (non-bloquant)\n let cursor = '0';\n do {\n const [nextCursor, keys] = await this.redis.scan(\n cursor, 'MATCH', `${this.prefix}*`, 'COUNT', 100\n );\n cursor = nextCursor;\n if (keys.length > 0) await this.redis.del(...keys);\n } while (cursor !== '0');\n }\n\n async has(key: string): Promise<boolean> {\n return (await this.redis.exists(this.key(key))) === 1;\n }\n\n /**\n * Ferme proprement les connexions Redis.\n * À appeler au shutdown de l'application.\n */\n async disconnect(): Promise<void> {\n await this.redis.quit();\n }\n\n /**\n * Expose l'instance Redis sous-jacente pour les cas avancés.\n */\n getClient(): Redis {\n return this.redis;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamvelix/cache-redis",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.6",
|
|
4
4
|
"description": "Redis cache adapter for Velix — implements ICacheAdapter via ioredis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,13 +16,6 @@
|
|
|
16
16
|
"README.md",
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
21
|
-
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
22
|
-
"typecheck": "tsc --noEmit",
|
|
23
|
-
"test": "vitest run",
|
|
24
|
-
"test:watch": "vitest"
|
|
25
|
-
},
|
|
26
19
|
"keywords": [
|
|
27
20
|
"velix",
|
|
28
21
|
"cache",
|
|
@@ -43,5 +36,12 @@
|
|
|
43
36
|
},
|
|
44
37
|
"publishConfig": {
|
|
45
38
|
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
42
|
+
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest"
|
|
46
46
|
}
|
|
47
|
-
}
|
|
47
|
+
}
|