@ls-stack/utils 3.46.0 → 3.47.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/dist/cache.cjs CHANGED
@@ -139,7 +139,26 @@ function createCache({
139
139
  return new WithExpiration(value, expiration);
140
140
  }
141
141
  };
142
+ function refreshEntry(key, now) {
143
+ const entry = cache.get(key);
144
+ if (entry && !isExpired(entry, now)) {
145
+ cache.delete(key);
146
+ cache.set(key, entry);
147
+ }
148
+ }
142
149
  return {
150
+ /**
151
+ * Gets a value from the cache or computes and stores it if not present.
152
+ * This is the primary method for synchronous caching operations.
153
+ *
154
+ * @param cacheKey - Unique key to identify the cached value
155
+ * @param val - Function that computes the value if not cached. Receives
156
+ * utility functions for advanced features.
157
+ * @param options - Optional configuration for this specific get operation
158
+ * @returns The cached or newly computed value
159
+ * @throws Error if the cached value is a promise (use getOrInsertAsync
160
+ * instead)
161
+ */
143
162
  getOrInsert(cacheKey, val, options) {
144
163
  const now = Date.now();
145
164
  const entry = cache.get(cacheKey);
@@ -162,8 +181,20 @@ function createCache({
162
181
  "Cache value is a promise, use getOrInsertAsync instead"
163
182
  );
164
183
  }
184
+ refreshEntry(cacheKey, now);
165
185
  return entry.value;
166
186
  },
187
+ /**
188
+ * Gets a value from the cache or computes and stores it asynchronously.
189
+ * Provides promise deduplication - concurrent calls with the same key will
190
+ * share the same promise.
191
+ *
192
+ * @param cacheKey - Unique key to identify the cached value
193
+ * @param val - Async function that computes the value if not cached.
194
+ * Receives utility functions for advanced features.
195
+ * @param options - Optional configuration for this specific get operation
196
+ * @returns Promise that resolves to the cached or newly computed value
197
+ */
167
198
  async getOrInsertAsync(cacheKey, val, options) {
168
199
  const entry = cache.get(cacheKey);
169
200
  if (entry && isPromise2(entry.value)) {
@@ -171,6 +202,7 @@ function createCache({
171
202
  }
172
203
  const now = Date.now();
173
204
  if (entry && !isExpired(entry, now)) {
205
+ refreshEntry(cacheKey, now);
174
206
  return entry.value;
175
207
  }
176
208
  const promise = val(utils).then((result) => {
@@ -204,31 +236,67 @@ function createCache({
204
236
  cleanExpiredItems();
205
237
  return promise;
206
238
  },
239
+ /** Removes all items from the cache. */
207
240
  clear() {
208
241
  cache.clear();
209
242
  },
243
+ /**
244
+ * Gets a value from the cache without computing it if missing. Returns
245
+ * undefined if the key doesn't exist or has expired.
246
+ *
247
+ * @param cacheKey - Key to look up in the cache
248
+ * @returns The cached value or undefined if not found/expired
249
+ * @throws Error if the cached value is a promise (use getAsync instead)
250
+ */
210
251
  get(cacheKey) {
211
252
  const entry = cache.get(cacheKey);
212
- if (!entry || isExpired(entry, Date.now())) {
253
+ const now = Date.now();
254
+ if (!entry || isExpired(entry, now)) {
213
255
  return void 0;
214
256
  }
215
257
  if (isPromise2(entry.value)) {
216
258
  throw new Error("Cache value is a promise, use getAsync instead");
217
259
  }
260
+ refreshEntry(cacheKey, now);
218
261
  return entry.value;
219
262
  },
263
+ /**
264
+ * Manually sets a value in the cache.
265
+ *
266
+ * @param cacheKey - Key to store the value under
267
+ * @param value - Value to store, or WithExpiration wrapper for custom
268
+ * expiration
269
+ */
220
270
  set(cacheKey, value) {
221
271
  cache.set(cacheKey, unwrapValue(value, Date.now()));
222
272
  trimToSize();
223
273
  cleanExpiredItems();
224
274
  },
275
+ /**
276
+ * Gets a value from the cache without computing it if missing. Works with
277
+ * both sync and async cached values.
278
+ *
279
+ * @param cacheKey - Key to look up in the cache
280
+ * @returns Promise that resolves to the cached value or undefined if not
281
+ * found/expired
282
+ */
225
283
  async getAsync(cacheKey) {
226
284
  const entry = cache.get(cacheKey);
227
- if (!entry || isExpired(entry, Date.now())) {
285
+ const now = Date.now();
286
+ if (!entry || isExpired(entry, now)) {
228
287
  return void 0;
229
288
  }
289
+ refreshEntry(cacheKey, now);
230
290
  return entry.value;
231
291
  },
292
+ /**
293
+ * Manually sets an async value in the cache. The promise will be stored
294
+ * immediately and shared with concurrent requests.
295
+ *
296
+ * @param cacheKey - Key to store the value under
297
+ * @param value - Async function that returns the value to cache
298
+ * @returns Promise that resolves to the computed value
299
+ */
232
300
  async setAsync(cacheKey, value) {
233
301
  const promise = value(utils).then((result) => {
234
302
  if (result instanceof SkipCaching) {
@@ -254,6 +322,10 @@ function createCache({
254
322
  cleanExpiredItems();
255
323
  return promise;
256
324
  },
325
+ /**
326
+ * Manually triggers cleanup of expired items. Normally this happens
327
+ * automatically during cache operations.
328
+ */
257
329
  cleanExpiredItems,
258
330
  /** @internal */
259
331
  " cache": { map: cache }
@@ -272,12 +344,18 @@ function fastCache({ maxCacheSize = 1e3 } = {}) {
272
344
  }
273
345
  function getOrInsert(cacheKey, val) {
274
346
  if (!cache.has(cacheKey)) {
275
- cache.set(cacheKey, val());
347
+ const value = val();
348
+ cache.set(cacheKey, value);
276
349
  trimCache();
350
+ return cache.get(cacheKey) ?? value;
277
351
  }
278
352
  return cache.get(cacheKey);
279
353
  }
280
- return { getOrInsert, clear: () => cache.clear() };
354
+ return {
355
+ getOrInsert,
356
+ /** Clears all cached values */
357
+ clear: () => cache.clear()
358
+ };
281
359
  }
282
360
  // Annotate the CommonJS export names for ESM import in node:
283
361
  0 && (module.exports = {
package/dist/cache.d.cts CHANGED
@@ -1,5 +1,23 @@
1
1
  import { DurationObj } from './time.cjs';
2
2
 
3
+ /**
4
+ * Creates a cached getter that only calls the provided function once. The first
5
+ * access computes and caches the value; subsequent accesses return the cached
6
+ * result. This is useful for lazy initialization of expensive computations.
7
+ *
8
+ * @example
9
+ * const expensive = cachedGetter(() => {
10
+ * console.log('Computing...');
11
+ * return heavyComputation();
12
+ * });
13
+ *
14
+ * console.log(expensive.value); // Logs "Computing..." and returns result
15
+ * console.log(expensive.value); // Returns cached result without logging
16
+ * console.log(expensive.value); // Returns cached result without logging
17
+ *
18
+ * @param getter - Function that computes the value to cache
19
+ * @returns Object with a `value` property that caches the result
20
+ */
3
21
  declare function cachedGetter<T>(getter: () => T): {
4
22
  value: T;
5
23
  };
@@ -20,10 +38,44 @@ type Options = {
20
38
  */
21
39
  expirationThrottle?: number;
22
40
  };
41
+ /**
42
+ * Wrapper class that prevents a value from being cached. When returned from a
43
+ * cache computation function, the value will be returned to the caller but not
44
+ * stored in the cache.
45
+ *
46
+ * @example
47
+ * const cache = createCache<string>();
48
+ * const result = cache.getOrInsert('dynamic', ({ skipCaching }) => {
49
+ * const data = generateData();
50
+ * if (data.isTemporary) {
51
+ * return skipCaching(data); // Won't be cached
52
+ * }
53
+ * return data; // Will be cached
54
+ * });
55
+ */
23
56
  declare class SkipCaching<T> {
24
57
  value: T;
25
58
  constructor(value: T);
26
59
  }
60
+ /**
61
+ * Wrapper class that sets a custom expiration time for a cached value. Allows
62
+ * individual cache entries to have different expiration times than the default
63
+ * cache expiration.
64
+ *
65
+ * @example
66
+ * const cache = createCache<string>({ maxItemAge: { hours: 1 } }); // Default 1 hour
67
+ *
68
+ * const result = cache.getOrInsert('short-lived', ({ withExpiration }) => {
69
+ * return withExpiration('temporary data', { minutes: 5 }); // Expires in 5 minutes
70
+ * });
71
+ *
72
+ * const longLived = cache.getOrInsert(
73
+ * 'long-lived',
74
+ * ({ withExpiration }) => {
75
+ * return withExpiration('persistent data', { days: 1 }); // Expires in 1 day
76
+ * },
77
+ * );
78
+ */
27
79
  declare class WithExpiration<T> {
28
80
  value: T;
29
81
  expiration: number;
@@ -73,12 +125,103 @@ type Cache<T> = {
73
125
  }>;
74
126
  };
75
127
  };
128
+ /**
129
+ * Creates a full-featured cache with time-based expiration, async support, and
130
+ * advanced features. This is a more powerful alternative to `fastCache` when
131
+ * you need expiration, async operations, or advanced caching strategies.
132
+ *
133
+ * @example
134
+ * // Basic usage with expiration
135
+ * const cache = createCache<string>({
136
+ * maxCacheSize: 100,
137
+ * maxItemAge: { minutes: 5 },
138
+ * });
139
+ *
140
+ * // Simple caching
141
+ * const result = cache.getOrInsert('user:123', () => {
142
+ * return fetchUserFromDatabase('123');
143
+ * });
144
+ *
145
+ * // Async caching with promise deduplication
146
+ * const asyncResult = await cache.getOrInsertAsync(
147
+ * 'api:data',
148
+ * async () => {
149
+ * return await fetchFromApi('/data');
150
+ * },
151
+ * );
152
+ *
153
+ * // Skip caching for certain values
154
+ * const value = cache.getOrInsert('dynamic', ({ skipCaching }) => {
155
+ * const data = generateDynamicData();
156
+ * if (data.shouldNotCache) {
157
+ * return skipCaching(data); // Won't be cached
158
+ * }
159
+ * return data;
160
+ * });
161
+ *
162
+ * // Custom expiration per item
163
+ * const shortLivedValue = cache.getOrInsert(
164
+ * 'temp',
165
+ * ({ withExpiration }) => {
166
+ * return withExpiration('temporary data', { seconds: 30 });
167
+ * },
168
+ * );
169
+ *
170
+ * // Conditional caching based on the computed value
171
+ * const result = cache.getOrInsert(
172
+ * 'conditional',
173
+ * () => {
174
+ * return computeValue();
175
+ * },
176
+ * {
177
+ * skipCachingWhen: (value) => value === null || value.error,
178
+ * },
179
+ * );
180
+ *
181
+ * @param options - Configuration options for the cache
182
+ * @param options.maxCacheSize - Maximum number of items to store. When
183
+ * exceeded, oldest items are removed first. Defaults to 1000.
184
+ * @param options.maxItemAge - Default expiration time for all cached items.
185
+ * Items older than this will be automatically removed.
186
+ * @param options.expirationThrottle - Minimum time in milliseconds between
187
+ * expiration cleanup runs. Prevents excessive cleanup operations. Defaults to
188
+ * 10,000ms.
189
+ * @returns A cache instance with various methods for storing and retrieving
190
+ * values
191
+ */
76
192
  declare function createCache<T>({ maxCacheSize, maxItemAge, expirationThrottle, }?: Options): Cache<T>;
77
193
  type FastCacheOptions = {
78
194
  maxCacheSize?: number;
79
195
  };
196
+ /**
197
+ * Creates a simple, fast cache with FIFO (First In, First Out) eviction policy.
198
+ * This is a lightweight alternative to `createCache` for basic caching needs
199
+ * without expiration, async support, or advanced features.
200
+ *
201
+ * @example
202
+ * const cache = fastCache<string>({ maxCacheSize: 100 });
203
+ *
204
+ * // Cache expensive computation
205
+ * const result = cache.getOrInsert('user:123', () => {
206
+ * return fetchUserFromDatabase('123');
207
+ * });
208
+ *
209
+ * // Subsequent calls return cached value without re-computation
210
+ * const cachedResult = cache.getOrInsert('user:123', () => {
211
+ * return fetchUserFromDatabase('123'); // Won't be called
212
+ * });
213
+ *
214
+ * // Clear all cached values
215
+ * cache.clear();
216
+ *
217
+ * @param options - Configuration options for the cache
218
+ * @param options.maxCacheSize - Maximum number of items to store in the cache.
219
+ * When exceeded, oldest items are removed first. Defaults to 1000.
220
+ * @returns An object with cache methods
221
+ */
80
222
  declare function fastCache<T>({ maxCacheSize }?: FastCacheOptions): {
81
223
  getOrInsert: (cacheKey: string, val: () => T) => T;
224
+ /** Clears all cached values */
82
225
  clear: () => void;
83
226
  };
84
227
 
package/dist/cache.d.ts CHANGED
@@ -1,5 +1,23 @@
1
1
  import { DurationObj } from './time.js';
2
2
 
3
+ /**
4
+ * Creates a cached getter that only calls the provided function once. The first
5
+ * access computes and caches the value; subsequent accesses return the cached
6
+ * result. This is useful for lazy initialization of expensive computations.
7
+ *
8
+ * @example
9
+ * const expensive = cachedGetter(() => {
10
+ * console.log('Computing...');
11
+ * return heavyComputation();
12
+ * });
13
+ *
14
+ * console.log(expensive.value); // Logs "Computing..." and returns result
15
+ * console.log(expensive.value); // Returns cached result without logging
16
+ * console.log(expensive.value); // Returns cached result without logging
17
+ *
18
+ * @param getter - Function that computes the value to cache
19
+ * @returns Object with a `value` property that caches the result
20
+ */
3
21
  declare function cachedGetter<T>(getter: () => T): {
4
22
  value: T;
5
23
  };
@@ -20,10 +38,44 @@ type Options = {
20
38
  */
21
39
  expirationThrottle?: number;
22
40
  };
41
+ /**
42
+ * Wrapper class that prevents a value from being cached. When returned from a
43
+ * cache computation function, the value will be returned to the caller but not
44
+ * stored in the cache.
45
+ *
46
+ * @example
47
+ * const cache = createCache<string>();
48
+ * const result = cache.getOrInsert('dynamic', ({ skipCaching }) => {
49
+ * const data = generateData();
50
+ * if (data.isTemporary) {
51
+ * return skipCaching(data); // Won't be cached
52
+ * }
53
+ * return data; // Will be cached
54
+ * });
55
+ */
23
56
  declare class SkipCaching<T> {
24
57
  value: T;
25
58
  constructor(value: T);
26
59
  }
60
+ /**
61
+ * Wrapper class that sets a custom expiration time for a cached value. Allows
62
+ * individual cache entries to have different expiration times than the default
63
+ * cache expiration.
64
+ *
65
+ * @example
66
+ * const cache = createCache<string>({ maxItemAge: { hours: 1 } }); // Default 1 hour
67
+ *
68
+ * const result = cache.getOrInsert('short-lived', ({ withExpiration }) => {
69
+ * return withExpiration('temporary data', { minutes: 5 }); // Expires in 5 minutes
70
+ * });
71
+ *
72
+ * const longLived = cache.getOrInsert(
73
+ * 'long-lived',
74
+ * ({ withExpiration }) => {
75
+ * return withExpiration('persistent data', { days: 1 }); // Expires in 1 day
76
+ * },
77
+ * );
78
+ */
27
79
  declare class WithExpiration<T> {
28
80
  value: T;
29
81
  expiration: number;
@@ -73,12 +125,103 @@ type Cache<T> = {
73
125
  }>;
74
126
  };
75
127
  };
128
+ /**
129
+ * Creates a full-featured cache with time-based expiration, async support, and
130
+ * advanced features. This is a more powerful alternative to `fastCache` when
131
+ * you need expiration, async operations, or advanced caching strategies.
132
+ *
133
+ * @example
134
+ * // Basic usage with expiration
135
+ * const cache = createCache<string>({
136
+ * maxCacheSize: 100,
137
+ * maxItemAge: { minutes: 5 },
138
+ * });
139
+ *
140
+ * // Simple caching
141
+ * const result = cache.getOrInsert('user:123', () => {
142
+ * return fetchUserFromDatabase('123');
143
+ * });
144
+ *
145
+ * // Async caching with promise deduplication
146
+ * const asyncResult = await cache.getOrInsertAsync(
147
+ * 'api:data',
148
+ * async () => {
149
+ * return await fetchFromApi('/data');
150
+ * },
151
+ * );
152
+ *
153
+ * // Skip caching for certain values
154
+ * const value = cache.getOrInsert('dynamic', ({ skipCaching }) => {
155
+ * const data = generateDynamicData();
156
+ * if (data.shouldNotCache) {
157
+ * return skipCaching(data); // Won't be cached
158
+ * }
159
+ * return data;
160
+ * });
161
+ *
162
+ * // Custom expiration per item
163
+ * const shortLivedValue = cache.getOrInsert(
164
+ * 'temp',
165
+ * ({ withExpiration }) => {
166
+ * return withExpiration('temporary data', { seconds: 30 });
167
+ * },
168
+ * );
169
+ *
170
+ * // Conditional caching based on the computed value
171
+ * const result = cache.getOrInsert(
172
+ * 'conditional',
173
+ * () => {
174
+ * return computeValue();
175
+ * },
176
+ * {
177
+ * skipCachingWhen: (value) => value === null || value.error,
178
+ * },
179
+ * );
180
+ *
181
+ * @param options - Configuration options for the cache
182
+ * @param options.maxCacheSize - Maximum number of items to store. When
183
+ * exceeded, oldest items are removed first. Defaults to 1000.
184
+ * @param options.maxItemAge - Default expiration time for all cached items.
185
+ * Items older than this will be automatically removed.
186
+ * @param options.expirationThrottle - Minimum time in milliseconds between
187
+ * expiration cleanup runs. Prevents excessive cleanup operations. Defaults to
188
+ * 10,000ms.
189
+ * @returns A cache instance with various methods for storing and retrieving
190
+ * values
191
+ */
76
192
  declare function createCache<T>({ maxCacheSize, maxItemAge, expirationThrottle, }?: Options): Cache<T>;
77
193
  type FastCacheOptions = {
78
194
  maxCacheSize?: number;
79
195
  };
196
+ /**
197
+ * Creates a simple, fast cache with FIFO (First In, First Out) eviction policy.
198
+ * This is a lightweight alternative to `createCache` for basic caching needs
199
+ * without expiration, async support, or advanced features.
200
+ *
201
+ * @example
202
+ * const cache = fastCache<string>({ maxCacheSize: 100 });
203
+ *
204
+ * // Cache expensive computation
205
+ * const result = cache.getOrInsert('user:123', () => {
206
+ * return fetchUserFromDatabase('123');
207
+ * });
208
+ *
209
+ * // Subsequent calls return cached value without re-computation
210
+ * const cachedResult = cache.getOrInsert('user:123', () => {
211
+ * return fetchUserFromDatabase('123'); // Won't be called
212
+ * });
213
+ *
214
+ * // Clear all cached values
215
+ * cache.clear();
216
+ *
217
+ * @param options - Configuration options for the cache
218
+ * @param options.maxCacheSize - Maximum number of items to store in the cache.
219
+ * When exceeded, oldest items are removed first. Defaults to 1000.
220
+ * @returns An object with cache methods
221
+ */
80
222
  declare function fastCache<T>({ maxCacheSize }?: FastCacheOptions): {
81
223
  getOrInsert: (cacheKey: string, val: () => T) => T;
224
+ /** Clears all cached values */
82
225
  clear: () => void;
83
226
  };
84
227
 
package/dist/cache.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  cachedGetter,
5
5
  createCache,
6
6
  fastCache
7
- } from "./chunk-OIAGLRII.js";
7
+ } from "./chunk-AULH7VMS.js";
8
8
  import "./chunk-5MNYPLZI.js";
9
9
  import "./chunk-HTCYUMDR.js";
10
10
  import "./chunk-II4R3VVX.js";
@@ -86,7 +86,26 @@ function createCache({
86
86
  return new WithExpiration(value, expiration);
87
87
  }
88
88
  };
89
+ function refreshEntry(key, now) {
90
+ const entry = cache.get(key);
91
+ if (entry && !isExpired(entry, now)) {
92
+ cache.delete(key);
93
+ cache.set(key, entry);
94
+ }
95
+ }
89
96
  return {
97
+ /**
98
+ * Gets a value from the cache or computes and stores it if not present.
99
+ * This is the primary method for synchronous caching operations.
100
+ *
101
+ * @param cacheKey - Unique key to identify the cached value
102
+ * @param val - Function that computes the value if not cached. Receives
103
+ * utility functions for advanced features.
104
+ * @param options - Optional configuration for this specific get operation
105
+ * @returns The cached or newly computed value
106
+ * @throws Error if the cached value is a promise (use getOrInsertAsync
107
+ * instead)
108
+ */
90
109
  getOrInsert(cacheKey, val, options) {
91
110
  const now = Date.now();
92
111
  const entry = cache.get(cacheKey);
@@ -109,8 +128,20 @@ function createCache({
109
128
  "Cache value is a promise, use getOrInsertAsync instead"
110
129
  );
111
130
  }
131
+ refreshEntry(cacheKey, now);
112
132
  return entry.value;
113
133
  },
134
+ /**
135
+ * Gets a value from the cache or computes and stores it asynchronously.
136
+ * Provides promise deduplication - concurrent calls with the same key will
137
+ * share the same promise.
138
+ *
139
+ * @param cacheKey - Unique key to identify the cached value
140
+ * @param val - Async function that computes the value if not cached.
141
+ * Receives utility functions for advanced features.
142
+ * @param options - Optional configuration for this specific get operation
143
+ * @returns Promise that resolves to the cached or newly computed value
144
+ */
114
145
  async getOrInsertAsync(cacheKey, val, options) {
115
146
  const entry = cache.get(cacheKey);
116
147
  if (entry && isPromise(entry.value)) {
@@ -118,6 +149,7 @@ function createCache({
118
149
  }
119
150
  const now = Date.now();
120
151
  if (entry && !isExpired(entry, now)) {
152
+ refreshEntry(cacheKey, now);
121
153
  return entry.value;
122
154
  }
123
155
  const promise = val(utils).then((result) => {
@@ -151,31 +183,67 @@ function createCache({
151
183
  cleanExpiredItems();
152
184
  return promise;
153
185
  },
186
+ /** Removes all items from the cache. */
154
187
  clear() {
155
188
  cache.clear();
156
189
  },
190
+ /**
191
+ * Gets a value from the cache without computing it if missing. Returns
192
+ * undefined if the key doesn't exist or has expired.
193
+ *
194
+ * @param cacheKey - Key to look up in the cache
195
+ * @returns The cached value or undefined if not found/expired
196
+ * @throws Error if the cached value is a promise (use getAsync instead)
197
+ */
157
198
  get(cacheKey) {
158
199
  const entry = cache.get(cacheKey);
159
- if (!entry || isExpired(entry, Date.now())) {
200
+ const now = Date.now();
201
+ if (!entry || isExpired(entry, now)) {
160
202
  return void 0;
161
203
  }
162
204
  if (isPromise(entry.value)) {
163
205
  throw new Error("Cache value is a promise, use getAsync instead");
164
206
  }
207
+ refreshEntry(cacheKey, now);
165
208
  return entry.value;
166
209
  },
210
+ /**
211
+ * Manually sets a value in the cache.
212
+ *
213
+ * @param cacheKey - Key to store the value under
214
+ * @param value - Value to store, or WithExpiration wrapper for custom
215
+ * expiration
216
+ */
167
217
  set(cacheKey, value) {
168
218
  cache.set(cacheKey, unwrapValue(value, Date.now()));
169
219
  trimToSize();
170
220
  cleanExpiredItems();
171
221
  },
222
+ /**
223
+ * Gets a value from the cache without computing it if missing. Works with
224
+ * both sync and async cached values.
225
+ *
226
+ * @param cacheKey - Key to look up in the cache
227
+ * @returns Promise that resolves to the cached value or undefined if not
228
+ * found/expired
229
+ */
172
230
  async getAsync(cacheKey) {
173
231
  const entry = cache.get(cacheKey);
174
- if (!entry || isExpired(entry, Date.now())) {
232
+ const now = Date.now();
233
+ if (!entry || isExpired(entry, now)) {
175
234
  return void 0;
176
235
  }
236
+ refreshEntry(cacheKey, now);
177
237
  return entry.value;
178
238
  },
239
+ /**
240
+ * Manually sets an async value in the cache. The promise will be stored
241
+ * immediately and shared with concurrent requests.
242
+ *
243
+ * @param cacheKey - Key to store the value under
244
+ * @param value - Async function that returns the value to cache
245
+ * @returns Promise that resolves to the computed value
246
+ */
179
247
  async setAsync(cacheKey, value) {
180
248
  const promise = value(utils).then((result) => {
181
249
  if (result instanceof SkipCaching) {
@@ -201,6 +269,10 @@ function createCache({
201
269
  cleanExpiredItems();
202
270
  return promise;
203
271
  },
272
+ /**
273
+ * Manually triggers cleanup of expired items. Normally this happens
274
+ * automatically during cache operations.
275
+ */
204
276
  cleanExpiredItems,
205
277
  /** @internal */
206
278
  " cache": { map: cache }
@@ -219,12 +291,18 @@ function fastCache({ maxCacheSize = 1e3 } = {}) {
219
291
  }
220
292
  function getOrInsert(cacheKey, val) {
221
293
  if (!cache.has(cacheKey)) {
222
- cache.set(cacheKey, val());
294
+ const value = val();
295
+ cache.set(cacheKey, value);
223
296
  trimCache();
297
+ return cache.get(cacheKey) ?? value;
224
298
  }
225
299
  return cache.get(cacheKey);
226
300
  }
227
- return { getOrInsert, clear: () => cache.clear() };
301
+ return {
302
+ getOrInsert,
303
+ /** Clears all cached values */
304
+ clear: () => cache.clear()
305
+ };
228
306
  }
229
307
 
230
308
  export {
@@ -0,0 +1,52 @@
1
+ import {
2
+ isPlainObject
3
+ } from "./chunk-JF2MDHOJ.js";
4
+
5
+ // src/deepReplaceValues.ts
6
+ function applyValueReplacements(value, replaceValues, visited, currentPath) {
7
+ function processValue(val, path) {
8
+ const replacement = replaceValues(val, path);
9
+ if (replacement !== false) {
10
+ return replacement.newValue;
11
+ }
12
+ if (Array.isArray(val)) {
13
+ if (visited.has(val)) {
14
+ throw new Error("Circular reference detected in array");
15
+ }
16
+ visited.add(val);
17
+ try {
18
+ return val.map((item, index) => {
19
+ const itemPath = path ? `${path}[${index}]` : `[${index}]`;
20
+ return processValue(item, itemPath);
21
+ });
22
+ } finally {
23
+ visited.delete(val);
24
+ }
25
+ }
26
+ if (isPlainObject(val)) {
27
+ if (visited.has(val)) {
28
+ throw new Error("Circular reference detected in object");
29
+ }
30
+ visited.add(val);
31
+ try {
32
+ const result = {};
33
+ for (const [key, itemValue] of Object.entries(val)) {
34
+ const itemPath = path ? `${path}.${key}` : key;
35
+ result[key] = processValue(itemValue, itemPath);
36
+ }
37
+ return result;
38
+ } finally {
39
+ visited.delete(val);
40
+ }
41
+ }
42
+ return val;
43
+ }
44
+ return processValue(value, currentPath);
45
+ }
46
+ function deepReplaceValues(value, replaceValues) {
47
+ return applyValueReplacements(value, replaceValues, /* @__PURE__ */ new Set(), "");
48
+ }
49
+
50
+ export {
51
+ deepReplaceValues
52
+ };
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/deepReplaceValues.ts
21
+ var deepReplaceValues_exports = {};
22
+ __export(deepReplaceValues_exports, {
23
+ deepReplaceValues: () => deepReplaceValues
24
+ });
25
+ module.exports = __toCommonJS(deepReplaceValues_exports);
26
+
27
+ // src/typeGuards.ts
28
+ function isPlainObject(value) {
29
+ if (!value || typeof value !== "object") return false;
30
+ const proto = Object.getPrototypeOf(value);
31
+ if (proto === null) {
32
+ return true;
33
+ }
34
+ const Ctor = Object.hasOwnProperty.call(proto, "constructor") && proto.constructor;
35
+ if (Ctor === Object) return true;
36
+ const objectCtorString = Object.prototype.constructor.toString();
37
+ return typeof Ctor == "function" && Function.toString.call(Ctor) === objectCtorString;
38
+ }
39
+
40
+ // src/deepReplaceValues.ts
41
+ function applyValueReplacements(value, replaceValues, visited, currentPath) {
42
+ function processValue(val, path) {
43
+ const replacement = replaceValues(val, path);
44
+ if (replacement !== false) {
45
+ return replacement.newValue;
46
+ }
47
+ if (Array.isArray(val)) {
48
+ if (visited.has(val)) {
49
+ throw new Error("Circular reference detected in array");
50
+ }
51
+ visited.add(val);
52
+ try {
53
+ return val.map((item, index) => {
54
+ const itemPath = path ? `${path}[${index}]` : `[${index}]`;
55
+ return processValue(item, itemPath);
56
+ });
57
+ } finally {
58
+ visited.delete(val);
59
+ }
60
+ }
61
+ if (isPlainObject(val)) {
62
+ if (visited.has(val)) {
63
+ throw new Error("Circular reference detected in object");
64
+ }
65
+ visited.add(val);
66
+ try {
67
+ const result = {};
68
+ for (const [key, itemValue] of Object.entries(val)) {
69
+ const itemPath = path ? `${path}.${key}` : key;
70
+ result[key] = processValue(itemValue, itemPath);
71
+ }
72
+ return result;
73
+ } finally {
74
+ visited.delete(val);
75
+ }
76
+ }
77
+ return val;
78
+ }
79
+ return processValue(value, currentPath);
80
+ }
81
+ function deepReplaceValues(value, replaceValues) {
82
+ return applyValueReplacements(value, replaceValues, /* @__PURE__ */ new Set(), "");
83
+ }
84
+ // Annotate the CommonJS export names for ESM import in node:
85
+ 0 && (module.exports = {
86
+ deepReplaceValues
87
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Recursively traverses an object or array and allows conditional replacement of values
3
+ * based on a provided callback function. The callback receives each value and its path
4
+ * within the data structure.
5
+ *
6
+ * @param value - The input value to process (object, array, or primitive)
7
+ * @param replaceValues - Callback function that receives each value and its path.
8
+ * Return `false` to keep the original value, or `{ newValue: unknown }` to replace it.
9
+ * The path uses dot notation for objects (e.g., "user.name") and bracket notation for arrays (e.g., "items[0]")
10
+ * @returns A new structure with replaced values. The original structure is not modified.
11
+ * @throws Error if circular references are detected
12
+ *
13
+ * @example
14
+ * const data = { user: { id: 1, name: "Alice" }, scores: [85, 92] };
15
+ * const result = deepReplaceValues(data, (value, path) => {
16
+ * if (typeof value === "number") {
17
+ * return { newValue: value * 2 };
18
+ * }
19
+ * return false;
20
+ * });
21
+ * // Result: { user: { id: 2, name: "Alice" }, scores: [170, 184] }
22
+ */
23
+ declare function deepReplaceValues<T, R = T>(value: T, replaceValues: (value: unknown, path: string) => false | {
24
+ newValue: unknown;
25
+ }): R;
26
+
27
+ export { deepReplaceValues };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Recursively traverses an object or array and allows conditional replacement of values
3
+ * based on a provided callback function. The callback receives each value and its path
4
+ * within the data structure.
5
+ *
6
+ * @param value - The input value to process (object, array, or primitive)
7
+ * @param replaceValues - Callback function that receives each value and its path.
8
+ * Return `false` to keep the original value, or `{ newValue: unknown }` to replace it.
9
+ * The path uses dot notation for objects (e.g., "user.name") and bracket notation for arrays (e.g., "items[0]")
10
+ * @returns A new structure with replaced values. The original structure is not modified.
11
+ * @throws Error if circular references are detected
12
+ *
13
+ * @example
14
+ * const data = { user: { id: 1, name: "Alice" }, scores: [85, 92] };
15
+ * const result = deepReplaceValues(data, (value, path) => {
16
+ * if (typeof value === "number") {
17
+ * return { newValue: value * 2 };
18
+ * }
19
+ * return false;
20
+ * });
21
+ * // Result: { user: { id: 2, name: "Alice" }, scores: [170, 184] }
22
+ */
23
+ declare function deepReplaceValues<T, R = T>(value: T, replaceValues: (value: unknown, path: string) => false | {
24
+ newValue: unknown;
25
+ }): R;
26
+
27
+ export { deepReplaceValues };
@@ -0,0 +1,7 @@
1
+ import {
2
+ deepReplaceValues
3
+ } from "./chunk-PUKVXYYL.js";
4
+ import "./chunk-JF2MDHOJ.js";
5
+ export {
6
+ deepReplaceValues
7
+ };
@@ -39,12 +39,18 @@ function fastCache({ maxCacheSize = 1e3 } = {}) {
39
39
  }
40
40
  function getOrInsert(cacheKey, val) {
41
41
  if (!cache2.has(cacheKey)) {
42
- cache2.set(cacheKey, val());
42
+ const value = val();
43
+ cache2.set(cacheKey, value);
43
44
  trimCache();
45
+ return cache2.get(cacheKey) ?? value;
44
46
  }
45
47
  return cache2.get(cacheKey);
46
48
  }
47
- return { getOrInsert, clear: () => cache2.clear() };
49
+ return {
50
+ getOrInsert,
51
+ /** Clears all cached values */
52
+ clear: () => cache2.clear()
53
+ };
48
54
  }
49
55
 
50
56
  // src/matchPath.ts
package/dist/matchPath.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  fastCache
3
- } from "./chunk-OIAGLRII.js";
3
+ } from "./chunk-AULH7VMS.js";
4
4
  import "./chunk-5MNYPLZI.js";
5
5
  import "./chunk-HTCYUMDR.js";
6
6
  import "./chunk-II4R3VVX.js";
@@ -155,6 +155,51 @@ function deepEqual(foo, bar, maxDepth = 20) {
155
155
  return foo !== foo && bar !== bar;
156
156
  }
157
157
 
158
+ // src/deepReplaceValues.ts
159
+ function applyValueReplacements(value, replaceValues, visited, currentPath) {
160
+ function processValue(val, path) {
161
+ const replacement = replaceValues(val, path);
162
+ if (replacement !== false) {
163
+ return replacement.newValue;
164
+ }
165
+ if (Array.isArray(val)) {
166
+ if (visited.has(val)) {
167
+ throw new Error("Circular reference detected in array");
168
+ }
169
+ visited.add(val);
170
+ try {
171
+ return val.map((item, index) => {
172
+ const itemPath = path ? `${path}[${index}]` : `[${index}]`;
173
+ return processValue(item, itemPath);
174
+ });
175
+ } finally {
176
+ visited.delete(val);
177
+ }
178
+ }
179
+ if (isPlainObject(val)) {
180
+ if (visited.has(val)) {
181
+ throw new Error("Circular reference detected in object");
182
+ }
183
+ visited.add(val);
184
+ try {
185
+ const result = {};
186
+ for (const [key, itemValue] of Object.entries(val)) {
187
+ const itemPath = path ? `${path}.${key}` : key;
188
+ result[key] = processValue(itemValue, itemPath);
189
+ }
190
+ return result;
191
+ } finally {
192
+ visited.delete(val);
193
+ }
194
+ }
195
+ return val;
196
+ }
197
+ return processValue(value, currentPath);
198
+ }
199
+ function deepReplaceValues(value, replaceValues) {
200
+ return applyValueReplacements(value, replaceValues, /* @__PURE__ */ new Set(), "");
201
+ }
202
+
158
203
  // src/filterObjectOrArrayKeys.ts
159
204
  var ID_PROP_REGEXP = /^(id_|key_|id-|key-)|(_id|_key|-id|-key)$/i;
160
205
  function filterObjectOrArrayKeys(objOrArray, {
@@ -1368,7 +1413,7 @@ function compactSnapshot(value, {
1368
1413
  }
1369
1414
  }
1370
1415
  if (replaceValues) {
1371
- processedValue = applyValueReplacements(processedValue, replaceValues);
1416
+ processedValue = deepReplaceValues(processedValue, replaceValues);
1372
1417
  }
1373
1418
  processedValue = showBooleansAs ? replaceBooleansWithEmoji(processedValue, showBooleansAs) : processedValue;
1374
1419
  return `
@@ -1379,46 +1424,6 @@ ${yamlStringify(processedValue, {
1379
1424
  ...options
1380
1425
  })}`;
1381
1426
  }
1382
- function applyValueReplacements(value, replaceValues, visited = /* @__PURE__ */ new Set(), currentPath = "") {
1383
- function processValue(val, path) {
1384
- const replacement = replaceValues(val, path);
1385
- if (replacement !== false) {
1386
- return replacement.newValue;
1387
- }
1388
- if (Array.isArray(val)) {
1389
- if (visited.has(val)) {
1390
- throw new Error("Circular reference detected in array");
1391
- }
1392
- visited.add(val);
1393
- try {
1394
- return val.map((item, index) => {
1395
- const itemPath = path ? `${path}[${index}]` : `[${index}]`;
1396
- return processValue(item, itemPath);
1397
- });
1398
- } finally {
1399
- visited.delete(val);
1400
- }
1401
- }
1402
- if (isPlainObject(val)) {
1403
- if (visited.has(val)) {
1404
- throw new Error("Circular reference detected in object");
1405
- }
1406
- visited.add(val);
1407
- try {
1408
- const result = {};
1409
- for (const [key, itemValue] of Object.entries(val)) {
1410
- const itemPath = path ? `${path}.${key}` : key;
1411
- result[key] = processValue(itemValue, itemPath);
1412
- }
1413
- return result;
1414
- } finally {
1415
- visited.delete(val);
1416
- }
1417
- }
1418
- return val;
1419
- }
1420
- return processValue(value, currentPath);
1421
- }
1422
1427
  function replaceBooleansWithEmoji(value, showBooleansAs, visited = /* @__PURE__ */ new Set()) {
1423
1428
  if (showBooleansAs === false) {
1424
1429
  return value;
package/dist/testUtils.js CHANGED
@@ -6,13 +6,16 @@ import {
6
6
  pick
7
7
  } from "./chunk-Y45CE75W.js";
8
8
  import "./chunk-GMJTLFM6.js";
9
+ import {
10
+ filterObjectOrArrayKeys
11
+ } from "./chunk-6CG6JZKB.js";
9
12
  import "./chunk-IATIXMCE.js";
10
13
  import {
11
14
  deepEqual
12
15
  } from "./chunk-JQFUKJU5.js";
13
16
  import {
14
- filterObjectOrArrayKeys
15
- } from "./chunk-6CG6JZKB.js";
17
+ deepReplaceValues
18
+ } from "./chunk-PUKVXYYL.js";
16
19
  import {
17
20
  defer
18
21
  } from "./chunk-DFXNVEH6.js";
@@ -281,7 +284,7 @@ function compactSnapshot(value, {
281
284
  }
282
285
  }
283
286
  if (replaceValues) {
284
- processedValue = applyValueReplacements(processedValue, replaceValues);
287
+ processedValue = deepReplaceValues(processedValue, replaceValues);
285
288
  }
286
289
  processedValue = showBooleansAs ? replaceBooleansWithEmoji(processedValue, showBooleansAs) : processedValue;
287
290
  return `
@@ -292,46 +295,6 @@ ${yamlStringify(processedValue, {
292
295
  ...options
293
296
  })}`;
294
297
  }
295
- function applyValueReplacements(value, replaceValues, visited = /* @__PURE__ */ new Set(), currentPath = "") {
296
- function processValue(val, path) {
297
- const replacement = replaceValues(val, path);
298
- if (replacement !== false) {
299
- return replacement.newValue;
300
- }
301
- if (Array.isArray(val)) {
302
- if (visited.has(val)) {
303
- throw new Error("Circular reference detected in array");
304
- }
305
- visited.add(val);
306
- try {
307
- return val.map((item, index) => {
308
- const itemPath = path ? `${path}[${index}]` : `[${index}]`;
309
- return processValue(item, itemPath);
310
- });
311
- } finally {
312
- visited.delete(val);
313
- }
314
- }
315
- if (isPlainObject(val)) {
316
- if (visited.has(val)) {
317
- throw new Error("Circular reference detected in object");
318
- }
319
- visited.add(val);
320
- try {
321
- const result = {};
322
- for (const [key, itemValue] of Object.entries(val)) {
323
- const itemPath = path ? `${path}.${key}` : key;
324
- result[key] = processValue(itemValue, itemPath);
325
- }
326
- return result;
327
- } finally {
328
- visited.delete(val);
329
- }
330
- }
331
- return val;
332
- }
333
- return processValue(value, currentPath);
334
- }
335
298
  function replaceBooleansWithEmoji(value, showBooleansAs, visited = /* @__PURE__ */ new Set()) {
336
299
  if (showBooleansAs === false) {
337
300
  return value;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ls-stack/utils",
3
3
  "description": "Universal TypeScript utilities for browser and Node.js",
4
- "version": "3.46.0",
4
+ "version": "3.47.0",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "dist",
@@ -76,6 +76,10 @@
76
76
  "import": "./dist/deepEqual.js",
77
77
  "require": "./dist/deepEqual.cjs"
78
78
  },
79
+ "./deepReplaceValues": {
80
+ "import": "./dist/deepReplaceValues.js",
81
+ "require": "./dist/deepReplaceValues.cjs"
82
+ },
79
83
  "./enhancedMap": {
80
84
  "import": "./dist/enhancedMap.js",
81
85
  "require": "./dist/enhancedMap.cjs"