ts-cache-mongoose 0.0.1
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/.eslintignore +4 -0
- package/.eslintrc +95 -0
- package/.swcrc +21 -0
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/cjs/cache/Cache.js +37 -0
- package/dist/cjs/cache/Cache.js.map +1 -0
- package/dist/cjs/cache/engine/MemoryCacheEngine.js +25 -0
- package/dist/cjs/cache/engine/MemoryCacheEngine.js.map +1 -0
- package/dist/cjs/cache/engine/RedisCacheEngine.js +28 -0
- package/dist/cjs/cache/engine/RedisCacheEngine.js.map +1 -0
- package/dist/cjs/crypto.js +28 -0
- package/dist/cjs/crypto.js.map +1 -0
- package/dist/cjs/interfaces/ICacheEngine.js +3 -0
- package/dist/cjs/interfaces/ICacheEngine.js.map +1 -0
- package/dist/cjs/interfaces/ICacheMongooseOptions.js +3 -0
- package/dist/cjs/interfaces/ICacheMongooseOptions.js.map +1 -0
- package/dist/cjs/plugin.js +82 -0
- package/dist/cjs/plugin.js.map +1 -0
- package/dist/cjs/types/cache/Cache.d.ts +12 -0
- package/dist/cjs/types/cache/Cache.d.ts.map +1 -0
- package/dist/cjs/types/cache/engine/MemoryCacheEngine.d.ts +11 -0
- package/dist/cjs/types/cache/engine/MemoryCacheEngine.d.ts.map +1 -0
- package/dist/cjs/types/cache/engine/RedisCacheEngine.d.ts +11 -0
- package/dist/cjs/types/cache/engine/RedisCacheEngine.d.ts.map +1 -0
- package/dist/cjs/types/crypto.d.ts +3 -0
- package/dist/cjs/types/crypto.d.ts.map +1 -0
- package/dist/cjs/types/interfaces/ICacheEngine.d.ts +8 -0
- package/dist/cjs/types/interfaces/ICacheEngine.d.ts.map +1 -0
- package/dist/cjs/types/interfaces/ICacheMongooseOptions.d.ts +10 -0
- package/dist/cjs/types/interfaces/ICacheMongooseOptions.d.ts.map +1 -0
- package/dist/cjs/types/plugin.d.ts +24 -0
- package/dist/cjs/types/plugin.d.ts.map +1 -0
- package/dist/esm/cache/Cache.js +33 -0
- package/dist/esm/cache/Cache.js.map +1 -0
- package/dist/esm/cache/engine/MemoryCacheEngine.js +23 -0
- package/dist/esm/cache/engine/MemoryCacheEngine.js.map +1 -0
- package/dist/esm/cache/engine/RedisCacheEngine.js +25 -0
- package/dist/esm/cache/engine/RedisCacheEngine.js.map +1 -0
- package/dist/esm/crypto.js +22 -0
- package/dist/esm/crypto.js.map +1 -0
- package/dist/esm/interfaces/ICacheEngine.js +2 -0
- package/dist/esm/interfaces/ICacheEngine.js.map +1 -0
- package/dist/esm/interfaces/ICacheMongooseOptions.js +2 -0
- package/dist/esm/interfaces/ICacheMongooseOptions.js.map +1 -0
- package/dist/esm/plugin.js.map +1 -0
- package/dist/esm/plugin.mjs +79 -0
- package/dist/esm/types/cache/Cache.d.ts +12 -0
- package/dist/esm/types/cache/Cache.d.ts.map +1 -0
- package/dist/esm/types/cache/engine/MemoryCacheEngine.d.ts +11 -0
- package/dist/esm/types/cache/engine/MemoryCacheEngine.d.ts.map +1 -0
- package/dist/esm/types/cache/engine/RedisCacheEngine.d.ts +11 -0
- package/dist/esm/types/cache/engine/RedisCacheEngine.d.ts.map +1 -0
- package/dist/esm/types/crypto.d.ts +3 -0
- package/dist/esm/types/crypto.d.ts.map +1 -0
- package/dist/esm/types/interfaces/ICacheEngine.d.ts +8 -0
- package/dist/esm/types/interfaces/ICacheEngine.d.ts.map +1 -0
- package/dist/esm/types/interfaces/ICacheMongooseOptions.d.ts +10 -0
- package/dist/esm/types/interfaces/ICacheMongooseOptions.d.ts.map +1 -0
- package/dist/esm/types/plugin.d.ts +24 -0
- package/dist/esm/types/plugin.d.ts.map +1 -0
- package/jest-mongodb-config.ts +10 -0
- package/jest.config.ts +33 -0
- package/package.json +112 -0
- package/src/cache/Cache.ts +46 -0
- package/src/cache/engine/MemoryCacheEngine.ts +32 -0
- package/src/cache/engine/RedisCacheEngine.ts +36 -0
- package/src/crypto.ts +27 -0
- package/src/interfaces/ICacheEngine.ts +8 -0
- package/src/interfaces/ICacheMongooseOptions.ts +10 -0
- package/src/plugin.ts +122 -0
- package/tests/cache.test.ts +51 -0
- package/tests/crypto.test.ts +60 -0
- package/tests/interfaces/IUser.ts +8 -0
- package/tests/schemas/UserSchema.ts +16 -0
- package/tsconfig.json +44 -0
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import ms from 'ms'
|
|
3
|
+
import Cache from './cache/Cache'
|
|
4
|
+
|
|
5
|
+
import type { Mongoose } from 'mongoose'
|
|
6
|
+
import type ICacheMongooseOptions from './interfaces/ICacheMongooseOptions'
|
|
7
|
+
import { getKey } from './crypto'
|
|
8
|
+
|
|
9
|
+
declare module 'mongoose' {
|
|
10
|
+
interface Query<ResultType, DocType, THelpers, RawDocType> {
|
|
11
|
+
cache: (this: Query<ResultType, DocType, THelpers, RawDocType>, ttl?: string) => this
|
|
12
|
+
_key?: string
|
|
13
|
+
getCacheKey: (this: Query<ResultType, DocType, THelpers, RawDocType>) => string
|
|
14
|
+
_ttl: number
|
|
15
|
+
getCacheTTL: (this: Query<ResultType, DocType, THelpers, RawDocType>) => number
|
|
16
|
+
op?: string
|
|
17
|
+
_fields?: unknown
|
|
18
|
+
_path?: unknown
|
|
19
|
+
_distinct?: unknown
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class CacheMongoose {
|
|
24
|
+
// eslint-disable-next-line no-use-before-define
|
|
25
|
+
private static instance: CacheMongoose | undefined
|
|
26
|
+
private cache!: Cache
|
|
27
|
+
|
|
28
|
+
private constructor () {
|
|
29
|
+
// Private constructor to prevent external instantiation
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
33
|
+
public static init (mongoose: Mongoose, cacheMongooseOptions: ICacheMongooseOptions): CacheMongoose {
|
|
34
|
+
if (typeof mongoose.Model.hydrate !== 'function') throw new Error('Cache is only compatible with versions of mongoose that implement the `model.hydrate` method')
|
|
35
|
+
if (!this.instance) {
|
|
36
|
+
this.instance = new CacheMongoose()
|
|
37
|
+
this.instance.cache = new Cache(cacheMongooseOptions.engine, cacheMongooseOptions.defaultTTL = '1 minute')
|
|
38
|
+
|
|
39
|
+
const cache = this.instance.cache
|
|
40
|
+
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
42
|
+
const mongooseExec = mongoose.Query.prototype.exec
|
|
43
|
+
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
45
|
+
mongoose.Query.prototype.getCacheKey = function () {
|
|
46
|
+
if (this._key) return this._key
|
|
47
|
+
|
|
48
|
+
const filter = this.getFilter()
|
|
49
|
+
const update = this.getUpdate()
|
|
50
|
+
const options = this.getOptions()
|
|
51
|
+
|
|
52
|
+
const data: Record<string, unknown> = {
|
|
53
|
+
model: this.model.modelName,
|
|
54
|
+
op: this.op,
|
|
55
|
+
filter,
|
|
56
|
+
update,
|
|
57
|
+
skip: options.skip,
|
|
58
|
+
limit: options.limit,
|
|
59
|
+
sort: options.sort,
|
|
60
|
+
_fields: this._fields,
|
|
61
|
+
_path: this._path,
|
|
62
|
+
_distinct: this._distinct
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return getKey(data)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
69
|
+
mongoose.Query.prototype.getCacheTTL = function () {
|
|
70
|
+
return this._ttl
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
74
|
+
mongoose.Query.prototype.cache = function (ttl?: string, customKey?: string) {
|
|
75
|
+
this._ttl = ms(ttl ?? '1 minute')
|
|
76
|
+
this._key = customKey
|
|
77
|
+
return this
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
81
|
+
mongoose.Query.prototype.exec = async function () {
|
|
82
|
+
if (!_.has(this, '_ttl')) {
|
|
83
|
+
return mongooseExec.apply(this)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const key = this.getCacheKey()
|
|
87
|
+
const ttl = this.getCacheTTL()
|
|
88
|
+
|
|
89
|
+
const model = this.model.modelName
|
|
90
|
+
|
|
91
|
+
const resultCache = await cache.get(key)
|
|
92
|
+
if (resultCache) {
|
|
93
|
+
const constructor = mongoose.model(model)
|
|
94
|
+
if (_.isArray(resultCache)) {
|
|
95
|
+
return resultCache.map((item) => constructor.hydrate(item) as Record<string, unknown>)
|
|
96
|
+
} else {
|
|
97
|
+
return constructor.hydrate(resultCache) as Record<string, unknown>
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const result = await mongooseExec.call(this) as Record<string, unknown>[] | Record<string, unknown>
|
|
102
|
+
cache.set(key, result, ttl).catch((err) => {
|
|
103
|
+
console.error(err)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return result
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return this.instance
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public async clearCache (customKey: string): Promise<void> {
|
|
114
|
+
if (!customKey) {
|
|
115
|
+
await this.cache.clear()
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
await this.cache.del(customKey)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default CacheMongoose
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import mongoose, { model } from 'mongoose'
|
|
2
|
+
import CacheMongoose from '../src/plugin'
|
|
3
|
+
|
|
4
|
+
import type ICacheOptions from '../src/interfaces/ICacheMongooseOptions'
|
|
5
|
+
|
|
6
|
+
import UserSchema from './schemas/UserSchema'
|
|
7
|
+
|
|
8
|
+
const cacheOptions: ICacheOptions = {
|
|
9
|
+
engine: 'memory'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('CacheMongoose', () => {
|
|
13
|
+
const uri = `${globalThis.__MONGO_URI__}${globalThis.__MONGO_DB_NAME__}`
|
|
14
|
+
|
|
15
|
+
const User = model('User', UserSchema)
|
|
16
|
+
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
await mongoose.connect(uri)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterAll(async () => {
|
|
22
|
+
await mongoose.connection.close()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
await mongoose.connection.collection('users').deleteMany({})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should be defined', async () => {
|
|
30
|
+
const cache = CacheMongoose.init(mongoose, cacheOptions)
|
|
31
|
+
expect(cache).toBeDefined()
|
|
32
|
+
|
|
33
|
+
const user = await User.create({
|
|
34
|
+
name: 'John Doe',
|
|
35
|
+
role: 'admin'
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const cachedUser1 = await User.findById(user._id).cache().lean()
|
|
39
|
+
console.log(cachedUser1)
|
|
40
|
+
|
|
41
|
+
await User.updateOne({ _id: user._id }, { name: 'John Doe 2' })
|
|
42
|
+
|
|
43
|
+
const cachedUser2 = await User.findById(user._id).cache()
|
|
44
|
+
console.log(cachedUser2)
|
|
45
|
+
|
|
46
|
+
expect(cachedUser1).not.toBeNull()
|
|
47
|
+
expect(cachedUser2).not.toBeNull()
|
|
48
|
+
expect(cachedUser1?._id).toEqual(cachedUser2?._id)
|
|
49
|
+
expect(cachedUser1?.name).toEqual(cachedUser2?.name)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { getKey } from '../src/crypto'
|
|
2
|
+
|
|
3
|
+
describe('generateHash()', () => {
|
|
4
|
+
const data1 = {
|
|
5
|
+
foo: 42,
|
|
6
|
+
bar: {
|
|
7
|
+
baz: [3, 2, 1],
|
|
8
|
+
qux: 'hello',
|
|
9
|
+
wow: {
|
|
10
|
+
word: 'world',
|
|
11
|
+
hey: {
|
|
12
|
+
waldo: true,
|
|
13
|
+
fred: null,
|
|
14
|
+
missing: undefined
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const data2 = {
|
|
21
|
+
bar: {
|
|
22
|
+
baz: [1, 2, 3],
|
|
23
|
+
wow: {
|
|
24
|
+
hey: {
|
|
25
|
+
fred: null,
|
|
26
|
+
missing: undefined,
|
|
27
|
+
waldo: true
|
|
28
|
+
},
|
|
29
|
+
word: 'world'
|
|
30
|
+
},
|
|
31
|
+
qux: 'hello'
|
|
32
|
+
},
|
|
33
|
+
foo: 42
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const data3 = {
|
|
37
|
+
bar: {
|
|
38
|
+
qux: 'hello',
|
|
39
|
+
baz: [3, 2, 1],
|
|
40
|
+
wow: {
|
|
41
|
+
hey: {
|
|
42
|
+
fred: null,
|
|
43
|
+
waldo: true,
|
|
44
|
+
missing: undefined
|
|
45
|
+
},
|
|
46
|
+
word: 'world'
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
foo: 42
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
it('should generate hash keys for objects with different key orders', () => {
|
|
53
|
+
const hash1 = getKey(data1)
|
|
54
|
+
const hash2 = getKey(data2)
|
|
55
|
+
const hash3 = getKey(data3)
|
|
56
|
+
|
|
57
|
+
expect(hash1).not.toEqual(hash2)
|
|
58
|
+
expect(hash1).toEqual(hash3)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Schema } from 'mongoose'
|
|
2
|
+
|
|
3
|
+
import type IUser from '../interfaces/IUser'
|
|
4
|
+
|
|
5
|
+
const UserSchema = new Schema<IUser>({
|
|
6
|
+
name: {
|
|
7
|
+
type: String,
|
|
8
|
+
required: true
|
|
9
|
+
},
|
|
10
|
+
role: {
|
|
11
|
+
type: String,
|
|
12
|
+
required: true
|
|
13
|
+
}
|
|
14
|
+
}, { timestamps: true })
|
|
15
|
+
|
|
16
|
+
export default UserSchema
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2021",
|
|
4
|
+
"lib": [
|
|
5
|
+
"es2021"
|
|
6
|
+
],
|
|
7
|
+
"module": "commonjs",
|
|
8
|
+
"moduleResolution": "node",
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"strictBindCallApply": true,
|
|
12
|
+
"strictFunctionTypes": true,
|
|
13
|
+
"strictNullChecks": true,
|
|
14
|
+
"strictPropertyInitialization": true,
|
|
15
|
+
"noEmitOnError": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"noImplicitAny": true,
|
|
18
|
+
"noImplicitOverride": true,
|
|
19
|
+
"noImplicitUseStrict": false,
|
|
20
|
+
"noImplicitReturns": true,
|
|
21
|
+
"noImplicitThis": true,
|
|
22
|
+
"noUnusedLocals": true,
|
|
23
|
+
"noUnusedParameters": true,
|
|
24
|
+
"allowSyntheticDefaultImports": true,
|
|
25
|
+
"declaration": true,
|
|
26
|
+
"declarationMap": true,
|
|
27
|
+
"sourceMap": true,
|
|
28
|
+
"forceConsistentCasingInFileNames": true,
|
|
29
|
+
"esModuleInterop": true,
|
|
30
|
+
"importHelpers": true,
|
|
31
|
+
"removeComments": true
|
|
32
|
+
},
|
|
33
|
+
"include": [
|
|
34
|
+
"src/**/*"
|
|
35
|
+
],
|
|
36
|
+
"exclude": [
|
|
37
|
+
"dist",
|
|
38
|
+
"tools",
|
|
39
|
+
"node_modules"
|
|
40
|
+
],
|
|
41
|
+
"ts-node": {
|
|
42
|
+
"swc": true
|
|
43
|
+
}
|
|
44
|
+
}
|