@rocksky/sdk 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -109,10 +109,12 @@ SHA-256, identical to the server and every other Rocksky SDK.
109
109
  ## Duplicate prevention + real-time sync
110
110
 
111
111
  An optional local index (embedded [classic-level](https://github.com/Level/classic-level)
112
- LevelDB) prevents duplicate writes and stays live off the firehose:
112
+ LevelDB) prevents duplicate writes and stays live off the firehose. It is
113
+ Node-only, so it lives on its own subpath — the main `@rocksky/sdk` entry stays
114
+ browser-safe:
113
115
 
114
116
  ```ts
115
- import { RockskyIndex } from "@rocksky/sdk";
117
+ import { RockskyIndex } from "@rocksky/sdk/dedup";
116
118
 
117
119
  const idx = new RockskyIndex("./dedup");
118
120
  await idx.open();
package/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Filter } from "./filter.js";
2
2
  import { RockskyLibrary } from "./library.js";
3
- import type { ActorProfileViewBasic, ActorProfileViewDetailed, AlbumViewBasic, AlbumViewDetailed, ArtistViewBasic, ArtistViewDetailed, ChartsView, FeedGeneratorsView, FeedRecommendationsView, FeedRecommendedAlbumsView, FeedRecommendedArtistsView, FeedSearchResultsView, FeedStoriesView, FeedView, GetActorCompatibilityOutput, GetActorNeighboursOutput, GetActorPlaylistsOutput, GetAlbumShoutsOutput, GetApikeysOutput, GetArtistListenersOutput, GetArtistRecentListenersOutput, GetArtistShoutsOutput, GetFeedGeneratorOutput, GetMirrorSourcesOutput, GetProfileShoutsOutput, GetShoutRepliesOutput, GetSongRecentListenersOutput, GetTrackShoutsOutput, GetUnreadCountOutput, ListNotificationsOutput, PlayerCurrentlyPlayingViewDetailed, PlayerPlaybackQueueViewDetailed, PlaylistGetPlaylistsOutput, PlaylistViewDetailed, RockboxSettingsView, ScrobbleViewBasic, SongViewBasic, SongViewDetailed, StatsGlobalStatsView, StatsView, StatsWrappedView, UpdateSeenOutput } from "./generated/types.js";
3
+ import type { ActorProfileViewBasic, ActorProfileViewDetailed, AlbumViewBasic, AlbumViewDetailed, ArtistViewBasic, ArtistViewDetailed, ChartsView, CreateScrobbleInput, FollowAccountOutput, FeedGeneratorsView, FeedRecommendationsView, FeedRecommendedAlbumsView, FeedRecommendedArtistsView, FeedSearchResultsView, FeedStoriesView, FeedView, GetActorCompatibilityOutput, GetActorNeighboursOutput, GetActorPlaylistsOutput, GetAlbumShoutsOutput, GetApikeysOutput, GetArtistListenersOutput, GetArtistRecentListenersOutput, GetArtistShoutsOutput, GetFeedGeneratorOutput, GetMirrorSourcesOutput, GetProfileShoutsOutput, GetShoutRepliesOutput, GetSongRecentListenersOutput, GetTrackShoutsOutput, GetUnreadCountOutput, ListNotificationsOutput, MirrorSourceView, PlayerCurrentlyPlayingViewDetailed, PlayerPlaybackQueueViewDetailed, PlaylistGetPlaylistsOutput, PlaylistViewDetailed, PutAudioSettingsInput, PutMirrorSourceInput, RockboxSettingsView, ScrobbleViewBasic, SongViewBasic, SongViewDetailed, StatsGlobalStatsView, StatsView, StatsWrappedView, UnfollowAccountOutput, UpdateSeenOutput } from "./generated/types.js";
4
4
  /**
5
5
  * A typed date window for the `top*` charts. Build one with the {@link Interval}
6
6
  * factories; `range` bounds are RFC-3339 datetimes.
@@ -51,6 +51,13 @@ export declare class RockskyClient {
51
51
  /** Call any AppView read query by nsid; returns the raw JSON response. Every
52
52
  * method here is sugar over this — use it for queries without a wrapper. */
53
53
  get(nsid: string, params?: Record<string, unknown>): Promise<unknown>;
54
+ /** Call any AppView procedure by nsid. `params` ride the query string (some
55
+ * procedures take their arguments there), `body` is the JSON input — omitted
56
+ * entirely when `undefined`. Escape hatch for procedures without a wrapper. */
57
+ post<T = unknown>(nsid: string, opts?: {
58
+ params?: Record<string, unknown>;
59
+ body?: unknown;
60
+ }): Promise<T>;
54
61
  /** An actor's most-played songs. */
55
62
  songs(actor: string, limit?: number, offset?: number): Promise<SongViewBasic[]>;
56
63
  /** An actor's loved (liked) songs. */
@@ -188,5 +195,17 @@ export declare class RockskyClient {
188
195
  * to mark **all** of the viewer's notifications. Returns the number remaining
189
196
  * unread. */
190
197
  updateSeen(ids?: string[]): Promise<UpdateSeenOutput>;
198
+ /** Follow an account by DID or handle (`app.rocksky.graph.followAccount`). Auth required. */
199
+ followAccount(account: string): Promise<FollowAccountOutput>;
200
+ /** Unfollow an account by DID or handle (`app.rocksky.graph.unfollowAccount`). Auth required. */
201
+ unfollowAccount(account: string): Promise<UnfollowAccountOutput>;
202
+ /** Submit a scrobble through the AppView (`app.rocksky.scrobble.createScrobble`).
203
+ * Auth required. For direct-to-PDS scrobbling use {@link Agent} instead. */
204
+ createScrobble(input: CreateScrobbleInput): Promise<ScrobbleViewBasic>;
205
+ /** Create or update a mirror source (`app.rocksky.mirror.putMirrorSource`). Auth required. */
206
+ putMirrorSource(input: PutMirrorSourceInput): Promise<MirrorSourceView>;
207
+ /** Patch the viewer's Rockbox audio settings (`app.rocksky.rockbox.putAudioSettings`).
208
+ * Auth required. */
209
+ putAudioSettings(input: PutAudioSettingsInput): Promise<RockboxSettingsView>;
191
210
  }
192
211
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/dedup.js ADDED
@@ -0,0 +1,215 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __returnValue = (v) => v;
3
+ function __exportSetter(name, newValue) {
4
+ this[name] = __returnValue.bind(null, newValue);
5
+ }
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true,
11
+ configurable: true,
12
+ set: __exportSetter.bind(all, name)
13
+ });
14
+ };
15
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
16
+
17
+ // src/dedup.ts
18
+ import { fromUint8Array } from "@atcute/car";
19
+ import { decode as cborDecode, fromBytes, isBytes, isCidLink } from "@atcute/cbor";
20
+ import { toString as cidToString } from "@atcute/cid";
21
+ import { ClassicLevel } from "classic-level";
22
+
23
+ // src/hash.ts
24
+ import { sha256 } from "@noble/hashes/sha2.js";
25
+ import { bytesToHex } from "@noble/hashes/utils.js";
26
+ function sha256Lower(s) {
27
+ return bytesToHex(sha256(new TextEncoder().encode(s.toLowerCase())));
28
+ }
29
+ function songHash(title, artist, album) {
30
+ return sha256Lower(`${title} - ${artist} - ${album}`);
31
+ }
32
+ function albumHash(album, albumArtist) {
33
+ return sha256Lower(`${album} - ${albumArtist}`);
34
+ }
35
+ function artistHash(albumArtist) {
36
+ return sha256Lower(albumArtist);
37
+ }
38
+
39
+ // src/dedup.ts
40
+ var C_ARTIST = "app.rocksky.artist";
41
+ var C_ALBUM = "app.rocksky.album";
42
+ var C_SONG = "app.rocksky.song";
43
+ var C_SCROBBLE = "app.rocksky.scrobble";
44
+ var SEP = "\x00";
45
+ function totalIndexed(s) {
46
+ return s.artists + s.albums + s.songs + s.scrobbles;
47
+ }
48
+ function identKey(did, col, hash) {
49
+ return `${did}${SEP}${col}${SEP}${hash}`;
50
+ }
51
+ function scrobbleKey(did, songH, secs) {
52
+ return `${did}${SEP}${C_SCROBBLE}${SEP}${songH}${SEP}${secs}`;
53
+ }
54
+ function primaryFor(did, col, rec) {
55
+ const s = (k) => typeof rec[k] === "string" ? rec[k] : "";
56
+ switch (col) {
57
+ case C_ARTIST:
58
+ return s("name") ? identKey(did, C_ARTIST, artistHash(s("name"))) : undefined;
59
+ case C_ALBUM:
60
+ return s("title") && s("artist") ? identKey(did, C_ALBUM, albumHash(s("title"), s("artist"))) : undefined;
61
+ case C_SONG:
62
+ return s("title") && s("artist") && s("album") ? identKey(did, C_SONG, songHash(s("title"), s("artist"), s("album"))) : undefined;
63
+ case C_SCROBBLE: {
64
+ if (s("title") && s("artist") && s("album") && s("createdAt")) {
65
+ const secs = Math.floor(Date.parse(s("createdAt")) / 1000);
66
+ if (!Number.isNaN(secs))
67
+ return scrobbleKey(did, songHash(s("title"), s("artist"), s("album")), secs);
68
+ }
69
+ return;
70
+ }
71
+ }
72
+ return;
73
+ }
74
+
75
+ class RockskyIndex {
76
+ db;
77
+ constructor(path) {
78
+ this.db = new ClassicLevel(path, { keyEncoding: "utf8", valueEncoding: "utf8" });
79
+ }
80
+ open() {
81
+ return this.db.open();
82
+ }
83
+ close() {
84
+ return this.db.close();
85
+ }
86
+ async get(key) {
87
+ try {
88
+ return await this.db.get(key);
89
+ } catch {
90
+ return;
91
+ }
92
+ }
93
+ songUri(did, title, artist, album) {
94
+ return this.get(identKey(did, C_SONG, songHash(title, artist, album)));
95
+ }
96
+ albumUri(did, album, albumArtist) {
97
+ return this.get(identKey(did, C_ALBUM, albumHash(album, albumArtist)));
98
+ }
99
+ artistUri(did, albumArtist) {
100
+ return this.get(identKey(did, C_ARTIST, artistHash(albumArtist)));
101
+ }
102
+ scrobbleUri(did, title, artist, album, secs) {
103
+ return this.get(scrobbleKey(did, songHash(title, artist, album), secs));
104
+ }
105
+ async putPrimary(did, col, primary, uri) {
106
+ const rkey = uri.slice(uri.lastIndexOf("/") + 1);
107
+ await this.db.batch([
108
+ { type: "put", key: primary, value: uri },
109
+ { type: "put", key: `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`, value: primary }
110
+ ]);
111
+ }
112
+ recordSong(did, title, artist, album, uri) {
113
+ return this.putPrimary(did, C_SONG, identKey(did, C_SONG, songHash(title, artist, album)), uri);
114
+ }
115
+ recordAlbum(did, album, albumArtist, uri) {
116
+ return this.putPrimary(did, C_ALBUM, identKey(did, C_ALBUM, albumHash(album, albumArtist)), uri);
117
+ }
118
+ recordArtist(did, albumArtist, uri) {
119
+ return this.putPrimary(did, C_ARTIST, identKey(did, C_ARTIST, artistHash(albumArtist)), uri);
120
+ }
121
+ recordScrobble(did, title, artist, album, secs, uri) {
122
+ return this.putPrimary(did, C_SCROBBLE, scrobbleKey(did, songHash(title, artist, album), secs), uri);
123
+ }
124
+ cursor(did) {
125
+ return this.get(`${SEP}meta${SEP}cursor${SEP}${did}`).then((v) => v ? Number(v) : 0);
126
+ }
127
+ async setCursor(did, timeUS) {
128
+ await this.db.put(`${SEP}meta${SEP}cursor${SEP}${did}`, String(timeUS));
129
+ }
130
+ async indexCar(did, car) {
131
+ const reader = fromUint8Array(car);
132
+ const blocks = new Map;
133
+ for (const entry of reader)
134
+ blocks.set(cidToString(entry.cid), entry.bytes);
135
+ const root = reader.roots[0];
136
+ if (!root)
137
+ throw new Error("CAR has no root");
138
+ const commit = cborDecode(blocks.get(root.$link));
139
+ const stats = { artists: 0, albums: 0, songs: 0, scrobbles: 0 };
140
+ const ops = [];
141
+ for (const [path, valueLink] of walkMst(blocks, commit.data.$link)) {
142
+ const slash = path.indexOf("/");
143
+ if (slash < 0)
144
+ continue;
145
+ const col = path.slice(0, slash);
146
+ const rkey = path.slice(slash + 1);
147
+ if (col !== C_ARTIST && col !== C_ALBUM && col !== C_SONG && col !== C_SCROBBLE)
148
+ continue;
149
+ const recBytes = blocks.get(valueLink);
150
+ if (!recBytes)
151
+ continue;
152
+ const rec = cborDecode(recBytes);
153
+ const primary = primaryFor(did, col, rec);
154
+ if (!primary)
155
+ continue;
156
+ const uri = `at://${did}/${col}/${rkey}`;
157
+ ops.push({ type: "put", key: primary, value: uri });
158
+ ops.push({ type: "put", key: `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`, value: primary });
159
+ if (col === C_ARTIST)
160
+ stats.artists++;
161
+ else if (col === C_ALBUM)
162
+ stats.albums++;
163
+ else if (col === C_SONG)
164
+ stats.songs++;
165
+ else
166
+ stats.scrobbles++;
167
+ }
168
+ if (commit.rev)
169
+ ops.push({ type: "put", key: `${SEP}meta${SEP}rev${SEP}${did}`, value: commit.rev });
170
+ await this.db.batch(ops);
171
+ return stats;
172
+ }
173
+ async applyCommit(did, col, operation, rkey, record) {
174
+ if (operation === "create" || operation === "update") {
175
+ if (!record)
176
+ return;
177
+ const primary = primaryFor(did, col, record);
178
+ if (!primary)
179
+ return;
180
+ await this.putPrimary(did, col, primary, `at://${did}/${col}/${rkey}`);
181
+ } else if (operation === "delete") {
182
+ const rk = `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`;
183
+ const primary = await this.get(rk);
184
+ if (primary) {
185
+ await this.db.batch([
186
+ { type: "del", key: primary },
187
+ { type: "del", key: rk }
188
+ ]);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ function* walkMst(blocks, cid) {
194
+ const raw = blocks.get(cid);
195
+ if (!raw)
196
+ return;
197
+ const node = cborDecode(raw);
198
+ if (node.l && isCidLink(node.l))
199
+ yield* walkMst(blocks, node.l.$link);
200
+ let last = new Uint8Array(0);
201
+ for (const e of node.e) {
202
+ const suffix = isBytes(e.k) ? fromBytes(e.k) : e.k;
203
+ const key = new Uint8Array(e.p + suffix.length);
204
+ key.set(last.subarray(0, e.p), 0);
205
+ key.set(suffix, e.p);
206
+ yield [new TextDecoder().decode(key), e.v.$link];
207
+ last = key;
208
+ if (e.t && isCidLink(e.t))
209
+ yield* walkMst(blocks, e.t.$link);
210
+ }
211
+ }
212
+ export {
213
+ RockskyIndex,
214
+ totalIndexed
215
+ };
package/dist/errors.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- /** Error thrown when a Rocksky XRPC call returns a non-2xx `{ error, message }`. */
2
1
  export declare class RockskyError extends Error {
2
+ /** The XRPC error code (e.g. "InvalidRequest", "AuthMissing"), when the
3
+ * server sent one. */
3
4
  readonly kind?: string;
4
- constructor(payload: unknown);
5
+ /** The HTTP status of the failed response, when known. */
6
+ readonly status?: number;
7
+ constructor(payload: unknown, status?: number);
5
8
  }
6
9
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBACX,OAAO,EAAE,OAAO;CAM7B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,YAAa,SAAQ,KAAK;IACrC;0BACsB;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM;CAO9C"}
@@ -1 +1 @@
1
- {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAQA,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEtD"}
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAUA,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEtD"}
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export { RockskyClient, DEFAULT_APPVIEW, Interval } from "./client.js";
12
12
  export type { DateInterval } from "./client.js";
13
13
  export { RockskyLibrary } from "./library.js";
14
14
  export { Agent, MAX_SAFE_WRITES_PER_HOUR, DEFAULT_MATCH_SONG_PER_HOUR, type ScrobbleInput, type ScrobbleMatchInput, type SongInput, type AlbumInput, type ArtistInput, type RateLimitOptions, type RateLimitState, } from "./agent.js";
15
- export { RockskyIndex, totalIndexed, type IndexStats } from "./dedup.js";
15
+ export type { IndexStats, RockskyIndex } from "./dedup.js";
16
16
  export { runJetstream, DEFAULT_JETSTREAM_SERVERS, type JetstreamOptions } from "./jetstream.js";
17
17
  export { RemotePlayer, DEFAULT_REMOTE_WS, type RemotePlayerOptions, type RemotePlayerHandlers, type RemoteNowPlaying, type RemoteQueueItem, type EnqueueCommand, } from "./remote-player.js";
18
18
  export { RemoteController, type RemoteControllerOptions, type RemoteControllerEvents, type RemoteDevice, type RemoteStatus, } from "./remote-controller.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,KAAK,EACL,wBAAwB,EACxB,2BAA2B,EAC3B,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,yBAAyB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,MAAM,EACN,UAAU,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,mBAAmB,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,KAAK,EACL,wBAAwB,EACxB,2BAA2B,EAC3B,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,yBAAyB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,MAAM,EACN,UAAU,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,mBAAmB,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -19,11 +19,13 @@ var RockskyError;
19
19
  var init_errors = __esm(() => {
20
20
  RockskyError = class RockskyError extends Error {
21
21
  kind;
22
- constructor(payload) {
22
+ status;
23
+ constructor(payload, status) {
23
24
  const p = payload;
24
25
  super(p?.message || p?.error || "rocksky request failed");
25
26
  this.name = "RockskyError";
26
27
  this.kind = p?.error;
28
+ this.status = status;
27
29
  }
28
30
  };
29
31
  });
@@ -42,7 +44,7 @@ class RockskyLibrary {
42
44
  }
43
45
  const res = await this.rpc.get(nsid, { params: clean });
44
46
  if (!res.ok)
45
- throw new RockskyError(res.data);
47
+ throw new RockskyError(res.data, res.status);
46
48
  return res.data;
47
49
  }
48
50
  async procedure(nsid, input) {
@@ -53,7 +55,7 @@ class RockskyLibrary {
53
55
  }
54
56
  const res = await this.rpc.post(nsid, { input: clean });
55
57
  if (!res.ok)
56
- throw new RockskyError(res.data);
58
+ throw new RockskyError(res.data, res.status);
57
59
  return res.data;
58
60
  }
59
61
  ping() {
@@ -230,7 +232,7 @@ class RockskyClient {
230
232
  }
231
233
  const res = await this.rpc.get(nsid, { params: clean });
232
234
  if (!res.ok)
233
- throw new RockskyError(res.data);
235
+ throw new RockskyError(res.data, res.status);
234
236
  return res.data;
235
237
  }
236
238
  profile(actor) {
@@ -247,6 +249,20 @@ class RockskyClient {
247
249
  get(nsid, params = {}) {
248
250
  return this.query(nsid, params);
249
251
  }
252
+ async post(nsid, opts = {}) {
253
+ const clean = {};
254
+ for (const [k, v] of Object.entries(opts.params ?? {})) {
255
+ if (v !== undefined && v !== "")
256
+ clean[k] = v;
257
+ }
258
+ const call = { params: clean };
259
+ if (opts.body !== undefined)
260
+ call.input = opts.body;
261
+ const res = await this.rpc.post(nsid, call);
262
+ if (!res.ok)
263
+ throw new RockskyError(res.data, res.status);
264
+ return res.data;
265
+ }
250
266
  async songs(actor, limit = 50, offset = 0) {
251
267
  const out = await this.query("app.rocksky.actor.getActorSongs", {
252
268
  did: actor,
@@ -489,9 +505,24 @@ class RockskyClient {
489
505
  input: ids && ids.length ? { ids } : {}
490
506
  });
491
507
  if (!res.ok)
492
- throw new RockskyError(res.data);
508
+ throw new RockskyError(res.data, res.status);
493
509
  return res.data;
494
510
  }
511
+ followAccount(account) {
512
+ return this.post("app.rocksky.graph.followAccount", { params: { account } });
513
+ }
514
+ unfollowAccount(account) {
515
+ return this.post("app.rocksky.graph.unfollowAccount", { params: { account } });
516
+ }
517
+ createScrobble(input) {
518
+ return this.post("app.rocksky.scrobble.createScrobble", { body: input });
519
+ }
520
+ putMirrorSource(input) {
521
+ return this.post("app.rocksky.mirror.putMirrorSource", { body: input });
522
+ }
523
+ putAudioSettings(input) {
524
+ return this.post("app.rocksky.rockbox.putAudioSettings", { body: input });
525
+ }
495
526
  }
496
527
  var Interval, DEFAULT_APPVIEW = "https://api.rocksky.app";
497
528
  var init_client = __esm(() => {
@@ -745,7 +776,7 @@ class Agent {
745
776
  input: { repo: this.did, collection, record: { ...record, $type: collection } }
746
777
  });
747
778
  if (!res.ok)
748
- throw new RockskyError(res.data);
779
+ throw new RockskyError(res.data, res.status);
749
780
  return res.data.uri;
750
781
  }
751
782
  async putRecord(collection, rkey, record) {
@@ -754,7 +785,7 @@ class Agent {
754
785
  input: { repo: this.did, collection, rkey, record: { ...record, $type: collection } }
755
786
  });
756
787
  if (!res.ok)
757
- throw new RockskyError(res.data);
788
+ throw new RockskyError(res.data, res.status);
758
789
  return res.data.uri;
759
790
  }
760
791
  async delete(collection, rkey) {
@@ -763,7 +794,7 @@ class Agent {
763
794
  input: { repo: this.did, collection, rkey }
764
795
  });
765
796
  if (!res.ok)
766
- throw new RockskyError(res.data);
797
+ throw new RockskyError(res.data, res.status);
767
798
  }
768
799
  async scrobble(rec) {
769
800
  const record = { ...rec, createdAt: rec.createdAt || nowISO() };
@@ -904,200 +935,6 @@ class Agent {
904
935
  return this.delete(C_STATUS, "self");
905
936
  }
906
937
  }
907
- // src/dedup.ts
908
- import { fromUint8Array } from "@atcute/car";
909
- import { decode as cborDecode, fromBytes, isBytes, isCidLink } from "@atcute/cbor";
910
- import { toString as cidToString } from "@atcute/cid";
911
- import { ClassicLevel } from "classic-level";
912
-
913
- // src/hash.ts
914
- import { createHash } from "node:crypto";
915
- function sha256Lower(s) {
916
- return createHash("sha256").update(s.toLowerCase()).digest("hex");
917
- }
918
- function songHash(title, artist, album) {
919
- return sha256Lower(`${title} - ${artist} - ${album}`);
920
- }
921
- function albumHash(album, albumArtist) {
922
- return sha256Lower(`${album} - ${albumArtist}`);
923
- }
924
- function artistHash(albumArtist) {
925
- return sha256Lower(albumArtist);
926
- }
927
-
928
- // src/dedup.ts
929
- var C_ARTIST2 = "app.rocksky.artist";
930
- var C_ALBUM2 = "app.rocksky.album";
931
- var C_SONG2 = "app.rocksky.song";
932
- var C_SCROBBLE2 = "app.rocksky.scrobble";
933
- var SEP = "\x00";
934
- function totalIndexed(s) {
935
- return s.artists + s.albums + s.songs + s.scrobbles;
936
- }
937
- function identKey(did, col, hash) {
938
- return `${did}${SEP}${col}${SEP}${hash}`;
939
- }
940
- function scrobbleKey(did, songH, secs) {
941
- return `${did}${SEP}${C_SCROBBLE2}${SEP}${songH}${SEP}${secs}`;
942
- }
943
- function primaryFor(did, col, rec) {
944
- const s = (k) => typeof rec[k] === "string" ? rec[k] : "";
945
- switch (col) {
946
- case C_ARTIST2:
947
- return s("name") ? identKey(did, C_ARTIST2, artistHash(s("name"))) : undefined;
948
- case C_ALBUM2:
949
- return s("title") && s("artist") ? identKey(did, C_ALBUM2, albumHash(s("title"), s("artist"))) : undefined;
950
- case C_SONG2:
951
- return s("title") && s("artist") && s("album") ? identKey(did, C_SONG2, songHash(s("title"), s("artist"), s("album"))) : undefined;
952
- case C_SCROBBLE2: {
953
- if (s("title") && s("artist") && s("album") && s("createdAt")) {
954
- const secs = Math.floor(Date.parse(s("createdAt")) / 1000);
955
- if (!Number.isNaN(secs))
956
- return scrobbleKey(did, songHash(s("title"), s("artist"), s("album")), secs);
957
- }
958
- return;
959
- }
960
- }
961
- return;
962
- }
963
-
964
- class RockskyIndex {
965
- db;
966
- constructor(path) {
967
- this.db = new ClassicLevel(path, { keyEncoding: "utf8", valueEncoding: "utf8" });
968
- }
969
- open() {
970
- return this.db.open();
971
- }
972
- close() {
973
- return this.db.close();
974
- }
975
- async get(key) {
976
- try {
977
- return await this.db.get(key);
978
- } catch {
979
- return;
980
- }
981
- }
982
- songUri(did, title, artist, album) {
983
- return this.get(identKey(did, C_SONG2, songHash(title, artist, album)));
984
- }
985
- albumUri(did, album, albumArtist) {
986
- return this.get(identKey(did, C_ALBUM2, albumHash(album, albumArtist)));
987
- }
988
- artistUri(did, albumArtist) {
989
- return this.get(identKey(did, C_ARTIST2, artistHash(albumArtist)));
990
- }
991
- scrobbleUri(did, title, artist, album, secs) {
992
- return this.get(scrobbleKey(did, songHash(title, artist, album), secs));
993
- }
994
- async putPrimary(did, col, primary, uri) {
995
- const rkey = uri.slice(uri.lastIndexOf("/") + 1);
996
- await this.db.batch([
997
- { type: "put", key: primary, value: uri },
998
- { type: "put", key: `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`, value: primary }
999
- ]);
1000
- }
1001
- recordSong(did, title, artist, album, uri) {
1002
- return this.putPrimary(did, C_SONG2, identKey(did, C_SONG2, songHash(title, artist, album)), uri);
1003
- }
1004
- recordAlbum(did, album, albumArtist, uri) {
1005
- return this.putPrimary(did, C_ALBUM2, identKey(did, C_ALBUM2, albumHash(album, albumArtist)), uri);
1006
- }
1007
- recordArtist(did, albumArtist, uri) {
1008
- return this.putPrimary(did, C_ARTIST2, identKey(did, C_ARTIST2, artistHash(albumArtist)), uri);
1009
- }
1010
- recordScrobble(did, title, artist, album, secs, uri) {
1011
- return this.putPrimary(did, C_SCROBBLE2, scrobbleKey(did, songHash(title, artist, album), secs), uri);
1012
- }
1013
- cursor(did) {
1014
- return this.get(`${SEP}meta${SEP}cursor${SEP}${did}`).then((v) => v ? Number(v) : 0);
1015
- }
1016
- async setCursor(did, timeUS) {
1017
- await this.db.put(`${SEP}meta${SEP}cursor${SEP}${did}`, String(timeUS));
1018
- }
1019
- async indexCar(did, car) {
1020
- const reader = fromUint8Array(car);
1021
- const blocks = new Map;
1022
- for (const entry of reader)
1023
- blocks.set(cidToString(entry.cid), entry.bytes);
1024
- const root = reader.roots[0];
1025
- if (!root)
1026
- throw new Error("CAR has no root");
1027
- const commit = cborDecode(blocks.get(root.$link));
1028
- const stats = { artists: 0, albums: 0, songs: 0, scrobbles: 0 };
1029
- const ops = [];
1030
- for (const [path, valueLink] of walkMst(blocks, commit.data.$link)) {
1031
- const slash = path.indexOf("/");
1032
- if (slash < 0)
1033
- continue;
1034
- const col = path.slice(0, slash);
1035
- const rkey = path.slice(slash + 1);
1036
- if (col !== C_ARTIST2 && col !== C_ALBUM2 && col !== C_SONG2 && col !== C_SCROBBLE2)
1037
- continue;
1038
- const recBytes = blocks.get(valueLink);
1039
- if (!recBytes)
1040
- continue;
1041
- const rec = cborDecode(recBytes);
1042
- const primary = primaryFor(did, col, rec);
1043
- if (!primary)
1044
- continue;
1045
- const uri = `at://${did}/${col}/${rkey}`;
1046
- ops.push({ type: "put", key: primary, value: uri });
1047
- ops.push({ type: "put", key: `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`, value: primary });
1048
- if (col === C_ARTIST2)
1049
- stats.artists++;
1050
- else if (col === C_ALBUM2)
1051
- stats.albums++;
1052
- else if (col === C_SONG2)
1053
- stats.songs++;
1054
- else
1055
- stats.scrobbles++;
1056
- }
1057
- if (commit.rev)
1058
- ops.push({ type: "put", key: `${SEP}meta${SEP}rev${SEP}${did}`, value: commit.rev });
1059
- await this.db.batch(ops);
1060
- return stats;
1061
- }
1062
- async applyCommit(did, col, operation, rkey, record) {
1063
- if (operation === "create" || operation === "update") {
1064
- if (!record)
1065
- return;
1066
- const primary = primaryFor(did, col, record);
1067
- if (!primary)
1068
- return;
1069
- await this.putPrimary(did, col, primary, `at://${did}/${col}/${rkey}`);
1070
- } else if (operation === "delete") {
1071
- const rk = `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`;
1072
- const primary = await this.get(rk);
1073
- if (primary) {
1074
- await this.db.batch([
1075
- { type: "del", key: primary },
1076
- { type: "del", key: rk }
1077
- ]);
1078
- }
1079
- }
1080
- }
1081
- }
1082
- function* walkMst(blocks, cid) {
1083
- const raw = blocks.get(cid);
1084
- if (!raw)
1085
- return;
1086
- const node = cborDecode(raw);
1087
- if (node.l && isCidLink(node.l))
1088
- yield* walkMst(blocks, node.l.$link);
1089
- let last = new Uint8Array(0);
1090
- for (const e of node.e) {
1091
- const suffix = isBytes(e.k) ? fromBytes(e.k) : e.k;
1092
- const key = new Uint8Array(e.p + suffix.length);
1093
- key.set(last.subarray(0, e.p), 0);
1094
- key.set(suffix, e.p);
1095
- yield [new TextDecoder().decode(key), e.v.$link];
1096
- last = key;
1097
- if (e.t && isCidLink(e.t))
1098
- yield* walkMst(blocks, e.t.$link);
1099
- }
1100
- }
1101
938
  // src/remote-player.ts
1102
939
  var DEFAULT_REMOTE_WS = "wss://api.rocksky.app/ws";
1103
940
 
@@ -1742,6 +1579,21 @@ var ScrobbleFields = {
1742
1579
  artistName: "artist.name",
1743
1580
  artistGenres: "artist.genres"
1744
1581
  };
1582
+ // src/hash.ts
1583
+ import { sha256 } from "@noble/hashes/sha2.js";
1584
+ import { bytesToHex } from "@noble/hashes/utils.js";
1585
+ function sha256Lower(s) {
1586
+ return bytesToHex(sha256(new TextEncoder().encode(s.toLowerCase())));
1587
+ }
1588
+ function songHash(title, artist, album) {
1589
+ return sha256Lower(`${title} - ${artist} - ${album}`);
1590
+ }
1591
+ function albumHash(album, albumArtist) {
1592
+ return sha256Lower(`${album} - ${albumArtist}`);
1593
+ }
1594
+ function artistHash(albumArtist) {
1595
+ return sha256Lower(albumArtist);
1596
+ }
1745
1597
 
1746
1598
  // src/index.ts
1747
1599
  init_errors();
@@ -1760,13 +1612,11 @@ export {
1760
1612
  RemotePlayer,
1761
1613
  RockskyClient,
1762
1614
  RockskyError,
1763
- RockskyIndex,
1764
1615
  RockskyLibrary,
1765
1616
  ScrobbleFields,
1766
1617
  SongFields,
1767
1618
  albumHash,
1768
1619
  artistHash,
1769
1620
  runJetstream,
1770
- songHash,
1771
- totalIndexed
1621
+ songHash
1772
1622
  };
@@ -1 +1 @@
1
- {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rocksky/sdk",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "TypeScript SDK for Rocksky — built on atcute: AppView reads, AT Protocol PDS writes (scrobble, like, follow, shout), a local dedup index, and Jetstream real-time sync.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,6 +26,11 @@
26
26
  "types": "./dist/remote.d.ts",
27
27
  "import": "./dist/remote.js",
28
28
  "default": "./dist/remote.js"
29
+ },
30
+ "./dedup": {
31
+ "types": "./dist/dedup.d.ts",
32
+ "import": "./dist/dedup.js",
33
+ "default": "./dist/dedup.js"
29
34
  }
30
35
  },
31
36
  "files": [
@@ -34,7 +39,7 @@
34
39
  "README.md"
35
40
  ],
36
41
  "scripts": {
37
- "build": "bun build ./src/index.ts ./src/remote.ts --outdir ./dist --target node --format esm --packages external && bun run build:types",
42
+ "build": "bun build ./src/index.ts ./src/remote.ts ./src/dedup.ts --outdir ./dist --target node --format esm --packages external && bun run build:types",
38
43
  "build:types": "tsc -p tsconfig.build.json",
39
44
  "typecheck": "tsc --noEmit",
40
45
  "test": "bun test",
@@ -69,6 +74,7 @@
69
74
  "@atcute/identity": "^2.0.2",
70
75
  "@atcute/identity-resolver": "^2.0.1",
71
76
  "@atcute/password-session": "^1.0.2",
77
+ "@noble/hashes": "^2.3.0",
72
78
  "classic-level": "^3.0.0"
73
79
  }
74
80
  }
package/src/agent.ts CHANGED
@@ -325,7 +325,7 @@ export class Agent {
325
325
  const res = await this.rpc.post("com.atproto.repo.createRecord" as never, {
326
326
  input: { repo: this.did, collection, record: { ...record, $type: collection } },
327
327
  } as never);
328
- if (!res.ok) throw new RockskyError(res.data);
328
+ if (!res.ok) throw new RockskyError(res.data, res.status);
329
329
  return (res.data as { uri: string }).uri;
330
330
  }
331
331
 
@@ -334,7 +334,7 @@ export class Agent {
334
334
  const res = await this.rpc.post("com.atproto.repo.putRecord" as never, {
335
335
  input: { repo: this.did, collection, rkey, record: { ...record, $type: collection } },
336
336
  } as never);
337
- if (!res.ok) throw new RockskyError(res.data);
337
+ if (!res.ok) throw new RockskyError(res.data, res.status);
338
338
  return (res.data as { uri: string }).uri;
339
339
  }
340
340
 
@@ -344,7 +344,7 @@ export class Agent {
344
344
  const res = await this.rpc.post("com.atproto.repo.deleteRecord" as never, {
345
345
  input: { repo: this.did, collection, rkey },
346
346
  } as never);
347
- if (!res.ok) throw new RockskyError(res.data);
347
+ if (!res.ok) throw new RockskyError(res.data, res.status);
348
348
  }
349
349
 
350
350
  /**
package/src/client.ts CHANGED
@@ -11,6 +11,8 @@ import type {
11
11
  ArtistViewBasic,
12
12
  ArtistViewDetailed,
13
13
  ChartsView,
14
+ CreateScrobbleInput,
15
+ FollowAccountOutput,
14
16
  FeedGeneratorsView,
15
17
  FeedRecommendationsView,
16
18
  FeedRecommendedAlbumsView,
@@ -37,10 +39,13 @@ import type {
37
39
  GetTrackShoutsOutput,
38
40
  GetUnreadCountOutput,
39
41
  ListNotificationsOutput,
42
+ MirrorSourceView,
40
43
  PlayerCurrentlyPlayingViewDetailed,
41
44
  PlayerPlaybackQueueViewDetailed,
42
45
  PlaylistGetPlaylistsOutput,
43
46
  PlaylistViewDetailed,
47
+ PutAudioSettingsInput,
48
+ PutMirrorSourceInput,
44
49
  RockboxSettingsView,
45
50
  ScrobbleViewBasic,
46
51
  SongViewBasic,
@@ -48,6 +53,7 @@ import type {
48
53
  StatsGlobalStatsView,
49
54
  StatsView,
50
55
  StatsWrappedView,
56
+ UnfollowAccountOutput,
51
57
  UpdateSeenOutput,
52
58
  } from "./generated/types.js";
53
59
 
@@ -136,7 +142,7 @@ export class RockskyClient {
136
142
  if (v !== undefined && v !== "") clean[k] = v;
137
143
  }
138
144
  const res = await this.rpc.get(nsid as never, { params: clean } as never);
139
- if (!res.ok) throw new RockskyError(res.data);
145
+ if (!res.ok) throw new RockskyError(res.data, res.status);
140
146
  return res.data as T;
141
147
  }
142
148
 
@@ -161,6 +167,24 @@ export class RockskyClient {
161
167
  return this.query(nsid, params);
162
168
  }
163
169
 
170
+ /** Call any AppView procedure by nsid. `params` ride the query string (some
171
+ * procedures take their arguments there), `body` is the JSON input — omitted
172
+ * entirely when `undefined`. Escape hatch for procedures without a wrapper. */
173
+ async post<T = unknown>(
174
+ nsid: string,
175
+ opts: { params?: Record<string, unknown>; body?: unknown } = {},
176
+ ): Promise<T> {
177
+ const clean: Record<string, unknown> = {};
178
+ for (const [k, v] of Object.entries(opts.params ?? {})) {
179
+ if (v !== undefined && v !== "") clean[k] = v;
180
+ }
181
+ const call: Record<string, unknown> = { params: clean };
182
+ if (opts.body !== undefined) call.input = opts.body;
183
+ const res = await this.rpc.post(nsid as never, call as never);
184
+ if (!res.ok) throw new RockskyError(res.data, res.status);
185
+ return res.data as T;
186
+ }
187
+
164
188
  /** An actor's most-played songs. */
165
189
  async songs(actor: string, limit = 50, offset = 0): Promise<SongViewBasic[]> {
166
190
  const out = await this.query<{ tracks?: SongViewBasic[] }>("app.rocksky.actor.getActorSongs", {
@@ -539,7 +563,34 @@ export class RockskyClient {
539
563
  const res = await this.rpc.post("app.rocksky.notification.updateSeen" as never, {
540
564
  input: (ids && ids.length ? { ids } : {}) as never,
541
565
  } as never);
542
- if (!res.ok) throw new RockskyError(res.data);
566
+ if (!res.ok) throw new RockskyError(res.data, res.status);
543
567
  return res.data as UpdateSeenOutput;
544
568
  }
569
+
570
+ /** Follow an account by DID or handle (`app.rocksky.graph.followAccount`). Auth required. */
571
+ followAccount(account: string): Promise<FollowAccountOutput> {
572
+ return this.post("app.rocksky.graph.followAccount", { params: { account } });
573
+ }
574
+
575
+ /** Unfollow an account by DID or handle (`app.rocksky.graph.unfollowAccount`). Auth required. */
576
+ unfollowAccount(account: string): Promise<UnfollowAccountOutput> {
577
+ return this.post("app.rocksky.graph.unfollowAccount", { params: { account } });
578
+ }
579
+
580
+ /** Submit a scrobble through the AppView (`app.rocksky.scrobble.createScrobble`).
581
+ * Auth required. For direct-to-PDS scrobbling use {@link Agent} instead. */
582
+ createScrobble(input: CreateScrobbleInput): Promise<ScrobbleViewBasic> {
583
+ return this.post("app.rocksky.scrobble.createScrobble", { body: input });
584
+ }
585
+
586
+ /** Create or update a mirror source (`app.rocksky.mirror.putMirrorSource`). Auth required. */
587
+ putMirrorSource(input: PutMirrorSourceInput): Promise<MirrorSourceView> {
588
+ return this.post("app.rocksky.mirror.putMirrorSource", { body: input });
589
+ }
590
+
591
+ /** Patch the viewer's Rockbox audio settings (`app.rocksky.rockbox.putAudioSettings`).
592
+ * Auth required. */
593
+ putAudioSettings(input: PutAudioSettingsInput): Promise<RockboxSettingsView> {
594
+ return this.post("app.rocksky.rockbox.putAudioSettings", { body: input });
595
+ }
545
596
  }
package/src/errors.ts CHANGED
@@ -1,10 +1,15 @@
1
- /** Error thrown when a Rocksky XRPC call returns a non-2xx `{ error, message }`. */
2
1
  export class RockskyError extends Error {
2
+ /** The XRPC error code (e.g. "InvalidRequest", "AuthMissing"), when the
3
+ * server sent one. */
3
4
  readonly kind?: string;
4
- constructor(payload: unknown) {
5
+ /** The HTTP status of the failed response, when known. */
6
+ readonly status?: number;
7
+
8
+ constructor(payload: unknown, status?: number) {
5
9
  const p = payload as { error?: string; message?: string } | undefined;
6
10
  super(p?.message || p?.error || "rocksky request failed");
7
11
  this.name = "RockskyError";
8
12
  this.kind = p?.error;
13
+ this.status = status;
9
14
  }
10
15
  }
package/src/hash.ts CHANGED
@@ -1,9 +1,11 @@
1
- import { createHash } from "node:crypto";
1
+ import { sha256 } from "@noble/hashes/sha2.js";
2
+ import { bytesToHex } from "@noble/hashes/utils.js";
2
3
 
3
4
  // Lowercase-hex SHA-256 of s.toLowerCase() — the lowercasing is applied to the
4
5
  // whole string (not per field), matching Rocksky's server and every other SDK.
6
+ // Pure-JS digest (@noble/hashes) so this module stays browser-safe.
5
7
  function sha256Lower(s: string): string {
6
- return createHash("sha256").update(s.toLowerCase()).digest("hex");
8
+ return bytesToHex(sha256(new TextEncoder().encode(s.toLowerCase())));
7
9
  }
8
10
 
9
11
  /** Identity hash of a song: sha256(lower("{title} - {artist} - {album}")). */
package/src/index.ts CHANGED
@@ -23,7 +23,9 @@ export {
23
23
  type RateLimitOptions,
24
24
  type RateLimitState,
25
25
  } from "./agent.js";
26
- export { RockskyIndex, totalIndexed, type IndexStats } from "./dedup.js";
26
+ // The dedup index (classic-level, Node-only) lives on the `@rocksky/sdk/dedup`
27
+ // subpath so this root entry stays browser-safe.
28
+ export type { IndexStats, RockskyIndex } from "./dedup.js";
27
29
  export { runJetstream, DEFAULT_JETSTREAM_SERVERS, type JetstreamOptions } from "./jetstream.js";
28
30
  export {
29
31
  RemotePlayer,
package/src/library.ts CHANGED
@@ -20,7 +20,7 @@ export class RockskyLibrary {
20
20
  if (v !== undefined && v !== "") clean[k] = v;
21
21
  }
22
22
  const res = await this.rpc.get(nsid as never, { params: clean } as never);
23
- if (!res.ok) throw new RockskyError(res.data);
23
+ if (!res.ok) throw new RockskyError(res.data, res.status);
24
24
  return res.data as T;
25
25
  }
26
26
 
@@ -30,7 +30,7 @@ export class RockskyLibrary {
30
30
  if (v !== undefined && v !== "") clean[k] = v;
31
31
  }
32
32
  const res = await this.rpc.post(nsid as never, { input: clean } as never);
33
- if (!res.ok) throw new RockskyError(res.data);
33
+ if (!res.ok) throw new RockskyError(res.data, res.status);
34
34
  return res.data as T;
35
35
  }
36
36
 
package/src/remote.ts CHANGED
@@ -1,9 +1,8 @@
1
- // Browser-safe entry: the remote-control player + controller only.
1
+ // Lightweight entry: the remote-control player + controller only.
2
2
  //
3
- // The main entry (`@rocksky/sdk`) bundles the dedup index (classic-level) and
4
- // the identity hashes (node:crypto), which are Node-only. The remote player /
5
- // controller are pure WebSocket + JSON with zero Node dependencies, so this
6
- // subpath (`@rocksky/sdk/remote`) is safe to import from a browser bundle.
3
+ // Since 0.14.0 the main entry (`@rocksky/sdk`) is browser-safe too the
4
+ // dedup index (classic-level, Node-only) moved to `@rocksky/sdk/dedup`. This
5
+ // subpath remains for consumers that only need the remote player/controller.
7
6
 
8
7
  export {
9
8
  RemotePlayer,