@nodellmcache/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -0
- package/dist/index.cjs +259 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +316 -0
- package/dist/index.d.ts +316 -0
- package/dist/index.mjs +223 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @nodellmcache/core
|
|
2
|
+
|
|
3
|
+
Shared interfaces, types, and utilities for [NodeLLMCache](https://github.com/mdmax007/node-llm-cache) — AI memory infrastructure for Node.js.
|
|
4
|
+
|
|
5
|
+
This is the central package every other `@nodellmcache/*` package depends on. It has **zero external dependencies** and exports only contracts and small, pure utilities. You rarely install it directly; a feature package (e.g. `@nodellmcache/prompt-cache`) pulls it in.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @nodellmcache/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What's inside
|
|
14
|
+
|
|
15
|
+
- **Interfaces** — `StorageAdapter`, `VectorStoreAdapter`, `CompressionEngine`, `CacheEntry`, `CacheMetadata`, `MetricsSink`, `CacheOptions`
|
|
16
|
+
- **Types** — `CacheType`, `LLMProvider`, `CompressionAlgo`, `DataHint`
|
|
17
|
+
- **`KeyBuilder`** — deterministic, hashed cache keys
|
|
18
|
+
- **`TTLManager`** — expiry arithmetic and sliding windows
|
|
19
|
+
- **`Serializer` / `JsonSerializer`** — pluggable value encoding
|
|
20
|
+
- **`BaseCacheManager`** — the cache-aside base class for every feature cache
|
|
21
|
+
- **Error hierarchy** — `NodeLLMCacheError` and typed subclasses
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
### Building keys
|
|
26
|
+
|
|
27
|
+
Keys follow the format `{type}:{provider}:{model}:{sha256}`. The raw input is normalized (`trim` → `toLowerCase` → collapse whitespace) and hashed, so **raw prompt text never appears in a key**.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { KeyBuilder } from '@nodellmcache/core'
|
|
31
|
+
|
|
32
|
+
KeyBuilder.build('prompt', 'openai', 'gpt-4o', 'Hello World')
|
|
33
|
+
// 'prompt:openai:gpt-4o:<sha256-of "hello world">'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Implementing a storage adapter
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import type { StorageAdapter, CacheEntry, AdapterStats } from '@nodellmcache/core'
|
|
40
|
+
|
|
41
|
+
class MyAdapter<T> implements StorageAdapter<T> {
|
|
42
|
+
async get(key: string): Promise<CacheEntry<T> | null> { /* ... */ }
|
|
43
|
+
async set(key: string, entry: CacheEntry<T>, ttl?: number): Promise<void> { /* ... */ }
|
|
44
|
+
async delete(key: string): Promise<void> { /* ... */ }
|
|
45
|
+
async clear(): Promise<void> { /* ... */ }
|
|
46
|
+
async has(key: string): Promise<boolean> { /* ... */ }
|
|
47
|
+
async stats(): Promise<AdapterStats> { /* ... */ }
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Building a feature cache
|
|
52
|
+
|
|
53
|
+
`BaseCacheManager` provides the cache-aside `getOrGenerate` flow, key building, TTL handling, hit/miss accounting, and metric emission. Subclasses only declare their `cacheType`.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { BaseCacheManager } from '@nodellmcache/core'
|
|
57
|
+
|
|
58
|
+
class PromptCache extends BaseCacheManager<string> {
|
|
59
|
+
protected readonly cacheType = 'prompt' as const
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const cache = new PromptCache({ adapter: myAdapter, defaultTTL: 3_600_000 })
|
|
63
|
+
|
|
64
|
+
const answer = await cache.getOrGenerate(
|
|
65
|
+
'Explain Redis in one paragraph',
|
|
66
|
+
() => callTheModel(),
|
|
67
|
+
{ provider: 'openai', model: 'gpt-4o' },
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API summary
|
|
72
|
+
|
|
73
|
+
| Symbol | Kind | Purpose |
|
|
74
|
+
|--------|------|---------|
|
|
75
|
+
| `KeyBuilder.build/normalize/hash` | class (static) | Cache key generation |
|
|
76
|
+
| `TTLManager.computeExpiresAt/isExpired/remaining/slide` | class (static) | TTL arithmetic |
|
|
77
|
+
| `JsonSerializer` | class | Default JSON value codec (implements `Serializer`) |
|
|
78
|
+
| `BaseCacheManager` | abstract class | Cache-aside base for feature caches |
|
|
79
|
+
| `StorageAdapter<T>` | interface | Backend contract |
|
|
80
|
+
| `VectorStoreAdapter<M>` | interface | Vector DB contract |
|
|
81
|
+
| `CompressionEngine` | interface | Compression contract |
|
|
82
|
+
| `MetricsSink` | interface | Metrics emission contract |
|
|
83
|
+
| `NodeLLMCacheError` + subclasses | classes | Typed error hierarchy |
|
|
84
|
+
|
|
85
|
+
## Notes
|
|
86
|
+
|
|
87
|
+
- The architecture specifies MessagePack as the primary serialization format. To keep `core` dependency-free, this package ships only `JsonSerializer`; a MessagePack `Serializer` can be supplied by an optional package and injected wherever a `Serializer` is accepted.
|
|
88
|
+
- `MetricsSink` defaults to a no-op (`noopMetrics`). Wire in `@nodellmcache/observability` to collect real metrics.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BaseCacheManager: () => BaseCacheManager,
|
|
24
|
+
CacheAdapterError: () => CacheAdapterError,
|
|
25
|
+
CompressionError: () => CompressionError,
|
|
26
|
+
JsonSerializer: () => JsonSerializer,
|
|
27
|
+
KeyBuilder: () => KeyBuilder,
|
|
28
|
+
NodeLLMCacheError: () => NodeLLMCacheError,
|
|
29
|
+
SerializationError: () => SerializationError,
|
|
30
|
+
TTLManager: () => TTLManager,
|
|
31
|
+
ValidationError: () => ValidationError,
|
|
32
|
+
noopMetrics: () => noopMetrics
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// src/errors.ts
|
|
37
|
+
var NodeLLMCacheError = class extends Error {
|
|
38
|
+
constructor(message, options) {
|
|
39
|
+
super(message, options);
|
|
40
|
+
this.name = new.target.name;
|
|
41
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var CacheAdapterError = class extends NodeLLMCacheError {
|
|
45
|
+
};
|
|
46
|
+
var CompressionError = class extends NodeLLMCacheError {
|
|
47
|
+
};
|
|
48
|
+
var SerializationError = class extends NodeLLMCacheError {
|
|
49
|
+
};
|
|
50
|
+
var ValidationError = class extends NodeLLMCacheError {
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/KeyBuilder.ts
|
|
54
|
+
var import_node_crypto = require("crypto");
|
|
55
|
+
var KeyBuilder = class _KeyBuilder {
|
|
56
|
+
/**
|
|
57
|
+
* Normalizes text before hashing so trivially different inputs collapse to
|
|
58
|
+
* the same key: trims surrounding whitespace, lowercases, and collapses any
|
|
59
|
+
* run of whitespace to a single space.
|
|
60
|
+
*/
|
|
61
|
+
static normalize(text) {
|
|
62
|
+
return text.trim().toLowerCase().replace(/\s+/g, " ");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Produces the SHA-256 hex digest of the normalized text.
|
|
66
|
+
*/
|
|
67
|
+
static hash(text) {
|
|
68
|
+
return (0, import_node_crypto.createHash)("sha256").update(_KeyBuilder.normalize(text)).digest("hex");
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Builds a fully namespaced cache key.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* KeyBuilder.build('prompt', 'openai', 'gpt-4o', 'hello world')
|
|
75
|
+
* // 'prompt:openai:gpt-4o:b94d27b9...'
|
|
76
|
+
*/
|
|
77
|
+
static build(type, provider, model, text) {
|
|
78
|
+
return `${type}:${provider}:${model}:${_KeyBuilder.hash(text)}`;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/TTLManager.ts
|
|
83
|
+
var TTLManager = class _TTLManager {
|
|
84
|
+
/**
|
|
85
|
+
* Computes the absolute expiry timestamp for an entry created at `createdAt`
|
|
86
|
+
* with a relative `ttl`. Returns `undefined` when `ttl` is not a positive
|
|
87
|
+
* number, meaning the entry never expires.
|
|
88
|
+
*/
|
|
89
|
+
static computeExpiresAt(createdAt, ttl) {
|
|
90
|
+
if (ttl === void 0 || ttl <= 0) return void 0;
|
|
91
|
+
return createdAt + ttl;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Returns true when the entry has an expiry and that expiry is at or before
|
|
95
|
+
* `now` (defaults to the current time).
|
|
96
|
+
*/
|
|
97
|
+
static isExpired(entry, now = Date.now()) {
|
|
98
|
+
return entry.expiresAt !== void 0 && entry.expiresAt <= now;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Milliseconds remaining until the entry expires. Returns `Infinity` for
|
|
102
|
+
* entries with no expiry, and `0` for already-expired entries.
|
|
103
|
+
*/
|
|
104
|
+
static remaining(entry, now = Date.now()) {
|
|
105
|
+
if (entry.expiresAt === void 0) return Infinity;
|
|
106
|
+
return Math.max(0, entry.expiresAt - now);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Computes a refreshed expiry for a sliding-window TTL: extends the entry's
|
|
110
|
+
* life by `ttl` from `now`. Returns `undefined` when `ttl` is not positive.
|
|
111
|
+
*/
|
|
112
|
+
static slide(ttl, now = Date.now()) {
|
|
113
|
+
return _TTLManager.computeExpiresAt(now, ttl);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/Serializer.ts
|
|
118
|
+
var JsonSerializer = class {
|
|
119
|
+
serialize(value) {
|
|
120
|
+
try {
|
|
121
|
+
return Buffer.from(JSON.stringify(value), "utf8");
|
|
122
|
+
} catch (cause) {
|
|
123
|
+
throw new SerializationError("Failed to serialize value to JSON", { cause });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
deserialize(data) {
|
|
127
|
+
try {
|
|
128
|
+
return JSON.parse(data.toString("utf8"));
|
|
129
|
+
} catch (cause) {
|
|
130
|
+
throw new SerializationError("Failed to deserialize JSON value", { cause });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// src/BaseCacheManager.ts
|
|
136
|
+
var noopMetrics = {
|
|
137
|
+
emit() {
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var BaseCacheManager = class {
|
|
141
|
+
adapter;
|
|
142
|
+
defaultTTL;
|
|
143
|
+
metrics;
|
|
144
|
+
hits = 0;
|
|
145
|
+
misses = 0;
|
|
146
|
+
constructor(options) {
|
|
147
|
+
this.adapter = options.adapter;
|
|
148
|
+
this.defaultTTL = options.defaultTTL;
|
|
149
|
+
this.metrics = options.metrics ?? noopMetrics;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Builds the storage key for an input. Defaults to the canonical
|
|
153
|
+
* `{type}:{provider}:{model}:{hash}` format via {@link KeyBuilder}.
|
|
154
|
+
*/
|
|
155
|
+
buildKey(input, options) {
|
|
156
|
+
return KeyBuilder.build(
|
|
157
|
+
this.cacheType,
|
|
158
|
+
options?.provider ?? "unknown",
|
|
159
|
+
options?.model ?? "default",
|
|
160
|
+
input
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Wraps a value in a {@link CacheEntry} with computed expiry and metadata.
|
|
165
|
+
*/
|
|
166
|
+
buildEntry(key, value, options) {
|
|
167
|
+
const createdAt = Date.now();
|
|
168
|
+
const ttl = options?.ttl ?? this.defaultTTL;
|
|
169
|
+
return {
|
|
170
|
+
key,
|
|
171
|
+
value,
|
|
172
|
+
createdAt,
|
|
173
|
+
expiresAt: TTLManager.computeExpiresAt(createdAt, ttl),
|
|
174
|
+
metadata: {
|
|
175
|
+
compressed: false,
|
|
176
|
+
originalSize: 0,
|
|
177
|
+
cacheType: this.cacheType,
|
|
178
|
+
provider: options?.provider,
|
|
179
|
+
model: options?.model,
|
|
180
|
+
tokenCount: options?.tokenCount
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Cache-aside read-through. Returns the cached value on a hit, otherwise
|
|
186
|
+
* invokes `generator`, stores the result, and returns it. Set
|
|
187
|
+
* `options.cache = false` to bypass the cache for both read and write.
|
|
188
|
+
*/
|
|
189
|
+
async getOrGenerate(input, generator, options) {
|
|
190
|
+
if (options?.cache === false) {
|
|
191
|
+
return generator();
|
|
192
|
+
}
|
|
193
|
+
const key = this.buildKey(input, options);
|
|
194
|
+
const start = Date.now();
|
|
195
|
+
const cached = await this.adapter.get(key);
|
|
196
|
+
if (cached && !TTLManager.isExpired(cached)) {
|
|
197
|
+
this.hits++;
|
|
198
|
+
this.metrics.emit("cache.hit", {
|
|
199
|
+
cacheType: this.cacheType,
|
|
200
|
+
latencyMs: Date.now() - start,
|
|
201
|
+
tokensSaved: cached.metadata.tokenCount,
|
|
202
|
+
provider: options?.provider,
|
|
203
|
+
model: options?.model
|
|
204
|
+
});
|
|
205
|
+
return cached.value;
|
|
206
|
+
}
|
|
207
|
+
this.misses++;
|
|
208
|
+
this.metrics.emit("cache.miss", {
|
|
209
|
+
cacheType: this.cacheType,
|
|
210
|
+
latencyMs: Date.now() - start,
|
|
211
|
+
provider: options?.provider,
|
|
212
|
+
model: options?.model
|
|
213
|
+
});
|
|
214
|
+
const value = await generator();
|
|
215
|
+
const ttl = options?.ttl ?? this.defaultTTL;
|
|
216
|
+
const entry = this.buildEntry(key, value, options);
|
|
217
|
+
await this.adapter.set(key, entry, ttl);
|
|
218
|
+
this.metrics.emit("cache.set", {
|
|
219
|
+
cacheType: this.cacheType,
|
|
220
|
+
latencyMs: Date.now() - start,
|
|
221
|
+
provider: options?.provider,
|
|
222
|
+
model: options?.model
|
|
223
|
+
});
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Removes a single entry by its input (and namespacing options).
|
|
228
|
+
*/
|
|
229
|
+
async invalidate(input, options) {
|
|
230
|
+
await this.adapter.delete(this.buildKey(input, options));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Returns hit/miss accounting for this manager plus the adapter's entry count.
|
|
234
|
+
*/
|
|
235
|
+
async stats() {
|
|
236
|
+
const total = this.hits + this.misses;
|
|
237
|
+
const adapterStats = await this.adapter.stats();
|
|
238
|
+
return {
|
|
239
|
+
hits: this.hits,
|
|
240
|
+
misses: this.misses,
|
|
241
|
+
hitRate: total === 0 ? 0 : this.hits / total,
|
|
242
|
+
entryCount: adapterStats.entryCount
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
247
|
+
0 && (module.exports = {
|
|
248
|
+
BaseCacheManager,
|
|
249
|
+
CacheAdapterError,
|
|
250
|
+
CompressionError,
|
|
251
|
+
JsonSerializer,
|
|
252
|
+
KeyBuilder,
|
|
253
|
+
NodeLLMCacheError,
|
|
254
|
+
SerializationError,
|
|
255
|
+
TTLManager,
|
|
256
|
+
ValidationError,
|
|
257
|
+
noopMetrics
|
|
258
|
+
});
|
|
259
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/KeyBuilder.ts","../src/TTLManager.ts","../src/Serializer.ts","../src/BaseCacheManager.ts"],"sourcesContent":["export * from './types.js'\nexport * from './interfaces.js'\nexport * from './errors.js'\nexport { KeyBuilder } from './KeyBuilder.js'\nexport { TTLManager } from './TTLManager.js'\nexport { type Serializer, JsonSerializer } from './Serializer.js'\nexport {\n BaseCacheManager,\n noopMetrics,\n type BaseCacheManagerOptions,\n} from './BaseCacheManager.js'\n","/**\n * Base class for every error thrown by NodeLLMCache packages. Catching this\n * catches anything the library throws intentionally.\n */\nexport class NodeLLMCacheError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = new.target.name\n // Restore prototype chain for instanceof across the ES5 transpile boundary.\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/** Thrown when a storage adapter operation fails. */\nexport class CacheAdapterError extends NodeLLMCacheError {}\n\n/** Thrown when compression or decompression fails. */\nexport class CompressionError extends NodeLLMCacheError {}\n\n/** Thrown when serialization or deserialization fails. */\nexport class SerializationError extends NodeLLMCacheError {}\n\n/** Thrown when a value fails validation (e.g. malformed configuration). */\nexport class ValidationError extends NodeLLMCacheError {}\n","import { createHash } from 'node:crypto'\nimport type { CacheType, LLMProvider } from './types.js'\n\n/**\n * Builds deterministic, collision-resistant cache keys.\n *\n * Format: `{type}:{provider}:{model}:{sha256-hash}`\n *\n * The raw input text is normalized and hashed with SHA-256 — keys never\n * contain raw prompt text, which keeps sensitive content out of storage keys\n * and logs.\n */\nexport class KeyBuilder {\n /**\n * Normalizes text before hashing so trivially different inputs collapse to\n * the same key: trims surrounding whitespace, lowercases, and collapses any\n * run of whitespace to a single space.\n */\n static normalize(text: string): string {\n return text.trim().toLowerCase().replace(/\\s+/g, ' ')\n }\n\n /**\n * Produces the SHA-256 hex digest of the normalized text.\n */\n static hash(text: string): string {\n return createHash('sha256').update(KeyBuilder.normalize(text)).digest('hex')\n }\n\n /**\n * Builds a fully namespaced cache key.\n *\n * @example\n * KeyBuilder.build('prompt', 'openai', 'gpt-4o', 'hello world')\n * // 'prompt:openai:gpt-4o:b94d27b9...'\n */\n static build(\n type: CacheType,\n provider: LLMProvider | string,\n model: string,\n text: string,\n ): string {\n return `${type}:${provider}:${model}:${KeyBuilder.hash(text)}`\n }\n}\n","import type { CacheEntry } from './interfaces.js'\n\n/**\n * Centralizes time-to-live arithmetic: computing expiry timestamps, checking\n * expiry, and supporting sliding-window refresh. All durations are relative\n * milliseconds; all timestamps are absolute epoch milliseconds.\n */\nexport class TTLManager {\n /**\n * Computes the absolute expiry timestamp for an entry created at `createdAt`\n * with a relative `ttl`. Returns `undefined` when `ttl` is not a positive\n * number, meaning the entry never expires.\n */\n static computeExpiresAt(createdAt: number, ttl?: number): number | undefined {\n if (ttl === undefined || ttl <= 0) return undefined\n return createdAt + ttl\n }\n\n /**\n * Returns true when the entry has an expiry and that expiry is at or before\n * `now` (defaults to the current time).\n */\n static isExpired(entry: Pick<CacheEntry<unknown>, 'expiresAt'>, now: number = Date.now()): boolean {\n return entry.expiresAt !== undefined && entry.expiresAt <= now\n }\n\n /**\n * Milliseconds remaining until the entry expires. Returns `Infinity` for\n * entries with no expiry, and `0` for already-expired entries.\n */\n static remaining(entry: Pick<CacheEntry<unknown>, 'expiresAt'>, now: number = Date.now()): number {\n if (entry.expiresAt === undefined) return Infinity\n return Math.max(0, entry.expiresAt - now)\n }\n\n /**\n * Computes a refreshed expiry for a sliding-window TTL: extends the entry's\n * life by `ttl` from `now`. Returns `undefined` when `ttl` is not positive.\n */\n static slide(ttl?: number, now: number = Date.now()): number | undefined {\n return TTLManager.computeExpiresAt(now, ttl)\n }\n}\n","import { SerializationError } from './errors.js'\n\n/**\n * Converts values to and from `Buffer` for storage. Implementations decide the\n * wire format.\n *\n * The architecture calls for MessagePack as the primary format, but\n * `@nodellmcache/core` must stay dependency-free, so it ships only the JSON\n * implementation below. A MessagePack serializer can be supplied by an optional\n * package and injected wherever a `Serializer` is accepted.\n */\nexport interface Serializer {\n serialize<T>(value: T): Buffer\n deserialize<T>(data: Buffer): T\n}\n\n/**\n * Default JSON-backed serializer. Zero dependencies; handles the common case\n * and wraps failures in {@link SerializationError}.\n */\nexport class JsonSerializer implements Serializer {\n serialize<T>(value: T): Buffer {\n try {\n return Buffer.from(JSON.stringify(value), 'utf8')\n } catch (cause) {\n throw new SerializationError('Failed to serialize value to JSON', { cause })\n }\n }\n\n deserialize<T>(data: Buffer): T {\n try {\n return JSON.parse(data.toString('utf8')) as T\n } catch (cause) {\n throw new SerializationError('Failed to deserialize JSON value', { cause })\n }\n }\n}\n","import { KeyBuilder } from './KeyBuilder.js'\nimport { TTLManager } from './TTLManager.js'\nimport type {\n CacheEntry,\n CacheOptions,\n CacheStats,\n MetricsSink,\n StorageAdapter,\n} from './interfaces.js'\nimport type { CacheType } from './types.js'\n\n/** A metrics sink that discards everything; the default when none is injected. */\nexport const noopMetrics: MetricsSink = {\n emit() {\n // intentionally empty\n },\n}\n\n/**\n * Construction options shared by all cache managers.\n */\nexport interface BaseCacheManagerOptions<T> {\n adapter: StorageAdapter<T>\n /** Default relative TTL in milliseconds applied to entries lacking their own. */\n defaultTTL?: number\n /** Metrics sink; defaults to a no-op so core stays dependency-free. */\n metrics?: MetricsSink\n}\n\n/**\n * Shared base for every feature cache (prompt, embedding, semantic, ...).\n *\n * Provides the cache-aside `getOrGenerate` flow, key building, entry\n * construction, invalidation, and hit/miss accounting. Subclasses declare their\n * {@link CacheType} and may override {@link buildKey} for custom namespacing.\n */\nexport abstract class BaseCacheManager<T> {\n /** The workload category for keys and metrics. */\n protected abstract readonly cacheType: CacheType\n\n protected readonly adapter: StorageAdapter<T>\n protected readonly defaultTTL: number | undefined\n protected readonly metrics: MetricsSink\n\n private hits = 0\n private misses = 0\n\n constructor(options: BaseCacheManagerOptions<T>) {\n this.adapter = options.adapter\n this.defaultTTL = options.defaultTTL\n this.metrics = options.metrics ?? noopMetrics\n }\n\n /**\n * Builds the storage key for an input. Defaults to the canonical\n * `{type}:{provider}:{model}:{hash}` format via {@link KeyBuilder}.\n */\n protected buildKey(input: string, options?: CacheOptions): string {\n return KeyBuilder.build(\n this.cacheType,\n options?.provider ?? 'unknown',\n options?.model ?? 'default',\n input,\n )\n }\n\n /**\n * Wraps a value in a {@link CacheEntry} with computed expiry and metadata.\n */\n protected buildEntry(key: string, value: T, options?: CacheOptions): CacheEntry<T> {\n const createdAt = Date.now()\n const ttl = options?.ttl ?? this.defaultTTL\n return {\n key,\n value,\n createdAt,\n expiresAt: TTLManager.computeExpiresAt(createdAt, ttl),\n metadata: {\n compressed: false,\n originalSize: 0,\n cacheType: this.cacheType,\n provider: options?.provider,\n model: options?.model,\n tokenCount: options?.tokenCount,\n },\n }\n }\n\n /**\n * Cache-aside read-through. Returns the cached value on a hit, otherwise\n * invokes `generator`, stores the result, and returns it. Set\n * `options.cache = false` to bypass the cache for both read and write.\n */\n async getOrGenerate(\n input: string,\n generator: () => Promise<T>,\n options?: CacheOptions,\n ): Promise<T> {\n // A deliberate bypass is neither a hit nor a miss — skip the cache and\n // metrics entirely so accounting reflects only real cache consultations.\n if (options?.cache === false) {\n return generator()\n }\n\n const key = this.buildKey(input, options)\n const start = Date.now()\n\n const cached = await this.adapter.get(key)\n if (cached && !TTLManager.isExpired(cached)) {\n this.hits++\n this.metrics.emit('cache.hit', {\n cacheType: this.cacheType,\n latencyMs: Date.now() - start,\n tokensSaved: cached.metadata.tokenCount,\n provider: options?.provider,\n model: options?.model,\n })\n return cached.value\n }\n\n this.misses++\n this.metrics.emit('cache.miss', {\n cacheType: this.cacheType,\n latencyMs: Date.now() - start,\n provider: options?.provider,\n model: options?.model,\n })\n\n const value = await generator()\n\n const ttl = options?.ttl ?? this.defaultTTL\n const entry = this.buildEntry(key, value, options)\n await this.adapter.set(key, entry, ttl)\n this.metrics.emit('cache.set', {\n cacheType: this.cacheType,\n latencyMs: Date.now() - start,\n provider: options?.provider,\n model: options?.model,\n })\n\n return value\n }\n\n /**\n * Removes a single entry by its input (and namespacing options).\n */\n async invalidate(input: string, options?: CacheOptions): Promise<void> {\n await this.adapter.delete(this.buildKey(input, options))\n }\n\n /**\n * Returns hit/miss accounting for this manager plus the adapter's entry count.\n */\n async stats(): Promise<CacheStats> {\n const total = this.hits + this.misses\n const adapterStats = await this.adapter.stats()\n return {\n hits: this.hits,\n misses: this.misses,\n hitRate: total === 0 ? 0 : this.hits / total,\n entryCount: adapterStats.entryCount,\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The category of data a cache stores. Used in key namespacing and metrics
|
|
3
|
+
* breakdowns so every cached value is attributable to a workload.
|
|
4
|
+
*/
|
|
5
|
+
type CacheType = 'prompt' | 'embedding' | 'semantic' | 'retrieval' | 'context' | 'agent' | 'tool' | 'conversation';
|
|
6
|
+
/**
|
|
7
|
+
* Canonical LLM provider names. See the providers table in CLAUDE.md for the
|
|
8
|
+
* common models associated with each.
|
|
9
|
+
*/
|
|
10
|
+
type LLMProvider = 'openai' | 'anthropic' | 'gemini' | 'deepseek' | 'llama' | 'mistral' | 'ollama';
|
|
11
|
+
/**
|
|
12
|
+
* Supported compression algorithms. `auto` defers the choice to the
|
|
13
|
+
* compression engine's size/hint heuristics; `none` is a passthrough.
|
|
14
|
+
*/
|
|
15
|
+
type CompressionAlgo = 'lz4' | 'brotli' | 'zstd' | 'gzip' | 'none' | 'auto';
|
|
16
|
+
/**
|
|
17
|
+
* A hint describing the shape of a payload so the compression engine can pick
|
|
18
|
+
* the best codec (e.g. `embedding` favours lz4, `text` favours brotli).
|
|
19
|
+
*/
|
|
20
|
+
type DataHint = 'embedding' | 'text' | 'json' | 'binary';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Metadata stored alongside every cache entry. Captures how the value was
|
|
24
|
+
* encoded and which LLM workload produced it.
|
|
25
|
+
*/
|
|
26
|
+
interface CacheMetadata {
|
|
27
|
+
compressed: boolean;
|
|
28
|
+
compressionAlgo?: CompressionAlgo;
|
|
29
|
+
originalSize: number;
|
|
30
|
+
compressedSize?: number;
|
|
31
|
+
cacheType: CacheType;
|
|
32
|
+
provider?: LLMProvider;
|
|
33
|
+
model?: string;
|
|
34
|
+
tokenCount?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A single cached value with its bookkeeping. `expiresAt` is an absolute epoch
|
|
38
|
+
* timestamp in milliseconds; when omitted the entry never expires.
|
|
39
|
+
*/
|
|
40
|
+
interface CacheEntry<T> {
|
|
41
|
+
key: string;
|
|
42
|
+
value: T;
|
|
43
|
+
createdAt: number;
|
|
44
|
+
expiresAt?: number;
|
|
45
|
+
metadata: CacheMetadata;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Aggregate statistics reported by a storage adapter.
|
|
49
|
+
*/
|
|
50
|
+
interface AdapterStats {
|
|
51
|
+
/** Number of entries currently held. */
|
|
52
|
+
entryCount: number;
|
|
53
|
+
/** Approximate bytes used by stored entries, if the adapter can measure it. */
|
|
54
|
+
sizeBytes?: number;
|
|
55
|
+
/** Number of evictions performed over the adapter's lifetime. */
|
|
56
|
+
evictions?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The contract every storage backend implements. Application code never
|
|
60
|
+
* imports a concrete adapter in business logic — adapters are injected.
|
|
61
|
+
*/
|
|
62
|
+
interface StorageAdapter<T = unknown> {
|
|
63
|
+
get(key: string): Promise<CacheEntry<T> | null>;
|
|
64
|
+
/** `ttl` is a relative duration in milliseconds from now. */
|
|
65
|
+
set(key: string, entry: CacheEntry<T>, ttl?: number): Promise<void>;
|
|
66
|
+
delete(key: string): Promise<void>;
|
|
67
|
+
clear(): Promise<void>;
|
|
68
|
+
has(key: string): Promise<boolean>;
|
|
69
|
+
stats(): Promise<AdapterStats>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Result of a single compression operation.
|
|
73
|
+
*/
|
|
74
|
+
interface CompressedResult {
|
|
75
|
+
data: Buffer;
|
|
76
|
+
algo: CompressionAlgo;
|
|
77
|
+
originalSize: number;
|
|
78
|
+
compressedSize: number;
|
|
79
|
+
ratio: number;
|
|
80
|
+
durationMs: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Statistics for a completed compression, suitable for observability rollups.
|
|
84
|
+
*/
|
|
85
|
+
interface CompressionStats {
|
|
86
|
+
originalSize: number;
|
|
87
|
+
compressedSize: number;
|
|
88
|
+
ratio: number;
|
|
89
|
+
savedBytes: number;
|
|
90
|
+
savedPercent: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The compression engine contract implemented by `@nodellmcache/compression`.
|
|
94
|
+
*/
|
|
95
|
+
interface CompressionEngine {
|
|
96
|
+
compress(data: Buffer, algo: CompressionAlgo): Promise<Buffer>;
|
|
97
|
+
decompress(data: Buffer, algo: CompressionAlgo): Promise<Buffer>;
|
|
98
|
+
auto(data: Buffer, hint?: DataHint): Promise<CompressedResult>;
|
|
99
|
+
stats(original: Buffer, compressed: Buffer): CompressionStats;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A vector match returned by a vector store query.
|
|
103
|
+
*/
|
|
104
|
+
interface VectorMatch<M = Record<string, unknown>> {
|
|
105
|
+
id: string;
|
|
106
|
+
score: number;
|
|
107
|
+
vector?: number[];
|
|
108
|
+
metadata?: M;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The contract every vector database adapter implements.
|
|
112
|
+
*/
|
|
113
|
+
interface VectorStoreAdapter<M = Record<string, unknown>> {
|
|
114
|
+
upsert(id: string, vector: number[], metadata?: M): Promise<void>;
|
|
115
|
+
query(vector: number[], topK: number, filter?: Partial<M>): Promise<VectorMatch<M>[]>;
|
|
116
|
+
delete(id: string): Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The names of metric events emitted by cache managers.
|
|
120
|
+
*/
|
|
121
|
+
type MetricEvent = 'cache.hit' | 'cache.miss' | 'cache.set' | 'cache.evict';
|
|
122
|
+
/**
|
|
123
|
+
* Payload accompanying a metric event.
|
|
124
|
+
*/
|
|
125
|
+
interface MetricData {
|
|
126
|
+
cacheType: CacheType;
|
|
127
|
+
latencyMs: number;
|
|
128
|
+
tokensSaved?: number;
|
|
129
|
+
estimatedCostUSD?: number;
|
|
130
|
+
provider?: LLMProvider;
|
|
131
|
+
model?: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A sink for cache metrics. `@nodellmcache/observability` provides the real
|
|
135
|
+
* implementation; core ships a no-op so it stays dependency-free.
|
|
136
|
+
*/
|
|
137
|
+
interface MetricsSink {
|
|
138
|
+
emit(event: MetricEvent, data: MetricData): void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Per-call options shared across cache managers.
|
|
142
|
+
*/
|
|
143
|
+
interface CacheOptions {
|
|
144
|
+
provider?: LLMProvider;
|
|
145
|
+
model?: string;
|
|
146
|
+
/** Relative TTL in milliseconds; overrides the manager default. */
|
|
147
|
+
ttl?: number;
|
|
148
|
+
/** When false, bypasses the cache entirely (read and write). */
|
|
149
|
+
cache?: boolean;
|
|
150
|
+
tokenCount?: number;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Hit/miss statistics reported by a cache manager.
|
|
154
|
+
*/
|
|
155
|
+
interface CacheStats {
|
|
156
|
+
hits: number;
|
|
157
|
+
misses: number;
|
|
158
|
+
hitRate: number;
|
|
159
|
+
entryCount: number;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Base class for every error thrown by NodeLLMCache packages. Catching this
|
|
164
|
+
* catches anything the library throws intentionally.
|
|
165
|
+
*/
|
|
166
|
+
declare class NodeLLMCacheError extends Error {
|
|
167
|
+
constructor(message: string, options?: ErrorOptions);
|
|
168
|
+
}
|
|
169
|
+
/** Thrown when a storage adapter operation fails. */
|
|
170
|
+
declare class CacheAdapterError extends NodeLLMCacheError {
|
|
171
|
+
}
|
|
172
|
+
/** Thrown when compression or decompression fails. */
|
|
173
|
+
declare class CompressionError extends NodeLLMCacheError {
|
|
174
|
+
}
|
|
175
|
+
/** Thrown when serialization or deserialization fails. */
|
|
176
|
+
declare class SerializationError extends NodeLLMCacheError {
|
|
177
|
+
}
|
|
178
|
+
/** Thrown when a value fails validation (e.g. malformed configuration). */
|
|
179
|
+
declare class ValidationError extends NodeLLMCacheError {
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Builds deterministic, collision-resistant cache keys.
|
|
184
|
+
*
|
|
185
|
+
* Format: `{type}:{provider}:{model}:{sha256-hash}`
|
|
186
|
+
*
|
|
187
|
+
* The raw input text is normalized and hashed with SHA-256 — keys never
|
|
188
|
+
* contain raw prompt text, which keeps sensitive content out of storage keys
|
|
189
|
+
* and logs.
|
|
190
|
+
*/
|
|
191
|
+
declare class KeyBuilder {
|
|
192
|
+
/**
|
|
193
|
+
* Normalizes text before hashing so trivially different inputs collapse to
|
|
194
|
+
* the same key: trims surrounding whitespace, lowercases, and collapses any
|
|
195
|
+
* run of whitespace to a single space.
|
|
196
|
+
*/
|
|
197
|
+
static normalize(text: string): string;
|
|
198
|
+
/**
|
|
199
|
+
* Produces the SHA-256 hex digest of the normalized text.
|
|
200
|
+
*/
|
|
201
|
+
static hash(text: string): string;
|
|
202
|
+
/**
|
|
203
|
+
* Builds a fully namespaced cache key.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* KeyBuilder.build('prompt', 'openai', 'gpt-4o', 'hello world')
|
|
207
|
+
* // 'prompt:openai:gpt-4o:b94d27b9...'
|
|
208
|
+
*/
|
|
209
|
+
static build(type: CacheType, provider: LLMProvider | string, model: string, text: string): string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Centralizes time-to-live arithmetic: computing expiry timestamps, checking
|
|
214
|
+
* expiry, and supporting sliding-window refresh. All durations are relative
|
|
215
|
+
* milliseconds; all timestamps are absolute epoch milliseconds.
|
|
216
|
+
*/
|
|
217
|
+
declare class TTLManager {
|
|
218
|
+
/**
|
|
219
|
+
* Computes the absolute expiry timestamp for an entry created at `createdAt`
|
|
220
|
+
* with a relative `ttl`. Returns `undefined` when `ttl` is not a positive
|
|
221
|
+
* number, meaning the entry never expires.
|
|
222
|
+
*/
|
|
223
|
+
static computeExpiresAt(createdAt: number, ttl?: number): number | undefined;
|
|
224
|
+
/**
|
|
225
|
+
* Returns true when the entry has an expiry and that expiry is at or before
|
|
226
|
+
* `now` (defaults to the current time).
|
|
227
|
+
*/
|
|
228
|
+
static isExpired(entry: Pick<CacheEntry<unknown>, 'expiresAt'>, now?: number): boolean;
|
|
229
|
+
/**
|
|
230
|
+
* Milliseconds remaining until the entry expires. Returns `Infinity` for
|
|
231
|
+
* entries with no expiry, and `0` for already-expired entries.
|
|
232
|
+
*/
|
|
233
|
+
static remaining(entry: Pick<CacheEntry<unknown>, 'expiresAt'>, now?: number): number;
|
|
234
|
+
/**
|
|
235
|
+
* Computes a refreshed expiry for a sliding-window TTL: extends the entry's
|
|
236
|
+
* life by `ttl` from `now`. Returns `undefined` when `ttl` is not positive.
|
|
237
|
+
*/
|
|
238
|
+
static slide(ttl?: number, now?: number): number | undefined;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Converts values to and from `Buffer` for storage. Implementations decide the
|
|
243
|
+
* wire format.
|
|
244
|
+
*
|
|
245
|
+
* The architecture calls for MessagePack as the primary format, but
|
|
246
|
+
* `@nodellmcache/core` must stay dependency-free, so it ships only the JSON
|
|
247
|
+
* implementation below. A MessagePack serializer can be supplied by an optional
|
|
248
|
+
* package and injected wherever a `Serializer` is accepted.
|
|
249
|
+
*/
|
|
250
|
+
interface Serializer {
|
|
251
|
+
serialize<T>(value: T): Buffer;
|
|
252
|
+
deserialize<T>(data: Buffer): T;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Default JSON-backed serializer. Zero dependencies; handles the common case
|
|
256
|
+
* and wraps failures in {@link SerializationError}.
|
|
257
|
+
*/
|
|
258
|
+
declare class JsonSerializer implements Serializer {
|
|
259
|
+
serialize<T>(value: T): Buffer;
|
|
260
|
+
deserialize<T>(data: Buffer): T;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** A metrics sink that discards everything; the default when none is injected. */
|
|
264
|
+
declare const noopMetrics: MetricsSink;
|
|
265
|
+
/**
|
|
266
|
+
* Construction options shared by all cache managers.
|
|
267
|
+
*/
|
|
268
|
+
interface BaseCacheManagerOptions<T> {
|
|
269
|
+
adapter: StorageAdapter<T>;
|
|
270
|
+
/** Default relative TTL in milliseconds applied to entries lacking their own. */
|
|
271
|
+
defaultTTL?: number;
|
|
272
|
+
/** Metrics sink; defaults to a no-op so core stays dependency-free. */
|
|
273
|
+
metrics?: MetricsSink;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Shared base for every feature cache (prompt, embedding, semantic, ...).
|
|
277
|
+
*
|
|
278
|
+
* Provides the cache-aside `getOrGenerate` flow, key building, entry
|
|
279
|
+
* construction, invalidation, and hit/miss accounting. Subclasses declare their
|
|
280
|
+
* {@link CacheType} and may override {@link buildKey} for custom namespacing.
|
|
281
|
+
*/
|
|
282
|
+
declare abstract class BaseCacheManager<T> {
|
|
283
|
+
/** The workload category for keys and metrics. */
|
|
284
|
+
protected abstract readonly cacheType: CacheType;
|
|
285
|
+
protected readonly adapter: StorageAdapter<T>;
|
|
286
|
+
protected readonly defaultTTL: number | undefined;
|
|
287
|
+
protected readonly metrics: MetricsSink;
|
|
288
|
+
private hits;
|
|
289
|
+
private misses;
|
|
290
|
+
constructor(options: BaseCacheManagerOptions<T>);
|
|
291
|
+
/**
|
|
292
|
+
* Builds the storage key for an input. Defaults to the canonical
|
|
293
|
+
* `{type}:{provider}:{model}:{hash}` format via {@link KeyBuilder}.
|
|
294
|
+
*/
|
|
295
|
+
protected buildKey(input: string, options?: CacheOptions): string;
|
|
296
|
+
/**
|
|
297
|
+
* Wraps a value in a {@link CacheEntry} with computed expiry and metadata.
|
|
298
|
+
*/
|
|
299
|
+
protected buildEntry(key: string, value: T, options?: CacheOptions): CacheEntry<T>;
|
|
300
|
+
/**
|
|
301
|
+
* Cache-aside read-through. Returns the cached value on a hit, otherwise
|
|
302
|
+
* invokes `generator`, stores the result, and returns it. Set
|
|
303
|
+
* `options.cache = false` to bypass the cache for both read and write.
|
|
304
|
+
*/
|
|
305
|
+
getOrGenerate(input: string, generator: () => Promise<T>, options?: CacheOptions): Promise<T>;
|
|
306
|
+
/**
|
|
307
|
+
* Removes a single entry by its input (and namespacing options).
|
|
308
|
+
*/
|
|
309
|
+
invalidate(input: string, options?: CacheOptions): Promise<void>;
|
|
310
|
+
/**
|
|
311
|
+
* Returns hit/miss accounting for this manager plus the adapter's entry count.
|
|
312
|
+
*/
|
|
313
|
+
stats(): Promise<CacheStats>;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export { type AdapterStats, BaseCacheManager, type BaseCacheManagerOptions, CacheAdapterError, type CacheEntry, type CacheMetadata, type CacheOptions, type CacheStats, type CacheType, type CompressedResult, type CompressionAlgo, type CompressionEngine, CompressionError, type CompressionStats, type DataHint, JsonSerializer, KeyBuilder, type LLMProvider, type MetricData, type MetricEvent, type MetricsSink, NodeLLMCacheError, SerializationError, type Serializer, type StorageAdapter, TTLManager, ValidationError, type VectorMatch, type VectorStoreAdapter, noopMetrics };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The category of data a cache stores. Used in key namespacing and metrics
|
|
3
|
+
* breakdowns so every cached value is attributable to a workload.
|
|
4
|
+
*/
|
|
5
|
+
type CacheType = 'prompt' | 'embedding' | 'semantic' | 'retrieval' | 'context' | 'agent' | 'tool' | 'conversation';
|
|
6
|
+
/**
|
|
7
|
+
* Canonical LLM provider names. See the providers table in CLAUDE.md for the
|
|
8
|
+
* common models associated with each.
|
|
9
|
+
*/
|
|
10
|
+
type LLMProvider = 'openai' | 'anthropic' | 'gemini' | 'deepseek' | 'llama' | 'mistral' | 'ollama';
|
|
11
|
+
/**
|
|
12
|
+
* Supported compression algorithms. `auto` defers the choice to the
|
|
13
|
+
* compression engine's size/hint heuristics; `none` is a passthrough.
|
|
14
|
+
*/
|
|
15
|
+
type CompressionAlgo = 'lz4' | 'brotli' | 'zstd' | 'gzip' | 'none' | 'auto';
|
|
16
|
+
/**
|
|
17
|
+
* A hint describing the shape of a payload so the compression engine can pick
|
|
18
|
+
* the best codec (e.g. `embedding` favours lz4, `text` favours brotli).
|
|
19
|
+
*/
|
|
20
|
+
type DataHint = 'embedding' | 'text' | 'json' | 'binary';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Metadata stored alongside every cache entry. Captures how the value was
|
|
24
|
+
* encoded and which LLM workload produced it.
|
|
25
|
+
*/
|
|
26
|
+
interface CacheMetadata {
|
|
27
|
+
compressed: boolean;
|
|
28
|
+
compressionAlgo?: CompressionAlgo;
|
|
29
|
+
originalSize: number;
|
|
30
|
+
compressedSize?: number;
|
|
31
|
+
cacheType: CacheType;
|
|
32
|
+
provider?: LLMProvider;
|
|
33
|
+
model?: string;
|
|
34
|
+
tokenCount?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A single cached value with its bookkeeping. `expiresAt` is an absolute epoch
|
|
38
|
+
* timestamp in milliseconds; when omitted the entry never expires.
|
|
39
|
+
*/
|
|
40
|
+
interface CacheEntry<T> {
|
|
41
|
+
key: string;
|
|
42
|
+
value: T;
|
|
43
|
+
createdAt: number;
|
|
44
|
+
expiresAt?: number;
|
|
45
|
+
metadata: CacheMetadata;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Aggregate statistics reported by a storage adapter.
|
|
49
|
+
*/
|
|
50
|
+
interface AdapterStats {
|
|
51
|
+
/** Number of entries currently held. */
|
|
52
|
+
entryCount: number;
|
|
53
|
+
/** Approximate bytes used by stored entries, if the adapter can measure it. */
|
|
54
|
+
sizeBytes?: number;
|
|
55
|
+
/** Number of evictions performed over the adapter's lifetime. */
|
|
56
|
+
evictions?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The contract every storage backend implements. Application code never
|
|
60
|
+
* imports a concrete adapter in business logic — adapters are injected.
|
|
61
|
+
*/
|
|
62
|
+
interface StorageAdapter<T = unknown> {
|
|
63
|
+
get(key: string): Promise<CacheEntry<T> | null>;
|
|
64
|
+
/** `ttl` is a relative duration in milliseconds from now. */
|
|
65
|
+
set(key: string, entry: CacheEntry<T>, ttl?: number): Promise<void>;
|
|
66
|
+
delete(key: string): Promise<void>;
|
|
67
|
+
clear(): Promise<void>;
|
|
68
|
+
has(key: string): Promise<boolean>;
|
|
69
|
+
stats(): Promise<AdapterStats>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Result of a single compression operation.
|
|
73
|
+
*/
|
|
74
|
+
interface CompressedResult {
|
|
75
|
+
data: Buffer;
|
|
76
|
+
algo: CompressionAlgo;
|
|
77
|
+
originalSize: number;
|
|
78
|
+
compressedSize: number;
|
|
79
|
+
ratio: number;
|
|
80
|
+
durationMs: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Statistics for a completed compression, suitable for observability rollups.
|
|
84
|
+
*/
|
|
85
|
+
interface CompressionStats {
|
|
86
|
+
originalSize: number;
|
|
87
|
+
compressedSize: number;
|
|
88
|
+
ratio: number;
|
|
89
|
+
savedBytes: number;
|
|
90
|
+
savedPercent: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The compression engine contract implemented by `@nodellmcache/compression`.
|
|
94
|
+
*/
|
|
95
|
+
interface CompressionEngine {
|
|
96
|
+
compress(data: Buffer, algo: CompressionAlgo): Promise<Buffer>;
|
|
97
|
+
decompress(data: Buffer, algo: CompressionAlgo): Promise<Buffer>;
|
|
98
|
+
auto(data: Buffer, hint?: DataHint): Promise<CompressedResult>;
|
|
99
|
+
stats(original: Buffer, compressed: Buffer): CompressionStats;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A vector match returned by a vector store query.
|
|
103
|
+
*/
|
|
104
|
+
interface VectorMatch<M = Record<string, unknown>> {
|
|
105
|
+
id: string;
|
|
106
|
+
score: number;
|
|
107
|
+
vector?: number[];
|
|
108
|
+
metadata?: M;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The contract every vector database adapter implements.
|
|
112
|
+
*/
|
|
113
|
+
interface VectorStoreAdapter<M = Record<string, unknown>> {
|
|
114
|
+
upsert(id: string, vector: number[], metadata?: M): Promise<void>;
|
|
115
|
+
query(vector: number[], topK: number, filter?: Partial<M>): Promise<VectorMatch<M>[]>;
|
|
116
|
+
delete(id: string): Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The names of metric events emitted by cache managers.
|
|
120
|
+
*/
|
|
121
|
+
type MetricEvent = 'cache.hit' | 'cache.miss' | 'cache.set' | 'cache.evict';
|
|
122
|
+
/**
|
|
123
|
+
* Payload accompanying a metric event.
|
|
124
|
+
*/
|
|
125
|
+
interface MetricData {
|
|
126
|
+
cacheType: CacheType;
|
|
127
|
+
latencyMs: number;
|
|
128
|
+
tokensSaved?: number;
|
|
129
|
+
estimatedCostUSD?: number;
|
|
130
|
+
provider?: LLMProvider;
|
|
131
|
+
model?: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A sink for cache metrics. `@nodellmcache/observability` provides the real
|
|
135
|
+
* implementation; core ships a no-op so it stays dependency-free.
|
|
136
|
+
*/
|
|
137
|
+
interface MetricsSink {
|
|
138
|
+
emit(event: MetricEvent, data: MetricData): void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Per-call options shared across cache managers.
|
|
142
|
+
*/
|
|
143
|
+
interface CacheOptions {
|
|
144
|
+
provider?: LLMProvider;
|
|
145
|
+
model?: string;
|
|
146
|
+
/** Relative TTL in milliseconds; overrides the manager default. */
|
|
147
|
+
ttl?: number;
|
|
148
|
+
/** When false, bypasses the cache entirely (read and write). */
|
|
149
|
+
cache?: boolean;
|
|
150
|
+
tokenCount?: number;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Hit/miss statistics reported by a cache manager.
|
|
154
|
+
*/
|
|
155
|
+
interface CacheStats {
|
|
156
|
+
hits: number;
|
|
157
|
+
misses: number;
|
|
158
|
+
hitRate: number;
|
|
159
|
+
entryCount: number;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Base class for every error thrown by NodeLLMCache packages. Catching this
|
|
164
|
+
* catches anything the library throws intentionally.
|
|
165
|
+
*/
|
|
166
|
+
declare class NodeLLMCacheError extends Error {
|
|
167
|
+
constructor(message: string, options?: ErrorOptions);
|
|
168
|
+
}
|
|
169
|
+
/** Thrown when a storage adapter operation fails. */
|
|
170
|
+
declare class CacheAdapterError extends NodeLLMCacheError {
|
|
171
|
+
}
|
|
172
|
+
/** Thrown when compression or decompression fails. */
|
|
173
|
+
declare class CompressionError extends NodeLLMCacheError {
|
|
174
|
+
}
|
|
175
|
+
/** Thrown when serialization or deserialization fails. */
|
|
176
|
+
declare class SerializationError extends NodeLLMCacheError {
|
|
177
|
+
}
|
|
178
|
+
/** Thrown when a value fails validation (e.g. malformed configuration). */
|
|
179
|
+
declare class ValidationError extends NodeLLMCacheError {
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Builds deterministic, collision-resistant cache keys.
|
|
184
|
+
*
|
|
185
|
+
* Format: `{type}:{provider}:{model}:{sha256-hash}`
|
|
186
|
+
*
|
|
187
|
+
* The raw input text is normalized and hashed with SHA-256 — keys never
|
|
188
|
+
* contain raw prompt text, which keeps sensitive content out of storage keys
|
|
189
|
+
* and logs.
|
|
190
|
+
*/
|
|
191
|
+
declare class KeyBuilder {
|
|
192
|
+
/**
|
|
193
|
+
* Normalizes text before hashing so trivially different inputs collapse to
|
|
194
|
+
* the same key: trims surrounding whitespace, lowercases, and collapses any
|
|
195
|
+
* run of whitespace to a single space.
|
|
196
|
+
*/
|
|
197
|
+
static normalize(text: string): string;
|
|
198
|
+
/**
|
|
199
|
+
* Produces the SHA-256 hex digest of the normalized text.
|
|
200
|
+
*/
|
|
201
|
+
static hash(text: string): string;
|
|
202
|
+
/**
|
|
203
|
+
* Builds a fully namespaced cache key.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* KeyBuilder.build('prompt', 'openai', 'gpt-4o', 'hello world')
|
|
207
|
+
* // 'prompt:openai:gpt-4o:b94d27b9...'
|
|
208
|
+
*/
|
|
209
|
+
static build(type: CacheType, provider: LLMProvider | string, model: string, text: string): string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Centralizes time-to-live arithmetic: computing expiry timestamps, checking
|
|
214
|
+
* expiry, and supporting sliding-window refresh. All durations are relative
|
|
215
|
+
* milliseconds; all timestamps are absolute epoch milliseconds.
|
|
216
|
+
*/
|
|
217
|
+
declare class TTLManager {
|
|
218
|
+
/**
|
|
219
|
+
* Computes the absolute expiry timestamp for an entry created at `createdAt`
|
|
220
|
+
* with a relative `ttl`. Returns `undefined` when `ttl` is not a positive
|
|
221
|
+
* number, meaning the entry never expires.
|
|
222
|
+
*/
|
|
223
|
+
static computeExpiresAt(createdAt: number, ttl?: number): number | undefined;
|
|
224
|
+
/**
|
|
225
|
+
* Returns true when the entry has an expiry and that expiry is at or before
|
|
226
|
+
* `now` (defaults to the current time).
|
|
227
|
+
*/
|
|
228
|
+
static isExpired(entry: Pick<CacheEntry<unknown>, 'expiresAt'>, now?: number): boolean;
|
|
229
|
+
/**
|
|
230
|
+
* Milliseconds remaining until the entry expires. Returns `Infinity` for
|
|
231
|
+
* entries with no expiry, and `0` for already-expired entries.
|
|
232
|
+
*/
|
|
233
|
+
static remaining(entry: Pick<CacheEntry<unknown>, 'expiresAt'>, now?: number): number;
|
|
234
|
+
/**
|
|
235
|
+
* Computes a refreshed expiry for a sliding-window TTL: extends the entry's
|
|
236
|
+
* life by `ttl` from `now`. Returns `undefined` when `ttl` is not positive.
|
|
237
|
+
*/
|
|
238
|
+
static slide(ttl?: number, now?: number): number | undefined;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Converts values to and from `Buffer` for storage. Implementations decide the
|
|
243
|
+
* wire format.
|
|
244
|
+
*
|
|
245
|
+
* The architecture calls for MessagePack as the primary format, but
|
|
246
|
+
* `@nodellmcache/core` must stay dependency-free, so it ships only the JSON
|
|
247
|
+
* implementation below. A MessagePack serializer can be supplied by an optional
|
|
248
|
+
* package and injected wherever a `Serializer` is accepted.
|
|
249
|
+
*/
|
|
250
|
+
interface Serializer {
|
|
251
|
+
serialize<T>(value: T): Buffer;
|
|
252
|
+
deserialize<T>(data: Buffer): T;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Default JSON-backed serializer. Zero dependencies; handles the common case
|
|
256
|
+
* and wraps failures in {@link SerializationError}.
|
|
257
|
+
*/
|
|
258
|
+
declare class JsonSerializer implements Serializer {
|
|
259
|
+
serialize<T>(value: T): Buffer;
|
|
260
|
+
deserialize<T>(data: Buffer): T;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** A metrics sink that discards everything; the default when none is injected. */
|
|
264
|
+
declare const noopMetrics: MetricsSink;
|
|
265
|
+
/**
|
|
266
|
+
* Construction options shared by all cache managers.
|
|
267
|
+
*/
|
|
268
|
+
interface BaseCacheManagerOptions<T> {
|
|
269
|
+
adapter: StorageAdapter<T>;
|
|
270
|
+
/** Default relative TTL in milliseconds applied to entries lacking their own. */
|
|
271
|
+
defaultTTL?: number;
|
|
272
|
+
/** Metrics sink; defaults to a no-op so core stays dependency-free. */
|
|
273
|
+
metrics?: MetricsSink;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Shared base for every feature cache (prompt, embedding, semantic, ...).
|
|
277
|
+
*
|
|
278
|
+
* Provides the cache-aside `getOrGenerate` flow, key building, entry
|
|
279
|
+
* construction, invalidation, and hit/miss accounting. Subclasses declare their
|
|
280
|
+
* {@link CacheType} and may override {@link buildKey} for custom namespacing.
|
|
281
|
+
*/
|
|
282
|
+
declare abstract class BaseCacheManager<T> {
|
|
283
|
+
/** The workload category for keys and metrics. */
|
|
284
|
+
protected abstract readonly cacheType: CacheType;
|
|
285
|
+
protected readonly adapter: StorageAdapter<T>;
|
|
286
|
+
protected readonly defaultTTL: number | undefined;
|
|
287
|
+
protected readonly metrics: MetricsSink;
|
|
288
|
+
private hits;
|
|
289
|
+
private misses;
|
|
290
|
+
constructor(options: BaseCacheManagerOptions<T>);
|
|
291
|
+
/**
|
|
292
|
+
* Builds the storage key for an input. Defaults to the canonical
|
|
293
|
+
* `{type}:{provider}:{model}:{hash}` format via {@link KeyBuilder}.
|
|
294
|
+
*/
|
|
295
|
+
protected buildKey(input: string, options?: CacheOptions): string;
|
|
296
|
+
/**
|
|
297
|
+
* Wraps a value in a {@link CacheEntry} with computed expiry and metadata.
|
|
298
|
+
*/
|
|
299
|
+
protected buildEntry(key: string, value: T, options?: CacheOptions): CacheEntry<T>;
|
|
300
|
+
/**
|
|
301
|
+
* Cache-aside read-through. Returns the cached value on a hit, otherwise
|
|
302
|
+
* invokes `generator`, stores the result, and returns it. Set
|
|
303
|
+
* `options.cache = false` to bypass the cache for both read and write.
|
|
304
|
+
*/
|
|
305
|
+
getOrGenerate(input: string, generator: () => Promise<T>, options?: CacheOptions): Promise<T>;
|
|
306
|
+
/**
|
|
307
|
+
* Removes a single entry by its input (and namespacing options).
|
|
308
|
+
*/
|
|
309
|
+
invalidate(input: string, options?: CacheOptions): Promise<void>;
|
|
310
|
+
/**
|
|
311
|
+
* Returns hit/miss accounting for this manager plus the adapter's entry count.
|
|
312
|
+
*/
|
|
313
|
+
stats(): Promise<CacheStats>;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export { type AdapterStats, BaseCacheManager, type BaseCacheManagerOptions, CacheAdapterError, type CacheEntry, type CacheMetadata, type CacheOptions, type CacheStats, type CacheType, type CompressedResult, type CompressionAlgo, type CompressionEngine, CompressionError, type CompressionStats, type DataHint, JsonSerializer, KeyBuilder, type LLMProvider, type MetricData, type MetricEvent, type MetricsSink, NodeLLMCacheError, SerializationError, type Serializer, type StorageAdapter, TTLManager, ValidationError, type VectorMatch, type VectorStoreAdapter, noopMetrics };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var NodeLLMCacheError = class extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = new.target.name;
|
|
6
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var CacheAdapterError = class extends NodeLLMCacheError {
|
|
10
|
+
};
|
|
11
|
+
var CompressionError = class extends NodeLLMCacheError {
|
|
12
|
+
};
|
|
13
|
+
var SerializationError = class extends NodeLLMCacheError {
|
|
14
|
+
};
|
|
15
|
+
var ValidationError = class extends NodeLLMCacheError {
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/KeyBuilder.ts
|
|
19
|
+
import { createHash } from "crypto";
|
|
20
|
+
var KeyBuilder = class _KeyBuilder {
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes text before hashing so trivially different inputs collapse to
|
|
23
|
+
* the same key: trims surrounding whitespace, lowercases, and collapses any
|
|
24
|
+
* run of whitespace to a single space.
|
|
25
|
+
*/
|
|
26
|
+
static normalize(text) {
|
|
27
|
+
return text.trim().toLowerCase().replace(/\s+/g, " ");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Produces the SHA-256 hex digest of the normalized text.
|
|
31
|
+
*/
|
|
32
|
+
static hash(text) {
|
|
33
|
+
return createHash("sha256").update(_KeyBuilder.normalize(text)).digest("hex");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Builds a fully namespaced cache key.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* KeyBuilder.build('prompt', 'openai', 'gpt-4o', 'hello world')
|
|
40
|
+
* // 'prompt:openai:gpt-4o:b94d27b9...'
|
|
41
|
+
*/
|
|
42
|
+
static build(type, provider, model, text) {
|
|
43
|
+
return `${type}:${provider}:${model}:${_KeyBuilder.hash(text)}`;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/TTLManager.ts
|
|
48
|
+
var TTLManager = class _TTLManager {
|
|
49
|
+
/**
|
|
50
|
+
* Computes the absolute expiry timestamp for an entry created at `createdAt`
|
|
51
|
+
* with a relative `ttl`. Returns `undefined` when `ttl` is not a positive
|
|
52
|
+
* number, meaning the entry never expires.
|
|
53
|
+
*/
|
|
54
|
+
static computeExpiresAt(createdAt, ttl) {
|
|
55
|
+
if (ttl === void 0 || ttl <= 0) return void 0;
|
|
56
|
+
return createdAt + ttl;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns true when the entry has an expiry and that expiry is at or before
|
|
60
|
+
* `now` (defaults to the current time).
|
|
61
|
+
*/
|
|
62
|
+
static isExpired(entry, now = Date.now()) {
|
|
63
|
+
return entry.expiresAt !== void 0 && entry.expiresAt <= now;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Milliseconds remaining until the entry expires. Returns `Infinity` for
|
|
67
|
+
* entries with no expiry, and `0` for already-expired entries.
|
|
68
|
+
*/
|
|
69
|
+
static remaining(entry, now = Date.now()) {
|
|
70
|
+
if (entry.expiresAt === void 0) return Infinity;
|
|
71
|
+
return Math.max(0, entry.expiresAt - now);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Computes a refreshed expiry for a sliding-window TTL: extends the entry's
|
|
75
|
+
* life by `ttl` from `now`. Returns `undefined` when `ttl` is not positive.
|
|
76
|
+
*/
|
|
77
|
+
static slide(ttl, now = Date.now()) {
|
|
78
|
+
return _TTLManager.computeExpiresAt(now, ttl);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/Serializer.ts
|
|
83
|
+
var JsonSerializer = class {
|
|
84
|
+
serialize(value) {
|
|
85
|
+
try {
|
|
86
|
+
return Buffer.from(JSON.stringify(value), "utf8");
|
|
87
|
+
} catch (cause) {
|
|
88
|
+
throw new SerializationError("Failed to serialize value to JSON", { cause });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
deserialize(data) {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(data.toString("utf8"));
|
|
94
|
+
} catch (cause) {
|
|
95
|
+
throw new SerializationError("Failed to deserialize JSON value", { cause });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// src/BaseCacheManager.ts
|
|
101
|
+
var noopMetrics = {
|
|
102
|
+
emit() {
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var BaseCacheManager = class {
|
|
106
|
+
adapter;
|
|
107
|
+
defaultTTL;
|
|
108
|
+
metrics;
|
|
109
|
+
hits = 0;
|
|
110
|
+
misses = 0;
|
|
111
|
+
constructor(options) {
|
|
112
|
+
this.adapter = options.adapter;
|
|
113
|
+
this.defaultTTL = options.defaultTTL;
|
|
114
|
+
this.metrics = options.metrics ?? noopMetrics;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Builds the storage key for an input. Defaults to the canonical
|
|
118
|
+
* `{type}:{provider}:{model}:{hash}` format via {@link KeyBuilder}.
|
|
119
|
+
*/
|
|
120
|
+
buildKey(input, options) {
|
|
121
|
+
return KeyBuilder.build(
|
|
122
|
+
this.cacheType,
|
|
123
|
+
options?.provider ?? "unknown",
|
|
124
|
+
options?.model ?? "default",
|
|
125
|
+
input
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Wraps a value in a {@link CacheEntry} with computed expiry and metadata.
|
|
130
|
+
*/
|
|
131
|
+
buildEntry(key, value, options) {
|
|
132
|
+
const createdAt = Date.now();
|
|
133
|
+
const ttl = options?.ttl ?? this.defaultTTL;
|
|
134
|
+
return {
|
|
135
|
+
key,
|
|
136
|
+
value,
|
|
137
|
+
createdAt,
|
|
138
|
+
expiresAt: TTLManager.computeExpiresAt(createdAt, ttl),
|
|
139
|
+
metadata: {
|
|
140
|
+
compressed: false,
|
|
141
|
+
originalSize: 0,
|
|
142
|
+
cacheType: this.cacheType,
|
|
143
|
+
provider: options?.provider,
|
|
144
|
+
model: options?.model,
|
|
145
|
+
tokenCount: options?.tokenCount
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Cache-aside read-through. Returns the cached value on a hit, otherwise
|
|
151
|
+
* invokes `generator`, stores the result, and returns it. Set
|
|
152
|
+
* `options.cache = false` to bypass the cache for both read and write.
|
|
153
|
+
*/
|
|
154
|
+
async getOrGenerate(input, generator, options) {
|
|
155
|
+
if (options?.cache === false) {
|
|
156
|
+
return generator();
|
|
157
|
+
}
|
|
158
|
+
const key = this.buildKey(input, options);
|
|
159
|
+
const start = Date.now();
|
|
160
|
+
const cached = await this.adapter.get(key);
|
|
161
|
+
if (cached && !TTLManager.isExpired(cached)) {
|
|
162
|
+
this.hits++;
|
|
163
|
+
this.metrics.emit("cache.hit", {
|
|
164
|
+
cacheType: this.cacheType,
|
|
165
|
+
latencyMs: Date.now() - start,
|
|
166
|
+
tokensSaved: cached.metadata.tokenCount,
|
|
167
|
+
provider: options?.provider,
|
|
168
|
+
model: options?.model
|
|
169
|
+
});
|
|
170
|
+
return cached.value;
|
|
171
|
+
}
|
|
172
|
+
this.misses++;
|
|
173
|
+
this.metrics.emit("cache.miss", {
|
|
174
|
+
cacheType: this.cacheType,
|
|
175
|
+
latencyMs: Date.now() - start,
|
|
176
|
+
provider: options?.provider,
|
|
177
|
+
model: options?.model
|
|
178
|
+
});
|
|
179
|
+
const value = await generator();
|
|
180
|
+
const ttl = options?.ttl ?? this.defaultTTL;
|
|
181
|
+
const entry = this.buildEntry(key, value, options);
|
|
182
|
+
await this.adapter.set(key, entry, ttl);
|
|
183
|
+
this.metrics.emit("cache.set", {
|
|
184
|
+
cacheType: this.cacheType,
|
|
185
|
+
latencyMs: Date.now() - start,
|
|
186
|
+
provider: options?.provider,
|
|
187
|
+
model: options?.model
|
|
188
|
+
});
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Removes a single entry by its input (and namespacing options).
|
|
193
|
+
*/
|
|
194
|
+
async invalidate(input, options) {
|
|
195
|
+
await this.adapter.delete(this.buildKey(input, options));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Returns hit/miss accounting for this manager plus the adapter's entry count.
|
|
199
|
+
*/
|
|
200
|
+
async stats() {
|
|
201
|
+
const total = this.hits + this.misses;
|
|
202
|
+
const adapterStats = await this.adapter.stats();
|
|
203
|
+
return {
|
|
204
|
+
hits: this.hits,
|
|
205
|
+
misses: this.misses,
|
|
206
|
+
hitRate: total === 0 ? 0 : this.hits / total,
|
|
207
|
+
entryCount: adapterStats.entryCount
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
export {
|
|
212
|
+
BaseCacheManager,
|
|
213
|
+
CacheAdapterError,
|
|
214
|
+
CompressionError,
|
|
215
|
+
JsonSerializer,
|
|
216
|
+
KeyBuilder,
|
|
217
|
+
NodeLLMCacheError,
|
|
218
|
+
SerializationError,
|
|
219
|
+
TTLManager,
|
|
220
|
+
ValidationError,
|
|
221
|
+
noopMetrics
|
|
222
|
+
};
|
|
223
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/KeyBuilder.ts","../src/TTLManager.ts","../src/Serializer.ts","../src/BaseCacheManager.ts"],"sourcesContent":["/**\n * Base class for every error thrown by NodeLLMCache packages. Catching this\n * catches anything the library throws intentionally.\n */\nexport class NodeLLMCacheError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = new.target.name\n // Restore prototype chain for instanceof across the ES5 transpile boundary.\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/** Thrown when a storage adapter operation fails. */\nexport class CacheAdapterError extends NodeLLMCacheError {}\n\n/** Thrown when compression or decompression fails. */\nexport class CompressionError extends NodeLLMCacheError {}\n\n/** Thrown when serialization or deserialization fails. */\nexport class SerializationError extends NodeLLMCacheError {}\n\n/** Thrown when a value fails validation (e.g. malformed configuration). */\nexport class ValidationError extends NodeLLMCacheError {}\n","import { createHash } from 'node:crypto'\nimport type { CacheType, LLMProvider } from './types.js'\n\n/**\n * Builds deterministic, collision-resistant cache keys.\n *\n * Format: `{type}:{provider}:{model}:{sha256-hash}`\n *\n * The raw input text is normalized and hashed with SHA-256 — keys never\n * contain raw prompt text, which keeps sensitive content out of storage keys\n * and logs.\n */\nexport class KeyBuilder {\n /**\n * Normalizes text before hashing so trivially different inputs collapse to\n * the same key: trims surrounding whitespace, lowercases, and collapses any\n * run of whitespace to a single space.\n */\n static normalize(text: string): string {\n return text.trim().toLowerCase().replace(/\\s+/g, ' ')\n }\n\n /**\n * Produces the SHA-256 hex digest of the normalized text.\n */\n static hash(text: string): string {\n return createHash('sha256').update(KeyBuilder.normalize(text)).digest('hex')\n }\n\n /**\n * Builds a fully namespaced cache key.\n *\n * @example\n * KeyBuilder.build('prompt', 'openai', 'gpt-4o', 'hello world')\n * // 'prompt:openai:gpt-4o:b94d27b9...'\n */\n static build(\n type: CacheType,\n provider: LLMProvider | string,\n model: string,\n text: string,\n ): string {\n return `${type}:${provider}:${model}:${KeyBuilder.hash(text)}`\n }\n}\n","import type { CacheEntry } from './interfaces.js'\n\n/**\n * Centralizes time-to-live arithmetic: computing expiry timestamps, checking\n * expiry, and supporting sliding-window refresh. All durations are relative\n * milliseconds; all timestamps are absolute epoch milliseconds.\n */\nexport class TTLManager {\n /**\n * Computes the absolute expiry timestamp for an entry created at `createdAt`\n * with a relative `ttl`. Returns `undefined` when `ttl` is not a positive\n * number, meaning the entry never expires.\n */\n static computeExpiresAt(createdAt: number, ttl?: number): number | undefined {\n if (ttl === undefined || ttl <= 0) return undefined\n return createdAt + ttl\n }\n\n /**\n * Returns true when the entry has an expiry and that expiry is at or before\n * `now` (defaults to the current time).\n */\n static isExpired(entry: Pick<CacheEntry<unknown>, 'expiresAt'>, now: number = Date.now()): boolean {\n return entry.expiresAt !== undefined && entry.expiresAt <= now\n }\n\n /**\n * Milliseconds remaining until the entry expires. Returns `Infinity` for\n * entries with no expiry, and `0` for already-expired entries.\n */\n static remaining(entry: Pick<CacheEntry<unknown>, 'expiresAt'>, now: number = Date.now()): number {\n if (entry.expiresAt === undefined) return Infinity\n return Math.max(0, entry.expiresAt - now)\n }\n\n /**\n * Computes a refreshed expiry for a sliding-window TTL: extends the entry's\n * life by `ttl` from `now`. Returns `undefined` when `ttl` is not positive.\n */\n static slide(ttl?: number, now: number = Date.now()): number | undefined {\n return TTLManager.computeExpiresAt(now, ttl)\n }\n}\n","import { SerializationError } from './errors.js'\n\n/**\n * Converts values to and from `Buffer` for storage. Implementations decide the\n * wire format.\n *\n * The architecture calls for MessagePack as the primary format, but\n * `@nodellmcache/core` must stay dependency-free, so it ships only the JSON\n * implementation below. A MessagePack serializer can be supplied by an optional\n * package and injected wherever a `Serializer` is accepted.\n */\nexport interface Serializer {\n serialize<T>(value: T): Buffer\n deserialize<T>(data: Buffer): T\n}\n\n/**\n * Default JSON-backed serializer. Zero dependencies; handles the common case\n * and wraps failures in {@link SerializationError}.\n */\nexport class JsonSerializer implements Serializer {\n serialize<T>(value: T): Buffer {\n try {\n return Buffer.from(JSON.stringify(value), 'utf8')\n } catch (cause) {\n throw new SerializationError('Failed to serialize value to JSON', { cause })\n }\n }\n\n deserialize<T>(data: Buffer): T {\n try {\n return JSON.parse(data.toString('utf8')) as T\n } catch (cause) {\n throw new SerializationError('Failed to deserialize JSON value', { cause })\n }\n }\n}\n","import { KeyBuilder } from './KeyBuilder.js'\nimport { TTLManager } from './TTLManager.js'\nimport type {\n CacheEntry,\n CacheOptions,\n CacheStats,\n MetricsSink,\n StorageAdapter,\n} from './interfaces.js'\nimport type { CacheType } from './types.js'\n\n/** A metrics sink that discards everything; the default when none is injected. */\nexport const noopMetrics: MetricsSink = {\n emit() {\n // intentionally empty\n },\n}\n\n/**\n * Construction options shared by all cache managers.\n */\nexport interface BaseCacheManagerOptions<T> {\n adapter: StorageAdapter<T>\n /** Default relative TTL in milliseconds applied to entries lacking their own. */\n defaultTTL?: number\n /** Metrics sink; defaults to a no-op so core stays dependency-free. */\n metrics?: MetricsSink\n}\n\n/**\n * Shared base for every feature cache (prompt, embedding, semantic, ...).\n *\n * Provides the cache-aside `getOrGenerate` flow, key building, entry\n * construction, invalidation, and hit/miss accounting. Subclasses declare their\n * {@link CacheType} and may override {@link buildKey} for custom namespacing.\n */\nexport abstract class BaseCacheManager<T> {\n /** The workload category for keys and metrics. */\n protected abstract readonly cacheType: CacheType\n\n protected readonly adapter: StorageAdapter<T>\n protected readonly defaultTTL: number | undefined\n protected readonly metrics: MetricsSink\n\n private hits = 0\n private misses = 0\n\n constructor(options: BaseCacheManagerOptions<T>) {\n this.adapter = options.adapter\n this.defaultTTL = options.defaultTTL\n this.metrics = options.metrics ?? noopMetrics\n }\n\n /**\n * Builds the storage key for an input. Defaults to the canonical\n * `{type}:{provider}:{model}:{hash}` format via {@link KeyBuilder}.\n */\n protected buildKey(input: string, options?: CacheOptions): string {\n return KeyBuilder.build(\n this.cacheType,\n options?.provider ?? 'unknown',\n options?.model ?? 'default',\n input,\n )\n }\n\n /**\n * Wraps a value in a {@link CacheEntry} with computed expiry and metadata.\n */\n protected buildEntry(key: string, value: T, options?: CacheOptions): CacheEntry<T> {\n const createdAt = Date.now()\n const ttl = options?.ttl ?? this.defaultTTL\n return {\n key,\n value,\n createdAt,\n expiresAt: TTLManager.computeExpiresAt(createdAt, ttl),\n metadata: {\n compressed: false,\n originalSize: 0,\n cacheType: this.cacheType,\n provider: options?.provider,\n model: options?.model,\n tokenCount: options?.tokenCount,\n },\n }\n }\n\n /**\n * Cache-aside read-through. Returns the cached value on a hit, otherwise\n * invokes `generator`, stores the result, and returns it. Set\n * `options.cache = false` to bypass the cache for both read and write.\n */\n async getOrGenerate(\n input: string,\n generator: () => Promise<T>,\n options?: CacheOptions,\n ): Promise<T> {\n // A deliberate bypass is neither a hit nor a miss — skip the cache and\n // metrics entirely so accounting reflects only real cache consultations.\n if (options?.cache === false) {\n return generator()\n }\n\n const key = this.buildKey(input, options)\n const start = Date.now()\n\n const cached = await this.adapter.get(key)\n if (cached && !TTLManager.isExpired(cached)) {\n this.hits++\n this.metrics.emit('cache.hit', {\n cacheType: this.cacheType,\n latencyMs: Date.now() - start,\n tokensSaved: cached.metadata.tokenCount,\n provider: options?.provider,\n model: options?.model,\n })\n return cached.value\n }\n\n this.misses++\n this.metrics.emit('cache.miss', {\n cacheType: this.cacheType,\n latencyMs: Date.now() - start,\n provider: options?.provider,\n model: options?.model,\n })\n\n const value = await generator()\n\n const ttl = options?.ttl ?? this.defaultTTL\n const entry = this.buildEntry(key, value, options)\n await this.adapter.set(key, entry, ttl)\n this.metrics.emit('cache.set', {\n cacheType: this.cacheType,\n latencyMs: Date.now() - start,\n provider: options?.provider,\n model: options?.model,\n })\n\n return value\n }\n\n /**\n * Removes a single entry by its input (and namespacing options).\n */\n async invalidate(input: string, options?: CacheOptions): Promise<void> {\n await this.adapter.delete(this.buildKey(input, options))\n }\n\n /**\n * Returns hit/miss accounting for this manager plus the adapter's entry count.\n */\n async stats(): Promise<CacheStats> {\n const total = this.hits + this.misses\n const adapterStats = await this.adapter.stats()\n return {\n hits: this.hits,\n misses: this.misses,\n hitRate: total === 0 ? 0 : this.hits / total,\n entryCount: adapterStats.entryCount,\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nodellmcache/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared interfaces, types, and utilities for NodeLLMCache — AI memory infrastructure for Node.js",
|
|
5
|
+
"keywords": ["llm", "cache", "ai", "memory", "nodejs", "embeddings"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mdmax007/node-llm-cache.git",
|
|
10
|
+
"directory": "packages/core"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/mdmax007/node-llm-cache/tree/main/packages/core#readme",
|
|
13
|
+
"bugs": "https://github.com/mdmax007/node-llm-cache/issues",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.cjs",
|
|
16
|
+
"module": "./dist/index.mjs",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.mjs",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": ["dist", "README.md", "CHANGELOG.md"],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"test:coverage": "vitest run --coverage",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"lint": "echo \"no lint configured\""
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"tsup": "^8.0.0",
|
|
37
|
+
"typescript": "^5.5.0",
|
|
38
|
+
"vitest": "^2.0.0",
|
|
39
|
+
"@vitest/coverage-v8": "^2.0.0"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|