@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/README.md +23 -0
- package/dist/client.d.ts +13 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/filter.d.ts +122 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.test.d.ts +2 -0
- package/dist/filter.test.d.ts.map +1 -0
- package/dist/generated/types.d.ts +20 -0
- package/dist/generated/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +172 -27
- package/dist/remote-player.d.ts +5 -0
- package/dist/remote-player.d.ts.map +1 -1
- package/dist/remote.js +6 -2
- package/package.json +1 -1
- package/src/client.ts +38 -8
- package/src/filter.test.ts +94 -0
- package/src/filter.ts +192 -0
- package/src/generated/types.ts +21 -0
- package/src/index.ts +8 -0
- package/src/remote-controller.ts +2 -0
- package/src/remote-player.ts +7 -0
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
|
-
|
|
190
|
+
DEFAULT_APPVIEW: () => DEFAULT_APPVIEW,
|
|
191
191
|
Interval: () => Interval,
|
|
192
|
-
|
|
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
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
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
|
-
|
|
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
|
};
|
package/dist/remote-player.d.ts
CHANGED
|
@@ -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
|
-
|
|
530
|
+
DEFAULT_REMOTE_WS,
|
|
527
531
|
RemoteController,
|
|
528
|
-
|
|
532
|
+
RemotePlayer
|
|
529
533
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rocksky/sdk",
|
|
3
|
-
"version": "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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
});
|