rust-node-cache 0.2.1 → 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.
package/dist/index.d.mts CHANGED
@@ -1,24 +1,22 @@
1
1
  /**
2
2
  * rust-node-cache — public API (TypeScript).
3
3
  *
4
- * "Ultra-fast in-memory cache powered by Rust."
4
+ * "In-memory cache for Node.js that keeps your data off the V8 heap."
5
5
  *
6
- * This layer is a thin, typed wrapper over the native `Cache` class generated
7
- * by Rust/napi-rs. Why wrap it instead of re-exporting it directly?
8
- * - it allows ergonomic **generics**: `cache.get<User>("user:1")`;
9
- * - it normalizes `undefined`/`null` so a miss always returns `null`;
10
- * - it gives one place for the JSDoc and for evolving the API without breaking the Rust core.
11
- *
12
- * The cost is one extra function call per operation — negligible next to the
13
- * real work (serialization + DashMap access) done in Rust.
6
+ * This layer is a typed wrapper over the native `Cache` class generated by
7
+ * Rust/napi-rs. It does the JSON encoding on the JavaScript side, where V8's
8
+ * `JSON.stringify`/`JSON.parse` are fastest, and hands Rust plain strings:
9
+ * - `set` validates the value (see `./encode`) and stores its JSON text;
10
+ * - `get` parses the stored text back, so every read returns a fresh copy;
11
+ * - it allows ergonomic **generics**: `cache.get<User>("user:1")`.
14
12
  */
15
13
  /** Eviction strategy when `maxSize` is reached. */
16
14
  type EvictionPolicy = "reject" | "lru";
17
15
  /** Cache constructor options. */
18
16
  interface CacheOptions {
19
17
  /**
20
- * Maximum number of keys. What happens when it is reached depends on
21
- * `evictionPolicy`. No value => unlimited cache.
18
+ * Maximum number of keys (a positive integer). What happens when it is
19
+ * reached depends on `evictionPolicy`. No value => unlimited cache.
22
20
  */
23
21
  maxSize?: number;
24
22
  /**
@@ -30,12 +28,16 @@ interface CacheOptions {
30
28
  /**
31
29
  * When set (> 0), starts a thread that sweeps expired entries every N
32
30
  * seconds (background expiration, on top of the lazy one on access).
31
+ * Fractions are allowed; `0` or no value leaves it off.
33
32
  */
34
33
  cleanupIntervalSeconds?: number;
35
34
  }
36
35
  /** Per-write options. */
37
36
  interface SetOptions {
38
- /** Time to live in seconds. Absent => the entry does not expire by time. */
37
+ /**
38
+ * Time to live in seconds; fractions are allowed (`0.5` = 500 ms, minimum
39
+ * 1 ms). Absent => the entry does not expire by time.
40
+ */
39
41
  ttlSeconds?: number;
40
42
  }
41
43
  /** Accumulated cache statistics (plus the current size). */
@@ -56,8 +58,9 @@ interface CacheStats {
56
58
  size: number;
57
59
  }
58
60
  /**
59
- * In-memory cache with a Rust core (DashMap + atomic counters). Thread-safe,
60
- * with an optional TTL per entry.
61
+ * In-memory cache whose entries live in Rust memory, outside the V8 heap.
62
+ * Exact LRU eviction (O(1)), an optional TTL per entry and an optional
63
+ * background sweeper.
61
64
  *
62
65
  * @example
63
66
  * ```ts
@@ -71,24 +74,50 @@ interface CacheStats {
71
74
  declare class Cache {
72
75
  /** Instance of the native (Rust) class. */
73
76
  private readonly native;
77
+ /** @throws if `maxSize`, `evictionPolicy` or `cleanupIntervalSeconds` is invalid. */
74
78
  constructor(options?: CacheOptions);
75
79
  /** The active eviction policy: `"reject"` or `"lru"`. */
76
80
  get evictionPolicy(): EvictionPolicy;
77
81
  /**
78
- * Stores (or overwrites) a value. The value is serialized as JSON in the core.
82
+ * Stores (or overwrites) a value, kept as JSON. Accepts plain objects,
83
+ * arrays, strings, finite numbers, booleans and `null`; object properties
84
+ * set to `undefined` are left out, as in JSON.
79
85
  *
80
86
  * @returns `true` on success; `false` if `maxSize` was reached and the key is new.
87
+ * @throws TypeError naming the offending path if the value holds something
88
+ * JSON cannot represent (a `Date`, `Map`, `Set`, `BigInt`, class instance,
89
+ * function, `NaN`, `Infinity`, an `undefined` array item, or `undefined`
90
+ * itself) — instead of storing a silently different value.
81
91
  */
82
92
  set<T>(key: string, value: T, options?: SetOptions): boolean;
83
93
  /**
84
94
  * Reads a value. Applies lazy expiration: expired keys count as a miss.
95
+ * Each call returns a new copy, so mutating it does not change the cache.
85
96
  *
86
- * @returns the deserialized value, or `null` if missing or expired.
97
+ * @returns the stored value, or `null` if missing or expired.
87
98
  */
88
99
  get<T = unknown>(key: string): T | null;
100
+ /**
101
+ * Stores text that is **already JSON**, as is — no validation, no
102
+ * re-encoding. Meant for payloads serialized elsewhere, such as an HTTP
103
+ * response body. Storing text that is not valid JSON makes `get()` throw.
104
+ *
105
+ * @returns `true` on success; `false` if `maxSize` was reached and the key is new.
106
+ */
107
+ setJson(key: string, json: string, options?: SetOptions): boolean;
108
+ /**
109
+ * Reads a value as its JSON text, without parsing it — e.g. to send it as
110
+ * an HTTP response body directly. Same expiration and stats as `get()`.
111
+ *
112
+ * @returns the JSON text, or `null` if missing or expired.
113
+ */
114
+ getJson(key: string): string | null;
89
115
  /** Removes a key. Returns `true` if it existed. */
90
116
  delete(key: string): boolean;
91
- /** Whether the key exists and is still valid (not expired). */
117
+ /**
118
+ * Whether the key exists and is still valid (not expired). Does not count
119
+ * as a use for LRU eviction.
120
+ */
92
121
  exists(key: string): boolean;
93
122
  /** Empties the cache (does not reset the historical `stats` counters). */
94
123
  clear(): void;
package/dist/index.d.ts CHANGED
@@ -1,24 +1,22 @@
1
1
  /**
2
2
  * rust-node-cache — public API (TypeScript).
3
3
  *
4
- * "Ultra-fast in-memory cache powered by Rust."
4
+ * "In-memory cache for Node.js that keeps your data off the V8 heap."
5
5
  *
6
- * This layer is a thin, typed wrapper over the native `Cache` class generated
7
- * by Rust/napi-rs. Why wrap it instead of re-exporting it directly?
8
- * - it allows ergonomic **generics**: `cache.get<User>("user:1")`;
9
- * - it normalizes `undefined`/`null` so a miss always returns `null`;
10
- * - it gives one place for the JSDoc and for evolving the API without breaking the Rust core.
11
- *
12
- * The cost is one extra function call per operation — negligible next to the
13
- * real work (serialization + DashMap access) done in Rust.
6
+ * This layer is a typed wrapper over the native `Cache` class generated by
7
+ * Rust/napi-rs. It does the JSON encoding on the JavaScript side, where V8's
8
+ * `JSON.stringify`/`JSON.parse` are fastest, and hands Rust plain strings:
9
+ * - `set` validates the value (see `./encode`) and stores its JSON text;
10
+ * - `get` parses the stored text back, so every read returns a fresh copy;
11
+ * - it allows ergonomic **generics**: `cache.get<User>("user:1")`.
14
12
  */
15
13
  /** Eviction strategy when `maxSize` is reached. */
16
14
  type EvictionPolicy = "reject" | "lru";
17
15
  /** Cache constructor options. */
18
16
  interface CacheOptions {
19
17
  /**
20
- * Maximum number of keys. What happens when it is reached depends on
21
- * `evictionPolicy`. No value => unlimited cache.
18
+ * Maximum number of keys (a positive integer). What happens when it is
19
+ * reached depends on `evictionPolicy`. No value => unlimited cache.
22
20
  */
23
21
  maxSize?: number;
24
22
  /**
@@ -30,12 +28,16 @@ interface CacheOptions {
30
28
  /**
31
29
  * When set (> 0), starts a thread that sweeps expired entries every N
32
30
  * seconds (background expiration, on top of the lazy one on access).
31
+ * Fractions are allowed; `0` or no value leaves it off.
33
32
  */
34
33
  cleanupIntervalSeconds?: number;
35
34
  }
36
35
  /** Per-write options. */
37
36
  interface SetOptions {
38
- /** Time to live in seconds. Absent => the entry does not expire by time. */
37
+ /**
38
+ * Time to live in seconds; fractions are allowed (`0.5` = 500 ms, minimum
39
+ * 1 ms). Absent => the entry does not expire by time.
40
+ */
39
41
  ttlSeconds?: number;
40
42
  }
41
43
  /** Accumulated cache statistics (plus the current size). */
@@ -56,8 +58,9 @@ interface CacheStats {
56
58
  size: number;
57
59
  }
58
60
  /**
59
- * In-memory cache with a Rust core (DashMap + atomic counters). Thread-safe,
60
- * with an optional TTL per entry.
61
+ * In-memory cache whose entries live in Rust memory, outside the V8 heap.
62
+ * Exact LRU eviction (O(1)), an optional TTL per entry and an optional
63
+ * background sweeper.
61
64
  *
62
65
  * @example
63
66
  * ```ts
@@ -71,24 +74,50 @@ interface CacheStats {
71
74
  declare class Cache {
72
75
  /** Instance of the native (Rust) class. */
73
76
  private readonly native;
77
+ /** @throws if `maxSize`, `evictionPolicy` or `cleanupIntervalSeconds` is invalid. */
74
78
  constructor(options?: CacheOptions);
75
79
  /** The active eviction policy: `"reject"` or `"lru"`. */
76
80
  get evictionPolicy(): EvictionPolicy;
77
81
  /**
78
- * Stores (or overwrites) a value. The value is serialized as JSON in the core.
82
+ * Stores (or overwrites) a value, kept as JSON. Accepts plain objects,
83
+ * arrays, strings, finite numbers, booleans and `null`; object properties
84
+ * set to `undefined` are left out, as in JSON.
79
85
  *
80
86
  * @returns `true` on success; `false` if `maxSize` was reached and the key is new.
87
+ * @throws TypeError naming the offending path if the value holds something
88
+ * JSON cannot represent (a `Date`, `Map`, `Set`, `BigInt`, class instance,
89
+ * function, `NaN`, `Infinity`, an `undefined` array item, or `undefined`
90
+ * itself) — instead of storing a silently different value.
81
91
  */
82
92
  set<T>(key: string, value: T, options?: SetOptions): boolean;
83
93
  /**
84
94
  * Reads a value. Applies lazy expiration: expired keys count as a miss.
95
+ * Each call returns a new copy, so mutating it does not change the cache.
85
96
  *
86
- * @returns the deserialized value, or `null` if missing or expired.
97
+ * @returns the stored value, or `null` if missing or expired.
87
98
  */
88
99
  get<T = unknown>(key: string): T | null;
100
+ /**
101
+ * Stores text that is **already JSON**, as is — no validation, no
102
+ * re-encoding. Meant for payloads serialized elsewhere, such as an HTTP
103
+ * response body. Storing text that is not valid JSON makes `get()` throw.
104
+ *
105
+ * @returns `true` on success; `false` if `maxSize` was reached and the key is new.
106
+ */
107
+ setJson(key: string, json: string, options?: SetOptions): boolean;
108
+ /**
109
+ * Reads a value as its JSON text, without parsing it — e.g. to send it as
110
+ * an HTTP response body directly. Same expiration and stats as `get()`.
111
+ *
112
+ * @returns the JSON text, or `null` if missing or expired.
113
+ */
114
+ getJson(key: string): string | null;
89
115
  /** Removes a key. Returns `true` if it existed. */
90
116
  delete(key: string): boolean;
91
- /** Whether the key exists and is still valid (not expired). */
117
+ /**
118
+ * Whether the key exists and is still valid (not expired). Does not count
119
+ * as a use for LRU eviction.
120
+ */
92
121
  exists(key: string): boolean;
93
122
  /** Empties the cache (does not reset the historical `stats` counters). */
94
123
  clear(): void;
package/dist/index.js CHANGED
@@ -34,7 +34,91 @@ __export(index_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(index_exports);
36
36
  var native = __toESM(require("../binding.js"));
37
+
38
+ // js/encode.ts
39
+ var MAX_DEPTH = 1e3;
40
+ var HINTS = {
41
+ Date: " Store date.toISOString() or date.getTime() instead.",
42
+ Map: " Store Object.fromEntries(map) instead.",
43
+ Set: " Store [...set] instead.",
44
+ BigInt: " Store String(n) instead."
45
+ };
46
+ var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
47
+ function encode(value) {
48
+ const problem2 = check(value, 0);
49
+ if (problem2 !== void 0) throw toError(problem2);
50
+ return JSON.stringify(value);
51
+ }
52
+ function check(value, depth) {
53
+ switch (typeof value) {
54
+ case "string":
55
+ case "boolean":
56
+ return void 0;
57
+ case "number":
58
+ return Number.isFinite(value) ? void 0 : problem(String(value));
59
+ case "object": {
60
+ if (value === null) return void 0;
61
+ if (depth >= MAX_DEPTH) return tooDeep(value);
62
+ if (Array.isArray(value)) {
63
+ for (let i = 0; i < value.length; i++) {
64
+ const item = value[i];
65
+ const p = item === void 0 ? problem("undefined") : check(item, depth + 1);
66
+ if (p !== void 0) {
67
+ p.path.unshift(i);
68
+ return p;
69
+ }
70
+ }
71
+ return void 0;
72
+ }
73
+ const proto = Object.getPrototypeOf(value);
74
+ if (proto !== Object.prototype && proto !== null) {
75
+ const name = value.constructor?.name;
76
+ return problem(
77
+ name ? `a ${name}` : "an object with a custom prototype",
78
+ name && HINTS[name] || ""
79
+ );
80
+ }
81
+ for (const key in value) {
82
+ const item = value[key];
83
+ if (item === void 0) continue;
84
+ const p = check(item, depth + 1);
85
+ if (p !== void 0) {
86
+ p.path.unshift(key);
87
+ return p;
88
+ }
89
+ }
90
+ return void 0;
91
+ }
92
+ case "bigint":
93
+ return problem("a BigInt", HINTS.BigInt);
94
+ case "undefined":
95
+ return problem("undefined");
96
+ default:
97
+ return problem(`a ${typeof value}`);
98
+ }
99
+ }
100
+ function problem(what, hint = "") {
101
+ return { path: [], what, hint };
102
+ }
103
+ function toError({ path, what, hint }) {
104
+ let where = "value";
105
+ for (const part of path) {
106
+ if (typeof part === "number") where += `[${part}]`;
107
+ else if (IDENTIFIER.test(part)) where += `.${part}`;
108
+ else where += `[${JSON.stringify(part)}]`;
109
+ }
110
+ return new TypeError(
111
+ `rust-node-cache: cannot store ${where} (${what}). Values are kept as JSON, so only plain objects, arrays, strings, finite numbers, booleans and null are accepted.${hint}`
112
+ );
113
+ }
114
+ function tooDeep(value) {
115
+ JSON.stringify(value);
116
+ throw new TypeError(`rust-node-cache: cannot store a value nested more than ${MAX_DEPTH} levels deep.`);
117
+ }
118
+
119
+ // js/index.ts
37
120
  var Cache2 = class {
121
+ /** @throws if `maxSize`, `evictionPolicy` or `cleanupIntervalSeconds` is invalid. */
38
122
  constructor(options = {}) {
39
123
  const hasOptions = options.maxSize !== void 0 || options.evictionPolicy !== void 0 || options.cleanupIntervalSeconds !== void 0;
40
124
  this.native = hasOptions ? new native.Cache({
@@ -48,27 +132,56 @@ var Cache2 = class {
48
132
  return this.native.evictionPolicy;
49
133
  }
50
134
  /**
51
- * Stores (or overwrites) a value. The value is serialized as JSON in the core.
135
+ * Stores (or overwrites) a value, kept as JSON. Accepts plain objects,
136
+ * arrays, strings, finite numbers, booleans and `null`; object properties
137
+ * set to `undefined` are left out, as in JSON.
52
138
  *
53
139
  * @returns `true` on success; `false` if `maxSize` was reached and the key is new.
140
+ * @throws TypeError naming the offending path if the value holds something
141
+ * JSON cannot represent (a `Date`, `Map`, `Set`, `BigInt`, class instance,
142
+ * function, `NaN`, `Infinity`, an `undefined` array item, or `undefined`
143
+ * itself) — instead of storing a silently different value.
54
144
  */
55
145
  set(key, value, options) {
56
- return this.native.set(key, value, options);
146
+ return this.native.set(key, encode(value), options);
57
147
  }
58
148
  /**
59
149
  * Reads a value. Applies lazy expiration: expired keys count as a miss.
150
+ * Each call returns a new copy, so mutating it does not change the cache.
60
151
  *
61
- * @returns the deserialized value, or `null` if missing or expired.
152
+ * @returns the stored value, or `null` if missing or expired.
62
153
  */
63
154
  get(key) {
64
- const value = this.native.get(key);
65
- return value === null || value === void 0 ? null : value;
155
+ const json = this.native.get(key);
156
+ return json === null ? null : JSON.parse(json);
157
+ }
158
+ /**
159
+ * Stores text that is **already JSON**, as is — no validation, no
160
+ * re-encoding. Meant for payloads serialized elsewhere, such as an HTTP
161
+ * response body. Storing text that is not valid JSON makes `get()` throw.
162
+ *
163
+ * @returns `true` on success; `false` if `maxSize` was reached and the key is new.
164
+ */
165
+ setJson(key, json, options) {
166
+ return this.native.set(key, json, options);
167
+ }
168
+ /**
169
+ * Reads a value as its JSON text, without parsing it — e.g. to send it as
170
+ * an HTTP response body directly. Same expiration and stats as `get()`.
171
+ *
172
+ * @returns the JSON text, or `null` if missing or expired.
173
+ */
174
+ getJson(key) {
175
+ return this.native.get(key);
66
176
  }
67
177
  /** Removes a key. Returns `true` if it existed. */
68
178
  delete(key) {
69
179
  return this.native.delete(key);
70
180
  }
71
- /** Whether the key exists and is still valid (not expired). */
181
+ /**
182
+ * Whether the key exists and is still valid (not expired). Does not count
183
+ * as a use for LRU eviction.
184
+ */
72
185
  exists(key) {
73
186
  return this.native.exists(key);
74
187
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../js/index.ts"],"sourcesContent":["/**\n * rust-node-cache — public API (TypeScript).\n *\n * \"Ultra-fast in-memory cache powered by Rust.\"\n *\n * This layer is a thin, typed wrapper over the native `Cache` class generated\n * by Rust/napi-rs. Why wrap it instead of re-exporting it directly?\n * - it allows ergonomic **generics**: `cache.get<User>(\"user:1\")`;\n * - it normalizes `undefined`/`null` so a miss always returns `null`;\n * - it gives one place for the JSDoc and for evolving the API without breaking the Rust core.\n *\n * The cost is one extra function call per operation — negligible next to the\n * real work (serialization + DashMap access) done in Rust.\n */\n\n// Namespace import: works the same in CommonJS and ESM when consuming the native\n// addon (a CJS module generated by napi-rs).\nimport * as native from \"../binding.js\";\n\n/** Eviction strategy when `maxSize` is reached. */\nexport type EvictionPolicy = \"reject\" | \"lru\";\n\n/** Cache constructor options. */\nexport interface CacheOptions {\n /**\n * Maximum number of keys. What happens when it is reached depends on\n * `evictionPolicy`. No value => unlimited cache.\n */\n maxSize?: number;\n /**\n * Policy when `maxSize` is reached on a **new** key:\n * - `\"reject\"` (default): `set` returns `false` (overwriting still works);\n * - `\"lru\"`: removes the least recently used entry and goes on.\n */\n evictionPolicy?: EvictionPolicy;\n /**\n * When set (> 0), starts a thread that sweeps expired entries every N\n * seconds (background expiration, on top of the lazy one on access).\n */\n cleanupIntervalSeconds?: number;\n}\n\n/** Per-write options. */\nexport interface SetOptions {\n /** Time to live in seconds. Absent => the entry does not expire by time. */\n ttlSeconds?: number;\n}\n\n/** Accumulated cache statistics (plus the current size). */\nexport interface CacheStats {\n /** Reads that found a valid entry. */\n hits: number;\n /** Reads without a valid key (missing or expired). */\n misses: number;\n /** Total writes (`set`). */\n sets: number;\n /** Total explicit removals (`delete`). */\n deletes: number;\n /** Total entries removed by expiration. */\n expired: number;\n /** Total entries removed by eviction (LRU policy when `maxSize` is reached). */\n evicted: number;\n /** Number of keys stored right now. */\n size: number;\n}\n\n/**\n * In-memory cache with a Rust core (DashMap + atomic counters). Thread-safe,\n * with an optional TTL per entry.\n *\n * @example\n * ```ts\n * import { Cache } from \"rust-node-cache\";\n *\n * const cache = new Cache();\n * cache.set(\"user:1\", { id: 1, name: \"Roberto\" });\n * cache.get<{ id: number; name: string }>(\"user:1\"); // { id: 1, name: \"Roberto\" }\n * ```\n */\nexport class Cache {\n /** Instance of the native (Rust) class. */\n private readonly native: native.Cache;\n\n constructor(options: CacheOptions = {}) {\n // An options object is passed to Rust only when it carries something.\n const hasOptions =\n options.maxSize !== undefined ||\n options.evictionPolicy !== undefined ||\n options.cleanupIntervalSeconds !== undefined;\n this.native = hasOptions\n ? new native.Cache({\n maxSize: options.maxSize,\n evictionPolicy: options.evictionPolicy,\n cleanupIntervalSeconds: options.cleanupIntervalSeconds,\n })\n : new native.Cache();\n }\n\n /** The active eviction policy: `\"reject\"` or `\"lru\"`. */\n get evictionPolicy(): EvictionPolicy {\n return this.native.evictionPolicy as EvictionPolicy;\n }\n\n /**\n * Stores (or overwrites) a value. The value is serialized as JSON in the core.\n *\n * @returns `true` on success; `false` if `maxSize` was reached and the key is new.\n */\n set<T>(key: string, value: T, options?: SetOptions): boolean {\n return this.native.set(key, value as unknown as object, options);\n }\n\n /**\n * Reads a value. Applies lazy expiration: expired keys count as a miss.\n *\n * @returns the deserialized value, or `null` if missing or expired.\n */\n get<T = unknown>(key: string): T | null {\n const value = this.native.get(key);\n return value === null || value === undefined ? null : (value as T);\n }\n\n /** Removes a key. Returns `true` if it existed. */\n delete(key: string): boolean {\n return this.native.delete(key);\n }\n\n /** Whether the key exists and is still valid (not expired). */\n exists(key: string): boolean {\n return this.native.exists(key);\n }\n\n /** Empties the cache (does not reset the historical `stats` counters). */\n clear(): void {\n this.native.clear();\n }\n\n /** Number of keys currently stored. */\n size(): number {\n return this.native.size();\n }\n\n /**\n * Sweeps and removes every expired entry at once.\n *\n * @returns how many entries were removed.\n */\n cleanupExpired(): number {\n return this.native.cleanupExpired();\n }\n\n /** Returns the accumulated statistics + the current size. */\n stats(): CacheStats {\n return this.native.stats();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA,eAAAA;AAAA;AAAA;AAiBA,aAAwB;AA8DjB,IAAMA,SAAN,MAAY;AAAA,EAIjB,YAAY,UAAwB,CAAC,GAAG;AAEtC,UAAM,aACJ,QAAQ,YAAY,UACpB,QAAQ,mBAAmB,UAC3B,QAAQ,2BAA2B;AACrC,SAAK,SAAS,aACV,IAAW,aAAM;AAAA,MACf,SAAS,QAAQ;AAAA,MACjB,gBAAgB,QAAQ;AAAA,MACxB,wBAAwB,QAAQ;AAAA,IAClC,CAAC,IACD,IAAW,aAAM;AAAA,EACvB;AAAA;AAAA,EAGA,IAAI,iBAAiC;AACnC,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAO,KAAa,OAAU,SAA+B;AAC3D,WAAO,KAAK,OAAO,IAAI,KAAK,OAA4B,OAAO;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAiB,KAAuB;AACtC,UAAM,QAAQ,KAAK,OAAO,IAAI,GAAG;AACjC,WAAO,UAAU,QAAQ,UAAU,SAAY,OAAQ;AAAA,EACzD;AAAA;AAAA,EAGA,OAAO,KAAsB;AAC3B,WAAO,KAAK,OAAO,OAAO,GAAG;AAAA,EAC/B;AAAA;AAAA,EAGA,OAAO,KAAsB;AAC3B,WAAO,KAAK,OAAO,OAAO,GAAG;AAAA,EAC/B;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,OAAO,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,OAAe;AACb,WAAO,KAAK,OAAO,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAyB;AACvB,WAAO,KAAK,OAAO,eAAe;AAAA,EACpC;AAAA;AAAA,EAGA,QAAoB;AAClB,WAAO,KAAK,OAAO,MAAM;AAAA,EAC3B;AACF;","names":["Cache"]}
1
+ {"version":3,"sources":["../js/index.ts","../js/encode.ts"],"sourcesContent":["/**\n * rust-node-cache — public API (TypeScript).\n *\n * \"In-memory cache for Node.js that keeps your data off the V8 heap.\"\n *\n * This layer is a typed wrapper over the native `Cache` class generated by\n * Rust/napi-rs. It does the JSON encoding on the JavaScript side, where V8's\n * `JSON.stringify`/`JSON.parse` are fastest, and hands Rust plain strings:\n * - `set` validates the value (see `./encode`) and stores its JSON text;\n * - `get` parses the stored text back, so every read returns a fresh copy;\n * - it allows ergonomic **generics**: `cache.get<User>(\"user:1\")`.\n */\n\n// Namespace import: works the same in CommonJS and ESM when consuming the native\n// addon (a CJS module generated by napi-rs).\nimport * as native from \"../binding.js\";\nimport { encode } from \"./encode\";\n\n/** Eviction strategy when `maxSize` is reached. */\nexport type EvictionPolicy = \"reject\" | \"lru\";\n\n/** Cache constructor options. */\nexport interface CacheOptions {\n /**\n * Maximum number of keys (a positive integer). What happens when it is\n * reached depends on `evictionPolicy`. No value => unlimited cache.\n */\n maxSize?: number;\n /**\n * Policy when `maxSize` is reached on a **new** key:\n * - `\"reject\"` (default): `set` returns `false` (overwriting still works);\n * - `\"lru\"`: removes the least recently used entry and goes on.\n */\n evictionPolicy?: EvictionPolicy;\n /**\n * When set (> 0), starts a thread that sweeps expired entries every N\n * seconds (background expiration, on top of the lazy one on access).\n * Fractions are allowed; `0` or no value leaves it off.\n */\n cleanupIntervalSeconds?: number;\n}\n\n/** Per-write options. */\nexport interface SetOptions {\n /**\n * Time to live in seconds; fractions are allowed (`0.5` = 500 ms, minimum\n * 1 ms). Absent => the entry does not expire by time.\n */\n ttlSeconds?: number;\n}\n\n/** Accumulated cache statistics (plus the current size). */\nexport interface CacheStats {\n /** Reads that found a valid entry. */\n hits: number;\n /** Reads without a valid key (missing or expired). */\n misses: number;\n /** Total writes (`set`). */\n sets: number;\n /** Total explicit removals (`delete`). */\n deletes: number;\n /** Total entries removed by expiration. */\n expired: number;\n /** Total entries removed by eviction (LRU policy when `maxSize` is reached). */\n evicted: number;\n /** Number of keys stored right now. */\n size: number;\n}\n\n/**\n * In-memory cache whose entries live in Rust memory, outside the V8 heap.\n * Exact LRU eviction (O(1)), an optional TTL per entry and an optional\n * background sweeper.\n *\n * @example\n * ```ts\n * import { Cache } from \"rust-node-cache\";\n *\n * const cache = new Cache();\n * cache.set(\"user:1\", { id: 1, name: \"Roberto\" });\n * cache.get<{ id: number; name: string }>(\"user:1\"); // { id: 1, name: \"Roberto\" }\n * ```\n */\nexport class Cache {\n /** Instance of the native (Rust) class. */\n private readonly native: native.Cache;\n\n /** @throws if `maxSize`, `evictionPolicy` or `cleanupIntervalSeconds` is invalid. */\n constructor(options: CacheOptions = {}) {\n // An options object is passed to Rust only when it carries something.\n const hasOptions =\n options.maxSize !== undefined ||\n options.evictionPolicy !== undefined ||\n options.cleanupIntervalSeconds !== undefined;\n this.native = hasOptions\n ? new native.Cache({\n maxSize: options.maxSize,\n evictionPolicy: options.evictionPolicy,\n cleanupIntervalSeconds: options.cleanupIntervalSeconds,\n })\n : new native.Cache();\n }\n\n /** The active eviction policy: `\"reject\"` or `\"lru\"`. */\n get evictionPolicy(): EvictionPolicy {\n return this.native.evictionPolicy as EvictionPolicy;\n }\n\n /**\n * Stores (or overwrites) a value, kept as JSON. Accepts plain objects,\n * arrays, strings, finite numbers, booleans and `null`; object properties\n * set to `undefined` are left out, as in JSON.\n *\n * @returns `true` on success; `false` if `maxSize` was reached and the key is new.\n * @throws TypeError naming the offending path if the value holds something\n * JSON cannot represent (a `Date`, `Map`, `Set`, `BigInt`, class instance,\n * function, `NaN`, `Infinity`, an `undefined` array item, or `undefined`\n * itself) — instead of storing a silently different value.\n */\n set<T>(key: string, value: T, options?: SetOptions): boolean {\n return this.native.set(key, encode(value), options);\n }\n\n /**\n * Reads a value. Applies lazy expiration: expired keys count as a miss.\n * Each call returns a new copy, so mutating it does not change the cache.\n *\n * @returns the stored value, or `null` if missing or expired.\n */\n get<T = unknown>(key: string): T | null {\n const json = this.native.get(key);\n return json === null ? null : (JSON.parse(json) as T);\n }\n\n /**\n * Stores text that is **already JSON**, as is — no validation, no\n * re-encoding. Meant for payloads serialized elsewhere, such as an HTTP\n * response body. Storing text that is not valid JSON makes `get()` throw.\n *\n * @returns `true` on success; `false` if `maxSize` was reached and the key is new.\n */\n setJson(key: string, json: string, options?: SetOptions): boolean {\n return this.native.set(key, json, options);\n }\n\n /**\n * Reads a value as its JSON text, without parsing it — e.g. to send it as\n * an HTTP response body directly. Same expiration and stats as `get()`.\n *\n * @returns the JSON text, or `null` if missing or expired.\n */\n getJson(key: string): string | null {\n return this.native.get(key);\n }\n\n /** Removes a key. Returns `true` if it existed. */\n delete(key: string): boolean {\n return this.native.delete(key);\n }\n\n /**\n * Whether the key exists and is still valid (not expired). Does not count\n * as a use for LRU eviction.\n */\n exists(key: string): boolean {\n return this.native.exists(key);\n }\n\n /** Empties the cache (does not reset the historical `stats` counters). */\n clear(): void {\n this.native.clear();\n }\n\n /** Number of keys currently stored. */\n size(): number {\n return this.native.size();\n }\n\n /**\n * Sweeps and removes every expired entry at once.\n *\n * @returns how many entries were removed.\n */\n cleanupExpired(): number {\n return this.native.cleanupExpired();\n }\n\n /** Returns the accumulated statistics + the current size. */\n stats(): CacheStats {\n return this.native.stats();\n }\n}\n","/**\n * Value encoding for the cache: validate, then `JSON.stringify`.\n *\n * Values are kept as JSON text in Rust memory. JSON cannot represent a `Date`,\n * `Map`, `Set`, `BigInt`, class instance, `NaN`/`Infinity` or an `undefined`\n * array slot — `JSON.stringify` would silently turn them into `{}`, a string or\n * `null`, and `get()` would return something else than what was stored. So\n * those are rejected with a `TypeError` naming where they are, instead.\n */\n\n/** Nesting deeper than this is reported instead of overflowing the stack. */\nconst MAX_DEPTH = 1000;\n\nconst HINTS: Record<string, string> = {\n Date: \" Store date.toISOString() or date.getTime() instead.\",\n Map: \" Store Object.fromEntries(map) instead.\",\n Set: \" Store [...set] instead.\",\n BigInt: \" Store String(n) instead.\",\n};\n\nconst IDENTIFIER = /^[A-Za-z_$][\\w$]*$/;\n\n/** Validates `value` and returns its JSON text. Throws `TypeError` otherwise. */\nexport function encode(value: unknown): string {\n const problem = check(value, 0);\n if (problem !== undefined) throw toError(problem);\n return JSON.stringify(value);\n}\n\n/** Why a value was rejected. `path` is filled in while unwinding, root first. */\ninterface Problem {\n path: (string | number)[];\n what: string;\n hint: string;\n}\n\n/**\n * Returns `undefined` when `value` round-trips through JSON unchanged, or the\n * first problem found. The path is only built on failure, so the common case\n * stays a bare walk.\n */\nfunction check(value: unknown, depth: number): Problem | undefined {\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return undefined;\n case \"number\":\n return Number.isFinite(value) ? undefined : problem(String(value));\n case \"object\": {\n if (value === null) return undefined;\n if (depth >= MAX_DEPTH) return tooDeep(value);\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const item = value[i];\n const p = item === undefined ? problem(\"undefined\") : check(item, depth + 1);\n if (p !== undefined) {\n p.path.unshift(i);\n return p;\n }\n }\n return undefined;\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) {\n const name = (value as object).constructor?.name;\n return problem(\n name ? `a ${name}` : \"an object with a custom prototype\",\n (name && HINTS[name]) || \"\",\n );\n }\n for (const key in value) {\n const item = (value as Record<string, unknown>)[key];\n // JSON omits undefined properties, and reading one back yields undefined\n // anyway, so they are accepted.\n if (item === undefined) continue;\n const p = check(item, depth + 1);\n if (p !== undefined) {\n p.path.unshift(key);\n return p;\n }\n }\n return undefined;\n }\n case \"bigint\":\n return problem(\"a BigInt\", HINTS.BigInt);\n case \"undefined\":\n return problem(\"undefined\");\n default:\n // function, symbol\n return problem(`a ${typeof value}`);\n }\n}\n\nfunction problem(what: string, hint = \"\"): Problem {\n return { path: [], what, hint };\n}\n\nfunction toError({ path, what, hint }: Problem): TypeError {\n let where = \"value\";\n for (const part of path) {\n if (typeof part === \"number\") where += `[${part}]`;\n else if (IDENTIFIER.test(part)) where += `.${part}`;\n else where += `[${JSON.stringify(part)}]`;\n }\n return new TypeError(\n `rust-node-cache: cannot store ${where} (${what}). Values are kept as JSON, so only ` +\n `plain objects, arrays, strings, finite numbers, booleans and null are accepted.${hint}`,\n );\n}\n\nfunction tooDeep(value: unknown): never {\n // A cycle also ends up here; let JSON.stringify name it if that is the case.\n JSON.stringify(value);\n throw new TypeError(`rust-node-cache: cannot store a value nested more than ${MAX_DEPTH} levels deep.`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA,eAAAA;AAAA;AAAA;AAeA,aAAwB;;;ACJxB,IAAM,YAAY;AAElB,IAAM,QAAgC;AAAA,EACpC,MAAM;AAAA,EACN,KAAK;AAAA,EACL,KAAK;AAAA,EACL,QAAQ;AACV;AAEA,IAAM,aAAa;AAGZ,SAAS,OAAO,OAAwB;AAC7C,QAAMC,WAAU,MAAM,OAAO,CAAC;AAC9B,MAAIA,aAAY,OAAW,OAAM,QAAQA,QAAO;AAChD,SAAO,KAAK,UAAU,KAAK;AAC7B;AAcA,SAAS,MAAM,OAAgB,OAAoC;AACjE,UAAQ,OAAO,OAAO;AAAA,IACpB,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,OAAO,SAAS,KAAK,IAAI,SAAY,QAAQ,OAAO,KAAK,CAAC;AAAA,IACnE,KAAK,UAAU;AACb,UAAI,UAAU,KAAM,QAAO;AAC3B,UAAI,SAAS,UAAW,QAAO,QAAQ,KAAK;AAC5C,UAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,gBAAM,OAAO,MAAM,CAAC;AACpB,gBAAM,IAAI,SAAS,SAAY,QAAQ,WAAW,IAAI,MAAM,MAAM,QAAQ,CAAC;AAC3E,cAAI,MAAM,QAAW;AACnB,cAAE,KAAK,QAAQ,CAAC;AAChB,mBAAO;AAAA,UACT;AAAA,QACF;AACA,eAAO;AAAA,MACT;AACA,YAAM,QAAQ,OAAO,eAAe,KAAK;AACzC,UAAI,UAAU,OAAO,aAAa,UAAU,MAAM;AAChD,cAAM,OAAQ,MAAiB,aAAa;AAC5C,eAAO;AAAA,UACL,OAAO,KAAK,IAAI,KAAK;AAAA,UACpB,QAAQ,MAAM,IAAI,KAAM;AAAA,QAC3B;AAAA,MACF;AACA,iBAAW,OAAO,OAAO;AACvB,cAAM,OAAQ,MAAkC,GAAG;AAGnD,YAAI,SAAS,OAAW;AACxB,cAAM,IAAI,MAAM,MAAM,QAAQ,CAAC;AAC/B,YAAI,MAAM,QAAW;AACnB,YAAE,KAAK,QAAQ,GAAG;AAClB,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AACH,aAAO,QAAQ,YAAY,MAAM,MAAM;AAAA,IACzC,KAAK;AACH,aAAO,QAAQ,WAAW;AAAA,IAC5B;AAEE,aAAO,QAAQ,KAAK,OAAO,KAAK,EAAE;AAAA,EACtC;AACF;AAEA,SAAS,QAAQ,MAAc,OAAO,IAAa;AACjD,SAAO,EAAE,MAAM,CAAC,GAAG,MAAM,KAAK;AAChC;AAEA,SAAS,QAAQ,EAAE,MAAM,MAAM,KAAK,GAAuB;AACzD,MAAI,QAAQ;AACZ,aAAW,QAAQ,MAAM;AACvB,QAAI,OAAO,SAAS,SAAU,UAAS,IAAI,IAAI;AAAA,aACtC,WAAW,KAAK,IAAI,EAAG,UAAS,IAAI,IAAI;AAAA,QAC5C,UAAS,IAAI,KAAK,UAAU,IAAI,CAAC;AAAA,EACxC;AACA,SAAO,IAAI;AAAA,IACT,iCAAiC,KAAK,KAAK,IAAI,sHACqC,IAAI;AAAA,EAC1F;AACF;AAEA,SAAS,QAAQ,OAAuB;AAEtC,OAAK,UAAU,KAAK;AACpB,QAAM,IAAI,UAAU,0DAA0D,SAAS,eAAe;AACxG;;;AD/BO,IAAMC,SAAN,MAAY;AAAA;AAAA,EAKjB,YAAY,UAAwB,CAAC,GAAG;AAEtC,UAAM,aACJ,QAAQ,YAAY,UACpB,QAAQ,mBAAmB,UAC3B,QAAQ,2BAA2B;AACrC,SAAK,SAAS,aACV,IAAW,aAAM;AAAA,MACf,SAAS,QAAQ;AAAA,MACjB,gBAAgB,QAAQ;AAAA,MACxB,wBAAwB,QAAQ;AAAA,IAClC,CAAC,IACD,IAAW,aAAM;AAAA,EACvB;AAAA;AAAA,EAGA,IAAI,iBAAiC;AACnC,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,IAAO,KAAa,OAAU,SAA+B;AAC3D,WAAO,KAAK,OAAO,IAAI,KAAK,OAAO,KAAK,GAAG,OAAO;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAiB,KAAuB;AACtC,UAAM,OAAO,KAAK,OAAO,IAAI,GAAG;AAChC,WAAO,SAAS,OAAO,OAAQ,KAAK,MAAM,IAAI;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAQ,KAAa,MAAc,SAA+B;AAChE,WAAO,KAAK,OAAO,IAAI,KAAK,MAAM,OAAO;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAQ,KAA4B;AAClC,WAAO,KAAK,OAAO,IAAI,GAAG;AAAA,EAC5B;AAAA;AAAA,EAGA,OAAO,KAAsB;AAC3B,WAAO,KAAK,OAAO,OAAO,GAAG;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,KAAsB;AAC3B,WAAO,KAAK,OAAO,OAAO,GAAG;AAAA,EAC/B;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,OAAO,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,OAAe;AACb,WAAO,KAAK,OAAO,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAyB;AACvB,WAAO,KAAK,OAAO,eAAe;AAAA,EACpC;AAAA;AAAA,EAGA,QAAoB;AAClB,WAAO,KAAK,OAAO,MAAM;AAAA,EAC3B;AACF;","names":["Cache","problem","Cache"]}
package/dist/index.mjs CHANGED
@@ -1,6 +1,90 @@
1
1
  // js/index.ts
2
2
  import * as native from "../binding.js";
3
+
4
+ // js/encode.ts
5
+ var MAX_DEPTH = 1e3;
6
+ var HINTS = {
7
+ Date: " Store date.toISOString() or date.getTime() instead.",
8
+ Map: " Store Object.fromEntries(map) instead.",
9
+ Set: " Store [...set] instead.",
10
+ BigInt: " Store String(n) instead."
11
+ };
12
+ var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
13
+ function encode(value) {
14
+ const problem2 = check(value, 0);
15
+ if (problem2 !== void 0) throw toError(problem2);
16
+ return JSON.stringify(value);
17
+ }
18
+ function check(value, depth) {
19
+ switch (typeof value) {
20
+ case "string":
21
+ case "boolean":
22
+ return void 0;
23
+ case "number":
24
+ return Number.isFinite(value) ? void 0 : problem(String(value));
25
+ case "object": {
26
+ if (value === null) return void 0;
27
+ if (depth >= MAX_DEPTH) return tooDeep(value);
28
+ if (Array.isArray(value)) {
29
+ for (let i = 0; i < value.length; i++) {
30
+ const item = value[i];
31
+ const p = item === void 0 ? problem("undefined") : check(item, depth + 1);
32
+ if (p !== void 0) {
33
+ p.path.unshift(i);
34
+ return p;
35
+ }
36
+ }
37
+ return void 0;
38
+ }
39
+ const proto = Object.getPrototypeOf(value);
40
+ if (proto !== Object.prototype && proto !== null) {
41
+ const name = value.constructor?.name;
42
+ return problem(
43
+ name ? `a ${name}` : "an object with a custom prototype",
44
+ name && HINTS[name] || ""
45
+ );
46
+ }
47
+ for (const key in value) {
48
+ const item = value[key];
49
+ if (item === void 0) continue;
50
+ const p = check(item, depth + 1);
51
+ if (p !== void 0) {
52
+ p.path.unshift(key);
53
+ return p;
54
+ }
55
+ }
56
+ return void 0;
57
+ }
58
+ case "bigint":
59
+ return problem("a BigInt", HINTS.BigInt);
60
+ case "undefined":
61
+ return problem("undefined");
62
+ default:
63
+ return problem(`a ${typeof value}`);
64
+ }
65
+ }
66
+ function problem(what, hint = "") {
67
+ return { path: [], what, hint };
68
+ }
69
+ function toError({ path, what, hint }) {
70
+ let where = "value";
71
+ for (const part of path) {
72
+ if (typeof part === "number") where += `[${part}]`;
73
+ else if (IDENTIFIER.test(part)) where += `.${part}`;
74
+ else where += `[${JSON.stringify(part)}]`;
75
+ }
76
+ return new TypeError(
77
+ `rust-node-cache: cannot store ${where} (${what}). Values are kept as JSON, so only plain objects, arrays, strings, finite numbers, booleans and null are accepted.${hint}`
78
+ );
79
+ }
80
+ function tooDeep(value) {
81
+ JSON.stringify(value);
82
+ throw new TypeError(`rust-node-cache: cannot store a value nested more than ${MAX_DEPTH} levels deep.`);
83
+ }
84
+
85
+ // js/index.ts
3
86
  var Cache2 = class {
87
+ /** @throws if `maxSize`, `evictionPolicy` or `cleanupIntervalSeconds` is invalid. */
4
88
  constructor(options = {}) {
5
89
  const hasOptions = options.maxSize !== void 0 || options.evictionPolicy !== void 0 || options.cleanupIntervalSeconds !== void 0;
6
90
  this.native = hasOptions ? new native.Cache({
@@ -14,27 +98,56 @@ var Cache2 = class {
14
98
  return this.native.evictionPolicy;
15
99
  }
16
100
  /**
17
- * Stores (or overwrites) a value. The value is serialized as JSON in the core.
101
+ * Stores (or overwrites) a value, kept as JSON. Accepts plain objects,
102
+ * arrays, strings, finite numbers, booleans and `null`; object properties
103
+ * set to `undefined` are left out, as in JSON.
18
104
  *
19
105
  * @returns `true` on success; `false` if `maxSize` was reached and the key is new.
106
+ * @throws TypeError naming the offending path if the value holds something
107
+ * JSON cannot represent (a `Date`, `Map`, `Set`, `BigInt`, class instance,
108
+ * function, `NaN`, `Infinity`, an `undefined` array item, or `undefined`
109
+ * itself) — instead of storing a silently different value.
20
110
  */
21
111
  set(key, value, options) {
22
- return this.native.set(key, value, options);
112
+ return this.native.set(key, encode(value), options);
23
113
  }
24
114
  /**
25
115
  * Reads a value. Applies lazy expiration: expired keys count as a miss.
116
+ * Each call returns a new copy, so mutating it does not change the cache.
26
117
  *
27
- * @returns the deserialized value, or `null` if missing or expired.
118
+ * @returns the stored value, or `null` if missing or expired.
28
119
  */
29
120
  get(key) {
30
- const value = this.native.get(key);
31
- return value === null || value === void 0 ? null : value;
121
+ const json = this.native.get(key);
122
+ return json === null ? null : JSON.parse(json);
123
+ }
124
+ /**
125
+ * Stores text that is **already JSON**, as is — no validation, no
126
+ * re-encoding. Meant for payloads serialized elsewhere, such as an HTTP
127
+ * response body. Storing text that is not valid JSON makes `get()` throw.
128
+ *
129
+ * @returns `true` on success; `false` if `maxSize` was reached and the key is new.
130
+ */
131
+ setJson(key, json, options) {
132
+ return this.native.set(key, json, options);
133
+ }
134
+ /**
135
+ * Reads a value as its JSON text, without parsing it — e.g. to send it as
136
+ * an HTTP response body directly. Same expiration and stats as `get()`.
137
+ *
138
+ * @returns the JSON text, or `null` if missing or expired.
139
+ */
140
+ getJson(key) {
141
+ return this.native.get(key);
32
142
  }
33
143
  /** Removes a key. Returns `true` if it existed. */
34
144
  delete(key) {
35
145
  return this.native.delete(key);
36
146
  }
37
- /** Whether the key exists and is still valid (not expired). */
147
+ /**
148
+ * Whether the key exists and is still valid (not expired). Does not count
149
+ * as a use for LRU eviction.
150
+ */
38
151
  exists(key) {
39
152
  return this.native.exists(key);
40
153
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../js/index.ts"],"sourcesContent":["/**\n * rust-node-cache — public API (TypeScript).\n *\n * \"Ultra-fast in-memory cache powered by Rust.\"\n *\n * This layer is a thin, typed wrapper over the native `Cache` class generated\n * by Rust/napi-rs. Why wrap it instead of re-exporting it directly?\n * - it allows ergonomic **generics**: `cache.get<User>(\"user:1\")`;\n * - it normalizes `undefined`/`null` so a miss always returns `null`;\n * - it gives one place for the JSDoc and for evolving the API without breaking the Rust core.\n *\n * The cost is one extra function call per operation — negligible next to the\n * real work (serialization + DashMap access) done in Rust.\n */\n\n// Namespace import: works the same in CommonJS and ESM when consuming the native\n// addon (a CJS module generated by napi-rs).\nimport * as native from \"../binding.js\";\n\n/** Eviction strategy when `maxSize` is reached. */\nexport type EvictionPolicy = \"reject\" | \"lru\";\n\n/** Cache constructor options. */\nexport interface CacheOptions {\n /**\n * Maximum number of keys. What happens when it is reached depends on\n * `evictionPolicy`. No value => unlimited cache.\n */\n maxSize?: number;\n /**\n * Policy when `maxSize` is reached on a **new** key:\n * - `\"reject\"` (default): `set` returns `false` (overwriting still works);\n * - `\"lru\"`: removes the least recently used entry and goes on.\n */\n evictionPolicy?: EvictionPolicy;\n /**\n * When set (> 0), starts a thread that sweeps expired entries every N\n * seconds (background expiration, on top of the lazy one on access).\n */\n cleanupIntervalSeconds?: number;\n}\n\n/** Per-write options. */\nexport interface SetOptions {\n /** Time to live in seconds. Absent => the entry does not expire by time. */\n ttlSeconds?: number;\n}\n\n/** Accumulated cache statistics (plus the current size). */\nexport interface CacheStats {\n /** Reads that found a valid entry. */\n hits: number;\n /** Reads without a valid key (missing or expired). */\n misses: number;\n /** Total writes (`set`). */\n sets: number;\n /** Total explicit removals (`delete`). */\n deletes: number;\n /** Total entries removed by expiration. */\n expired: number;\n /** Total entries removed by eviction (LRU policy when `maxSize` is reached). */\n evicted: number;\n /** Number of keys stored right now. */\n size: number;\n}\n\n/**\n * In-memory cache with a Rust core (DashMap + atomic counters). Thread-safe,\n * with an optional TTL per entry.\n *\n * @example\n * ```ts\n * import { Cache } from \"rust-node-cache\";\n *\n * const cache = new Cache();\n * cache.set(\"user:1\", { id: 1, name: \"Roberto\" });\n * cache.get<{ id: number; name: string }>(\"user:1\"); // { id: 1, name: \"Roberto\" }\n * ```\n */\nexport class Cache {\n /** Instance of the native (Rust) class. */\n private readonly native: native.Cache;\n\n constructor(options: CacheOptions = {}) {\n // An options object is passed to Rust only when it carries something.\n const hasOptions =\n options.maxSize !== undefined ||\n options.evictionPolicy !== undefined ||\n options.cleanupIntervalSeconds !== undefined;\n this.native = hasOptions\n ? new native.Cache({\n maxSize: options.maxSize,\n evictionPolicy: options.evictionPolicy,\n cleanupIntervalSeconds: options.cleanupIntervalSeconds,\n })\n : new native.Cache();\n }\n\n /** The active eviction policy: `\"reject\"` or `\"lru\"`. */\n get evictionPolicy(): EvictionPolicy {\n return this.native.evictionPolicy as EvictionPolicy;\n }\n\n /**\n * Stores (or overwrites) a value. The value is serialized as JSON in the core.\n *\n * @returns `true` on success; `false` if `maxSize` was reached and the key is new.\n */\n set<T>(key: string, value: T, options?: SetOptions): boolean {\n return this.native.set(key, value as unknown as object, options);\n }\n\n /**\n * Reads a value. Applies lazy expiration: expired keys count as a miss.\n *\n * @returns the deserialized value, or `null` if missing or expired.\n */\n get<T = unknown>(key: string): T | null {\n const value = this.native.get(key);\n return value === null || value === undefined ? null : (value as T);\n }\n\n /** Removes a key. Returns `true` if it existed. */\n delete(key: string): boolean {\n return this.native.delete(key);\n }\n\n /** Whether the key exists and is still valid (not expired). */\n exists(key: string): boolean {\n return this.native.exists(key);\n }\n\n /** Empties the cache (does not reset the historical `stats` counters). */\n clear(): void {\n this.native.clear();\n }\n\n /** Number of keys currently stored. */\n size(): number {\n return this.native.size();\n }\n\n /**\n * Sweeps and removes every expired entry at once.\n *\n * @returns how many entries were removed.\n */\n cleanupExpired(): number {\n return this.native.cleanupExpired();\n }\n\n /** Returns the accumulated statistics + the current size. */\n stats(): CacheStats {\n return this.native.stats();\n }\n}\n"],"mappings":";AAiBA,YAAY,YAAY;AA8DjB,IAAMA,SAAN,MAAY;AAAA,EAIjB,YAAY,UAAwB,CAAC,GAAG;AAEtC,UAAM,aACJ,QAAQ,YAAY,UACpB,QAAQ,mBAAmB,UAC3B,QAAQ,2BAA2B;AACrC,SAAK,SAAS,aACV,IAAW,aAAM;AAAA,MACf,SAAS,QAAQ;AAAA,MACjB,gBAAgB,QAAQ;AAAA,MACxB,wBAAwB,QAAQ;AAAA,IAClC,CAAC,IACD,IAAW,aAAM;AAAA,EACvB;AAAA;AAAA,EAGA,IAAI,iBAAiC;AACnC,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAO,KAAa,OAAU,SAA+B;AAC3D,WAAO,KAAK,OAAO,IAAI,KAAK,OAA4B,OAAO;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAiB,KAAuB;AACtC,UAAM,QAAQ,KAAK,OAAO,IAAI,GAAG;AACjC,WAAO,UAAU,QAAQ,UAAU,SAAY,OAAQ;AAAA,EACzD;AAAA;AAAA,EAGA,OAAO,KAAsB;AAC3B,WAAO,KAAK,OAAO,OAAO,GAAG;AAAA,EAC/B;AAAA;AAAA,EAGA,OAAO,KAAsB;AAC3B,WAAO,KAAK,OAAO,OAAO,GAAG;AAAA,EAC/B;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,OAAO,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,OAAe;AACb,WAAO,KAAK,OAAO,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAyB;AACvB,WAAO,KAAK,OAAO,eAAe;AAAA,EACpC;AAAA;AAAA,EAGA,QAAoB;AAClB,WAAO,KAAK,OAAO,MAAM;AAAA,EAC3B;AACF;","names":["Cache"]}
1
+ {"version":3,"sources":["../js/index.ts","../js/encode.ts"],"sourcesContent":["/**\n * rust-node-cache — public API (TypeScript).\n *\n * \"In-memory cache for Node.js that keeps your data off the V8 heap.\"\n *\n * This layer is a typed wrapper over the native `Cache` class generated by\n * Rust/napi-rs. It does the JSON encoding on the JavaScript side, where V8's\n * `JSON.stringify`/`JSON.parse` are fastest, and hands Rust plain strings:\n * - `set` validates the value (see `./encode`) and stores its JSON text;\n * - `get` parses the stored text back, so every read returns a fresh copy;\n * - it allows ergonomic **generics**: `cache.get<User>(\"user:1\")`.\n */\n\n// Namespace import: works the same in CommonJS and ESM when consuming the native\n// addon (a CJS module generated by napi-rs).\nimport * as native from \"../binding.js\";\nimport { encode } from \"./encode\";\n\n/** Eviction strategy when `maxSize` is reached. */\nexport type EvictionPolicy = \"reject\" | \"lru\";\n\n/** Cache constructor options. */\nexport interface CacheOptions {\n /**\n * Maximum number of keys (a positive integer). What happens when it is\n * reached depends on `evictionPolicy`. No value => unlimited cache.\n */\n maxSize?: number;\n /**\n * Policy when `maxSize` is reached on a **new** key:\n * - `\"reject\"` (default): `set` returns `false` (overwriting still works);\n * - `\"lru\"`: removes the least recently used entry and goes on.\n */\n evictionPolicy?: EvictionPolicy;\n /**\n * When set (> 0), starts a thread that sweeps expired entries every N\n * seconds (background expiration, on top of the lazy one on access).\n * Fractions are allowed; `0` or no value leaves it off.\n */\n cleanupIntervalSeconds?: number;\n}\n\n/** Per-write options. */\nexport interface SetOptions {\n /**\n * Time to live in seconds; fractions are allowed (`0.5` = 500 ms, minimum\n * 1 ms). Absent => the entry does not expire by time.\n */\n ttlSeconds?: number;\n}\n\n/** Accumulated cache statistics (plus the current size). */\nexport interface CacheStats {\n /** Reads that found a valid entry. */\n hits: number;\n /** Reads without a valid key (missing or expired). */\n misses: number;\n /** Total writes (`set`). */\n sets: number;\n /** Total explicit removals (`delete`). */\n deletes: number;\n /** Total entries removed by expiration. */\n expired: number;\n /** Total entries removed by eviction (LRU policy when `maxSize` is reached). */\n evicted: number;\n /** Number of keys stored right now. */\n size: number;\n}\n\n/**\n * In-memory cache whose entries live in Rust memory, outside the V8 heap.\n * Exact LRU eviction (O(1)), an optional TTL per entry and an optional\n * background sweeper.\n *\n * @example\n * ```ts\n * import { Cache } from \"rust-node-cache\";\n *\n * const cache = new Cache();\n * cache.set(\"user:1\", { id: 1, name: \"Roberto\" });\n * cache.get<{ id: number; name: string }>(\"user:1\"); // { id: 1, name: \"Roberto\" }\n * ```\n */\nexport class Cache {\n /** Instance of the native (Rust) class. */\n private readonly native: native.Cache;\n\n /** @throws if `maxSize`, `evictionPolicy` or `cleanupIntervalSeconds` is invalid. */\n constructor(options: CacheOptions = {}) {\n // An options object is passed to Rust only when it carries something.\n const hasOptions =\n options.maxSize !== undefined ||\n options.evictionPolicy !== undefined ||\n options.cleanupIntervalSeconds !== undefined;\n this.native = hasOptions\n ? new native.Cache({\n maxSize: options.maxSize,\n evictionPolicy: options.evictionPolicy,\n cleanupIntervalSeconds: options.cleanupIntervalSeconds,\n })\n : new native.Cache();\n }\n\n /** The active eviction policy: `\"reject\"` or `\"lru\"`. */\n get evictionPolicy(): EvictionPolicy {\n return this.native.evictionPolicy as EvictionPolicy;\n }\n\n /**\n * Stores (or overwrites) a value, kept as JSON. Accepts plain objects,\n * arrays, strings, finite numbers, booleans and `null`; object properties\n * set to `undefined` are left out, as in JSON.\n *\n * @returns `true` on success; `false` if `maxSize` was reached and the key is new.\n * @throws TypeError naming the offending path if the value holds something\n * JSON cannot represent (a `Date`, `Map`, `Set`, `BigInt`, class instance,\n * function, `NaN`, `Infinity`, an `undefined` array item, or `undefined`\n * itself) — instead of storing a silently different value.\n */\n set<T>(key: string, value: T, options?: SetOptions): boolean {\n return this.native.set(key, encode(value), options);\n }\n\n /**\n * Reads a value. Applies lazy expiration: expired keys count as a miss.\n * Each call returns a new copy, so mutating it does not change the cache.\n *\n * @returns the stored value, or `null` if missing or expired.\n */\n get<T = unknown>(key: string): T | null {\n const json = this.native.get(key);\n return json === null ? null : (JSON.parse(json) as T);\n }\n\n /**\n * Stores text that is **already JSON**, as is — no validation, no\n * re-encoding. Meant for payloads serialized elsewhere, such as an HTTP\n * response body. Storing text that is not valid JSON makes `get()` throw.\n *\n * @returns `true` on success; `false` if `maxSize` was reached and the key is new.\n */\n setJson(key: string, json: string, options?: SetOptions): boolean {\n return this.native.set(key, json, options);\n }\n\n /**\n * Reads a value as its JSON text, without parsing it — e.g. to send it as\n * an HTTP response body directly. Same expiration and stats as `get()`.\n *\n * @returns the JSON text, or `null` if missing or expired.\n */\n getJson(key: string): string | null {\n return this.native.get(key);\n }\n\n /** Removes a key. Returns `true` if it existed. */\n delete(key: string): boolean {\n return this.native.delete(key);\n }\n\n /**\n * Whether the key exists and is still valid (not expired). Does not count\n * as a use for LRU eviction.\n */\n exists(key: string): boolean {\n return this.native.exists(key);\n }\n\n /** Empties the cache (does not reset the historical `stats` counters). */\n clear(): void {\n this.native.clear();\n }\n\n /** Number of keys currently stored. */\n size(): number {\n return this.native.size();\n }\n\n /**\n * Sweeps and removes every expired entry at once.\n *\n * @returns how many entries were removed.\n */\n cleanupExpired(): number {\n return this.native.cleanupExpired();\n }\n\n /** Returns the accumulated statistics + the current size. */\n stats(): CacheStats {\n return this.native.stats();\n }\n}\n","/**\n * Value encoding for the cache: validate, then `JSON.stringify`.\n *\n * Values are kept as JSON text in Rust memory. JSON cannot represent a `Date`,\n * `Map`, `Set`, `BigInt`, class instance, `NaN`/`Infinity` or an `undefined`\n * array slot — `JSON.stringify` would silently turn them into `{}`, a string or\n * `null`, and `get()` would return something else than what was stored. So\n * those are rejected with a `TypeError` naming where they are, instead.\n */\n\n/** Nesting deeper than this is reported instead of overflowing the stack. */\nconst MAX_DEPTH = 1000;\n\nconst HINTS: Record<string, string> = {\n Date: \" Store date.toISOString() or date.getTime() instead.\",\n Map: \" Store Object.fromEntries(map) instead.\",\n Set: \" Store [...set] instead.\",\n BigInt: \" Store String(n) instead.\",\n};\n\nconst IDENTIFIER = /^[A-Za-z_$][\\w$]*$/;\n\n/** Validates `value` and returns its JSON text. Throws `TypeError` otherwise. */\nexport function encode(value: unknown): string {\n const problem = check(value, 0);\n if (problem !== undefined) throw toError(problem);\n return JSON.stringify(value);\n}\n\n/** Why a value was rejected. `path` is filled in while unwinding, root first. */\ninterface Problem {\n path: (string | number)[];\n what: string;\n hint: string;\n}\n\n/**\n * Returns `undefined` when `value` round-trips through JSON unchanged, or the\n * first problem found. The path is only built on failure, so the common case\n * stays a bare walk.\n */\nfunction check(value: unknown, depth: number): Problem | undefined {\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return undefined;\n case \"number\":\n return Number.isFinite(value) ? undefined : problem(String(value));\n case \"object\": {\n if (value === null) return undefined;\n if (depth >= MAX_DEPTH) return tooDeep(value);\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const item = value[i];\n const p = item === undefined ? problem(\"undefined\") : check(item, depth + 1);\n if (p !== undefined) {\n p.path.unshift(i);\n return p;\n }\n }\n return undefined;\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) {\n const name = (value as object).constructor?.name;\n return problem(\n name ? `a ${name}` : \"an object with a custom prototype\",\n (name && HINTS[name]) || \"\",\n );\n }\n for (const key in value) {\n const item = (value as Record<string, unknown>)[key];\n // JSON omits undefined properties, and reading one back yields undefined\n // anyway, so they are accepted.\n if (item === undefined) continue;\n const p = check(item, depth + 1);\n if (p !== undefined) {\n p.path.unshift(key);\n return p;\n }\n }\n return undefined;\n }\n case \"bigint\":\n return problem(\"a BigInt\", HINTS.BigInt);\n case \"undefined\":\n return problem(\"undefined\");\n default:\n // function, symbol\n return problem(`a ${typeof value}`);\n }\n}\n\nfunction problem(what: string, hint = \"\"): Problem {\n return { path: [], what, hint };\n}\n\nfunction toError({ path, what, hint }: Problem): TypeError {\n let where = \"value\";\n for (const part of path) {\n if (typeof part === \"number\") where += `[${part}]`;\n else if (IDENTIFIER.test(part)) where += `.${part}`;\n else where += `[${JSON.stringify(part)}]`;\n }\n return new TypeError(\n `rust-node-cache: cannot store ${where} (${what}). Values are kept as JSON, so only ` +\n `plain objects, arrays, strings, finite numbers, booleans and null are accepted.${hint}`,\n );\n}\n\nfunction tooDeep(value: unknown): never {\n // A cycle also ends up here; let JSON.stringify name it if that is the case.\n JSON.stringify(value);\n throw new TypeError(`rust-node-cache: cannot store a value nested more than ${MAX_DEPTH} levels deep.`);\n}\n"],"mappings":"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","names":["problem","Cache"]}