cachetta 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/LICENSE +21 -0
- package/README.md +431 -0
- package/dist/index.js +368 -0
- package/dist/src/Cachetta.d.ts +87 -0
- package/dist/src/Cachetta.test.d.ts +1 -0
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.test.d.ts +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/read-cache.d.ts +8 -0
- package/dist/src/read-cache.test.d.ts +1 -0
- package/dist/src/type-guards.d.ts +5 -0
- package/dist/src/type-guards.test.d.ts +1 -0
- package/dist/src/types.d.ts +27 -0
- package/dist/src/utils/cache-fn.d.ts +3 -0
- package/dist/src/utils/cache-fn.test.d.ts +1 -0
- package/dist/src/utils/get-extension.d.ts +2 -0
- package/dist/src/utils/get-extension.test.d.ts +1 -0
- package/dist/src/utils/get-last-updated.d.ts +2 -0
- package/dist/src/utils/get-last-updated.test.d.ts +1 -0
- package/dist/src/utils/is-cache-expired.d.ts +1 -0
- package/dist/src/utils/is-cache-expired.test.d.ts +1 -0
- package/dist/src/utils/logger.d.ts +4 -0
- package/dist/src/utils/logger.test.d.ts +1 -0
- package/dist/src/utils/should-use-read-cache.d.ts +2 -0
- package/dist/src/utils/should-use-read-cache.test.d.ts +1 -0
- package/dist/src/utils/validate-cache-path.d.ts +1 -0
- package/dist/src/utils/validate-cache-path.test.d.ts +1 -0
- package/dist/src/write-cache.d.ts +2 -0
- package/dist/src/write-cache.test.d.ts +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kevin Scott
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# Cachetta for JavaScript/TypeScript
|
|
2
|
+
|
|
3
|
+
File-based JSON caching for JavaScript and TypeScript. Part of the [Cachetta](../../README.md) project, which provides the same caching API in both JS/TS and Python -- learn it once, use it in either language.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add cachetta
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Local File Storage**: Supports local files with automatic directory creation
|
|
14
|
+
- **JSON Serialization**: JSON-based caching (native JavaScript support)
|
|
15
|
+
- **Async Support**: Works with both synchronous and asynchronous functions
|
|
16
|
+
- **Automatic Expiration**: Cache expiration based on file modification time
|
|
17
|
+
- **In-Memory LRU**: Optional in-memory LRU layer for fast repeated access
|
|
18
|
+
- **Stale-While-Revalidate**: Serve stale data while refreshing in the background
|
|
19
|
+
- **Conditional Caching**: Cache only when a condition is met
|
|
20
|
+
- **Cache Inspection**: Check existence, age, and expiry state of cache entries
|
|
21
|
+
- **Auto Cache Keys**: Automatic unique paths based on function arguments
|
|
22
|
+
- **Flexible Paths**: Dynamic cache paths using functions
|
|
23
|
+
- **Error Handling**: Graceful handling of corrupt cache files
|
|
24
|
+
- **Logging**: Built-in logging for debugging
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Basic Usage
|
|
29
|
+
|
|
30
|
+
Create a cache object:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
import { Cachetta } from 'cachetta';
|
|
34
|
+
|
|
35
|
+
const cache = new Cachetta({
|
|
36
|
+
read: true, // allow reading from local caches
|
|
37
|
+
write: true, // allow writing to local caches
|
|
38
|
+
path: './cache.json', // specify path to cache file
|
|
39
|
+
duration: 24 * 60 * 60 * 1000, // specify length of cache in milliseconds (1 day)
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Read and write to a cache object:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
import { readCache, writeCache } from 'cachetta';
|
|
47
|
+
|
|
48
|
+
async function getData() {
|
|
49
|
+
const cachedData = await readCache(cache);
|
|
50
|
+
if (cachedData) {
|
|
51
|
+
return cachedData;
|
|
52
|
+
} else {
|
|
53
|
+
const data = await fetchData(); // some long running process
|
|
54
|
+
await writeCache(cache, data);
|
|
55
|
+
return data;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Specifying paths
|
|
61
|
+
|
|
62
|
+
You can specify a base path for your cache folder and then quickly specify cache paths within that folder:
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
import { readCache, writeCache, Cachetta } from 'cachetta';
|
|
66
|
+
import path from 'path';
|
|
67
|
+
|
|
68
|
+
const cache = new Cachetta({
|
|
69
|
+
path: './cache', // our base cache folder
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
async function getData() {
|
|
73
|
+
// specify your file path like below:
|
|
74
|
+
const cachePath = path.join(cache.path, 'my-data.json');
|
|
75
|
+
const cachedData = await readCache(cache.copy({ path: cachePath }));
|
|
76
|
+
// ...
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For modifying other attributes of a base `cache` object, use `copy`:
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
const cache = new Cachetta({
|
|
84
|
+
path: './cache', // our base cache folder
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const newCache = cache.copy({
|
|
88
|
+
read: false,
|
|
89
|
+
write: false,
|
|
90
|
+
duration: 2 * 24 * 60 * 60 * 1000, // 2 days
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Note**: The `copy` method is the intended public API for creating variations of cache configurations. It creates a new `Cachetta` instance with the specified overrides while preserving the original configuration.
|
|
95
|
+
|
|
96
|
+
### Decorators
|
|
97
|
+
|
|
98
|
+
You can use `Cachetta` as a decorator (requires experimental decorators):
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
import { Cachetta } from 'cachetta';
|
|
102
|
+
|
|
103
|
+
class DataService {
|
|
104
|
+
@Cachetta({ path: '/my-cache.json' })
|
|
105
|
+
async getData() {
|
|
106
|
+
const parts = [];
|
|
107
|
+
for (let i = 0; i < 10; i++) {
|
|
108
|
+
parts.push(i);
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
110
|
+
}
|
|
111
|
+
return parts;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
You can also use a specific cache object as a decorator:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
import { Cachetta } from 'cachetta';
|
|
120
|
+
|
|
121
|
+
const cache = new Cachetta({ path: '/my-cache.json' });
|
|
122
|
+
|
|
123
|
+
class DataService {
|
|
124
|
+
@cache
|
|
125
|
+
async getData() {
|
|
126
|
+
const parts = [];
|
|
127
|
+
for (let i = 0; i < 10; i++) {
|
|
128
|
+
parts.push(i);
|
|
129
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
130
|
+
}
|
|
131
|
+
return parts;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Or with arguments:
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
import { Cachetta } from 'cachetta';
|
|
140
|
+
|
|
141
|
+
const cache = new Cachetta({ path: '/my-cache.json' });
|
|
142
|
+
|
|
143
|
+
class DataService {
|
|
144
|
+
@cache({ duration: 1000 })
|
|
145
|
+
async getData() {
|
|
146
|
+
const parts = [];
|
|
147
|
+
for (let i = 0; i < 10; i++) {
|
|
148
|
+
parts.push(i);
|
|
149
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
150
|
+
}
|
|
151
|
+
return parts;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Important Note**: Decorated functions always return Promises, even if the original function is synchronous. This is because the caching mechanism involves async file operations. Always use `await` when calling decorated functions:
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
const cache = new Cachetta({ path: './cache.json' });
|
|
160
|
+
|
|
161
|
+
// Even though this is a sync function, the decorated version returns a Promise
|
|
162
|
+
const cachedFunction = cache.call(() => {
|
|
163
|
+
return "Hello World";
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Must await the result
|
|
167
|
+
const result = await cachedFunction();
|
|
168
|
+
console.log(result); // "Hello World"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Async Function Support
|
|
172
|
+
|
|
173
|
+
Cachetta works seamlessly with async functions:
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
import { Cachetta } from 'cachetta';
|
|
177
|
+
|
|
178
|
+
@Cachetta({ path: './async-cache.json' })
|
|
179
|
+
async function getAsyncData() {
|
|
180
|
+
// Simulate async API call
|
|
181
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
182
|
+
return { status: "success", data: [1, 2, 3] };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Usage
|
|
186
|
+
async function main() {
|
|
187
|
+
const result = await getAsyncData();
|
|
188
|
+
console.log(result);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Auto Cache Keys
|
|
193
|
+
|
|
194
|
+
When a wrapped function receives arguments, Cachetta automatically generates unique cache paths by hashing the arguments:
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
const cache = new Cachetta({ path: './cache/users.json' });
|
|
198
|
+
|
|
199
|
+
const getUser = cache((userId) => fetchUser(userId));
|
|
200
|
+
|
|
201
|
+
await getUser(1); // cached at ./cache/users-<hash1>.json
|
|
202
|
+
await getUser(2); // cached at ./cache/users-<hash2>.json
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### In-Memory LRU
|
|
206
|
+
|
|
207
|
+
Add an in-memory LRU layer that is checked before hitting disk:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
const cache = new Cachetta({
|
|
211
|
+
path: './cache.json',
|
|
212
|
+
lruSize: 100, // keep up to 100 entries in memory
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
LRU entries respect the same `duration` as disk entries and use lazy expiration (evicted on access, not via background timers).
|
|
217
|
+
|
|
218
|
+
### Conditional Caching
|
|
219
|
+
|
|
220
|
+
Cache results only when a condition function returns `true`:
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
const cache = new Cachetta({
|
|
224
|
+
path: './cache.json',
|
|
225
|
+
condition: (result) => result !== null, // don't cache null
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Stale-While-Revalidate
|
|
230
|
+
|
|
231
|
+
Return expired (stale) data immediately while refreshing the cache in the background:
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
const cache = new Cachetta({
|
|
235
|
+
path: './cache.json',
|
|
236
|
+
duration: 60 * 60 * 1000, // 1 hour
|
|
237
|
+
staleDuration: 30 * 60 * 1000, // serve stale data up to 30min past expiry
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Cache Invalidation
|
|
242
|
+
|
|
243
|
+
Delete cache files on disk:
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
const cache = new Cachetta({ path: './cache.json' });
|
|
247
|
+
|
|
248
|
+
await cache.invalidate(); // or cache.clear()
|
|
249
|
+
|
|
250
|
+
// With arguments (when using path functions)
|
|
251
|
+
await cache.invalidate('userId');
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Cache Inspection
|
|
255
|
+
|
|
256
|
+
Query cache state without reading the cached data:
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
const cache = new Cachetta({ path: './cache.json' });
|
|
260
|
+
|
|
261
|
+
await cache.exists(); // true if the cache file exists
|
|
262
|
+
await cache.age(); // age in milliseconds, or null
|
|
263
|
+
await cache.info(); // { exists: true, age: 1234, expired: false, stale: false, path: "..." }
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Dynamic Cache Paths
|
|
267
|
+
|
|
268
|
+
You can specify a function for defining the path as well:
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
function getCachePath(n) {
|
|
272
|
+
return `./cache/${n}.json`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@Cachetta({ path: getCachePath })
|
|
276
|
+
async function foo(n) {
|
|
277
|
+
const parts = [];
|
|
278
|
+
for (let i = 0; i < n; i++) {
|
|
279
|
+
parts.push(i);
|
|
280
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
281
|
+
}
|
|
282
|
+
return parts;
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Or, using a pre-existing cache object:
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
const cache = new Cachetta({ path: './cache' });
|
|
290
|
+
|
|
291
|
+
function getCachePath(n) {
|
|
292
|
+
return path.join(cache.path, `${n}.json`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@cache.copy({ path: getCachePath })
|
|
296
|
+
async function foo(n) {
|
|
297
|
+
const parts = [];
|
|
298
|
+
for (let i = 0; i < n; i++) {
|
|
299
|
+
parts.push(i);
|
|
300
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
301
|
+
}
|
|
302
|
+
return parts;
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Function Wrapper (Alternative to Decorators)
|
|
307
|
+
|
|
308
|
+
If you're not using decorators, you can wrap functions manually:
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
import { Cachetta } from 'cachetta';
|
|
312
|
+
|
|
313
|
+
const cache = new Cachetta({ path: './my-cache.json' });
|
|
314
|
+
|
|
315
|
+
async function getData() {
|
|
316
|
+
const parts = [];
|
|
317
|
+
for (let i = 0; i < 10; i++) {
|
|
318
|
+
parts.push(i);
|
|
319
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
320
|
+
}
|
|
321
|
+
return parts;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Wrap the function with caching
|
|
325
|
+
const cachedGetData = cache(getData);
|
|
326
|
+
|
|
327
|
+
// Usage - always await the result, even for sync functions
|
|
328
|
+
const result = await cachedGetData();
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
You can also pass configuration when wrapping:
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
const cache = new Cachetta({ path: './cache' });
|
|
335
|
+
|
|
336
|
+
function getData(id) {
|
|
337
|
+
return { id, data: 'some data' };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Wrap with specific configuration
|
|
341
|
+
const cachedGetData = cache(getData, {
|
|
342
|
+
path: (id) => `./cache/data-${id}.json`,
|
|
343
|
+
duration: 5000
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Usage
|
|
347
|
+
const result = await cachedGetData(123);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Note**: Wrapped functions always return Promises, even if the original function is synchronous, due to the async nature of file operations in the caching mechanism.
|
|
351
|
+
|
|
352
|
+
### Error Handling
|
|
353
|
+
|
|
354
|
+
Cachetta gracefully handles corrupt cache files:
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
import { readCache, writeCache, Cachetta } from 'cachetta';
|
|
358
|
+
|
|
359
|
+
const cache = new Cachetta({ path: './corrupt-cache.json' });
|
|
360
|
+
|
|
361
|
+
// If the cache file is corrupt, readCache will return null
|
|
362
|
+
async function getData() {
|
|
363
|
+
const data = await readCache(cache);
|
|
364
|
+
if (data === null) {
|
|
365
|
+
// Cache is missing or corrupt, regenerate data
|
|
366
|
+
const freshData = await fetchFreshData();
|
|
367
|
+
await writeCache(cache, freshData);
|
|
368
|
+
return freshData;
|
|
369
|
+
}
|
|
370
|
+
return data;
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Logging
|
|
375
|
+
|
|
376
|
+
Cachetta provides detailed logging for debugging. You can configure the log level:
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
import { setLogLevel } from 'cachetta';
|
|
380
|
+
|
|
381
|
+
// Enable debug logging
|
|
382
|
+
setLogLevel('debug');
|
|
383
|
+
|
|
384
|
+
// Available levels: 'error', 'warn', 'info', 'debug'
|
|
385
|
+
// Default is 'warn'
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Cachetta uses a simple logger that outputs to `console` by default, but you can also configure it to use your preferred logging library:
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
import { setLogger } from 'cachetta';
|
|
392
|
+
|
|
393
|
+
// Use a custom logger (e.g., winston, pino)
|
|
394
|
+
setLogger({
|
|
395
|
+
debug: (msg) => console.debug(`[Cachetta] ${msg}`),
|
|
396
|
+
info: (msg) => console.info(`[Cachetta] ${msg}`),
|
|
397
|
+
warn: (msg) => console.warn(`[Cachetta] ${msg}`),
|
|
398
|
+
error: (msg) => console.error(`[Cachetta] ${msg}`),
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### TypeScript Support
|
|
403
|
+
|
|
404
|
+
Cachetta includes full TypeScript support:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { Cachetta } from 'cachetta';
|
|
408
|
+
|
|
409
|
+
interface UserData {
|
|
410
|
+
id: number;
|
|
411
|
+
name: string;
|
|
412
|
+
email: string;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const cache = new Cachetta<UserData>({
|
|
416
|
+
path: './user-cache.json',
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
@cache
|
|
420
|
+
async function fetchUserData(id: number): Promise<UserData> {
|
|
421
|
+
// API call implementation
|
|
422
|
+
return { id, name: 'John Doe', email: 'john@example.com' };
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Default Configuration
|
|
427
|
+
|
|
428
|
+
- **Default duration**: 7 days (7 * 24 * 60 * 60 * 1000 milliseconds)
|
|
429
|
+
- **Default read**: `true`
|
|
430
|
+
- **Default write**: `true`
|
|
431
|
+
- **Supported format**: JSON only
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { promises as c } from "fs";
|
|
2
|
+
import { normalize as I, resolve as D, dirname as C, join as $ } from "path";
|
|
3
|
+
import { randomBytes as U, createHash as N } from "crypto";
|
|
4
|
+
import { inspect as O } from "util";
|
|
5
|
+
const R = (e) => typeof e == "object" && e !== null, F = (e) => R(e) && ("path" in e || "write" in e || "read" in e || "duration" in e || "lruSize" in e || "condition" in e || "staleDuration" in e), j = (e) => typeof e == "function" && "__cacheBuddy__" in e && e.__cacheBuddy__ === !0, _ = Symbol("LRU_MISS");
|
|
6
|
+
class m extends Error {
|
|
7
|
+
constructor(t) {
|
|
8
|
+
super(t), this.name = "CachettaError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
class M extends m {
|
|
12
|
+
constructor(t) {
|
|
13
|
+
super(`Invalid cache path (path traversal detected): ${t}`), this.name = "InvalidPathError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class E extends m {
|
|
17
|
+
constructor(t) {
|
|
18
|
+
super(`Unsupported cache format: ${t}`), this.name = "UnsupportedFormatError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function v(e) {
|
|
22
|
+
var i;
|
|
23
|
+
const n = (i = String(e).split("/").pop()) == null ? void 0 : i.split(".");
|
|
24
|
+
if (!n || n.length === 1 || n[n.length - 1] === "")
|
|
25
|
+
throw new m(`Missing file extension: ${e}`);
|
|
26
|
+
return n[n.length - 1];
|
|
27
|
+
}
|
|
28
|
+
async function d(e) {
|
|
29
|
+
try {
|
|
30
|
+
return (await c.stat(e)).mtime.getTime();
|
|
31
|
+
} catch (t) {
|
|
32
|
+
if (t.code === "ENOENT")
|
|
33
|
+
return null;
|
|
34
|
+
throw t;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function A(e, t, n) {
|
|
38
|
+
if (e > t)
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Invalid arguments, cache time ${e} cannot be greater than now ${t}`
|
|
41
|
+
);
|
|
42
|
+
return t - e >= n;
|
|
43
|
+
}
|
|
44
|
+
let P = "warn";
|
|
45
|
+
const x = ["error", "warn", "info", "debug"], G = (e) => {
|
|
46
|
+
const t = x.indexOf(P);
|
|
47
|
+
return x.indexOf(e) <= t;
|
|
48
|
+
}, J = (e) => {
|
|
49
|
+
switch (e) {
|
|
50
|
+
case "debug":
|
|
51
|
+
return console.debug;
|
|
52
|
+
case "info":
|
|
53
|
+
return console.info;
|
|
54
|
+
case "warn":
|
|
55
|
+
return console.warn;
|
|
56
|
+
case "error":
|
|
57
|
+
return console.error;
|
|
58
|
+
}
|
|
59
|
+
}, p = (e) => (...t) => {
|
|
60
|
+
if (G(e)) {
|
|
61
|
+
const n = J(e);
|
|
62
|
+
n && n("[Cachetta]", ...t);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
let a = {
|
|
66
|
+
debug: p("debug"),
|
|
67
|
+
info: p("info"),
|
|
68
|
+
warn: p("warn"),
|
|
69
|
+
error: p("error")
|
|
70
|
+
};
|
|
71
|
+
function Z(e) {
|
|
72
|
+
P = e;
|
|
73
|
+
}
|
|
74
|
+
function k(e) {
|
|
75
|
+
a = e;
|
|
76
|
+
}
|
|
77
|
+
async function T({ duration: e, read: t }, n) {
|
|
78
|
+
const i = e ?? 6048e5, r = await d(n);
|
|
79
|
+
if (r === null)
|
|
80
|
+
return a.debug(`Cache time is null for ${n}`), !1;
|
|
81
|
+
if (i <= 0)
|
|
82
|
+
return a.debug(`Cache length is ${i}, considering expired for ${n}`), !1;
|
|
83
|
+
const s = Date.now();
|
|
84
|
+
return r > s ? (a.debug(`Cache time ${r} is ahead of now ${s}, treating as valid cache for ${n}`), t ?? !0) : A(r, s, i) ? (a.debug(
|
|
85
|
+
`Cache is expired (${r}, expected ${r + i}) for ${n}`
|
|
86
|
+
), !1) : (a.debug(
|
|
87
|
+
`Cache is not expired (${r}, expected ${r + i}) for ${n}`
|
|
88
|
+
), t ?? !0);
|
|
89
|
+
}
|
|
90
|
+
function h(e) {
|
|
91
|
+
const t = I(e);
|
|
92
|
+
if (t.split(/[/\\]/).includes(".."))
|
|
93
|
+
throw new M(e);
|
|
94
|
+
return D(t);
|
|
95
|
+
}
|
|
96
|
+
const K = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
97
|
+
async function z(e) {
|
|
98
|
+
const t = v(e);
|
|
99
|
+
if (t !== "json")
|
|
100
|
+
throw new E(t);
|
|
101
|
+
try {
|
|
102
|
+
const n = await c.readFile(e, "utf8");
|
|
103
|
+
return JSON.parse(n, (i, r) => {
|
|
104
|
+
if (!K.has(i))
|
|
105
|
+
return r;
|
|
106
|
+
});
|
|
107
|
+
} catch (n) {
|
|
108
|
+
return n.code === "ENOENT" ? null : n instanceof SyntaxError ? (a.error(`Corrupt JSON: ${n}`), null) : (a.error(`Read error: ${n}`), null);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function W(e, ...t) {
|
|
112
|
+
if (!j(e))
|
|
113
|
+
throw new m(`Invalid value provided, you must provide an instance of Cachetta: ${e}`);
|
|
114
|
+
const n = e._getPath(...t);
|
|
115
|
+
h(n);
|
|
116
|
+
const i = e._lruGet(n);
|
|
117
|
+
if (i !== _)
|
|
118
|
+
return a.debug(`LRU cache hit for ${n}`), i;
|
|
119
|
+
if (await T(e, n)) {
|
|
120
|
+
a.debug(`Using cache at ${n}`);
|
|
121
|
+
const r = await z(n);
|
|
122
|
+
return r !== null && (a.debug(`Used cache at ${n}`), e._lruSet(n, r)), r;
|
|
123
|
+
} else
|
|
124
|
+
return a.debug("cache.read is false, skipping cache"), null;
|
|
125
|
+
}
|
|
126
|
+
async function H(e, ...t) {
|
|
127
|
+
if (!e.staleDuration || !e.read) return null;
|
|
128
|
+
const n = e._getPath(...t);
|
|
129
|
+
h(n);
|
|
130
|
+
const i = await d(n);
|
|
131
|
+
if (i === null) return null;
|
|
132
|
+
const r = Date.now() - i, s = r >= e.duration, o = r < e.duration + e.staleDuration;
|
|
133
|
+
return s && o ? (a.debug(`Returning stale cache for ${n} (age: ${r}ms)`), z(n)) : null;
|
|
134
|
+
}
|
|
135
|
+
const b = /* @__PURE__ */ new Set();
|
|
136
|
+
async function y(e, t, ...n) {
|
|
137
|
+
if (!e || !e.write)
|
|
138
|
+
return;
|
|
139
|
+
const i = e._getPath(...n);
|
|
140
|
+
h(i);
|
|
141
|
+
const r = v(i), s = D(C(i));
|
|
142
|
+
if (b.has(s) || (await c.mkdir(s, { recursive: !0 }), b.add(s)), r === "json") {
|
|
143
|
+
const o = JSON.stringify(t), u = $(s, `.cachetta-${U(8).toString("hex")}.tmp`);
|
|
144
|
+
try {
|
|
145
|
+
await c.writeFile(u, o, "utf8"), await c.rename(u, i), e._lruSet(i, t);
|
|
146
|
+
} catch (l) {
|
|
147
|
+
try {
|
|
148
|
+
await c.unlink(u);
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
throw l;
|
|
152
|
+
}
|
|
153
|
+
} else
|
|
154
|
+
throw new E(r);
|
|
155
|
+
}
|
|
156
|
+
const w = /* @__PURE__ */ new Map(), S = /* @__PURE__ */ new Set(), g = (e, t) => {
|
|
157
|
+
async function n(...i) {
|
|
158
|
+
const r = await W(e, ...i);
|
|
159
|
+
if (r != null)
|
|
160
|
+
return r;
|
|
161
|
+
const s = e._getPath(...i);
|
|
162
|
+
if (e.staleDuration) {
|
|
163
|
+
const l = await H(e, ...i);
|
|
164
|
+
if (l != null)
|
|
165
|
+
return !S.has(s) && !w.has(s) && (S.add(s), (async () => {
|
|
166
|
+
try {
|
|
167
|
+
const f = await t.apply(this, i);
|
|
168
|
+
(!e.condition || e.condition(f)) && await y(e, f, ...i);
|
|
169
|
+
} catch (f) {
|
|
170
|
+
a.error(`Background revalidation failed for ${s}: ${f}`);
|
|
171
|
+
} finally {
|
|
172
|
+
S.delete(s);
|
|
173
|
+
}
|
|
174
|
+
})()), l;
|
|
175
|
+
}
|
|
176
|
+
const o = w.get(s);
|
|
177
|
+
if (o)
|
|
178
|
+
return o;
|
|
179
|
+
const u = (async () => {
|
|
180
|
+
const l = await t.apply(this, i);
|
|
181
|
+
return (!e.condition || e.condition(l)) && await y(e, l, ...i), l;
|
|
182
|
+
})();
|
|
183
|
+
w.set(s, u);
|
|
184
|
+
try {
|
|
185
|
+
return await u;
|
|
186
|
+
} finally {
|
|
187
|
+
w.delete(s);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return n;
|
|
191
|
+
}, Y = 10080 * 60 * 1e3;
|
|
192
|
+
class L extends Function {
|
|
193
|
+
constructor(t) {
|
|
194
|
+
super(), this.__cacheBuddy__ = !0, this.path = t.path, this.write = t.write ?? !0, this.read = t.read ?? !0, this.duration = t.duration ?? Y, this.lruSize = t.lruSize, this.condition = t.condition, this.staleDuration = t.staleDuration, this._lru = this.lruSize ? /* @__PURE__ */ new Map() : void 0;
|
|
195
|
+
const n = this.call.bind(this), i = Object.assign(
|
|
196
|
+
n,
|
|
197
|
+
this,
|
|
198
|
+
{
|
|
199
|
+
copy: this.copy.bind(this),
|
|
200
|
+
wrap: this.wrap.bind(this),
|
|
201
|
+
invalidate: this.invalidate.bind(this),
|
|
202
|
+
clear: this.invalidate.bind(this),
|
|
203
|
+
// alias
|
|
204
|
+
exists: this.exists.bind(this),
|
|
205
|
+
age: this.age.bind(this),
|
|
206
|
+
info: this.info.bind(this),
|
|
207
|
+
_getPath: this._getPath.bind(this),
|
|
208
|
+
_lruGet: this._lruGet.bind(this),
|
|
209
|
+
_lruSet: this._lruSet.bind(this),
|
|
210
|
+
__cacheBuddy__: !0,
|
|
211
|
+
_lru: this._lru,
|
|
212
|
+
lruSize: this.lruSize,
|
|
213
|
+
condition: this.condition,
|
|
214
|
+
staleDuration: this.staleDuration
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
return i[O.custom] = () => `Cachetta { path: '${this.path}', write: ${this.write}, read: ${this.read}, duration: ${this.duration} }`, i;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Creates a copy of this Cachetta instance with overridden configuration.
|
|
221
|
+
* Useful for creating variations of a base cache configuration.
|
|
222
|
+
*
|
|
223
|
+
* @param kwargs - Partial configuration to override
|
|
224
|
+
* @returns A new Cachetta instance with the specified overrides
|
|
225
|
+
*/
|
|
226
|
+
copy(t) {
|
|
227
|
+
return new L({
|
|
228
|
+
path: t.path ?? this.path,
|
|
229
|
+
write: t.write ?? this.write,
|
|
230
|
+
read: t.read ?? this.read,
|
|
231
|
+
duration: t.duration ?? this.duration,
|
|
232
|
+
lruSize: t.lruSize ?? this.lruSize,
|
|
233
|
+
condition: t.condition ?? this.condition,
|
|
234
|
+
staleDuration: t.staleDuration ?? this.staleDuration
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Wraps a function with caching behavior. Alias for calling the cache instance directly.
|
|
239
|
+
*
|
|
240
|
+
* @param fn - The function to wrap
|
|
241
|
+
* @returns A cached version of the function
|
|
242
|
+
*/
|
|
243
|
+
wrap(t) {
|
|
244
|
+
return g(this, t);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Deletes the cache file on disk and clears LRU entries for this path.
|
|
248
|
+
* No-op if the cache file does not exist.
|
|
249
|
+
*
|
|
250
|
+
* @param args - Arguments to resolve the cache path (when using a path function)
|
|
251
|
+
*/
|
|
252
|
+
async invalidate(...t) {
|
|
253
|
+
const n = this._getPath(...t);
|
|
254
|
+
h(n), this._lru && this._lru.delete(n);
|
|
255
|
+
try {
|
|
256
|
+
await c.unlink(n);
|
|
257
|
+
} catch (i) {
|
|
258
|
+
if (i.code !== "ENOENT")
|
|
259
|
+
throw i;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Checks whether the cache file exists on disk.
|
|
264
|
+
*
|
|
265
|
+
* @param args - Arguments to resolve the cache path (when using a path function)
|
|
266
|
+
* @returns true if the cache file exists
|
|
267
|
+
*/
|
|
268
|
+
async exists(...t) {
|
|
269
|
+
const n = this._getPath(...t);
|
|
270
|
+
return h(n), await d(n) !== null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Returns the age of the cache file in milliseconds, or null if it does not exist.
|
|
274
|
+
*
|
|
275
|
+
* @param args - Arguments to resolve the cache path (when using a path function)
|
|
276
|
+
* @returns Age in ms, or null
|
|
277
|
+
*/
|
|
278
|
+
async age(...t) {
|
|
279
|
+
const n = this._getPath(...t);
|
|
280
|
+
h(n);
|
|
281
|
+
const i = await d(n);
|
|
282
|
+
return i === null ? null : Date.now() - i;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Returns detailed information about the cache state.
|
|
286
|
+
*
|
|
287
|
+
* @param args - Arguments to resolve the cache path (when using a path function)
|
|
288
|
+
* @returns CacheInfo with exists, age, expired, stale, and path fields
|
|
289
|
+
*/
|
|
290
|
+
async info(...t) {
|
|
291
|
+
const n = this._getPath(...t);
|
|
292
|
+
h(n);
|
|
293
|
+
const i = await d(n);
|
|
294
|
+
if (i === null)
|
|
295
|
+
return { exists: !1, age: null, expired: !1, stale: !1, path: n };
|
|
296
|
+
const r = Date.now() - i, s = r >= this.duration, o = s && this.staleDuration != null && r < this.duration + this.staleDuration;
|
|
297
|
+
return { exists: !0, age: r, expired: s, stale: o, path: n };
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Internal method to resolve the cache path.
|
|
301
|
+
* When path is a string and arguments are provided, auto-generates a unique
|
|
302
|
+
* cache path by hashing the arguments.
|
|
303
|
+
* @internal
|
|
304
|
+
*/
|
|
305
|
+
_getPath(...t) {
|
|
306
|
+
if (typeof this.path == "string") {
|
|
307
|
+
if (t.length === 0)
|
|
308
|
+
return this.path;
|
|
309
|
+
const n = N("sha256").update(JSON.stringify(t)).digest("hex").slice(0, 16), i = C(this.path), r = this.path.split("/").pop(), s = r.lastIndexOf(".");
|
|
310
|
+
if (s === -1)
|
|
311
|
+
return $(i, `${r}-${n}`);
|
|
312
|
+
const o = r.slice(0, s), u = r.slice(s);
|
|
313
|
+
return $(i, `${o}-${n}${u}`);
|
|
314
|
+
}
|
|
315
|
+
return this.path(...t);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Get a value from the in-memory LRU cache.
|
|
319
|
+
* Returns undefined if LRU is disabled, key not found, or entry is expired.
|
|
320
|
+
* @internal
|
|
321
|
+
*/
|
|
322
|
+
_lruGet(t) {
|
|
323
|
+
if (!this._lru) return _;
|
|
324
|
+
const n = this._lru.get(t);
|
|
325
|
+
return n ? Date.now() - n.timestamp > this.duration ? (this._lru.delete(t), _) : (this._lru.delete(t), this._lru.set(t, n), n.value) : _;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Set a value in the in-memory LRU cache.
|
|
329
|
+
* No-op if LRU is disabled.
|
|
330
|
+
* @internal
|
|
331
|
+
*/
|
|
332
|
+
_lruSet(t, n) {
|
|
333
|
+
if (!(!this._lru || !this.lruSize)) {
|
|
334
|
+
if (this._lru.size >= this.lruSize && !this._lru.has(t)) {
|
|
335
|
+
const i = this._lru.keys().next().value;
|
|
336
|
+
i !== void 0 && this._lru.delete(i);
|
|
337
|
+
}
|
|
338
|
+
this._lru.set(t, { value: n, timestamp: Date.now() });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// Implementation signature
|
|
342
|
+
call(t, n, i) {
|
|
343
|
+
if (F(t)) {
|
|
344
|
+
const s = t;
|
|
345
|
+
return this.copy(s);
|
|
346
|
+
}
|
|
347
|
+
if (i) {
|
|
348
|
+
const s = i.value;
|
|
349
|
+
return i.value = g(this, s), i;
|
|
350
|
+
}
|
|
351
|
+
const r = t;
|
|
352
|
+
if (n) {
|
|
353
|
+
const s = n, o = this.copy(s);
|
|
354
|
+
return g(o, r);
|
|
355
|
+
}
|
|
356
|
+
return g(this, r);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
export {
|
|
360
|
+
L as Cachetta,
|
|
361
|
+
m as CachettaError,
|
|
362
|
+
M as InvalidPathError,
|
|
363
|
+
E as UnsupportedFormatError,
|
|
364
|
+
W as readCache,
|
|
365
|
+
Z as setLogLevel,
|
|
366
|
+
k as setLogger,
|
|
367
|
+
y as writeCache
|
|
368
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { CacheConfig, CacheInfo, CachableFunction, PathFn } from './types.js';
|
|
2
|
+
import { LRU_MISS } from './constants.js';
|
|
3
|
+
interface LruEntry {
|
|
4
|
+
value: unknown;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class Cachetta<Path extends string | PathFn<any> = string> extends Function {
|
|
8
|
+
protected __cacheBuddy__: boolean;
|
|
9
|
+
path: Path;
|
|
10
|
+
write: boolean;
|
|
11
|
+
read: boolean;
|
|
12
|
+
duration: number;
|
|
13
|
+
lruSize: number | undefined;
|
|
14
|
+
condition: ((result: unknown) => boolean) | undefined;
|
|
15
|
+
staleDuration: number | undefined;
|
|
16
|
+
/** Alias for {@link invalidate}. Deletes the cache file. */
|
|
17
|
+
clear: (...args: unknown[]) => Promise<void>;
|
|
18
|
+
/** @internal */
|
|
19
|
+
_lru: Map<string, LruEntry> | undefined;
|
|
20
|
+
constructor(config: CacheConfig<Path>);
|
|
21
|
+
/**
|
|
22
|
+
* Creates a copy of this Cachetta instance with overridden configuration.
|
|
23
|
+
* Useful for creating variations of a base cache configuration.
|
|
24
|
+
*
|
|
25
|
+
* @param kwargs - Partial configuration to override
|
|
26
|
+
* @returns A new Cachetta instance with the specified overrides
|
|
27
|
+
*/
|
|
28
|
+
copy<NewPath extends string | PathFn<any> = string>(kwargs: Partial<CacheConfig<NewPath>>): Cachetta<NewPath>;
|
|
29
|
+
/**
|
|
30
|
+
* Wraps a function with caching behavior. Alias for calling the cache instance directly.
|
|
31
|
+
*
|
|
32
|
+
* @param fn - The function to wrap
|
|
33
|
+
* @returns A cached version of the function
|
|
34
|
+
*/
|
|
35
|
+
wrap(fn: CachableFunction): CachableFunction;
|
|
36
|
+
/**
|
|
37
|
+
* Deletes the cache file on disk and clears LRU entries for this path.
|
|
38
|
+
* No-op if the cache file does not exist.
|
|
39
|
+
*
|
|
40
|
+
* @param args - Arguments to resolve the cache path (when using a path function)
|
|
41
|
+
*/
|
|
42
|
+
invalidate(...args: unknown[]): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Checks whether the cache file exists on disk.
|
|
45
|
+
*
|
|
46
|
+
* @param args - Arguments to resolve the cache path (when using a path function)
|
|
47
|
+
* @returns true if the cache file exists
|
|
48
|
+
*/
|
|
49
|
+
exists(...args: unknown[]): Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* Returns the age of the cache file in milliseconds, or null if it does not exist.
|
|
52
|
+
*
|
|
53
|
+
* @param args - Arguments to resolve the cache path (when using a path function)
|
|
54
|
+
* @returns Age in ms, or null
|
|
55
|
+
*/
|
|
56
|
+
age(...args: unknown[]): Promise<number | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Returns detailed information about the cache state.
|
|
59
|
+
*
|
|
60
|
+
* @param args - Arguments to resolve the cache path (when using a path function)
|
|
61
|
+
* @returns CacheInfo with exists, age, expired, stale, and path fields
|
|
62
|
+
*/
|
|
63
|
+
info(...args: unknown[]): Promise<CacheInfo>;
|
|
64
|
+
/**
|
|
65
|
+
* Internal method to resolve the cache path.
|
|
66
|
+
* When path is a string and arguments are provided, auto-generates a unique
|
|
67
|
+
* cache path by hashing the arguments.
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
_getPath(...args: unknown[]): string;
|
|
71
|
+
/**
|
|
72
|
+
* Get a value from the in-memory LRU cache.
|
|
73
|
+
* Returns undefined if LRU is disabled, key not found, or entry is expired.
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
_lruGet(key: string): unknown | typeof LRU_MISS;
|
|
77
|
+
/**
|
|
78
|
+
* Set a value in the in-memory LRU cache.
|
|
79
|
+
* No-op if LRU is disabled.
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
_lruSet(key: string, value: unknown): void;
|
|
83
|
+
call(target: CachableFunction, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor;
|
|
84
|
+
call(target: CachableFunction): CachableFunction;
|
|
85
|
+
call(config: Partial<CacheConfig>): Cachetta;
|
|
86
|
+
}
|
|
87
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class CachettaError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class InvalidPathError extends CachettaError {
|
|
5
|
+
constructor(cachePath: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class UnsupportedFormatError extends CachettaError {
|
|
8
|
+
constructor(extension: string);
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Cachetta } from './Cachetta.js';
|
|
2
|
+
export { writeCache } from './write-cache.js';
|
|
3
|
+
export { readCache } from './read-cache.js';
|
|
4
|
+
export { setLogLevel, setLogger } from './utils/logger.js';
|
|
5
|
+
export { CachettaError, InvalidPathError, UnsupportedFormatError } from './errors.js';
|
|
6
|
+
export type { CacheConfig, CacheInfo, PathFn, CachableFunction, Logger, LogLevel } from './types.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Cachetta } from './Cachetta.js';
|
|
2
|
+
export declare function readCache<T>(cacheBuddy: Cachetta<any>, ...args: unknown[]): Promise<T | null>;
|
|
3
|
+
/**
|
|
4
|
+
* Reads stale cache data: returns data only if the file exists and is within the
|
|
5
|
+
* staleDuration window (expired but not yet past duration + staleDuration).
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare function readStaleCache<T>(cacheBuddy: Cachetta<any>, ...args: unknown[]): Promise<T | null>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Cachetta } from './Cachetta.js';
|
|
2
|
+
import { CacheConfig } from './types.js';
|
|
3
|
+
export declare const isCacheConfig: (value: unknown) => value is CacheConfig;
|
|
4
|
+
export declare const isPartialCacheConfig: (value: unknown) => value is Partial<CacheConfig>;
|
|
5
|
+
export declare const isCachetta: (value: unknown) => value is Cachetta<any>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface CacheConfig<Path extends string | PathFn<any> = string> {
|
|
2
|
+
path: Path;
|
|
3
|
+
write?: boolean;
|
|
4
|
+
read?: boolean;
|
|
5
|
+
duration?: number;
|
|
6
|
+
lruSize?: number;
|
|
7
|
+
/** Function that decides whether to cache a result. Return true to cache, false to skip. */
|
|
8
|
+
condition?: (result: unknown) => boolean;
|
|
9
|
+
/** Duration in ms after `duration` expires during which stale data is returned while a background refresh runs. */
|
|
10
|
+
staleDuration?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface CacheInfo {
|
|
13
|
+
exists: boolean;
|
|
14
|
+
age: number | null;
|
|
15
|
+
expired: boolean;
|
|
16
|
+
stale: boolean;
|
|
17
|
+
path: string;
|
|
18
|
+
}
|
|
19
|
+
export type PathFn<T extends unknown[] = unknown[]> = (...args: T) => string;
|
|
20
|
+
export type CachableFunction = (...args: unknown[]) => unknown;
|
|
21
|
+
export interface Logger {
|
|
22
|
+
debug: (...messages: unknown[]) => void;
|
|
23
|
+
info: (...messages: unknown[]) => void;
|
|
24
|
+
warn: (...messages: unknown[]) => void;
|
|
25
|
+
error: (...messages: unknown[]) => void;
|
|
26
|
+
}
|
|
27
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Cachetta } from '../Cachetta.js';
|
|
2
|
+
import { CachableFunction } from '../types.js';
|
|
3
|
+
export declare const cacheFn: (cache: Cachetta<any>, originalMethod: CachableFunction) => (this: ThisParameterType<typeof originalMethod>, ...args: Parameters<typeof originalMethod>) => Promise<unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isCacheExpired(cacheTime: number, now: number, cacheLength: number): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validateCachePath(cachePath: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cachetta",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"wireit": {
|
|
19
|
+
"test:unit": {
|
|
20
|
+
"command": "vitest run -c vitest.config.unit.ts"
|
|
21
|
+
},
|
|
22
|
+
"test:unit:watch": {
|
|
23
|
+
"command": "vitest watch -c vitest.config.unit.ts"
|
|
24
|
+
},
|
|
25
|
+
"test:integration": {
|
|
26
|
+
"command": "vitest run -c vitest.config.integration.ts",
|
|
27
|
+
"dependencies": [
|
|
28
|
+
"build"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"test:integration:watch": {
|
|
32
|
+
"command": "vitest watch -c vitest.config.integration.ts",
|
|
33
|
+
"dependencies": [
|
|
34
|
+
"build"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"build": {
|
|
38
|
+
"command": "vite build",
|
|
39
|
+
"dependencies": [],
|
|
40
|
+
"files": [
|
|
41
|
+
"./src/**/*.ts",
|
|
42
|
+
"./tsconfig.json",
|
|
43
|
+
"./package.json",
|
|
44
|
+
"./vite.config.ts"
|
|
45
|
+
],
|
|
46
|
+
"output": [
|
|
47
|
+
"dist"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^22.10.0",
|
|
53
|
+
"typescript": "^5.7.2",
|
|
54
|
+
"vite": "^6.0.0",
|
|
55
|
+
"vite-plugin-dts": "^4.5.4",
|
|
56
|
+
"vitest": "^2.1.6",
|
|
57
|
+
"wireit": "^0.14.9",
|
|
58
|
+
"cachetta": "0.1.0"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"test:unit": "wireit",
|
|
63
|
+
"test:unit:watch": "wireit",
|
|
64
|
+
"test:integration": "wireit",
|
|
65
|
+
"test:integration:watch": "wireit",
|
|
66
|
+
"build": "wireit"
|
|
67
|
+
}
|
|
68
|
+
}
|