11import type { CacheError , Data , Cache as ICache , Key , Options , Stats , ValueSetItem , WrappedValue } from './types'
22import { Buffer } from 'node:buffer'
33import { EventEmitter } from 'node:events'
4- import clone from 'clone'
54import { DEFAULT_OPTIONS , ERROR_MESSAGES } from './config'
65
6+ /**
7+ * Fast deep clone using structuredClone (fastest option in modern runtimes)
8+ * Falls back to JSON parse/stringify if structuredClone is not available
9+ */
10+ function fastClone < T > ( value : T ) : T {
11+ if ( typeof structuredClone !== 'undefined' ) {
12+ return structuredClone ( value )
13+ }
14+ // Fallback for older environments
15+ return JSON . parse ( JSON . stringify ( value ) )
16+ }
17+
718/**
819 * TypeScript port of node-cache
920 * Simple and fast NodeJS internal caching
@@ -50,22 +61,27 @@ export class Cache extends EventEmitter implements ICache {
5061 * @returns The value stored in the key or undefined if not found
5162 */
5263 get < T > ( key : Key ) : T | undefined {
53- // handle invalid key types
54- const err = this . _isInvalidKey ( key )
55- if ( err )
56- throw err
57-
58- // get data and increment stats
59- const keyStr = key . toString ( )
60- if ( this . data [ keyStr ] && this . _check ( keyStr , this . data [ keyStr ] ) ) {
61- this . stats . hits ++
62- return this . _unwrap < T > ( this . data [ keyStr ] )
63- }
64- else {
65- // if not found return undefined
66- this . stats . misses ++
67- return undefined
64+ // Fast path: convert key to string once
65+ const keyStr = typeof key === 'string' ? key : key . toString ( )
66+ const data = this . data [ keyStr ]
67+
68+ // Fast existence and TTL check
69+ if ( data !== undefined ) {
70+ // Inline TTL check for performance
71+ if ( data . t === 0 || data . t >= Date . now ( ) ) {
72+ this . stats . hits ++
73+ // Inline unwrap for performance
74+ return this . options . useClones ? fastClone ( data . v ) : data . v
75+ }
76+ else if ( this . options . deleteOnExpire ) {
77+ // Expired, clean up
78+ this . del ( key )
79+ }
6880 }
81+
82+ // Not found or expired
83+ this . stats . misses ++
84+ return undefined
6985 }
7086
7187 /**
@@ -74,34 +90,35 @@ export class Cache extends EventEmitter implements ICache {
7490 * @returns An object containing the values stored in the matching keys
7591 */
7692 mget < T > ( keys : Key [ ] ) : { [ key : string ] : T } {
77- // validate keys is an array
78- if ( ! Array . isArray ( keys ) ) {
79- const err = this . _error ( 'EKEYSTYPE' )
80- throw err
81- }
82-
83- // define return
93+ // Pre-allocate result object
8494 const result : { [ key : string ] : T } = { }
85-
86- for ( const key of keys ) {
87- // handle invalid key types
88- const err = this . _isInvalidKey ( key )
89- if ( err )
90- throw err
91-
92- // get data and increment stats
93- const keyStr = key . toString ( )
94- if ( this . data [ keyStr ] && this . _check ( keyStr , this . data [ keyStr ] ) ) {
95- this . stats . hits ++
96- result [ keyStr ] = this . _unwrap < T > ( this . data [ keyStr ] )
95+ const now = Date . now ( )
96+ const useClones = this . options . useClones
97+
98+ // Optimized loop - avoid repeated method calls
99+ for ( let i = 0 , len = keys . length ; i < len ; i ++ ) {
100+ const key = keys [ i ]
101+ const keyStr = typeof key === 'string' ? key : key . toString ( )
102+ const data = this . data [ keyStr ]
103+
104+ if ( data !== undefined ) {
105+ // Inline TTL check
106+ if ( data . t === 0 || data . t >= now ) {
107+ this . stats . hits ++
108+ result [ keyStr ] = useClones ? fastClone ( data . v ) : data . v
109+ }
110+ else {
111+ this . stats . misses ++
112+ if ( this . options . deleteOnExpire ) {
113+ this . del ( key )
114+ }
115+ }
97116 }
98117 else {
99- // if not found, increment misses
100118 this . stats . misses ++
101119 }
102120 }
103121
104- // return all found keys
105122 return result
106123 }
107124
@@ -113,55 +130,60 @@ export class Cache extends EventEmitter implements ICache {
113130 * @returns Boolean indicating if the operation was successful
114131 */
115132 set < T > ( key : Key , value : T , ttl ?: number | string ) : boolean {
116- // check if cache is overflowing
117- if ( this . options . maxKeys && this . options . maxKeys > - 1 && this . stats . keys >= this . options . maxKeys ) {
133+ // Fast path: convert key to string once
134+ const keyStr = typeof key === 'string' ? key : key . toString ( )
135+ const existent = this . data [ keyStr ] !== undefined
136+
137+ // Check max keys only for new keys
138+ if ( ! existent && this . options . maxKeys && this . options . maxKeys > - 1 && this . stats . keys >= this . options . maxKeys ) {
118139 const err = this . _error ( 'ECACHEFULL' )
119140 throw err
120141 }
121142
122- // force the data to string if configured
123- let valueToStore : any = value
124- if ( this . options . forceString && typeof value !== 'string' ) {
125- valueToStore = JSON . stringify ( value )
126- }
143+ // Force string if configured
144+ const valueToStore = this . options . forceString && typeof value !== 'string'
145+ ? JSON . stringify ( value )
146+ : value
147+
148+ // Parse TTL if string
149+ const ttlValue = typeof ttl === 'string' ? Number . parseInt ( ttl , 10 ) : ttl
150+
151+ // Calculate TTL timestamp
152+ const now = Date . now ( )
153+ let livetime = 0
127154
128- // set default ttl if not passed
129- let ttlValue : number | undefined
130- if ( typeof ttl === 'string' ) {
131- ttlValue = Number . parseInt ( ttl , 10 )
155+ if ( ttlValue === 0 ) {
156+ livetime = 0
132157 }
133- else {
134- ttlValue = ttl
158+ else if ( ttlValue ) {
159+ livetime = now + ( ttlValue * 1000 )
160+ }
161+ else if ( this . options . stdTTL === 0 ) {
162+ livetime = 0
163+ }
164+ else if ( this . options . stdTTL ) {
165+ livetime = now + ( this . options . stdTTL * 1000 )
135166 }
136167
137- // handle invalid key types
138- const err = this . _isInvalidKey ( key )
139- if ( err )
140- throw err
141-
142- // internal helper variables
143- let existent = false
144- const keyStr = key . toString ( )
168+ // Update stats for existing keys
169+ if ( existent ) {
170+ this . stats . vsize -= this . _getValLength ( this . data [ keyStr ] . v )
171+ }
145172
146- // remove existing data from stats
147- if ( this . data [ keyStr ] ) {
148- existent = true
149- this . stats . vsize -= this . _getValLength ( this . _unwrap ( this . data [ keyStr ] , false ) )
173+ // Wrap and store value (inline for performance)
174+ this . data [ keyStr ] = {
175+ t : livetime ,
176+ v : this . options . useClones ? fastClone ( valueToStore ) : valueToStore ,
150177 }
151178
152- // set the value
153- this . data [ keyStr ] = this . _wrap ( valueToStore , ttlValue )
154179 this . stats . vsize += this . _getValLength ( valueToStore )
155180
156- // only add the keys and key-size if the key is new
181+ // Update stats for new keys
157182 if ( ! existent ) {
158- this . stats . ksize += this . _getKeyLength ( key )
183+ this . stats . ksize += keyStr . length
159184 this . stats . keys ++
160185 }
161186
162- this . emit ( 'set' , key , value )
163-
164- // return true
165187 return true
166188 }
167189
@@ -207,32 +229,75 @@ export class Cache extends EventEmitter implements ICache {
207229 * @returns Boolean indicating if the operation was successful
208230 */
209231 mset < T > ( keyValueSet : ValueSetItem < T > [ ] ) : boolean {
210- // check if cache is overflowing
232+ // Pre-calculate values for performance
233+ const now = Date . now ( )
234+ const useClones = this . options . useClones
235+ const stdTTL = this . options . stdTTL
236+ const forceString = this . options . forceString
237+
238+ // Count new keys for maxKeys check
239+ let newKeysCount = 0
240+ for ( let i = 0 , len = keyValueSet . length ; i < len ; i ++ ) {
241+ const keyStr = typeof keyValueSet [ i ] . key === 'string'
242+ ? keyValueSet [ i ] . key as string
243+ : keyValueSet [ i ] . key . toString ( )
244+
245+ if ( this . data [ keyStr ] === undefined ) {
246+ newKeysCount ++
247+ }
248+ }
249+
250+ // Check max keys
211251 if ( this . options . maxKeys && this . options . maxKeys > - 1
212- && this . stats . keys + keyValueSet . length >= this . options . maxKeys ) {
252+ && this . stats . keys + newKeysCount > this . options . maxKeys ) {
213253 const err = this . _error ( 'ECACHEFULL' )
214254 throw err
215255 }
216256
217- // loop over keyValueSet to validate key and ttl
218- for ( const keyValuePair of keyValueSet ) {
219- const { key, ttl } = keyValuePair
257+ // Optimized batch set
258+ for ( let i = 0 , len = keyValueSet . length ; i < len ; i ++ ) {
259+ const { key, val, ttl } = keyValueSet [ i ]
260+ const keyStr = typeof key === 'string' ? key : key . toString ( )
261+ const existent = this . data [ keyStr ] !== undefined
220262
221- // check if there is ttl and it's a number
222- if ( ttl && typeof ttl !== 'number' ) {
223- const err = this . _error ( 'ETTLTYPE' )
224- throw err
263+ // Force string if configured
264+ const valueToStore = forceString && typeof val !== 'string'
265+ ? JSON . stringify ( val )
266+ : val
267+
268+ // Calculate TTL
269+ let livetime = 0
270+ if ( ttl === 0 ) {
271+ livetime = 0
272+ }
273+ else if ( ttl ) {
274+ livetime = now + ( ttl * 1000 )
275+ }
276+ else if ( stdTTL === 0 ) {
277+ livetime = 0
278+ }
279+ else if ( stdTTL ) {
280+ livetime = now + ( stdTTL * 1000 )
225281 }
226282
227- // handle invalid key types
228- const err = this . _isInvalidKey ( key )
229- if ( err )
230- throw err
231- }
283+ // Update stats for existing keys
284+ if ( existent ) {
285+ this . stats . vsize -= this . _getValLength ( this . data [ keyStr ] . v )
286+ }
287+
288+ // Store value
289+ this . data [ keyStr ] = {
290+ t : livetime ,
291+ v : useClones ? fastClone ( valueToStore ) : valueToStore ,
292+ }
232293
233- for ( const keyValuePair of keyValueSet ) {
234- const { key, val, ttl } = keyValuePair
235- this . set ( key , val , ttl )
294+ this . stats . vsize += this . _getValLength ( valueToStore )
295+
296+ // Update stats for new keys
297+ if ( ! existent ) {
298+ this . stats . ksize += keyStr . length
299+ this . stats . keys ++
300+ }
236301 }
237302
238303 return true
@@ -372,8 +437,15 @@ export class Cache extends EventEmitter implements ICache {
372437 * @returns Boolean indicating if the key is cached
373438 */
374439 has ( key : Key ) : boolean {
375- const keyStr = key . toString ( )
376- return ! ! ( this . data [ keyStr ] && this . _check ( keyStr , this . data [ keyStr ] ) )
440+ const keyStr = typeof key === 'string' ? key : key . toString ( )
441+ const data = this . data [ keyStr ]
442+
443+ // Fast existence and TTL check
444+ if ( data !== undefined ) {
445+ return data . t === 0 || data . t >= Date . now ( )
446+ }
447+
448+ return false
377449 }
378450
379451 /**
0 commit comments