soundcloud-api-ts-next 1.3.0 → 1.3.1

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 (2) hide show
  1. package/README.md +196 -243
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,30 +1,31 @@
1
1
  # soundcloud-api-ts-next
2
2
 
3
- Next.js integration for [soundcloud-api-ts](https://github.com/twin-paws/soundcloud-api-ts) — React hooks + secure API route handlers.
3
+ [![npm version](https://img.shields.io/npm/v/soundcloud-api-ts-next)](https://www.npmjs.com/package/soundcloud-api-ts-next)
4
+ [![npm downloads](https://img.shields.io/npm/dm/soundcloud-api-ts-next)](https://www.npmjs.com/package/soundcloud-api-ts-next)
5
+ [![license](https://img.shields.io/npm/l/soundcloud-api-ts-next)](https://github.com/twin-paws/soundcloud-api-ts-next/blob/main/LICENSE)
4
6
 
5
- ## Installation
7
+ React hooks and Next.js API route handlers for the SoundCloud API. Client secrets stay on the server.
8
+
9
+ Built on [soundcloud-api-ts](https://github.com/twin-paws/soundcloud-api-ts).
10
+
11
+ ## Install
6
12
 
7
13
  ```bash
8
14
  npm install soundcloud-api-ts-next
9
- # or
10
- pnpm add soundcloud-api-ts-next
11
15
  ```
12
16
 
13
- ## Setup
14
-
15
- ### 1. Server Routes
16
-
17
- Create an API route handler that proxies SoundCloud requests (keeps your credentials server-side).
17
+ ## Quick Start
18
18
 
19
- **App Router** (`app/api/soundcloud/[...route]/route.ts`):
19
+ **1. Create API routes** — secrets stay server-side:
20
20
 
21
21
  ```ts
22
+ // app/api/soundcloud/[...route]/route.ts
22
23
  import { createSoundCloudRoutes } from "soundcloud-api-ts-next/server";
23
24
 
24
25
  const sc = createSoundCloudRoutes({
25
26
  clientId: process.env.SOUNDCLOUD_CLIENT_ID!,
26
27
  clientSecret: process.env.SOUNDCLOUD_CLIENT_SECRET!,
27
- redirectUri: process.env.SOUNDCLOUD_REDIRECT_URI, // Required for OAuth
28
+ redirectUri: process.env.SOUNDCLOUD_REDIRECT_URI, // for OAuth
28
29
  });
29
30
 
30
31
  const handler = sc.handler();
@@ -33,25 +34,26 @@ export const POST = handler;
33
34
  export const DELETE = handler;
34
35
  ```
35
36
 
36
- **Pages Router** (`pages/api/soundcloud/[...route].ts`):
37
+ <details>
38
+ <summary>Pages Router setup</summary>
37
39
 
38
40
  ```ts
41
+ // pages/api/soundcloud/[...route].ts
39
42
  import { createSoundCloudRoutes } from "soundcloud-api-ts-next/server";
40
43
 
41
44
  const sc = createSoundCloudRoutes({
42
45
  clientId: process.env.SOUNDCLOUD_CLIENT_ID!,
43
46
  clientSecret: process.env.SOUNDCLOUD_CLIENT_SECRET!,
44
- redirectUri: process.env.SOUNDCLOUD_REDIRECT_URI,
45
47
  });
46
48
 
47
49
  export default sc.pagesHandler();
48
50
  ```
51
+ </details>
49
52
 
50
- ### 2. Client Provider
51
-
52
- Wrap your app with the `SoundCloudProvider`:
53
+ **2. Add the provider:**
53
54
 
54
55
  ```tsx
56
+ // app/layout.tsx
55
57
  import { SoundCloudProvider } from "soundcloud-api-ts-next";
56
58
 
57
59
  export default function Layout({ children }) {
@@ -63,29 +65,117 @@ export default function Layout({ children }) {
63
65
  }
64
66
  ```
65
67
 
66
- ## Authentication (OAuth + PKCE)
68
+ **3. Use hooks:**
69
+
70
+ ```tsx
71
+ import { useTrackSearch, usePlayer } from "soundcloud-api-ts-next";
72
+
73
+ function SearchPage() {
74
+ const { data: tracks, loading } = useTrackSearch("lofi beats");
75
+
76
+ if (loading) return <p>Searching...</p>;
77
+
78
+ return tracks?.map((track) => (
79
+ <div key={track.id}>
80
+ <p>{track.title} — {track.user.username}</p>
81
+ </div>
82
+ ));
83
+ }
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Hooks
89
+
90
+ All hooks return `{ data, loading, error }`.
91
+
92
+ ### Tracks
93
+
94
+ | Hook | Description |
95
+ |------|-------------|
96
+ | `useTrack(id)` | Single track |
97
+ | `useTrackSearch(query)` | Search tracks |
98
+ | `useTrackComments(id)` | Track comments |
99
+ | `useTrackLikes(id)` | Users who liked a track |
100
+ | `useRelatedTracks(id)` | Related tracks |
101
+ | `usePlayer(streamUrl)` | Audio player — `{ playing, progress, duration, play, pause, toggle, seek }` |
102
+
103
+ ### Users
104
+
105
+ | Hook | Description |
106
+ |------|-------------|
107
+ | `useUser(id)` | Single user |
108
+ | `useUserSearch(query)` | Search users |
109
+ | `useUserTracks(id)` | User's tracks |
110
+ | `useUserPlaylists(id)` | User's playlists |
111
+ | `useUserLikes(id)` | User's liked tracks |
112
+ | `useUserFollowers(id)` | User's followers |
113
+ | `useUserFollowings(id)` | User's followings |
114
+
115
+ ### Playlists
116
+
117
+ | Hook | Description |
118
+ |------|-------------|
119
+ | `usePlaylist(id)` | Single playlist |
120
+ | `usePlaylistSearch(query)` | Search playlists |
121
+ | `usePlaylistTracks(id)` | Playlist tracks |
122
+
123
+ ---
124
+
125
+ ## Infinite Scroll
126
+
127
+ Cursor-based pagination with `loadMore()` and `reset()`. All return `InfiniteResult<T>`:
128
+
129
+ ```ts
130
+ { data: T[], loading, error, hasMore, loadMore, reset }
131
+ ```
132
+
133
+ ```tsx
134
+ import { useInfiniteTrackSearch } from "soundcloud-api-ts-next";
135
+
136
+ function Feed() {
137
+ const { data, loading, hasMore, loadMore } = useInfiniteTrackSearch("dubstep");
138
+
139
+ return (
140
+ <>
141
+ {data.map((track) => <TrackCard key={track.id} track={track} />)}
142
+ {hasMore && <button onClick={loadMore} disabled={loading}>Load More</button>}
143
+ </>
144
+ );
145
+ }
146
+ ```
147
+
148
+ | Hook | Description |
149
+ |------|-------------|
150
+ | `useInfiniteTrackSearch(query)` | Paginated track search |
151
+ | `useInfiniteUserSearch(query)` | Paginated user search |
152
+ | `useInfinitePlaylistSearch(query)` | Paginated playlist search |
153
+ | `useInfiniteUserTracks(id)` | User's tracks |
154
+ | `useInfiniteUserPlaylists(id)` | User's playlists |
155
+ | `useInfiniteUserLikes(id)` | User's liked tracks |
156
+ | `useInfiniteUserFollowers(id)` | User's followers |
157
+ | `useInfiniteUserFollowings(id)` | User's followings |
158
+ | `useInfiniteTrackComments(id)` | Track comments |
159
+ | `useInfinitePlaylistTracks(id)` | Playlist tracks |
67
160
 
68
- soundcloud-api-ts-next includes a complete OAuth flow with PKCE for secure user authentication.
161
+ ---
69
162
 
70
- ### Setup
163
+ ## Authentication
71
164
 
72
- 1. Add `redirectUri` to your server config (see above)
73
- 2. Set your SoundCloud app's redirect URI to match (e.g., `http://localhost:3000/callback`)
165
+ Full OAuth 2.1 with PKCE. No secrets on the client.
74
166
 
75
- ### Login Flow
167
+ ### Login
76
168
 
77
169
  ```tsx
78
170
  import { useSCAuth } from "soundcloud-api-ts-next";
79
171
 
80
172
  function LoginButton() {
81
- const { isAuthenticated, user, login, logout, loading } = useSCAuth();
82
-
83
- if (loading) return <p>Loading...</p>;
173
+ const { isAuthenticated, user, login, logout } = useSCAuth();
84
174
 
85
175
  if (isAuthenticated) {
86
176
  return (
87
177
  <div>
88
- <p>Logged in as {user?.username}</p>
178
+ <p>Welcome, {user?.username}</p>
89
179
  <button onClick={logout}>Logout</button>
90
180
  </div>
91
181
  );
@@ -97,264 +187,117 @@ function LoginButton() {
97
187
 
98
188
  ### Callback Page
99
189
 
100
- Create a callback page that handles the OAuth redirect:
101
-
102
190
  ```tsx
103
191
  // app/callback/page.tsx
104
192
  "use client";
105
-
106
193
  import { useEffect } from "react";
107
194
  import { useSearchParams, useRouter } from "next/navigation";
108
195
  import { useSCAuth } from "soundcloud-api-ts-next";
109
196
 
110
- export default function CallbackPage() {
111
- const searchParams = useSearchParams();
197
+ export default function Callback() {
198
+ const params = useSearchParams();
112
199
  const router = useRouter();
113
200
  const { handleCallback } = useSCAuth();
114
201
 
115
202
  useEffect(() => {
116
- const code = searchParams.get("code");
117
- const state = searchParams.get("state");
203
+ const code = params.get("code");
204
+ const state = params.get("state");
118
205
  if (code && state) {
119
- handleCallback(code, state).then(() => {
120
- router.push("/");
121
- });
206
+ handleCallback(code, state).then(() => router.push("/"));
122
207
  }
123
- }, [searchParams]);
208
+ }, [params]);
124
209
 
125
210
  return <p>Authenticating...</p>;
126
211
  }
127
212
  ```
128
213
 
129
- ### Authenticated User Hooks
130
-
131
- These hooks fetch data for the currently logged-in user. They require authentication and automatically pass the access token.
132
-
133
- | Hook | Returns | Description |
134
- |------|---------|-------------|
135
- | `useMe()` | `HookResult<SoundCloudUser>` | Current user's profile |
136
- | `useMeTracks()` | `HookResult<SoundCloudTrack[]>` | Current user's tracks |
137
- | `useMeLikes()` | `HookResult<SoundCloudTrack[]>` | Current user's liked tracks |
138
- | `useMePlaylists()` | `HookResult<SoundCloudPlaylist[]>` | Current user's playlists |
139
- | `useMeFollowings()` | `HookResult<SoundCloudUser[]>` | Who current user follows |
140
- | `useMeFollowers()` | `HookResult<SoundCloudUser[]>` | Current user's followers |
214
+ ### Authenticated Hooks
141
215
 
142
- ```tsx
143
- import { useMe, useMeTracks, useMeLikes } from "soundcloud-api-ts-next";
144
-
145
- function MyProfile() {
146
- const { data: me } = useMe();
147
- const { data: tracks } = useMeTracks();
148
- const { data: likes } = useMeLikes();
216
+ Available after login. Automatically pass the user's access token.
149
217
 
150
- if (!me) return null;
151
-
152
- return (
153
- <div>
154
- <h1>{me.username}</h1>
155
- <p>{tracks?.length} tracks, {likes?.length} likes</p>
156
- </div>
157
- );
158
- }
159
- ```
218
+ | Hook | Description |
219
+ |------|-------------|
220
+ | `useMe()` | Current user profile |
221
+ | `useMeTracks()` | Your tracks |
222
+ | `useMeLikes()` | Your liked tracks |
223
+ | `useMePlaylists()` | Your playlists |
224
+ | `useMeFollowings()` | Who you follow |
225
+ | `useMeFollowers()` | Your followers |
160
226
 
161
- ### Action Hooks
227
+ ### Actions
162
228
 
163
- Mutation hooks for user actions. All require authentication.
229
+ Mutation hooks for authenticated users.
164
230
 
165
- | Hook | Methods | Description |
166
- |------|---------|-------------|
167
- | `useFollow()` | `follow(userId)`, `unfollow(userId)` | Follow/unfollow a user |
168
- | `useLike()` | `likeTrack(trackId)`, `unlikeTrack(trackId)` | Like/unlike a track |
169
- | `useRepost()` | `repostTrack(trackId)`, `unrepostTrack(trackId)` | Repost/unrepost a track |
231
+ | Hook | Methods |
232
+ |------|---------|
233
+ | `useLike()` | `likeTrack(id)`, `unlikeTrack(id)` |
234
+ | `useFollow()` | `follow(userId)`, `unfollow(userId)` |
235
+ | `useRepost()` | `repostTrack(id)`, `unrepostTrack(id)` |
170
236
 
171
237
  ```tsx
172
238
  import { useLike, useFollow } from "soundcloud-api-ts-next";
173
239
 
174
240
  function TrackActions({ trackId, artistId }) {
175
- const { likeTrack, unlikeTrack, loading: likeLoading } = useLike();
176
- const { follow, loading: followLoading } = useFollow();
241
+ const { likeTrack } = useLike();
242
+ const { follow } = useFollow();
177
243
 
178
244
  return (
179
- <div>
180
- <button onClick={() => likeTrack(trackId)} disabled={likeLoading}>
181
- ❤️ Like
182
- </button>
183
- <button onClick={() => follow(artistId)} disabled={followLoading}>
184
- ➕ Follow Artist
185
- </button>
186
- </div>
245
+ <>
246
+ <button onClick={() => likeTrack(trackId)}>❤️ Like</button>
247
+ <button onClick={() => follow(artistId)}>➕ Follow</button>
248
+ </>
187
249
  );
188
250
  }
189
251
  ```
190
252
 
191
- ## Hooks
192
-
193
- All hooks return `{ data, loading, error }`.
194
-
195
- ### Tracks
196
-
197
- | Hook | Arguments | Description |
198
- |------|-----------|-------------|
199
- | `useTrack(trackId)` | `string \| number \| undefined` | Fetch a single track |
200
- | `useTrackSearch(query, options?)` | `string`, `{ limit? }` | Search tracks |
201
- | `useTrackComments(trackId)` | `string \| number \| undefined` | Get track comments |
202
- | `useTrackLikes(trackId)` | `string \| number \| undefined` | Get users who liked a track |
203
- | `useRelatedTracks(trackId)` | `string \| number \| undefined` | Get related tracks |
204
-
205
- ### Users
206
-
207
- | Hook | Arguments | Description |
208
- |------|-----------|-------------|
209
- | `useUser(userId)` | `string \| number \| undefined` | Fetch a single user |
210
- | `useUserSearch(query)` | `string` | Search users |
211
- | `useUserTracks(userId)` | `string \| number \| undefined` | Get a user's tracks |
212
- | `useUserPlaylists(userId)` | `string \| number \| undefined` | Get a user's playlists |
213
- | `useUserLikes(userId)` | `string \| number \| undefined` | Get a user's liked tracks |
214
- | `useUserFollowers(userId)` | `string \| number \| undefined` | Get a user's followers |
215
- | `useUserFollowings(userId)` | `string \| number \| undefined` | Get a user's followings |
216
-
217
- ### Playlists
218
-
219
- | Hook | Arguments | Description |
220
- |------|-----------|-------------|
221
- | `usePlaylist(playlistId)` | `string \| number \| undefined` | Fetch a single playlist |
222
- | `usePlaylistTracks(playlistId)` | `string \| number \| undefined` | Get tracks in a playlist |
223
- | `usePlaylistSearch(query)` | `string` | Search playlists |
224
-
225
- ### Player
226
-
227
- | Hook | Arguments | Description |
228
- |------|-----------|-------------|
229
- | `usePlayer(streamUrl)` | `string \| undefined` | Audio player with play/pause/seek |
253
+ ---
230
254
 
231
255
  ## Server Routes
232
256
 
233
- All routes are available via the catch-all handler and as individual methods on the routes object.
234
-
235
- ### Auth Routes
236
-
237
- | Route | Method | Description |
238
- |-------|--------|-------------|
239
- | `/auth/login` | GET | Get SoundCloud OAuth URL (PKCE) |
240
- | `/auth/callback?code=...&state=...` | GET | Exchange auth code for tokens |
241
- | `/auth/refresh` | POST | Refresh access token |
242
- | `/auth/logout` | POST | Sign out / revoke token |
243
-
244
- ### Me Routes (require `Authorization: Bearer <token>` header)
245
-
246
- | Route | Method | Description |
247
- |-------|--------|-------------|
248
- | `/me` | GET | Current user profile |
249
- | `/me/tracks` | GET | Current user's tracks |
250
- | `/me/likes` | GET | Current user's liked tracks |
251
- | `/me/playlists` | GET | Current user's playlists |
252
- | `/me/followings` | GET | Who current user follows |
253
- | `/me/followers` | GET | Current user's followers |
254
-
255
- ### Action Routes (require `Authorization: Bearer <token>` header)
256
-
257
- | Route | Method | Description |
258
- |-------|--------|-------------|
259
- | `/me/follow/:userId` | POST | Follow a user |
260
- | `/me/follow/:userId` | DELETE | Unfollow a user |
261
- | `/tracks/:id/like` | POST | Like a track |
262
- | `/tracks/:id/like` | DELETE | Unlike a track |
263
- | `/tracks/:id/repost` | POST | Repost a track |
264
- | `/tracks/:id/repost` | DELETE | Unrepost a track |
265
- | `/playlists/:id/like` | POST/DELETE | Like/unlike a playlist |
266
- | `/playlists/:id/repost` | POST/DELETE | Repost/unrepost a playlist |
267
-
268
- ### Search
269
-
270
- | Route | Method | Description |
271
- |-------|--------|-------------|
272
- | `GET /search/tracks?q=...` | `searchTracks(q, page?)` | Search tracks |
273
- | `GET /search/users?q=...` | `searchUsers(q)` | Search users |
274
- | `GET /search/playlists?q=...` | `searchPlaylists(q)` | Search playlists |
275
-
276
- ### Tracks
277
-
278
- | Route | Method | Description |
279
- |-------|--------|-------------|
280
- | `GET /tracks/:id` | `getTrack(id)` | Get track details |
281
- | `GET /tracks/:id/stream` | `getTrackStreams(id)` | Get stream URLs |
282
- | `GET /tracks/:id/comments` | `getTrackComments(id)` | Get track comments |
283
- | `GET /tracks/:id/likes` | `getTrackLikes(id)` | Get track likes |
284
- | `GET /tracks/:id/related` | `getRelatedTracks(id)` | Get related tracks |
285
-
286
- ### Users
287
-
288
- | Route | Method | Description |
289
- |-------|--------|-------------|
290
- | `GET /users/:id` | `getUser(id)` | Get user details |
291
- | `GET /users/:id/tracks` | `getUserTracks(id, limit?)` | Get user's tracks |
292
- | `GET /users/:id/playlists` | `getUserPlaylists(id)` | Get user's playlists |
293
- | `GET /users/:id/likes/tracks` | `getUserLikesTracks(id)` | Get user's liked tracks |
294
- | `GET /users/:id/followers` | `getFollowers(id)` | Get user's followers |
295
- | `GET /users/:id/followings` | `getFollowings(id)` | Get user's followings |
296
-
297
- ### Playlists
257
+ The catch-all handler exposes these routes automatically:
298
258
 
299
259
  | Route | Method | Description |
300
260
  |-------|--------|-------------|
301
- | `GET /playlists/:id` | `getPlaylist(id)` | Get playlist details |
302
- | `GET /playlists/:id/tracks` | `getPlaylistTracks(id)` | Get playlist tracks |
303
-
304
- ## Pagination / Infinite Scroll
305
-
306
- All paginated endpoints have `useInfinite*` hooks that handle cursor-based pagination automatically:
307
-
308
- ```tsx
309
- import { useInfiniteTrackSearch } from "soundcloud-api-ts-next";
310
-
311
- function TrackList() {
312
- const { data, loading, error, hasMore, loadMore } = useInfiniteTrackSearch("lofi");
313
-
314
- return (
315
- <div>
316
- {data.map((track) => (
317
- <div key={track.id}>{track.title}</div>
318
- ))}
319
- {loading && <p>Loading...</p>}
320
- {error && <p>Error: {error.message}</p>}
321
- {hasMore && <button onClick={loadMore}>Load More</button>}
322
- </div>
323
- );
324
- }
325
- ```
326
-
327
- ### Available infinite hooks
328
-
329
- | Hook | Description |
330
- | ---- | ----------- |
331
- | `useInfiniteTrackSearch(query, options?)` | Paginated track search |
332
- | `useInfiniteUserSearch(query, options?)` | Paginated user search |
333
- | `useInfinitePlaylistSearch(query, options?)` | Paginated playlist search |
334
- | `useInfiniteUserTracks(userId)` | User's tracks |
335
- | `useInfiniteUserPlaylists(userId)` | User's playlists |
336
- | `useInfiniteUserLikes(userId)` | User's liked tracks |
337
- | `useInfiniteUserFollowers(userId)` | User's followers |
338
- | `useInfiniteUserFollowings(userId)` | User's followings |
339
- | `useInfiniteTrackComments(trackId)` | Track comments |
340
- | `useInfinitePlaylistTracks(playlistId)` | Playlist tracks |
341
-
342
- All hooks return `InfiniteResult<T>`:
343
-
344
- ```ts
345
- interface InfiniteResult<T> {
346
- data: T[]; // accumulated items across all pages
347
- loading: boolean;
348
- error: Error | null;
349
- hasMore: boolean; // true if more pages exist
350
- loadMore: () => void;
351
- reset: () => void; // clear and refetch from page 1
352
- }
353
- ```
261
+ | `/search/tracks?q=` | GET | Search tracks |
262
+ | `/search/users?q=` | GET | Search users |
263
+ | `/search/playlists?q=` | GET | Search playlists |
264
+ | `/tracks/:id` | GET | Track details |
265
+ | `/tracks/:id/stream` | GET | Stream URLs |
266
+ | `/tracks/:id/comments` | GET | Track comments |
267
+ | `/tracks/:id/likes` | GET | Track likes |
268
+ | `/tracks/:id/related` | GET | Related tracks |
269
+ | `/tracks/:id/like` | POST/DELETE | Like/unlike (auth) |
270
+ | `/tracks/:id/repost` | POST/DELETE | Repost/unrepost (auth) |
271
+ | `/users/:id` | GET | User details |
272
+ | `/users/:id/tracks` | GET | User tracks |
273
+ | `/users/:id/playlists` | GET | User playlists |
274
+ | `/users/:id/likes/tracks` | GET | User likes |
275
+ | `/users/:id/followers` | GET | User followers |
276
+ | `/users/:id/followings` | GET | User followings |
277
+ | `/playlists/:id` | GET | Playlist details |
278
+ | `/playlists/:id/tracks` | GET | Playlist tracks |
279
+ | `/playlists/:id/like` | POST/DELETE | Like/unlike (auth) |
280
+ | `/playlists/:id/repost` | POST/DELETE | Repost/unrepost (auth) |
281
+ | `/me` | GET | Current user (auth) |
282
+ | `/me/tracks` | GET | Your tracks (auth) |
283
+ | `/me/likes` | GET | Your likes (auth) |
284
+ | `/me/playlists` | GET | Your playlists (auth) |
285
+ | `/me/followings` | GET | Your followings (auth) |
286
+ | `/me/followers` | GET | Your followers (auth) |
287
+ | `/me/follow/:userId` | POST/DELETE | Follow/unfollow (auth) |
288
+ | `/auth/login` | GET | OAuth URL (PKCE) |
289
+ | `/auth/callback` | GET | Token exchange |
290
+ | `/auth/refresh` | POST | Refresh token |
291
+ | `/auth/logout` | POST | Sign out |
292
+ | `/next?url=` | GET | Pagination cursor |
293
+
294
+ Routes marked **(auth)** require `Authorization: Bearer <token>` header.
295
+
296
+ ---
354
297
 
355
298
  ## Types
356
299
 
357
- All SoundCloud types are re-exported from `soundcloud-api-ts`:
300
+ Re-exported from [soundcloud-api-ts](https://github.com/twin-paws/soundcloud-api-ts):
358
301
 
359
302
  ```ts
360
303
  import type {
@@ -364,11 +307,21 @@ import type {
364
307
  SoundCloudComment,
365
308
  SoundCloudStreams,
366
309
  SoundCloudToken,
367
- AuthState,
368
- MutationResult,
369
310
  } from "soundcloud-api-ts-next";
370
311
  ```
371
312
 
313
+ ---
314
+
315
+ ## Requirements
316
+
317
+ - **Next.js** 13+ (App Router or Pages Router)
318
+ - **React** 18+
319
+ - **soundcloud-api-ts** installed automatically as a dependency
320
+
372
321
  ## License
373
322
 
374
323
  MIT
324
+
325
+ ## Related
326
+
327
+ - [soundcloud-api-ts](https://github.com/twin-paws/soundcloud-api-ts) — The TypeScript-first SoundCloud API client this package is built on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soundcloud-api-ts-next",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Next.js integration for soundcloud-api-ts — React hooks + secure API route handlers",
5
5
  "license": "MIT",
6
6
  "repository": {