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/dist/nest/index.cjs
CHANGED
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
var common = require('@nestjs/common');
|
|
4
4
|
var mongoose = require('mongoose');
|
|
5
5
|
var index = require('../index.cjs');
|
|
6
|
+
require('node:v8');
|
|
6
7
|
require('bson');
|
|
7
8
|
require('ioredis');
|
|
8
9
|
require('node:crypto');
|
|
9
10
|
|
|
10
11
|
const CACHE_OPTIONS = Symbol("CACHE_OPTIONS");
|
|
11
12
|
class CacheService {
|
|
13
|
+
logger = new common.Logger(CacheService.name);
|
|
14
|
+
options;
|
|
15
|
+
cacheMongoose;
|
|
12
16
|
constructor(options) {
|
|
13
|
-
this.logger = new common.Logger(CacheService.name);
|
|
14
17
|
this.options = options;
|
|
15
18
|
}
|
|
16
19
|
get instance() {
|
package/dist/nest/index.mjs
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { Logger, Module } from '@nestjs/common';
|
|
2
2
|
import mongoose from 'mongoose';
|
|
3
3
|
import CacheMongoose from '../index.mjs';
|
|
4
|
+
import 'node:v8';
|
|
4
5
|
import 'bson';
|
|
5
6
|
import 'ioredis';
|
|
6
7
|
import 'node:crypto';
|
|
7
8
|
|
|
8
9
|
const CACHE_OPTIONS = Symbol("CACHE_OPTIONS");
|
|
9
10
|
class CacheService {
|
|
11
|
+
logger = new Logger(CacheService.name);
|
|
12
|
+
options;
|
|
13
|
+
cacheMongoose;
|
|
10
14
|
constructor(options) {
|
|
11
|
-
this.logger = new Logger(CacheService.name);
|
|
12
15
|
this.options = options;
|
|
13
16
|
}
|
|
14
17
|
get instance() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-cache-mongoose",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Cache plugin for mongoose Queries and Aggregate (in-memory, redis)",
|
|
5
5
|
"author": "ilovepixelart",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,9 +12,6 @@
|
|
|
12
12
|
"url": "https://github.com/ilovepixelart/ts-cache-mongoose/issues"
|
|
13
13
|
},
|
|
14
14
|
"homepage": "https://github.com/ilovepixelart/ts-cache-mongoose#readme",
|
|
15
|
-
"directories": {
|
|
16
|
-
"examples": "examples"
|
|
17
|
-
},
|
|
18
15
|
"keywords": [
|
|
19
16
|
"backend",
|
|
20
17
|
"mongo",
|
|
@@ -35,15 +32,10 @@
|
|
|
35
32
|
"aggregate"
|
|
36
33
|
],
|
|
37
34
|
"engines": {
|
|
38
|
-
"node": ">=
|
|
35
|
+
"node": ">=20"
|
|
39
36
|
},
|
|
40
37
|
"files": [
|
|
41
|
-
"dist"
|
|
42
|
-
"src",
|
|
43
|
-
"tests",
|
|
44
|
-
"tsconfig.json",
|
|
45
|
-
"vite.config.mts",
|
|
46
|
-
"biome.json"
|
|
38
|
+
"dist"
|
|
47
39
|
],
|
|
48
40
|
"type": "module",
|
|
49
41
|
"exports": {
|
|
@@ -78,37 +70,44 @@
|
|
|
78
70
|
"main": "./dist/index.cjs",
|
|
79
71
|
"module": "./dist/index.mjs",
|
|
80
72
|
"types": "./dist/index.d.cts",
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public",
|
|
75
|
+
"provenance": true
|
|
76
|
+
},
|
|
81
77
|
"scripts": {
|
|
82
|
-
"prepare": "simple-git-hooks",
|
|
83
78
|
"biome": "npx @biomejs/biome check",
|
|
84
79
|
"biome:fix": "npx @biomejs/biome check --write .",
|
|
85
80
|
"test": "vitest run --coverage",
|
|
86
81
|
"test:open": "vitest run --coverage && open-cli coverage/lcov-report/index.html",
|
|
87
82
|
"clean": "rm -rf ./dist",
|
|
88
83
|
"type:check": "tsc --noEmit",
|
|
84
|
+
"type:check:tests": "tsc --noEmit -p tests/tsconfig.json",
|
|
89
85
|
"build": "pkgroll --clean-dist",
|
|
90
|
-
"release": "npm install && npm run biome && npm run type:check && npm run build && np --no-publish"
|
|
86
|
+
"release": "npm install && npm run biome && npm run type:check && npm run type:check:tests && npm run build && np --no-publish"
|
|
91
87
|
},
|
|
92
88
|
"dependencies": {
|
|
93
89
|
"ioredis": "5.10.0"
|
|
94
90
|
},
|
|
95
91
|
"devDependencies": {
|
|
96
|
-
"@biomejs/biome": "2.4.
|
|
97
|
-
"@nestjs/common": "11.1.
|
|
98
|
-
"@
|
|
99
|
-
"@
|
|
92
|
+
"@biomejs/biome": "2.4.11",
|
|
93
|
+
"@nestjs/common": "11.1.18",
|
|
94
|
+
"@nestjs/core": "11.1.18",
|
|
95
|
+
"@nestjs/testing": "11.1.18",
|
|
96
|
+
"@types/node": "25.6.0",
|
|
97
|
+
"@vitest/coverage-v8": "4.1.4",
|
|
100
98
|
"bson": "7.2.0",
|
|
99
|
+
"fast-check": "4.6.0",
|
|
101
100
|
"mongodb-memory-server": "11.0.1",
|
|
102
|
-
"mongoose": "9.
|
|
103
|
-
"np": "11.0.
|
|
104
|
-
"open-cli": "
|
|
101
|
+
"mongoose": "9.4.1",
|
|
102
|
+
"np": "11.0.3",
|
|
103
|
+
"open-cli": "9.0.0",
|
|
105
104
|
"pkgroll": "2.27.0",
|
|
106
105
|
"simple-git-hooks": "2.13.1",
|
|
107
106
|
"typescript": "5.9.3",
|
|
108
|
-
"vitest": "4.1.
|
|
107
|
+
"vitest": "4.1.4"
|
|
109
108
|
},
|
|
110
109
|
"peerDependencies": {
|
|
111
|
-
"@nestjs/common": ">=9.0.0",
|
|
110
|
+
"@nestjs/common": ">=9.0.0 < 12",
|
|
112
111
|
"bson": ">=4.7.2 < 8",
|
|
113
112
|
"mongoose": ">=6.6.0 < 10"
|
|
114
113
|
},
|
|
@@ -125,7 +124,6 @@
|
|
|
125
124
|
"publish": false
|
|
126
125
|
},
|
|
127
126
|
"overrides": {
|
|
128
|
-
"tmp": "0.2.5"
|
|
129
|
-
"file-type": "21.3.2"
|
|
127
|
+
"tmp": "0.2.5"
|
|
130
128
|
}
|
|
131
129
|
}
|
package/biome.json
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/2.4.7/schema.json",
|
|
3
|
-
"vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
|
|
4
|
-
"files": {
|
|
5
|
-
"ignoreUnknown": false,
|
|
6
|
-
"includes": ["src/**/*.ts", "tests/**/*.ts"]
|
|
7
|
-
},
|
|
8
|
-
"formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 },
|
|
9
|
-
"assist": {
|
|
10
|
-
"actions": {
|
|
11
|
-
"source": {
|
|
12
|
-
"organizeImports": {
|
|
13
|
-
"level": "on",
|
|
14
|
-
"options": {
|
|
15
|
-
"groups": [
|
|
16
|
-
"vitest",
|
|
17
|
-
":BLANK_LINE:",
|
|
18
|
-
":NODE:",
|
|
19
|
-
{ "type": false },
|
|
20
|
-
":BLANK_LINE:"
|
|
21
|
-
]
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"linter": {
|
|
28
|
-
"enabled": true,
|
|
29
|
-
"rules": {
|
|
30
|
-
"recommended": true
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"javascript": {
|
|
34
|
-
"formatter": {
|
|
35
|
-
"trailingCommas": "all",
|
|
36
|
-
"quoteStyle": "single",
|
|
37
|
-
"semicolons": "asNeeded",
|
|
38
|
-
"lineWidth": 320
|
|
39
|
-
},
|
|
40
|
-
"globals": ["Atomics", "SharedArrayBuffer"]
|
|
41
|
-
},
|
|
42
|
-
"json": {
|
|
43
|
-
"formatter": {
|
|
44
|
-
"trailingCommas": "none"
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
package/src/cache/Cache.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { ms } from '../ms'
|
|
2
|
-
import { MemoryCacheEngine } from './engine/MemoryCacheEngine'
|
|
3
|
-
import { RedisCacheEngine } from './engine/RedisCacheEngine'
|
|
4
|
-
|
|
5
|
-
import type { CacheData, CacheEngine, CacheOptions, CacheTTL } from '../types'
|
|
6
|
-
|
|
7
|
-
export class Cache {
|
|
8
|
-
readonly #engine!: CacheEngine
|
|
9
|
-
readonly #defaultTTL: number
|
|
10
|
-
readonly #debug: boolean
|
|
11
|
-
readonly #engines = ['memory', 'redis'] as const
|
|
12
|
-
|
|
13
|
-
constructor(cacheOptions: CacheOptions) {
|
|
14
|
-
if (!this.#engines.includes(cacheOptions.engine)) {
|
|
15
|
-
throw new Error(`Invalid engine name: ${cacheOptions.engine}`)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (cacheOptions.engine === 'redis' && !cacheOptions.engineOptions) {
|
|
19
|
-
throw new Error(`Engine options are required for ${cacheOptions.engine} engine`)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
cacheOptions.defaultTTL ??= '1 minute'
|
|
23
|
-
|
|
24
|
-
this.#defaultTTL = typeof cacheOptions.defaultTTL === 'string' ? ms(cacheOptions.defaultTTL) : cacheOptions.defaultTTL
|
|
25
|
-
|
|
26
|
-
if (cacheOptions.engine === 'redis' && cacheOptions.engineOptions) {
|
|
27
|
-
this.#engine = new RedisCacheEngine(cacheOptions.engineOptions)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (cacheOptions.engine === 'memory') {
|
|
31
|
-
this.#engine = new MemoryCacheEngine()
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
this.#debug = cacheOptions.debug === true
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async get(key: string): Promise<CacheData> {
|
|
38
|
-
const cacheEntry = await this.#engine.get(key)
|
|
39
|
-
if (this.#debug) {
|
|
40
|
-
const cacheHit = cacheEntry == null ? 'MISS' : 'HIT'
|
|
41
|
-
console.log(`[ts-cache-mongoose] GET '${key}' - ${cacheHit}`)
|
|
42
|
-
}
|
|
43
|
-
return cacheEntry
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async set(key: string, value: CacheData, ttl: CacheTTL | null): Promise<void> {
|
|
47
|
-
const givenTTL = typeof ttl === 'string' ? ms(ttl) : ttl
|
|
48
|
-
const actualTTL = givenTTL ?? this.#defaultTTL
|
|
49
|
-
await this.#engine.set(key, value, actualTTL)
|
|
50
|
-
if (this.#debug) {
|
|
51
|
-
console.log(`[ts-cache-mongoose] SET '${key}' - ttl: ${actualTTL.toFixed(0)} ms`)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async del(key: string): Promise<void> {
|
|
56
|
-
await this.#engine.del(key)
|
|
57
|
-
if (this.#debug) {
|
|
58
|
-
console.log(`[ts-cache-mongoose] DEL '${key}'`)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async clear(): Promise<void> {
|
|
63
|
-
await this.#engine.clear()
|
|
64
|
-
if (this.#debug) {
|
|
65
|
-
console.log('[ts-cache-mongoose] CLEAR')
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async close(): Promise<void> {
|
|
70
|
-
return this.#engine.close()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { ms } from '../../ms'
|
|
2
|
-
|
|
3
|
-
import type { CacheData, CacheEngine, CacheTTL } from '../../types'
|
|
4
|
-
|
|
5
|
-
export class MemoryCacheEngine implements CacheEngine {
|
|
6
|
-
readonly #cache: Map<string, { value: CacheData; expiresAt: number } | undefined>
|
|
7
|
-
|
|
8
|
-
constructor() {
|
|
9
|
-
this.#cache = new Map()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
get(key: string): CacheData {
|
|
13
|
-
const item = this.#cache.get(key)
|
|
14
|
-
if (!item || item.expiresAt < Date.now()) {
|
|
15
|
-
this.del(key)
|
|
16
|
-
return undefined
|
|
17
|
-
}
|
|
18
|
-
return item.value
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
set(key: string, value: CacheData, ttl?: CacheTTL): void {
|
|
22
|
-
const givenTTL = typeof ttl === 'string' ? ms(ttl) : ttl
|
|
23
|
-
const actualTTL = givenTTL ?? Number.POSITIVE_INFINITY
|
|
24
|
-
this.#cache.set(key, {
|
|
25
|
-
value,
|
|
26
|
-
expiresAt: Date.now() + actualTTL,
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
del(key: string): void {
|
|
31
|
-
this.#cache.delete(key)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
clear(): void {
|
|
35
|
-
this.#cache.clear()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
close(): void {
|
|
39
|
-
// do nothing
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { EJSON } from 'bson'
|
|
2
|
-
import IORedis from 'ioredis'
|
|
3
|
-
import { ms } from '../../ms'
|
|
4
|
-
import { convertToObject } from '../../version'
|
|
5
|
-
|
|
6
|
-
import type { Redis, RedisOptions } from 'ioredis'
|
|
7
|
-
import type { CacheData, CacheEngine, CacheTTL } from '../../types'
|
|
8
|
-
|
|
9
|
-
export class RedisCacheEngine implements CacheEngine {
|
|
10
|
-
readonly #client: Redis
|
|
11
|
-
|
|
12
|
-
constructor(options: RedisOptions) {
|
|
13
|
-
options.keyPrefix ??= 'cache-mongoose:'
|
|
14
|
-
this.#client = new IORedis(options)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async get(key: string): Promise<CacheData> {
|
|
18
|
-
try {
|
|
19
|
-
const value = await this.#client.get(key)
|
|
20
|
-
if (value === null) {
|
|
21
|
-
return undefined
|
|
22
|
-
}
|
|
23
|
-
return EJSON.parse(value) as CacheData
|
|
24
|
-
} catch (err) {
|
|
25
|
-
console.error(err)
|
|
26
|
-
return undefined
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async set(key: string, value: CacheData, ttl?: CacheTTL): Promise<void> {
|
|
31
|
-
try {
|
|
32
|
-
const givenTTL = typeof ttl === 'string' ? ms(ttl) : ttl
|
|
33
|
-
const actualTTL = givenTTL ?? Number.POSITIVE_INFINITY
|
|
34
|
-
const serializedValue = EJSON.stringify(convertToObject(value))
|
|
35
|
-
await this.#client.setex(key, Math.ceil(actualTTL / 1000), serializedValue)
|
|
36
|
-
} catch (err) {
|
|
37
|
-
console.error(err)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async del(key: string): Promise<void> {
|
|
42
|
-
await this.#client.del(key)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async clear(): Promise<void> {
|
|
46
|
-
await this.#client.flushdb()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async close(): Promise<void> {
|
|
50
|
-
await this.#client.quit()
|
|
51
|
-
}
|
|
52
|
-
}
|
package/src/extend/aggregate.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { getKey } from '../key'
|
|
2
|
-
|
|
3
|
-
import type { Mongoose } from 'mongoose'
|
|
4
|
-
import type { Cache } from '../cache/Cache'
|
|
5
|
-
import type { CacheTTL } from '../types'
|
|
6
|
-
|
|
7
|
-
export function extendAggregate(mongoose: Mongoose, cache: Cache): void {
|
|
8
|
-
const mongooseExec = mongoose.Aggregate.prototype.exec
|
|
9
|
-
|
|
10
|
-
mongoose.Aggregate.prototype.getCacheKey = function (): string {
|
|
11
|
-
if (this._key != null) return this._key
|
|
12
|
-
|
|
13
|
-
return getKey({
|
|
14
|
-
pipeline: this.pipeline(),
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
mongoose.Aggregate.prototype.getCacheTTL = function (): CacheTTL | null {
|
|
19
|
-
return this._ttl
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
mongoose.Aggregate.prototype.cache = function (ttl?: CacheTTL, customKey?: string) {
|
|
23
|
-
this._ttl = ttl ?? null
|
|
24
|
-
this._key = customKey ?? null
|
|
25
|
-
return this
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
mongoose.Aggregate.prototype.exec = async function (...args: []) {
|
|
29
|
-
// biome-ignore lint/suspicious/noPrototypeBuiltins: to support node 16
|
|
30
|
-
if (!Object.prototype.hasOwnProperty.call(this, '_ttl')) {
|
|
31
|
-
return mongooseExec.apply(this, args)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const key = this.getCacheKey()
|
|
35
|
-
const ttl = this.getCacheTTL()
|
|
36
|
-
|
|
37
|
-
const resultCache = await cache.get(key).catch((err: unknown) => {
|
|
38
|
-
console.error(err)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
if (resultCache) {
|
|
42
|
-
return resultCache
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const result = (await mongooseExec.call(this)) as Record<string, unknown>[] | Record<string, unknown>
|
|
46
|
-
await cache.set(key, result, ttl).catch((err: unknown) => {
|
|
47
|
-
console.error(err)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
return result
|
|
51
|
-
}
|
|
52
|
-
}
|
package/src/extend/query.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { getKey } from '../key'
|
|
2
|
-
|
|
3
|
-
import type { Mongoose } from 'mongoose'
|
|
4
|
-
import type { Cache } from '../cache/Cache'
|
|
5
|
-
import type { CacheTTL } from '../types'
|
|
6
|
-
|
|
7
|
-
export function extendQuery(mongoose: Mongoose, cache: Cache): void {
|
|
8
|
-
const mongooseExec = mongoose.Query.prototype.exec
|
|
9
|
-
|
|
10
|
-
mongoose.Query.prototype.getCacheKey = function (): string {
|
|
11
|
-
if (this._key != null) return this._key
|
|
12
|
-
|
|
13
|
-
const filter = this.getFilter()
|
|
14
|
-
const update = this.getUpdate()
|
|
15
|
-
const options = this.getOptions()
|
|
16
|
-
const mongooseOptions = this.mongooseOptions()
|
|
17
|
-
|
|
18
|
-
return getKey({
|
|
19
|
-
model: this.model.modelName,
|
|
20
|
-
op: this.op,
|
|
21
|
-
filter,
|
|
22
|
-
update,
|
|
23
|
-
options,
|
|
24
|
-
mongooseOptions,
|
|
25
|
-
_path: this._path,
|
|
26
|
-
_fields: this._fields,
|
|
27
|
-
_distinct: this._distinct,
|
|
28
|
-
_conditions: this._conditions,
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
mongoose.Query.prototype.getCacheTTL = function (): CacheTTL | null {
|
|
33
|
-
return this._ttl
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
mongoose.Query.prototype.cache = function (ttl?: CacheTTL, customKey?: string) {
|
|
37
|
-
this._ttl = ttl ?? null
|
|
38
|
-
this._key = customKey ?? null
|
|
39
|
-
return this
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
mongoose.Query.prototype.exec = async function (...args: []) {
|
|
43
|
-
// biome-ignore lint/suspicious/noPrototypeBuiltins: to support node 16
|
|
44
|
-
if (!Object.prototype.hasOwnProperty.call(this, '_ttl')) {
|
|
45
|
-
return mongooseExec.apply(this, args)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const key = this.getCacheKey()
|
|
49
|
-
const ttl = this.getCacheTTL()
|
|
50
|
-
const mongooseOptions = this.mongooseOptions()
|
|
51
|
-
|
|
52
|
-
const isCount = this.op?.includes('count') ?? false
|
|
53
|
-
const isDistinct = this.op === 'distinct'
|
|
54
|
-
const model = this.model.modelName
|
|
55
|
-
|
|
56
|
-
const resultCache = await cache.get(key).catch((err: unknown) => {
|
|
57
|
-
console.error(err)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
if (resultCache) {
|
|
61
|
-
if (isCount || isDistinct || mongooseOptions.lean) {
|
|
62
|
-
return resultCache
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const modelConstructor = mongoose.model<unknown>(model)
|
|
66
|
-
|
|
67
|
-
if (Array.isArray(resultCache)) {
|
|
68
|
-
return resultCache.map((item) => {
|
|
69
|
-
return modelConstructor.hydrate(item)
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
return modelConstructor.hydrate(resultCache)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const result = (await mongooseExec.call(this)) as Record<string, unknown>[] | Record<string, unknown>
|
|
76
|
-
await cache.set(key, result, ttl).catch((err: unknown) => {
|
|
77
|
-
console.error(err)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
return result
|
|
81
|
-
}
|
|
82
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { Cache } from './cache/Cache'
|
|
2
|
-
import { extendAggregate } from './extend/aggregate'
|
|
3
|
-
import { extendQuery } from './extend/query'
|
|
4
|
-
|
|
5
|
-
import type { Mongoose } from 'mongoose'
|
|
6
|
-
import type { CacheOptions, CacheTTL } from './types'
|
|
7
|
-
|
|
8
|
-
export * from './types'
|
|
9
|
-
|
|
10
|
-
declare module 'mongoose' {
|
|
11
|
-
interface Query<ResultType, DocType, THelpers, RawDocType> {
|
|
12
|
-
cache: (this: Query<ResultType, DocType, THelpers, RawDocType>, ttl?: CacheTTL, customKey?: string) => this
|
|
13
|
-
_key: string | null
|
|
14
|
-
getCacheKey: (this: Query<ResultType, DocType, THelpers, RawDocType>) => string
|
|
15
|
-
_ttl: CacheTTL | null
|
|
16
|
-
getCacheTTL: (this: Query<ResultType, DocType, THelpers, RawDocType>) => CacheTTL | null
|
|
17
|
-
op?: string
|
|
18
|
-
_path?: unknown
|
|
19
|
-
_fields?: unknown
|
|
20
|
-
_distinct?: unknown
|
|
21
|
-
_conditions?: unknown
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface Aggregate<ResultType> {
|
|
25
|
-
cache: (this: Aggregate<ResultType>, ttl?: CacheTTL, customKey?: string) => this
|
|
26
|
-
_key: string | null
|
|
27
|
-
getCacheKey: (this: Aggregate<ResultType>) => string
|
|
28
|
-
_ttl: CacheTTL | null
|
|
29
|
-
getCacheTTL: (this: Aggregate<ResultType>) => CacheTTL | null
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class CacheMongoose {
|
|
34
|
-
static #instance: CacheMongoose | undefined
|
|
35
|
-
private cache!: Cache
|
|
36
|
-
|
|
37
|
-
private constructor() {
|
|
38
|
-
// Private constructor to prevent external instantiation
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
public static init(mongoose: Mongoose, cacheOptions: CacheOptions): CacheMongoose {
|
|
42
|
-
if (!CacheMongoose.#instance) {
|
|
43
|
-
CacheMongoose.#instance = new CacheMongoose()
|
|
44
|
-
CacheMongoose.#instance.cache = new Cache(cacheOptions)
|
|
45
|
-
|
|
46
|
-
const cache = CacheMongoose.#instance.cache
|
|
47
|
-
|
|
48
|
-
extendQuery(mongoose, cache)
|
|
49
|
-
extendAggregate(mongoose, cache)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return CacheMongoose.#instance
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
public async clear(customKey?: string): Promise<void> {
|
|
56
|
-
if (customKey == null) {
|
|
57
|
-
await this.cache.clear()
|
|
58
|
-
} else {
|
|
59
|
-
await this.cache.del(customKey)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public async close(): Promise<void> {
|
|
64
|
-
await this.cache.close()
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export default CacheMongoose
|
package/src/key.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
import { sortKeys } from './sort-keys'
|
|
3
|
-
|
|
4
|
-
export function getKey(data: Record<string, unknown>[] | Record<string, unknown>): string {
|
|
5
|
-
const sortedObj = sortKeys(data)
|
|
6
|
-
const sortedStr = JSON.stringify(sortedObj, (_, val: unknown) => {
|
|
7
|
-
return val instanceof RegExp ? String(val) : val
|
|
8
|
-
})
|
|
9
|
-
return createHash('sha1').update(sortedStr).digest('hex')
|
|
10
|
-
}
|
package/src/ms.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const s = 1000
|
|
2
|
-
const m = s * 60
|
|
3
|
-
const h = m * 60
|
|
4
|
-
const d = h * 24
|
|
5
|
-
const w = d * 7
|
|
6
|
-
const y = d * 365.25
|
|
7
|
-
|
|
8
|
-
// NOSONAR — regex from ms package, intentionally covers all time unit aliases
|
|
9
|
-
const RE = /^(-?(?:\d+)?\.?\d+)\s*(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i
|
|
10
|
-
|
|
11
|
-
const UNITS: Record<string, number> = {
|
|
12
|
-
years: y,
|
|
13
|
-
year: y,
|
|
14
|
-
yrs: y,
|
|
15
|
-
yr: y,
|
|
16
|
-
y,
|
|
17
|
-
weeks: w,
|
|
18
|
-
week: w,
|
|
19
|
-
w,
|
|
20
|
-
days: d,
|
|
21
|
-
day: d,
|
|
22
|
-
d,
|
|
23
|
-
hours: h,
|
|
24
|
-
hour: h,
|
|
25
|
-
hrs: h,
|
|
26
|
-
hr: h,
|
|
27
|
-
h,
|
|
28
|
-
minutes: m,
|
|
29
|
-
minute: m,
|
|
30
|
-
mins: m,
|
|
31
|
-
min: m,
|
|
32
|
-
m,
|
|
33
|
-
seconds: s,
|
|
34
|
-
second: s,
|
|
35
|
-
secs: s,
|
|
36
|
-
sec: s,
|
|
37
|
-
s,
|
|
38
|
-
milliseconds: 1,
|
|
39
|
-
millisecond: 1,
|
|
40
|
-
msecs: 1,
|
|
41
|
-
msec: 1,
|
|
42
|
-
ms: 1,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const ms = (val: string): number => {
|
|
46
|
-
const str = String(val)
|
|
47
|
-
if (str.length > 100) return 0
|
|
48
|
-
|
|
49
|
-
const match = RE.exec(str)
|
|
50
|
-
if (!match) return 0
|
|
51
|
-
|
|
52
|
-
const n = Number.parseFloat(match[1] ?? '')
|
|
53
|
-
const type = (match[2] ?? 'ms').toLowerCase()
|
|
54
|
-
return n * (UNITS[type] ?? 0)
|
|
55
|
-
}
|