@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.
Files changed (105) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +126 -20
  3. package/dist/cache/index.d.ts +7 -0
  4. package/dist/cache/index.d.ts.map +1 -0
  5. package/dist/cache/index.js +7 -0
  6. package/dist/cache/index.js.map +1 -0
  7. package/dist/cache/interface.d.ts +57 -0
  8. package/dist/cache/interface.d.ts.map +1 -0
  9. package/dist/cache/interface.js +5 -0
  10. package/dist/cache/interface.js.map +1 -0
  11. package/dist/cache/manager.d.ts +19 -0
  12. package/dist/cache/manager.d.ts.map +1 -0
  13. package/dist/cache/manager.js +63 -0
  14. package/dist/cache/manager.js.map +1 -0
  15. package/dist/cache/schemas.d.ts +15 -0
  16. package/dist/cache/schemas.d.ts.map +1 -0
  17. package/dist/cache/schemas.js +13 -0
  18. package/dist/cache/schemas.js.map +1 -0
  19. package/dist/cache/service.d.ts +13 -0
  20. package/dist/cache/service.d.ts.map +1 -0
  21. package/dist/cache/service.js +12 -0
  22. package/dist/cache/service.js.map +1 -0
  23. package/dist/cache/stores/memory.d.ts +33 -0
  24. package/dist/cache/stores/memory.d.ts.map +1 -0
  25. package/dist/cache/stores/memory.js +85 -0
  26. package/dist/cache/stores/memory.js.map +1 -0
  27. package/dist/cache/stores/storage.d.ts +42 -0
  28. package/dist/cache/stores/storage.d.ts.map +1 -0
  29. package/dist/cache/stores/storage.js +109 -0
  30. package/dist/cache/stores/storage.js.map +1 -0
  31. package/dist/client.d.ts +4 -0
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +10 -0
  34. package/dist/client.js.map +1 -1
  35. package/dist/config.d.ts +4 -0
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +13 -0
  38. package/dist/config.js.map +1 -1
  39. package/dist/core/services/album.d.ts.map +1 -1
  40. package/dist/core/services/album.js +4 -4
  41. package/dist/core/services/album.js.map +1 -1
  42. package/dist/core/services/artist.d.ts.map +1 -1
  43. package/dist/core/services/artist.js +8 -8
  44. package/dist/core/services/artist.js.map +1 -1
  45. package/dist/core/services/chart.d.ts.map +1 -1
  46. package/dist/core/services/chart.js +3 -3
  47. package/dist/core/services/chart.js.map +1 -1
  48. package/dist/core/services/geo.js +2 -2
  49. package/dist/core/services/geo.js.map +1 -1
  50. package/dist/core/services/library.js +1 -1
  51. package/dist/core/services/library.js.map +1 -1
  52. package/dist/core/services/tag.d.ts.map +1 -1
  53. package/dist/core/services/tag.js +7 -7
  54. package/dist/core/services/tag.js.map +1 -1
  55. package/dist/core/services/track.d.ts.map +1 -1
  56. package/dist/core/services/track.js +6 -6
  57. package/dist/core/services/track.js.map +1 -1
  58. package/dist/core/services/user.d.ts.map +1 -1
  59. package/dist/core/services/user.js +13 -13
  60. package/dist/core/services/user.js.map +1 -1
  61. package/dist/entrypoints/cache.d.ts +2 -0
  62. package/dist/entrypoints/cache.d.ts.map +1 -0
  63. package/dist/entrypoints/cache.js +2 -0
  64. package/dist/entrypoints/cache.js.map +1 -0
  65. package/dist/entrypoints/cache.schemas.d.ts +2 -0
  66. package/dist/entrypoints/cache.schemas.d.ts.map +1 -0
  67. package/dist/entrypoints/cache.schemas.js +2 -0
  68. package/dist/entrypoints/cache.schemas.js.map +1 -0
  69. package/dist/entrypoints/watcher.d.ts +2 -0
  70. package/dist/entrypoints/watcher.d.ts.map +1 -0
  71. package/dist/entrypoints/watcher.js +2 -0
  72. package/dist/entrypoints/watcher.js.map +1 -0
  73. package/dist/entrypoints/watcher.schemas.d.ts +2 -0
  74. package/dist/entrypoints/watcher.schemas.d.ts.map +1 -0
  75. package/dist/entrypoints/watcher.schemas.js +2 -0
  76. package/dist/entrypoints/watcher.schemas.js.map +1 -0
  77. package/dist/index.d.ts +4 -0
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +4 -0
  80. package/dist/index.js.map +1 -1
  81. package/dist/utils.d.ts +3 -2
  82. package/dist/utils.d.ts.map +1 -1
  83. package/dist/utils.js +19 -2
  84. package/dist/utils.js.map +1 -1
  85. package/dist/watcher/emitter.d.ts +14 -0
  86. package/dist/watcher/emitter.d.ts.map +1 -0
  87. package/dist/watcher/emitter.js +72 -0
  88. package/dist/watcher/emitter.js.map +1 -0
  89. package/dist/watcher/index.d.ts +5 -0
  90. package/dist/watcher/index.d.ts.map +1 -0
  91. package/dist/watcher/index.js +5 -0
  92. package/dist/watcher/index.js.map +1 -0
  93. package/dist/watcher/schemas.d.ts +39 -0
  94. package/dist/watcher/schemas.d.ts.map +1 -0
  95. package/dist/watcher/schemas.js +33 -0
  96. package/dist/watcher/schemas.js.map +1 -0
  97. package/dist/watcher/service.d.ts +15 -0
  98. package/dist/watcher/service.d.ts.map +1 -0
  99. package/dist/watcher/service.js +8 -0
  100. package/dist/watcher/service.js.map +1 -0
  101. package/dist/watcher/watcher.d.ts +35 -0
  102. package/dist/watcher/watcher.d.ts.map +1 -0
  103. package/dist/watcher/watcher.js +196 -0
  104. package/dist/watcher/watcher.js.map +1 -0
  105. 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**: 9 high-level derived analytical views (Shannon diversity, enriched Now Playing, diurnal histograms, binge runs, ranking diffs, new discoveries, 2D mood classification, personality archetypes, and Jaccard user comparison)
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 v4.0.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.
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 9 derived analytical methods in the `InsightsService`. See [docs/api-coverage.md](docs/api-coverage.md) for the full table.
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 v4.0.0)
649
- - **InsightsService**: 9 methods — `getSummary`, `getNowPlaying`, `getHoursHistogram`, `getBinges`, `getTrends`, `getDiscoveries`, `getMood`, `getPersonality`, `compareUsers`
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 57
730
- canonical Last.fm methods are wired declaratively from the package's own
731
- Zod schemas and service functions no method is hand-written, no schema
732
- is duplicated.
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 is split into two layers:
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
- An `inventory.test.ts` file asserts the **57/57 canonical-method baseline** 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) and stays in sync with the inventory test.
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,7 @@
1
+ export * from './interface.js';
2
+ export * from './manager.js';
3
+ export * from './schemas.js';
4
+ export * from './service.js';
5
+ export * from './stores/memory.js';
6
+ export * from './stores/storage.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -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,7 @@
1
+ export * from './interface.js';
2
+ export * from './manager.js';
3
+ export * from './schemas.js';
4
+ export * from './service.js';
5
+ export * from './stores/memory.js';
6
+ export * from './stores/storage.js';
7
+ //# sourceMappingURL=index.js.map
@@ -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,5 @@
1
+ /**
2
+ * Interface definition for Cache Stores in @ansango/lastfm-api
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=interface.js.map
@@ -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"}