diff --git a/README.md b/README.md
index 7812bb3b..ed87a0d2 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
> Caching for Nodejs based on Keyv
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/blob/main/LICENSE)
`Cacheable` provides a robust, scalable, and maintained set of caching packages that can be used in various projects. The packages in this repository are:
diff --git a/packages/cache-manager/README.md b/packages/cache-manager/README.md
index 1b5484c2..a3029785 100644
--- a/packages/cache-manager/README.md
+++ b/packages/cache-manager/README.md
@@ -1,7 +1,7 @@
[
](https://github.com/jaredwray/cacheable)
# cache-manager
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[](https://npmjs.com/package/cache-manager)
[](https://npmjs.com/package/cache-manager)
diff --git a/packages/cacheable-request/README.md b/packages/cacheable-request/README.md
index fa985655..40a52a71 100644
--- a/packages/cacheable-request/README.md
+++ b/packages/cacheable-request/README.md
@@ -4,7 +4,7 @@
> Wrap native HTTP requests with RFC compliant cache support
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[](https://www.npmjs.com/package/cacheable-request)
[](https://www.npmjs.com/package/cacheable-request)
diff --git a/packages/cacheable/README.md b/packages/cacheable/README.md
index c4197c78..ca78dc71 100644
--- a/packages/cacheable/README.md
+++ b/packages/cacheable/README.md
@@ -2,7 +2,7 @@
> High Performance Layer 1 / Layer 2 Caching with Keyv Storage
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[](https://www.npmjs.com/package/cacheable)
[](https://www.npmjs.com/package/cacheable)
diff --git a/packages/file-entry-cache/README.md b/packages/file-entry-cache/README.md
index 357381d1..50f9a066 100644
--- a/packages/file-entry-cache/README.md
+++ b/packages/file-entry-cache/README.md
@@ -3,7 +3,7 @@
# file-entry-cache
> A lightweight cache for file metadata, ideal for processes that work on a specific set of files and only need to reprocess files that have changed since the last run
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[](https://www.npmjs.com/package/file-entry-cache)
[](https://www.npmjs.com/package/file-entry-cache)
diff --git a/packages/flat-cache/README.md b/packages/flat-cache/README.md
index 80215f17..9d7351d7 100644
--- a/packages/flat-cache/README.md
+++ b/packages/flat-cache/README.md
@@ -3,7 +3,7 @@
# flat-cache
> A simple key/value storage using files to persist the data
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[](https://www.npmjs.com/package/flat-cache)
[](https://www.npmjs.com/package/flat-cache)
diff --git a/packages/memory/README.md b/packages/memory/README.md
index 8845b76c..8a652571 100644
--- a/packages/memory/README.md
+++ b/packages/memory/README.md
@@ -2,7 +2,7 @@
> High Performance Layer 1 / Layer 2 Caching with Keyv Storage
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[](https://www.npmjs.com/package/@cacheable/memory)
[](https://www.npmjs.com/package/@cacheable/memory)
diff --git a/packages/net/README.md b/packages/net/README.md
index 5b205240..2ef91d00 100644
--- a/packages/net/README.md
+++ b/packages/net/README.md
@@ -2,7 +2,7 @@
> High Performance Network Caching for Node.js with fetch support and HTTP cache semantics
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[](https://www.npmjs.com/package/@cacheable/net)
[](https://www.npmjs.com/package/@cacheable/net)
diff --git a/packages/node-cache/README.md b/packages/node-cache/README.md
index eda9fa9e..10bad526 100644
--- a/packages/node-cache/README.md
+++ b/packages/node-cache/README.md
@@ -4,7 +4,7 @@
> Simple and Maintained fast Node.js caching
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[](https://www.npmjs.com/package/@cacheable/node-cache)
[](https://www.npmjs.com/package/@cacheable/node-cache)
diff --git a/packages/node-cache/src/index.ts b/packages/node-cache/src/index.ts
index 77af5ed7..6516e239 100644
--- a/packages/node-cache/src/index.ts
+++ b/packages/node-cache/src/index.ts
@@ -109,10 +109,19 @@ export class NodeCache extends Hookified {
/**
* Sets a key value pair. It is possible to define a ttl (in seconds). Returns true on success.
+ *
+ * TTL behavior:
+ * - `ttl > 0`: cache expires after the given number of seconds
+ * - `ttl === 0`: cache indefinitely (overrides stdTTL)
+ * - `ttl < 0`: store the value but it expires immediately on next access (matches original node-cache behavior)
+ * - `ttl` omitted/undefined: use `stdTTL` from options (0 = unlimited if stdTTL is 0 or not set)
+ * - `ttl` as string: shorthand format like '1h', '30s', '5m', '2d'
+ *
* @param {string | number} key - it will convert the key to a string
* @param {T} value
- * @param {number | string} [ttl] - this is in seconds and undefined will use the default ttl
- * @returns {boolean}
+ * @param {number | string} [ttl] - TTL in seconds. 0 = unlimited, negative = expires immediately, string = shorthand format
+ * @returns {boolean} true on success
+ * @throws {Error} If the `key` or `ttl` is of an invalid type.
*/
public set(key: string | number, value: T, ttl?: number | string): boolean {
// Check on key type
@@ -131,15 +140,20 @@ export class NodeCache extends Hookified {
throw this.createError(NodeCacheErrors.ETTLTYPE, this.formatKey(key));
}
- // Reject negative TTL values (numeric or numeric string)
- if (this.isNegativeTtl(ttl)) {
- return false;
- }
-
const keyValue = this.formatKey(key);
let expirationTimestamp = 0; // 0 = never delete
- if (ttl !== undefined && (typeof ttl === "string" || ttl > 0)) {
+ if (this.isNegativeTtl(ttl)) {
+ // Negative TTL: store with a past timestamp so it expires immediately on next access.
+ // Math.max(1, ...) ensures the timestamp is always > 0, since the expiration
+ // check only triggers for ttl > 0 (0 means "unlimited").
+ expirationTimestamp = Math.max(
+ 1,
+ this.getExpirationTimestamp(
+ typeof ttl === "string" ? Number(ttl) : (ttl as number),
+ ),
+ );
+ } else if (ttl !== undefined && (typeof ttl === "string" || ttl > 0)) {
// Explicit positive TTL or string shorthand overrides stdTTL
expirationTimestamp = this.resolveExpiration(ttl);
} else if (
@@ -179,8 +193,16 @@ export class NodeCache extends Hookified {
/**
* Sets multiple key val pairs. It is possible to define a ttl (seconds). Returns true on success.
+ *
+ * Each item follows the same TTL behavior as `set()`:
+ * - Positive TTL: expires after the given seconds
+ * - `0`: cache indefinitely
+ * - Negative TTL: stored but expires immediately on next access
+ * - Omitted: uses `stdTTL` from options
+ *
* @param {PartialNodeCacheItem[]} data an array of key value pairs with optional ttl
- * @returns {boolean}
+ * @returns {boolean} true on success
+ * @throws {Error} If `data` is not an array, or if any item has an invalid key or ttl type.
*/
public mset(data: Array>): boolean {
// Check on keys type
@@ -189,14 +211,11 @@ export class NodeCache extends Hookified {
throw this.createError(NodeCacheErrors.EKEYSTYPE);
}
- let success = true;
for (const item of data) {
- if (!this.set(item.key, item.value, item.ttl)) {
- success = false;
- }
+ this.set(item.key, item.value, item.ttl);
}
- return success;
+ return true;
}
/**
@@ -325,19 +344,43 @@ export class NodeCache extends Hookified {
/**
* Redefine the ttl of a key. Returns true if the key has been found and changed.
* Otherwise returns false. If the ttl-argument isn't passed the default-TTL will be used.
+ *
+ * TTL behavior:
+ * - `ttl > 0`: key expires after the given number of seconds
+ * - `ttl === 0`: key lives indefinitely (overrides stdTTL)
+ * - `ttl < 0`: key expires immediately on next access (matches original node-cache behavior)
+ * - `ttl` omitted/undefined: use `stdTTL` from options (0 = unlimited if stdTTL is 0 or not set)
+ * - `ttl` as string: shorthand format like '1h', '30s', '5m', '2d'
+ *
* @param {string | number} key if the key is a number it will convert it to a string
- * @param {number | string} [ttl] the ttl in seconds if number, or a shorthand string like '1h' for 1 hour
+ * @param {number | string} [ttl] TTL in seconds. 0 = unlimited, negative = expires immediately, string = shorthand format
* @returns {boolean} true if the key has been found and changed. Otherwise returns false.
+ * @throws {Error} If the `ttl` is of an invalid type (must be a number or string).
*/
public ttl(key: string | number, ttl?: number | string): boolean {
- // Reject negative TTL values (numeric or numeric string)
- if (this.isNegativeTtl(ttl)) {
- return false;
+ // Check on ttl type
+ /* v8 ignore next -- @preserve */
+ if (
+ ttl !== undefined &&
+ typeof ttl !== "number" &&
+ typeof ttl !== "string"
+ ) {
+ throw this.createError(NodeCacheErrors.ETTLTYPE, this.formatKey(key));
}
const result = this.store.get(this.formatKey(key));
if (result) {
- if (ttl !== undefined && (typeof ttl === "string" || ttl > 0)) {
+ if (this.isNegativeTtl(ttl)) {
+ // Negative TTL: set past timestamp so it expires immediately on next access.
+ // Math.max(1, ...) ensures the timestamp is always > 0, since the expiration
+ // check only triggers for ttl > 0 (0 means "unlimited").
+ result.ttl = Math.max(
+ 1,
+ this.getExpirationTimestamp(
+ typeof ttl === "string" ? Number(ttl) : (ttl as number),
+ ),
+ );
+ } else if (ttl !== undefined && (typeof ttl === "string" || ttl > 0)) {
// Explicit positive TTL or string shorthand
result.ttl = this.resolveExpiration(ttl);
} else if (ttl === 0) {
diff --git a/packages/node-cache/test/index.test.ts b/packages/node-cache/test/index.test.ts
index 507fd148..8cfd976f 100644
--- a/packages/node-cache/test/index.test.ts
+++ b/packages/node-cache/test/index.test.ts
@@ -48,15 +48,19 @@ describe("NodeCache", () => {
expect(cache.get("baz")).toBe("qux");
});
- test("should return false from mset when any item has a negative ttl", () => {
+ test("should store items with negative ttl in mset but they expire immediately on access", () => {
const cache = new NodeCache({ checkperiod: 0 });
const list = [
{ key: "good", value: "ok" },
{ key: "bad", value: "nope", ttl: -1 },
];
const result = cache.mset(list);
- expect(result).toBe(false);
+ expect(result).toBe(true);
expect(cache.get("good")).toBe("ok");
+ // Negative TTL item is stored but expires immediately on get
+ expect(cache.has("bad")).toBe(true);
+ expect(cache.get("bad")).toBe(undefined);
+ // After get triggers expiration + deleteOnExpire, key is removed
expect(cache.has("bad")).toBe(false);
});
@@ -256,35 +260,46 @@ describe("NodeCache", () => {
expect(cache.get("unlimited")).toBe("stays");
});
- test("should return false and not store key when ttl is negative", () => {
+ test("should store key with negative ttl but it expires immediately on access", () => {
const cache = new NodeCache({ checkperiod: 0 });
const result = cache.set("foo", "bar", -1);
- expect(result).toBe(false);
- expect(cache.has("foo")).toBe(false);
+ expect(result).toBe(true);
+ // Key is stored with a past expiration timestamp
+ expect(cache.has("foo")).toBe(true);
+ // get() sees it's expired and returns undefined
expect(cache.get("foo")).toBe(undefined);
+ // After get triggers expiration + deleteOnExpire, key is removed
+ expect(cache.has("foo")).toBe(false);
});
- test("should return false on ttl() method when ttl is negative", () => {
+ test("should expire key immediately when ttl() is called with negative ttl", () => {
const cache = new NodeCache({ checkperiod: 0 });
cache.set("foo", "bar");
const result = cache.ttl("foo", -1);
- expect(result).toBe(false);
- expect(cache.get("foo")).toBe("bar");
+ expect(result).toBe(true);
+ // Key exists but will expire on next access
+ expect(cache.has("foo")).toBe(true);
+ expect(cache.get("foo")).toBe(undefined);
+ expect(cache.has("foo")).toBe(false);
});
- test("should reject negative TTL passed as a numeric string in set()", () => {
+ test("should store but expire immediately when negative TTL is passed as a numeric string in set()", () => {
const cache = new NodeCache({ checkperiod: 0 });
const result = cache.set("foo", "bar", "-1");
- expect(result).toBe(false);
+ expect(result).toBe(true);
+ expect(cache.has("foo")).toBe(true);
+ expect(cache.get("foo")).toBe(undefined);
expect(cache.has("foo")).toBe(false);
});
- test("should reject negative TTL passed as a numeric string in ttl()", () => {
+ test("should expire immediately when negative TTL is passed as a numeric string in ttl()", () => {
const cache = new NodeCache({ checkperiod: 0 });
cache.set("foo", "bar");
const result = cache.ttl("foo", "-5");
- expect(result).toBe(false);
- expect(cache.get("foo")).toBe("bar");
+ expect(result).toBe(true);
+ expect(cache.has("foo")).toBe(true);
+ expect(cache.get("foo")).toBe(undefined);
+ expect(cache.has("foo")).toBe(false);
});
test("should set unlimited expiration on ttl() method when ttl is 0", async () => {
diff --git a/packages/utils/README.md b/packages/utils/README.md
index 6ac1bdff..4714943d 100644
--- a/packages/utils/README.md
+++ b/packages/utils/README.md
@@ -2,7 +2,7 @@
> Cacheble Utils
-[](https://codecov.io/gh/jaredwray/cacheable)
+[](https://codecov.io/gh/jaredwray/cacheable)
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[](https://www.npmjs.com/package/@cacheable/utils)
[](https://www.npmjs.com/package/@cacheable/utils)