@neezco/cache 0.2.0 → 0.3.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.
@@ -100,6 +100,34 @@ interface InvalidateTagOptions {
100
100
  asStale?: boolean;
101
101
  [key: string]: unknown;
102
102
  }
103
+ /**
104
+ * Status of a cache entry.
105
+ */
106
+ declare enum ENTRY_STATUS {
107
+ /** The entry is fresh and valid. */
108
+ FRESH = "fresh",
109
+ /** The entry is stale but can still be served. */
110
+ STALE = "stale",
111
+ /** The entry has expired and is no longer valid. */
112
+ EXPIRED = "expired",
113
+ }
114
+ /**
115
+ * Metadata returned when includeMetadata is enabled in get().
116
+ * Contains complete information about a cache entry including
117
+ * timing, status, and associated tags.
118
+ */
119
+ interface EntryMetadata<T = unknown> {
120
+ /** The cached value. */
121
+ data: T;
122
+ /** Absolute timestamp when this entry becomes fully expired (in milliseconds). */
123
+ expirationTime: number;
124
+ /** Absolute timestamp when the stale window expires (in milliseconds). */
125
+ staleWindowExpiration: number;
126
+ /** Current status of the entry (fresh, stale, or expired). */
127
+ status: ENTRY_STATUS;
128
+ /** Tags associated with this entry, or null if no tags are set. */
129
+ tags: string[] | null;
130
+ }
103
131
  //#endregion
104
132
  //#region src/index.d.ts
105
133
  /**
@@ -156,22 +184,31 @@ declare class LocalTtlCache {
156
184
  * Returns the value if it exists and is not fully expired. If an entry is in the
157
185
  * stale window (expired but still within staleWindow), the stale value is returned.
158
186
  *
159
-
160
187
  * @param key - The key to retrieve
161
- * @returns The cached value if valid, undefined otherwise
188
+ * @param options - Optional configuration object
189
+ * @param options.includeMetadata - If true, returns entry metadata (data, status, expirationTime, staleWindowExpiration, tags). Defaults to false
190
+ * @returns The cached value, or entry metadata if includeMetadata is true. Returns undefined if not found or expired
162
191
  *
163
192
  * @example
164
193
  * ```typescript
165
- * const user = cache.get<{ name: string }>("user:123");
194
+ * // Get value
195
+ * const user = cache.get("user:123");
196
+ *
197
+ * // Get with metadata
198
+ * const entry = cache.get("user:123", { includeMetadata: true });
199
+ * if (entry) {
200
+ * console.log(entry.data);
201
+ * console.log(entry.status);
202
+ * }
166
203
  * ```
167
- *
168
- * @edge-cases
169
- * - Returns `undefined` if the key doesn't exist
170
- * - Returns `undefined` if the key has expired beyond the stale window
171
- * - Returns the stale value if within the stale window
172
- * - If `purgeStaleOnGet` is enabled, stale entries are deleted after being returned
173
204
  */
174
205
  get<T = unknown>(key: string): T | undefined;
206
+ get<T = unknown>(key: string, options: {
207
+ includeMetadata: true;
208
+ }): EntryMetadata<T> | undefined;
209
+ get<T = unknown>(key: string, options: {
210
+ includeMetadata: false;
211
+ }): T | undefined;
175
212
  /**
176
213
  * Sets or updates a value in the cache.
177
214
  *
@@ -296,5 +333,5 @@ declare class LocalTtlCache {
296
333
  invalidateTag(tags: string | string[], options?: InvalidateTagOptions): void;
297
334
  }
298
335
  //#endregion
299
- export { type CacheOptions, type InvalidateTagOptions, LocalTtlCache };
336
+ export { type CacheOptions, type EntryMetadata, type InvalidateTagOptions, LocalTtlCache };
300
337
  //# sourceMappingURL=index.d.cts.map
@@ -100,6 +100,34 @@ interface InvalidateTagOptions {
100
100
  asStale?: boolean;
101
101
  [key: string]: unknown;
102
102
  }
103
+ /**
104
+ * Status of a cache entry.
105
+ */
106
+ declare enum ENTRY_STATUS {
107
+ /** The entry is fresh and valid. */
108
+ FRESH = "fresh",
109
+ /** The entry is stale but can still be served. */
110
+ STALE = "stale",
111
+ /** The entry has expired and is no longer valid. */
112
+ EXPIRED = "expired",
113
+ }
114
+ /**
115
+ * Metadata returned when includeMetadata is enabled in get().
116
+ * Contains complete information about a cache entry including
117
+ * timing, status, and associated tags.
118
+ */
119
+ interface EntryMetadata<T = unknown> {
120
+ /** The cached value. */
121
+ data: T;
122
+ /** Absolute timestamp when this entry becomes fully expired (in milliseconds). */
123
+ expirationTime: number;
124
+ /** Absolute timestamp when the stale window expires (in milliseconds). */
125
+ staleWindowExpiration: number;
126
+ /** Current status of the entry (fresh, stale, or expired). */
127
+ status: ENTRY_STATUS;
128
+ /** Tags associated with this entry, or null if no tags are set. */
129
+ tags: string[] | null;
130
+ }
103
131
  //#endregion
104
132
  //#region src/index.d.ts
105
133
  /**
@@ -156,22 +184,31 @@ declare class LocalTtlCache {
156
184
  * Returns the value if it exists and is not fully expired. If an entry is in the
157
185
  * stale window (expired but still within staleWindow), the stale value is returned.
158
186
  *
159
-
160
187
  * @param key - The key to retrieve
161
- * @returns The cached value if valid, undefined otherwise
188
+ * @param options - Optional configuration object
189
+ * @param options.includeMetadata - If true, returns entry metadata (data, status, expirationTime, staleWindowExpiration, tags). Defaults to false
190
+ * @returns The cached value, or entry metadata if includeMetadata is true. Returns undefined if not found or expired
162
191
  *
163
192
  * @example
164
193
  * ```typescript
165
- * const user = cache.get<{ name: string }>("user:123");
194
+ * // Get value
195
+ * const user = cache.get("user:123");
196
+ *
197
+ * // Get with metadata
198
+ * const entry = cache.get("user:123", { includeMetadata: true });
199
+ * if (entry) {
200
+ * console.log(entry.data);
201
+ * console.log(entry.status);
202
+ * }
166
203
  * ```
167
- *
168
- * @edge-cases
169
- * - Returns `undefined` if the key doesn't exist
170
- * - Returns `undefined` if the key has expired beyond the stale window
171
- * - Returns the stale value if within the stale window
172
- * - If `purgeStaleOnGet` is enabled, stale entries are deleted after being returned
173
204
  */
174
205
  get<T = unknown>(key: string): T | undefined;
206
+ get<T = unknown>(key: string, options: {
207
+ includeMetadata: true;
208
+ }): EntryMetadata<T> | undefined;
209
+ get<T = unknown>(key: string, options: {
210
+ includeMetadata: false;
211
+ }): T | undefined;
175
212
  /**
176
213
  * Sets or updates a value in the cache.
177
214
  *
@@ -296,5 +333,5 @@ declare class LocalTtlCache {
296
333
  invalidateTag(tags: string | string[], options?: InvalidateTagOptions): void;
297
334
  }
298
335
  //#endregion
299
- export { type CacheOptions, type InvalidateTagOptions, LocalTtlCache };
336
+ export { type CacheOptions, type EntryMetadata, type InvalidateTagOptions, LocalTtlCache };
300
337
  //# sourceMappingURL=index.d.mts.map
@@ -566,11 +566,18 @@ function computeEntryStatus(state, entry, now) {
566
566
  * - It has not expired according to its own timestamps, and
567
567
  * - No associated tag imposes a stricter stale or expired rule.
568
568
  *
569
+ * `entry` can be either a {@link CacheEntry} or a pre-computed {@link ENTRY_STATUS}.
570
+ * Passing a pre-computed status avoids recalculating the entry status.
571
+ *
569
572
  * @param state - The cache state containing tag metadata.
570
- * @param entry - The cache entry being evaluated.
573
+ * @param entry - The cache entry or pre-computed status being evaluated.
574
+ * @param now - The current timestamp.
571
575
  * @returns True if the entry is fresh.
572
576
  */
573
- const isFresh = (state, entry, now) => computeEntryStatus(state, entry, now) === ENTRY_STATUS.FRESH;
577
+ const isFresh = (state, entry, now) => {
578
+ if (typeof entry === "string") return entry === ENTRY_STATUS.FRESH;
579
+ return computeEntryStatus(state, entry, now) === ENTRY_STATUS.FRESH;
580
+ };
574
581
  /**
575
582
  * Determines whether a cache entry is stale.
576
583
  *
@@ -578,11 +585,18 @@ const isFresh = (state, entry, now) => computeEntryStatus(state, entry, now) ===
578
585
  * - It has passed its TTL but is still within its stale window, or
579
586
  * - A tag imposes a stale rule that applies to this entry.
580
587
  *
588
+ * `entry` can be either a {@link CacheEntry} or a pre-computed {@link ENTRY_STATUS}.
589
+ * Passing a pre-computed status avoids recalculating the entry status.
590
+ *
581
591
  * @param state - The cache state containing tag metadata.
582
- * @param entry - The cache entry being evaluated.
592
+ * @param entry - The cache entry or pre-computed status being evaluated.
593
+ * @param now - The current timestamp.
583
594
  * @returns True if the entry is stale.
584
595
  */
585
- const isStale = (state, entry, now) => computeEntryStatus(state, entry, now) === ENTRY_STATUS.STALE;
596
+ const isStale = (state, entry, now) => {
597
+ if (typeof entry === "string") return entry === ENTRY_STATUS.STALE;
598
+ return computeEntryStatus(state, entry, now) === ENTRY_STATUS.STALE;
599
+ };
586
600
  /**
587
601
  * Determines whether a cache entry is expired.
588
602
  *
@@ -590,11 +604,18 @@ const isStale = (state, entry, now) => computeEntryStatus(state, entry, now) ===
590
604
  * - It has exceeded both its TTL and stale TTL, or
591
605
  * - A tag imposes an expiration rule that applies to this entry.
592
606
  *
607
+ * `entry` can be either a {@link CacheEntry} or a pre-computed {@link ENTRY_STATUS}.
608
+ * Passing a pre-computed status avoids recalculating the entry status.
609
+ *
593
610
  * @param state - The cache state containing tag metadata.
594
- * @param entry - The cache entry being evaluated.
611
+ * @param entry - The cache entry or pre-computed status being evaluated.
612
+ * @param now - The current timestamp.
595
613
  * @returns True if the entry is expired.
596
614
  */
597
- const isExpired = (state, entry, now) => computeEntryStatus(state, entry, now) === ENTRY_STATUS.EXPIRED;
615
+ const isExpired = (state, entry, now) => {
616
+ if (typeof entry === "string") return entry === ENTRY_STATUS.EXPIRED;
617
+ return computeEntryStatus(state, entry, now) === ENTRY_STATUS.EXPIRED;
618
+ };
598
619
 
599
620
  //#endregion
600
621
  //#region src/sweep/sweep-once.ts
@@ -619,10 +640,11 @@ function _sweepOnce(state, _maxKeysPerBatch = MAX_KEYS_PER_BATCH) {
619
640
  processed += 1;
620
641
  const [key, entry] = next.value;
621
642
  const now = Date.now();
622
- if (isExpired(state, entry, now)) {
643
+ const status = computeEntryStatus(state, entry, now);
644
+ if (isExpired(state, status, now)) {
623
645
  deleteKey(state, key, DELETE_REASON.EXPIRED);
624
646
  expiredCount += 1;
625
- } else if (isStale(state, entry, now)) {
647
+ } else if (isStale(state, status, now)) {
626
648
  staleCount += 1;
627
649
  if (state.purgeStaleOnSweep) deleteKey(state, key, DELETE_REASON.STALE);
628
650
  }
@@ -819,21 +841,40 @@ const createCache = (options = {}) => {
819
841
  //#endregion
820
842
  //#region src/cache/get.ts
821
843
  /**
822
- * Retrieves a value from the cache if the entry is valid.
844
+ * Internal function that retrieves a value from the cache with its status information.
845
+ * Returns a tuple containing the entry status and the complete cache entry.
846
+ *
823
847
  * @param state - The cache state.
824
848
  * @param key - The key to retrieve.
825
849
  * @param now - Optional timestamp override (defaults to Date.now()).
826
- * @returns The cached value if valid, null otherwise.
850
+ * @returns A tuple of [status, entry] if the entry is valid, or [null, undefined] if not found or expired.
851
+ *
852
+ * @internal
827
853
  */
828
- const get = (state, key, now = Date.now()) => {
854
+ const getWithStatus = (state, key, now = Date.now()) => {
829
855
  const entry = state.store.get(key);
830
- if (!entry) return void 0;
831
- if (isFresh(state, entry, now)) return entry[1];
832
- if (isStale(state, entry, now)) {
856
+ if (!entry) return [null, void 0];
857
+ const status = computeEntryStatus(state, entry, now);
858
+ if (isFresh(state, status, now)) return [status, entry];
859
+ if (isStale(state, status, now)) {
833
860
  if (state.purgeStaleOnGet) deleteKey(state, key, DELETE_REASON.STALE);
834
- return entry[1];
861
+ return [status, entry];
835
862
  }
836
863
  deleteKey(state, key, DELETE_REASON.EXPIRED);
864
+ return [status, void 0];
865
+ };
866
+ /**
867
+ * Retrieves a value from the cache if the entry is valid.
868
+ * @param state - The cache state.
869
+ * @param key - The key to retrieve.
870
+ * @param now - Optional timestamp override (defaults to Date.now()).
871
+ * @returns The cached value if valid, undefined otherwise.
872
+ *
873
+ * @internal
874
+ */
875
+ const get = (state, key, now = Date.now()) => {
876
+ const [, entry] = getWithStatus(state, key, now);
877
+ return entry ? entry[1] : void 0;
837
878
  };
838
879
 
839
880
  //#endregion
@@ -982,28 +1023,20 @@ var LocalTtlCache = class {
982
1023
  get size() {
983
1024
  return this.state.size;
984
1025
  }
985
- /**
986
- * Retrieves a value from the cache.
987
- *
988
- * Returns the value if it exists and is not fully expired. If an entry is in the
989
- * stale window (expired but still within staleWindow), the stale value is returned.
990
- *
991
-
992
- * @param key - The key to retrieve
993
- * @returns The cached value if valid, undefined otherwise
994
- *
995
- * @example
996
- * ```typescript
997
- * const user = cache.get<{ name: string }>("user:123");
998
- * ```
999
- *
1000
- * @edge-cases
1001
- * - Returns `undefined` if the key doesn't exist
1002
- * - Returns `undefined` if the key has expired beyond the stale window
1003
- * - Returns the stale value if within the stale window
1004
- * - If `purgeStaleOnGet` is enabled, stale entries are deleted after being returned
1005
- */
1006
- get(key) {
1026
+ get(key, options) {
1027
+ if (options?.includeMetadata) {
1028
+ const [status, entry] = getWithStatus(this.state, key);
1029
+ if (!entry) return void 0;
1030
+ const [timestamps, value, tags] = entry;
1031
+ const [, expiresAt, staleExpiresAt] = timestamps;
1032
+ return {
1033
+ data: value,
1034
+ expirationTime: expiresAt,
1035
+ staleWindowExpiration: staleExpiresAt,
1036
+ status,
1037
+ tags
1038
+ };
1039
+ }
1007
1040
  return get(this.state, key);
1008
1041
  }
1009
1042
  /**