@rocksky/sdk 0.12.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/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,
@@ -297,27 +313,30 @@ class RockskyClient {
297
313
  });
298
314
  return out.artists ?? [];
299
315
  }
300
- async catalogAlbums(limit = 50, offset = 0, genre) {
316
+ async catalogAlbums(limit = 50, offset = 0, genre, filter) {
301
317
  const out = await this.query("app.rocksky.album.getAlbums", {
302
318
  limit,
303
319
  offset,
304
- genre
320
+ genre,
321
+ filter: filter?.toString()
305
322
  });
306
323
  return out.albums ?? [];
307
324
  }
308
- async catalogArtists(limit = 50, offset = 0, genre) {
325
+ async catalogArtists(limit = 50, offset = 0, genre, filter) {
309
326
  const out = await this.query("app.rocksky.artist.getArtists", {
310
327
  limit,
311
328
  offset,
312
- genre
329
+ genre,
330
+ filter: filter?.toString()
313
331
  });
314
332
  return out.artists ?? [];
315
333
  }
316
- async catalogSongs(limit = 50, offset = 0, genre) {
334
+ async catalogSongs(limit = 50, offset = 0, genre, filter) {
317
335
  const out = await this.query("app.rocksky.song.getSongs", {
318
336
  limit,
319
337
  offset,
320
- genre
338
+ genre,
339
+ filter: filter?.toString()
321
340
  });
322
341
  return out.tracks ?? [];
323
342
  }
@@ -337,12 +356,13 @@ class RockskyClient {
337
356
  });
338
357
  return out.tracks ?? [];
339
358
  }
340
- async scrobbleFeed(did, following = false, limit = 50, offset = 0) {
359
+ async scrobbleFeed(did, following = false, limit = 50, offset = 0, filter) {
341
360
  const out = await this.query("app.rocksky.scrobble.getScrobbles", {
342
361
  did,
343
362
  following,
344
363
  limit,
345
- offset
364
+ offset,
365
+ filter: filter?.toString()
346
366
  });
347
367
  return out.scrobbles ?? [];
348
368
  }
@@ -485,9 +505,24 @@ class RockskyClient {
485
505
  input: ids && ids.length ? { ids } : {}
486
506
  });
487
507
  if (!res.ok)
488
- throw new RockskyError(res.data);
508
+ throw new RockskyError(res.data, res.status);
489
509
  return res.data;
490
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
+ }
491
526
  }
492
527
  var Interval, DEFAULT_APPVIEW = "https://api.rocksky.app";
493
528
  var init_client = __esm(() => {
@@ -741,7 +776,7 @@ class Agent {
741
776
  input: { repo: this.did, collection, record: { ...record, $type: collection } }
742
777
  });
743
778
  if (!res.ok)
744
- throw new RockskyError(res.data);
779
+ throw new RockskyError(res.data, res.status);
745
780
  return res.data.uri;
746
781
  }
747
782
  async putRecord(collection, rkey, record) {
@@ -750,7 +785,7 @@ class Agent {
750
785
  input: { repo: this.did, collection, rkey, record: { ...record, $type: collection } }
751
786
  });
752
787
  if (!res.ok)
753
- throw new RockskyError(res.data);
788
+ throw new RockskyError(res.data, res.status);
754
789
  return res.data.uri;
755
790
  }
756
791
  async delete(collection, rkey) {
@@ -759,7 +794,7 @@ class Agent {
759
794
  input: { repo: this.did, collection, rkey }
760
795
  });
761
796
  if (!res.ok)
762
- throw new RockskyError(res.data);
797
+ throw new RockskyError(res.data, res.status);
763
798
  }
764
799
  async scrobble(rec) {
765
800
  const record = { ...rec, createdAt: rec.createdAt || nowISO() };
@@ -900,200 +935,6 @@ class Agent {
900
935
  return this.delete(C_STATUS, "self");
901
936
  }
902
937
  }
903
- // src/dedup.ts
904
- import { fromUint8Array } from "@atcute/car";
905
- import { decode as cborDecode, fromBytes, isBytes, isCidLink } from "@atcute/cbor";
906
- import { toString as cidToString } from "@atcute/cid";
907
- import { ClassicLevel } from "classic-level";
908
-
909
- // src/hash.ts
910
- import { createHash } from "node:crypto";
911
- function sha256Lower(s) {
912
- return createHash("sha256").update(s.toLowerCase()).digest("hex");
913
- }
914
- function songHash(title, artist, album) {
915
- return sha256Lower(`${title} - ${artist} - ${album}`);
916
- }
917
- function albumHash(album, albumArtist) {
918
- return sha256Lower(`${album} - ${albumArtist}`);
919
- }
920
- function artistHash(albumArtist) {
921
- return sha256Lower(albumArtist);
922
- }
923
-
924
- // src/dedup.ts
925
- var C_ARTIST2 = "app.rocksky.artist";
926
- var C_ALBUM2 = "app.rocksky.album";
927
- var C_SONG2 = "app.rocksky.song";
928
- var C_SCROBBLE2 = "app.rocksky.scrobble";
929
- var SEP = "\x00";
930
- function totalIndexed(s) {
931
- return s.artists + s.albums + s.songs + s.scrobbles;
932
- }
933
- function identKey(did, col, hash) {
934
- return `${did}${SEP}${col}${SEP}${hash}`;
935
- }
936
- function scrobbleKey(did, songH, secs) {
937
- return `${did}${SEP}${C_SCROBBLE2}${SEP}${songH}${SEP}${secs}`;
938
- }
939
- function primaryFor(did, col, rec) {
940
- const s = (k) => typeof rec[k] === "string" ? rec[k] : "";
941
- switch (col) {
942
- case C_ARTIST2:
943
- return s("name") ? identKey(did, C_ARTIST2, artistHash(s("name"))) : undefined;
944
- case C_ALBUM2:
945
- return s("title") && s("artist") ? identKey(did, C_ALBUM2, albumHash(s("title"), s("artist"))) : undefined;
946
- case C_SONG2:
947
- return s("title") && s("artist") && s("album") ? identKey(did, C_SONG2, songHash(s("title"), s("artist"), s("album"))) : undefined;
948
- case C_SCROBBLE2: {
949
- if (s("title") && s("artist") && s("album") && s("createdAt")) {
950
- const secs = Math.floor(Date.parse(s("createdAt")) / 1000);
951
- if (!Number.isNaN(secs))
952
- return scrobbleKey(did, songHash(s("title"), s("artist"), s("album")), secs);
953
- }
954
- return;
955
- }
956
- }
957
- return;
958
- }
959
-
960
- class RockskyIndex {
961
- db;
962
- constructor(path) {
963
- this.db = new ClassicLevel(path, { keyEncoding: "utf8", valueEncoding: "utf8" });
964
- }
965
- open() {
966
- return this.db.open();
967
- }
968
- close() {
969
- return this.db.close();
970
- }
971
- async get(key) {
972
- try {
973
- return await this.db.get(key);
974
- } catch {
975
- return;
976
- }
977
- }
978
- songUri(did, title, artist, album) {
979
- return this.get(identKey(did, C_SONG2, songHash(title, artist, album)));
980
- }
981
- albumUri(did, album, albumArtist) {
982
- return this.get(identKey(did, C_ALBUM2, albumHash(album, albumArtist)));
983
- }
984
- artistUri(did, albumArtist) {
985
- return this.get(identKey(did, C_ARTIST2, artistHash(albumArtist)));
986
- }
987
- scrobbleUri(did, title, artist, album, secs) {
988
- return this.get(scrobbleKey(did, songHash(title, artist, album), secs));
989
- }
990
- async putPrimary(did, col, primary, uri) {
991
- const rkey = uri.slice(uri.lastIndexOf("/") + 1);
992
- await this.db.batch([
993
- { type: "put", key: primary, value: uri },
994
- { type: "put", key: `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`, value: primary }
995
- ]);
996
- }
997
- recordSong(did, title, artist, album, uri) {
998
- return this.putPrimary(did, C_SONG2, identKey(did, C_SONG2, songHash(title, artist, album)), uri);
999
- }
1000
- recordAlbum(did, album, albumArtist, uri) {
1001
- return this.putPrimary(did, C_ALBUM2, identKey(did, C_ALBUM2, albumHash(album, albumArtist)), uri);
1002
- }
1003
- recordArtist(did, albumArtist, uri) {
1004
- return this.putPrimary(did, C_ARTIST2, identKey(did, C_ARTIST2, artistHash(albumArtist)), uri);
1005
- }
1006
- recordScrobble(did, title, artist, album, secs, uri) {
1007
- return this.putPrimary(did, C_SCROBBLE2, scrobbleKey(did, songHash(title, artist, album), secs), uri);
1008
- }
1009
- cursor(did) {
1010
- return this.get(`${SEP}meta${SEP}cursor${SEP}${did}`).then((v) => v ? Number(v) : 0);
1011
- }
1012
- async setCursor(did, timeUS) {
1013
- await this.db.put(`${SEP}meta${SEP}cursor${SEP}${did}`, String(timeUS));
1014
- }
1015
- async indexCar(did, car) {
1016
- const reader = fromUint8Array(car);
1017
- const blocks = new Map;
1018
- for (const entry of reader)
1019
- blocks.set(cidToString(entry.cid), entry.bytes);
1020
- const root = reader.roots[0];
1021
- if (!root)
1022
- throw new Error("CAR has no root");
1023
- const commit = cborDecode(blocks.get(root.$link));
1024
- const stats = { artists: 0, albums: 0, songs: 0, scrobbles: 0 };
1025
- const ops = [];
1026
- for (const [path, valueLink] of walkMst(blocks, commit.data.$link)) {
1027
- const slash = path.indexOf("/");
1028
- if (slash < 0)
1029
- continue;
1030
- const col = path.slice(0, slash);
1031
- const rkey = path.slice(slash + 1);
1032
- if (col !== C_ARTIST2 && col !== C_ALBUM2 && col !== C_SONG2 && col !== C_SCROBBLE2)
1033
- continue;
1034
- const recBytes = blocks.get(valueLink);
1035
- if (!recBytes)
1036
- continue;
1037
- const rec = cborDecode(recBytes);
1038
- const primary = primaryFor(did, col, rec);
1039
- if (!primary)
1040
- continue;
1041
- const uri = `at://${did}/${col}/${rkey}`;
1042
- ops.push({ type: "put", key: primary, value: uri });
1043
- ops.push({ type: "put", key: `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`, value: primary });
1044
- if (col === C_ARTIST2)
1045
- stats.artists++;
1046
- else if (col === C_ALBUM2)
1047
- stats.albums++;
1048
- else if (col === C_SONG2)
1049
- stats.songs++;
1050
- else
1051
- stats.scrobbles++;
1052
- }
1053
- if (commit.rev)
1054
- ops.push({ type: "put", key: `${SEP}meta${SEP}rev${SEP}${did}`, value: commit.rev });
1055
- await this.db.batch(ops);
1056
- return stats;
1057
- }
1058
- async applyCommit(did, col, operation, rkey, record) {
1059
- if (operation === "create" || operation === "update") {
1060
- if (!record)
1061
- return;
1062
- const primary = primaryFor(did, col, record);
1063
- if (!primary)
1064
- return;
1065
- await this.putPrimary(did, col, primary, `at://${did}/${col}/${rkey}`);
1066
- } else if (operation === "delete") {
1067
- const rk = `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`;
1068
- const primary = await this.get(rk);
1069
- if (primary) {
1070
- await this.db.batch([
1071
- { type: "del", key: primary },
1072
- { type: "del", key: rk }
1073
- ]);
1074
- }
1075
- }
1076
- }
1077
- }
1078
- function* walkMst(blocks, cid) {
1079
- const raw = blocks.get(cid);
1080
- if (!raw)
1081
- return;
1082
- const node = cborDecode(raw);
1083
- if (node.l && isCidLink(node.l))
1084
- yield* walkMst(blocks, node.l.$link);
1085
- let last = new Uint8Array(0);
1086
- for (const e of node.e) {
1087
- const suffix = isBytes(e.k) ? fromBytes(e.k) : e.k;
1088
- const key = new Uint8Array(e.p + suffix.length);
1089
- key.set(last.subarray(0, e.p), 0);
1090
- key.set(suffix, e.p);
1091
- yield [new TextDecoder().decode(key), e.v.$link];
1092
- last = key;
1093
- if (e.t && isCidLink(e.t))
1094
- yield* walkMst(blocks, e.t.$link);
1095
- }
1096
- }
1097
938
  // src/remote-player.ts
1098
939
  var DEFAULT_REMOTE_WS = "wss://api.rocksky.app/ws";
1099
940
 
@@ -1606,26 +1447,176 @@ function deviceFromJson(d) {
1606
1447
  queue: (d.queue?.queue ?? []).map(queueItemFromJson)
1607
1448
  };
1608
1449
  }
1450
+ // src/filter.ts
1451
+ var SAFE_VALUE = /^[A-Za-z0-9_.:@*+-]+$/;
1452
+ var renderValue = (value) => {
1453
+ if (typeof value === "number" || typeof value === "boolean")
1454
+ return String(value);
1455
+ if (value.length > 0 && SAFE_VALUE.test(value))
1456
+ return value;
1457
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
1458
+ };
1459
+
1460
+ class Filter {
1461
+ expr;
1462
+ kind;
1463
+ constructor(expr, kind) {
1464
+ this.expr = expr;
1465
+ this.kind = kind;
1466
+ }
1467
+ static comparison(field, op, value) {
1468
+ return new Filter(`${field}${op}${renderValue(value)}`, "comparison");
1469
+ }
1470
+ static list(field, op, values) {
1471
+ if (values.length === 0) {
1472
+ throw new Error(`Filter.${op === "=in=" ? "in" : "out"}("${field}", ...) needs at least one value`);
1473
+ }
1474
+ return new Filter(`${field}${op}(${values.map(renderValue).join(",")})`, "comparison");
1475
+ }
1476
+ static eq(field, value) {
1477
+ return Filter.comparison(field, "==", value);
1478
+ }
1479
+ static ne(field, value) {
1480
+ return Filter.comparison(field, "!=", value);
1481
+ }
1482
+ static gt(field, value) {
1483
+ return Filter.comparison(field, "=gt=", value);
1484
+ }
1485
+ static ge(field, value) {
1486
+ return Filter.comparison(field, "=ge=", value);
1487
+ }
1488
+ static lt(field, value) {
1489
+ return Filter.comparison(field, "=lt=", value);
1490
+ }
1491
+ static le(field, value) {
1492
+ return Filter.comparison(field, "=le=", value);
1493
+ }
1494
+ static in(field, values) {
1495
+ return Filter.list(field, "=in=", values);
1496
+ }
1497
+ static out(field, values) {
1498
+ return Filter.list(field, "=out=", values);
1499
+ }
1500
+ static isNull(field) {
1501
+ return new Filter(`${field}==null`, "comparison");
1502
+ }
1503
+ static isNotNull(field) {
1504
+ return new Filter(`${field}!=null`, "comparison");
1505
+ }
1506
+ and(other) {
1507
+ return new Filter(`${this.renderIn("and")};${other.renderIn("and")}`, "and");
1508
+ }
1509
+ or(other) {
1510
+ return new Filter(`${this.expr},${other.expr}`, "or");
1511
+ }
1512
+ renderIn(parent) {
1513
+ return this.kind === "or" ? `(${this.expr})` : this.expr;
1514
+ }
1515
+ build() {
1516
+ return this.expr;
1517
+ }
1518
+ toString() {
1519
+ return this.expr;
1520
+ }
1521
+ }
1522
+ var SongFields = {
1523
+ title: "title",
1524
+ artist: "artist",
1525
+ album: "album",
1526
+ albumArtist: "albumArtist",
1527
+ genre: "genre",
1528
+ composer: "composer",
1529
+ label: "label",
1530
+ duration: "duration",
1531
+ trackNumber: "trackNumber",
1532
+ discNumber: "discNumber",
1533
+ mbId: "mbId",
1534
+ isrc: "isrc",
1535
+ sha256: "sha256",
1536
+ uri: "uri",
1537
+ albumUri: "albumUri",
1538
+ artistUri: "artistUri",
1539
+ createdAt: "createdAt"
1540
+ };
1541
+ var AlbumFields = {
1542
+ title: "title",
1543
+ artist: "artist",
1544
+ year: "year",
1545
+ releaseDate: "releaseDate",
1546
+ sha256: "sha256",
1547
+ uri: "uri",
1548
+ artistUri: "artistUri",
1549
+ createdAt: "createdAt"
1550
+ };
1551
+ var ArtistFields = {
1552
+ name: "name",
1553
+ genres: "genres",
1554
+ bornIn: "bornIn",
1555
+ born: "born",
1556
+ died: "died",
1557
+ sha256: "sha256",
1558
+ uri: "uri",
1559
+ createdAt: "createdAt"
1560
+ };
1561
+ var ScrobbleFields = {
1562
+ uri: "uri",
1563
+ date: "date",
1564
+ timestamp: "timestamp",
1565
+ title: "title",
1566
+ artist: "artist",
1567
+ album: "album",
1568
+ trackTitle: "track.title",
1569
+ trackArtist: "track.artist",
1570
+ trackAlbum: "track.album",
1571
+ trackAlbumArtist: "track.albumArtist",
1572
+ trackGenre: "track.genre",
1573
+ trackDuration: "track.duration",
1574
+ trackIsrc: "track.isrc",
1575
+ trackMbId: "track.mbId",
1576
+ userDid: "user.did",
1577
+ userHandle: "user.handle",
1578
+ userDisplayName: "user.displayName",
1579
+ artistName: "artist.name",
1580
+ artistGenres: "artist.genres"
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
+ }
1609
1597
 
1610
1598
  // src/index.ts
1611
1599
  init_errors();
1612
1600
  export {
1613
1601
  Agent,
1602
+ AlbumFields,
1603
+ ArtistFields,
1614
1604
  DEFAULT_APPVIEW,
1615
1605
  DEFAULT_JETSTREAM_SERVERS,
1616
1606
  DEFAULT_MATCH_SONG_PER_HOUR,
1617
1607
  DEFAULT_REMOTE_WS,
1608
+ Filter,
1618
1609
  Interval,
1619
1610
  MAX_SAFE_WRITES_PER_HOUR,
1620
1611
  RemoteController,
1621
1612
  RemotePlayer,
1622
1613
  RockskyClient,
1623
1614
  RockskyError,
1624
- RockskyIndex,
1625
1615
  RockskyLibrary,
1616
+ ScrobbleFields,
1617
+ SongFields,
1626
1618
  albumHash,
1627
1619
  artistHash,
1628
1620
  runJetstream,
1629
- songHash,
1630
- totalIndexed
1621
+ songHash
1631
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.12.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
  /**