@rocksky/sdk 0.11.0 → 0.13.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/dist/index.js CHANGED
@@ -187,9 +187,9 @@ var init_library = __esm(() => {
187
187
  // src/client.ts
188
188
  var exports_client = {};
189
189
  __export(exports_client, {
190
- RockskyClient: () => RockskyClient,
190
+ DEFAULT_APPVIEW: () => DEFAULT_APPVIEW,
191
191
  Interval: () => Interval,
192
- DEFAULT_APPVIEW: () => DEFAULT_APPVIEW
192
+ RockskyClient: () => RockskyClient
193
193
  });
194
194
  import { Client, simpleFetchHandler } from "@atcute/client";
195
195
  function since(days = 0, months = 0, years = 0) {
@@ -297,27 +297,30 @@ class RockskyClient {
297
297
  });
298
298
  return out.artists ?? [];
299
299
  }
300
- async catalogAlbums(limit = 50, offset = 0, genre) {
300
+ async catalogAlbums(limit = 50, offset = 0, genre, filter) {
301
301
  const out = await this.query("app.rocksky.album.getAlbums", {
302
302
  limit,
303
303
  offset,
304
- genre
304
+ genre,
305
+ filter: filter?.toString()
305
306
  });
306
307
  return out.albums ?? [];
307
308
  }
308
- async catalogArtists(limit = 50, offset = 0, genre) {
309
+ async catalogArtists(limit = 50, offset = 0, genre, filter) {
309
310
  const out = await this.query("app.rocksky.artist.getArtists", {
310
311
  limit,
311
312
  offset,
312
- genre
313
+ genre,
314
+ filter: filter?.toString()
313
315
  });
314
316
  return out.artists ?? [];
315
317
  }
316
- async catalogSongs(limit = 50, offset = 0, genre) {
318
+ async catalogSongs(limit = 50, offset = 0, genre, filter) {
317
319
  const out = await this.query("app.rocksky.song.getSongs", {
318
320
  limit,
319
321
  offset,
320
- genre
322
+ genre,
323
+ filter: filter?.toString()
321
324
  });
322
325
  return out.tracks ?? [];
323
326
  }
@@ -337,12 +340,13 @@ class RockskyClient {
337
340
  });
338
341
  return out.tracks ?? [];
339
342
  }
340
- async scrobbleFeed(did, following = false, limit = 50, offset = 0) {
343
+ async scrobbleFeed(did, following = false, limit = 50, offset = 0, filter) {
341
344
  const out = await this.query("app.rocksky.scrobble.getScrobbles", {
342
345
  did,
343
346
  following,
344
347
  limit,
345
- offset
348
+ offset,
349
+ filter: filter?.toString()
346
350
  });
347
351
  return out.scrobbles ?? [];
348
352
  }
@@ -1160,6 +1164,8 @@ class RemotePlayer {
1160
1164
  duration_ms: track.durationMs ?? 0,
1161
1165
  album_art: track.albumArt,
1162
1166
  is_playing: track.isPlaying ?? true,
1167
+ codec: track.codec,
1168
+ sample_rate: track.sampleRate,
1163
1169
  device_name: this.opts.name
1164
1170
  }
1165
1171
  });
@@ -1571,6 +1577,8 @@ function trackFromJson(d) {
1571
1577
  durationMs: d.duration_ms ?? d.length,
1572
1578
  elapsedMs: d.elapsed,
1573
1579
  isPlaying: d.is_playing,
1580
+ codec: d.codec,
1581
+ sampleRate: d.sample_rate,
1574
1582
  songUri: d.song_uri,
1575
1583
  albumUri: d.album_uri,
1576
1584
  artistUri: d.artist_uri,
@@ -1602,26 +1610,163 @@ function deviceFromJson(d) {
1602
1610
  queue: (d.queue?.queue ?? []).map(queueItemFromJson)
1603
1611
  };
1604
1612
  }
1613
+ // src/filter.ts
1614
+ var SAFE_VALUE = /^[A-Za-z0-9_.:@*+-]+$/;
1615
+ var renderValue = (value) => {
1616
+ if (typeof value === "number" || typeof value === "boolean")
1617
+ return String(value);
1618
+ if (value.length > 0 && SAFE_VALUE.test(value))
1619
+ return value;
1620
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
1621
+ };
1622
+
1623
+ class Filter {
1624
+ expr;
1625
+ kind;
1626
+ constructor(expr, kind) {
1627
+ this.expr = expr;
1628
+ this.kind = kind;
1629
+ }
1630
+ static comparison(field, op, value) {
1631
+ return new Filter(`${field}${op}${renderValue(value)}`, "comparison");
1632
+ }
1633
+ static list(field, op, values) {
1634
+ if (values.length === 0) {
1635
+ throw new Error(`Filter.${op === "=in=" ? "in" : "out"}("${field}", ...) needs at least one value`);
1636
+ }
1637
+ return new Filter(`${field}${op}(${values.map(renderValue).join(",")})`, "comparison");
1638
+ }
1639
+ static eq(field, value) {
1640
+ return Filter.comparison(field, "==", value);
1641
+ }
1642
+ static ne(field, value) {
1643
+ return Filter.comparison(field, "!=", value);
1644
+ }
1645
+ static gt(field, value) {
1646
+ return Filter.comparison(field, "=gt=", value);
1647
+ }
1648
+ static ge(field, value) {
1649
+ return Filter.comparison(field, "=ge=", value);
1650
+ }
1651
+ static lt(field, value) {
1652
+ return Filter.comparison(field, "=lt=", value);
1653
+ }
1654
+ static le(field, value) {
1655
+ return Filter.comparison(field, "=le=", value);
1656
+ }
1657
+ static in(field, values) {
1658
+ return Filter.list(field, "=in=", values);
1659
+ }
1660
+ static out(field, values) {
1661
+ return Filter.list(field, "=out=", values);
1662
+ }
1663
+ static isNull(field) {
1664
+ return new Filter(`${field}==null`, "comparison");
1665
+ }
1666
+ static isNotNull(field) {
1667
+ return new Filter(`${field}!=null`, "comparison");
1668
+ }
1669
+ and(other) {
1670
+ return new Filter(`${this.renderIn("and")};${other.renderIn("and")}`, "and");
1671
+ }
1672
+ or(other) {
1673
+ return new Filter(`${this.expr},${other.expr}`, "or");
1674
+ }
1675
+ renderIn(parent) {
1676
+ return this.kind === "or" ? `(${this.expr})` : this.expr;
1677
+ }
1678
+ build() {
1679
+ return this.expr;
1680
+ }
1681
+ toString() {
1682
+ return this.expr;
1683
+ }
1684
+ }
1685
+ var SongFields = {
1686
+ title: "title",
1687
+ artist: "artist",
1688
+ album: "album",
1689
+ albumArtist: "albumArtist",
1690
+ genre: "genre",
1691
+ composer: "composer",
1692
+ label: "label",
1693
+ duration: "duration",
1694
+ trackNumber: "trackNumber",
1695
+ discNumber: "discNumber",
1696
+ mbId: "mbId",
1697
+ isrc: "isrc",
1698
+ sha256: "sha256",
1699
+ uri: "uri",
1700
+ albumUri: "albumUri",
1701
+ artistUri: "artistUri",
1702
+ createdAt: "createdAt"
1703
+ };
1704
+ var AlbumFields = {
1705
+ title: "title",
1706
+ artist: "artist",
1707
+ year: "year",
1708
+ releaseDate: "releaseDate",
1709
+ sha256: "sha256",
1710
+ uri: "uri",
1711
+ artistUri: "artistUri",
1712
+ createdAt: "createdAt"
1713
+ };
1714
+ var ArtistFields = {
1715
+ name: "name",
1716
+ genres: "genres",
1717
+ bornIn: "bornIn",
1718
+ born: "born",
1719
+ died: "died",
1720
+ sha256: "sha256",
1721
+ uri: "uri",
1722
+ createdAt: "createdAt"
1723
+ };
1724
+ var ScrobbleFields = {
1725
+ uri: "uri",
1726
+ date: "date",
1727
+ timestamp: "timestamp",
1728
+ title: "title",
1729
+ artist: "artist",
1730
+ album: "album",
1731
+ trackTitle: "track.title",
1732
+ trackArtist: "track.artist",
1733
+ trackAlbum: "track.album",
1734
+ trackAlbumArtist: "track.albumArtist",
1735
+ trackGenre: "track.genre",
1736
+ trackDuration: "track.duration",
1737
+ trackIsrc: "track.isrc",
1738
+ trackMbId: "track.mbId",
1739
+ userDid: "user.did",
1740
+ userHandle: "user.handle",
1741
+ userDisplayName: "user.displayName",
1742
+ artistName: "artist.name",
1743
+ artistGenres: "artist.genres"
1744
+ };
1605
1745
 
1606
1746
  // src/index.ts
1607
1747
  init_errors();
1608
1748
  export {
1609
- totalIndexed,
1610
- songHash,
1611
- runJetstream,
1612
- artistHash,
1613
- albumHash,
1614
- RockskyLibrary,
1615
- RockskyIndex,
1616
- RockskyError,
1617
- RockskyClient,
1618
- RemotePlayer,
1619
- RemoteController,
1620
- MAX_SAFE_WRITES_PER_HOUR,
1621
- Interval,
1622
- DEFAULT_REMOTE_WS,
1623
- DEFAULT_MATCH_SONG_PER_HOUR,
1624
- DEFAULT_JETSTREAM_SERVERS,
1749
+ Agent,
1750
+ AlbumFields,
1751
+ ArtistFields,
1625
1752
  DEFAULT_APPVIEW,
1626
- Agent
1753
+ DEFAULT_JETSTREAM_SERVERS,
1754
+ DEFAULT_MATCH_SONG_PER_HOUR,
1755
+ DEFAULT_REMOTE_WS,
1756
+ Filter,
1757
+ Interval,
1758
+ MAX_SAFE_WRITES_PER_HOUR,
1759
+ RemoteController,
1760
+ RemotePlayer,
1761
+ RockskyClient,
1762
+ RockskyError,
1763
+ RockskyIndex,
1764
+ RockskyLibrary,
1765
+ ScrobbleFields,
1766
+ SongFields,
1767
+ albumHash,
1768
+ artistHash,
1769
+ runJetstream,
1770
+ songHash,
1771
+ totalIndexed
1627
1772
  };
@@ -34,6 +34,11 @@ export interface RemoteNowPlaying {
34
34
  /** Current position, ms. */
35
35
  elapsedMs?: number;
36
36
  isPlaying?: boolean;
37
+ /** Audio codec / container of the playing file (e.g. "mp3", "flac"),
38
+ * when the player knows it. Shown as a format badge by controller UIs. */
39
+ codec?: string;
40
+ /** Audio sample rate in Hz (e.g. 44100), when the player knows it. */
41
+ sampleRate?: number;
37
42
  /** The song's `at://` URI. */
38
43
  songUri?: string;
39
44
  /** The album's `at://` URI. */
@@ -1 +1 @@
1
- {"version":3,"file":"remote-player.d.ts","sourceRoot":"","sources":["../src/remote-player.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"remote-player.d.ts","sourceRoot":"","sources":["../src/remote-player.ts"],"names":[],"mappings":"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"}
package/dist/remote.js CHANGED
@@ -80,6 +80,8 @@ class RemotePlayer {
80
80
  duration_ms: track.durationMs ?? 0,
81
81
  album_art: track.albumArt,
82
82
  is_playing: track.isPlaying ?? true,
83
+ codec: track.codec,
84
+ sample_rate: track.sampleRate,
83
85
  device_name: this.opts.name
84
86
  }
85
87
  });
@@ -491,6 +493,8 @@ function trackFromJson(d) {
491
493
  durationMs: d.duration_ms ?? d.length,
492
494
  elapsedMs: d.elapsed,
493
495
  isPlaying: d.is_playing,
496
+ codec: d.codec,
497
+ sampleRate: d.sample_rate,
494
498
  songUri: d.song_uri,
495
499
  albumUri: d.album_uri,
496
500
  artistUri: d.artist_uri,
@@ -523,7 +527,7 @@ function deviceFromJson(d) {
523
527
  };
524
528
  }
525
529
  export {
526
- RemotePlayer,
530
+ DEFAULT_REMOTE_WS,
527
531
  RemoteController,
528
- DEFAULT_REMOTE_WS
532
+ RemotePlayer
529
533
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rocksky/sdk",
3
- "version": "0.11.0",
3
+ "version": "0.13.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": {
package/src/client.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Client, simpleFetchHandler } from "@atcute/client";
2
2
 
3
3
  import { RockskyError } from "./errors.js";
4
+ import type { Filter } from "./filter.js";
4
5
  import { RockskyLibrary } from "./library.js";
5
6
  import type {
6
7
  ActorProfileViewBasic,
@@ -229,32 +230,53 @@ export class RockskyClient {
229
230
  return out.artists ?? [];
230
231
  }
231
232
 
232
- /** The album catalog, optionally filtered by `genre`. */
233
- async catalogAlbums(limit = 50, offset = 0, genre?: string): Promise<AlbumViewBasic[]> {
233
+ /** The album catalog, optionally filtered by `genre` and/or an RSQL
234
+ * {@link Filter} expression (see {@link AlbumFields}). */
235
+ async catalogAlbums(
236
+ limit = 50,
237
+ offset = 0,
238
+ genre?: string,
239
+ filter?: string | Filter,
240
+ ): Promise<AlbumViewBasic[]> {
234
241
  const out = await this.query<{ albums?: AlbumViewBasic[] }>("app.rocksky.album.getAlbums", {
235
242
  limit,
236
243
  offset,
237
244
  genre,
245
+ filter: filter?.toString(),
238
246
  });
239
247
  return out.albums ?? [];
240
248
  }
241
249
 
242
- /** The artist catalog, optionally filtered by `genre`. */
243
- async catalogArtists(limit = 50, offset = 0, genre?: string): Promise<ArtistViewBasic[]> {
250
+ /** The artist catalog, optionally filtered by `genre` and/or an RSQL
251
+ * {@link Filter} expression (see {@link ArtistFields}). */
252
+ async catalogArtists(
253
+ limit = 50,
254
+ offset = 0,
255
+ genre?: string,
256
+ filter?: string | Filter,
257
+ ): Promise<ArtistViewBasic[]> {
244
258
  const out = await this.query<{ artists?: ArtistViewBasic[] }>("app.rocksky.artist.getArtists", {
245
259
  limit,
246
260
  offset,
247
261
  genre,
262
+ filter: filter?.toString(),
248
263
  });
249
264
  return out.artists ?? [];
250
265
  }
251
266
 
252
- /** The song catalog, optionally filtered by `genre`. */
253
- async catalogSongs(limit = 50, offset = 0, genre?: string): Promise<SongViewBasic[]> {
267
+ /** The song catalog, optionally filtered by `genre` and/or an RSQL
268
+ * {@link Filter} expression (see {@link SongFields}). */
269
+ async catalogSongs(
270
+ limit = 50,
271
+ offset = 0,
272
+ genre?: string,
273
+ filter?: string | Filter,
274
+ ): Promise<SongViewBasic[]> {
254
275
  const out = await this.query<{ tracks?: SongViewBasic[] }>("app.rocksky.song.getSongs", {
255
276
  limit,
256
277
  offset,
257
278
  genre,
279
+ filter: filter?.toString(),
258
280
  });
259
281
  return out.tracks ?? [];
260
282
  }
@@ -282,13 +304,21 @@ export class RockskyClient {
282
304
  }
283
305
 
284
306
  /** A social/global scrobbles feed. Pass `did` to scope to an actor and
285
- * `following = true` for their follow graph. */
286
- async scrobbleFeed(did?: string, following = false, limit = 50, offset = 0): Promise<ScrobbleViewBasic[]> {
307
+ * `following = true` for their follow graph. `filter` takes an RSQL
308
+ * {@link Filter} expression (see {@link ScrobbleFields}). */
309
+ async scrobbleFeed(
310
+ did?: string,
311
+ following = false,
312
+ limit = 50,
313
+ offset = 0,
314
+ filter?: string | Filter,
315
+ ): Promise<ScrobbleViewBasic[]> {
287
316
  const out = await this.query<{ scrobbles?: ScrobbleViewBasic[] }>("app.rocksky.scrobble.getScrobbles", {
288
317
  did,
289
318
  following,
290
319
  limit,
291
320
  offset,
321
+ filter: filter?.toString(),
292
322
  });
293
323
  return out.scrobbles ?? [];
294
324
  }
@@ -0,0 +1,94 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { AlbumFields, ArtistFields, Filter, ScrobbleFields, SongFields } from "./filter.js";
3
+
4
+ describe("Filter", () => {
5
+ test("eq renders a bare value when safe", () => {
6
+ expect(Filter.eq("artist", "Radiohead").build()).toBe("artist==Radiohead");
7
+ });
8
+
9
+ test("eq quotes values with spaces", () => {
10
+ expect(Filter.eq("artist", "Daft Punk").build()).toBe('artist=="Daft Punk"');
11
+ });
12
+
13
+ test("eq escapes embedded quotes and backslashes", () => {
14
+ expect(Filter.eq("title", 'He said "hi"').build()).toBe('title=="He said \\"hi\\""');
15
+ expect(Filter.eq("title", "back\\slash").build()).toBe('title=="back\\\\slash"');
16
+ });
17
+
18
+ test("wildcards pass through unquoted", () => {
19
+ expect(Filter.eq("artist", "Daft*").build()).toBe("artist==Daft*");
20
+ });
21
+
22
+ test("ne", () => {
23
+ expect(Filter.ne("artist", "Eminem").build()).toBe("artist!=Eminem");
24
+ });
25
+
26
+ test("ordered comparisons render verbose operators", () => {
27
+ expect(Filter.gt("duration", 200000).build()).toBe("duration=gt=200000");
28
+ expect(Filter.ge("year", 2000).build()).toBe("year=ge=2000");
29
+ expect(Filter.lt("trackNumber", 5).build()).toBe("trackNumber=lt=5");
30
+ expect(Filter.le("year", 1999).build()).toBe("year=le=1999");
31
+ });
32
+
33
+ test("in and out lists", () => {
34
+ expect(Filter.in("genre", ["house", "electro"]).build()).toBe("genre=in=(house,electro)");
35
+ expect(Filter.out("genre", ["hip hop"]).build()).toBe('genre=out=("hip hop")');
36
+ });
37
+
38
+ test("in/out reject empty lists", () => {
39
+ expect(() => Filter.in("genre", [])).toThrow();
40
+ expect(() => Filter.out("genre", [])).toThrow();
41
+ });
42
+
43
+ test("null checks", () => {
44
+ expect(Filter.isNull("uri").build()).toBe("uri==null");
45
+ expect(Filter.isNotNull("uri").build()).toBe("uri!=null");
46
+ });
47
+
48
+ test("and joins with ;", () => {
49
+ const f = Filter.eq("artist", "Radiohead").and(Filter.gt("duration", 200000));
50
+ expect(f.build()).toBe("artist==Radiohead;duration=gt=200000");
51
+ });
52
+
53
+ test("or joins with ,", () => {
54
+ const f = Filter.eq("artist", "Radiohead").or(Filter.eq("artist", "Muse"));
55
+ expect(f.build()).toBe("artist==Radiohead,artist==Muse");
56
+ });
57
+
58
+ test("or nested in and is parenthesized", () => {
59
+ const left = Filter.eq("artist", "Radiohead").or(Filter.eq("artist", "Muse"));
60
+ expect(left.and(Filter.gt("duration", 200000)).build()).toBe(
61
+ "(artist==Radiohead,artist==Muse);duration=gt=200000",
62
+ );
63
+ const right = Filter.eq("genre", "house").or(Filter.eq("genre", "electro"));
64
+ expect(Filter.eq("artist", "Radiohead").and(right).build()).toBe(
65
+ "artist==Radiohead;(genre==house,genre==electro)",
66
+ );
67
+ });
68
+
69
+ test("and nested in or needs no parentheses", () => {
70
+ const f = Filter.eq("artist", "Radiohead")
71
+ .and(Filter.gt("duration", 200000))
72
+ .or(Filter.eq("genre", "house"));
73
+ expect(f.build()).toBe("artist==Radiohead;duration=gt=200000,genre==house");
74
+ });
75
+
76
+ test("booleans and dotted fields", () => {
77
+ expect(Filter.eq("user.handle", "tsiry.dev").build()).toBe("user.handle==tsiry.dev");
78
+ expect(Filter.eq("liked", true).build()).toBe("liked==true");
79
+ });
80
+
81
+ test("toString matches build so filters interpolate", () => {
82
+ const f = Filter.eq("artist", "Radiohead");
83
+ expect(`${f}`).toBe(f.build());
84
+ });
85
+
86
+ test("field constants map to server selectors", () => {
87
+ expect(SongFields.albumArtist).toBe("albumArtist");
88
+ expect(AlbumFields.releaseDate).toBe("releaseDate");
89
+ expect(ArtistFields.genres).toBe("genres");
90
+ expect(ScrobbleFields.trackArtist).toBe("track.artist");
91
+ expect(ScrobbleFields.userHandle).toBe("user.handle");
92
+ expect(Filter.eq(ScrobbleFields.artistGenres, "house").build()).toBe("artist.genres==house");
93
+ });
94
+ });