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/src/nest/cache.module.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/** biome-ignore-all lint/complexity/noStaticOnlyClass: nest */
|
|
2
|
-
import { Module } from '@nestjs/common'
|
|
3
|
-
import { CACHE_OPTIONS, CacheService } from './cache.service'
|
|
4
|
-
|
|
5
|
-
import type { DynamicModule, Provider } from '@nestjs/common'
|
|
6
|
-
import type { CacheModuleAsyncOptions, CacheModuleOptions, CacheOptionsFactory } from './interfaces'
|
|
7
|
-
|
|
8
|
-
@Module({})
|
|
9
|
-
export class CacheModule {
|
|
10
|
-
static forRoot(options: CacheModuleOptions & { isGlobal?: boolean }): DynamicModule {
|
|
11
|
-
return {
|
|
12
|
-
module: CacheModule,
|
|
13
|
-
global: options.isGlobal ?? false,
|
|
14
|
-
providers: [
|
|
15
|
-
{ provide: CACHE_OPTIONS, useValue: options },
|
|
16
|
-
{
|
|
17
|
-
provide: CacheService,
|
|
18
|
-
useFactory: (opts: CacheModuleOptions) => new CacheService(opts),
|
|
19
|
-
inject: [CACHE_OPTIONS],
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
exports: [CacheService],
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
static forRootAsync(options: CacheModuleAsyncOptions): DynamicModule {
|
|
27
|
-
const asyncProviders = CacheModule.createAsyncProviders(options)
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
module: CacheModule,
|
|
31
|
-
global: options.isGlobal ?? false,
|
|
32
|
-
imports: options.imports ?? [],
|
|
33
|
-
providers: [
|
|
34
|
-
...asyncProviders,
|
|
35
|
-
{
|
|
36
|
-
provide: CacheService,
|
|
37
|
-
useFactory: (opts: CacheModuleOptions) => new CacheService(opts),
|
|
38
|
-
inject: [CACHE_OPTIONS],
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
exports: [CacheService],
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private static createAsyncProviders(options: CacheModuleAsyncOptions): Provider[] {
|
|
46
|
-
if (options.useFactory) {
|
|
47
|
-
return [
|
|
48
|
-
{
|
|
49
|
-
provide: CACHE_OPTIONS,
|
|
50
|
-
useFactory: options.useFactory,
|
|
51
|
-
inject: options.inject ?? [],
|
|
52
|
-
},
|
|
53
|
-
]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (options.useClass) {
|
|
57
|
-
return [
|
|
58
|
-
{ provide: options.useClass, useClass: options.useClass },
|
|
59
|
-
{
|
|
60
|
-
provide: CACHE_OPTIONS,
|
|
61
|
-
useFactory: (factory: CacheOptionsFactory) => factory.createCacheOptions(),
|
|
62
|
-
inject: [options.useClass],
|
|
63
|
-
},
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (options.useExisting) {
|
|
68
|
-
return [
|
|
69
|
-
{
|
|
70
|
-
provide: CACHE_OPTIONS,
|
|
71
|
-
useFactory: (factory: CacheOptionsFactory) => factory.createCacheOptions(),
|
|
72
|
-
inject: [options.useExisting],
|
|
73
|
-
},
|
|
74
|
-
]
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return []
|
|
78
|
-
}
|
|
79
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Logger } from '@nestjs/common'
|
|
2
|
-
import mongoose from 'mongoose'
|
|
3
|
-
import CacheMongoose from '../index'
|
|
4
|
-
|
|
5
|
-
import type { OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common'
|
|
6
|
-
import type { CacheModuleOptions } from './interfaces'
|
|
7
|
-
|
|
8
|
-
export const CACHE_OPTIONS = Symbol('CACHE_OPTIONS')
|
|
9
|
-
|
|
10
|
-
export class CacheService implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
11
|
-
private readonly logger = new Logger(CacheService.name)
|
|
12
|
-
private readonly options: CacheModuleOptions
|
|
13
|
-
private cacheMongoose!: CacheMongoose
|
|
14
|
-
|
|
15
|
-
constructor(options: CacheModuleOptions) {
|
|
16
|
-
this.options = options
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
get instance(): CacheMongoose {
|
|
20
|
-
return this.cacheMongoose
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async onApplicationBootstrap(): Promise<void> {
|
|
24
|
-
this.cacheMongoose = CacheMongoose.init(mongoose, this.options)
|
|
25
|
-
this.logger.log(`Cache initialized with ${this.options.engine} engine`)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async onApplicationShutdown(): Promise<void> {
|
|
29
|
-
if (this.cacheMongoose) {
|
|
30
|
-
await this.cacheMongoose.close()
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async clear(customKey?: string): Promise<void> {
|
|
35
|
-
await this.cacheMongoose.clear(customKey)
|
|
36
|
-
}
|
|
37
|
-
}
|
package/src/nest/index.ts
DELETED
package/src/nest/interfaces.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { InjectionToken, ModuleMetadata, OptionalFactoryDependency, Type } from '@nestjs/common'
|
|
2
|
-
import type { CacheOptions } from '../types'
|
|
3
|
-
|
|
4
|
-
export type CacheModuleOptions = CacheOptions
|
|
5
|
-
|
|
6
|
-
export interface CacheOptionsFactory {
|
|
7
|
-
createCacheOptions(): CacheModuleOptions | Promise<CacheModuleOptions>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface CacheModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
11
|
-
isGlobal?: boolean
|
|
12
|
-
inject?: (InjectionToken | OptionalFactoryDependency)[]
|
|
13
|
-
useClass?: Type<CacheOptionsFactory>
|
|
14
|
-
useExisting?: Type<CacheOptionsFactory>
|
|
15
|
-
// biome-ignore lint/suspicious/noExplicitAny: NestJS convention for factory args
|
|
16
|
-
useFactory?: (...args: any[]) => CacheModuleOptions | Promise<CacheModuleOptions>
|
|
17
|
-
}
|
package/src/sort-keys.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
2
|
-
if (typeof value !== 'object' || value === null) return false
|
|
3
|
-
const proto = Object.getPrototypeOf(value) as unknown
|
|
4
|
-
return proto === Object.prototype || proto === null
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export const sortKeys = (input: Record<string, unknown> | Record<string, unknown>[]): Record<string, unknown> | Record<string, unknown>[] => {
|
|
8
|
-
const seen = new WeakSet<object>()
|
|
9
|
-
|
|
10
|
-
const sortObject = (obj: Record<string, unknown>): Record<string, unknown> => {
|
|
11
|
-
if (seen.has(obj)) return obj
|
|
12
|
-
seen.add(obj)
|
|
13
|
-
|
|
14
|
-
const sorted: Record<string, unknown> = {}
|
|
15
|
-
for (const key of Object.keys(obj).sort((a, b) => a.localeCompare(b))) {
|
|
16
|
-
const value = obj[key]
|
|
17
|
-
if (Array.isArray(value)) {
|
|
18
|
-
sorted[key] = sortArray(value)
|
|
19
|
-
} else if (isPlainObject(value)) {
|
|
20
|
-
sorted[key] = sortObject(value)
|
|
21
|
-
} else {
|
|
22
|
-
sorted[key] = value
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return sorted
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const sortArray = (arr: unknown[]): unknown[] => {
|
|
29
|
-
return arr.map((item) => {
|
|
30
|
-
if (Array.isArray(item)) return sortArray(item)
|
|
31
|
-
if (isPlainObject(item)) return sortObject(item)
|
|
32
|
-
return item
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (Array.isArray(input)) return sortArray(input) as Record<string, unknown>[]
|
|
37
|
-
return sortObject(input)
|
|
38
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { RedisOptions } from 'ioredis'
|
|
2
|
-
|
|
3
|
-
export type CacheTTL = number | string
|
|
4
|
-
|
|
5
|
-
export type CacheData = Record<string, unknown> | Record<string, unknown>[] | unknown[] | number | undefined
|
|
6
|
-
|
|
7
|
-
export type CacheOptions = {
|
|
8
|
-
engine: 'memory' | 'redis'
|
|
9
|
-
engineOptions?: RedisOptions
|
|
10
|
-
defaultTTL?: CacheTTL
|
|
11
|
-
debug?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface CacheEngine {
|
|
15
|
-
get: (key: string) => Promise<CacheData> | CacheData
|
|
16
|
-
set: (key: string, value: CacheData, ttl?: CacheTTL) => Promise<void> | void
|
|
17
|
-
del: (key: string) => Promise<void> | void
|
|
18
|
-
clear: () => Promise<void> | void
|
|
19
|
-
close: () => Promise<void> | void
|
|
20
|
-
}
|
package/src/version.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import mongoose from 'mongoose'
|
|
2
|
-
|
|
3
|
-
import type { CacheData } from './types'
|
|
4
|
-
|
|
5
|
-
export const isMongooseLessThan7 = Number.parseInt(mongoose.version, 10) < 7
|
|
6
|
-
|
|
7
|
-
export const convertToObject = <T>(value: (T & { toObject?: () => CacheData }) | undefined): CacheData => {
|
|
8
|
-
if (isMongooseLessThan7) {
|
|
9
|
-
if (value != null && typeof value === 'object' && !Array.isArray(value) && value.toObject) {
|
|
10
|
-
return value.toObject()
|
|
11
|
-
}
|
|
12
|
-
if (Array.isArray(value)) {
|
|
13
|
-
return value.map((doc) => convertToObject(doc))
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return value
|
|
18
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import mongoose from 'mongoose'
|
|
4
|
-
import CacheMongoose from '../src/index'
|
|
5
|
-
import { UserModel } from './models/User'
|
|
6
|
-
import { server } from './mongo/server'
|
|
7
|
-
|
|
8
|
-
describe('cache-debug', async () => {
|
|
9
|
-
const instance = server('cache-debug')
|
|
10
|
-
let cache: CacheMongoose
|
|
11
|
-
|
|
12
|
-
beforeAll(async () => {
|
|
13
|
-
cache = CacheMongoose.init(mongoose, {
|
|
14
|
-
engine: 'memory',
|
|
15
|
-
debug: true,
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
await instance.create()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
afterAll(async () => {
|
|
22
|
-
await cache.clear()
|
|
23
|
-
await cache.close()
|
|
24
|
-
await instance.destroy()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
beforeEach(async () => {
|
|
28
|
-
vi.spyOn(global.console, 'log')
|
|
29
|
-
await mongoose.connection.collection('users').deleteMany({})
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
afterEach(async () => {
|
|
33
|
-
vi.restoreAllMocks()
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
describe('debug scenarios', () => {
|
|
37
|
-
it('should create a use and and query it two time first is cache miss second is hit, also clear by key and global', async () => {
|
|
38
|
-
const user = await UserModel.create({
|
|
39
|
-
name: 'John Doe',
|
|
40
|
-
role: 'admin',
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
const key = 'key'
|
|
44
|
-
const ttl = '1 second'
|
|
45
|
-
const cacheMissRegExp = /\[ts-cache-mongoose\] GET '.*?' - MISS/
|
|
46
|
-
const cacheHitRegExp = /\[ts-cache-mongoose\] GET '.*?' - HIT/
|
|
47
|
-
const cacheSetRegExp = /\[ts-cache-mongoose\] SET '.*?' - ttl: \d+ ms/
|
|
48
|
-
const cacheClearRegExp = /\[ts-cache-mongoose\] CLEAR/
|
|
49
|
-
const cacheDelRegExp = /\[ts-cache-mongoose\] DEL '.*?'/
|
|
50
|
-
|
|
51
|
-
const userCacheMiss = await UserModel.findById(user._id).cache(ttl, key).exec()
|
|
52
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheMissRegExp))
|
|
53
|
-
expect(userCacheMiss).not.toBeNull()
|
|
54
|
-
expect(userCacheMiss?._id.toString()).toBe(user._id.toString())
|
|
55
|
-
expect(userCacheMiss?.name).toEqual(user.name)
|
|
56
|
-
expect(userCacheMiss?.role).toEqual(user.role)
|
|
57
|
-
|
|
58
|
-
const userCacheHit = await UserModel.findById(user._id).cache(ttl, key).exec()
|
|
59
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheSetRegExp))
|
|
60
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheHitRegExp))
|
|
61
|
-
expect(userCacheHit).not.toBeNull()
|
|
62
|
-
expect(userCacheHit?._id.toString()).toBe(user._id.toString())
|
|
63
|
-
expect(userCacheHit?.name).toEqual(user.name)
|
|
64
|
-
expect(userCacheHit?.role).toEqual(user.role)
|
|
65
|
-
|
|
66
|
-
await cache.clear(key)
|
|
67
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheDelRegExp))
|
|
68
|
-
|
|
69
|
-
await cache.clear()
|
|
70
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheClearRegExp))
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
})
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import mongoose from 'mongoose'
|
|
4
|
-
import CacheMongoose from '../src/index'
|
|
5
|
-
import { UserModel } from './models/User'
|
|
6
|
-
import { server } from './mongo/server'
|
|
7
|
-
|
|
8
|
-
describe('cache-memory', async () => {
|
|
9
|
-
const instance = server('cache-memory')
|
|
10
|
-
let cache: CacheMongoose
|
|
11
|
-
|
|
12
|
-
beforeAll(async () => {
|
|
13
|
-
cache = CacheMongoose.init(mongoose, {
|
|
14
|
-
engine: 'memory',
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
await instance.create()
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
afterAll(async () => {
|
|
21
|
-
await cache.clear()
|
|
22
|
-
await cache.close()
|
|
23
|
-
await instance.destroy()
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
beforeEach(async () => {
|
|
27
|
-
await mongoose.connection.collection('users').deleteMany({})
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
describe('memory scenarios', () => {
|
|
31
|
-
it('should use memory cache', async () => {
|
|
32
|
-
const user = await UserModel.create({
|
|
33
|
-
name: 'John Doe',
|
|
34
|
-
role: 'admin',
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const user1 = await UserModel.findById(user._id).cache().exec()
|
|
38
|
-
await UserModel.findOneAndUpdate({ _id: user._id }, { name: 'John Doe 2' }).exec()
|
|
39
|
-
const user2 = await UserModel.findById(user._id).cache().exec()
|
|
40
|
-
|
|
41
|
-
expect(user1).not.toBeNull()
|
|
42
|
-
expect(user2).not.toBeNull()
|
|
43
|
-
expect(user1?._id.toString()).toBe(user2?._id.toString())
|
|
44
|
-
expect(user1?.name).toEqual(user2?.name)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should not use cache', async () => {
|
|
48
|
-
const user = await UserModel.create({
|
|
49
|
-
name: 'John Doe',
|
|
50
|
-
role: 'admin',
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const cache1 = await UserModel.findById(user._id).cache().exec()
|
|
54
|
-
await UserModel.findByIdAndUpdate(user._id, { name: 'John Doe 2' }).exec()
|
|
55
|
-
await cache.clear()
|
|
56
|
-
const cache2 = await UserModel.findById(user._id).cache().exec()
|
|
57
|
-
|
|
58
|
-
expect(cache1).not.toBeNull()
|
|
59
|
-
expect(cache2).not.toBeNull()
|
|
60
|
-
expect(cache1?._id.toString()).toBe(cache2?._id.toString())
|
|
61
|
-
expect(cache1?.name).not.toEqual(cache2?.name)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('should use memory cache with custom key', async () => {
|
|
65
|
-
const user = await UserModel.create({
|
|
66
|
-
name: 'John Doe',
|
|
67
|
-
role: 'admin',
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
const cache1 = await UserModel.findById(user._id).cache('1 minute', 'test-custom-key').exec()
|
|
71
|
-
await UserModel.findOneAndUpdate({ _id: user._id }, { name: 'John Doe 2' }).exec()
|
|
72
|
-
const cache2 = await UserModel.findById(user._id).cache('1 minute', 'test-custom-key').exec()
|
|
73
|
-
|
|
74
|
-
expect(cache1).not.toBeNull()
|
|
75
|
-
expect(cache2).not.toBeNull()
|
|
76
|
-
expect(cache1?._id.toString()).toBe(cache2?._id.toString())
|
|
77
|
-
expect(cache1?.name).toEqual(cache2?.name)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('should use memory cache and clear custom key', async () => {
|
|
81
|
-
const user = await UserModel.create({
|
|
82
|
-
name: 'John Doe',
|
|
83
|
-
role: 'admin',
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const cache1 = await UserModel.findById(user._id).cache('1 minute', 'test-custom-key-second').exec()
|
|
87
|
-
await UserModel.updateOne({ _id: user._id }, { name: 'John Doe 2' }).exec()
|
|
88
|
-
await cache.clear('test-custom-key-second')
|
|
89
|
-
const cache2 = await UserModel.findById(user._id).cache('1 minute', 'test-custom-key-second').exec()
|
|
90
|
-
|
|
91
|
-
expect(cache1).not.toBeNull()
|
|
92
|
-
expect(cache2).not.toBeNull()
|
|
93
|
-
expect(cache1?._id.toString()).toBe(cache2?._id.toString())
|
|
94
|
-
expect(cache1?.name).not.toEqual(cache2?.name)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('should use memory cache and custom key with an empty string', async () => {
|
|
98
|
-
const user = await UserModel.create({
|
|
99
|
-
name: 'John Doe',
|
|
100
|
-
role: 'admin',
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
const cache1 = await UserModel.findById(user._id).cache('1 minute', '').exec()
|
|
104
|
-
await UserModel.updateOne({ _id: user._id }, { name: 'John Doe 2' }).exec()
|
|
105
|
-
const cache2 = await UserModel.findById(user._id).cache('1 minute', '').exec()
|
|
106
|
-
|
|
107
|
-
expect(cache1).not.toBeNull()
|
|
108
|
-
expect(cache2).not.toBeNull()
|
|
109
|
-
expect(cache1?._id.toString()).toBe(cache2?._id.toString())
|
|
110
|
-
expect(cache1?.name).toEqual(cache2?.name)
|
|
111
|
-
|
|
112
|
-
await cache.clear('')
|
|
113
|
-
const cache3 = await UserModel.findById(user._id).cache('1 minute', '').exec()
|
|
114
|
-
expect(cache3).not.toBeNull()
|
|
115
|
-
expect(cache2?._id.toString()).toBe(cache3?._id.toString())
|
|
116
|
-
expect(cache2?.name).not.toEqual(cache3?.name)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('should use memory cache and aggregate', async () => {
|
|
120
|
-
await UserModel.create([
|
|
121
|
-
{ name: 'John', role: 'admin' },
|
|
122
|
-
{ name: 'Bob', role: 'admin' },
|
|
123
|
-
{ name: 'Alice', role: 'user' },
|
|
124
|
-
])
|
|
125
|
-
|
|
126
|
-
const cache1 = await UserModel.aggregate([{ $match: { role: 'admin' } }, { $group: { _id: '$role', count: { $sum: 1 } } }])
|
|
127
|
-
.cache()
|
|
128
|
-
.exec()
|
|
129
|
-
|
|
130
|
-
await UserModel.create({ name: 'Mark', role: 'admin' })
|
|
131
|
-
|
|
132
|
-
const cache2 = await UserModel.aggregate([{ $match: { role: 'admin' } }, { $group: { _id: '$role', count: { $sum: 1 } } }])
|
|
133
|
-
.cache()
|
|
134
|
-
.exec()
|
|
135
|
-
|
|
136
|
-
expect(cache1).not.toBeNull()
|
|
137
|
-
expect(cache2).not.toBeNull()
|
|
138
|
-
expect(cache1?.[0].count).toEqual(cache2?.[0].count)
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
describe('memory scenarios with ttl', () => {
|
|
143
|
-
const users = [
|
|
144
|
-
{ name: 'John', age: 30, role: 'admin' },
|
|
145
|
-
{ name: 'Alice', age: 25, role: 'user' },
|
|
146
|
-
{ name: 'Bob', age: 35, role: 'user' },
|
|
147
|
-
]
|
|
148
|
-
|
|
149
|
-
beforeEach(async () => {
|
|
150
|
-
// Delete all users before each test
|
|
151
|
-
await UserModel.deleteMany().exec()
|
|
152
|
-
|
|
153
|
-
// Create new users
|
|
154
|
-
await UserModel.create(users)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('findById', async () => {
|
|
158
|
-
const john = await UserModel.create({ name: 'John', age: 30, role: 'admin' })
|
|
159
|
-
const user = await UserModel.findById(john._id).cache('1 minute').exec()
|
|
160
|
-
const cachedUser = await UserModel.findById(john._id).cache('1 minute').exec()
|
|
161
|
-
|
|
162
|
-
expect(user?._id.toString()).toBe(cachedUser?._id.toString())
|
|
163
|
-
expect(user?.name).toEqual(cachedUser?.name)
|
|
164
|
-
expect(user?.createdAt).toEqual(cachedUser?.createdAt)
|
|
165
|
-
expect(user?.updatedAt).toEqual(cachedUser?.updatedAt)
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('findOne', async () => {
|
|
169
|
-
const user = await UserModel.findOne({ name: 'John', age: 30, role: 'admin' }).cache('1 minute').exec()
|
|
170
|
-
await UserModel.create({ name: 'Steve', age: 30, role: 'admin' })
|
|
171
|
-
const cachedUser = await UserModel.findOne({
|
|
172
|
-
name: 'John',
|
|
173
|
-
age: 30,
|
|
174
|
-
role: 'admin',
|
|
175
|
-
})
|
|
176
|
-
.cache('1 minute')
|
|
177
|
-
.exec()
|
|
178
|
-
|
|
179
|
-
expect(user?._id.toString()).toBe(cachedUser?._id.toString())
|
|
180
|
-
expect(user?.name).toEqual(cachedUser?.name)
|
|
181
|
-
expect(user?.createdAt).toEqual(cachedUser?.createdAt)
|
|
182
|
-
expect(user?.updatedAt).toEqual(cachedUser?.updatedAt)
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('find', async () => {
|
|
186
|
-
const users = await UserModel.find({ age: { $gte: 30 } })
|
|
187
|
-
.cache('1 minute')
|
|
188
|
-
.exec()
|
|
189
|
-
await UserModel.create({ name: 'Steve', age: 30, role: 'admin' })
|
|
190
|
-
const cachedUsers = await UserModel.find({ age: { $gte: 30 } })
|
|
191
|
-
.cache('1 minute')
|
|
192
|
-
.exec()
|
|
193
|
-
|
|
194
|
-
expect(users).toHaveLength(cachedUsers.length)
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('count', async () => {
|
|
198
|
-
const count = await UserModel.countDocuments({ age: { $gte: 30 } })
|
|
199
|
-
.cache('1 minute')
|
|
200
|
-
.exec()
|
|
201
|
-
await UserModel.create({ name: 'Steve', age: 30, role: 'admin' })
|
|
202
|
-
const cachedCount = await UserModel.countDocuments({ age: { $gte: 30 } })
|
|
203
|
-
.cache('1 minute')
|
|
204
|
-
.exec()
|
|
205
|
-
|
|
206
|
-
expect(count).toEqual(cachedCount)
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
it('distinct', async () => {
|
|
210
|
-
const emails = await UserModel.distinct('name').cache('1 minute').exec()
|
|
211
|
-
await UserModel.create({ name: 'Steve', age: 30, role: 'admin' })
|
|
212
|
-
const cachedEmails = await UserModel.distinct('name').cache('1 minute').exec()
|
|
213
|
-
|
|
214
|
-
expect(emails).toEqual(cachedEmails)
|
|
215
|
-
})
|
|
216
|
-
})
|
|
217
|
-
})
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { Cache } from '../src/cache/Cache'
|
|
4
|
-
|
|
5
|
-
import type { CacheOptions } from '../src/types'
|
|
6
|
-
|
|
7
|
-
describe('Cache class tests', () => {
|
|
8
|
-
it('should create a new instance of Cache', () => {
|
|
9
|
-
const cacheOptions: CacheOptions = {
|
|
10
|
-
engine: 'memory',
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const cache = new Cache(cacheOptions)
|
|
14
|
-
expect(cache).toBeInstanceOf(Cache)
|
|
15
|
-
expect(cache).toHaveProperty('get')
|
|
16
|
-
expect(cache).toHaveProperty('set')
|
|
17
|
-
expect(cache).toHaveProperty('del')
|
|
18
|
-
expect(cache).toHaveProperty('clear')
|
|
19
|
-
expect(cache).toHaveProperty('close')
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('should throw an error if the cache engine is not supported', () => {
|
|
23
|
-
const cacheOptions: CacheOptions = {
|
|
24
|
-
// @ts-expect-error Testing invalid engine name
|
|
25
|
-
engine: 'not-supported',
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
expect(() => new Cache(cacheOptions)).toThrow(`Invalid engine name: ${cacheOptions.engine}`)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should throw an error if the cache engine is redis and no engine options are provided', () => {
|
|
32
|
-
const cacheOptions: CacheOptions = {
|
|
33
|
-
engine: 'redis',
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
expect(() => new Cache(cacheOptions)).toThrow(`Engine options are required for ${cacheOptions.engine} engine`)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('should create a new instance of Cache with redis engine', async () => {
|
|
40
|
-
const cacheOptions: CacheOptions = {
|
|
41
|
-
engine: 'redis',
|
|
42
|
-
engineOptions: {
|
|
43
|
-
host: 'localhost',
|
|
44
|
-
port: 6379,
|
|
45
|
-
},
|
|
46
|
-
defaultTTL: '10 minutes',
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const cache = new Cache(cacheOptions)
|
|
50
|
-
expect(cache).toBeInstanceOf(Cache)
|
|
51
|
-
expect(cache).toHaveProperty('get')
|
|
52
|
-
expect(cache).toHaveProperty('set')
|
|
53
|
-
expect(cache).toHaveProperty('del')
|
|
54
|
-
expect(cache).toHaveProperty('clear')
|
|
55
|
-
expect(cache).toHaveProperty('close')
|
|
56
|
-
|
|
57
|
-
await cache.set('bob', { test: 'bob' }, null)
|
|
58
|
-
await cache.set('john', { test: 'john' }, '1 minute')
|
|
59
|
-
|
|
60
|
-
const value = await cache.get('bob')
|
|
61
|
-
expect(value).toEqual({ test: 'bob' })
|
|
62
|
-
|
|
63
|
-
await cache.del('bob')
|
|
64
|
-
const clearBob = await cache.get('bob')
|
|
65
|
-
expect(clearBob).toBeUndefined()
|
|
66
|
-
|
|
67
|
-
const john = await cache.get('john')
|
|
68
|
-
expect(john).toEqual({ test: 'john' })
|
|
69
|
-
|
|
70
|
-
await cache.clear()
|
|
71
|
-
const clearJohn = await cache.get('john')
|
|
72
|
-
expect(clearJohn).toBeUndefined()
|
|
73
|
-
|
|
74
|
-
const mockSet = vi.fn()
|
|
75
|
-
cache.set = mockSet
|
|
76
|
-
|
|
77
|
-
await cache.set('bob', { test: 'bob' }, null)
|
|
78
|
-
expect(mockSet).toHaveBeenCalled()
|
|
79
|
-
expect(mockSet).toHaveBeenCalledWith('bob', { test: 'bob' }, null)
|
|
80
|
-
|
|
81
|
-
await cache.close()
|
|
82
|
-
})
|
|
83
|
-
})
|