@ansango/lastfm-api 3.1.3

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 (204) hide show
  1. package/CHANGELOG.md +323 -0
  2. package/LICENSE +21 -0
  3. package/README.md +470 -0
  4. package/dist/__tests__/utils.test.d.ts +2 -0
  5. package/dist/__tests__/utils.test.d.ts.map +1 -0
  6. package/dist/__tests__/utils.test.js +86 -0
  7. package/dist/__tests__/utils.test.js.map +1 -0
  8. package/dist/client.d.ts +52 -0
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +68 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/config.d.ts +23 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +59 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/entrypoints/album.d.ts +4 -0
  17. package/dist/entrypoints/album.d.ts.map +1 -0
  18. package/dist/entrypoints/album.js +3 -0
  19. package/dist/entrypoints/album.js.map +1 -0
  20. package/dist/entrypoints/album.schemas.d.ts +2 -0
  21. package/dist/entrypoints/album.schemas.d.ts.map +1 -0
  22. package/dist/entrypoints/album.schemas.js +3 -0
  23. package/dist/entrypoints/album.schemas.js.map +1 -0
  24. package/dist/entrypoints/artist.d.ts +4 -0
  25. package/dist/entrypoints/artist.d.ts.map +1 -0
  26. package/dist/entrypoints/artist.js +3 -0
  27. package/dist/entrypoints/artist.js.map +1 -0
  28. package/dist/entrypoints/artist.schemas.d.ts +2 -0
  29. package/dist/entrypoints/artist.schemas.d.ts.map +1 -0
  30. package/dist/entrypoints/artist.schemas.js +3 -0
  31. package/dist/entrypoints/artist.schemas.js.map +1 -0
  32. package/dist/entrypoints/auth.d.ts +4 -0
  33. package/dist/entrypoints/auth.d.ts.map +1 -0
  34. package/dist/entrypoints/auth.js +3 -0
  35. package/dist/entrypoints/auth.js.map +1 -0
  36. package/dist/entrypoints/auth.schemas.d.ts +2 -0
  37. package/dist/entrypoints/auth.schemas.d.ts.map +1 -0
  38. package/dist/entrypoints/auth.schemas.js +3 -0
  39. package/dist/entrypoints/auth.schemas.js.map +1 -0
  40. package/dist/entrypoints/base.schemas.d.ts +2 -0
  41. package/dist/entrypoints/base.schemas.d.ts.map +1 -0
  42. package/dist/entrypoints/base.schemas.js +3 -0
  43. package/dist/entrypoints/base.schemas.js.map +1 -0
  44. package/dist/entrypoints/chart.d.ts +4 -0
  45. package/dist/entrypoints/chart.d.ts.map +1 -0
  46. package/dist/entrypoints/chart.js +3 -0
  47. package/dist/entrypoints/chart.js.map +1 -0
  48. package/dist/entrypoints/chart.schemas.d.ts +2 -0
  49. package/dist/entrypoints/chart.schemas.d.ts.map +1 -0
  50. package/dist/entrypoints/chart.schemas.js +3 -0
  51. package/dist/entrypoints/chart.schemas.js.map +1 -0
  52. package/dist/entrypoints/geo.d.ts +4 -0
  53. package/dist/entrypoints/geo.d.ts.map +1 -0
  54. package/dist/entrypoints/geo.js +3 -0
  55. package/dist/entrypoints/geo.js.map +1 -0
  56. package/dist/entrypoints/geo.schemas.d.ts +2 -0
  57. package/dist/entrypoints/geo.schemas.d.ts.map +1 -0
  58. package/dist/entrypoints/geo.schemas.js +3 -0
  59. package/dist/entrypoints/geo.schemas.js.map +1 -0
  60. package/dist/entrypoints/library.d.ts +4 -0
  61. package/dist/entrypoints/library.d.ts.map +1 -0
  62. package/dist/entrypoints/library.js +3 -0
  63. package/dist/entrypoints/library.js.map +1 -0
  64. package/dist/entrypoints/library.schemas.d.ts +2 -0
  65. package/dist/entrypoints/library.schemas.d.ts.map +1 -0
  66. package/dist/entrypoints/library.schemas.js +3 -0
  67. package/dist/entrypoints/library.schemas.js.map +1 -0
  68. package/dist/entrypoints/tag.d.ts +4 -0
  69. package/dist/entrypoints/tag.d.ts.map +1 -0
  70. package/dist/entrypoints/tag.js +3 -0
  71. package/dist/entrypoints/tag.js.map +1 -0
  72. package/dist/entrypoints/tag.schemas.d.ts +2 -0
  73. package/dist/entrypoints/tag.schemas.d.ts.map +1 -0
  74. package/dist/entrypoints/tag.schemas.js +3 -0
  75. package/dist/entrypoints/tag.schemas.js.map +1 -0
  76. package/dist/entrypoints/track.d.ts +4 -0
  77. package/dist/entrypoints/track.d.ts.map +1 -0
  78. package/dist/entrypoints/track.js +3 -0
  79. package/dist/entrypoints/track.js.map +1 -0
  80. package/dist/entrypoints/track.schemas.d.ts +2 -0
  81. package/dist/entrypoints/track.schemas.d.ts.map +1 -0
  82. package/dist/entrypoints/track.schemas.js +3 -0
  83. package/dist/entrypoints/track.schemas.js.map +1 -0
  84. package/dist/entrypoints/user.d.ts +4 -0
  85. package/dist/entrypoints/user.d.ts.map +1 -0
  86. package/dist/entrypoints/user.js +3 -0
  87. package/dist/entrypoints/user.js.map +1 -0
  88. package/dist/entrypoints/user.schemas.d.ts +2 -0
  89. package/dist/entrypoints/user.schemas.d.ts.map +1 -0
  90. package/dist/entrypoints/user.schemas.js +3 -0
  91. package/dist/entrypoints/user.schemas.js.map +1 -0
  92. package/dist/index.d.ts +26 -0
  93. package/dist/index.d.ts.map +1 -0
  94. package/dist/index.js +20 -0
  95. package/dist/index.js.map +1 -0
  96. package/dist/services/album.d.ts +38 -0
  97. package/dist/services/album.d.ts.map +1 -0
  98. package/dist/services/album.js +10 -0
  99. package/dist/services/album.js.map +1 -0
  100. package/dist/services/album.schemas.d.ts +171 -0
  101. package/dist/services/album.schemas.d.ts.map +1 -0
  102. package/dist/services/album.schemas.js +128 -0
  103. package/dist/services/album.schemas.js.map +1 -0
  104. package/dist/services/artist.d.ts +62 -0
  105. package/dist/services/artist.d.ts.map +1 -0
  106. package/dist/services/artist.js +13 -0
  107. package/dist/services/artist.js.map +1 -0
  108. package/dist/services/artist.schemas.d.ts +306 -0
  109. package/dist/services/artist.schemas.d.ts.map +1 -0
  110. package/dist/services/artist.schemas.js +199 -0
  111. package/dist/services/artist.schemas.js.map +1 -0
  112. package/dist/services/auth.d.ts +14 -0
  113. package/dist/services/auth.d.ts.map +1 -0
  114. package/dist/services/auth.js +7 -0
  115. package/dist/services/auth.js.map +1 -0
  116. package/dist/services/auth.schemas.d.ts +20 -0
  117. package/dist/services/auth.schemas.d.ts.map +1 -0
  118. package/dist/services/auth.schemas.js +16 -0
  119. package/dist/services/auth.schemas.js.map +1 -0
  120. package/dist/services/chart.d.ts +30 -0
  121. package/dist/services/chart.d.ts.map +1 -0
  122. package/dist/services/chart.js +9 -0
  123. package/dist/services/chart.js.map +1 -0
  124. package/dist/services/chart.schemas.d.ts +84 -0
  125. package/dist/services/chart.schemas.d.ts.map +1 -0
  126. package/dist/services/chart.schemas.js +73 -0
  127. package/dist/services/chart.schemas.js.map +1 -0
  128. package/dist/services/geo.d.ts +22 -0
  129. package/dist/services/geo.d.ts.map +1 -0
  130. package/dist/services/geo.js +8 -0
  131. package/dist/services/geo.js.map +1 -0
  132. package/dist/services/geo.schemas.d.ts +68 -0
  133. package/dist/services/geo.schemas.d.ts.map +1 -0
  134. package/dist/services/geo.schemas.js +59 -0
  135. package/dist/services/geo.schemas.js.map +1 -0
  136. package/dist/services/index.d.ts +20 -0
  137. package/dist/services/index.d.ts.map +1 -0
  138. package/dist/services/index.js +21 -0
  139. package/dist/services/index.js.map +1 -0
  140. package/dist/services/library.d.ts +14 -0
  141. package/dist/services/library.d.ts.map +1 -0
  142. package/dist/services/library.js +7 -0
  143. package/dist/services/library.js.map +1 -0
  144. package/dist/services/library.schemas.d.ts +31 -0
  145. package/dist/services/library.schemas.d.ts.map +1 -0
  146. package/dist/services/library.schemas.js +27 -0
  147. package/dist/services/library.schemas.js.map +1 -0
  148. package/dist/services/schemas/base.entities.schemas.d.ts +12 -0
  149. package/dist/services/schemas/base.entities.schemas.d.ts.map +1 -0
  150. package/dist/services/schemas/base.entities.schemas.js +18 -0
  151. package/dist/services/schemas/base.entities.schemas.js.map +1 -0
  152. package/dist/services/schemas/base.entity-names.schemas.d.ts +12 -0
  153. package/dist/services/schemas/base.entity-names.schemas.d.ts.map +1 -0
  154. package/dist/services/schemas/base.entity-names.schemas.js +8 -0
  155. package/dist/services/schemas/base.entity-names.schemas.js.map +1 -0
  156. package/dist/services/schemas/base.metadata.schemas.d.ts +26 -0
  157. package/dist/services/schemas/base.metadata.schemas.d.ts.map +1 -0
  158. package/dist/services/schemas/base.metadata.schemas.js +17 -0
  159. package/dist/services/schemas/base.metadata.schemas.js.map +1 -0
  160. package/dist/services/schemas/base.pagination.schemas.d.ts +18 -0
  161. package/dist/services/schemas/base.pagination.schemas.d.ts.map +1 -0
  162. package/dist/services/schemas/base.pagination.schemas.js +12 -0
  163. package/dist/services/schemas/base.pagination.schemas.js.map +1 -0
  164. package/dist/services/schemas/base.primitives.schemas.d.ts +50 -0
  165. package/dist/services/schemas/base.primitives.schemas.d.ts.map +1 -0
  166. package/dist/services/schemas/base.primitives.schemas.js +36 -0
  167. package/dist/services/schemas/base.primitives.schemas.js.map +1 -0
  168. package/dist/services/schemas/index.d.ts +6 -0
  169. package/dist/services/schemas/index.d.ts.map +1 -0
  170. package/dist/services/schemas/index.js +7 -0
  171. package/dist/services/schemas/index.js.map +1 -0
  172. package/dist/services/tag.d.ts +62 -0
  173. package/dist/services/tag.d.ts.map +1 -0
  174. package/dist/services/tag.js +13 -0
  175. package/dist/services/tag.js.map +1 -0
  176. package/dist/services/tag.schemas.d.ts +192 -0
  177. package/dist/services/tag.schemas.d.ts.map +1 -0
  178. package/dist/services/tag.schemas.js +156 -0
  179. package/dist/services/tag.schemas.js.map +1 -0
  180. package/dist/services/track.d.ts +74 -0
  181. package/dist/services/track.d.ts.map +1 -0
  182. package/dist/services/track.js +25 -0
  183. package/dist/services/track.js.map +1 -0
  184. package/dist/services/track.schemas.d.ts +284 -0
  185. package/dist/services/track.schemas.d.ts.map +1 -0
  186. package/dist/services/track.schemas.js +191 -0
  187. package/dist/services/track.schemas.js.map +1 -0
  188. package/dist/services/track.utils.d.ts +10 -0
  189. package/dist/services/track.utils.d.ts.map +1 -0
  190. package/dist/services/track.utils.js +56 -0
  191. package/dist/services/track.utils.js.map +1 -0
  192. package/dist/services/user.d.ts +102 -0
  193. package/dist/services/user.d.ts.map +1 -0
  194. package/dist/services/user.js +18 -0
  195. package/dist/services/user.js.map +1 -0
  196. package/dist/services/user.schemas.d.ts +609 -0
  197. package/dist/services/user.schemas.d.ts.map +1 -0
  198. package/dist/services/user.schemas.js +321 -0
  199. package/dist/services/user.schemas.js.map +1 -0
  200. package/dist/utils.d.ts +39 -0
  201. package/dist/utils.d.ts.map +1 -0
  202. package/dist/utils.js +125 -0
  203. package/dist/utils.js.map +1 -0
  204. package/package.json +156 -0
package/README.md ADDED
@@ -0,0 +1,470 @@
1
+ # @ansango/lastfm-api
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/%40ansango%2Flastfm-api.svg?branch=main)](https://www.npmjs.com/package/@ansango/lastfm-api)
4
+ [![Downloads Per Week](https://img.shields.io/npm/dw/%40ansango%2Flastfm-api.svg?color=blue)](https://www.npmjs.com/package/@ansango/lastfm-api)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A universal Last.fm API client for Node.js and Browser, written in TypeScript.
8
+
9
+ > [!NOTE]
10
+ > This package was previously published as `lastfm-client-ts`. The legacy package remains
11
+ > installable but is deprecated. Migrate by installing `@ansango/lastfm-api` and updating the
12
+ > package specifier in your imports; the exported API and subpaths are unchanged.
13
+
14
+ ## Features
15
+
16
+ - ✅ **Universal**: Works in Node.js (≥20.0.0) and Browser
17
+ - ✅ **TypeScript**: Full type safety with comprehensive type definitions
18
+ - ✅ **Zod Schemas**: Runtime validation schemas for all types
19
+ - ✅ **ESM**: Modern ES modules with tree-shaking support
20
+ - ✅ **Flexible**: Global configuration or per-instance configuration
21
+ - ✅ **Modular**: Import only what you need
22
+ - ✅ **Minimal Dependencies**: Only `js-md5` for API signatures and `zod` for runtime validation
23
+
24
+ ## Table of Contents
25
+
26
+ - [Installation](#installation)
27
+ - [Quick Start](#quick-start)
28
+ - [Usage](#usage)
29
+ - [Using the Client Class](#using-the-client-class)
30
+ - [Using Global Configuration](#using-global-configuration)
31
+ - [Using Individual Services](#using-individual-services)
32
+ - [Environment Variables](#environment-variables)
33
+ - [Authentication & Scrobbling](#authentication--scrobbling)
34
+ - [Error Handling](#error-handling)
35
+ - [API Reference](#api-reference)
36
+ - [TypeScript Support](#typescript-support)
37
+ - [Contributing](#contributing)
38
+ - [License](#license)
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ npm install @ansango/lastfm-api
44
+ ```
45
+
46
+ **Requirements:**
47
+ - Node.js ≥ 20.0.0 (for native fetch support)
48
+ - Modern browsers with fetch API support
49
+
50
+ ## Quick Start
51
+
52
+ ```typescript
53
+ import { LastFmClient } from '@ansango/lastfm-api';
54
+
55
+ // Create a client instance
56
+ const client = new LastFmClient({
57
+ apiKey: 'YOUR_API_KEY'
58
+ });
59
+
60
+ // Fetch user information
61
+ const userInfo = await client.user.getInfo({ user: 'ansango' });
62
+ console.log(userInfo);
63
+
64
+ // Search for albums
65
+ const albums = await client.album.search({ album: 'Believe' });
66
+ console.log(albums);
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ### Using the Client Class (Recommended)
72
+
73
+ The client class provides access to all API services in one place:
74
+
75
+ ```typescript
76
+ import { LastFmClient } from '@ansango/lastfm-api';
77
+
78
+ const client = new LastFmClient({
79
+ apiKey: 'YOUR_API_KEY',
80
+ sharedSecret: 'YOUR_SHARED_SECRET', // Optional, required for authenticated methods
81
+ sessionKey: 'USER_SESSION_KEY' // Optional, required for user-specific methods
82
+ });
83
+
84
+ // User service
85
+ const userInfo = await client.user.getInfo({ user: 'ansango' });
86
+ const topArtists = await client.user.getTopArtists({ user: 'ansango', period: '7day' });
87
+
88
+ // Album service
89
+ const albumInfo = await client.album.getInfo({ artist: 'Cher', album: 'Believe' });
90
+ const albumSearch = await client.album.search({ album: 'Believe', limit: 10 });
91
+
92
+ // Artist service
93
+ const artistInfo = await client.artist.getInfo({ artist: 'Radiohead' });
94
+ const similarArtists = await client.artist.getSimilar({ artist: 'Radiohead' });
95
+
96
+ // Track service
97
+ const trackInfo = await client.track.getInfo({ artist: 'The Beatles', track: 'Yesterday' });
98
+ const trackSearch = await client.track.search({ track: 'Yesterday', limit: 10 });
99
+
100
+ // Scrobble (auth required — `sk` is auto-injected from `config.sessionKey`)
101
+ const scrobbleResult = await client.track.scrobble({
102
+ artist: 'Cher',
103
+ track: 'Believe',
104
+ timestamp: Math.floor(Date.now() / 1000),
105
+ });
106
+
107
+ // Batch scrobble (max 50 tracks per call)
108
+ const batchResult = await client.track.scrobbleMany({
109
+ tracks: [
110
+ { artist: 'Cher', track: 'Believe', timestamp: 1700000000 },
111
+ { artist: 'Cher', track: 'If You Believe', timestamp: 1700000600 },
112
+ ],
113
+ });
114
+
115
+ // Chart service
116
+ const topChartArtists = await client.chart.getTopArtists();
117
+ const topChartTracks = await client.chart.getTopTracks();
118
+
119
+ // Tag service
120
+ const tagInfo = await client.tag.getInfo({ tag: 'rock' });
121
+ const topTagArtists = await client.tag.getTopArtists({ tag: 'rock' });
122
+
123
+ // Geo service
124
+ const topArtistsByCountry = await client.geo.getTopArtists({ country: 'spain' });
125
+
126
+ // Library service
127
+ const libraryArtists = await client.library.getArtists({ user: 'ansango' });
128
+
129
+ // Auth service (for scrobbling and authenticated methods)
130
+ const session = await client.auth.getSession({ token: 'AUTH_TOKEN' });
131
+ ```
132
+
133
+ ### Using Global Configuration
134
+
135
+ Set configuration globally and reuse it across multiple client instances:
136
+
137
+ ```typescript
138
+ import { setGlobalConfig, createClient } from '@ansango/lastfm-api';
139
+
140
+ // Set global configuration once
141
+ setGlobalConfig({
142
+ apiKey: process.env.LASTFM_API_KEY!,
143
+ sharedSecret: process.env.LASTFM_SHARED_SECRET
144
+ });
145
+
146
+ // Create clients without passing config
147
+ const client1 = createClient();
148
+ const client2 = createClient();
149
+
150
+ // Both clients use the same global configuration
151
+ const user1 = await client1.user.getInfo({ user: 'user1' });
152
+ const user2 = await client2.user.getInfo({ user: 'user2' });
153
+ ```
154
+
155
+ ### Using Individual Services
156
+
157
+ Import only the services you need for better tree-shaking:
158
+
159
+ ```typescript
160
+ // Import only the user service
161
+ import { createUserService } from '@ansango/lastfm-api/user';
162
+ import type { UserGetInfoRequest } from '@ansango/lastfm-api/user';
163
+
164
+ const userService = createUserService({
165
+ apiKey: 'YOUR_API_KEY'
166
+ });
167
+
168
+ const params: UserGetInfoRequest = { user: 'ansango' };
169
+ const userInfo = await userService.getInfo(params);
170
+ ```
171
+
172
+ ```typescript
173
+ // Import multiple services
174
+ import { createAlbumService } from '@ansango/lastfm-api/album';
175
+ import { createTrackService } from '@ansango/lastfm-api/track';
176
+
177
+ const config = { apiKey: 'YOUR_API_KEY' };
178
+
179
+ const albumService = createAlbumService(config);
180
+ const trackService = createTrackService(config);
181
+
182
+ const albums = await albumService.search({ album: 'Abbey Road' });
183
+ const tracks = await trackService.search({ track: 'Come Together' });
184
+ ```
185
+
186
+ **Available service imports:**
187
+ - `@ansango/lastfm-api/user`
188
+ - `@ansango/lastfm-api/album`
189
+ - `@ansango/lastfm-api/artist`
190
+ - `@ansango/lastfm-api/track`
191
+ - `@ansango/lastfm-api/tag`
192
+ - `@ansango/lastfm-api/chart`
193
+ - `@ansango/lastfm-api/geo`
194
+ - `@ansango/lastfm-api/library`
195
+ - `@ansango/lastfm-api/auth`
196
+
197
+ ## Zod Schema Validation
198
+
199
+ The library includes automatically generated Zod schemas for runtime validation. These schemas mirror all TypeScript types and can be used to validate API responses or user input at runtime.
200
+
201
+ ### Importing Schemas
202
+
203
+ Schemas are available through modular imports, following the same pattern as the services:
204
+
205
+ ```typescript
206
+ import { userGetInfoRequestSchema, userGetInfoResponseSchema } from '@ansango/lastfm-api/user/schemas';
207
+ import { albumSearchRequestSchema } from '@ansango/lastfm-api/album/schemas';
208
+ import { trackGetInfoResponseSchema } from '@ansango/lastfm-api/track/schemas';
209
+ ```
210
+
211
+ ### Usage Example
212
+
213
+ ```typescript
214
+ import { userGetInfoRequestSchema, userGetInfoResponseSchema } from '@ansango/lastfm-api/user/schemas';
215
+
216
+ // Validate request parameters
217
+ const params = { user: 'ansango' };
218
+ const validatedParams = userGetInfoRequestSchema.parse(params);
219
+
220
+ // Validate API response
221
+ const response = await fetch(`https://ws.audioscrobbler.com/2.0/...`);
222
+ const data = await response.json();
223
+ const validatedData = userGetInfoResponseSchema.parse(data);
224
+
225
+ // Safe parsing (doesn't throw)
226
+ const result = userGetInfoResponseSchema.safeParse(data);
227
+ if (result.success) {
228
+ console.log(result.data);
229
+ } else {
230
+ console.error(result.error);
231
+ }
232
+ ```
233
+
234
+ **Available schema imports:**
235
+ - `@ansango/lastfm-api/user/schemas`
236
+ - `@ansango/lastfm-api/album/schemas`
237
+ - `@ansango/lastfm-api/artist/schemas`
238
+ - `@ansango/lastfm-api/track/schemas`
239
+ - `@ansango/lastfm-api/tag/schemas`
240
+ - `@ansango/lastfm-api/chart/schemas`
241
+ - `@ansango/lastfm-api/geo/schemas`
242
+ - `@ansango/lastfm-api/library/schemas`
243
+ - `@ansango/lastfm-api/auth/schemas`
244
+ - `@ansango/lastfm-api/schemas` (base types like `imageSchema`, `datePropSchema`, etc.)
245
+
246
+ ## Environment Variables
247
+
248
+ In Node.js environments, the client automatically loads configuration from environment variables:
249
+
250
+ ```bash
251
+ # .env file
252
+ LASTFM_API_KEY=your_api_key_here
253
+ LASTFM_SHARED_SECRET=your_shared_secret_here
254
+ LASTFM_SESSION_KEY=user_session_key_here
255
+
256
+ # Optional: Custom base URL
257
+ LASTFM_BASE_URL=https://ws.audioscrobbler.com/2.0/
258
+ ```
259
+
260
+ ```typescript
261
+ // Configuration is loaded automatically from process.env
262
+ import { createClient } from '@ansango/lastfm-api';
263
+
264
+ const client = createClient(); // Uses environment variables
265
+ ```
266
+
267
+ **Browser Usage:**
268
+
269
+ In browser environments, pass configuration explicitly or use your bundler's environment variable system:
270
+
271
+ ```typescript
272
+ // Vite
273
+ const client = new LastFmClient({
274
+ apiKey: import.meta.env.VITE_LASTFM_API_KEY
275
+ });
276
+
277
+ // Webpack
278
+ const client = new LastFmClient({
279
+ apiKey: process.env.REACT_APP_LASTFM_API_KEY
280
+ });
281
+ ```
282
+
283
+ ## Authentication & Scrobbling
284
+
285
+ Methods that mutate user state (`track.scrobble`, `track.scrobbleMany`, plus future write methods) require an authenticated session. Get one with `client.auth.getSession({ token })` after the user authorizes the token in a browser. Once you have a session key, pass it as `sessionKey` in your config — the client injects it into every authenticated call.
286
+
287
+ ```typescript
288
+ import { LastFmClient } from '@ansango/lastfm-api';
289
+
290
+ const client = new LastFmClient({
291
+ apiKey: process.env.LASTFM_API_KEY!,
292
+ sharedSecret: process.env.LASTFM_SHARED_SECRET!,
293
+ sessionKey: process.env.LASTFM_SESSION_KEY!,
294
+ });
295
+
296
+ // Scrobble a track — `sk` is auto-injected from `config.sessionKey`,
297
+ // so you don't need to pass it explicitly.
298
+ await client.track.scrobble({
299
+ artist: 'Cher',
300
+ track: 'Believe',
301
+ timestamp: Math.floor(Date.now() / 1000),
302
+ });
303
+ ```
304
+
305
+ > **Note:** The previous method names `postTrackScrobble` and `postBatchTrackScrobble` are still available as deprecated aliases. They forward to `scrobble` and `scrobbleMany` respectively and will be removed in the next major release.
306
+
307
+ ## Error Handling
308
+
309
+ API errors throw a `LastFmApiError` that carries the HTTP status and the Last.fm error code, so consumers can distinguish failures programmatically:
310
+
311
+ ```typescript
312
+ import { LastFmClient, LastFmApiError } from '@ansango/lastfm-api';
313
+
314
+ try {
315
+ await client.user.getInfo({ user: 'nonexistent_user_xyz' });
316
+ } catch (e) {
317
+ if (e instanceof LastFmApiError) {
318
+ console.error(`Last.fm error ${e.code}: ${e.message} (HTTP ${e.httpStatus})`);
319
+ // e.g. "Last.fm error 14: This token has not been authorized (HTTP 401)"
320
+ } else {
321
+ throw e;
322
+ }
323
+ }
324
+ ```
325
+
326
+ The `code` is the numeric error code from the [Last.fm API documentation](https://www.last.fm/api/show/user.getInfo#errors) (e.g. `9` for "Invalid session key", `10` for "Invalid API key", `29` for "Rate limit exceeded").
327
+
328
+ ## API Reference
329
+
330
+ ### Client
331
+
332
+ The main client class with all services:
333
+
334
+ ```typescript
335
+ class LastFmClient {
336
+ user: UserService;
337
+ album: AlbumService;
338
+ artist: ArtistService;
339
+ track: TrackService;
340
+ tag: TagService;
341
+ chart: ChartService;
342
+ geo: GeoService;
343
+ library: LibraryService;
344
+ auth: AuthService;
345
+
346
+ constructor(config?: Partial<LastFmConfig>);
347
+ getConfig(): Readonly<LastFmConfig>;
348
+ }
349
+ ```
350
+
351
+ ### Configuration
352
+
353
+ ```typescript
354
+ interface LastFmConfig {
355
+ apiKey: string; // Required: Your Last.fm API key
356
+ sharedSecret?: string; // Optional: Required for authenticated methods
357
+ sessionKey?: string; // Optional: User session key for scrobbling
358
+ baseUrl?: string; // Optional: API base URL (default: https://ws.audioscrobbler.com/2.0/)
359
+ }
360
+
361
+ // Configuration functions
362
+ function createConfig(options?: Partial<LastFmConfig>): LastFmConfig;
363
+ function setGlobalConfig(config: Partial<LastFmConfig>): void;
364
+ function getGlobalConfig(): LastFmConfig;
365
+ function resetGlobalConfig(): void;
366
+ ```
367
+
368
+ ### Services
369
+
370
+ Each service provides methods for interacting with specific Last.fm API endpoints:
371
+
372
+ - **UserService**: User-related methods (getInfo, getTopArtists, getRecentTracks, etc.)
373
+ - **AlbumService**: Album methods (getInfo, search, getTags, etc.)
374
+ - **ArtistService**: Artist methods (getInfo, getSimilar, getTopAlbums, etc.)
375
+ - **TrackService**: Track methods (getInfo, search, scrobble, scrobbleMany, etc.)
376
+ - **TagService**: Tag methods (getInfo, getTopArtists, getTopTracks, etc.)
377
+ - **ChartService**: Chart methods (getTopArtists, getTopTracks, etc.)
378
+ - **GeoService**: Geographic methods (getTopArtists, getTopTracks by country)
379
+ - **LibraryService**: Library methods (getArtists, etc.)
380
+ - **AuthService**: Authentication methods (getSession, etc.)
381
+
382
+ ## TypeScript Support
383
+
384
+ The library is fully typed with comprehensive TypeScript definitions:
385
+
386
+ ```typescript
387
+ import { LastFmClient } from '@ansango/lastfm-api';
388
+ import type {
389
+ UserGetInfoRequest,
390
+ UserGetInfoResponse,
391
+ AlbumSearchRequest,
392
+ AlbumSearchResponse
393
+ } from '@ansango/lastfm-api';
394
+
395
+ const client = new LastFmClient({ apiKey: 'YOUR_API_KEY' });
396
+
397
+ // Type-safe requests and responses
398
+ const userParams: UserGetInfoRequest = { user: 'ansango' };
399
+ const userInfo: UserGetInfoResponse = await client.user.getInfo(userParams);
400
+
401
+ const albumParams: AlbumSearchRequest = { album: 'Believe', limit: 10 };
402
+ const albums: AlbumSearchResponse = await client.album.search(albumParams);
403
+ ```
404
+
405
+ All request parameters and response types are exported for your convenience.
406
+
407
+ ## Contributing
408
+
409
+ Contributions are always welcome!
410
+
411
+ 1. Fork the repository
412
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
413
+ 3. Commit your changes using [Conventional Commits](https://www.conventionalcommits.org/)
414
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
415
+ 5. Open a Pull Request
416
+
417
+ ### Development Scripts
418
+
419
+ ```bash
420
+ # Install dependencies
421
+ bun install
422
+
423
+ # Run in development mode (watch mode)
424
+ bun run dev
425
+
426
+ # Build the project
427
+ bun run build
428
+
429
+ # Run tests
430
+ bun test
431
+
432
+ # Clean build artifacts
433
+ bun run clean
434
+ ```
435
+
436
+ ### Release Process
437
+
438
+ This project uses automated release scripts:
439
+
440
+ ```bash
441
+ # Create a patch release (1.0.0 -> 1.0.1)
442
+ bun run release:patch
443
+
444
+ # Create a minor release (1.0.0 -> 1.1.0)
445
+ bun run release:minor
446
+
447
+ # Create a major release (1.0.0 -> 2.0.0)
448
+ bun run release:major
449
+
450
+ # Create an alpha release (1.0.0 -> 1.0.1-alpha.0)
451
+ bun run release:alpha
452
+
453
+ # Create a beta release (1.0.0 -> 1.0.1-beta.0)
454
+ bun run release:beta
455
+ ```
456
+
457
+ The release script will:
458
+ - Run tests
459
+ - Build the project
460
+ - Generate changelog from commits
461
+ - Bump version in package.json
462
+ - Create git tag
463
+ - Create GitHub release
464
+ - Publish to npm
465
+
466
+ For more details, see [scripts/README.md](scripts/README.md).
467
+
468
+ ## License
469
+
470
+ [MIT](LICENSE) © [Anibal Santos](https://github.com/ansango)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ import { test, beforeEach, afterEach, describe } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { LastFmClient } from '../client.js';
4
+ import { buildUrl, buildAuthUrl } from '../utils.js';
5
+ /**
6
+ * Regression tests for issue #58:
7
+ * `new LastFmClient({ apiKey })` must not throw "Failed to parse URL from
8
+ * undefined" because `buildUrl` / `buildAuthUrl` default `baseUrl`.
9
+ */
10
+ /** Minimal fake artist payload that satisfies the Zod schema. */
11
+ const fakeArtistPayload = {
12
+ artist: {
13
+ name: 'Radiohead',
14
+ mbid: 'a74b1b7f-71a5-4011-9441-d0b5e4122711',
15
+ url: 'https://www.last.fm/music/Radiohead',
16
+ image: [],
17
+ streamable: '0',
18
+ ontour: '0',
19
+ stats: {
20
+ listeners: '1',
21
+ playcount: '1',
22
+ userplaycount: '0'
23
+ },
24
+ similar: { artist: [] },
25
+ tags: { tag: [] },
26
+ bio: {
27
+ published: '2024-01-01',
28
+ summary: 'Test',
29
+ content: 'Test'
30
+ }
31
+ }
32
+ };
33
+ describe('issue #58: default baseUrl', () => {
34
+ const originalFetch = globalThis.fetch;
35
+ beforeEach(() => {
36
+ // Mock fetch so the test does not require a real API key.
37
+ globalThis.fetch = (async (input) => {
38
+ const url = typeof input === 'string' ? input : input.toString();
39
+ // Sanity: a syntactically valid URL must be passed to fetch.
40
+ // Before the fix this would throw "Failed to parse URL from undefined".
41
+ new URL(url);
42
+ return new Response(JSON.stringify(fakeArtistPayload), {
43
+ status: 200,
44
+ headers: { 'Content-Type': 'application/json' }
45
+ });
46
+ });
47
+ });
48
+ afterEach(() => {
49
+ globalThis.fetch = originalFetch;
50
+ });
51
+ test('LastFmClient constructed without baseUrl + getInfo does not throw "Failed to parse URL from undefined"', async () => {
52
+ const client = new LastFmClient({ apiKey: 'test' });
53
+ const result = await client.artist.getInfo({ artist: 'Radiohead' });
54
+ assert.equal(result.artist.name, 'Radiohead');
55
+ });
56
+ test('buildUrl defaults baseUrl when config.baseUrl is undefined', () => {
57
+ const config = { apiKey: 'test' };
58
+ const url = buildUrl(config, 'artist.getInfo', { artist: 'Radiohead' });
59
+ assert.match(url, /^https:\/\/ws\.audioscrobbler\.com\/2\.0\/\?/);
60
+ assert.match(url, /method=artist\.getInfo/);
61
+ assert.match(url, /api_key=test/);
62
+ // Must not produce the literal "undefined?..." string that caused the original bug.
63
+ assert.doesNotMatch(url, /^undefined/);
64
+ });
65
+ test('buildAuthUrl defaults baseUrl when config.baseUrl is undefined', () => {
66
+ const config = { apiKey: 'test', sharedSecret: 'secret' };
67
+ const url = buildAuthUrl(config, 'track.scrobble', {
68
+ artist: 'Radiohead',
69
+ track: 'OK Computer',
70
+ timestamp: 1,
71
+ sk: 'session'
72
+ });
73
+ assert.match(url, /^https:\/\/ws\.audioscrobbler\.com\/2\.0\/\?/);
74
+ assert.match(url, /api_sig=[a-f0-9]{32}/);
75
+ assert.doesNotMatch(url, /^undefined/);
76
+ });
77
+ test('buildUrl honours an explicit baseUrl when provided', () => {
78
+ const config = {
79
+ apiKey: 'test',
80
+ baseUrl: 'https://example.test/api/'
81
+ };
82
+ const url = buildUrl(config, 'artist.getInfo', { artist: 'X' });
83
+ assert.match(url, /^https:\/\/example\.test\/api\/\?/);
84
+ });
85
+ });
86
+ //# sourceMappingURL=utils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.test.js","sourceRoot":"","sources":["../../src/__tests__/utils.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { LastFmConfig } from './config.js';
2
+ import { type UserService, type AlbumService, type ArtistService, type TrackService, type TagService, type ChartService, type GeoService, type LibraryService, type AuthService } from './services/index.js';
3
+ /**
4
+ * Last.fm API Client
5
+ *
6
+ * Universal client for interacting with the Last.fm API.
7
+ * Works in both Node.js and browser environments.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // With explicit config
12
+ * const client = new LastFmClient({
13
+ * apiKey: 'YOUR_API_KEY',
14
+ * sharedSecret: 'YOUR_SHARED_SECRET'
15
+ * });
16
+ *
17
+ * // Using global config
18
+ * setGlobalConfig({ apiKey: 'YOUR_API_KEY' });
19
+ * const client = new LastFmClient();
20
+ * ```
21
+ */
22
+ export declare class LastFmClient {
23
+ readonly user: UserService;
24
+ readonly album: AlbumService;
25
+ readonly artist: ArtistService;
26
+ readonly track: TrackService;
27
+ readonly tag: TagService;
28
+ readonly chart: ChartService;
29
+ readonly geo: GeoService;
30
+ readonly library: LibraryService;
31
+ readonly auth: AuthService;
32
+ private readonly config;
33
+ constructor(config?: Partial<LastFmConfig>);
34
+ /**
35
+ * Get the current configuration (read-only)
36
+ */
37
+ getConfig(): Readonly<LastFmConfig>;
38
+ }
39
+ /**
40
+ * Helper function to create a Last.fm client instance
41
+ *
42
+ * @param config - Optional configuration. If not provided, uses global config.
43
+ * @returns A new LastFmClient instance
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const client = createClient({ apiKey: 'YOUR_API_KEY' });
48
+ * const info = await client.user.getInfo({ user: 'ansango' });
49
+ * ```
50
+ */
51
+ export declare function createClient(config?: Partial<LastFmConfig>): LastFmClient;
52
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAUN,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,YAAY;IACxB,SAAgB,IAAI,EAAE,WAAW,CAAC;IAClC,SAAgB,KAAK,EAAE,YAAY,CAAC;IACpC,SAAgB,MAAM,EAAE,aAAa,CAAC;IACtC,SAAgB,KAAK,EAAE,YAAY,CAAC;IACpC,SAAgB,GAAG,EAAE,UAAU,CAAC;IAChC,SAAgB,KAAK,EAAE,YAAY,CAAC;IACpC,SAAgB,GAAG,EAAE,UAAU,CAAC;IAChC,SAAgB,OAAO,EAAE,cAAc,CAAC;IACxC,SAAgB,IAAI,EAAE,WAAW,CAAC;IAElC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC;IAe1C;;OAEG;IACH,SAAS,IAAI,QAAQ,CAAC,YAAY,CAAC;CAGnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAEzE"}
package/dist/client.js ADDED
@@ -0,0 +1,68 @@
1
+ import { getGlobalConfig, createConfig } from './config.js';
2
+ import { createUserService, createAlbumService, createArtistService, createTrackService, createTagService, createChartService, createGeoService, createLibraryService, createAuthService } from './services/index.js';
3
+ /**
4
+ * Last.fm API Client
5
+ *
6
+ * Universal client for interacting with the Last.fm API.
7
+ * Works in both Node.js and browser environments.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // With explicit config
12
+ * const client = new LastFmClient({
13
+ * apiKey: 'YOUR_API_KEY',
14
+ * sharedSecret: 'YOUR_SHARED_SECRET'
15
+ * });
16
+ *
17
+ * // Using global config
18
+ * setGlobalConfig({ apiKey: 'YOUR_API_KEY' });
19
+ * const client = new LastFmClient();
20
+ * ```
21
+ */
22
+ export class LastFmClient {
23
+ user;
24
+ album;
25
+ artist;
26
+ track;
27
+ tag;
28
+ chart;
29
+ geo;
30
+ library;
31
+ auth;
32
+ config;
33
+ constructor(config) {
34
+ this.config = config ? createConfig(config) : getGlobalConfig();
35
+ // Initialize all services
36
+ this.user = createUserService(this.config);
37
+ this.album = createAlbumService(this.config);
38
+ this.artist = createArtistService(this.config);
39
+ this.track = createTrackService(this.config);
40
+ this.tag = createTagService(this.config);
41
+ this.chart = createChartService(this.config);
42
+ this.geo = createGeoService(this.config);
43
+ this.library = createLibraryService(this.config);
44
+ this.auth = createAuthService(this.config);
45
+ }
46
+ /**
47
+ * Get the current configuration (read-only)
48
+ */
49
+ getConfig() {
50
+ return { ...this.config };
51
+ }
52
+ }
53
+ /**
54
+ * Helper function to create a Last.fm client instance
55
+ *
56
+ * @param config - Optional configuration. If not provided, uses global config.
57
+ * @returns A new LastFmClient instance
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const client = createClient({ apiKey: 'YOUR_API_KEY' });
62
+ * const info = await client.user.getInfo({ user: 'ansango' });
63
+ * ```
64
+ */
65
+ export function createClient(config) {
66
+ return new LastFmClient(config);
67
+ }
68
+ //# sourceMappingURL=client.js.map