ts-cache-mongoose 2.0.0 → 2.2.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/README.md +68 -16
- package/dist/index.cjs +154 -132
- package/dist/index.d.cts +52 -10
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +52 -10
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +154 -132
- package/dist/nest/index.cjs +4 -1
- package/dist/nest/index.mjs +4 -1
- package/package.json +22 -24
- package/biome.json +0 -47
- package/src/cache/Cache.ts +0 -72
- package/src/cache/engine/MemoryCacheEngine.ts +0 -41
- package/src/cache/engine/RedisCacheEngine.ts +0 -52
- package/src/extend/aggregate.ts +0 -52
- package/src/extend/query.ts +0 -82
- package/src/index.ts +0 -68
- package/src/key.ts +0 -10
- package/src/ms.ts +0 -55
- package/src/nest/cache.module.ts +0 -79
- package/src/nest/cache.service.ts +0 -37
- package/src/nest/index.ts +0 -4
- package/src/nest/interfaces.ts +0 -17
- package/src/sort-keys.ts +0 -38
- package/src/types.ts +0 -20
- package/src/version.ts +0 -18
- package/tests/cache-debug.test.ts +0 -73
- package/tests/cache-memory.test.ts +0 -217
- package/tests/cache-options.test.ts +0 -83
- package/tests/cache-redis.test.ts +0 -521
- package/tests/key.test.ts +0 -103
- package/tests/models/Story.ts +0 -29
- package/tests/models/User.ts +0 -39
- package/tests/mongo/.gitignore +0 -3
- package/tests/mongo/server.ts +0 -29
- package/tests/ms.test.ts +0 -93
- package/tests/nest.test.ts +0 -158
- package/tests/sort-keys.test.ts +0 -80
- package/tsconfig.json +0 -34
- package/vite.config.mts +0 -23
package/README.md
CHANGED
|
@@ -12,22 +12,28 @@ Cache query and aggregate in mongoose using in-memory or redis
|
|
|
12
12
|
[](https://sonarcloud.io/summary/new_code?id=ilovepixelart_ts-cache-mongoose)
|
|
13
13
|
[](https://sonarcloud.io/summary/new_code?id=ilovepixelart_ts-cache-mongoose)
|
|
14
14
|
[](https://sonarcloud.io/summary/new_code?id=ilovepixelart_ts-cache-mongoose)
|
|
15
|
+
\
|
|
16
|
+
[](https://socket.dev/npm/package/ts-cache-mongoose)
|
|
17
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/ilovepixelart/ts-cache-mongoose)
|
|
18
|
+
[](https://www.bestpractices.dev/en/projects/12484)
|
|
15
19
|
|
|
16
20
|
## Motivation
|
|
17
21
|
|
|
18
22
|
ts-cache-mongoose is a plugin for mongoose
|
|
19
23
|
\
|
|
20
|
-
|
|
24
|
+
I need a way to cache mongoose queries and aggregations to improve application performance. It should support both in-memory and Redis cache engines, work with all major Node.js frameworks, and be easy to use with a simple `.cache()` method on queries and aggregations.
|
|
21
25
|
|
|
22
26
|
## Supports and tested with
|
|
23
27
|
|
|
24
28
|
```json
|
|
25
29
|
{
|
|
26
30
|
"node": "20.x || 22.x || 24.x",
|
|
27
|
-
"mongoose": ">=6.6.
|
|
31
|
+
"mongoose": ">=6.6.0 <10"
|
|
28
32
|
}
|
|
29
33
|
```
|
|
30
34
|
|
|
35
|
+
CI tests against mongoose `6.12.2`, `7.6.4`, `8.23.0`, and `9.4.1`.
|
|
36
|
+
|
|
31
37
|
## Features
|
|
32
38
|
|
|
33
39
|
- In-memory caching
|
|
@@ -42,22 +48,13 @@ Caching queries is a good way to improve performance of your application
|
|
|
42
48
|
|
|
43
49
|
## Installation
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
npm install ts-cache-mongoose
|
|
49
|
-
pnpm add ts-cache-mongoose
|
|
50
|
-
yarn add ts-cache-mongoose
|
|
51
|
-
bun add ts-cache-mongoose
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
- This plugin requires `mongoose` to be installed as a peer dependency
|
|
51
|
+
`mongoose` is a peer dependency — install it alongside `ts-cache-mongoose`.
|
|
55
52
|
|
|
56
53
|
```bash
|
|
57
|
-
npm install mongoose
|
|
58
|
-
pnpm add mongoose
|
|
59
|
-
yarn add mongoose
|
|
60
|
-
bun add mongoose
|
|
54
|
+
npm install ts-cache-mongoose mongoose
|
|
55
|
+
pnpm add ts-cache-mongoose mongoose
|
|
56
|
+
yarn add ts-cache-mongoose mongoose
|
|
57
|
+
bun add ts-cache-mongoose mongoose
|
|
61
58
|
```
|
|
62
59
|
|
|
63
60
|
## Example
|
|
@@ -105,6 +102,53 @@ const books = await Book.aggregate([
|
|
|
105
102
|
]).cache('1 minute').exec()
|
|
106
103
|
```
|
|
107
104
|
|
|
105
|
+
### Bounded in-memory cache
|
|
106
|
+
|
|
107
|
+
The in-memory engine is unbounded by default. For workloads where query keys are driven by user input (search, filters, pagination), cap the cache so a caller generating unique cache keys cannot grow the map without limit. Two bounds are available and can be combined — eviction is LRU, and whichever bound is hit first triggers it:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
cache.init(mongoose, {
|
|
111
|
+
engine: 'memory',
|
|
112
|
+
defaultTTL: '60 seconds',
|
|
113
|
+
maxEntries: 10_000, // cap by entry count
|
|
114
|
+
maxBytes: 50 * 1024 * 1024, // cap by serialized bytes (50 MB)
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`maxBytes` measures entry size via `node:v8.serialize(value).byteLength` by default — handles circular references (mongoose `populate` parent-refs), single C++ call per `set`, works on Node / Bun / Deno. Provide your own `sizeCalculation` callback if you want an O(1) estimate instead:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
cache.init(mongoose, {
|
|
122
|
+
engine: 'memory',
|
|
123
|
+
maxBytes: 50 * 1024 * 1024,
|
|
124
|
+
sizeCalculation: (value) => {
|
|
125
|
+
if (Array.isArray(value)) return value.length * 512
|
|
126
|
+
return 512
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Eviction is soft: the just-written entry is never dropped, even if its own size exceeds `maxBytes`. Everything older gets evicted until both bounds are satisfied (or only the new entry remains).
|
|
132
|
+
|
|
133
|
+
Both options are ignored for the Redis engine — use Redis's own `maxmemory` + `maxmemory-policy` instead.
|
|
134
|
+
|
|
135
|
+
### Custom error handling
|
|
136
|
+
|
|
137
|
+
By default, cache engine failures (Redis disconnects, serialization errors, etc.) are logged via `console.error` and the query falls through to the database. Pass an `onError` callback to route them somewhere else — e.g. a structured logger, Sentry, or a metric counter:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
cache.init(mongoose, {
|
|
141
|
+
engine: 'redis',
|
|
142
|
+
defaultTTL: '60 seconds',
|
|
143
|
+
engineOptions: { host: 'localhost', port: 6379 },
|
|
144
|
+
onError: (error) => {
|
|
145
|
+
logger.warn({ err: error }, 'cache engine failure')
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The callback receives the raw `Error`. Cache reads and writes never throw — a failing engine degrades to a cache miss.
|
|
151
|
+
|
|
108
152
|
### Cache invalidation
|
|
109
153
|
|
|
110
154
|
```typescript
|
|
@@ -164,6 +208,14 @@ export class SomeService {
|
|
|
164
208
|
}
|
|
165
209
|
```
|
|
166
210
|
|
|
211
|
+
## Contributing
|
|
212
|
+
|
|
213
|
+
Check [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
|
|
218
|
+
|
|
167
219
|
## Check my other projects
|
|
168
220
|
|
|
169
221
|
- [ts-migrate-mongoose](https://github.com/ilovepixelart/ts-migrate-mongoose) - Migration framework for mongoose
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var node_v8 = require('node:v8');
|
|
3
4
|
var bson = require('bson');
|
|
4
5
|
var IORedis = require('ioredis');
|
|
5
6
|
var mongoose = require('mongoose');
|
|
@@ -11,89 +12,119 @@ const h = m * 60;
|
|
|
11
12
|
const d = h * 24;
|
|
12
13
|
const w = d * 7;
|
|
13
14
|
const y = d * 365.25;
|
|
14
|
-
const
|
|
15
|
+
const mo = y / 12;
|
|
15
16
|
const UNITS = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
weeks: w,
|
|
22
|
-
week: w,
|
|
23
|
-
w,
|
|
24
|
-
days: d,
|
|
25
|
-
day: d,
|
|
26
|
-
d,
|
|
27
|
-
hours: h,
|
|
28
|
-
hour: h,
|
|
29
|
-
hrs: h,
|
|
30
|
-
hr: h,
|
|
31
|
-
h,
|
|
32
|
-
minutes: m,
|
|
33
|
-
minute: m,
|
|
34
|
-
mins: m,
|
|
35
|
-
min: m,
|
|
36
|
-
m,
|
|
17
|
+
milliseconds: 1,
|
|
18
|
+
millisecond: 1,
|
|
19
|
+
msecs: 1,
|
|
20
|
+
msec: 1,
|
|
21
|
+
ms: 1,
|
|
37
22
|
seconds: s,
|
|
38
23
|
second: s,
|
|
39
24
|
secs: s,
|
|
40
25
|
sec: s,
|
|
41
26
|
s,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
27
|
+
minutes: m,
|
|
28
|
+
minute: m,
|
|
29
|
+
mins: m,
|
|
30
|
+
min: m,
|
|
31
|
+
m,
|
|
32
|
+
hours: h,
|
|
33
|
+
hour: h,
|
|
34
|
+
hrs: h,
|
|
35
|
+
hr: h,
|
|
36
|
+
h,
|
|
37
|
+
days: d,
|
|
38
|
+
day: d,
|
|
39
|
+
d,
|
|
40
|
+
weeks: w,
|
|
41
|
+
week: w,
|
|
42
|
+
w,
|
|
43
|
+
months: mo,
|
|
44
|
+
month: mo,
|
|
45
|
+
mo,
|
|
46
|
+
years: y,
|
|
47
|
+
year: y,
|
|
48
|
+
yrs: y,
|
|
49
|
+
yr: y,
|
|
50
|
+
y
|
|
47
51
|
};
|
|
52
|
+
const unitPattern = Object.keys(UNITS).sort((a, b) => b.length - a.length).join("|");
|
|
53
|
+
const RE = new RegExp(String.raw`^(-?(?:\d+)?\.?\d+)\s*(${unitPattern})?$`, "i");
|
|
48
54
|
const ms = (val) => {
|
|
49
55
|
const str = String(val);
|
|
50
|
-
if (str.length > 100) return
|
|
56
|
+
if (str.length > 100) return Number.NaN;
|
|
51
57
|
const match = RE.exec(str);
|
|
52
|
-
if (!match) return
|
|
58
|
+
if (!match) return Number.NaN;
|
|
53
59
|
const n = Number.parseFloat(match[1] ?? "");
|
|
54
60
|
const type = (match[2] ?? "ms").toLowerCase();
|
|
55
61
|
return n * (UNITS[type] ?? 0);
|
|
56
62
|
};
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
throw TypeError(msg);
|
|
60
|
-
};
|
|
61
|
-
var __accessCheck$3 = (obj, member, msg) => member.has(obj) || __typeError$3("Cannot " + msg);
|
|
62
|
-
var __privateGet$3 = (obj, member, getter) => (__accessCheck$3(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
63
|
-
var __privateAdd$3 = (obj, member, value) => member.has(obj) ? __typeError$3("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
64
|
-
var __privateSet$3 = (obj, member, value, setter) => (__accessCheck$3(obj, member, "write to private field"), member.set(obj, value), value);
|
|
65
|
-
var _cache;
|
|
64
|
+
const defaultSizer = (value) => node_v8.serialize(value).byteLength;
|
|
66
65
|
class MemoryCacheEngine {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
#cache;
|
|
67
|
+
#maxEntries;
|
|
68
|
+
#maxBytes;
|
|
69
|
+
#sizeOf;
|
|
70
|
+
#totalBytes;
|
|
71
|
+
constructor(options) {
|
|
72
|
+
this.#cache = /* @__PURE__ */ new Map();
|
|
73
|
+
this.#maxEntries = options?.maxEntries != null && options.maxEntries > 0 ? options.maxEntries : Number.POSITIVE_INFINITY;
|
|
74
|
+
this.#maxBytes = options?.maxBytes != null && options.maxBytes > 0 ? options.maxBytes : Number.POSITIVE_INFINITY;
|
|
75
|
+
this.#sizeOf = options?.sizeCalculation ?? defaultSizer;
|
|
76
|
+
this.#totalBytes = 0;
|
|
77
|
+
}
|
|
78
|
+
get totalBytes() {
|
|
79
|
+
return this.#totalBytes;
|
|
80
|
+
}
|
|
81
|
+
get size() {
|
|
82
|
+
return this.#cache.size;
|
|
70
83
|
}
|
|
71
84
|
get(key) {
|
|
72
|
-
const item =
|
|
73
|
-
if (!item
|
|
74
|
-
|
|
85
|
+
const item = this.#cache.get(key);
|
|
86
|
+
if (!item) return void 0;
|
|
87
|
+
if (item.expiresAt < Date.now()) {
|
|
88
|
+
this.#cache.delete(key);
|
|
89
|
+
this.#totalBytes -= item.bytes;
|
|
75
90
|
return void 0;
|
|
76
91
|
}
|
|
92
|
+
this.#cache.delete(key);
|
|
93
|
+
this.#cache.set(key, item);
|
|
77
94
|
return item.value;
|
|
78
95
|
}
|
|
79
96
|
set(key, value, ttl) {
|
|
80
|
-
const givenTTL =
|
|
97
|
+
const givenTTL = ttl == null ? void 0 : ms(ttl);
|
|
81
98
|
const actualTTL = givenTTL ?? Number.POSITIVE_INFINITY;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
const existing = this.#cache.get(key);
|
|
100
|
+
if (existing) {
|
|
101
|
+
this.#cache.delete(key);
|
|
102
|
+
this.#totalBytes -= existing.bytes;
|
|
103
|
+
}
|
|
104
|
+
const bytes = this.#sizeOf(value);
|
|
105
|
+
this.#cache.set(key, { value, expiresAt: Date.now() + actualTTL, bytes });
|
|
106
|
+
this.#totalBytes += bytes;
|
|
107
|
+
while ((this.#cache.size > this.#maxEntries || this.#totalBytes > this.#maxBytes) && this.#cache.size > 1) {
|
|
108
|
+
const oldestKey = this.#cache.keys().next().value;
|
|
109
|
+
if (oldestKey === void 0 || oldestKey === key) break;
|
|
110
|
+
const oldest = this.#cache.get(oldestKey);
|
|
111
|
+
this.#cache.delete(oldestKey);
|
|
112
|
+
if (oldest) this.#totalBytes -= oldest.bytes;
|
|
113
|
+
}
|
|
86
114
|
}
|
|
87
115
|
del(key) {
|
|
88
|
-
|
|
116
|
+
const item = this.#cache.get(key);
|
|
117
|
+
if (!item) return;
|
|
118
|
+
this.#cache.delete(key);
|
|
119
|
+
this.#totalBytes -= item.bytes;
|
|
89
120
|
}
|
|
90
121
|
clear() {
|
|
91
|
-
|
|
122
|
+
this.#cache.clear();
|
|
123
|
+
this.#totalBytes = 0;
|
|
92
124
|
}
|
|
93
125
|
close() {
|
|
94
126
|
}
|
|
95
127
|
}
|
|
96
|
-
_cache = new WeakMap();
|
|
97
128
|
|
|
98
129
|
const isMongooseLessThan7 = Number.parseInt(mongoose.version, 10) < 7;
|
|
99
130
|
const convertToObject = (value) => {
|
|
@@ -108,120 +139,120 @@ const convertToObject = (value) => {
|
|
|
108
139
|
return value;
|
|
109
140
|
};
|
|
110
141
|
|
|
111
|
-
var __typeError$2 = (msg) => {
|
|
112
|
-
throw TypeError(msg);
|
|
113
|
-
};
|
|
114
|
-
var __accessCheck$2 = (obj, member, msg) => member.has(obj) || __typeError$2("Cannot " + msg);
|
|
115
|
-
var __privateGet$2 = (obj, member, getter) => (__accessCheck$2(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
116
|
-
var __privateAdd$2 = (obj, member, value) => member.has(obj) ? __typeError$2("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
117
|
-
var __privateSet$2 = (obj, member, value, setter) => (__accessCheck$2(obj, member, "write to private field"), member.set(obj, value), value);
|
|
118
|
-
var _client;
|
|
119
142
|
class RedisCacheEngine {
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
#client;
|
|
144
|
+
#onError;
|
|
145
|
+
constructor(options, onError) {
|
|
122
146
|
options.keyPrefix ??= "cache-mongoose:";
|
|
123
|
-
|
|
147
|
+
this.#client = new IORedis(options);
|
|
148
|
+
this.#onError = onError;
|
|
124
149
|
}
|
|
125
150
|
async get(key) {
|
|
126
151
|
try {
|
|
127
|
-
const value = await
|
|
152
|
+
const value = await this.#client.get(key);
|
|
128
153
|
if (value === null) {
|
|
129
154
|
return void 0;
|
|
130
155
|
}
|
|
131
156
|
return bson.EJSON.parse(value);
|
|
132
157
|
} catch (err) {
|
|
133
|
-
|
|
158
|
+
this.#onError(err);
|
|
134
159
|
return void 0;
|
|
135
160
|
}
|
|
136
161
|
}
|
|
137
162
|
async set(key, value, ttl) {
|
|
138
163
|
try {
|
|
139
|
-
const
|
|
164
|
+
const converted = convertToObject(value);
|
|
165
|
+
if (converted === void 0) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const givenTTL = ttl == null ? void 0 : ms(ttl);
|
|
140
169
|
const actualTTL = givenTTL ?? Number.POSITIVE_INFINITY;
|
|
141
|
-
const serializedValue = bson.EJSON.stringify(
|
|
142
|
-
await
|
|
170
|
+
const serializedValue = bson.EJSON.stringify(converted);
|
|
171
|
+
await this.#client.setex(key, Math.ceil(actualTTL / 1e3), serializedValue);
|
|
143
172
|
} catch (err) {
|
|
144
|
-
|
|
173
|
+
this.#onError(err);
|
|
145
174
|
}
|
|
146
175
|
}
|
|
147
176
|
async del(key) {
|
|
148
|
-
await
|
|
177
|
+
await this.#client.del(key);
|
|
149
178
|
}
|
|
150
179
|
async clear() {
|
|
151
|
-
await
|
|
180
|
+
await this.#client.flushdb();
|
|
152
181
|
}
|
|
153
182
|
async close() {
|
|
154
|
-
await
|
|
183
|
+
await this.#client.quit();
|
|
155
184
|
}
|
|
156
185
|
}
|
|
157
|
-
_client = new WeakMap();
|
|
158
186
|
|
|
159
|
-
var __typeError$1 = (msg) => {
|
|
160
|
-
throw TypeError(msg);
|
|
161
|
-
};
|
|
162
|
-
var __accessCheck$1 = (obj, member, msg) => member.has(obj) || __typeError$1("Cannot " + msg);
|
|
163
|
-
var __privateGet$1 = (obj, member, getter) => (__accessCheck$1(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
164
|
-
var __privateAdd$1 = (obj, member, value) => member.has(obj) ? __typeError$1("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
165
|
-
var __privateSet$1 = (obj, member, value, setter) => (__accessCheck$1(obj, member, "write to private field"), member.set(obj, value), value);
|
|
166
|
-
var _engine, _defaultTTL, _debug, _engines;
|
|
167
187
|
class Cache {
|
|
188
|
+
#engine;
|
|
189
|
+
#defaultTTL;
|
|
190
|
+
#debug;
|
|
191
|
+
#onError;
|
|
192
|
+
#engines = ["memory", "redis"];
|
|
168
193
|
constructor(cacheOptions) {
|
|
169
|
-
|
|
170
|
-
__privateAdd$1(this, _defaultTTL);
|
|
171
|
-
__privateAdd$1(this, _debug);
|
|
172
|
-
__privateAdd$1(this, _engines, ["memory", "redis"]);
|
|
173
|
-
if (!__privateGet$1(this, _engines).includes(cacheOptions.engine)) {
|
|
194
|
+
if (!this.#engines.includes(cacheOptions.engine)) {
|
|
174
195
|
throw new Error(`Invalid engine name: ${cacheOptions.engine}`);
|
|
175
196
|
}
|
|
176
197
|
if (cacheOptions.engine === "redis" && !cacheOptions.engineOptions) {
|
|
177
198
|
throw new Error(`Engine options are required for ${cacheOptions.engine} engine`);
|
|
178
199
|
}
|
|
179
200
|
cacheOptions.defaultTTL ??= "1 minute";
|
|
180
|
-
|
|
201
|
+
this.#defaultTTL = ms(cacheOptions.defaultTTL);
|
|
202
|
+
this.#onError = cacheOptions.onError ?? console.error;
|
|
181
203
|
if (cacheOptions.engine === "redis" && cacheOptions.engineOptions) {
|
|
182
|
-
|
|
204
|
+
this.#engine = new RedisCacheEngine(cacheOptions.engineOptions, this.#onError);
|
|
183
205
|
}
|
|
184
206
|
if (cacheOptions.engine === "memory") {
|
|
185
|
-
|
|
207
|
+
this.#engine = new MemoryCacheEngine({
|
|
208
|
+
maxEntries: cacheOptions.maxEntries,
|
|
209
|
+
maxBytes: cacheOptions.maxBytes,
|
|
210
|
+
sizeCalculation: cacheOptions.sizeCalculation
|
|
211
|
+
});
|
|
186
212
|
}
|
|
187
|
-
|
|
213
|
+
this.#debug = cacheOptions.debug === true;
|
|
214
|
+
}
|
|
215
|
+
get onError() {
|
|
216
|
+
return this.#onError;
|
|
188
217
|
}
|
|
189
218
|
async get(key) {
|
|
190
|
-
const cacheEntry = await
|
|
191
|
-
if (
|
|
219
|
+
const cacheEntry = await this.#engine.get(key);
|
|
220
|
+
if (this.#debug) {
|
|
192
221
|
const cacheHit = cacheEntry == null ? "MISS" : "HIT";
|
|
193
222
|
console.log(`[ts-cache-mongoose] GET '${key}' - ${cacheHit}`);
|
|
194
223
|
}
|
|
195
224
|
return cacheEntry;
|
|
196
225
|
}
|
|
197
226
|
async set(key, value, ttl) {
|
|
198
|
-
const givenTTL =
|
|
199
|
-
const actualTTL = givenTTL ??
|
|
200
|
-
|
|
201
|
-
|
|
227
|
+
const givenTTL = ttl == null ? null : ms(ttl);
|
|
228
|
+
const actualTTL = givenTTL ?? this.#defaultTTL;
|
|
229
|
+
if (Number.isNaN(actualTTL) || actualTTL <= 0) {
|
|
230
|
+
if (this.#debug) {
|
|
231
|
+
console.log(`[ts-cache-mongoose] SET '${key}' - skipped (non-positive ttl: ${String(actualTTL)} ms)`);
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
await this.#engine.set(key, value, actualTTL);
|
|
236
|
+
if (this.#debug) {
|
|
202
237
|
console.log(`[ts-cache-mongoose] SET '${key}' - ttl: ${actualTTL.toFixed(0)} ms`);
|
|
203
238
|
}
|
|
204
239
|
}
|
|
205
240
|
async del(key) {
|
|
206
|
-
await
|
|
207
|
-
if (
|
|
241
|
+
await this.#engine.del(key);
|
|
242
|
+
if (this.#debug) {
|
|
208
243
|
console.log(`[ts-cache-mongoose] DEL '${key}'`);
|
|
209
244
|
}
|
|
210
245
|
}
|
|
211
246
|
async clear() {
|
|
212
|
-
await
|
|
213
|
-
if (
|
|
247
|
+
await this.#engine.clear();
|
|
248
|
+
if (this.#debug) {
|
|
214
249
|
console.log("[ts-cache-mongoose] CLEAR");
|
|
215
250
|
}
|
|
216
251
|
}
|
|
217
252
|
async close() {
|
|
218
|
-
return
|
|
253
|
+
return this.#engine.close();
|
|
219
254
|
}
|
|
220
255
|
}
|
|
221
|
-
_engine = new WeakMap();
|
|
222
|
-
_defaultTTL = new WeakMap();
|
|
223
|
-
_debug = new WeakMap();
|
|
224
|
-
_engines = new WeakMap();
|
|
225
256
|
|
|
226
257
|
const isPlainObject = (value) => {
|
|
227
258
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -273,7 +304,7 @@ function extendAggregate(mongoose, cache) {
|
|
|
273
304
|
pipeline: this.pipeline()
|
|
274
305
|
});
|
|
275
306
|
};
|
|
276
|
-
mongoose.Aggregate.prototype.
|
|
307
|
+
mongoose.Aggregate.prototype.getDuration = function() {
|
|
277
308
|
return this._ttl;
|
|
278
309
|
};
|
|
279
310
|
mongoose.Aggregate.prototype.cache = function(ttl, customKey) {
|
|
@@ -282,20 +313,20 @@ function extendAggregate(mongoose, cache) {
|
|
|
282
313
|
return this;
|
|
283
314
|
};
|
|
284
315
|
mongoose.Aggregate.prototype.exec = async function(...args) {
|
|
285
|
-
if (!Object.
|
|
316
|
+
if (!Object.hasOwn(this, "_ttl")) {
|
|
286
317
|
return mongooseExec.apply(this, args);
|
|
287
318
|
}
|
|
288
319
|
const key = this.getCacheKey();
|
|
289
|
-
const ttl = this.
|
|
320
|
+
const ttl = this.getDuration();
|
|
290
321
|
const resultCache = await cache.get(key).catch((err) => {
|
|
291
|
-
|
|
322
|
+
cache.onError(err);
|
|
292
323
|
});
|
|
293
324
|
if (resultCache) {
|
|
294
325
|
return resultCache;
|
|
295
326
|
}
|
|
296
327
|
const result = await mongooseExec.call(this);
|
|
297
328
|
await cache.set(key, result, ttl).catch((err) => {
|
|
298
|
-
|
|
329
|
+
cache.onError(err);
|
|
299
330
|
});
|
|
300
331
|
return result;
|
|
301
332
|
};
|
|
@@ -322,7 +353,7 @@ function extendQuery(mongoose, cache) {
|
|
|
322
353
|
_conditions: this._conditions
|
|
323
354
|
});
|
|
324
355
|
};
|
|
325
|
-
mongoose.Query.prototype.
|
|
356
|
+
mongoose.Query.prototype.getDuration = function() {
|
|
326
357
|
return this._ttl;
|
|
327
358
|
};
|
|
328
359
|
mongoose.Query.prototype.cache = function(ttl, customKey) {
|
|
@@ -331,17 +362,17 @@ function extendQuery(mongoose, cache) {
|
|
|
331
362
|
return this;
|
|
332
363
|
};
|
|
333
364
|
mongoose.Query.prototype.exec = async function(...args) {
|
|
334
|
-
if (!Object.
|
|
365
|
+
if (!Object.hasOwn(this, "_ttl")) {
|
|
335
366
|
return mongooseExec.apply(this, args);
|
|
336
367
|
}
|
|
337
368
|
const key = this.getCacheKey();
|
|
338
|
-
const ttl = this.
|
|
369
|
+
const ttl = this.getDuration();
|
|
339
370
|
const mongooseOptions = this.mongooseOptions();
|
|
340
371
|
const isCount = this.op?.includes("count") ?? false;
|
|
341
372
|
const isDistinct = this.op === "distinct";
|
|
342
373
|
const model = this.model.modelName;
|
|
343
374
|
const resultCache = await cache.get(key).catch((err) => {
|
|
344
|
-
|
|
375
|
+
cache.onError(err);
|
|
345
376
|
});
|
|
346
377
|
if (resultCache) {
|
|
347
378
|
if (isCount || isDistinct || mongooseOptions.lean) {
|
|
@@ -357,32 +388,26 @@ function extendQuery(mongoose, cache) {
|
|
|
357
388
|
}
|
|
358
389
|
const result = await mongooseExec.call(this);
|
|
359
390
|
await cache.set(key, result, ttl).catch((err) => {
|
|
360
|
-
|
|
391
|
+
cache.onError(err);
|
|
361
392
|
});
|
|
362
393
|
return result;
|
|
363
394
|
};
|
|
364
395
|
}
|
|
365
396
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
370
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
371
|
-
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
372
|
-
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
|
|
373
|
-
var _instance;
|
|
374
|
-
const _CacheMongoose = class _CacheMongoose {
|
|
397
|
+
class CacheMongoose {
|
|
398
|
+
static #instance;
|
|
399
|
+
cache;
|
|
375
400
|
constructor() {
|
|
376
401
|
}
|
|
377
402
|
static init(mongoose, cacheOptions) {
|
|
378
|
-
if (!
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const cache =
|
|
403
|
+
if (!CacheMongoose.#instance) {
|
|
404
|
+
CacheMongoose.#instance = new CacheMongoose();
|
|
405
|
+
CacheMongoose.#instance.cache = new Cache(cacheOptions);
|
|
406
|
+
const cache = CacheMongoose.#instance.cache;
|
|
382
407
|
extendQuery(mongoose, cache);
|
|
383
408
|
extendAggregate(mongoose, cache);
|
|
384
409
|
}
|
|
385
|
-
return
|
|
410
|
+
return CacheMongoose.#instance;
|
|
386
411
|
}
|
|
387
412
|
async clear(customKey) {
|
|
388
413
|
if (customKey == null) {
|
|
@@ -394,9 +419,6 @@ const _CacheMongoose = class _CacheMongoose {
|
|
|
394
419
|
async close() {
|
|
395
420
|
await this.cache.close();
|
|
396
421
|
}
|
|
397
|
-
}
|
|
398
|
-
_instance = new WeakMap();
|
|
399
|
-
__privateAdd(_CacheMongoose, _instance);
|
|
400
|
-
let CacheMongoose = _CacheMongoose;
|
|
422
|
+
}
|
|
401
423
|
|
|
402
424
|
module.exports = CacheMongoose;
|