ts-cache-mongoose 2.1.0 → 2.2.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/README.md +54 -1
- package/dist/index.cjs +73 -17
- package/dist/index.d.cts +4 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +4 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +73 -17
- package/dist/nest/index.cjs +1 -0
- package/dist/nest/index.mjs +1 -0
- package/package.json +20 -26
- 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 -51
- package/src/extend/query.ts +0 -81
- package/src/index.ts +0 -68
- package/src/key.ts +0 -10
- package/src/ms.ts +0 -66
- 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 -21
- 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 -113
- package/tests/nest.test.ts +0 -158
- package/tests/sort-keys.test.ts +0 -80
- package/tsconfig.json +0 -33
- package/vite.config.mts +0 -23
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, Duration } 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 = ms(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: Duration | null): Promise<void> {
|
|
47
|
-
const givenTTL = ttl == null ? null : ms(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, Duration } 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?: Duration): void {
|
|
22
|
-
const givenTTL = ttl == null ? undefined : ms(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, Duration } 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?: Duration): Promise<void> {
|
|
31
|
-
try {
|
|
32
|
-
const givenTTL = ttl == null ? undefined : ms(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,51 +0,0 @@
|
|
|
1
|
-
import { getKey } from '../key'
|
|
2
|
-
|
|
3
|
-
import type { Mongoose } from 'mongoose'
|
|
4
|
-
import type { Cache } from '../cache/Cache'
|
|
5
|
-
import type { Duration } 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.getDuration = function (): Duration | null {
|
|
19
|
-
return this._ttl
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
mongoose.Aggregate.prototype.cache = function (ttl?: Duration, 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
|
-
if (!Object.hasOwn(this, '_ttl')) {
|
|
30
|
-
return mongooseExec.apply(this, args)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const key = this.getCacheKey()
|
|
34
|
-
const ttl = this.getDuration()
|
|
35
|
-
|
|
36
|
-
const resultCache = await cache.get(key).catch((err: unknown) => {
|
|
37
|
-
console.error(err)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
if (resultCache) {
|
|
41
|
-
return resultCache
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const result = (await mongooseExec.call(this)) as Record<string, unknown>[] | Record<string, unknown>
|
|
45
|
-
await cache.set(key, result, ttl).catch((err: unknown) => {
|
|
46
|
-
console.error(err)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
return result
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/extend/query.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { getKey } from '../key'
|
|
2
|
-
|
|
3
|
-
import type { Mongoose } from 'mongoose'
|
|
4
|
-
import type { Cache } from '../cache/Cache'
|
|
5
|
-
import type { Duration } 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.getDuration = function (): Duration | null {
|
|
33
|
-
return this._ttl
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
mongoose.Query.prototype.cache = function (ttl?: Duration, 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
|
-
if (!Object.hasOwn(this, '_ttl')) {
|
|
44
|
-
return mongooseExec.apply(this, args)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const key = this.getCacheKey()
|
|
48
|
-
const ttl = this.getDuration()
|
|
49
|
-
const mongooseOptions = this.mongooseOptions()
|
|
50
|
-
|
|
51
|
-
const isCount = this.op?.includes('count') ?? false
|
|
52
|
-
const isDistinct = this.op === 'distinct'
|
|
53
|
-
const model = this.model.modelName
|
|
54
|
-
|
|
55
|
-
const resultCache = await cache.get(key).catch((err: unknown) => {
|
|
56
|
-
console.error(err)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
if (resultCache) {
|
|
60
|
-
if (isCount || isDistinct || mongooseOptions.lean) {
|
|
61
|
-
return resultCache
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const modelConstructor = mongoose.model<unknown>(model)
|
|
65
|
-
|
|
66
|
-
if (Array.isArray(resultCache)) {
|
|
67
|
-
return resultCache.map((item) => {
|
|
68
|
-
return modelConstructor.hydrate(item)
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
return modelConstructor.hydrate(resultCache)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const result = (await mongooseExec.call(this)) as Record<string, unknown>[] | Record<string, unknown>
|
|
75
|
-
await cache.set(key, result, ttl).catch((err: unknown) => {
|
|
76
|
-
console.error(err)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
return result
|
|
80
|
-
}
|
|
81
|
-
}
|
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, Duration } 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?: Duration, customKey?: string) => this
|
|
13
|
-
_key: string | null
|
|
14
|
-
getCacheKey: (this: Query<ResultType, DocType, THelpers, RawDocType>) => string
|
|
15
|
-
_ttl: Duration | null
|
|
16
|
-
getDuration: (this: Query<ResultType, DocType, THelpers, RawDocType>) => Duration | 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?: Duration, customKey?: string) => this
|
|
26
|
-
_key: string | null
|
|
27
|
-
getCacheKey: (this: Aggregate<ResultType>) => string
|
|
28
|
-
_ttl: Duration | null
|
|
29
|
-
getDuration: (this: Aggregate<ResultType>) => Duration | 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,66 +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
|
-
const mo = y / 12
|
|
8
|
-
|
|
9
|
-
export const UNITS = {
|
|
10
|
-
milliseconds: 1,
|
|
11
|
-
millisecond: 1,
|
|
12
|
-
msecs: 1,
|
|
13
|
-
msec: 1,
|
|
14
|
-
ms: 1,
|
|
15
|
-
seconds: s,
|
|
16
|
-
second: s,
|
|
17
|
-
secs: s,
|
|
18
|
-
sec: s,
|
|
19
|
-
s,
|
|
20
|
-
minutes: m,
|
|
21
|
-
minute: m,
|
|
22
|
-
mins: m,
|
|
23
|
-
min: m,
|
|
24
|
-
m,
|
|
25
|
-
hours: h,
|
|
26
|
-
hour: h,
|
|
27
|
-
hrs: h,
|
|
28
|
-
hr: h,
|
|
29
|
-
h,
|
|
30
|
-
days: d,
|
|
31
|
-
day: d,
|
|
32
|
-
d,
|
|
33
|
-
weeks: w,
|
|
34
|
-
week: w,
|
|
35
|
-
w,
|
|
36
|
-
months: mo,
|
|
37
|
-
month: mo,
|
|
38
|
-
mo,
|
|
39
|
-
years: y,
|
|
40
|
-
year: y,
|
|
41
|
-
yrs: y,
|
|
42
|
-
yr: y,
|
|
43
|
-
y,
|
|
44
|
-
} as const satisfies Record<string, number>
|
|
45
|
-
|
|
46
|
-
export type Unit = keyof typeof UNITS
|
|
47
|
-
|
|
48
|
-
export type Duration = number | `${number}` | `${number}${Unit}` | `${number} ${Unit}`
|
|
49
|
-
|
|
50
|
-
const unitPattern = Object.keys(UNITS)
|
|
51
|
-
.sort((a, b) => b.length - a.length)
|
|
52
|
-
.join('|')
|
|
53
|
-
|
|
54
|
-
const RE = new RegExp(String.raw`^(-?(?:\d+)?\.?\d+)\s*(${unitPattern})?$`, 'i')
|
|
55
|
-
|
|
56
|
-
export const ms = (val: Duration): number => {
|
|
57
|
-
const str = String(val)
|
|
58
|
-
if (str.length > 100) return Number.NaN
|
|
59
|
-
|
|
60
|
-
const match = RE.exec(str)
|
|
61
|
-
if (!match) return Number.NaN
|
|
62
|
-
|
|
63
|
-
const n = Number.parseFloat(match[1] ?? '')
|
|
64
|
-
const type = (match[2] ?? 'ms').toLowerCase()
|
|
65
|
-
return n * (UNITS[type as Unit] ?? 0)
|
|
66
|
-
}
|
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,21 +0,0 @@
|
|
|
1
|
-
import type { RedisOptions } from 'ioredis'
|
|
2
|
-
import type { Duration } from './ms'
|
|
3
|
-
|
|
4
|
-
export type { Duration } from './ms'
|
|
5
|
-
|
|
6
|
-
export type CacheData = Record<string, unknown> | Record<string, unknown>[] | unknown[] | number | undefined
|
|
7
|
-
|
|
8
|
-
export type CacheOptions = {
|
|
9
|
-
engine: 'memory' | 'redis'
|
|
10
|
-
engineOptions?: RedisOptions
|
|
11
|
-
defaultTTL?: Duration
|
|
12
|
-
debug?: boolean
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface CacheEngine {
|
|
16
|
-
get: (key: string) => Promise<CacheData> | CacheData
|
|
17
|
-
set: (key: string, value: CacheData, ttl?: Duration) => Promise<void> | void
|
|
18
|
-
del: (key: string) => Promise<void> | void
|
|
19
|
-
clear: () => Promise<void> | void
|
|
20
|
-
close: () => Promise<void> | void
|
|
21
|
-
}
|
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
|
-
}
|