rust-node-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.
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({
@@ -43,52 +127,81 @@ var Cache2 = class {
43
127
  cleanupIntervalSeconds: options.cleanupIntervalSeconds
44
128
  }) : new native.Cache();
45
129
  }
46
- /** A política de evicção ativa: `"reject"` ou `"lru"`. */
130
+ /** The active eviction policy: `"reject"` or `"lru"`. */
47
131
  get evictionPolicy() {
48
132
  return this.native.evictionPolicy;
49
133
  }
50
134
  /**
51
- * Armazena (ou sobrescreve) um valor. O valor é serializado como JSON no 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
- * @returns `true` em sucesso; `false` se `maxSize` foi atingido e a chave é nova.
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
- * Lê um valor. Aplica expiração preguiçosa: chaves vencidas contam como miss.
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 o valor desserializado, ou `null` se ausente/expirado.
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
- /** Remove uma chave. Retorna `true` se ela existia. */
177
+ /** Removes a key. Returns `true` if it existed. */
68
178
  delete(key) {
69
179
  return this.native.delete(key);
70
180
  }
71
- /** Indica se a chave existe e está válida (não expirada). */
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
  }
75
- /** Esvazia o cache (não zera os contadores históricos de `stats`). */
188
+ /** Empties the cache (does not reset the historical `stats` counters). */
76
189
  clear() {
77
190
  this.native.clear();
78
191
  }
79
- /** Número de chaves armazenadas no momento. */
192
+ /** Number of keys currently stored. */
80
193
  size() {
81
194
  return this.native.size();
82
195
  }
83
196
  /**
84
- * Varre e remove todas as entradas expiradas de uma só vez.
197
+ * Sweeps and removes every expired entry at once.
85
198
  *
86
- * @returns quantas entradas foram removidas.
199
+ * @returns how many entries were removed.
87
200
  */
88
201
  cleanupExpired() {
89
202
  return this.native.cleanupExpired();
90
203
  }
91
- /** Retorna as estatísticas acumuladas + o tamanho atual. */
204
+ /** Returns the accumulated statistics + the current size. */
92
205
  stats() {
93
206
  return this.native.stats();
94
207
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../js/index.ts"],"sourcesContent":["/**\n * rust-node-cache — API pública (TypeScript).\n *\n * \"Ultra-fast in-memory cache powered by Rust.\"\n *\n * Esta camada é um wrapper fino e tipado sobre a classe `Cache` nativa gerada\n * pelo Rust/napi-rs. Por que envolver em vez de re-exportar direto?\n * - permite **genéricos** ergonômicos: `cache.get<User>(\"user:1\")`;\n * - normaliza `undefined`/`null` para sempre devolver `null` num miss;\n * - dá um único lugar para JSDoc e para evoluir a API sem quebrar o core Rust.\n *\n * O custo é uma chamada de função extra por operação — desprezível frente ao\n * trabalho real (serialização + acesso ao DashMap) que acontece no Rust.\n */\n\n// Namespace import: funciona igual em CommonJS e ESM ao consumir o addon nativo\n// (que é um módulo CJS gerado pelo napi-rs).\nimport * as native from \"../binding.js\";\n\n/** Estratégia de evicção quando `maxSize` é atingido. */\nexport type EvictionPolicy = \"reject\" | \"lru\";\n\n/** Opções do construtor do cache. */\nexport interface CacheOptions {\n /**\n * Limite máximo de chaves. O que acontece ao atingir depende de\n * `evictionPolicy`. Sem valor => cache ilimitado.\n */\n maxSize?: number;\n /**\n * Política ao atingir `maxSize` numa chave **nova**:\n * - `\"reject\"` (padrão): `set` retorna `false` (sobrescrever continua ok);\n * - `\"lru\"`: remove a entrada menos recentemente usada e prossegue.\n */\n evictionPolicy?: EvictionPolicy;\n /**\n * Se definido (> 0), liga uma thread que varre entradas expiradas a cada\n * N segundos (expiração em background, além da preguiçosa por acesso).\n */\n cleanupIntervalSeconds?: number;\n}\n\n/** Opções por operação de escrita. */\nexport interface SetOptions {\n /** Tempo de vida em segundos. Ausente => a entrada não expira por tempo. */\n ttlSeconds?: number;\n}\n\n/** Estatísticas acumuladas do cache (mais o tamanho atual). */\nexport interface CacheStats {\n /** Leituras que encontraram uma entrada válida. */\n hits: number;\n /** Leituras sem chave válida (ausente ou expirada). */\n misses: number;\n /** Total de escritas (`set`). */\n sets: number;\n /** Total de remoções explícitas (`delete`). */\n deletes: number;\n /** Total de entradas removidas por expiração. */\n expired: number;\n /** Total de entradas removidas por evicção (política LRU ao atingir `maxSize`). */\n evicted: number;\n /** Número de chaves armazenadas agora. */\n size: number;\n}\n\n/**\n * Cache em memória ultrarrápido com núcleo em Rust (DashMap + contadores\n * atômicos). Thread-safe e com suporte a TTL por entrada.\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 /** Instância da classe nativa (Rust). */\n private readonly native: native.Cache;\n\n constructor(options: CacheOptions = {}) {\n // Só repassamos um objeto de opções ao Rust quando há algo relevante.\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 /** A política de evicção ativa: `\"reject\"` ou `\"lru\"`. */\n get evictionPolicy(): EvictionPolicy {\n return this.native.evictionPolicy as EvictionPolicy;\n }\n\n /**\n * Armazena (ou sobrescreve) um valor. O valor é serializado como JSON no core.\n *\n * @returns `true` em sucesso; `false` se `maxSize` foi atingido e a chave é nova.\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 * Lê um valor. Aplica expiração preguiçosa: chaves vencidas contam como miss.\n *\n * @returns o valor desserializado, ou `null` se ausente/expirado.\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 /** Remove uma chave. Retorna `true` se ela existia. */\n delete(key: string): boolean {\n return this.native.delete(key);\n }\n\n /** Indica se a chave existe e está válida (não expirada). */\n exists(key: string): boolean {\n return this.native.exists(key);\n }\n\n /** Esvazia o cache (não zera os contadores históricos de `stats`). */\n clear(): void {\n this.native.clear();\n }\n\n /** Número de chaves armazenadas no momento. */\n size(): number {\n return this.native.size();\n }\n\n /**\n * Varre e remove todas as entradas expiradas de uma só vez.\n *\n * @returns quantas entradas foram removidas.\n */\n cleanupExpired(): number {\n return this.native.cleanupExpired();\n }\n\n /** Retorna as estatísticas acumuladas + o tamanho atual. */\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":"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","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({
@@ -9,52 +93,81 @@ var Cache2 = class {
9
93
  cleanupIntervalSeconds: options.cleanupIntervalSeconds
10
94
  }) : new native.Cache();
11
95
  }
12
- /** A política de evicção ativa: `"reject"` ou `"lru"`. */
96
+ /** The active eviction policy: `"reject"` or `"lru"`. */
13
97
  get evictionPolicy() {
14
98
  return this.native.evictionPolicy;
15
99
  }
16
100
  /**
17
- * Armazena (ou sobrescreve) um valor. O valor é serializado como JSON no 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
- * @returns `true` em sucesso; `false` se `maxSize` foi atingido e a chave é nova.
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
- * Lê um valor. Aplica expiração preguiçosa: chaves vencidas contam como miss.
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 o valor desserializado, ou `null` se ausente/expirado.
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);
32
133
  }
33
- /** Remove uma chave. Retorna `true` se ela existia. */
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);
142
+ }
143
+ /** Removes a key. Returns `true` if it existed. */
34
144
  delete(key) {
35
145
  return this.native.delete(key);
36
146
  }
37
- /** Indica se a chave existe e está válida (não expirada). */
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
  }
41
- /** Esvazia o cache (não zera os contadores históricos de `stats`). */
154
+ /** Empties the cache (does not reset the historical `stats` counters). */
42
155
  clear() {
43
156
  this.native.clear();
44
157
  }
45
- /** Número de chaves armazenadas no momento. */
158
+ /** Number of keys currently stored. */
46
159
  size() {
47
160
  return this.native.size();
48
161
  }
49
162
  /**
50
- * Varre e remove todas as entradas expiradas de uma só vez.
163
+ * Sweeps and removes every expired entry at once.
51
164
  *
52
- * @returns quantas entradas foram removidas.
165
+ * @returns how many entries were removed.
53
166
  */
54
167
  cleanupExpired() {
55
168
  return this.native.cleanupExpired();
56
169
  }
57
- /** Retorna as estatísticas acumuladas + o tamanho atual. */
170
+ /** Returns the accumulated statistics + the current size. */
58
171
  stats() {
59
172
  return this.native.stats();
60
173
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../js/index.ts"],"sourcesContent":["/**\n * rust-node-cache — API pública (TypeScript).\n *\n * \"Ultra-fast in-memory cache powered by Rust.\"\n *\n * Esta camada é um wrapper fino e tipado sobre a classe `Cache` nativa gerada\n * pelo Rust/napi-rs. Por que envolver em vez de re-exportar direto?\n * - permite **genéricos** ergonômicos: `cache.get<User>(\"user:1\")`;\n * - normaliza `undefined`/`null` para sempre devolver `null` num miss;\n * - dá um único lugar para JSDoc e para evoluir a API sem quebrar o core Rust.\n *\n * O custo é uma chamada de função extra por operação — desprezível frente ao\n * trabalho real (serialização + acesso ao DashMap) que acontece no Rust.\n */\n\n// Namespace import: funciona igual em CommonJS e ESM ao consumir o addon nativo\n// (que é um módulo CJS gerado pelo napi-rs).\nimport * as native from \"../binding.js\";\n\n/** Estratégia de evicção quando `maxSize` é atingido. */\nexport type EvictionPolicy = \"reject\" | \"lru\";\n\n/** Opções do construtor do cache. */\nexport interface CacheOptions {\n /**\n * Limite máximo de chaves. O que acontece ao atingir depende de\n * `evictionPolicy`. Sem valor => cache ilimitado.\n */\n maxSize?: number;\n /**\n * Política ao atingir `maxSize` numa chave **nova**:\n * - `\"reject\"` (padrão): `set` retorna `false` (sobrescrever continua ok);\n * - `\"lru\"`: remove a entrada menos recentemente usada e prossegue.\n */\n evictionPolicy?: EvictionPolicy;\n /**\n * Se definido (> 0), liga uma thread que varre entradas expiradas a cada\n * N segundos (expiração em background, além da preguiçosa por acesso).\n */\n cleanupIntervalSeconds?: number;\n}\n\n/** Opções por operação de escrita. */\nexport interface SetOptions {\n /** Tempo de vida em segundos. Ausente => a entrada não expira por tempo. */\n ttlSeconds?: number;\n}\n\n/** Estatísticas acumuladas do cache (mais o tamanho atual). */\nexport interface CacheStats {\n /** Leituras que encontraram uma entrada válida. */\n hits: number;\n /** Leituras sem chave válida (ausente ou expirada). */\n misses: number;\n /** Total de escritas (`set`). */\n sets: number;\n /** Total de remoções explícitas (`delete`). */\n deletes: number;\n /** Total de entradas removidas por expiração. */\n expired: number;\n /** Total de entradas removidas por evicção (política LRU ao atingir `maxSize`). */\n evicted: number;\n /** Número de chaves armazenadas agora. */\n size: number;\n}\n\n/**\n * Cache em memória ultrarrápido com núcleo em Rust (DashMap + contadores\n * atômicos). Thread-safe e com suporte a TTL por entrada.\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 /** Instância da classe nativa (Rust). */\n private readonly native: native.Cache;\n\n constructor(options: CacheOptions = {}) {\n // Só repassamos um objeto de opções ao Rust quando há algo relevante.\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 /** A política de evicção ativa: `\"reject\"` ou `\"lru\"`. */\n get evictionPolicy(): EvictionPolicy {\n return this.native.evictionPolicy as EvictionPolicy;\n }\n\n /**\n * Armazena (ou sobrescreve) um valor. O valor é serializado como JSON no core.\n *\n * @returns `true` em sucesso; `false` se `maxSize` foi atingido e a chave é nova.\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 * Lê um valor. Aplica expiração preguiçosa: chaves vencidas contam como miss.\n *\n * @returns o valor desserializado, ou `null` se ausente/expirado.\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 /** Remove uma chave. Retorna `true` se ela existia. */\n delete(key: string): boolean {\n return this.native.delete(key);\n }\n\n /** Indica se a chave existe e está válida (não expirada). */\n exists(key: string): boolean {\n return this.native.exists(key);\n }\n\n /** Esvazia o cache (não zera os contadores históricos de `stats`). */\n clear(): void {\n this.native.clear();\n }\n\n /** Número de chaves armazenadas no momento. */\n size(): number {\n return this.native.size();\n }\n\n /**\n * Varre e remove todas as entradas expiradas de uma só vez.\n *\n * @returns quantas entradas foram removidas.\n */\n cleanupExpired(): number {\n return this.native.cleanupExpired();\n }\n\n /** Retorna as estatísticas acumuladas + o tamanho atual. */\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"]}
package/dist/nestjs.d.mts CHANGED
@@ -3,36 +3,36 @@ import { Observable } from 'rxjs';
3
3
  import { Cache } from './index.mjs';
4
4
 
5
5
  /**
6
- * Integração com NestJS: interceptor de cache de respostas.
6
+ * NestJS integration: a response caching interceptor.
7
7
  *
8
- * Uso:
8
+ * Usage:
9
9
  * import { Cache } from "rust-node-cache";
10
10
  * import { CacheInterceptor } from "rust-node-cache/nestjs";
11
11
  *
12
12
  * const cache = new Cache();
13
13
  * app.useGlobalInterceptors(new CacheInterceptor({ cache, ttlSeconds: 60 }));
14
14
  *
15
- * Usamos `import type` para `@nestjs/common`, ou seja, NÃO há import em runtime
16
- * do Nest aqui — o pacote não força o Nest como dependência. A única dependência
17
- * de runtime são os operadores `of`/`tap` do RxJS (presentes em qualquer app Nest).
15
+ * `@nestjs/common` is imported with `import type`, so there is NO runtime import
16
+ * of Nest here — the package does not force Nest as a dependency. The only runtime
17
+ * dependencies are RxJS's `of`/`tap` operators (present in any Nest app).
18
18
  */
19
19
 
20
20
  interface CacheInterceptorOptions {
21
- /** Instância de cache a usar. */
21
+ /** Cache instance to use. */
22
22
  cache: Cache;
23
- /** TTL aplicado às respostas guardadas (em segundos). */
23
+ /** TTL applied to stored responses (in seconds). */
24
24
  ttlSeconds?: number;
25
- /** Métodos HTTP elegíveis para cache. Padrão: `["GET"]`. */
25
+ /** HTTP methods eligible for caching. Default: `["GET"]`. */
26
26
  methods?: string[];
27
- /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */
27
+ /** Builds the cache key from the request. Default: `METHOD:url`. */
28
28
  keyGenerator?: (req: {
29
29
  method: string;
30
30
  url: string;
31
31
  }) => string;
32
32
  }
33
33
  /**
34
- * Interceptor NestJS que cacheia o valor retornado pelos handlers. Num HIT,
35
- * devolve o valor do cache sem executar o handler.
34
+ * NestJS interceptor that caches the value returned by handlers. On a HIT, it
35
+ * returns the cached value without running the handler.
36
36
  */
37
37
  declare class CacheInterceptor implements NestInterceptor {
38
38
  private readonly cache;
package/dist/nestjs.d.ts CHANGED
@@ -3,36 +3,36 @@ import { Observable } from 'rxjs';
3
3
  import { Cache } from './index.js';
4
4
 
5
5
  /**
6
- * Integração com NestJS: interceptor de cache de respostas.
6
+ * NestJS integration: a response caching interceptor.
7
7
  *
8
- * Uso:
8
+ * Usage:
9
9
  * import { Cache } from "rust-node-cache";
10
10
  * import { CacheInterceptor } from "rust-node-cache/nestjs";
11
11
  *
12
12
  * const cache = new Cache();
13
13
  * app.useGlobalInterceptors(new CacheInterceptor({ cache, ttlSeconds: 60 }));
14
14
  *
15
- * Usamos `import type` para `@nestjs/common`, ou seja, NÃO há import em runtime
16
- * do Nest aqui — o pacote não força o Nest como dependência. A única dependência
17
- * de runtime são os operadores `of`/`tap` do RxJS (presentes em qualquer app Nest).
15
+ * `@nestjs/common` is imported with `import type`, so there is NO runtime import
16
+ * of Nest here — the package does not force Nest as a dependency. The only runtime
17
+ * dependencies are RxJS's `of`/`tap` operators (present in any Nest app).
18
18
  */
19
19
 
20
20
  interface CacheInterceptorOptions {
21
- /** Instância de cache a usar. */
21
+ /** Cache instance to use. */
22
22
  cache: Cache;
23
- /** TTL aplicado às respostas guardadas (em segundos). */
23
+ /** TTL applied to stored responses (in seconds). */
24
24
  ttlSeconds?: number;
25
- /** Métodos HTTP elegíveis para cache. Padrão: `["GET"]`. */
25
+ /** HTTP methods eligible for caching. Default: `["GET"]`. */
26
26
  methods?: string[];
27
- /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */
27
+ /** Builds the cache key from the request. Default: `METHOD:url`. */
28
28
  keyGenerator?: (req: {
29
29
  method: string;
30
30
  url: string;
31
31
  }) => string;
32
32
  }
33
33
  /**
34
- * Interceptor NestJS que cacheia o valor retornado pelos handlers. Num HIT,
35
- * devolve o valor do cache sem executar o handler.
34
+ * NestJS interceptor that caches the value returned by handlers. On a HIT, it
35
+ * returns the cached value without running the handler.
36
36
  */
37
37
  declare class CacheInterceptor implements NestInterceptor {
38
38
  private readonly cache;
package/dist/nestjs.js CHANGED
@@ -43,10 +43,11 @@ var CacheInterceptor = class {
43
43
  }
44
44
  return next.handle().pipe(
45
45
  (0, import_rxjs.tap)((body) => {
46
- if (body !== void 0 && body !== null) {
47
- this.cache.set(
46
+ const json = toJson(body);
47
+ if (json !== void 0 && json !== "null") {
48
+ this.cache.setJson(
48
49
  key,
49
- body,
50
+ json,
50
51
  this.ttlSeconds ? { ttlSeconds: this.ttlSeconds } : void 0
51
52
  );
52
53
  }
@@ -54,6 +55,13 @@ var CacheInterceptor = class {
54
55
  );
55
56
  }
56
57
  };
58
+ function toJson(body) {
59
+ try {
60
+ return JSON.stringify(body);
61
+ } catch {
62
+ return void 0;
63
+ }
64
+ }
57
65
  // Annotate the CommonJS export names for ESM import in node:
58
66
  0 && (module.exports = {
59
67
  CacheInterceptor
@@ -1 +1 @@
1
- {"version":3,"sources":["../js/nestjs.ts"],"sourcesContent":["/**\n * Integração com NestJS: interceptor de cache de respostas.\n *\n * Uso:\n * import { Cache } from \"rust-node-cache\";\n * import { CacheInterceptor } from \"rust-node-cache/nestjs\";\n *\n * const cache = new Cache();\n * app.useGlobalInterceptors(new CacheInterceptor({ cache, ttlSeconds: 60 }));\n *\n * Usamos `import type` para `@nestjs/common`, ou seja, NÃO há import em runtime\n * do Nest aqui — o pacote não força o Nest como dependência. A única dependência\n * de runtime são os operadores `of`/`tap` do RxJS (presentes em qualquer app Nest).\n */\n\nimport type {\n CallHandler,\n ExecutionContext,\n NestInterceptor,\n} from \"@nestjs/common\";\nimport type { Observable } from \"rxjs\";\nimport { of, tap } from \"rxjs\";\nimport type { Cache } from \"./index\";\n\nexport interface CacheInterceptorOptions {\n /** Instância de cache a usar. */\n cache: Cache;\n /** TTL aplicado às respostas guardadas (em segundos). */\n ttlSeconds?: number;\n /** Métodos HTTP elegíveis para cache. Padrão: `[\"GET\"]`. */\n methods?: string[];\n /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */\n keyGenerator?: (req: { method: string; url: string }) => string;\n}\n\n/**\n * Interceptor NestJS que cacheia o valor retornado pelos handlers. Num HIT,\n * devolve o valor do cache sem executar o handler.\n */\nexport class CacheInterceptor implements NestInterceptor {\n private readonly cache: Cache;\n private readonly ttlSeconds?: number;\n private readonly methods: string[];\n private readonly keyGenerator: (req: {\n method: string;\n url: string;\n }) => string;\n\n constructor(options: CacheInterceptorOptions) {\n this.cache = options.cache;\n this.ttlSeconds = options.ttlSeconds;\n this.methods = options.methods ?? [\"GET\"];\n this.keyGenerator =\n options.keyGenerator ?? ((req) => `${req.method}:${req.url}`);\n }\n\n intercept(\n context: ExecutionContext,\n next: CallHandler,\n ): Observable<unknown> {\n const req = context.switchToHttp().getRequest<{\n method: string;\n url: string;\n }>();\n\n if (!this.methods.includes(req.method)) {\n return next.handle();\n }\n\n const key = this.keyGenerator(req);\n const cached = this.cache.get(key);\n if (cached !== null) {\n return of(cached);\n }\n\n return next.handle().pipe(\n tap((body) => {\n if (body !== undefined && body !== null) {\n this.cache.set(\n key,\n body,\n this.ttlSeconds ? { ttlSeconds: this.ttlSeconds } : undefined,\n );\n }\n }),\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBA,kBAAwB;AAkBjB,IAAM,mBAAN,MAAkD;AAAA,EASvD,YAAY,SAAkC;AAC5C,SAAK,QAAQ,QAAQ;AACrB,SAAK,aAAa,QAAQ;AAC1B,SAAK,UAAU,QAAQ,WAAW,CAAC,KAAK;AACxC,SAAK,eACH,QAAQ,iBAAiB,CAAC,QAAQ,GAAG,IAAI,MAAM,IAAI,IAAI,GAAG;AAAA,EAC9D;AAAA,EAEA,UACE,SACA,MACqB;AACrB,UAAM,MAAM,QAAQ,aAAa,EAAE,WAGhC;AAEH,QAAI,CAAC,KAAK,QAAQ,SAAS,IAAI,MAAM,GAAG;AACtC,aAAO,KAAK,OAAO;AAAA,IACrB;AAEA,UAAM,MAAM,KAAK,aAAa,GAAG;AACjC,UAAM,SAAS,KAAK,MAAM,IAAI,GAAG;AACjC,QAAI,WAAW,MAAM;AACnB,iBAAO,gBAAG,MAAM;AAAA,IAClB;AAEA,WAAO,KAAK,OAAO,EAAE;AAAA,UACnB,iBAAI,CAAC,SAAS;AACZ,YAAI,SAAS,UAAa,SAAS,MAAM;AACvC,eAAK,MAAM;AAAA,YACT;AAAA,YACA;AAAA,YACA,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI;AAAA,UACtD;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../js/nestjs.ts"],"sourcesContent":["/**\n * NestJS integration: a response caching interceptor.\n *\n * Usage:\n * import { Cache } from \"rust-node-cache\";\n * import { CacheInterceptor } from \"rust-node-cache/nestjs\";\n *\n * const cache = new Cache();\n * app.useGlobalInterceptors(new CacheInterceptor({ cache, ttlSeconds: 60 }));\n *\n * `@nestjs/common` is imported with `import type`, so there is NO runtime import\n * of Nest here — the package does not force Nest as a dependency. The only runtime\n * dependencies are RxJS's `of`/`tap` operators (present in any Nest app).\n */\n\nimport type {\n CallHandler,\n ExecutionContext,\n NestInterceptor,\n} from \"@nestjs/common\";\nimport type { Observable } from \"rxjs\";\nimport { of, tap } from \"rxjs\";\nimport type { Cache } from \"./index\";\n\nexport interface CacheInterceptorOptions {\n /** Cache instance to use. */\n cache: Cache;\n /** TTL applied to stored responses (in seconds). */\n ttlSeconds?: number;\n /** HTTP methods eligible for caching. Default: `[\"GET\"]`. */\n methods?: string[];\n /** Builds the cache key from the request. Default: `METHOD:url`. */\n keyGenerator?: (req: { method: string; url: string }) => string;\n}\n\n/**\n * NestJS interceptor that caches the value returned by handlers. On a HIT, it\n * returns the cached value without running the handler.\n */\nexport class CacheInterceptor implements NestInterceptor {\n private readonly cache: Cache;\n private readonly ttlSeconds?: number;\n private readonly methods: string[];\n private readonly keyGenerator: (req: {\n method: string;\n url: string;\n }) => string;\n\n constructor(options: CacheInterceptorOptions) {\n this.cache = options.cache;\n this.ttlSeconds = options.ttlSeconds;\n this.methods = options.methods ?? [\"GET\"];\n this.keyGenerator =\n options.keyGenerator ?? ((req) => `${req.method}:${req.url}`);\n }\n\n intercept(\n context: ExecutionContext,\n next: CallHandler,\n ): Observable<unknown> {\n const req = context.switchToHttp().getRequest<{\n method: string;\n url: string;\n }>();\n\n if (!this.methods.includes(req.method)) {\n return next.handle();\n }\n\n const key = this.keyGenerator(req);\n const cached = this.cache.get(key);\n if (cached !== null) {\n return of(cached);\n }\n\n return next.handle().pipe(\n tap((body) => {\n // Store the body as Nest will send it (JSON), so a HIT returns the\n // same data as the MISS did (a `Date` becomes its ISO string).\n const json = toJson(body);\n if (json !== undefined && json !== \"null\") {\n this.cache.setJson(\n key,\n json,\n this.ttlSeconds ? { ttlSeconds: this.ttlSeconds } : undefined,\n );\n }\n }),\n );\n }\n}\n\n/** The body as JSON text, or `undefined` when it cannot be cached. */\nfunction toJson(body: unknown): string | undefined {\n try {\n return JSON.stringify(body);\n } catch {\n return undefined; // circular or BigInt: left for Nest to report.\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBA,kBAAwB;AAkBjB,IAAM,mBAAN,MAAkD;AAAA,EASvD,YAAY,SAAkC;AAC5C,SAAK,QAAQ,QAAQ;AACrB,SAAK,aAAa,QAAQ;AAC1B,SAAK,UAAU,QAAQ,WAAW,CAAC,KAAK;AACxC,SAAK,eACH,QAAQ,iBAAiB,CAAC,QAAQ,GAAG,IAAI,MAAM,IAAI,IAAI,GAAG;AAAA,EAC9D;AAAA,EAEA,UACE,SACA,MACqB;AACrB,UAAM,MAAM,QAAQ,aAAa,EAAE,WAGhC;AAEH,QAAI,CAAC,KAAK,QAAQ,SAAS,IAAI,MAAM,GAAG;AACtC,aAAO,KAAK,OAAO;AAAA,IACrB;AAEA,UAAM,MAAM,KAAK,aAAa,GAAG;AACjC,UAAM,SAAS,KAAK,MAAM,IAAI,GAAG;AACjC,QAAI,WAAW,MAAM;AACnB,iBAAO,gBAAG,MAAM;AAAA,IAClB;AAEA,WAAO,KAAK,OAAO,EAAE;AAAA,UACnB,iBAAI,CAAC,SAAS;AAGZ,cAAM,OAAO,OAAO,IAAI;AACxB,YAAI,SAAS,UAAa,SAAS,QAAQ;AACzC,eAAK,MAAM;AAAA,YACT;AAAA,YACA;AAAA,YACA,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI;AAAA,UACtD;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAGA,SAAS,OAAO,MAAmC;AACjD,MAAI;AACF,WAAO,KAAK,UAAU,IAAI;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}