@knaus94/prisma-extension-cache-manager 1.4.2 → 1.5.0
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/dist/index.d.ts +6 -1
- package/dist/index.js +158 -75
- package/dist/types.d.ts +4 -4
- package/dist/types.js +1 -0
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { ModelExtension, PrismaRedisCacheConfig } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Основная функция расширения Prisma для управления кешированием с использованием Redis.
|
|
4
|
+
* @param config - Конфигурация для кеша Redis и TTL по умолчанию.
|
|
5
|
+
* @returns Prisma расширение.
|
|
6
|
+
*/
|
|
2
7
|
declare const _default: ({ cache, defaultTTL }: PrismaRedisCacheConfig) => (client: any) => import("@prisma/client/extension").PrismaClientExtends<import("@prisma/client/runtime/library").InternalArgs<{}, {
|
|
3
8
|
$allModels: ModelExtension;
|
|
4
9
|
}, {}, {
|
|
5
|
-
$cache: import("cache-manager").
|
|
10
|
+
$cache: import("cache-manager-ioredis-yet").RedisCache;
|
|
6
11
|
}>>;
|
|
7
12
|
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,124 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const extension_1 = require("@prisma/client/extension");
|
|
4
6
|
const types_1 = require("./types");
|
|
5
7
|
const crypto_1 = require("crypto");
|
|
8
|
+
const library_1 = require("@prisma/client/runtime/library");
|
|
9
|
+
const extension_1 = require("@prisma/client/extension");
|
|
10
|
+
const msgpack5_1 = __importDefault(require("msgpack5"));
|
|
11
|
+
/**
|
|
12
|
+
* Генерирует уникальный ключ для кеширования на основе модели и аргументов запроса.
|
|
13
|
+
* @param options - Опции для генерации ключа.
|
|
14
|
+
* @returns Сгенерированный ключ.
|
|
15
|
+
*/
|
|
6
16
|
function generateComposedKey(options) {
|
|
7
17
|
const hash = (0, crypto_1.createHash)("md5")
|
|
8
|
-
.update(JSON.stringify(options
|
|
18
|
+
.update(JSON.stringify(options.queryArgs))
|
|
9
19
|
.digest("hex");
|
|
10
20
|
return `${options.model}@${hash}`;
|
|
11
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Создаёт ключ с опциональным пространством имен.
|
|
24
|
+
* @param key - Основной ключ.
|
|
25
|
+
* @param namespace - Пространство имен.
|
|
26
|
+
* @returns Полный ключ с пространством имен, если оно указано.
|
|
27
|
+
*/
|
|
12
28
|
function createKey(key, namespace) {
|
|
13
29
|
return namespace ? `${namespace}:${key}` : key;
|
|
14
30
|
}
|
|
31
|
+
// Универсальная функция для сериализации данных
|
|
15
32
|
function serializeData(data) {
|
|
16
|
-
|
|
33
|
+
if (data instanceof Date) {
|
|
34
|
+
return { __type: "Date", value: data.toISOString() };
|
|
35
|
+
}
|
|
36
|
+
else if (data instanceof library_1.Decimal) {
|
|
37
|
+
return { __type: "Decimal", value: data.toString() };
|
|
38
|
+
}
|
|
39
|
+
else if (Array.isArray(data)) {
|
|
40
|
+
return data.map(serializeData); // Рекурсивно обрабатываем массивы
|
|
41
|
+
}
|
|
42
|
+
else if (data !== null && typeof data === "object") {
|
|
43
|
+
return Object.fromEntries(Object.entries(data).map(([key, value]) => [key, serializeData(value)]));
|
|
44
|
+
}
|
|
45
|
+
return data; // Простые значения возвращаем как есть
|
|
46
|
+
}
|
|
47
|
+
// Универсальная функция для десериализации данных
|
|
48
|
+
function deserializeData(data) {
|
|
49
|
+
if (data && data.__type === "Date") {
|
|
50
|
+
return new Date(data.value);
|
|
51
|
+
}
|
|
52
|
+
else if (data && data.__type === "Decimal") {
|
|
53
|
+
return new library_1.Decimal(data.value);
|
|
54
|
+
}
|
|
55
|
+
else if (Array.isArray(data)) {
|
|
56
|
+
return data.map(deserializeData); // Рекурсивно обрабатываем массивы
|
|
57
|
+
}
|
|
58
|
+
else if (data !== null && typeof data === "object") {
|
|
59
|
+
return Object.fromEntries(Object.entries(data).map(([key, value]) => [key, deserializeData(value)]));
|
|
60
|
+
}
|
|
61
|
+
return data; // Простые значения возвращаем как есть
|
|
17
62
|
}
|
|
18
|
-
|
|
19
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Обрабатывает удаление ключей из кеша после операций записи.
|
|
65
|
+
* @param cache - Кеш-менеджер.
|
|
66
|
+
* @param uncacheOption - Опции удаления кеша.
|
|
67
|
+
* @param result - Результат операции.
|
|
68
|
+
* @returns Promise<boolean> указывающий на успешность удаления.
|
|
69
|
+
*/
|
|
70
|
+
async function processUncache(cache, uncacheOption, result) {
|
|
71
|
+
let keysToDelete = [];
|
|
72
|
+
if (typeof uncacheOption === "function") {
|
|
73
|
+
const keys = uncacheOption(result);
|
|
74
|
+
keysToDelete = Array.isArray(keys) ? keys : [keys];
|
|
75
|
+
}
|
|
76
|
+
else if (typeof uncacheOption === "string") {
|
|
77
|
+
keysToDelete = [uncacheOption];
|
|
78
|
+
}
|
|
79
|
+
else if (Array.isArray(uncacheOption)) {
|
|
80
|
+
if (typeof uncacheOption[0] === "string") {
|
|
81
|
+
keysToDelete = uncacheOption;
|
|
82
|
+
}
|
|
83
|
+
else if (typeof uncacheOption[0] === "object") {
|
|
84
|
+
keysToDelete = uncacheOption.map((obj) => obj.namespace ? `${obj.namespace}:${obj.key}` : obj.key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (keysToDelete.length === 0)
|
|
88
|
+
return true;
|
|
89
|
+
try {
|
|
90
|
+
await cache.store.mdel(...keysToDelete);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
20
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Определяет, следует ли использовать кеширование для текущей операции.
|
|
99
|
+
* @param cacheOption - Опции кеширования.
|
|
100
|
+
* @returns boolean указывающий, использовать ли кеш.
|
|
101
|
+
*/
|
|
102
|
+
function shouldUseCache(cacheOption) {
|
|
103
|
+
return (cacheOption !== undefined &&
|
|
104
|
+
["boolean", "object", "number", "string"].includes(typeof cacheOption));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Определяет, следует ли использовать удаление из кеша для текущей операции.
|
|
108
|
+
* @param uncacheOption - Опции удаления кеша.
|
|
109
|
+
* @returns boolean указывающий, использовать ли удаление кеша.
|
|
110
|
+
*/
|
|
111
|
+
function shouldUseUncache(uncacheOption) {
|
|
112
|
+
return (uncacheOption !== undefined &&
|
|
113
|
+
(typeof uncacheOption === "function" ||
|
|
114
|
+
typeof uncacheOption === "string" ||
|
|
115
|
+
Array.isArray(uncacheOption)));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Основная функция расширения Prisma для управления кешированием с использованием Redis.
|
|
119
|
+
* @param config - Конфигурация для кеша Redis и TTL по умолчанию.
|
|
120
|
+
* @returns Prisma расширение.
|
|
121
|
+
*/
|
|
21
122
|
exports.default = ({ cache, defaultTTL }) => {
|
|
22
123
|
return extension_1.Prisma.defineExtension({
|
|
23
124
|
name: "prisma-extension-cache-manager",
|
|
@@ -29,9 +130,16 @@ exports.default = ({ cache, defaultTTL }) => {
|
|
|
29
130
|
},
|
|
30
131
|
query: {
|
|
31
132
|
$allModels: {
|
|
133
|
+
/**
|
|
134
|
+
* Обрабатывает все операции моделей, добавляя логику кеширования.
|
|
135
|
+
* @param params - Параметры операции.
|
|
136
|
+
* @returns Результат операции, возможно из кеша.
|
|
137
|
+
*/
|
|
32
138
|
async $allOperations({ model, operation, args, query }) {
|
|
33
|
-
|
|
139
|
+
// Проверяем, относится ли операция к кешируемым
|
|
140
|
+
if (!types_1.CACHE_OPERATIONS.includes(operation)) {
|
|
34
141
|
return query(args);
|
|
142
|
+
}
|
|
35
143
|
const isWriteOperation = [
|
|
36
144
|
"create",
|
|
37
145
|
"createMany",
|
|
@@ -40,88 +148,63 @@ exports.default = ({ cache, defaultTTL }) => {
|
|
|
40
148
|
"update",
|
|
41
149
|
].includes(operation);
|
|
42
150
|
const { cache: cacheOption, uncache: uncacheOption, ...queryArgs } = args;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (typeof option === "function") {
|
|
47
|
-
const keys = option(result);
|
|
48
|
-
keysToDelete = Array.isArray(keys) ? keys : [keys];
|
|
49
|
-
}
|
|
50
|
-
else if (typeof option === "string") {
|
|
51
|
-
keysToDelete = [option];
|
|
52
|
-
}
|
|
53
|
-
else if (Array.isArray(option)) {
|
|
54
|
-
if (typeof option[0] === "string") {
|
|
55
|
-
keysToDelete = option;
|
|
56
|
-
}
|
|
57
|
-
else if (typeof option[0] === "object") {
|
|
58
|
-
keysToDelete = option.map((obj) => obj.namespace ? `${obj.namespace}:${obj.key}` : obj.key);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (!keysToDelete.length)
|
|
62
|
-
return true;
|
|
63
|
-
return cache.store
|
|
64
|
-
.mdel(...keysToDelete)
|
|
65
|
-
.then(() => true)
|
|
66
|
-
.catch(() => false);
|
|
67
|
-
}
|
|
68
|
-
const useCache = cacheOption !== undefined &&
|
|
69
|
-
["boolean", "object", "number", "string"].includes(typeof cacheOption);
|
|
70
|
-
const useUncache = uncacheOption !== undefined &&
|
|
71
|
-
(typeof uncacheOption === "function" ||
|
|
72
|
-
typeof uncacheOption === "string" ||
|
|
73
|
-
Array.isArray(uncacheOption));
|
|
151
|
+
const useCache = shouldUseCache(cacheOption);
|
|
152
|
+
const useUncache = shouldUseUncache(uncacheOption);
|
|
153
|
+
// Если не используем кеш, просто выполняем запрос и обрабатываем удаление кеша, если требуется
|
|
74
154
|
if (!useCache) {
|
|
75
155
|
const result = await query(queryArgs);
|
|
76
|
-
if (useUncache)
|
|
77
|
-
processUncache(result);
|
|
156
|
+
if (useUncache) {
|
|
157
|
+
await processUncache(cache, uncacheOption, result);
|
|
158
|
+
}
|
|
78
159
|
return result;
|
|
79
160
|
}
|
|
161
|
+
// Генерация ключа кеша
|
|
162
|
+
let cacheKey;
|
|
163
|
+
let ttl;
|
|
80
164
|
if (["boolean", "number", "string"].includes(typeof cacheOption)) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
model,
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
if (!isWriteOperation) {
|
|
88
|
-
const cached = await cache.get(cacheKey);
|
|
89
|
-
if (cached) {
|
|
90
|
-
return deserializeData(cached);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const result = await query(queryArgs);
|
|
94
|
-
if (useUncache)
|
|
95
|
-
processUncache(result);
|
|
96
|
-
const ttl = typeof cacheOption === "number"
|
|
97
|
-
? cacheOption
|
|
98
|
-
: defaultTTL ?? undefined;
|
|
99
|
-
await cache.set(cacheKey, serializeData(result), ttl);
|
|
100
|
-
return result;
|
|
165
|
+
cacheKey =
|
|
166
|
+
typeof cacheOption === "string"
|
|
167
|
+
? cacheOption
|
|
168
|
+
: generateComposedKey({ model, queryArgs });
|
|
169
|
+
ttl = typeof cacheOption === "number" ? cacheOption : defaultTTL;
|
|
101
170
|
}
|
|
102
|
-
if (typeof cacheOption.key === "function") {
|
|
171
|
+
else if (typeof cacheOption.key === "function") {
|
|
103
172
|
const result = await query(queryArgs);
|
|
104
|
-
if (useUncache)
|
|
105
|
-
processUncache(result);
|
|
106
|
-
|
|
107
|
-
|
|
173
|
+
if (useUncache) {
|
|
174
|
+
await processUncache(cache, uncacheOption, result);
|
|
175
|
+
}
|
|
176
|
+
cacheKey = cacheOption.key(result);
|
|
177
|
+
ttl = cacheOption.ttl ?? defaultTTL;
|
|
178
|
+
await cache.set(cacheKey, msgpack5_1.default.encode(serializeData(result)), ttl);
|
|
108
179
|
return result;
|
|
109
180
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
181
|
+
else {
|
|
182
|
+
cacheKey =
|
|
183
|
+
createKey(cacheOption.key, cacheOption.namespace) ||
|
|
184
|
+
generateComposedKey({ model, queryArgs });
|
|
185
|
+
ttl = cacheOption.ttl ?? defaultTTL;
|
|
186
|
+
}
|
|
187
|
+
// Для операций чтения пытаемся получить данные из кеша
|
|
115
188
|
if (!isWriteOperation) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
189
|
+
try {
|
|
190
|
+
const cached = await cache.store.client.getBuffer(cacheKey);
|
|
191
|
+
if (cached) {
|
|
192
|
+
return deserializeData(msgpack5_1.default.decode(cached));
|
|
193
|
+
}
|
|
119
194
|
}
|
|
195
|
+
catch { }
|
|
120
196
|
}
|
|
197
|
+
// Выполняем запрос к базе данных
|
|
121
198
|
const result = await query(queryArgs);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
199
|
+
// Обрабатываем удаление кеша, если требуется
|
|
200
|
+
if (useUncache) {
|
|
201
|
+
await processUncache(cache, uncacheOption, result);
|
|
202
|
+
}
|
|
203
|
+
// Сохраняем результат в кеш
|
|
204
|
+
try {
|
|
205
|
+
await cache.set(cacheKey, msgpack5_1.default.encode(serializeData(result)), ttl);
|
|
206
|
+
}
|
|
207
|
+
catch { }
|
|
125
208
|
return result;
|
|
126
209
|
},
|
|
127
210
|
},
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Prisma } from "@prisma/client/extension";
|
|
2
|
-
import {
|
|
3
|
-
export declare const REQUIRED_ARGS_OPERATIONS: readonly ["delete", "findUnique", "findUniqueOrThrow", "groupBy", "update", "upsert", "create", "createMany", "updateMany"];
|
|
2
|
+
import { RedisCache } from "cache-manager-ioredis-yet";
|
|
3
|
+
export declare const REQUIRED_ARGS_OPERATIONS: readonly ["delete", "findUnique", "findUniqueOrThrow", "aggregate", "groupBy", "update", "upsert", "create", "createMany", "updateMany"];
|
|
4
4
|
export declare const OPTIONAL_ARGS_OPERATIONS: readonly ["findMany", "findFirst", "findFirstOrThrow", "count"];
|
|
5
|
-
export declare const CACHE_OPERATIONS: readonly ["delete", "findUnique", "findUniqueOrThrow", "groupBy", "update", "upsert", "create", "createMany", "updateMany", "findMany", "findFirst", "findFirstOrThrow", "count"];
|
|
5
|
+
export declare const CACHE_OPERATIONS: readonly ["delete", "findUnique", "findUniqueOrThrow", "aggregate", "groupBy", "update", "upsert", "create", "createMany", "updateMany", "findMany", "findFirst", "findFirstOrThrow", "count"];
|
|
6
6
|
type RequiredArgsOperation = (typeof REQUIRED_ARGS_OPERATIONS)[number];
|
|
7
7
|
type OptionalArgsOperation = (typeof OPTIONAL_ARGS_OPERATIONS)[number];
|
|
8
8
|
type RequiredArgsFunction<O extends RequiredArgsOperation> = <T, A>(this: T, args: Prisma.Exact<A, Prisma.Args<T, O> & PrismaCacheArgs<T, A, O>>) => Prisma.PrismaPromise<Prisma.Result<T, A, O>>;
|
|
@@ -34,7 +34,7 @@ export interface PrismaCacheArgs<T, A, O extends RequiredArgsOperation | Optiona
|
|
|
34
34
|
}[];
|
|
35
35
|
}
|
|
36
36
|
export interface PrismaRedisCacheConfig {
|
|
37
|
-
cache:
|
|
37
|
+
cache: RedisCache;
|
|
38
38
|
defaultTTL?: number;
|
|
39
39
|
}
|
|
40
40
|
export {};
|
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knaus94/prisma-extension-cache-manager",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/knaus94/prisma-extension-cache-manager.git"
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@prisma/client": "^5.7.1",
|
|
58
|
-
"cache-manager": "^5.2.
|
|
58
|
+
"cache-manager": "^5.2.3",
|
|
59
|
+
"cache-manager-ioredis-yet": "^2.1.1",
|
|
60
|
+
"msgpack5": "^6.0.2"
|
|
59
61
|
}
|
|
60
62
|
}
|