@ansango/lastfm-api 3.5.0 → 3.6.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/CHANGELOG.md +65 -0
- package/README.md +126 -20
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +7 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/interface.d.ts +57 -0
- package/dist/cache/interface.d.ts.map +1 -0
- package/dist/cache/interface.js +5 -0
- package/dist/cache/interface.js.map +1 -0
- package/dist/cache/manager.d.ts +19 -0
- package/dist/cache/manager.d.ts.map +1 -0
- package/dist/cache/manager.js +63 -0
- package/dist/cache/manager.js.map +1 -0
- package/dist/cache/schemas.d.ts +15 -0
- package/dist/cache/schemas.d.ts.map +1 -0
- package/dist/cache/schemas.js +13 -0
- package/dist/cache/schemas.js.map +1 -0
- package/dist/cache/service.d.ts +13 -0
- package/dist/cache/service.d.ts.map +1 -0
- package/dist/cache/service.js +12 -0
- package/dist/cache/service.js.map +1 -0
- package/dist/cache/stores/memory.d.ts +33 -0
- package/dist/cache/stores/memory.d.ts.map +1 -0
- package/dist/cache/stores/memory.js +85 -0
- package/dist/cache/stores/memory.js.map +1 -0
- package/dist/cache/stores/storage.d.ts +42 -0
- package/dist/cache/stores/storage.d.ts.map +1 -0
- package/dist/cache/stores/storage.js +109 -0
- package/dist/cache/stores/storage.js.map +1 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +10 -0
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -1
- package/dist/core/services/album.d.ts.map +1 -1
- package/dist/core/services/album.js +4 -4
- package/dist/core/services/album.js.map +1 -1
- package/dist/core/services/artist.d.ts.map +1 -1
- package/dist/core/services/artist.js +8 -8
- package/dist/core/services/artist.js.map +1 -1
- package/dist/core/services/chart.d.ts.map +1 -1
- package/dist/core/services/chart.js +3 -3
- package/dist/core/services/chart.js.map +1 -1
- package/dist/core/services/geo.js +2 -2
- package/dist/core/services/geo.js.map +1 -1
- package/dist/core/services/library.js +1 -1
- package/dist/core/services/library.js.map +1 -1
- package/dist/core/services/tag.d.ts.map +1 -1
- package/dist/core/services/tag.js +7 -7
- package/dist/core/services/tag.js.map +1 -1
- package/dist/core/services/track.d.ts.map +1 -1
- package/dist/core/services/track.js +6 -6
- package/dist/core/services/track.js.map +1 -1
- package/dist/core/services/user.d.ts.map +1 -1
- package/dist/core/services/user.js +13 -13
- package/dist/core/services/user.js.map +1 -1
- package/dist/entrypoints/cache.d.ts +2 -0
- package/dist/entrypoints/cache.d.ts.map +1 -0
- package/dist/entrypoints/cache.js +2 -0
- package/dist/entrypoints/cache.js.map +1 -0
- package/dist/entrypoints/cache.schemas.d.ts +2 -0
- package/dist/entrypoints/cache.schemas.d.ts.map +1 -0
- package/dist/entrypoints/cache.schemas.js +2 -0
- package/dist/entrypoints/cache.schemas.js.map +1 -0
- package/dist/entrypoints/watcher.d.ts +2 -0
- package/dist/entrypoints/watcher.d.ts.map +1 -0
- package/dist/entrypoints/watcher.js +2 -0
- package/dist/entrypoints/watcher.js.map +1 -0
- package/dist/entrypoints/watcher.schemas.d.ts +2 -0
- package/dist/entrypoints/watcher.schemas.d.ts.map +1 -0
- package/dist/entrypoints/watcher.schemas.js +2 -0
- package/dist/entrypoints/watcher.schemas.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +3 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +19 -2
- package/dist/utils.js.map +1 -1
- package/dist/watcher/emitter.d.ts +14 -0
- package/dist/watcher/emitter.d.ts.map +1 -0
- package/dist/watcher/emitter.js +72 -0
- package/dist/watcher/emitter.js.map +1 -0
- package/dist/watcher/index.d.ts +5 -0
- package/dist/watcher/index.d.ts.map +1 -0
- package/dist/watcher/index.js +5 -0
- package/dist/watcher/index.js.map +1 -0
- package/dist/watcher/schemas.d.ts +39 -0
- package/dist/watcher/schemas.d.ts.map +1 -0
- package/dist/watcher/schemas.js +33 -0
- package/dist/watcher/schemas.js.map +1 -0
- package/dist/watcher/service.d.ts +15 -0
- package/dist/watcher/service.d.ts.map +1 -0
- package/dist/watcher/service.js +8 -0
- package/dist/watcher/service.js.map +1 -0
- package/dist/watcher/watcher.d.ts +35 -0
- package/dist/watcher/watcher.d.ts.map +1 -0
- package/dist/watcher/watcher.js +196 -0
- package/dist/watcher/watcher.js.map +1 -0
- package/package.json +17 -3
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,71 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
> [ansango/lastfm-api](https://github.com/ansango/lastfm-api). Entries below preserve the
|
|
10
10
|
> historical package and repository names used by their original releases.
|
|
11
11
|
|
|
12
|
+
## [3.6.0] - 2026-08-29
|
|
13
|
+
|
|
14
|
+
### ✨ Features
|
|
15
|
+
|
|
16
|
+
- **watcher**: implement real-time scrobble watcher and event emitter (closes #164, #165) (#167) ([a9545a1](https://github.com/ansango/lastfm-api/commit/a9545a1))
|
|
17
|
+
- **cache**: implement pluggable cache layer (closes #163) (#166) ([4eaa1e0](https://github.com/ansango/lastfm-api/commit/4eaa1e0))
|
|
18
|
+
|
|
19
|
+
### ♻️ Code Refactoring
|
|
20
|
+
|
|
21
|
+
- **index**: remove dotenv loading for environment variables ([57236a6](https://github.com/ansango/lastfm-api/commit/57236a6))
|
|
22
|
+
|
|
23
|
+
### 🔧 Chores
|
|
24
|
+
|
|
25
|
+
- **docs**: update README with complete v3.5.0 features and remove dotenv and test-real ([e2abeb9](https://github.com/ansango/lastfm-api/commit/e2abeb9))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## [3.5.0] - 2026-08-29
|
|
29
|
+
|
|
30
|
+
### ✨ Features
|
|
31
|
+
|
|
32
|
+
- **exporter**: implement exporter namespace (closes #157) (#161) ([d7385ff](https://github.com/ansango/lastfm-api/commit/d7385ff))
|
|
33
|
+
- **playlists**: implement playlists namespace (closes #156) (#160) ([9113b17](https://github.com/ansango/lastfm-api/commit/9113b17))
|
|
34
|
+
- **reports**: implement reports namespace (closes #155) (#159) ([b5524e1](https://github.com/ansango/lastfm-api/commit/b5524e1))
|
|
35
|
+
- **core**: implement automatic async pagination and streaming iterators (closes #154) (#158) ([406caa0](https://github.com/ansango/lastfm-api/commit/406caa0))
|
|
36
|
+
- **insights**: implement compareTasteGroup (closes #143) (#150) ([49b69b4](https://github.com/ansango/lastfm-api/commit/49b69b4))
|
|
37
|
+
- **insights**: implement getSmartRecommendations and getBridgeArtists (closes #142) (#149) ([3b2b2f2](https://github.com/ansango/lastfm-api/commit/3b2b2f2))
|
|
38
|
+
- **insights**: implement getGenreBreakdown and getGenreEvolution (closes #141) (#148) ([4c82e23](https://github.com/ansango/lastfm-api/commit/4c82e23))
|
|
39
|
+
- **insights**: implement getAlbumHabits (closes #140) (#147) ([0b9ceeb](https://github.com/ansango/lastfm-api/commit/0b9ceeb))
|
|
40
|
+
- **insights**: implement getListeningStreaks and getListeningHeatmap (closes #139) (#146) ([756b4de](https://github.com/ansango/lastfm-api/commit/756b4de))
|
|
41
|
+
- **insights**: implement getForgottenFavorites and getObsessions (closes #138) (#145) ([aac4776](https://github.com/ansango/lastfm-api/commit/aac4776))
|
|
42
|
+
- **insights**: implement getObscurityScore (closes #137) (#144) ([83be889](https://github.com/ansango/lastfm-api/commit/83be889))
|
|
43
|
+
|
|
44
|
+
### 🐛 Bug Fixes
|
|
45
|
+
|
|
46
|
+
- **ci**: point test-real to src and smoke-test core entrypoints ([847da33](https://github.com/ansango/lastfm-api/commit/847da33))
|
|
47
|
+
|
|
48
|
+
### ♻️ Code Refactoring
|
|
49
|
+
|
|
50
|
+
- **arch**: decouple core canonical API from insights engine (closes #151) (#152) ([6e3b261](https://github.com/ansango/lastfm-api/commit/6e3b261))
|
|
51
|
+
|
|
52
|
+
### 📚 Documentation
|
|
53
|
+
|
|
54
|
+
- **insights**: document 11 new analytical methods in README and api-coverage (closes #136) ([35f5d60](https://github.com/ansango/lastfm-api/commit/35f5d60))
|
|
55
|
+
|
|
56
|
+
### 🔧 Chores
|
|
57
|
+
|
|
58
|
+
- **release**: bump version to v3.5.0 ([abcc3c7](https://github.com/ansango/lastfm-api/commit/abcc3c7))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## [3.5.0] - 2026-08-29
|
|
62
|
+
|
|
63
|
+
### ✨ Features
|
|
64
|
+
|
|
65
|
+
- **watcher**: implement real-time scrobble watcher and event emitter (closes #164, #165) (#167) ([a9545a1](https://github.com/ansango/lastfm-api/commit/a9545a1))
|
|
66
|
+
- **cache**: implement pluggable cache layer (closes #163) (#166) ([4eaa1e0](https://github.com/ansango/lastfm-api/commit/4eaa1e0))
|
|
67
|
+
|
|
68
|
+
### ♻️ Code Refactoring
|
|
69
|
+
|
|
70
|
+
- **index**: remove dotenv loading for environment variables ([57236a6](https://github.com/ansango/lastfm-api/commit/57236a6))
|
|
71
|
+
|
|
72
|
+
### 🔧 Chores
|
|
73
|
+
|
|
74
|
+
- **docs**: update README with complete v3.5.0 features and remove dotenv and test-real ([e2abeb9](https://github.com/ansango/lastfm-api/commit/e2abeb9))
|
|
75
|
+
|
|
76
|
+
|
|
12
77
|
## [3.4.0] - 2026-08-28
|
|
13
78
|
|
|
14
79
|
### ✨ Features
|
package/README.md
CHANGED
|
@@ -15,7 +15,13 @@ A universal Last.fm API client for Node.js and Browser, written in TypeScript.
|
|
|
15
15
|
|
|
16
16
|
- ✅ **Universal**: Works in Node.js (≥20.0.0) and Browser
|
|
17
17
|
- ✅ **Complete coverage**: All 56 canonical Last.fm API methods across 9 namespaces
|
|
18
|
-
- ✅ **Insights & Analytics Engine**:
|
|
18
|
+
- ✅ **Insights & Analytics Engine**: 20 high-level derived analytical views (Shannon diversity, enriched Now Playing, diurnal histograms, binge runs, ranking diffs, new discoveries, 2D mood classification, personality archetypes, obscurity scores, streaks, heatmaps, album habits, genre breakdown & evolution, smart recommendations, bridge artists, and user/group comparison)
|
|
19
|
+
- ✅ **Pluggable Cache Layer**: Zero-dependency caching for GET/read requests with `MemoryCacheStore` (LRU) and `StorageCacheStore` (localStorage), plus granular TTL by namespace/method
|
|
20
|
+
- ✅ **Real-Time Scrobble Watcher**: Isomorphic event-driven listening monitor emitting `nowPlaying`, `nowPlayingEnd`, `scrobble`, and `idle` events
|
|
21
|
+
- ✅ **Reports & Wrapped Engine**: Custom Year-in-Review, historical milestone projections, and monthly digests
|
|
22
|
+
- ✅ **Smart Playlists Generator**: Algorithmic playlist generation (heavy rotation, time capsule, deep cuts, discovery radar) with M3U and CSV exports
|
|
23
|
+
- ✅ **Bulk Data Exporter & Backup**: Resilient scrobble, loved tracks, and library catalog backup with UTS checkpointing and ListenBrainz/JSONL/CSV formats
|
|
24
|
+
- ✅ **Async Pagination & Streaming**: Native `for await` streaming iterators (`iterateItems`, `collectAll`, `iteratePages`) with automatic rate limiting and boundary controls
|
|
19
25
|
- ✅ **TypeScript**: Full type safety with comprehensive type definitions
|
|
20
26
|
- ✅ **Zod Schemas**: Runtime validation schemas for all types
|
|
21
27
|
- ✅ **ESM**: Modern ES modules with tree-shaking support
|
|
@@ -31,6 +37,14 @@ A universal Last.fm API client for Node.js and Browser, written in TypeScript.
|
|
|
31
37
|
- [Using the Client Class](#using-the-client-class)
|
|
32
38
|
- [Using Global Configuration](#using-global-configuration)
|
|
33
39
|
- [Using Individual Services](#using-individual-services)
|
|
40
|
+
- [Zod Schema Validation](#zod-schema-validation)
|
|
41
|
+
- [Pluggable Cache Layer](#pluggable-cache-layer)
|
|
42
|
+
- [Real-Time Scrobble Watcher](#real-time-scrobble-watcher)
|
|
43
|
+
- [Insights & Analytics Engine](#insights--analytics-engine)
|
|
44
|
+
- [Reports & Wrapped Engine](#reports--wrapped-engine)
|
|
45
|
+
- [Smart Playlists Generator](#smart-playlists-generator)
|
|
46
|
+
- [Bulk Data Exporter & Backup](#bulk-data-exporter--backup)
|
|
47
|
+
- [Async Pagination & Streaming Iterators](#async-pagination--streaming-iterators)
|
|
34
48
|
- [Environment Variables](#environment-variables)
|
|
35
49
|
- [Authentication & Scrobbling](#authentication--scrobbling)
|
|
36
50
|
- [Error Handling](#error-handling)
|
|
@@ -213,6 +227,7 @@ const tracks = await trackService.search({ track: 'Come Together' });
|
|
|
213
227
|
```
|
|
214
228
|
|
|
215
229
|
**Available service imports:**
|
|
230
|
+
- `@ansango/lastfm-api/core` (canonical methods + pagination)
|
|
216
231
|
- `@ansango/lastfm-api/user`
|
|
217
232
|
- `@ansango/lastfm-api/album`
|
|
218
233
|
- `@ansango/lastfm-api/artist`
|
|
@@ -222,7 +237,12 @@ const tracks = await trackService.search({ track: 'Come Together' });
|
|
|
222
237
|
- `@ansango/lastfm-api/geo`
|
|
223
238
|
- `@ansango/lastfm-api/library`
|
|
224
239
|
- `@ansango/lastfm-api/auth`
|
|
240
|
+
- `@ansango/lastfm-api/cache`
|
|
241
|
+
- `@ansango/lastfm-api/watcher`
|
|
225
242
|
- `@ansango/lastfm-api/insights`
|
|
243
|
+
- `@ansango/lastfm-api/reports`
|
|
244
|
+
- `@ansango/lastfm-api/playlists`
|
|
245
|
+
- `@ansango/lastfm-api/exporter`
|
|
226
246
|
|
|
227
247
|
## Zod Schema Validation
|
|
228
248
|
|
|
@@ -236,6 +256,12 @@ Schemas are available through modular imports, following the same pattern as the
|
|
|
236
256
|
import { userGetInfoRequestSchema, userGetInfoResponseSchema } from '@ansango/lastfm-api/user/schemas';
|
|
237
257
|
import { albumSearchRequestSchema } from '@ansango/lastfm-api/album/schemas';
|
|
238
258
|
import { trackGetInfoResponseSchema } from '@ansango/lastfm-api/track/schemas';
|
|
259
|
+
import { cacheOptionsSchema } from '@ansango/lastfm-api/cache/schemas';
|
|
260
|
+
import { watcherOptionsSchema } from '@ansango/lastfm-api/watcher/schemas';
|
|
261
|
+
import { insightsSummaryResponseSchema } from '@ansango/lastfm-api/insights/schemas';
|
|
262
|
+
import { reportsWrappedResponseSchema } from '@ansango/lastfm-api/reports/schemas';
|
|
263
|
+
import { playlistsGenerateResponseSchema } from '@ansango/lastfm-api/playlists/schemas';
|
|
264
|
+
import { exporterScrobblesResponseSchema } from '@ansango/lastfm-api/exporter/schemas';
|
|
239
265
|
```
|
|
240
266
|
|
|
241
267
|
### Usage Example
|
|
@@ -262,6 +288,7 @@ if (result.success) {
|
|
|
262
288
|
```
|
|
263
289
|
|
|
264
290
|
**Available schema imports:**
|
|
291
|
+
- `@ansango/lastfm-api/core/schemas`
|
|
265
292
|
- `@ansango/lastfm-api/user/schemas`
|
|
266
293
|
- `@ansango/lastfm-api/album/schemas`
|
|
267
294
|
- `@ansango/lastfm-api/artist/schemas`
|
|
@@ -271,9 +298,85 @@ if (result.success) {
|
|
|
271
298
|
- `@ansango/lastfm-api/geo/schemas`
|
|
272
299
|
- `@ansango/lastfm-api/library/schemas`
|
|
273
300
|
- `@ansango/lastfm-api/auth/schemas`
|
|
301
|
+
- `@ansango/lastfm-api/cache/schemas`
|
|
302
|
+
- `@ansango/lastfm-api/watcher/schemas`
|
|
274
303
|
- `@ansango/lastfm-api/insights/schemas`
|
|
304
|
+
- `@ansango/lastfm-api/reports/schemas`
|
|
305
|
+
- `@ansango/lastfm-api/playlists/schemas`
|
|
306
|
+
- `@ansango/lastfm-api/exporter/schemas`
|
|
275
307
|
- `@ansango/lastfm-api/schemas` (base types like `imageSchema`, `datePropSchema`, etc.)
|
|
276
308
|
|
|
309
|
+
## Pluggable Cache Layer
|
|
310
|
+
|
|
311
|
+
Transparent caching for read (`GET`) operations with configurable TTLs, LRU eviction, and interchangeable storage backends:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { LastFmClient, MemoryCacheStore, StorageCacheStore } from '@ansango/lastfm-api';
|
|
315
|
+
|
|
316
|
+
// 1. Enable in-memory cache with granular TTL policies
|
|
317
|
+
const client = new LastFmClient({
|
|
318
|
+
apiKey: 'YOUR_API_KEY',
|
|
319
|
+
cache: {
|
|
320
|
+
defaultTtlMs: 300_000, // 5 minutes default
|
|
321
|
+
ttlByNamespace: {
|
|
322
|
+
artist: 86_400_000, // 24 hours for artist metadata
|
|
323
|
+
user: 60_000, // 1 minute for user endpoints
|
|
324
|
+
},
|
|
325
|
+
ttlByMethod: {
|
|
326
|
+
'user.getRecentTracks': 15_000, // 15 seconds for recent tracks
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// 2. First call performs network fetch; second call hits cache instantly
|
|
332
|
+
const artist1 = await client.artist.getInfo({ artist: 'Radiohead' });
|
|
333
|
+
const artist2 = await client.artist.getInfo({ artist: 'Radiohead' }); // Cache hit!
|
|
334
|
+
|
|
335
|
+
// 3. Inspect or manage cache metrics directly
|
|
336
|
+
console.log(client.cache.stats()); // { hits: 1, misses: 1, size: 1 }
|
|
337
|
+
await client.cache.clear();
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Real-Time Scrobble Watcher
|
|
341
|
+
|
|
342
|
+
Isomorphic event-driven listening monitor emitting events as playback updates in real time:
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { LastFmClient } from '@ansango/lastfm-api';
|
|
346
|
+
|
|
347
|
+
const client = new LastFmClient({ apiKey: 'YOUR_API_KEY' });
|
|
348
|
+
|
|
349
|
+
// Create a watcher for a user with 10-second polling
|
|
350
|
+
const watcher = client.watcher.watchUser({
|
|
351
|
+
user: 'ansango',
|
|
352
|
+
intervalMs: 10_000,
|
|
353
|
+
idleThresholdMs: 300_000, // 5 minutes without activity triggers idle
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Event listeners
|
|
357
|
+
watcher.on('nowPlaying', (track) => {
|
|
358
|
+
console.log(`🎵 Now Playing: ${track.name} by ${track.artist}`);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
watcher.on('nowPlayingEnd', (track) => {
|
|
362
|
+
console.log(`⏹️ Finished: ${track.name}`);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
watcher.on('scrobble', (track) => {
|
|
366
|
+
console.log(`✅ Scrobble recorded: ${track.name} (UTS: ${track.uts})`);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
watcher.on('idle', ({ idleMinutes }) => {
|
|
370
|
+
console.log(`💤 User has been idle for ${idleMinutes} minutes`);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Start polling
|
|
374
|
+
watcher.start();
|
|
375
|
+
|
|
376
|
+
// Stop when done
|
|
377
|
+
// watcher.stop();
|
|
378
|
+
```
|
|
379
|
+
|
|
277
380
|
## Insights & Analytics Engine
|
|
278
381
|
|
|
279
382
|
The package includes a comprehensive, built-in analytics engine providing 20 high-level derived views and statistical metrics computed over Last.fm data:
|
|
@@ -536,7 +639,7 @@ Before the browser flow works end-to-end, set a callback URL on your API account
|
|
|
536
639
|
|
|
537
640
|
If you skip this step, the redirect after **Allow access** lands on a Last.fm error page instead of your URL, and the token is lost. You have to call `auth.getToken` again and re-authorize.
|
|
538
641
|
|
|
539
|
-
> **Note:** `auth.getMobileSession` was removed in
|
|
642
|
+
> **Note:** `auth.getMobileSession` was removed in v3.3.0. Last.fm restricts that endpoint to mobile-classified API keys, which are not exposed through the public self-service create form; the browser flow above works for every API key Last.fm issues today.
|
|
540
643
|
|
|
541
644
|
### Passing the session key to write methods
|
|
542
645
|
|
|
@@ -600,6 +703,7 @@ The main client class with all services:
|
|
|
600
703
|
|
|
601
704
|
```typescript
|
|
602
705
|
class LastFmClient {
|
|
706
|
+
core: LastFmCoreClient;
|
|
603
707
|
user: UserService;
|
|
604
708
|
album: AlbumService;
|
|
605
709
|
artist: ArtistService;
|
|
@@ -610,6 +714,11 @@ class LastFmClient {
|
|
|
610
714
|
library: LibraryService;
|
|
611
715
|
auth: AuthService;
|
|
612
716
|
insights: InsightsService;
|
|
717
|
+
reports: ReportsService;
|
|
718
|
+
playlists: PlaylistsService;
|
|
719
|
+
exporter: ExporterService;
|
|
720
|
+
cache: CacheService;
|
|
721
|
+
watcher: WatcherService;
|
|
613
722
|
|
|
614
723
|
constructor(config?: Partial<LastFmConfig>);
|
|
615
724
|
getConfig(): Readonly<LastFmConfig>;
|
|
@@ -635,8 +744,9 @@ function resetGlobalConfig(): void;
|
|
|
635
744
|
|
|
636
745
|
### Services
|
|
637
746
|
|
|
638
|
-
The package covers all 56 canonical Last.fm API methods across 9 namespaces, plus
|
|
747
|
+
The package covers all 56 canonical Last.fm API methods across 9 namespaces, plus 20 derived analytical methods in `InsightsService`, 3 reporting methods in `ReportsService`, 3 smart playlist methods in `PlaylistsService`, and 3 data export engines in `ExporterService` (83 total endpoints). See [docs/api-coverage.md](docs/api-coverage.md) for the full table.
|
|
639
748
|
|
|
749
|
+
- **CoreClient (`core`)**: Bundle of all 56 canonical Last.fm methods with built-in async pagination (`iterateItems`, `collectAll`, `iteratePages`)
|
|
640
750
|
- **UserService**: 13 methods — `getInfo`, `getFriends`, `getLovedTracks`, `getRecentTracks`, `getTopAlbums`, `getTopArtists`, `getTopTags`, `getTopTracks`, `getWeeklyAlbumChart`, `getWeeklyArtistChart`, `getWeeklyChartList`, `getWeeklyTrackChart`, `getPersonalTags`
|
|
641
751
|
- **AlbumService**: 6 methods — `getInfo`, `getTags`, `getTopTags`, `search`, `addTags`¹, `removeTag`¹
|
|
642
752
|
- **ArtistService**: 10 methods — `getInfo`, `getTags`, `getSimilar`, `getTopTags`, `getTopAlbums`, `getTopTracks`, `search`, `getCorrection`, `addTags`¹, `removeTag`¹
|
|
@@ -645,8 +755,11 @@ The package covers all 56 canonical Last.fm API methods across 9 namespaces, plu
|
|
|
645
755
|
- **ChartService**: 3 methods — `getTopArtists`, `getTopTags`, `getTopTracks`
|
|
646
756
|
- **GeoService**: 2 methods — `getTopArtists`, `getTopTracks`
|
|
647
757
|
- **LibraryService**: 1 method — `getArtists`
|
|
648
|
-
- **AuthService**: 2 methods — `getSession`, `getToken` (removed `getMobileSession` in
|
|
649
|
-
- **InsightsService**:
|
|
758
|
+
- **AuthService**: 2 methods — `getSession`, `getToken` (removed `getMobileSession` in v3.3.0)
|
|
759
|
+
- **InsightsService**: 20 methods — `getSummary`, `getNowPlaying`, `getHoursHistogram`, `getBinges`, `getTrends`, `getDiscoveries`, `getMood`, `getPersonality`, `compareUsers`, `getObscurityScore`, `getObsessions`, `getForgottenFavorites`, `getListeningStreaks`, `getListeningHeatmap`, `getAlbumHabits`, `getGenreBreakdown`, `getGenreEvolution`, `getSmartRecommendations`, `getBridgeArtists`, `compareTasteGroup`
|
|
760
|
+
- **ReportsService**: 3 methods — `getWrapped`, `getMilestones`, `getMonthlyDigest`
|
|
761
|
+
- **PlaylistsService**: 3 methods — `generate`, `exportM3U`, `exportCsv`
|
|
762
|
+
- **ExporterService**: 3 methods — `exportScrobbles`, `exportLovedTracks`, `exportLibrary`
|
|
650
763
|
|
|
651
764
|
¹ Requires an authenticated session.
|
|
652
765
|
|
|
@@ -701,10 +814,7 @@ bun run build
|
|
|
701
814
|
bun run typecheck
|
|
702
815
|
|
|
703
816
|
# Run deterministic unit tests (no network, mocked fetch)
|
|
704
|
-
bun run test:unit
|
|
705
|
-
|
|
706
|
-
# Run live integration (requires Last.fm credentials in .env)
|
|
707
|
-
bun run test:integration:live
|
|
817
|
+
bun run test:unit # or simply 'bun test'
|
|
708
818
|
|
|
709
819
|
# Lint (Biome — formatter + linter, single command)
|
|
710
820
|
bun run lint
|
|
@@ -726,10 +836,10 @@ bun run clean
|
|
|
726
836
|
## Interactive API Explorer
|
|
727
837
|
|
|
728
838
|
The repo ships with a local Hono + Scalar server under `tool/api-scalar/`
|
|
729
|
-
that turns the package into a fully interactive OpenAPI explorer. All
|
|
730
|
-
canonical Last.fm methods
|
|
731
|
-
|
|
732
|
-
|
|
839
|
+
that turns the package into a fully interactive OpenAPI explorer. All 56
|
|
840
|
+
canonical Last.fm methods and 27 extension methods (83 total operations)
|
|
841
|
+
are wired declaratively into 5 collapsible sections from the package's
|
|
842
|
+
own Zod schemas and service functions.
|
|
733
843
|
|
|
734
844
|
```sh
|
|
735
845
|
bun install --cwd tool/api-scalar
|
|
@@ -745,14 +855,10 @@ for the design notes.
|
|
|
745
855
|
|
|
746
856
|
### Testing
|
|
747
857
|
|
|
748
|
-
The test suite
|
|
749
|
-
|
|
750
|
-
- **`bun run test:unit`** — deterministic tests with mocked `globalThis.fetch`. They cover every canonical Last.fm method that the package implements, assert the correct `namespace.method` routing, validate that `api_key`/`format=json` are present, and verify that Last.fm error envelopes surface as `LastFmApiError`. They do not require an API key or any network access, and they run as part of CI on every push and pull request.
|
|
751
|
-
- **`bun run test:integration:live`** — runs `test-real.ts` against the real Last.fm API. It is **not** part of CI and must be invoked manually by a developer with valid credentials in `.env`.
|
|
752
|
-
|
|
753
|
-
The `bun test` alias points to the deterministic suite, so a normal `bun test` is safe to run anywhere.
|
|
858
|
+
The test suite consists of deterministic unit tests with mocked `globalThis.fetch`:
|
|
754
859
|
|
|
755
|
-
|
|
860
|
+
- **`bun test`** (or **`bun run test:unit`**) — covers every method that the package implements, asserts the correct `namespace.method` routing, validates that `api_key`/`format=json` are present, and verifies that Last.fm error envelopes surface as `LastFmApiError`. They do not require an API key or any network access, and they run as part of CI on every push and pull request.
|
|
861
|
+
- **`inventory.test.ts`** — asserts the **56/56 canonical-method baseline** ensuring every namespace.method pair listed in the official Last.fm API index is exposed on the `LastFmClient` and callable. The per-namespace breakdown is mirrored in [docs/api-coverage.md](docs/api-coverage.md).
|
|
756
862
|
|
|
757
863
|
### Release Process
|
|
758
864
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface definition for Cache Stores in @ansango/lastfm-api
|
|
3
|
+
*/
|
|
4
|
+
export interface CacheStoreStats {
|
|
5
|
+
hits: number;
|
|
6
|
+
misses: number;
|
|
7
|
+
size: number;
|
|
8
|
+
}
|
|
9
|
+
export interface CacheStore {
|
|
10
|
+
/**
|
|
11
|
+
* Retrieve a cached value by key
|
|
12
|
+
*/
|
|
13
|
+
get<T = unknown>(key: string): Promise<T | undefined> | T | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Store a value in the cache with an optional TTL in milliseconds
|
|
16
|
+
*/
|
|
17
|
+
set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void> | void;
|
|
18
|
+
/**
|
|
19
|
+
* Remove a specific key from the cache
|
|
20
|
+
*/
|
|
21
|
+
delete(key: string): Promise<boolean> | boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Clear all cached values
|
|
24
|
+
*/
|
|
25
|
+
clear(): Promise<void> | void;
|
|
26
|
+
/**
|
|
27
|
+
* Check if a non-expired key exists in the cache
|
|
28
|
+
*/
|
|
29
|
+
has(key: string): Promise<boolean> | boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Get cache metrics if supported
|
|
32
|
+
*/
|
|
33
|
+
stats?(): CacheStoreStats;
|
|
34
|
+
}
|
|
35
|
+
export interface CacheOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Custom cache store implementation (defaults to MemoryCacheStore)
|
|
38
|
+
*/
|
|
39
|
+
store?: CacheStore;
|
|
40
|
+
/**
|
|
41
|
+
* Default TTL in milliseconds for all read requests (default: 300,000 ms / 5 minutes)
|
|
42
|
+
*/
|
|
43
|
+
defaultTtlMs?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Granular TTL overrides by namespace (e.g. { artist: 86_400_000, user: 60_000 })
|
|
46
|
+
*/
|
|
47
|
+
ttlByNamespace?: Record<string, number>;
|
|
48
|
+
/**
|
|
49
|
+
* Granular TTL overrides by method (e.g. { 'user.getRecentTracks': 10_000 })
|
|
50
|
+
*/
|
|
51
|
+
ttlByMethod?: Record<string, number>;
|
|
52
|
+
/**
|
|
53
|
+
* Whether caching is enabled (default: true)
|
|
54
|
+
*/
|
|
55
|
+
enabled?: boolean;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/cache/interface.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,UAAU;IAC1B;;OAEG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IAErE;;OAEG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAE7E;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAE/C;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAE7B;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAE5C;;OAEG;IACH,KAAK,CAAC,IAAI,eAAe,CAAA;CACzB;AAED,MAAM,WAAW,YAAY;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,UAAU,CAAA;IAElB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEvC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEpC;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/cache/interface.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CacheOptions, CacheStoreStats } from './interface.js';
|
|
2
|
+
export declare class CacheManager {
|
|
3
|
+
private readonly store;
|
|
4
|
+
private readonly defaultTtlMs;
|
|
5
|
+
private readonly ttlByNamespace;
|
|
6
|
+
private readonly ttlByMethod;
|
|
7
|
+
private readonly enabled;
|
|
8
|
+
constructor(options?: CacheOptions);
|
|
9
|
+
isEnabled(): boolean;
|
|
10
|
+
resolveTtl(methodId?: string): number;
|
|
11
|
+
wrap<T>(key: string, fetcherFn: () => Promise<T>, ttlMs?: number): Promise<T>;
|
|
12
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
13
|
+
set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void>;
|
|
14
|
+
delete(key: string): Promise<boolean>;
|
|
15
|
+
clear(): Promise<void>;
|
|
16
|
+
has(key: string): Promise<boolean>;
|
|
17
|
+
stats(): CacheStoreStats | undefined;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/cache/manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAG/E,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwB;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,GAAE,YAAiB;IAQ/B,SAAS,IAAI,OAAO;IAIpB,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAiB/B,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAe7E,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAIrD,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxC,KAAK,IAAI,eAAe,GAAG,SAAS;CAG3C"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { MemoryCacheStore } from './stores/memory.js';
|
|
2
|
+
export class CacheManager {
|
|
3
|
+
store;
|
|
4
|
+
defaultTtlMs;
|
|
5
|
+
ttlByNamespace;
|
|
6
|
+
ttlByMethod;
|
|
7
|
+
enabled;
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.store = options.store ?? new MemoryCacheStore({ defaultTtlMs: options.defaultTtlMs });
|
|
10
|
+
this.defaultTtlMs = options.defaultTtlMs ?? 300_000;
|
|
11
|
+
this.ttlByNamespace = options.ttlByNamespace ?? {};
|
|
12
|
+
this.ttlByMethod = options.ttlByMethod ?? {};
|
|
13
|
+
this.enabled = options.enabled ?? true;
|
|
14
|
+
}
|
|
15
|
+
isEnabled() {
|
|
16
|
+
return this.enabled;
|
|
17
|
+
}
|
|
18
|
+
resolveTtl(methodId) {
|
|
19
|
+
if (!methodId)
|
|
20
|
+
return this.defaultTtlMs;
|
|
21
|
+
// 1. Direct method match (e.g. 'user.getRecentTracks' or 'user.getInfo')
|
|
22
|
+
if (this.ttlByMethod[methodId] !== undefined) {
|
|
23
|
+
return this.ttlByMethod[methodId];
|
|
24
|
+
}
|
|
25
|
+
// 2. Namespace match (e.g. 'user', 'artist', 'album')
|
|
26
|
+
const [ns] = methodId.split('.');
|
|
27
|
+
if (ns && this.ttlByNamespace[ns] !== undefined) {
|
|
28
|
+
return this.ttlByNamespace[ns];
|
|
29
|
+
}
|
|
30
|
+
return this.defaultTtlMs;
|
|
31
|
+
}
|
|
32
|
+
async wrap(key, fetcherFn, ttlMs) {
|
|
33
|
+
if (!this.enabled) {
|
|
34
|
+
return fetcherFn();
|
|
35
|
+
}
|
|
36
|
+
const cached = await this.store.get(key);
|
|
37
|
+
if (cached !== undefined) {
|
|
38
|
+
return cached;
|
|
39
|
+
}
|
|
40
|
+
const fresh = await fetcherFn();
|
|
41
|
+
await this.store.set(key, fresh, ttlMs ?? this.defaultTtlMs);
|
|
42
|
+
return fresh;
|
|
43
|
+
}
|
|
44
|
+
async get(key) {
|
|
45
|
+
return this.store.get(key);
|
|
46
|
+
}
|
|
47
|
+
async set(key, value, ttlMs) {
|
|
48
|
+
return this.store.set(key, value, ttlMs ?? this.defaultTtlMs);
|
|
49
|
+
}
|
|
50
|
+
async delete(key) {
|
|
51
|
+
return this.store.delete(key);
|
|
52
|
+
}
|
|
53
|
+
async clear() {
|
|
54
|
+
return this.store.clear();
|
|
55
|
+
}
|
|
56
|
+
async has(key) {
|
|
57
|
+
return this.store.has(key);
|
|
58
|
+
}
|
|
59
|
+
stats() {
|
|
60
|
+
return this.store.stats?.();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/cache/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const cacheStoreStatsSchema: z.ZodObject<{
|
|
3
|
+
hits: z.ZodNumber;
|
|
4
|
+
misses: z.ZodNumber;
|
|
5
|
+
size: z.ZodNumber;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export declare const cacheOptionsSchema: z.ZodObject<{
|
|
8
|
+
defaultTtlMs: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
ttlByNamespace: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
10
|
+
ttlByMethod: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
11
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export type CacheStoreStatsPayload = z.infer<typeof cacheStoreStatsSchema>;
|
|
14
|
+
export type CacheOptionsSchema = z.infer<typeof cacheOptionsSchema>;
|
|
15
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/cache/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,qBAAqB;;;;iBAIhC,CAAA;AAEF,eAAO,MAAM,kBAAkB;;;;;iBAK7B,CAAA;AAEF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AAC1E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const cacheStoreStatsSchema = z.object({
|
|
3
|
+
hits: z.number().int().nonnegative(),
|
|
4
|
+
misses: z.number().int().nonnegative(),
|
|
5
|
+
size: z.number().int().nonnegative(),
|
|
6
|
+
});
|
|
7
|
+
export const cacheOptionsSchema = z.object({
|
|
8
|
+
defaultTtlMs: z.number().positive().optional(),
|
|
9
|
+
ttlByNamespace: z.record(z.string(), z.number().positive()).optional(),
|
|
10
|
+
ttlByMethod: z.record(z.string(), z.number().positive()).optional(),
|
|
11
|
+
enabled: z.boolean().optional(),
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/cache/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACpC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACtC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;CACpC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC9C,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE;IACtE,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE;IACnE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CacheStoreStats } from './interface.js';
|
|
2
|
+
import type { CacheManager } from './manager.js';
|
|
3
|
+
export interface CacheService {
|
|
4
|
+
get: <T = unknown>(key: string) => Promise<T | undefined>;
|
|
5
|
+
set: <T = unknown>(key: string, value: T, ttlMs?: number) => Promise<void>;
|
|
6
|
+
delete: (key: string) => Promise<boolean>;
|
|
7
|
+
clear: () => Promise<void>;
|
|
8
|
+
has: (key: string) => Promise<boolean>;
|
|
9
|
+
stats: () => CacheStoreStats | undefined;
|
|
10
|
+
manager: CacheManager;
|
|
11
|
+
}
|
|
12
|
+
export declare function createCacheService(manager: CacheManager): CacheService;
|
|
13
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/cache/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAA;IACzD,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1E,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACzC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,KAAK,EAAE,MAAM,eAAe,GAAG,SAAS,CAAA;IACxC,OAAO,EAAE,YAAY,CAAA;CACrB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAUtE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function createCacheService(manager) {
|
|
2
|
+
return {
|
|
3
|
+
get: (key) => manager.get(key),
|
|
4
|
+
set: (key, value, ttlMs) => manager.set(key, value, ttlMs),
|
|
5
|
+
delete: (key) => manager.delete(key),
|
|
6
|
+
clear: () => manager.clear(),
|
|
7
|
+
has: (key) => manager.has(key),
|
|
8
|
+
stats: () => manager.stats(),
|
|
9
|
+
manager,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/cache/service.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,kBAAkB,CAAC,OAAqB;IACvD,OAAO;QACN,GAAG,EAAE,CAAc,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAI,GAAG,CAAC;QACtD,GAAG,EAAE,CAAc,GAAW,EAAE,KAAQ,EAAE,KAAc,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC;QAC3F,MAAM,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC;QAC5C,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE;QAC5B,GAAG,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;QACtC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE;QAC5B,OAAO;KACP,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CacheStore, CacheStoreStats } from '../interface.js';
|
|
2
|
+
export interface MemoryCacheStoreOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Maximum number of entries before oldest entries are evicted (default: 500)
|
|
5
|
+
*/
|
|
6
|
+
maxEntries?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Default TTL in milliseconds (default: 300,000 / 5 minutes)
|
|
9
|
+
*/
|
|
10
|
+
defaultTtlMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* High-performance in-memory cache store with TTL expiration and LRU eviction.
|
|
14
|
+
*/
|
|
15
|
+
export declare class MemoryCacheStore implements CacheStore {
|
|
16
|
+
private readonly entries;
|
|
17
|
+
private readonly maxEntries;
|
|
18
|
+
private readonly defaultTtlMs;
|
|
19
|
+
private hitCount;
|
|
20
|
+
private missCount;
|
|
21
|
+
constructor(options?: MemoryCacheStoreOptions);
|
|
22
|
+
get<T = unknown>(key: string): T | undefined;
|
|
23
|
+
set<T = unknown>(key: string, value: T, ttlMs?: number): void;
|
|
24
|
+
delete(key: string): boolean;
|
|
25
|
+
clear(): void;
|
|
26
|
+
has(key: string): boolean;
|
|
27
|
+
stats(): CacheStoreStats;
|
|
28
|
+
/**
|
|
29
|
+
* Removes all expired entries from memory
|
|
30
|
+
*/
|
|
31
|
+
pruneExpired(): number;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/cache/stores/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAElE,MAAM,WAAW,uBAAuB;IACvC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACrB;AAOD;;GAEG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0C;IAClE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,SAAS,CAAI;gBAET,OAAO,GAAE,uBAA4B;IAK1C,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAoB5C,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAgB7D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI5B,KAAK,IAAI,IAAI;IAMb,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAUzB,KAAK,IAAI,eAAe;IAS/B;;OAEG;IACI,YAAY,IAAI,MAAM;CAW7B"}
|