qznt 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,6 +55,7 @@ import { Cache, Storage } from "qznt";
55
55
  // Cache with a 1-minute global TTL
56
56
  const userCache = new Cache<UserData>(60000);
57
57
  userCache.set("user_1", data);
58
+ userCache.delete("user_1");
58
59
 
59
60
  // Persistent storage (Browser or NodeJS based)
60
61
  const settings = new Storage("app_settings");
package/dist/index.cjs CHANGED
@@ -63,6 +63,7 @@ __export(index_exports, {
63
63
  isSortedArray: () => isSortedArray,
64
64
  isString: () => isString,
65
65
  isToday: () => isToday,
66
+ keyedQueue: () => keyedQueue,
66
67
  lerp: () => lerp,
67
68
  memoize: () => memoize,
68
69
  merge: () => merge,
@@ -128,6 +129,14 @@ var Cache = class {
128
129
  }
129
130
  return data.value;
130
131
  }
132
+ /**
133
+ * Removes a single entry from the cache.
134
+ * @param key The key to remove
135
+ * @returns Whether an entry existed and was removed
136
+ */
137
+ delete(key) {
138
+ return this.cache.delete(key);
139
+ }
131
140
  cleanup() {
132
141
  if (this.cache.size === 0) return;
133
142
  const now = Date.now();
@@ -596,6 +605,24 @@ function unique(array, key) {
596
605
  }
597
606
 
598
607
  // src/async.ts
608
+ function keyedQueue() {
609
+ const queues = /* @__PURE__ */ new Map();
610
+ return {
611
+ run(key, work) {
612
+ const previous = queues.get(key) ?? Promise.resolve();
613
+ const result = previous.then(work);
614
+ const release = () => {
615
+ if (queues.get(key) === tail) queues.delete(key);
616
+ };
617
+ const tail = result.then(release, release);
618
+ queues.set(key, tail);
619
+ return result;
620
+ },
621
+ get size() {
622
+ return queues.size;
623
+ }
624
+ };
625
+ }
599
626
  async function retryPromise(fn, options = {}) {
600
627
  const { attempts = 3, delay = 500, timeout, signal } = options;
601
628
  if (signal?.aborted) throw new Error("Operation aborted");
@@ -711,9 +738,28 @@ function throttle(fn, limit) {
711
738
  function memoize(fn, options = {}) {
712
739
  const cache = /* @__PURE__ */ new Map();
713
740
  const { resolver, maxAge } = options;
741
+ let lastSweep = Date.now();
742
+ const getKey = (...args) => resolver ? resolver(...args) : JSON.stringify(args);
743
+ const sweep = (now) => {
744
+ if (!maxAge || now - lastSweep < maxAge) return;
745
+ lastSweep = now;
746
+ for (const [key, entry] of cache) {
747
+ if (now - entry.timestamp >= maxAge) cache.delete(key);
748
+ }
749
+ };
750
+ const cacheValue = (key, value, timestamp) => {
751
+ const entry = { value, timestamp };
752
+ cache.set(key, entry);
753
+ if (value !== null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") {
754
+ void Promise.resolve(value).catch(() => {
755
+ if (cache.get(key) === entry) cache.delete(key);
756
+ });
757
+ }
758
+ };
714
759
  const memoized = function(...args) {
715
- const key = resolver ? resolver(...args) : JSON.stringify(args);
760
+ const key = getKey(...args);
716
761
  const now = Date.now();
762
+ sweep(now);
717
763
  const entry = cache.get(key);
718
764
  if (entry) {
719
765
  if (!maxAge || now - entry.timestamp < maxAge) {
@@ -722,10 +768,17 @@ function memoize(fn, options = {}) {
722
768
  cache.delete(key);
723
769
  }
724
770
  const result = fn.apply(this, args);
725
- cache.set(key, { value: result, timestamp: now });
771
+ cacheValue(key, result, now);
726
772
  return result;
727
773
  };
728
774
  memoized.clear = () => cache.clear();
775
+ memoized.delete = (...args) => cache.delete(getKey(...args));
776
+ memoized.set = (value, ...args) => {
777
+ const key = getKey(...args);
778
+ const now = Date.now();
779
+ sweep(now);
780
+ cacheValue(key, value, now);
781
+ };
729
782
  return memoized;
730
783
  }
731
784
 
@@ -1072,6 +1125,7 @@ function toTitleCase(str, smart = true) {
1072
1125
  isSortedArray,
1073
1126
  isString,
1074
1127
  isToday,
1128
+ keyedQueue,
1075
1129
  lerp,
1076
1130
  memoize,
1077
1131
  merge,
package/dist/index.d.cts CHANGED
@@ -9,6 +9,12 @@ declare class Cache<T> {
9
9
  constructor(cleanupMs?: number);
10
10
  set(key: string | number, value: T, ttlMs?: number): void;
11
11
  get(key: string | number): T | null;
12
+ /**
13
+ * Removes a single entry from the cache.
14
+ * @param key The key to remove
15
+ * @returns Whether an entry existed and was removed
16
+ */
17
+ delete(key: string | number): boolean;
12
18
  private cleanup;
13
19
  clear(): void;
14
20
  }
@@ -202,6 +208,20 @@ interface RetryOptions {
202
208
  /** An AbortSignal to cancel the entire operation. */
203
209
  signal?: AbortSignal;
204
210
  }
211
+ /**
212
+ * Creates a queue that runs tasks in call order for each key, while different keys run concurrently.
213
+ * Task errors reach their callers without blocking later tasks. Drained keys are removed automatically.
214
+ * @example
215
+ * const queue = keyedQueue();
216
+ * const first = queue.run("user-1", () => saveInventory("user-1"));
217
+ * const second = queue.run("user-1", () => readInventory("user-1"));
218
+ * await Promise.all([first, second]); // The read runs after the save finishes
219
+ * console.log(queue.size); // 0 active keys
220
+ */
221
+ declare function keyedQueue(): {
222
+ run<T>(key: string | number, work: () => T | Promise<T>): Promise<T>;
223
+ readonly size: number;
224
+ };
205
225
  /**
206
226
  * Retries an async function until the maximum number of attempts is reached.
207
227
  *
@@ -269,17 +289,23 @@ declare function debounce<T extends (...args: unknown[]) => unknown>(fn: T, wait
269
289
  */
270
290
  declare function throttle<T extends (...args: unknown[]) => unknown>(fn: T, limit: number): (...args: Parameters<T>) => void;
271
291
 
272
- interface MemoizedFunction<T extends (...args: any[]) => any> {
292
+ interface MemoizedFunction<T extends (...args: never[]) => unknown> {
273
293
  (...args: Parameters<T>): ReturnType<T>;
274
294
  /** Clears the cache. */
275
295
  clear: () => void;
296
+ /** Removes the entry for these arguments, returning whether it existed. */
297
+ delete: (...args: Parameters<T>) => boolean;
298
+ /** Caches a value for these arguments, starting a fresh TTL. */
299
+ set: (value: ReturnType<T>, ...args: Parameters<T>) => void;
276
300
  }
277
301
  /**
278
302
  * Memoizes a function by caching its results based on the input arguments.
279
303
  * A resolver function can be provided to customize the key generation.
280
304
  * If no resolver is provided, the key will be generated by JSON stringify-ing the input arguments.
305
+ * Rejected promises are evicted. Expired entries are swept lazily on calls and writes,
306
+ * at most once per maxAge interval; no cleanup timers are created.
281
307
  * @param fn The function to memoize
282
- * @param resolver An optional resolver function to customize the key generation
308
+ * @param options Optional key resolver and cache lifetime in milliseconds
283
309
  * @example
284
310
  * // 1. Basic usage
285
311
  * const heavyCalc = memoize((n: number) => {
@@ -300,8 +326,14 @@ interface MemoizedFunction<T extends (...args: any[]) => any> {
300
326
  * @example
301
327
  * // 4. Manual cache clearing
302
328
  * heavyCalc.clear();
329
+ * @example
330
+ * // 5. Seed or invalidate one entry using the original arguments
331
+ * const getFlag = memoize(async (id: string) => readFlag(id));
332
+ * await writeFlag("drops", true);
333
+ * getFlag.set(Promise.resolve(true), "drops");
334
+ * getFlag.delete("drops");
303
335
  */
304
- declare function memoize<T extends (...args: any[]) => any>(fn: T, options?: {
336
+ declare function memoize<T extends (...args: never[]) => unknown>(fn: T, options?: {
305
337
  /** Function to generate a cache key from the input arguments. */
306
338
  resolver?: (...args: Parameters<T>) => string;
307
339
  /** The time to wait in milliseconds before expiring a cache entry. */
@@ -667,4 +699,4 @@ declare function pluralize(count: number, singular: string, plural?: string): st
667
699
  */
668
700
  declare function toTitleCase(str: string, smart?: boolean): string;
669
701
 
670
- export { type AlphaCasing, type AnyFunc, Cache, type DebouncedFunction, type DeepPartial, type FormatDurationOptions, FormatNumberCompact, Loop, type LoopEvents, type MemoizedFunction, type ParseTimeOptions, Pipe, type ReadDirOptions, type RetryOptions, type RndArrayOptions, type RndStrOptions, type SequentialMapContext, Storage, type TypedEmitter, chunk, chunkAdj, clampNumber, cluster, compactArray, createSampler, debounce, escapeRegex, forceArray, formatCurrency, formatDuration, formatETA, formatMemory, formatNumber, formatOrdinal, getAge, getFlag, getProp, hasFlag, hasProp, inverseLerp, isDefined, isEmpty, isInRange, isObject, isSortedArray, isString, isToday, lerp, memoize, merge, msToSecs, omit, parseTime, percent, pick, pluralize, prng, readDir, remap, retryPromise, rndChance, rndChoice, rndFloat, rndIndex, rndInt, rndString, searchSorted, secsToMs, seqMap, setProp, shuffle, sortBy, sum, throttle, toTitleCase, unique, wait, weightedRnd, wrap };
702
+ export { type AlphaCasing, type AnyFunc, Cache, type DebouncedFunction, type DeepPartial, type FormatDurationOptions, FormatNumberCompact, Loop, type LoopEvents, type MemoizedFunction, type ParseTimeOptions, Pipe, type ReadDirOptions, type RetryOptions, type RndArrayOptions, type RndStrOptions, type SequentialMapContext, Storage, type TypedEmitter, chunk, chunkAdj, clampNumber, cluster, compactArray, createSampler, debounce, escapeRegex, forceArray, formatCurrency, formatDuration, formatETA, formatMemory, formatNumber, formatOrdinal, getAge, getFlag, getProp, hasFlag, hasProp, inverseLerp, isDefined, isEmpty, isInRange, isObject, isSortedArray, isString, isToday, keyedQueue, lerp, memoize, merge, msToSecs, omit, parseTime, percent, pick, pluralize, prng, readDir, remap, retryPromise, rndChance, rndChoice, rndFloat, rndIndex, rndInt, rndString, searchSorted, secsToMs, seqMap, setProp, shuffle, sortBy, sum, throttle, toTitleCase, unique, wait, weightedRnd, wrap };
package/dist/index.d.ts CHANGED
@@ -9,6 +9,12 @@ declare class Cache<T> {
9
9
  constructor(cleanupMs?: number);
10
10
  set(key: string | number, value: T, ttlMs?: number): void;
11
11
  get(key: string | number): T | null;
12
+ /**
13
+ * Removes a single entry from the cache.
14
+ * @param key The key to remove
15
+ * @returns Whether an entry existed and was removed
16
+ */
17
+ delete(key: string | number): boolean;
12
18
  private cleanup;
13
19
  clear(): void;
14
20
  }
@@ -202,6 +208,20 @@ interface RetryOptions {
202
208
  /** An AbortSignal to cancel the entire operation. */
203
209
  signal?: AbortSignal;
204
210
  }
211
+ /**
212
+ * Creates a queue that runs tasks in call order for each key, while different keys run concurrently.
213
+ * Task errors reach their callers without blocking later tasks. Drained keys are removed automatically.
214
+ * @example
215
+ * const queue = keyedQueue();
216
+ * const first = queue.run("user-1", () => saveInventory("user-1"));
217
+ * const second = queue.run("user-1", () => readInventory("user-1"));
218
+ * await Promise.all([first, second]); // The read runs after the save finishes
219
+ * console.log(queue.size); // 0 active keys
220
+ */
221
+ declare function keyedQueue(): {
222
+ run<T>(key: string | number, work: () => T | Promise<T>): Promise<T>;
223
+ readonly size: number;
224
+ };
205
225
  /**
206
226
  * Retries an async function until the maximum number of attempts is reached.
207
227
  *
@@ -269,17 +289,23 @@ declare function debounce<T extends (...args: unknown[]) => unknown>(fn: T, wait
269
289
  */
270
290
  declare function throttle<T extends (...args: unknown[]) => unknown>(fn: T, limit: number): (...args: Parameters<T>) => void;
271
291
 
272
- interface MemoizedFunction<T extends (...args: any[]) => any> {
292
+ interface MemoizedFunction<T extends (...args: never[]) => unknown> {
273
293
  (...args: Parameters<T>): ReturnType<T>;
274
294
  /** Clears the cache. */
275
295
  clear: () => void;
296
+ /** Removes the entry for these arguments, returning whether it existed. */
297
+ delete: (...args: Parameters<T>) => boolean;
298
+ /** Caches a value for these arguments, starting a fresh TTL. */
299
+ set: (value: ReturnType<T>, ...args: Parameters<T>) => void;
276
300
  }
277
301
  /**
278
302
  * Memoizes a function by caching its results based on the input arguments.
279
303
  * A resolver function can be provided to customize the key generation.
280
304
  * If no resolver is provided, the key will be generated by JSON stringify-ing the input arguments.
305
+ * Rejected promises are evicted. Expired entries are swept lazily on calls and writes,
306
+ * at most once per maxAge interval; no cleanup timers are created.
281
307
  * @param fn The function to memoize
282
- * @param resolver An optional resolver function to customize the key generation
308
+ * @param options Optional key resolver and cache lifetime in milliseconds
283
309
  * @example
284
310
  * // 1. Basic usage
285
311
  * const heavyCalc = memoize((n: number) => {
@@ -300,8 +326,14 @@ interface MemoizedFunction<T extends (...args: any[]) => any> {
300
326
  * @example
301
327
  * // 4. Manual cache clearing
302
328
  * heavyCalc.clear();
329
+ * @example
330
+ * // 5. Seed or invalidate one entry using the original arguments
331
+ * const getFlag = memoize(async (id: string) => readFlag(id));
332
+ * await writeFlag("drops", true);
333
+ * getFlag.set(Promise.resolve(true), "drops");
334
+ * getFlag.delete("drops");
303
335
  */
304
- declare function memoize<T extends (...args: any[]) => any>(fn: T, options?: {
336
+ declare function memoize<T extends (...args: never[]) => unknown>(fn: T, options?: {
305
337
  /** Function to generate a cache key from the input arguments. */
306
338
  resolver?: (...args: Parameters<T>) => string;
307
339
  /** The time to wait in milliseconds before expiring a cache entry. */
@@ -667,4 +699,4 @@ declare function pluralize(count: number, singular: string, plural?: string): st
667
699
  */
668
700
  declare function toTitleCase(str: string, smart?: boolean): string;
669
701
 
670
- export { type AlphaCasing, type AnyFunc, Cache, type DebouncedFunction, type DeepPartial, type FormatDurationOptions, FormatNumberCompact, Loop, type LoopEvents, type MemoizedFunction, type ParseTimeOptions, Pipe, type ReadDirOptions, type RetryOptions, type RndArrayOptions, type RndStrOptions, type SequentialMapContext, Storage, type TypedEmitter, chunk, chunkAdj, clampNumber, cluster, compactArray, createSampler, debounce, escapeRegex, forceArray, formatCurrency, formatDuration, formatETA, formatMemory, formatNumber, formatOrdinal, getAge, getFlag, getProp, hasFlag, hasProp, inverseLerp, isDefined, isEmpty, isInRange, isObject, isSortedArray, isString, isToday, lerp, memoize, merge, msToSecs, omit, parseTime, percent, pick, pluralize, prng, readDir, remap, retryPromise, rndChance, rndChoice, rndFloat, rndIndex, rndInt, rndString, searchSorted, secsToMs, seqMap, setProp, shuffle, sortBy, sum, throttle, toTitleCase, unique, wait, weightedRnd, wrap };
702
+ export { type AlphaCasing, type AnyFunc, Cache, type DebouncedFunction, type DeepPartial, type FormatDurationOptions, FormatNumberCompact, Loop, type LoopEvents, type MemoizedFunction, type ParseTimeOptions, Pipe, type ReadDirOptions, type RetryOptions, type RndArrayOptions, type RndStrOptions, type SequentialMapContext, Storage, type TypedEmitter, chunk, chunkAdj, clampNumber, cluster, compactArray, createSampler, debounce, escapeRegex, forceArray, formatCurrency, formatDuration, formatETA, formatMemory, formatNumber, formatOrdinal, getAge, getFlag, getProp, hasFlag, hasProp, inverseLerp, isDefined, isEmpty, isInRange, isObject, isSortedArray, isString, isToday, keyedQueue, lerp, memoize, merge, msToSecs, omit, parseTime, percent, pick, pluralize, prng, readDir, remap, retryPromise, rndChance, rndChoice, rndFloat, rndIndex, rndInt, rndString, searchSorted, secsToMs, seqMap, setProp, shuffle, sortBy, sum, throttle, toTitleCase, unique, wait, weightedRnd, wrap };
package/dist/index.js CHANGED
@@ -28,6 +28,14 @@ var Cache = class {
28
28
  }
29
29
  return data.value;
30
30
  }
31
+ /**
32
+ * Removes a single entry from the cache.
33
+ * @param key The key to remove
34
+ * @returns Whether an entry existed and was removed
35
+ */
36
+ delete(key) {
37
+ return this.cache.delete(key);
38
+ }
31
39
  cleanup() {
32
40
  if (this.cache.size === 0) return;
33
41
  const now = Date.now();
@@ -496,6 +504,24 @@ function unique(array, key) {
496
504
  }
497
505
 
498
506
  // src/async.ts
507
+ function keyedQueue() {
508
+ const queues = /* @__PURE__ */ new Map();
509
+ return {
510
+ run(key, work) {
511
+ const previous = queues.get(key) ?? Promise.resolve();
512
+ const result = previous.then(work);
513
+ const release = () => {
514
+ if (queues.get(key) === tail) queues.delete(key);
515
+ };
516
+ const tail = result.then(release, release);
517
+ queues.set(key, tail);
518
+ return result;
519
+ },
520
+ get size() {
521
+ return queues.size;
522
+ }
523
+ };
524
+ }
499
525
  async function retryPromise(fn, options = {}) {
500
526
  const { attempts = 3, delay = 500, timeout, signal } = options;
501
527
  if (signal?.aborted) throw new Error("Operation aborted");
@@ -611,9 +637,28 @@ function throttle(fn, limit) {
611
637
  function memoize(fn, options = {}) {
612
638
  const cache = /* @__PURE__ */ new Map();
613
639
  const { resolver, maxAge } = options;
640
+ let lastSweep = Date.now();
641
+ const getKey = (...args) => resolver ? resolver(...args) : JSON.stringify(args);
642
+ const sweep = (now) => {
643
+ if (!maxAge || now - lastSweep < maxAge) return;
644
+ lastSweep = now;
645
+ for (const [key, entry] of cache) {
646
+ if (now - entry.timestamp >= maxAge) cache.delete(key);
647
+ }
648
+ };
649
+ const cacheValue = (key, value, timestamp) => {
650
+ const entry = { value, timestamp };
651
+ cache.set(key, entry);
652
+ if (value !== null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") {
653
+ void Promise.resolve(value).catch(() => {
654
+ if (cache.get(key) === entry) cache.delete(key);
655
+ });
656
+ }
657
+ };
614
658
  const memoized = function(...args) {
615
- const key = resolver ? resolver(...args) : JSON.stringify(args);
659
+ const key = getKey(...args);
616
660
  const now = Date.now();
661
+ sweep(now);
617
662
  const entry = cache.get(key);
618
663
  if (entry) {
619
664
  if (!maxAge || now - entry.timestamp < maxAge) {
@@ -622,10 +667,17 @@ function memoize(fn, options = {}) {
622
667
  cache.delete(key);
623
668
  }
624
669
  const result = fn.apply(this, args);
625
- cache.set(key, { value: result, timestamp: now });
670
+ cacheValue(key, result, now);
626
671
  return result;
627
672
  };
628
673
  memoized.clear = () => cache.clear();
674
+ memoized.delete = (...args) => cache.delete(getKey(...args));
675
+ memoized.set = (value, ...args) => {
676
+ const key = getKey(...args);
677
+ const now = Date.now();
678
+ sweep(now);
679
+ cacheValue(key, value, now);
680
+ };
629
681
  return memoized;
630
682
  }
631
683
 
@@ -971,6 +1023,7 @@ export {
971
1023
  isSortedArray,
972
1024
  isString,
973
1025
  isToday,
1026
+ keyedQueue,
974
1027
  lerp,
975
1028
  memoize,
976
1029
  merge,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qznt",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "A lightweight typed utility toolkit for modern TypeScript environments.",
5
5
  "license": "MIT",
6
6
  "author": "xsqu1znt",