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