@rocksky/sdk 0.3.0 → 0.5.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 (51) hide show
  1. package/README.md +71 -245
  2. package/dist/agent.d.ts +82 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/client.d.ts +165 -111
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/dedup.d.ts +38 -0
  7. package/dist/dedup.d.ts.map +1 -0
  8. package/dist/errors.d.ts +3 -23
  9. package/dist/errors.d.ts.map +1 -1
  10. package/dist/generated/types.d.ts +106 -8
  11. package/dist/generated/types.d.ts.map +1 -1
  12. package/dist/hash.d.ts +7 -0
  13. package/dist/hash.d.ts.map +1 -0
  14. package/dist/index.d.ts +17 -17
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6956 -1267
  17. package/dist/jetstream.d.ts +17 -0
  18. package/dist/jetstream.d.ts.map +1 -0
  19. package/package.json +15 -11
  20. package/src/agent.ts +278 -0
  21. package/src/client.ts +393 -212
  22. package/src/dedup.ts +207 -0
  23. package/src/errors.ts +6 -43
  24. package/src/generated/types.ts +115 -8
  25. package/src/hash.ts +22 -0
  26. package/src/index.ts +17 -86
  27. package/src/jetstream.ts +122 -0
  28. package/src/http.ts +0 -195
  29. package/src/namespaces/_helpers.ts +0 -27
  30. package/src/namespaces/actor.ts +0 -93
  31. package/src/namespaces/album.ts +0 -34
  32. package/src/namespaces/apikey.ts +0 -50
  33. package/src/namespaces/artist.ts +0 -68
  34. package/src/namespaces/charts.ts +0 -38
  35. package/src/namespaces/dropbox.ts +0 -53
  36. package/src/namespaces/feed.ts +0 -112
  37. package/src/namespaces/googledrive.ts +0 -41
  38. package/src/namespaces/graph.ts +0 -62
  39. package/src/namespaces/like.ts +0 -46
  40. package/src/namespaces/mirror.ts +0 -27
  41. package/src/namespaces/player.ts +0 -125
  42. package/src/namespaces/playlist.ts +0 -95
  43. package/src/namespaces/scrobble.ts +0 -41
  44. package/src/namespaces/shout.ts +0 -99
  45. package/src/namespaces/song.ts +0 -60
  46. package/src/namespaces/spotify.ts +0 -56
  47. package/src/namespaces/stats.ts +0 -27
  48. package/src/paginate.ts +0 -90
  49. package/src/pipe.ts +0 -146
  50. package/src/realtime.ts +0 -408
  51. package/src/types.ts +0 -41
package/README.md CHANGED
@@ -1,284 +1,110 @@
1
1
  # @rocksky/sdk
2
2
 
3
- A TypeScript SDK for the [Rocksky](https://rocksky.app) XRPC API.
4
-
5
- - **Type-safe** every endpoint and parameter is statically typed.
6
- - **Builder-friendly** fluent `RockskyClient.builder()` for ergonomic setup.
7
- - **Pipe-friendly** — composable async operators (`pipe`, `withRetry`, `withTimeout`, `map`, `tap`, `withFallback`, `catchError`).
8
- - **Zero-dependency** — only uses the platform `fetch` and the standard library. Works on Bun, Node 18+, Deno, browsers, Cloudflare Workers.
3
+ Official TypeScript SDK for [Rocksky](https://rocksky.app) a music scrobbling &
4
+ discovery platform on the AT Protocol. Built on
5
+ [atcute](https://github.com/mary-ext/atcute): `RockskyClient` does unauthenticated
6
+ AppView reads, and `Agent` logs in with an app password and writes
7
+ `app.rocksky.*` records to the user's PDS.
9
8
 
10
9
  ## Install
11
10
 
12
- ```bash
13
- bun add @rocksky/sdk
14
- # or
15
- npm i @rocksky/sdk
16
- ```
17
-
18
- ## Quick start
19
-
20
- ```ts
21
- import { createClient } from "@rocksky/sdk";
22
-
23
- const client = createClient();
24
-
25
- const profile = await client.actor.getProfile({ did: "did:plc:7vdlgi2bflelz7mmuxoqjfcr" });
26
- const topTracks = await client.charts.getTopTracks({ limit: 5 });
11
+ ```sh
12
+ npm install @rocksky/sdk # or: bun add @rocksky/sdk
27
13
  ```
28
14
 
29
- ## Authentication
30
-
31
- The SDK accepts a bearer token either as a string or as a (sync/async) function:
15
+ ## Quickstart
32
16
 
33
17
  ```ts
34
- import { createClient } from "@rocksky/sdk";
35
-
36
- const client = createClient({ auth: process.env.ROCKSKY_TOKEN });
37
-
38
- // or refresh on every call
39
- const client = createClient({
40
- auth: async () => loadTokenFromKeychain(),
18
+ import { RockskyClient, Agent } from "@rocksky/sdk";
19
+
20
+ // Reads unauthenticated. new RockskyClient() uses https://api.rocksky.app.
21
+ const rk = new RockskyClient();
22
+ const stats = await rk.globalStats();
23
+ const top = await rk.topTracks(10, 0);
24
+
25
+ // Writes — log in with an app password (resolves the PDS automatically).
26
+ const agent = await Agent.login("alice.bsky.social", "app-password");
27
+ const uri = await agent.scrobble({
28
+ title: "Chaser", artist: "Calibro 35",
29
+ album: "Jazzploitation", albumArtist: "Calibro 35", duration: 182320,
41
30
  });
42
31
  ```
43
32
 
44
- Endpoints that require auth throw `RockskyAuthError` when no token is configured.
45
-
46
- ## Builder
33
+ ## API
47
34
 
48
- ```ts
49
- import { RockskyClient } from "@rocksky/sdk";
50
-
51
- const client = RockskyClient.builder()
52
- .baseUrl("https://api.rocksky.app")
53
- .bearer(process.env.ROCKSKY_TOKEN!)
54
- .userAgent("my-app/1.0")
55
- .timeout(10_000)
56
- .retries(3)
57
- .retryDelay(200)
58
- .header("x-trace-id", crypto.randomUUID())
59
- .build();
60
- ```
35
+ **Reads — `RockskyClient`**: the client now covers the whole `app.rocksky.*`
36
+ read surface. Typed methods include `profile`, `scrobbles`, `songs`, `albums`,
37
+ `artists`, `topTracks`, `topArtists`, `search`, `globalStats`, `lovedSongs`,
38
+ `catalogAlbums`, `catalogArtists`, `catalogSongs`, `albumTracks`, `artistAlbums`,
39
+ `artistTracks`, `scrobbleFeed`, `scrobble` (single by uri), `follows`,
40
+ `followers`, `knownFollowers`. Raw (`unknown`-returning) detail/long-tail
41
+ methods cover the rest: `album`, `artist`, `song`, `feed`, `playlists`,
42
+ `playlist`, `stats`, `wrapped`, `scrobblesChart`, `recommendations`,
43
+ `neighbours`, shouts, and more.
61
44
 
62
- `withAuth(token)` and `withBaseUrl(url)` return a new client without mutating the original handy for per-request overrides.
45
+ Every named method is sugar over the universal escape hatch **`rk.get(nsid,
46
+ params)`**, which calls ANY read query by nsid and returns `unknown`.
63
47
 
64
- ## Pipe-style composition
48
+ **Typed date-window charts**: `topTracksInterval(limit, offset, interval)` and
49
+ `topArtistsInterval(...)` take a `DateInterval` built with the `Interval`
50
+ factories — `Interval.allTime()`, `Interval.lastDays(n)`, `Interval.lastWeeks(n)`,
51
+ `Interval.lastMonths(n)`, `Interval.lastYears(n)`, `Interval.range(start, end)`.
52
+ `topTracks` / `topArtists` remain all-time shorthands.
65
53
 
66
54
  ```ts
67
- import {
68
- createClient,
69
- map,
70
- pipe,
71
- tap,
72
- withFallback,
73
- withRetry,
74
- withTimeout,
75
- } from "@rocksky/sdk";
55
+ import { RockskyClient, Interval } from "@rocksky/sdk";
76
56
 
77
- const client = createClient();
57
+ const rk = new RockskyClient();
58
+ const monthly = await rk.topTracksInterval(10, 0, Interval.lastMonths(1));
78
59
 
79
- // Pass a thunk so withRetry can re-invoke the network call.
80
- const handle = await pipe(
81
- () => client.actor.getProfile({ did: "did:plc:7vdlgi2bflelz7mmuxoqjfcr" }),
82
- withRetry(3, { delayMs: 200 }),
83
- withTimeout(5_000),
84
- tap((p) => console.log("loaded", p.handle)),
85
- map((p) => p.displayName ?? p.handle),
86
- withFallback("anonymous"),
87
- );
60
+ // Resolve a bare title + artist into full canonical metadata.
61
+ const song = await rk.matchSong("Chaser", "Calibro 35");
88
62
  ```
89
63
 
90
- `pipe` accepts either a thunk `() => Promise<T>` (preferred — `withRetry` re-runs it) or a bare `Promise<T>` for one-shot composition.
64
+ **`matchSong(title, artist, mbId?, isrc?)`**: resolves a bare title + artist into
65
+ full canonical metadata (album, artwork, duration, MBID, ISRC, links).
91
66
 
92
- | Operator | Description |
93
- | --- | --- |
94
- | `map(fn)` | Transform the resolved value. |
95
- | `tap(fn)` | Run a side-effect; pass the value through. |
96
- | `withRetry(n, { delayMs, factor, shouldRetry })` | Retry on rejection with exponential backoff. |
97
- | `withTimeout(ms)` | Reject with `RockskyTimeoutError` if it exceeds `ms`. |
98
- | `withFallback(value \| fn)` | Recover from any error with a default. |
99
- | `catchError(fn)` | Map a thrown error to a value. |
67
+ **Auth-gated reads**: pass an optional bearer access token —
68
+ `new RockskyClient(appview, token)` and it is sent as
69
+ `Authorization: Bearer <token>`.
100
70
 
101
- ## Namespaces
71
+ **Writes — `Agent`**: two scrobble paths. `scrobble(rec)` writes full metadata
72
+ you already have; `scrobbleMatch(title, artist, album?, mbId?, isrc?)` resolves
73
+ full metadata via `matchSong` first, then writes. Plus
74
+ `createSong`/`createAlbum`/`createArtist`, `like`, `follow`,
75
+ `shout`/`replyShout`, `setNowPlaying`/`clearNowPlaying`, `delete`. Records are the
76
+ generated types from `./generated/types`.
102
77
 
103
- ```
104
- client.actor getProfile, getActorAlbums, getActorArtists, getActorSongs,
105
- getActorScrobbles, getActorLovedSongs, getActorPlaylists,
106
- getActorNeighbours, getActorCompatibility
107
- client.album getAlbum, getAlbums, getAlbumTracks
108
- client.apikey getApikeys, createApikey, updateApikey, removeApikey
109
- client.artist getArtist, getArtists, getArtistAlbums, getArtistTracks,
110
- getArtistListeners, getArtistRecentListeners
111
- client.charts getScrobblesChart, getTopArtists, getTopTracks
112
- client.dropbox getFiles, getMetadata, getTemporaryLink, downloadFile
113
- client.feed search, getFeed, getFeedGenerators, getFeedGenerator,
114
- describeFeedGenerator, getFeedSkeleton,
115
- getRecommendations, getArtistRecommendations,
116
- getAlbumRecommendations, getStories
117
- client.googledrive getFile, getFiles, downloadFile
118
- client.graph followAccount, unfollowAccount, getFollowers, getFollows,
119
- getKnownFollowers
120
- client.like likeSong, dislikeSong, likeShout, dislikeShout
121
- client.mirror getMirrorSources, putMirrorSource
122
- client.player getCurrentlyPlaying, getPlaybackQueue, play, pause, next,
123
- previous, seek, playFile, playDirectory, addItemsToQueue,
124
- addDirectoryToQueue
125
- client.playlist getPlaylists, getPlaylist, createPlaylist, removePlaylist,
126
- startPlaylist, insertDirectory, insertFiles, removeTrack
127
- client.scrobble createScrobble, getScrobble, getScrobbles
128
- client.shout createShout, replyShout, reportShout, removeShout,
129
- getShoutReplies, getProfileShouts, getTrackShouts,
130
- getArtistShouts, getAlbumShouts
131
- client.song getSong, getSongs, getSongRecentListeners, matchSong,
132
- createSong
133
- client.spotify getCurrentlyPlaying, play, pause, next, previous, seek
134
- client.stats getStats, getWrapped
135
- ```
78
+ **Identity hashes**: `songHash`, `albumHash`, `artistHash` — lowercase-hex
79
+ SHA-256, identical to the server and every other Rocksky SDK.
136
80
 
137
- Every method takes a typed parameter object and returns `Promise<T>`. Pass a generic to narrow the response type:
81
+ ## Duplicate prevention + real-time sync
138
82
 
139
- ```ts
140
- type Profile = { handle: string; did: string; displayName?: string };
141
- const me = await client.actor.getProfile<Profile>({ did: "alice.bsky.social" });
142
- ```
143
-
144
- ## Escape hatch
145
-
146
- For endpoints not yet wrapped, call `xrpc` directly:
83
+ An optional local index (embedded [classic-level](https://github.com/Level/classic-level)
84
+ LevelDB) prevents duplicate writes and stays live off the firehose:
147
85
 
148
86
  ```ts
149
- const result = await client.xrpc<MyType>(
150
- "app.rocksky.something.notWrappedYet",
151
- "GET",
152
- { params: { foo: "bar" } },
153
- );
154
- ```
87
+ import { RockskyIndex } from "@rocksky/sdk";
155
88
 
156
- ## Error handling
89
+ const idx = new RockskyIndex("./dedup");
90
+ await idx.open();
91
+ agent.useIndex(idx);
157
92
 
158
- The SDK throws four error classes all extending `RockskyError`:
159
-
160
- - `RockskyHttpError` — non-2xx response. Exposes `.status`, `.statusText`, `.url`, `.body`.
161
- - `RockskyTimeoutError` — request exceeded `timeoutMs`.
162
- - `RockskyAuthError` — endpoint requires auth but no token was provided.
163
- - `RockskyError` — base class.
164
-
165
- ```ts
166
- import { RockskyHttpError } from "@rocksky/sdk";
167
-
168
- try {
169
- await client.scrobble.getScrobble({ uri: "at://x" });
170
- } catch (err) {
171
- if (err instanceof RockskyHttpError && err.status === 404) {
172
- console.log("not found");
173
- } else {
174
- throw err;
175
- }
176
- }
93
+ const stats = await agent.syncRepo(); // backfill from the repo CAR (com.atproto.sync.getRepo)
94
+ agent.hydrateFromJetstream(); // keep it live from Jetstream (all 4 servers)
177
95
  ```
178
96
 
179
- ## Pagination
180
-
181
- `paginate()` (and `client.paginate()`) gives you a typed async iterable over any `{ limit, offset }` or `{ cursor }` endpoint.
97
+ With an index attached, the write verbs skip records that already exist (return
98
+ the existing URI) and a same-second scrobble of the same track isn't duplicated.
182
99
 
183
- ```ts
184
- import { createClient, paginate } from "@rocksky/sdk";
185
-
186
- const client = createClient();
187
-
188
- // Offset/limit — fetcher returns the items array, helper handles offset.
189
- for await (const s of paginate({
190
- fetch: ({ limit, offset }) =>
191
- client.actor.getActorScrobbles({ did, limit, offset }).then((p) => p.scrobbles ?? []),
192
- pageSize: 50,
193
- maxItems: 200,
194
- })) {
195
- console.log(s.track.title);
196
- }
197
-
198
- // Cursor-based — fetcher returns { items, cursor }.
199
- const followers = await client
200
- .paginate({
201
- fetch: async ({ limit, cursor }) => {
202
- const page = await client.graph.getFollowers({ actor, limit, cursor });
203
- return { items: page.followers, cursor: page.cursor };
204
- },
205
- pageSize: 100,
206
- })
207
- .toArray();
208
- ```
209
-
210
- Options: `pageSize`, `maxItems`, `signal` (AbortSignal). The helper stops on an empty page, a short page (offset mode), or a missing cursor (cursor mode).
211
-
212
- ## Realtime (WebSocket)
213
-
214
- The Rocksky API exposes a WebSocket endpoint at `/ws` for now-playing events and device control. The SDK ships a typed client with reconnect, ping, and a fluent builder.
215
-
216
- ```ts
217
- import { RealtimeClient, createClient } from "@rocksky/sdk";
218
-
219
- // Builder style.
220
- const rt = RealtimeClient.builder()
221
- .baseUrl("https://api.rocksky.app")
222
- .token(process.env.ROCKSKY_TOKEN!)
223
- .clientName("my-app")
224
- .pingInterval(20_000)
225
- .reconnect({ backoffMs: 1000, maxBackoffMs: 60_000 })
226
- .build();
227
-
228
- // Or — inherit baseUrl from a RockskyClient.
229
- const client = createClient({ baseUrl: "https://api.rocksky.app" });
230
- const rt2 = client.realtime({ token: process.env.ROCKSKY_TOKEN!, clientName: "my-app" });
231
-
232
- rt.on("open", () => console.log("connected"));
233
- rt.on("registered", ({ deviceId }) => console.log("device id:", deviceId));
234
- rt.on("message", ({ data, device_id }) => console.log(device_id, data));
235
- rt.on("control", (c) => console.log("control:", c));
236
- rt.on("close", ({ code, reason }) => console.log("closed", code, reason));
237
- rt.on("error", (err) => console.error(err));
238
-
239
- await rt.connect();
240
-
241
- // Broadcast a now-playing update to your devices.
242
- await rt.sendMessage({
243
- type: "track",
244
- title: "Heart of Glass",
245
- artist: "Blondie",
246
- });
247
-
248
- // Control a target device (or all of them).
249
- await rt.sendControl({ action: "play", target: "device-id-123" });
250
-
251
- await rt.close();
252
- ```
253
-
254
- Events: `open`, `close`, `error`, `registered`, `deviceRegistered`, `message`, `control`, `raw`.
255
-
256
- For tests, pass a fake `WebSocket` constructor via `.webSocket(FakeWebSocket)`.
257
-
258
- ## Types
259
-
260
- Public model types are derived from the [Rocksky lexicons](https://tangled.org/rocksky.app/rocksky/tree/main/apps/api/lexicons) and live in `src/generated/types.ts`. They are regenerated from `apps/api/lexicons/**/*.json` by running `bun run lexgen:types` at the repo root.
261
-
262
-
263
- ## Development
264
-
265
- ```bash
266
- bun install
267
- bun test
268
- bun run typecheck
269
- bun run build
270
- ```
100
+ Node ≥ 22 (global `WebSocket`/`fetch`) or Bun.
271
101
 
272
- Run individual examples:
102
+ ## Example
273
103
 
274
- ```bash
275
- bun run example:quickstart
276
- bun run example:builder
277
- bun run example:pipe
278
- bun run example:scrobble
279
- bun run example:pagination
104
+ ```sh
105
+ bun run examples/native.ts
280
106
  ```
281
107
 
282
108
  ## License
283
109
 
284
- [MIT](LICENSE) © Tsiry Sandratraina.
110
+ MIT.
@@ -0,0 +1,82 @@
1
+ import { PasswordSession } from "@atcute/password-session";
2
+ import type { AlbumRecord, ArtistRecord, ActorTrackView, ScrobbleRecord, SongRecord } from "./generated/types.js";
3
+ import type { IndexStats, RockskyIndex } from "./dedup.js";
4
+ import { type JetstreamOptions } from "./jetstream.js";
5
+ /** Input for {@link Agent.scrobble} (createdAt defaults to now). */
6
+ export type ScrobbleInput = Omit<ScrobbleRecord, "createdAt"> & {
7
+ createdAt?: string;
8
+ };
9
+ /** Input for {@link Agent.createSong}. */
10
+ export type SongInput = Omit<SongRecord, "createdAt"> & {
11
+ createdAt?: string;
12
+ };
13
+ /** Input for {@link Agent.createAlbum} (`artist` is the album artist). */
14
+ export type AlbumInput = Omit<AlbumRecord, "createdAt"> & {
15
+ createdAt?: string;
16
+ };
17
+ /** Input for {@link Agent.createArtist}. */
18
+ export type ArtistInput = Omit<ArtistRecord, "createdAt"> & {
19
+ createdAt?: string;
20
+ };
21
+ /**
22
+ * Authenticated Rocksky client: logs in with an app password and writes
23
+ * app.rocksky.* records to the user's PDS (via atcute). Attach a
24
+ * {@link RockskyIndex} with {@link Agent.useIndex} for duplicate prevention.
25
+ */
26
+ export declare class Agent {
27
+ private rpc;
28
+ readonly did: string;
29
+ readonly session: PasswordSession;
30
+ private pds;
31
+ private idx?;
32
+ private constructor();
33
+ /**
34
+ * Resolve the account's PDS, authenticate with an app password, and return an
35
+ * Agent. `identifier` is a handle or DID.
36
+ */
37
+ static login(identifier: string, password: string): Promise<Agent>;
38
+ /** Attach a local dedup index — write verbs then skip records that already exist. */
39
+ useIndex(idx: RockskyIndex): void;
40
+ /**
41
+ * Download the caller's full repository and (re)build the dedup index. Requires
42
+ * an attached index ({@link Agent.useIndex}). Full backfill; keep it current
43
+ * with {@link Agent.hydrateFromJetstream}.
44
+ */
45
+ syncRepo(): Promise<IndexStats>;
46
+ /**
47
+ * Keep the dedup index live from the Bluesky Jetstream firehose (all four
48
+ * servers at once, filtered to this DID + app.rocksky.*). Resolves when the
49
+ * options' AbortSignal fires. Requires an attached index.
50
+ */
51
+ hydrateFromJetstream(opts?: JetstreamOptions): Promise<void>;
52
+ private create;
53
+ private putRecord;
54
+ /** Delete a record by collection + rkey. */
55
+ delete(collection: string, rkey: string): Promise<void>;
56
+ /** Scrobble a play (app.rocksky.scrobble). createdAt defaults to now. */
57
+ scrobble(rec: ScrobbleInput): Promise<string>;
58
+ /** Scrobble from just a title + artist (album optional, plus optional
59
+ * `mbId`/`isrc` anchors): resolve full metadata via `matchSong`, then write.
60
+ * Matching uses the public AppView unless `appview` is given; an empty match
61
+ * falls back to a minimal record. */
62
+ scrobbleMatch(title: string, artist: string, album?: string, mbId?: string, isrc?: string, appview?: string): Promise<string>;
63
+ /** Create a canonical track record (app.rocksky.song). */
64
+ createSong(rec: SongInput): Promise<string>;
65
+ /** Create an album record (app.rocksky.album). `artist` is the album artist. */
66
+ createAlbum(rec: AlbumInput): Promise<string>;
67
+ /** Create an artist record (app.rocksky.artist). */
68
+ createArtist(rec: ArtistInput): Promise<string>;
69
+ /** Like a record by strong reference (uri + cid). Returns the like URI. */
70
+ like(uri: string, cid: string): Promise<string>;
71
+ /** Follow an account by DID. Returns the follow URI. */
72
+ follow(did: string): Promise<string>;
73
+ /** Post a shout on a subject. Returns the shout URI. */
74
+ shout(subjectUri: string, subjectCid: string, message: string): Promise<string>;
75
+ /** Reply to a shout, with a parent strong-ref. */
76
+ replyShout(subjectUri: string, subjectCid: string, parentUri: string, parentCid: string, message: string): Promise<string>;
77
+ /** Upsert the actor's now-playing status singleton (rkey "self"). */
78
+ setNowPlaying(track: ActorTrackView): Promise<string>;
79
+ /** Delete the actor's now-playing status singleton. */
80
+ clearNowPlaying(): Promise<void>;
81
+ }
82
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}