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.
Files changed (76) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc +95 -0
  3. package/.swcrc +21 -0
  4. package/LICENSE +21 -0
  5. package/README.md +26 -0
  6. package/dist/cjs/cache/Cache.js +37 -0
  7. package/dist/cjs/cache/Cache.js.map +1 -0
  8. package/dist/cjs/cache/engine/MemoryCacheEngine.js +25 -0
  9. package/dist/cjs/cache/engine/MemoryCacheEngine.js.map +1 -0
  10. package/dist/cjs/cache/engine/RedisCacheEngine.js +28 -0
  11. package/dist/cjs/cache/engine/RedisCacheEngine.js.map +1 -0
  12. package/dist/cjs/crypto.js +28 -0
  13. package/dist/cjs/crypto.js.map +1 -0
  14. package/dist/cjs/interfaces/ICacheEngine.js +3 -0
  15. package/dist/cjs/interfaces/ICacheEngine.js.map +1 -0
  16. package/dist/cjs/interfaces/ICacheMongooseOptions.js +3 -0
  17. package/dist/cjs/interfaces/ICacheMongooseOptions.js.map +1 -0
  18. package/dist/cjs/plugin.js +82 -0
  19. package/dist/cjs/plugin.js.map +1 -0
  20. package/dist/cjs/types/cache/Cache.d.ts +12 -0
  21. package/dist/cjs/types/cache/Cache.d.ts.map +1 -0
  22. package/dist/cjs/types/cache/engine/MemoryCacheEngine.d.ts +11 -0
  23. package/dist/cjs/types/cache/engine/MemoryCacheEngine.d.ts.map +1 -0
  24. package/dist/cjs/types/cache/engine/RedisCacheEngine.d.ts +11 -0
  25. package/dist/cjs/types/cache/engine/RedisCacheEngine.d.ts.map +1 -0
  26. package/dist/cjs/types/crypto.d.ts +3 -0
  27. package/dist/cjs/types/crypto.d.ts.map +1 -0
  28. package/dist/cjs/types/interfaces/ICacheEngine.d.ts +8 -0
  29. package/dist/cjs/types/interfaces/ICacheEngine.d.ts.map +1 -0
  30. package/dist/cjs/types/interfaces/ICacheMongooseOptions.d.ts +10 -0
  31. package/dist/cjs/types/interfaces/ICacheMongooseOptions.d.ts.map +1 -0
  32. package/dist/cjs/types/plugin.d.ts +24 -0
  33. package/dist/cjs/types/plugin.d.ts.map +1 -0
  34. package/dist/esm/cache/Cache.js +33 -0
  35. package/dist/esm/cache/Cache.js.map +1 -0
  36. package/dist/esm/cache/engine/MemoryCacheEngine.js +23 -0
  37. package/dist/esm/cache/engine/MemoryCacheEngine.js.map +1 -0
  38. package/dist/esm/cache/engine/RedisCacheEngine.js +25 -0
  39. package/dist/esm/cache/engine/RedisCacheEngine.js.map +1 -0
  40. package/dist/esm/crypto.js +22 -0
  41. package/dist/esm/crypto.js.map +1 -0
  42. package/dist/esm/interfaces/ICacheEngine.js +2 -0
  43. package/dist/esm/interfaces/ICacheEngine.js.map +1 -0
  44. package/dist/esm/interfaces/ICacheMongooseOptions.js +2 -0
  45. package/dist/esm/interfaces/ICacheMongooseOptions.js.map +1 -0
  46. package/dist/esm/plugin.js.map +1 -0
  47. package/dist/esm/plugin.mjs +79 -0
  48. package/dist/esm/types/cache/Cache.d.ts +12 -0
  49. package/dist/esm/types/cache/Cache.d.ts.map +1 -0
  50. package/dist/esm/types/cache/engine/MemoryCacheEngine.d.ts +11 -0
  51. package/dist/esm/types/cache/engine/MemoryCacheEngine.d.ts.map +1 -0
  52. package/dist/esm/types/cache/engine/RedisCacheEngine.d.ts +11 -0
  53. package/dist/esm/types/cache/engine/RedisCacheEngine.d.ts.map +1 -0
  54. package/dist/esm/types/crypto.d.ts +3 -0
  55. package/dist/esm/types/crypto.d.ts.map +1 -0
  56. package/dist/esm/types/interfaces/ICacheEngine.d.ts +8 -0
  57. package/dist/esm/types/interfaces/ICacheEngine.d.ts.map +1 -0
  58. package/dist/esm/types/interfaces/ICacheMongooseOptions.d.ts +10 -0
  59. package/dist/esm/types/interfaces/ICacheMongooseOptions.d.ts.map +1 -0
  60. package/dist/esm/types/plugin.d.ts +24 -0
  61. package/dist/esm/types/plugin.d.ts.map +1 -0
  62. package/jest-mongodb-config.ts +10 -0
  63. package/jest.config.ts +33 -0
  64. package/package.json +112 -0
  65. package/src/cache/Cache.ts +46 -0
  66. package/src/cache/engine/MemoryCacheEngine.ts +32 -0
  67. package/src/cache/engine/RedisCacheEngine.ts +36 -0
  68. package/src/crypto.ts +27 -0
  69. package/src/interfaces/ICacheEngine.ts +8 -0
  70. package/src/interfaces/ICacheMongooseOptions.ts +10 -0
  71. package/src/plugin.ts +122 -0
  72. package/tests/cache.test.ts +51 -0
  73. package/tests/crypto.test.ts +60 -0
  74. package/tests/interfaces/IUser.ts +8 -0
  75. package/tests/schemas/UserSchema.ts +16 -0
  76. 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,8 @@
1
+ interface IUser {
2
+ name: string
3
+ role: string
4
+ createdAt?: Date
5
+ updatedAt?: Date
6
+ }
7
+
8
+ export default IUser
@@ -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
+ }