ts-cache-mongoose 1.7.7 → 2.0.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.
@@ -0,0 +1,108 @@
1
+ import { Logger, Module } from '@nestjs/common';
2
+ import mongoose from 'mongoose';
3
+ import CacheMongoose from '../index.mjs';
4
+ import 'bson';
5
+ import 'ioredis';
6
+ import 'node:crypto';
7
+
8
+ const CACHE_OPTIONS = Symbol("CACHE_OPTIONS");
9
+ class CacheService {
10
+ constructor(options) {
11
+ this.logger = new Logger(CacheService.name);
12
+ this.options = options;
13
+ }
14
+ get instance() {
15
+ return this.cacheMongoose;
16
+ }
17
+ async onApplicationBootstrap() {
18
+ this.cacheMongoose = CacheMongoose.init(mongoose, this.options);
19
+ this.logger.log(`Cache initialized with ${this.options.engine} engine`);
20
+ }
21
+ async onApplicationShutdown() {
22
+ if (this.cacheMongoose) {
23
+ await this.cacheMongoose.close();
24
+ }
25
+ }
26
+ async clear(customKey) {
27
+ await this.cacheMongoose.clear(customKey);
28
+ }
29
+ }
30
+
31
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
32
+ var __decorateClass = (decorators, target, key, kind) => {
33
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
34
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
35
+ if (decorator = decorators[i])
36
+ result = (decorator(result)) || result;
37
+ return result;
38
+ };
39
+ let CacheModule = class {
40
+ static forRoot(options) {
41
+ return {
42
+ module: CacheModule,
43
+ global: options.isGlobal ?? false,
44
+ providers: [
45
+ { provide: CACHE_OPTIONS, useValue: options },
46
+ {
47
+ provide: CacheService,
48
+ useFactory: (opts) => new CacheService(opts),
49
+ inject: [CACHE_OPTIONS]
50
+ }
51
+ ],
52
+ exports: [CacheService]
53
+ };
54
+ }
55
+ static forRootAsync(options) {
56
+ const asyncProviders = CacheModule.createAsyncProviders(options);
57
+ return {
58
+ module: CacheModule,
59
+ global: options.isGlobal ?? false,
60
+ imports: options.imports ?? [],
61
+ providers: [
62
+ ...asyncProviders,
63
+ {
64
+ provide: CacheService,
65
+ useFactory: (opts) => new CacheService(opts),
66
+ inject: [CACHE_OPTIONS]
67
+ }
68
+ ],
69
+ exports: [CacheService]
70
+ };
71
+ }
72
+ static createAsyncProviders(options) {
73
+ if (options.useFactory) {
74
+ return [
75
+ {
76
+ provide: CACHE_OPTIONS,
77
+ useFactory: options.useFactory,
78
+ inject: options.inject ?? []
79
+ }
80
+ ];
81
+ }
82
+ if (options.useClass) {
83
+ return [
84
+ { provide: options.useClass, useClass: options.useClass },
85
+ {
86
+ provide: CACHE_OPTIONS,
87
+ useFactory: (factory) => factory.createCacheOptions(),
88
+ inject: [options.useClass]
89
+ }
90
+ ];
91
+ }
92
+ if (options.useExisting) {
93
+ return [
94
+ {
95
+ provide: CACHE_OPTIONS,
96
+ useFactory: (factory) => factory.createCacheOptions(),
97
+ inject: [options.useExisting]
98
+ }
99
+ ];
100
+ }
101
+ return [];
102
+ }
103
+ };
104
+ CacheModule = __decorateClass([
105
+ Module({})
106
+ ], CacheModule);
107
+
108
+ export { CACHE_OPTIONS, CacheModule, CacheService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-cache-mongoose",
3
- "version": "1.7.7",
3
+ "version": "2.0.0",
4
4
  "description": "Cache plugin for mongoose Queries and Aggregate (in-memory, redis)",
5
5
  "author": "ilovepixelart",
6
6
  "license": "MIT",
@@ -47,13 +47,32 @@
47
47
  ],
48
48
  "type": "module",
49
49
  "exports": {
50
- "require": {
51
- "types": "./dist/index.d.cts",
52
- "default": "./dist/index.cjs"
50
+ ".": {
51
+ "require": {
52
+ "types": "./dist/index.d.cts",
53
+ "default": "./dist/index.cjs"
54
+ },
55
+ "import": {
56
+ "types": "./dist/index.d.mts",
57
+ "default": "./dist/index.mjs"
58
+ }
53
59
  },
54
- "import": {
55
- "types": "./dist/index.d.mts",
56
- "default": "./dist/index.mjs"
60
+ "./nest": {
61
+ "require": {
62
+ "types": "./dist/nest/index.d.cts",
63
+ "default": "./dist/nest/index.cjs"
64
+ },
65
+ "import": {
66
+ "types": "./dist/nest/index.d.mts",
67
+ "default": "./dist/nest/index.mjs"
68
+ }
69
+ }
70
+ },
71
+ "typesVersions": {
72
+ "*": {
73
+ "nest": [
74
+ "./dist/nest/index.d.cts"
75
+ ]
57
76
  }
58
77
  },
59
78
  "main": "./dist/index.cjs",
@@ -71,31 +90,33 @@
71
90
  "release": "npm install && npm run biome && npm run type:check && npm run build && np --no-publish"
72
91
  },
73
92
  "dependencies": {
74
- "@types/ms": "2.1.0",
75
- "@types/semver": "7.7.1",
76
- "ioredis": "5.9.3",
77
- "ms": "2.1.3",
78
- "semver": "7.7.4",
79
- "sort-keys": "4.2.0"
93
+ "ioredis": "5.10.0"
80
94
  },
81
95
  "devDependencies": {
82
- "@biomejs/biome": "2.4.4",
83
- "@types/node": "25.3.0",
84
- "@vitest/coverage-v8": "4.0.18",
96
+ "@biomejs/biome": "2.4.7",
97
+ "@nestjs/common": "11.1.16",
98
+ "@types/node": "25.5.0",
99
+ "@vitest/coverage-v8": "4.1.0",
85
100
  "bson": "7.2.0",
86
101
  "mongodb-memory-server": "11.0.1",
87
- "mongoose": "9.2.2",
102
+ "mongoose": "9.3.0",
103
+ "np": "11.0.2",
88
104
  "open-cli": "8.0.0",
89
- "pkgroll": "2.26.3",
105
+ "pkgroll": "2.27.0",
90
106
  "simple-git-hooks": "2.13.1",
91
107
  "typescript": "5.9.3",
92
- "vitest": "4.0.18",
93
- "np": "11.0.2"
108
+ "vitest": "4.1.0"
94
109
  },
95
110
  "peerDependencies": {
111
+ "@nestjs/common": ">=9.0.0",
96
112
  "bson": ">=4.7.2 < 8",
97
113
  "mongoose": ">=6.6.0 < 10"
98
114
  },
115
+ "peerDependenciesMeta": {
116
+ "@nestjs/common": {
117
+ "optional": true
118
+ }
119
+ },
99
120
  "simple-git-hooks": {
100
121
  "pre-commit": "npm run type:check",
101
122
  "pre-push": "npm run biome:fix"
@@ -104,6 +125,7 @@
104
125
  "publish": false
105
126
  },
106
127
  "overrides": {
107
- "esbuild": "0.25.0"
128
+ "tmp": "0.2.5",
129
+ "file-type": "21.3.2"
108
130
  }
109
131
  }
@@ -1,4 +1,4 @@
1
- import ms from 'ms'
1
+ import { ms } from '../ms'
2
2
  import { MemoryCacheEngine } from './engine/MemoryCacheEngine'
3
3
  import { RedisCacheEngine } from './engine/RedisCacheEngine'
4
4
 
@@ -1,4 +1,4 @@
1
- import ms from 'ms'
1
+ import { ms } from '../../ms'
2
2
 
3
3
  import type { CacheData, CacheEngine, CacheTTL } from '../../types'
4
4
 
@@ -1,6 +1,6 @@
1
1
  import { EJSON } from 'bson'
2
2
  import IORedis from 'ioredis'
3
- import ms from 'ms'
3
+ import { ms } from '../../ms'
4
4
  import { convertToObject } from '../../version'
5
5
 
6
6
  import type { Redis, RedisOptions } from 'ioredis'
package/src/key.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createHash } from 'node:crypto'
2
- import sortKeys from 'sort-keys'
2
+ import { sortKeys } from './sort-keys'
3
3
 
4
4
  export function getKey(data: Record<string, unknown>[] | Record<string, unknown>): string {
5
- const sortedObj = sortKeys(data, { deep: true })
5
+ const sortedObj = sortKeys(data)
6
6
  const sortedStr = JSON.stringify(sortedObj, (_, val: unknown) => {
7
7
  return val instanceof RegExp ? String(val) : val
8
8
  })
package/src/ms.ts ADDED
@@ -0,0 +1,55 @@
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
+ }
@@ -0,0 +1,79 @@
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
+ }
@@ -0,0 +1,37 @@
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
+ }
@@ -0,0 +1,4 @@
1
+ export { CacheModule } from './cache.module'
2
+ export { CACHE_OPTIONS, CacheService } from './cache.service'
3
+
4
+ export type { CacheModuleAsyncOptions, CacheModuleOptions, CacheOptionsFactory } from './interfaces'
@@ -0,0 +1,17 @@
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
+ }
@@ -0,0 +1,38 @@
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 CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { RedisOptions } from 'ioredis'
2
- import type { StringValue } from 'ms'
3
2
 
4
- export type CacheTTL = number | StringValue
3
+ export type CacheTTL = number | string
5
4
 
6
5
  export type CacheData = Record<string, unknown> | Record<string, unknown>[] | unknown[] | number | undefined
7
6
 
package/src/version.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import mongoose from 'mongoose'
2
- import { satisfies } from 'semver'
3
2
 
4
3
  import type { CacheData } from './types'
5
4
 
6
- export const isMongooseLessThan7 = satisfies(mongoose.version, '<7')
5
+ export const isMongooseLessThan7 = Number.parseInt(mongoose.version, 10) < 7
7
6
 
8
7
  export const convertToObject = <T>(value: (T & { toObject?: () => CacheData }) | undefined): CacheData => {
9
8
  if (isMongooseLessThan7) {
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { ms } from '../src/ms'
4
+
5
+ describe('ms', () => {
6
+ it('should parse seconds', () => {
7
+ expect(ms('1s')).toBe(1000)
8
+ expect(ms('5 seconds')).toBe(5000)
9
+ expect(ms('30 sec')).toBe(30000)
10
+ expect(ms('1 second')).toBe(1000)
11
+ expect(ms('2 secs')).toBe(2000)
12
+ })
13
+
14
+ it('should parse minutes', () => {
15
+ expect(ms('1m')).toBe(60000)
16
+ expect(ms('5 minutes')).toBe(300000)
17
+ expect(ms('1 minute')).toBe(60000)
18
+ expect(ms('2 min')).toBe(120000)
19
+ expect(ms('3 mins')).toBe(180000)
20
+ })
21
+
22
+ it('should parse hours', () => {
23
+ expect(ms('1h')).toBe(3600000)
24
+ expect(ms('2 hours')).toBe(7200000)
25
+ expect(ms('1 hour')).toBe(3600000)
26
+ expect(ms('3 hr')).toBe(10800000)
27
+ expect(ms('4 hrs')).toBe(14400000)
28
+ })
29
+
30
+ it('should parse days', () => {
31
+ expect(ms('1d')).toBe(86400000)
32
+ expect(ms('2 days')).toBe(172800000)
33
+ expect(ms('1 day')).toBe(86400000)
34
+ })
35
+
36
+ it('should parse weeks', () => {
37
+ expect(ms('1w')).toBe(604800000)
38
+ expect(ms('2 weeks')).toBe(1209600000)
39
+ expect(ms('1 week')).toBe(604800000)
40
+ })
41
+
42
+ it('should parse years', () => {
43
+ expect(ms('1y')).toBe(31557600000)
44
+ expect(ms('1 year')).toBe(31557600000)
45
+ expect(ms('2 yrs')).toBe(63115200000)
46
+ expect(ms('1 yr')).toBe(31557600000)
47
+ })
48
+
49
+ it('should parse milliseconds', () => {
50
+ expect(ms('100ms')).toBe(100)
51
+ expect(ms('500 milliseconds')).toBe(500)
52
+ expect(ms('1 millisecond')).toBe(1)
53
+ expect(ms('200 msec')).toBe(200)
54
+ expect(ms('300 msecs')).toBe(300)
55
+ })
56
+
57
+ it('should parse decimal values', () => {
58
+ expect(ms('1.5h')).toBe(5400000)
59
+ expect(ms('0.5d')).toBe(43200000)
60
+ expect(ms('.5s')).toBe(500)
61
+ })
62
+
63
+ it('should parse negative values', () => {
64
+ expect(ms('-1s')).toBe(-1000)
65
+ expect(ms('-3m')).toBe(-180000)
66
+ })
67
+
68
+ it('should default to milliseconds without unit', () => {
69
+ expect(ms('100')).toBe(100)
70
+ expect(ms('0')).toBe(0)
71
+ })
72
+
73
+ it('should be case insensitive', () => {
74
+ expect(ms('1S')).toBe(1000)
75
+ expect(ms('1M')).toBe(60000)
76
+ expect(ms('1H')).toBe(3600000)
77
+ })
78
+
79
+ it('should return 0 for invalid strings', () => {
80
+ expect(ms('invalid')).toBe(0)
81
+ expect(ms('')).toBe(0)
82
+ expect(ms('abc123')).toBe(0)
83
+ })
84
+
85
+ it('should return 0 for strings longer than 100 characters', () => {
86
+ expect(ms('a'.repeat(101))).toBe(0)
87
+ })
88
+
89
+ it('should handle whitespace between number and unit', () => {
90
+ expect(ms('1 s')).toBe(1000)
91
+ expect(ms('5 minutes')).toBe(300000)
92
+ })
93
+ })