@freqhole/api-client 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/auth-test.html +651 -0
  2. package/package.json +42 -0
  3. package/src/AdminClient.ts +154 -0
  4. package/src/CharnelLocalTransport.ts +598 -0
  5. package/src/CharnelTransport.ts +616 -0
  6. package/src/FreqholeClient.ts +261 -0
  7. package/src/WasmTransport.ts +1077 -0
  8. package/src/automerge/IrohNetworkAdapter.ts +727 -0
  9. package/src/automerge/index.ts +14 -0
  10. package/src/codegen/admin_commands.ts +61 -0
  11. package/src/codegen/routes.ts +213 -0
  12. package/src/codegen/schema.ts +6721 -0
  13. package/src/domains/admin.ts +97 -0
  14. package/src/domains/app.ts +98 -0
  15. package/src/domains/auth.ts +191 -0
  16. package/src/domains/favorites.types.ts +52 -0
  17. package/src/domains/jobs.ts +48 -0
  18. package/src/domains/music.ts +1935 -0
  19. package/src/domains/types.ts +19 -0
  20. package/src/domains/upload.ts +214 -0
  21. package/src/index.ts +48 -0
  22. package/src/permissions.ts +468 -0
  23. package/src/playlistz/convert.ts +267 -0
  24. package/src/playlistz/index.ts +6 -0
  25. package/src/playlistz/mutations.ts +181 -0
  26. package/src/playlistz/protocol.ts +222 -0
  27. package/src/playlistz/schema.ts +138 -0
  28. package/src/playlistz/shareLink.ts +76 -0
  29. package/src/storage/blobs.ts +272 -0
  30. package/src/storage/identity.ts +190 -0
  31. package/src/storage/index.ts +5 -0
  32. package/src/storage/webLocks.ts +92 -0
  33. package/src/test/automerge.ts +796 -0
  34. package/src/test/coverage.ts +105 -0
  35. package/src/test/fixtures.ts +460 -0
  36. package/src/test/integration.ts +320 -0
  37. package/src/test/playlistz.ts +631 -0
  38. package/src/test/stateful.ts +561 -0
  39. package/src/test/storage.ts +443 -0
  40. package/src/test.ts +75 -0
  41. package/src/transport.ts +391 -0
  42. package/src/utils.ts +79 -0
  43. package/src/webauthn.ts +228 -0
  44. package/tsconfig.json +22 -0
  45. package/vite.config.js +9 -0
@@ -0,0 +1,1935 @@
1
+ // music domain methods for FreqholeClient
2
+
3
+ import { routes } from "../codegen/routes.js";
4
+ import type * as s from "../codegen/schema.js";
5
+ import {
6
+ UpdateAlbumRequestSchema,
7
+ UpdateArtistRequestSchema,
8
+ UpdateSongsRequestSchema,
9
+ } from "../codegen/schema.js";
10
+ import { ListFavoritesResponseSchema } from "./favorites.types.js";
11
+ import type { CallFn } from "./types.js";
12
+
13
+ // partial schemas for update operations
14
+ const UpdateSongsRequestPartialSchema =
15
+ UpdateSongsRequestSchema.partial().required({ song_ids: true });
16
+ const UpdateArtistRequestPartialSchema =
17
+ UpdateArtistRequestSchema.partial().required({ artist_id: true });
18
+ const UpdateAlbumRequestPartialSchema =
19
+ UpdateAlbumRequestSchema.partial().required({ album_id: true });
20
+
21
+ export function createMusicMethods(call: CallFn) {
22
+ return {
23
+ // search
24
+ search: (params: s.SearchRequest) => {
25
+ return call(
26
+ "music",
27
+ "search",
28
+ routes.music.search.resp,
29
+ routes.music.search.req,
30
+ routes.music.search.method,
31
+ routes.music.search.path,
32
+ params,
33
+ );
34
+ },
35
+
36
+ suggestions: (params: s.SuggestionsRequest) => {
37
+ return call(
38
+ "music",
39
+ "suggestions",
40
+ routes.music.suggestions.resp,
41
+ routes.music.suggestions.req,
42
+ routes.music.suggestions.method,
43
+ routes.music.suggestions.path,
44
+ params,
45
+ );
46
+ },
47
+
48
+ // songs
49
+ querySongs: (params: s.QueryParams) => {
50
+ return call(
51
+ "music",
52
+ "query_songs",
53
+ routes.music.query_songs.resp,
54
+ routes.music.query_songs.req,
55
+ routes.music.query_songs.method,
56
+ routes.music.query_songs.path,
57
+ params,
58
+ );
59
+ },
60
+
61
+ recentSongs: (params: s.RecentSongsRequest) => {
62
+ return call(
63
+ "music",
64
+ "recent_songs",
65
+ routes.music.recent_songs.resp,
66
+ routes.music.recent_songs.req,
67
+ routes.music.recent_songs.method,
68
+ routes.music.recent_songs.path,
69
+ params,
70
+ );
71
+ },
72
+
73
+ updateSongs: (
74
+ params: Partial<s.UpdateSongsRequest> & { song_ids: string[] },
75
+ ) => {
76
+ return call(
77
+ "music",
78
+ "update_songs",
79
+ routes.music.update_songs.resp,
80
+ UpdateSongsRequestPartialSchema,
81
+ routes.music.update_songs.method,
82
+ routes.music.update_songs.path,
83
+ params,
84
+ );
85
+ },
86
+
87
+ deleteSong: (params: s.DeleteSongRequest) => {
88
+ return call(
89
+ "music",
90
+ "delete_song",
91
+ routes.music.delete_song.resp,
92
+ routes.music.delete_song.req,
93
+ routes.music.delete_song.method,
94
+ routes.music.delete_song.path,
95
+ params,
96
+ );
97
+ },
98
+
99
+ bulkDeleteSongs: (params: s.BulkDeleteSongsRequest) => {
100
+ return call(
101
+ "music",
102
+ "bulk_delete_songs",
103
+ routes.music.bulk_delete_songs.resp,
104
+ routes.music.bulk_delete_songs.req,
105
+ routes.music.bulk_delete_songs.method,
106
+ routes.music.bulk_delete_songs.path,
107
+ params,
108
+ );
109
+ },
110
+
111
+ bulkClearSongArtwork: (params: s.BulkClearSongArtworkRequest) => {
112
+ return call(
113
+ "music",
114
+ "bulk_clear_song_artwork",
115
+ routes.music.bulk_clear_song_artwork.resp,
116
+ routes.music.bulk_clear_song_artwork.req,
117
+ routes.music.bulk_clear_song_artwork.method,
118
+ routes.music.bulk_clear_song_artwork.path,
119
+ params,
120
+ );
121
+ },
122
+
123
+ // albums
124
+ queryAlbums: (params: s.QueryParams) => {
125
+ return call(
126
+ "music",
127
+ "query_albums",
128
+ routes.music.query_albums.resp,
129
+ routes.music.query_albums.req,
130
+ routes.music.query_albums.method,
131
+ routes.music.query_albums.path,
132
+ params,
133
+ );
134
+ },
135
+
136
+ queryAlbumStatusCounts: (params: s.QueryParams) => {
137
+ return call(
138
+ "music",
139
+ "query_album_status_counts",
140
+ routes.music.query_album_status_counts.resp,
141
+ routes.music.query_album_status_counts.req,
142
+ routes.music.query_album_status_counts.method,
143
+ routes.music.query_album_status_counts.path,
144
+ params,
145
+ );
146
+ },
147
+
148
+ getAlbum: (params: s.GetAlbumRequest) => {
149
+ return call(
150
+ "music",
151
+ "get_album",
152
+ routes.music.get_album.resp,
153
+ routes.music.get_album.req,
154
+ routes.music.get_album.method,
155
+ routes.music.get_album.path,
156
+ params,
157
+ );
158
+ },
159
+
160
+ updateAlbum: (
161
+ params: Partial<s.UpdateAlbumRequest> & { album_id: string },
162
+ ) => {
163
+ return call(
164
+ "music",
165
+ "update_album",
166
+ routes.music.update_album.resp,
167
+ UpdateAlbumRequestPartialSchema,
168
+ routes.music.update_album.method,
169
+ routes.music.update_album.path,
170
+ params,
171
+ );
172
+ },
173
+
174
+ deleteAlbum: (params: s.DeleteAlbumRequest) => {
175
+ return call(
176
+ "music",
177
+ "delete_album",
178
+ routes.music.delete_album.resp,
179
+ routes.music.delete_album.req,
180
+ routes.music.delete_album.method,
181
+ routes.music.delete_album.path,
182
+ params,
183
+ );
184
+ },
185
+
186
+ getAlbumImages: (params: { id: string }) => {
187
+ return call(
188
+ "music",
189
+ "get_album_images",
190
+ routes.music.get_album_images.resp,
191
+ routes.music.get_album_images.req,
192
+ routes.music.get_album_images.method,
193
+ routes.music.get_album_images.path,
194
+ params,
195
+ );
196
+ },
197
+
198
+ // bulk enrichment review (phase 11)
199
+ proposeTaxons: (params: s.ProposeTaxonsRequest) => {
200
+ return call(
201
+ "music",
202
+ "propose_taxons",
203
+ routes.music.propose_taxons.resp,
204
+ routes.music.propose_taxons.req,
205
+ routes.music.propose_taxons.method,
206
+ routes.music.propose_taxons.path,
207
+ params,
208
+ );
209
+ },
210
+
211
+ applyTaxonProposals: (params: s.ApplyTaxonProposalsRequest) => {
212
+ return call(
213
+ "music",
214
+ "apply_taxon_proposals",
215
+ routes.music.apply_taxon_proposals.resp,
216
+ routes.music.apply_taxon_proposals.req,
217
+ routes.music.apply_taxon_proposals.method,
218
+ routes.music.apply_taxon_proposals.path,
219
+ params,
220
+ );
221
+ },
222
+
223
+ setMbLookupStatus: (params: s.SetMbLookupStatusRequest) => {
224
+ return call(
225
+ "music",
226
+ "set_mb_lookup_status",
227
+ routes.music.set_mb_lookup_status.resp,
228
+ routes.music.set_mb_lookup_status.req,
229
+ routes.music.set_mb_lookup_status.method,
230
+ routes.music.set_mb_lookup_status.path,
231
+ params,
232
+ );
233
+ },
234
+
235
+ // external-url proposals (phase 11.x)
236
+ proposeExternalUrls: (params: s.ProposeExternalUrlsRequest) => {
237
+ return call(
238
+ "music",
239
+ "propose_external_urls",
240
+ routes.music.propose_external_urls.resp,
241
+ routes.music.propose_external_urls.req,
242
+ routes.music.propose_external_urls.method,
243
+ routes.music.propose_external_urls.path,
244
+ params,
245
+ );
246
+ },
247
+
248
+ applyExternalUrls: (params: s.ApplyExternalUrlsRequest) => {
249
+ return call(
250
+ "music",
251
+ "apply_external_urls",
252
+ routes.music.apply_external_urls.resp,
253
+ routes.music.apply_external_urls.req,
254
+ routes.music.apply_external_urls.method,
255
+ routes.music.apply_external_urls.path,
256
+ params,
257
+ );
258
+ },
259
+
260
+ // artist bio review (slice 4a)
261
+ proposeArtistBios: (params: s.ProposeArtistBiosRequest) => {
262
+ return call(
263
+ "music",
264
+ "propose_artist_bios",
265
+ routes.music.propose_artist_bios.resp,
266
+ routes.music.propose_artist_bios.req,
267
+ routes.music.propose_artist_bios.method,
268
+ routes.music.propose_artist_bios.path,
269
+ params,
270
+ );
271
+ },
272
+
273
+ applyArtistBio: (params: s.ApplyArtistBioRequest) => {
274
+ return call(
275
+ "music",
276
+ "apply_artist_bio",
277
+ routes.music.apply_artist_bio.resp,
278
+ routes.music.apply_artist_bio.req,
279
+ routes.music.apply_artist_bio.method,
280
+ routes.music.apply_artist_bio.path,
281
+ params,
282
+ );
283
+ },
284
+
285
+ // related-artist review (slice 4c)
286
+ proposeRelatedArtists: (params: s.ProposeRelatedArtistsRequest) => {
287
+ return call(
288
+ "music",
289
+ "propose_related_artists",
290
+ routes.music.propose_related_artists.resp,
291
+ routes.music.propose_related_artists.req,
292
+ routes.music.propose_related_artists.method,
293
+ routes.music.propose_related_artists.path,
294
+ params,
295
+ );
296
+ },
297
+
298
+ applyRelatedArtists: (params: s.ApplyRelatedArtistsRequest) => {
299
+ return call(
300
+ "music",
301
+ "apply_related_artists",
302
+ routes.music.apply_related_artists.resp,
303
+ routes.music.apply_related_artists.req,
304
+ routes.music.apply_related_artists.method,
305
+ routes.music.apply_related_artists.path,
306
+ params,
307
+ );
308
+ },
309
+
310
+ // artists
311
+ queryArtists: (params: s.QueryParams) => {
312
+ return call(
313
+ "music",
314
+ "query_artists",
315
+ routes.music.query_artists.resp,
316
+ routes.music.query_artists.req,
317
+ routes.music.query_artists.method,
318
+ routes.music.query_artists.path,
319
+ params,
320
+ );
321
+ },
322
+
323
+ getArtist: (params: s.GetArtistRequest) => {
324
+ return call(
325
+ "music",
326
+ "get_artist",
327
+ routes.music.get_artist.resp,
328
+ routes.music.get_artist.req,
329
+ routes.music.get_artist.method,
330
+ routes.music.get_artist.path,
331
+ params,
332
+ );
333
+ },
334
+
335
+ createArtist: (params: s.CreateArtistRequest) => {
336
+ return call(
337
+ "music",
338
+ "create_artist",
339
+ routes.music.create_artist.resp,
340
+ routes.music.create_artist.req,
341
+ routes.music.create_artist.method,
342
+ routes.music.create_artist.path,
343
+ params,
344
+ );
345
+ },
346
+
347
+ updateArtist: (
348
+ params: Partial<s.UpdateArtistRequest> & { artist_id: string },
349
+ ) => {
350
+ return call(
351
+ "music",
352
+ "update_artist",
353
+ routes.music.update_artist.resp,
354
+ UpdateArtistRequestPartialSchema,
355
+ routes.music.update_artist.method,
356
+ routes.music.update_artist.path,
357
+ params,
358
+ );
359
+ },
360
+
361
+ deleteArtist: (params: s.DeleteArtistRequest) => {
362
+ return call(
363
+ "music",
364
+ "delete_artist",
365
+ routes.music.delete_artist.resp,
366
+ routes.music.delete_artist.req,
367
+ routes.music.delete_artist.method,
368
+ routes.music.delete_artist.path,
369
+ params,
370
+ );
371
+ },
372
+
373
+ getArtistImages: (params: { id: string }) => {
374
+ return call(
375
+ "music",
376
+ "get_artist_images",
377
+ routes.music.get_artist_images.resp,
378
+ routes.music.get_artist_images.req,
379
+ routes.music.get_artist_images.method,
380
+ routes.music.get_artist_images.path,
381
+ params,
382
+ );
383
+ },
384
+
385
+ // genres are now exposed via the unified taxonomy api
386
+ // (queryTaxons / getTaxon with kind_slug='genre'). dedicated genre
387
+ // routes were removed during the taxonomy refactor.
388
+
389
+ // playlists
390
+ listPlaylists: (params: s.QueryParams) => {
391
+ return call(
392
+ "music",
393
+ "list_playlists",
394
+ routes.music.list_playlists.resp,
395
+ routes.music.list_playlists.req,
396
+ routes.music.list_playlists.method,
397
+ routes.music.list_playlists.path,
398
+ params,
399
+ );
400
+ },
401
+
402
+ getPlaylistById: (params: s.GetPlaylistRequest) => {
403
+ return call(
404
+ "music",
405
+ "get_playlist_by_id",
406
+ routes.music.get_playlist_by_id.resp,
407
+ routes.music.get_playlist_by_id.req,
408
+ routes.music.get_playlist_by_id.method,
409
+ routes.music.get_playlist_by_id.path,
410
+ params,
411
+ );
412
+ },
413
+
414
+ getPlaylistEtag: (params: s.GetPlaylistRequest) => {
415
+ const path = routes.music.get_playlist_etag.path.replace(
416
+ "{id}",
417
+ params.id,
418
+ );
419
+ return call(
420
+ "music",
421
+ "get_playlist_etag",
422
+ routes.music.get_playlist_etag.resp,
423
+ routes.music.get_playlist_etag.req,
424
+ routes.music.get_playlist_etag.method,
425
+ path,
426
+ params,
427
+ );
428
+ },
429
+
430
+ getPlaylistImages: (params: { id: string }) => {
431
+ return call(
432
+ "music",
433
+ "get_playlist_images",
434
+ routes.music.get_playlist_images.resp,
435
+ routes.music.get_playlist_images.req,
436
+ routes.music.get_playlist_images.method,
437
+ routes.music.get_playlist_images.path,
438
+ params,
439
+ );
440
+ },
441
+
442
+ createPlaylist: (params: s.CreatePlaylistRequest) => {
443
+ return call(
444
+ "music",
445
+ "create_playlist",
446
+ routes.music.create_playlist.resp,
447
+ routes.music.create_playlist.req,
448
+ routes.music.create_playlist.method,
449
+ routes.music.create_playlist.path,
450
+ params,
451
+ );
452
+ },
453
+
454
+ updatePlaylist: (params: s.UpdatePlaylistRequest) => {
455
+ return call(
456
+ "music",
457
+ "update_playlist",
458
+ routes.music.update_playlist.resp,
459
+ routes.music.update_playlist.req,
460
+ routes.music.update_playlist.method,
461
+ routes.music.update_playlist.path,
462
+ params,
463
+ );
464
+ },
465
+
466
+ deletePlaylist: (params: s.DeletePlaylistRequest) => {
467
+ return call(
468
+ "music",
469
+ "delete_playlist",
470
+ routes.music.delete_playlist.resp,
471
+ routes.music.delete_playlist.req,
472
+ routes.music.delete_playlist.method,
473
+ routes.music.delete_playlist.path,
474
+ params,
475
+ );
476
+ },
477
+
478
+ queryPlaylistSongs: (params: s.QueryPlaylistSongsRequest) => {
479
+ return call(
480
+ "music",
481
+ "query_playlist_songs",
482
+ routes.music.query_playlist_songs.resp,
483
+ routes.music.query_playlist_songs.req,
484
+ routes.music.query_playlist_songs.method,
485
+ routes.music.query_playlist_songs.path,
486
+ params,
487
+ );
488
+ },
489
+
490
+ addSongsToPlaylist: (params: s.AddSongsToPlaylistRequest) => {
491
+ return call(
492
+ "music",
493
+ "add_songs_to_playlist",
494
+ routes.music.add_songs_to_playlist.resp,
495
+ routes.music.add_songs_to_playlist.req,
496
+ routes.music.add_songs_to_playlist.method,
497
+ routes.music.add_songs_to_playlist.path,
498
+ params,
499
+ );
500
+ },
501
+
502
+ removeSongsFromPlaylist: (params: s.RemoveSongsFromPlaylistRequest) => {
503
+ return call(
504
+ "music",
505
+ "remove_songs_from_playlist",
506
+ routes.music.remove_songs_from_playlist.resp,
507
+ routes.music.remove_songs_from_playlist.req,
508
+ routes.music.remove_songs_from_playlist.method,
509
+ routes.music.remove_songs_from_playlist.path,
510
+ params,
511
+ );
512
+ },
513
+
514
+ reorderPlaylistSongs: (params: s.ReorderPlaylistSongsRequest) => {
515
+ return call(
516
+ "music",
517
+ "reorder_playlist_songs",
518
+ routes.music.reorder_playlist_songs.resp,
519
+ routes.music.reorder_playlist_songs.req,
520
+ routes.music.reorder_playlist_songs.method,
521
+ routes.music.reorder_playlist_songs.path,
522
+ params,
523
+ );
524
+ },
525
+
526
+ // tags
527
+ listTags: () => {
528
+ return call(
529
+ "music",
530
+ "list_tags",
531
+ routes.music.list_tags.resp,
532
+ routes.music.list_tags.req,
533
+ routes.music.list_tags.method,
534
+ routes.music.list_tags.path,
535
+ );
536
+ },
537
+
538
+ getTag: (params: s.GetTagRequest) => {
539
+ return call(
540
+ "music",
541
+ "get_tag",
542
+ routes.music.get_tag.resp,
543
+ routes.music.get_tag.req,
544
+ routes.music.get_tag.method,
545
+ routes.music.get_tag.path,
546
+ params,
547
+ );
548
+ },
549
+
550
+ queryTags: (params: s.QueryTagsRequest) => {
551
+ return call(
552
+ "music",
553
+ "query_tags",
554
+ routes.music.query_tags.resp,
555
+ routes.music.query_tags.req,
556
+ routes.music.query_tags.method,
557
+ routes.music.query_tags.path,
558
+ params,
559
+ );
560
+ },
561
+
562
+ deleteTag: (params: s.DeleteTagRequest) => {
563
+ return call(
564
+ "music",
565
+ "delete_tag",
566
+ routes.music.delete_tag.resp,
567
+ routes.music.delete_tag.req,
568
+ routes.music.delete_tag.method,
569
+ routes.music.delete_tag.path,
570
+ params,
571
+ );
572
+ },
573
+
574
+ getAlbumsTags: (params: s.GetAlbumsTagsRequest) => {
575
+ return call(
576
+ "music",
577
+ "get_albums_tags",
578
+ routes.music.get_albums_tags.resp,
579
+ routes.music.get_albums_tags.req,
580
+ routes.music.get_albums_tags.method,
581
+ routes.music.get_albums_tags.path,
582
+ params,
583
+ );
584
+ },
585
+
586
+ addAlbumsTags: (params: s.AddAlbumsTagsRequest) => {
587
+ return call(
588
+ "music",
589
+ "add_albums_tags",
590
+ routes.music.add_albums_tags.resp,
591
+ routes.music.add_albums_tags.req,
592
+ routes.music.add_albums_tags.method,
593
+ routes.music.add_albums_tags.path,
594
+ params,
595
+ );
596
+ },
597
+
598
+ removeAlbumsTags: (params: s.RemoveAlbumsTagsRequest) => {
599
+ return call(
600
+ "music",
601
+ "remove_albums_tags",
602
+ routes.music.remove_albums_tags.resp,
603
+ routes.music.remove_albums_tags.req,
604
+ routes.music.remove_albums_tags.method,
605
+ routes.music.remove_albums_tags.path,
606
+ params,
607
+ );
608
+ },
609
+
610
+ replaceAlbumsTags: (params: s.ReplaceAlbumsTagsRequest) => {
611
+ return call(
612
+ "music",
613
+ "replace_albums_tags",
614
+ routes.music.replace_albums_tags.resp,
615
+ routes.music.replace_albums_tags.req,
616
+ routes.music.replace_albums_tags.method,
617
+ routes.music.replace_albums_tags.path,
618
+ params,
619
+ );
620
+ },
621
+
622
+ // ratings
623
+ setRating: (params: s.SetRatingRequest) => {
624
+ return call(
625
+ "music",
626
+ "set_rating",
627
+ routes.music.set_rating.resp,
628
+ routes.music.set_rating.req,
629
+ routes.music.set_rating.method,
630
+ routes.music.set_rating.path,
631
+ params,
632
+ );
633
+ },
634
+
635
+ removeRating: (params: s.RemoveRatingRequest) => {
636
+ return call(
637
+ "music",
638
+ "remove_rating",
639
+ routes.music.remove_rating.resp,
640
+ routes.music.remove_rating.req,
641
+ routes.music.remove_rating.method,
642
+ routes.music.remove_rating.path,
643
+ params,
644
+ );
645
+ },
646
+
647
+ getRatingStats: (params: s.GetRatingStatsRequest) => {
648
+ return call(
649
+ "music",
650
+ "get_rating_stats",
651
+ routes.music.get_rating_stats.resp,
652
+ routes.music.get_rating_stats.req,
653
+ routes.music.get_rating_stats.method,
654
+ routes.music.get_rating_stats.path,
655
+ params,
656
+ );
657
+ },
658
+
659
+ // favorites
660
+ listFavorites: (params: s.ListFavoritesRequest) => {
661
+ return call(
662
+ "music",
663
+ "list_favorites",
664
+ ListFavoritesResponseSchema, // use hand-rolled schema for discriminated union
665
+ routes.music.list_favorites.req,
666
+ routes.music.list_favorites.method,
667
+ routes.music.list_favorites.path,
668
+ params,
669
+ );
670
+ },
671
+
672
+ setFavorite: (params: s.SetFavoriteRequest) => {
673
+ return call(
674
+ "music",
675
+ "set_favorite",
676
+ routes.music.set_favorite.resp,
677
+ routes.music.set_favorite.req,
678
+ routes.music.set_favorite.method,
679
+ routes.music.set_favorite.path,
680
+ params,
681
+ );
682
+ },
683
+
684
+ listBeloved: (params: s.ListBelovedRequest = {}) => {
685
+ return call(
686
+ "music",
687
+ "list_beloved",
688
+ routes.music.list_beloved.resp,
689
+ routes.music.list_beloved.req,
690
+ routes.music.list_beloved.method,
691
+ routes.music.list_beloved.path,
692
+ params,
693
+ );
694
+ },
695
+
696
+ // analytics
697
+ recordPlay: (params: s.RecordPlayRequest) => {
698
+ return call(
699
+ "music",
700
+ "record_play",
701
+ routes.music.record_play.resp,
702
+ routes.music.record_play.req,
703
+ routes.music.record_play.method,
704
+ routes.music.record_play.path,
705
+ params,
706
+ );
707
+ },
708
+
709
+ activityFeed: (params: s.FeedRequest) => {
710
+ return call(
711
+ "music",
712
+ "activity_feed",
713
+ routes.music.activity_feed.resp,
714
+ routes.music.activity_feed.req,
715
+ routes.music.activity_feed.method,
716
+ routes.music.activity_feed.path,
717
+ params,
718
+ );
719
+ },
720
+
721
+ listeningHistory: (params: s.ListeningHistoryRequest) => {
722
+ return call(
723
+ "music",
724
+ "listening_history",
725
+ routes.music.listening_history.resp,
726
+ routes.music.listening_history.req,
727
+ routes.music.listening_history.method,
728
+ routes.music.listening_history.path,
729
+ params,
730
+ );
731
+ },
732
+
733
+ songAnalytics: (params: s.SongAnalyticsRequest) => {
734
+ return call(
735
+ "music",
736
+ "song_analytics",
737
+ routes.music.song_analytics.resp,
738
+ routes.music.song_analytics.req,
739
+ routes.music.song_analytics.method,
740
+ routes.music.song_analytics.path,
741
+ params,
742
+ );
743
+ },
744
+
745
+ topSongs: (params: s.TopSongsRequest) => {
746
+ return call(
747
+ "music",
748
+ "top_songs",
749
+ routes.music.top_songs.resp,
750
+ routes.music.top_songs.req,
751
+ routes.music.top_songs.method,
752
+ routes.music.top_songs.path,
753
+ params,
754
+ );
755
+ },
756
+
757
+ topAlbums: (params: s.TopAlbumsRequest) => {
758
+ return call(
759
+ "music",
760
+ "top_albums",
761
+ routes.music.top_albums.resp,
762
+ routes.music.top_albums.req,
763
+ routes.music.top_albums.method,
764
+ routes.music.top_albums.path,
765
+ params,
766
+ );
767
+ },
768
+
769
+ topArtists: (params: s.TopArtistsRequest) => {
770
+ return call(
771
+ "music",
772
+ "top_artists",
773
+ routes.music.top_artists.resp,
774
+ routes.music.top_artists.req,
775
+ routes.music.top_artists.method,
776
+ routes.music.top_artists.path,
777
+ params,
778
+ );
779
+ },
780
+
781
+ // listen sessions
782
+ createListenSession: (params: s.CreateListenSessionRequest) => {
783
+ return call(
784
+ "music",
785
+ "create_listen_session",
786
+ routes.music.create_listen_session.resp,
787
+ routes.music.create_listen_session.req,
788
+ routes.music.create_listen_session.method,
789
+ routes.music.create_listen_session.path,
790
+ params,
791
+ );
792
+ },
793
+
794
+ getListenSession: (id: string) => {
795
+ const path = routes.music.get_listen_session.path.replace("{id}", id);
796
+ return call(
797
+ "music",
798
+ "get_listen_session",
799
+ routes.music.get_listen_session.resp,
800
+ routes.music.get_listen_session.req,
801
+ routes.music.get_listen_session.method,
802
+ path,
803
+ );
804
+ },
805
+
806
+ deleteListenSession: (id: string) => {
807
+ return call(
808
+ "music",
809
+ "delete_listen_session",
810
+ routes.music.delete_listen_session.resp,
811
+ routes.music.delete_listen_session.req,
812
+ routes.music.delete_listen_session.method,
813
+ routes.music.delete_listen_session.path,
814
+ { id },
815
+ );
816
+ },
817
+
818
+ deleteFeedEvent: (id: string) => {
819
+ return call(
820
+ "music",
821
+ "delete_feed_event",
822
+ routes.music.delete_feed_event.resp,
823
+ routes.music.delete_feed_event.req,
824
+ routes.music.delete_feed_event.method,
825
+ routes.music.delete_feed_event.path,
826
+ { id },
827
+ );
828
+ },
829
+
830
+ recordPlaylistPlay: (id: string) => {
831
+ return call(
832
+ "music",
833
+ "record_playlist_play",
834
+ routes.music.record_playlist_play.resp,
835
+ routes.music.record_playlist_play.req,
836
+ routes.music.record_playlist_play.method,
837
+ routes.music.record_playlist_play.path,
838
+ { id },
839
+ );
840
+ },
841
+
842
+ updateListenSessionProgress: (
843
+ id: string,
844
+ params: s.UpdateListenSessionProgressRequest,
845
+ ) => {
846
+ const path = routes.music.update_listen_session_progress.path.replace(
847
+ "{id}",
848
+ id,
849
+ );
850
+ return call(
851
+ "music",
852
+ "update_listen_session_progress",
853
+ routes.music.update_listen_session_progress.resp,
854
+ routes.music.update_listen_session_progress.req,
855
+ routes.music.update_listen_session_progress.method,
856
+ path,
857
+ params,
858
+ );
859
+ },
860
+
861
+ updateListenSessionStatus: (id: string, status: string) => {
862
+ const path = routes.music.update_listen_session_status.path
863
+ .replace("{id}", id)
864
+ .replace("{status}", status);
865
+ return call(
866
+ "music",
867
+ "update_listen_session_status",
868
+ routes.music.update_listen_session_status.resp,
869
+ routes.music.update_listen_session_status.req,
870
+ routes.music.update_listen_session_status.method,
871
+ path,
872
+ );
873
+ },
874
+
875
+ updateListenSessionSongs: (
876
+ id: string,
877
+ params: s.UpdateListenSessionSongsRequest,
878
+ ) => {
879
+ const path = routes.music.update_listen_session_songs.path.replace(
880
+ "{id}",
881
+ id,
882
+ );
883
+ return call(
884
+ "music",
885
+ "update_listen_session_songs",
886
+ routes.music.update_listen_session_songs.resp,
887
+ routes.music.update_listen_session_songs.req,
888
+ routes.music.update_listen_session_songs.method,
889
+ path,
890
+ params,
891
+ );
892
+ },
893
+
894
+ listListenSessions: (params: s.ListListenSessionsRequest) => {
895
+ return call(
896
+ "music",
897
+ "list_listen_sessions",
898
+ routes.music.list_listen_sessions.resp,
899
+ routes.music.list_listen_sessions.req,
900
+ routes.music.list_listen_sessions.method,
901
+ routes.music.list_listen_sessions.path,
902
+ params,
903
+ );
904
+ },
905
+
906
+ // jobs
907
+ getJobStatus: (params: s.GetJobsStatusRequest) => {
908
+ return call(
909
+ "music",
910
+ "get_job_status",
911
+ routes.music.get_job_status.resp,
912
+ routes.music.get_job_status.req,
913
+ routes.music.get_job_status.method,
914
+ routes.music.get_job_status.path,
915
+ params,
916
+ );
917
+ },
918
+
919
+ listJobs: (params: s.ListJobsRequest) => {
920
+ return call(
921
+ "music",
922
+ "list_jobs",
923
+ routes.music.list_jobs.resp,
924
+ routes.music.list_jobs.req,
925
+ routes.music.list_jobs.method,
926
+ routes.music.list_jobs.path,
927
+ params,
928
+ );
929
+ },
930
+
931
+ // blobs
932
+ blobMetadata: (params: { id: string }) => {
933
+ return call(
934
+ "music",
935
+ "blob_metadata",
936
+ routes.music.blob_metadata.resp,
937
+ routes.music.blob_metadata.req,
938
+ routes.music.blob_metadata.method,
939
+ routes.music.blob_metadata.path,
940
+ params,
941
+ );
942
+ },
943
+
944
+ // images
945
+ deleteImage: (params: s.DeleteImageRequest) => {
946
+ return call(
947
+ "music",
948
+ "delete_image",
949
+ routes.music.delete_image.resp,
950
+ routes.music.delete_image.req,
951
+ routes.music.delete_image.method,
952
+ routes.music.delete_image.path,
953
+ params,
954
+ );
955
+ },
956
+
957
+ setPrimaryImage: (params: s.SetPrimaryImageRequest) => {
958
+ return call(
959
+ "music",
960
+ "set_primary_image",
961
+ routes.music.set_primary_image.resp,
962
+ routes.music.set_primary_image.req,
963
+ routes.music.set_primary_image.method,
964
+ routes.music.set_primary_image.path,
965
+ params,
966
+ );
967
+ },
968
+
969
+ // musicbrainz
970
+ getMusicbrainzRelease: (params: s.GetReleaseRequest) => {
971
+ return call(
972
+ "music",
973
+ "get_musicbrainz_release",
974
+ routes.music.get_musicbrainz_release.resp,
975
+ routes.music.get_musicbrainz_release.req,
976
+ routes.music.get_musicbrainz_release.method,
977
+ routes.music.get_musicbrainz_release.path,
978
+ params,
979
+ );
980
+ },
981
+
982
+ searchMusicbrainzReleases: (params: s.SearchReleasesRequest) => {
983
+ return call(
984
+ "music",
985
+ "search_musicbrainz_releases",
986
+ routes.music.search_musicbrainz_releases.resp,
987
+ routes.music.search_musicbrainz_releases.req,
988
+ routes.music.search_musicbrainz_releases.method,
989
+ routes.music.search_musicbrainz_releases.path,
990
+ params,
991
+ );
992
+ },
993
+
994
+ // fetch/import
995
+ createFetchJob: (params: s.FetchMediaParams) => {
996
+ return call(
997
+ "music",
998
+ "create_fetch_job",
999
+ routes.music.create_fetch_job.resp,
1000
+ routes.music.create_fetch_job.req,
1001
+ routes.music.create_fetch_job.method,
1002
+ routes.music.create_fetch_job.path,
1003
+ params,
1004
+ );
1005
+ },
1006
+
1007
+ createPrecheckFetchJob: (params: s.PreCheckFetchParams) => {
1008
+ return call(
1009
+ "music",
1010
+ "create_precheck_fetch_job",
1011
+ routes.music.create_precheck_fetch_job.resp,
1012
+ routes.music.create_precheck_fetch_job.req,
1013
+ routes.music.create_precheck_fetch_job.method,
1014
+ routes.music.create_precheck_fetch_job.path,
1015
+ params,
1016
+ );
1017
+ },
1018
+
1019
+ cancelJob: (params: s.CancelJobRequest) => {
1020
+ return call(
1021
+ "music",
1022
+ "cancel_job",
1023
+ routes.music.cancel_job.resp,
1024
+ routes.music.cancel_job.req,
1025
+ routes.music.cancel_job.method,
1026
+ routes.music.cancel_job.path,
1027
+ params,
1028
+ );
1029
+ },
1030
+
1031
+ getFetchJob: (params: s.GetJobRequest) => {
1032
+ return call(
1033
+ "music",
1034
+ "get_fetch_job",
1035
+ routes.music.get_fetch_job.resp,
1036
+ routes.music.get_fetch_job.req,
1037
+ routes.music.get_fetch_job.method,
1038
+ routes.music.get_fetch_job.path,
1039
+ params,
1040
+ );
1041
+ },
1042
+
1043
+ // musicbrainz album-search bulk enqueue (admin only). spawns one
1044
+ // `MbAlbumSearch` job per album id, returns the job ids so the caller
1045
+ // can poll status.
1046
+ enqueueMbAlbumSearch: (params: s.EnqueueMbAlbumSearchRequest) => {
1047
+ return call(
1048
+ "music",
1049
+ "enqueue_mb_album_search",
1050
+ routes.music.enqueue_mb_album_search.resp,
1051
+ routes.music.enqueue_mb_album_search.req,
1052
+ routes.music.enqueue_mb_album_search.method,
1053
+ routes.music.enqueue_mb_album_search.path,
1054
+ params,
1055
+ );
1056
+ },
1057
+
1058
+ // confirm a musicbrainz candidate as the canonical match for an album
1059
+ // (admin only). updates `mb_lookup_status` to `Confirmed` and stamps
1060
+ // the chosen release/release-group ids into `metadata.musicbrainz`.
1061
+ confirmMbMatch: (params: s.ConfirmMbMatchRequest) => {
1062
+ return call(
1063
+ "music",
1064
+ "confirm_mb_match",
1065
+ routes.music.confirm_mb_match.resp,
1066
+ routes.music.confirm_mb_match.req,
1067
+ routes.music.confirm_mb_match.method,
1068
+ routes.music.confirm_mb_match.path,
1069
+ params,
1070
+ );
1071
+ },
1072
+
1073
+ // reject all candidates for an album (admin only). flips
1074
+ // `mb_lookup_status` to `Rejected` and clears stored candidates so
1075
+ // the next lookup starts fresh.
1076
+ rejectMbMatch: (params: s.RejectMbMatchRequest) => {
1077
+ return call(
1078
+ "music",
1079
+ "reject_mb_match",
1080
+ routes.music.reject_mb_match.resp,
1081
+ routes.music.reject_mb_match.req,
1082
+ routes.music.reject_mb_match.method,
1083
+ routes.music.reject_mb_match.path,
1084
+ params,
1085
+ );
1086
+ },
1087
+
1088
+ // bulk auto-confirm musicbrainz matches (admin only). confirms the
1089
+ // top candidate per album where it clears both a confidence floor
1090
+ // and a gap-to-#2 floor. returns per-album confirmed/skipped/errors.
1091
+ autoConfirmMbMatches: (params: s.AutoConfirmMbMatchesRequest) => {
1092
+ return call(
1093
+ "music",
1094
+ "auto_confirm_mb_matches",
1095
+ routes.music.auto_confirm_mb_matches.resp,
1096
+ routes.music.auto_confirm_mb_matches.req,
1097
+ routes.music.auto_confirm_mb_matches.method,
1098
+ routes.music.auto_confirm_mb_matches.path,
1099
+ params,
1100
+ );
1101
+ },
1102
+
1103
+ // last.fm album-detail bulk enqueue (admin only). spawns one
1104
+ // `LastFmAlbumDetail` job per album id, fetching album.getInfo and
1105
+ // artist.getInfo and storing the raw response under
1106
+ // `metadata.lastfm` for review.
1107
+ enqueueLastfmAlbumDetail: (params: s.EnqueueLastFmAlbumDetailRequest) => {
1108
+ return call(
1109
+ "music",
1110
+ "enqueue_lastfm_album_detail",
1111
+ routes.music.enqueue_lastfm_album_detail.resp,
1112
+ routes.music.enqueue_lastfm_album_detail.req,
1113
+ routes.music.enqueue_lastfm_album_detail.method,
1114
+ routes.music.enqueue_lastfm_album_detail.path,
1115
+ params,
1116
+ );
1117
+ },
1118
+
1119
+ // theaudiodb album-detail bulk enqueue (admin only). spawns one
1120
+ // `AudioDbAlbumDetail` job per album id, fetching the album record
1121
+ // (by mbid or text-search) and the artist record (by mbid), and
1122
+ // storing the captured snapshot under `metadata.audiodb` for review.
1123
+ enqueueAudiodbAlbumDetail: (params: s.EnqueueAudioDbAlbumDetailRequest) => {
1124
+ return call(
1125
+ "music",
1126
+ "enqueue_audiodb_album_detail",
1127
+ routes.music.enqueue_audiodb_album_detail.resp,
1128
+ routes.music.enqueue_audiodb_album_detail.req,
1129
+ routes.music.enqueue_audiodb_album_detail.method,
1130
+ routes.music.enqueue_audiodb_album_detail.path,
1131
+ params,
1132
+ );
1133
+ },
1134
+
1135
+ // special routes - these exist for route coverage but have alternate implementations
1136
+ // for actual blob streaming, use client.fetchBlob(id) instead
1137
+ streamBlob: (params: { id: string }) => {
1138
+ return call(
1139
+ "music",
1140
+ "stream_blob",
1141
+ routes.music.stream_blob.resp,
1142
+ routes.music.stream_blob.req,
1143
+ routes.music.stream_blob.method,
1144
+ routes.music.stream_blob.path,
1145
+ params,
1146
+ );
1147
+ },
1148
+
1149
+ // for blob thumbnails - returns binary image data
1150
+ getBlobThumbnail: (params: { id: string; size: string }) => {
1151
+ return call(
1152
+ "music",
1153
+ "get_blob_thumbnail",
1154
+ routes.music.get_blob_thumbnail.resp,
1155
+ routes.music.get_blob_thumbnail.req,
1156
+ routes.music.get_blob_thumbnail.method,
1157
+ routes.music.get_blob_thumbnail.path,
1158
+ params,
1159
+ );
1160
+ },
1161
+
1162
+ // for image uploads, use utils.uploadImage() which handles FormData properly
1163
+ uploadImage: () => {
1164
+ return call(
1165
+ "music",
1166
+ "upload_image",
1167
+ routes.music.upload_image.resp,
1168
+ routes.music.upload_image.req,
1169
+ routes.music.upload_image.method,
1170
+ routes.music.upload_image.path,
1171
+ undefined,
1172
+ );
1173
+ },
1174
+
1175
+ // for music uploads, use utils.uploadMusic() which handles FormData properly
1176
+ uploadMusic: () => {
1177
+ return call(
1178
+ "music",
1179
+ "upload_music",
1180
+ routes.music.upload_music.resp,
1181
+ routes.music.upload_music.req,
1182
+ routes.music.upload_music.method,
1183
+ routes.music.upload_music.path,
1184
+ undefined,
1185
+ );
1186
+ },
1187
+
1188
+ // ----------------------------------------------------------------------
1189
+ // taxonomy (cross-kind labels: genre, label, mood, era, region, ...)
1190
+ // ----------------------------------------------------------------------
1191
+
1192
+ listTaxonKinds: () => {
1193
+ return call(
1194
+ "music",
1195
+ "list_taxon_kinds",
1196
+ routes.music.list_taxon_kinds.resp,
1197
+ routes.music.list_taxon_kinds.req,
1198
+ routes.music.list_taxon_kinds.method,
1199
+ routes.music.list_taxon_kinds.path,
1200
+ undefined,
1201
+ );
1202
+ },
1203
+
1204
+ createTaxonKind: (params: s.CreateTaxonKindRequest) => {
1205
+ return call(
1206
+ "music",
1207
+ "create_taxon_kind",
1208
+ routes.music.create_taxon_kind.resp,
1209
+ routes.music.create_taxon_kind.req,
1210
+ routes.music.create_taxon_kind.method,
1211
+ routes.music.create_taxon_kind.path,
1212
+ params,
1213
+ );
1214
+ },
1215
+
1216
+ listTaxonsByKind: (params: s.ListTaxonsByKindRequest) => {
1217
+ return call(
1218
+ "music",
1219
+ "list_taxons_by_kind",
1220
+ routes.music.list_taxons_by_kind.resp,
1221
+ routes.music.list_taxons_by_kind.req,
1222
+ routes.music.list_taxons_by_kind.method,
1223
+ routes.music.list_taxons_by_kind.path,
1224
+ params,
1225
+ );
1226
+ },
1227
+
1228
+ queryTaxons: (params: s.QueryTaxonsRequest) => {
1229
+ return call(
1230
+ "music",
1231
+ "query_taxons",
1232
+ routes.music.query_taxons.resp,
1233
+ routes.music.query_taxons.req,
1234
+ routes.music.query_taxons.method,
1235
+ routes.music.query_taxons.path,
1236
+ params,
1237
+ );
1238
+ },
1239
+
1240
+ getTaxon: (params: s.GetTaxonRequest) => {
1241
+ return call(
1242
+ "music",
1243
+ "get_taxon",
1244
+ routes.music.get_taxon.resp,
1245
+ routes.music.get_taxon.req,
1246
+ routes.music.get_taxon.method,
1247
+ routes.music.get_taxon.path,
1248
+ params,
1249
+ );
1250
+ },
1251
+
1252
+ createTaxon: (params: s.CreateTaxonRequest) => {
1253
+ return call(
1254
+ "music",
1255
+ "create_taxon",
1256
+ routes.music.create_taxon.resp,
1257
+ routes.music.create_taxon.req,
1258
+ routes.music.create_taxon.method,
1259
+ routes.music.create_taxon.path,
1260
+ params,
1261
+ );
1262
+ },
1263
+
1264
+ deleteTaxon: (params: s.DeleteTaxonRequest) => {
1265
+ return call(
1266
+ "music",
1267
+ "delete_taxon",
1268
+ routes.music.delete_taxon.resp,
1269
+ routes.music.delete_taxon.req,
1270
+ routes.music.delete_taxon.method,
1271
+ routes.music.delete_taxon.path,
1272
+ params,
1273
+ );
1274
+ },
1275
+
1276
+ addTaxonParent: (params: s.AddTaxonParentRequest) => {
1277
+ return call(
1278
+ "music",
1279
+ "add_taxon_parent",
1280
+ routes.music.add_taxon_parent.resp,
1281
+ routes.music.add_taxon_parent.req,
1282
+ routes.music.add_taxon_parent.method,
1283
+ routes.music.add_taxon_parent.path,
1284
+ params,
1285
+ );
1286
+ },
1287
+
1288
+ listTaxonParentsForKind: (params: s.ListTaxonParentsForKindRequest) => {
1289
+ return call(
1290
+ "music",
1291
+ "list_taxon_parents_for_kind",
1292
+ routes.music.list_taxon_parents_for_kind.resp,
1293
+ routes.music.list_taxon_parents_for_kind.req,
1294
+ routes.music.list_taxon_parents_for_kind.method,
1295
+ routes.music.list_taxon_parents_for_kind.path,
1296
+ params,
1297
+ );
1298
+ },
1299
+
1300
+ removeTaxonParent: (params: s.RemoveTaxonParentRequest) => {
1301
+ return call(
1302
+ "music",
1303
+ "remove_taxon_parent",
1304
+ routes.music.remove_taxon_parent.resp,
1305
+ routes.music.remove_taxon_parent.req,
1306
+ routes.music.remove_taxon_parent.method,
1307
+ routes.music.remove_taxon_parent.path,
1308
+ params,
1309
+ );
1310
+ },
1311
+
1312
+ getTaxonAncestors: (params: s.GetTaxonRequest) => {
1313
+ return call(
1314
+ "music",
1315
+ "get_taxon_ancestors",
1316
+ routes.music.get_taxon_ancestors.resp,
1317
+ routes.music.get_taxon_ancestors.req,
1318
+ routes.music.get_taxon_ancestors.method,
1319
+ routes.music.get_taxon_ancestors.path,
1320
+ params,
1321
+ );
1322
+ },
1323
+
1324
+ getTaxonDescendants: (params: s.GetTaxonRequest) => {
1325
+ return call(
1326
+ "music",
1327
+ "get_taxon_descendants",
1328
+ routes.music.get_taxon_descendants.resp,
1329
+ routes.music.get_taxon_descendants.req,
1330
+ routes.music.get_taxon_descendants.method,
1331
+ routes.music.get_taxon_descendants.path,
1332
+ params,
1333
+ );
1334
+ },
1335
+
1336
+ getAlbumTaxonLinks: (params: s.GetAlbumTaxonLinksRequest) => {
1337
+ return call(
1338
+ "music",
1339
+ "get_album_taxon_links",
1340
+ routes.music.get_album_taxon_links.resp,
1341
+ routes.music.get_album_taxon_links.req,
1342
+ routes.music.get_album_taxon_links.method,
1343
+ routes.music.get_album_taxon_links.path,
1344
+ params,
1345
+ );
1346
+ },
1347
+
1348
+ addAlbumTaxon: (params: s.AddAlbumTaxonRequest) => {
1349
+ return call(
1350
+ "music",
1351
+ "add_album_taxon",
1352
+ routes.music.add_album_taxon.resp,
1353
+ routes.music.add_album_taxon.req,
1354
+ routes.music.add_album_taxon.method,
1355
+ routes.music.add_album_taxon.path,
1356
+ params,
1357
+ );
1358
+ },
1359
+
1360
+ removeAlbumTaxon: (params: s.RemoveAlbumTaxonRequest) => {
1361
+ return call(
1362
+ "music",
1363
+ "remove_album_taxon",
1364
+ routes.music.remove_album_taxon.resp,
1365
+ routes.music.remove_album_taxon.req,
1366
+ routes.music.remove_album_taxon.method,
1367
+ routes.music.remove_album_taxon.path,
1368
+ params,
1369
+ );
1370
+ },
1371
+
1372
+ setAlbumTaxons: (params: s.SetAlbumTaxonsRequest) => {
1373
+ return call(
1374
+ "music",
1375
+ "set_album_taxons",
1376
+ routes.music.set_album_taxons.resp,
1377
+ routes.music.set_album_taxons.req,
1378
+ routes.music.set_album_taxons.method,
1379
+ routes.music.set_album_taxons.path,
1380
+ params,
1381
+ );
1382
+ },
1383
+
1384
+ setTaxonColor: (params: s.SetTaxonColorRequest) => {
1385
+ return call(
1386
+ "music",
1387
+ "set_taxon_color",
1388
+ routes.music.set_taxon_color.resp,
1389
+ routes.music.set_taxon_color.req,
1390
+ routes.music.set_taxon_color.method,
1391
+ routes.music.set_taxon_color.path,
1392
+ params,
1393
+ );
1394
+ },
1395
+
1396
+ setTaxonLabel: (params: s.SetTaxonLabelRequest) => {
1397
+ return call(
1398
+ "music",
1399
+ "set_taxon_label",
1400
+ routes.music.set_taxon_label.resp,
1401
+ routes.music.set_taxon_label.req,
1402
+ routes.music.set_taxon_label.method,
1403
+ routes.music.set_taxon_label.path,
1404
+ params,
1405
+ );
1406
+ },
1407
+
1408
+ setTaxonKindColor: (params: s.SetTaxonKindColorRequest) => {
1409
+ return call(
1410
+ "music",
1411
+ "set_taxon_kind_color",
1412
+ routes.music.set_taxon_kind_color.resp,
1413
+ routes.music.set_taxon_kind_color.req,
1414
+ routes.music.set_taxon_kind_color.method,
1415
+ routes.music.set_taxon_kind_color.path,
1416
+ params,
1417
+ );
1418
+ },
1419
+
1420
+ setTaxonKindLabel: (params: s.SetTaxonKindLabelRequest) => {
1421
+ return call(
1422
+ "music",
1423
+ "set_taxon_kind_label",
1424
+ routes.music.set_taxon_kind_label.resp,
1425
+ routes.music.set_taxon_kind_label.req,
1426
+ routes.music.set_taxon_kind_label.method,
1427
+ routes.music.set_taxon_kind_label.path,
1428
+ params,
1429
+ );
1430
+ },
1431
+
1432
+ setScalarAttribute: (params: s.SetScalarAttributeRequest) => {
1433
+ return call(
1434
+ "music",
1435
+ "set_scalar_attribute",
1436
+ routes.music.set_scalar_attribute.resp,
1437
+ routes.music.set_scalar_attribute.req,
1438
+ routes.music.set_scalar_attribute.method,
1439
+ routes.music.set_scalar_attribute.path,
1440
+ params,
1441
+ );
1442
+ },
1443
+
1444
+ queryAlbumsByScalarRange: (params: s.QueryScalarRangeRequest) => {
1445
+ return call(
1446
+ "music",
1447
+ "query_albums_by_scalar_range",
1448
+ routes.music.query_albums_by_scalar_range.resp,
1449
+ routes.music.query_albums_by_scalar_range.req,
1450
+ routes.music.query_albums_by_scalar_range.method,
1451
+ routes.music.query_albums_by_scalar_range.path,
1452
+ params,
1453
+ );
1454
+ },
1455
+
1456
+ // bulk-enrichment review (phase 14.4e). spawns one
1457
+ // `AlbumEnrichmentPipeline` job per album_id with shared session_id
1458
+ // so the caller can poll progress / cancel as a group.
1459
+ enqueueBulkEnrichment: (params: s.BulkEnrichmentRequest) => {
1460
+ return call(
1461
+ "music",
1462
+ "enqueue_bulk_enrichment",
1463
+ routes.music.enqueue_bulk_enrichment.resp,
1464
+ routes.music.enqueue_bulk_enrichment.req,
1465
+ routes.music.enqueue_bulk_enrichment.method,
1466
+ routes.music.enqueue_bulk_enrichment.path,
1467
+ params,
1468
+ );
1469
+ },
1470
+
1471
+ // cancel all pending/running jobs in a bulk-enrichment session
1472
+ // (phase 14.4e). already-completed jobs are left untouched.
1473
+ cancelBulkEnrichment: (params: s.CancelBulkEnrichmentRequest) => {
1474
+ return call(
1475
+ "music",
1476
+ "cancel_bulk_enrichment",
1477
+ routes.music.cancel_bulk_enrichment.resp,
1478
+ routes.music.cancel_bulk_enrichment.req,
1479
+ routes.music.cancel_bulk_enrichment.method,
1480
+ routes.music.cancel_bulk_enrichment.path,
1481
+ params,
1482
+ );
1483
+ },
1484
+
1485
+ // fetch per-source enrichment status for a batch of album ids
1486
+ // (phase 14.4e). used by the review modal to render status badges +
1487
+ // last-error / retry-count.
1488
+ getEnrichmentProgress: (params: s.GetEnrichmentProgressRequest) => {
1489
+ return call(
1490
+ "music",
1491
+ "get_enrichment_progress",
1492
+ routes.music.get_enrichment_progress.resp,
1493
+ routes.music.get_enrichment_progress.req,
1494
+ routes.music.get_enrichment_progress.method,
1495
+ routes.music.get_enrichment_progress.path,
1496
+ params,
1497
+ );
1498
+ },
1499
+
1500
+ // re-enqueue a single source for a single album with optional
1501
+ // override query (phase 14.5). supports per-album manual retry from
1502
+ // the review modal's per-source tab.
1503
+ requeryEnrichment: (params: s.RequeryEnrichmentRequest) => {
1504
+ return call(
1505
+ "music",
1506
+ "requery_enrichment",
1507
+ routes.music.requery_enrichment.resp,
1508
+ routes.music.requery_enrichment.req,
1509
+ routes.music.requery_enrichment.method,
1510
+ routes.music.requery_enrichment.path,
1511
+ params,
1512
+ );
1513
+ },
1514
+
1515
+ // download a remote image url and link it to an album or artist
1516
+ // (phase 14.6). dedups by sha256.
1517
+ ingestRemoteImage: (params: s.IngestRemoteImageRequest) => {
1518
+ return call(
1519
+ "music",
1520
+ "ingest_remote_image",
1521
+ routes.music.ingest_remote_image.resp,
1522
+ routes.music.ingest_remote_image.req,
1523
+ routes.music.ingest_remote_image.method,
1524
+ routes.music.ingest_remote_image.path,
1525
+ params,
1526
+ );
1527
+ },
1528
+
1529
+ // surface remote image candidates for an album from stored
1530
+ // metadata snapshots (audiodb thumbs + musicbrainz coverart).
1531
+ // read-only - does not make any external http calls.
1532
+ imageCandidatesForAlbum: (params: s.AlbumImageCandidatesRequest) => {
1533
+ return call(
1534
+ "music",
1535
+ "image_candidates_for_album",
1536
+ routes.music.image_candidates_for_album.resp,
1537
+ routes.music.image_candidates_for_album.req,
1538
+ routes.music.image_candidates_for_album.method,
1539
+ routes.music.image_candidates_for_album.path,
1540
+ params,
1541
+ );
1542
+ },
1543
+
1544
+ // surface remote image candidates for an artist from stored
1545
+ // metadata snapshots (audiodb artist_thumb / artist_fanart).
1546
+ // accepts artist_id directly OR album_id (resolved server-side).
1547
+ imageCandidatesForArtist: (params: s.ArtistImageCandidatesRequest) => {
1548
+ return call(
1549
+ "music",
1550
+ "image_candidates_for_artist",
1551
+ routes.music.image_candidates_for_artist.resp,
1552
+ routes.music.image_candidates_for_artist.req,
1553
+ routes.music.image_candidates_for_artist.method,
1554
+ routes.music.image_candidates_for_artist.path,
1555
+ params,
1556
+ );
1557
+ },
1558
+
1559
+ // update an artist's enrichment metadata blob (phase 14.10).
1560
+ // never touches `name`. honours skip-if-complete unless `force=true`.
1561
+ updateArtistMetadata: (params: s.UpdateArtistMetadataRequest) => {
1562
+ return call(
1563
+ "music",
1564
+ "update_artist_metadata",
1565
+ routes.music.update_artist_metadata.resp,
1566
+ routes.music.update_artist_metadata.req,
1567
+ routes.music.update_artist_metadata.method,
1568
+ routes.music.update_artist_metadata.path,
1569
+ params,
1570
+ );
1571
+ },
1572
+
1573
+ // related-artists (phase 13h). cross-source index of artist
1574
+ // recommendations harvested from lastfm / audiodb / mb enrichment.
1575
+ listRelatedArtists: (params: s.ListRelatedArtistsRequest) => {
1576
+ return call(
1577
+ "music",
1578
+ "list_related_artists",
1579
+ routes.music.list_related_artists.resp,
1580
+ routes.music.list_related_artists.req,
1581
+ routes.music.list_related_artists.method,
1582
+ routes.music.list_related_artists.path,
1583
+ params,
1584
+ );
1585
+ },
1586
+
1587
+ // batched lookup: fetch related-artist lists for many source artists in
1588
+ // one round trip. avoids fanning out N calls when rendering large graphs.
1589
+ listRelatedArtistsBatch: (params: s.ListRelatedArtistsBatchRequest) => {
1590
+ return call(
1591
+ "music",
1592
+ "list_related_artists_batch",
1593
+ routes.music.list_related_artists_batch.resp,
1594
+ routes.music.list_related_artists_batch.req,
1595
+ routes.music.list_related_artists_batch.method,
1596
+ routes.music.list_related_artists_batch.path,
1597
+ params,
1598
+ );
1599
+ },
1600
+
1601
+ // admin-only manual override for a related artist's bandcamp links.
1602
+ setRelatedArtistBandcamp: (params: s.SetRelatedArtistBandcampRequest) => {
1603
+ return call(
1604
+ "music",
1605
+ "set_related_artist_bandcamp",
1606
+ routes.music.set_related_artist_bandcamp.resp,
1607
+ routes.music.set_related_artist_bandcamp.req,
1608
+ routes.music.set_related_artist_bandcamp.method,
1609
+ routes.music.set_related_artist_bandcamp.path,
1610
+ params,
1611
+ );
1612
+ },
1613
+
1614
+ // cross-remote walk routes (phase 11). all POST. used by the graph
1615
+ // viz's lazy walk-expansion to fetch membership / taxons / merged
1616
+ // entities in a single round trip per (remote, hub) pair.
1617
+
1618
+ // list albums belonging to a (kind, value_norm) taxon. supports
1619
+ // optional limit + offset for paging large clusters.
1620
+ albumsByValue: (params: s.AlbumsByValueRequest) => {
1621
+ return call(
1622
+ "music",
1623
+ "albums_by_value",
1624
+ routes.music.albums_by_value.resp,
1625
+ routes.music.albums_by_value.req,
1626
+ routes.music.albums_by_value.method,
1627
+ routes.music.albums_by_value.path,
1628
+ params,
1629
+ );
1630
+ },
1631
+
1632
+ // batched lookup: fetch taxons for many entities (album OR artist)
1633
+ // in one round trip. avoids N+1 when walk-expanding a cluster.
1634
+ entityTaxonsBatch: (params: s.EntityTaxonsBatchRequest) => {
1635
+ return call(
1636
+ "music",
1637
+ "entity_taxons_batch",
1638
+ routes.music.entity_taxons_batch.resp,
1639
+ routes.music.entity_taxons_batch.req,
1640
+ routes.music.entity_taxons_batch.method,
1641
+ routes.music.entity_taxons_batch.path,
1642
+ params,
1643
+ );
1644
+ },
1645
+
1646
+ // confirm which entities on this remote match a list of merged
1647
+ // keys (lowercased "artist::title" / "artist"). used for
1648
+ // per-remote chip rendering in popovers and for cross-remote
1649
+ // dedup validation.
1650
+ findByMergedKey: (params: s.FindByMergedKeyRequest) => {
1651
+ return call(
1652
+ "music",
1653
+ "find_by_merged_key",
1654
+ routes.music.find_by_merged_key.resp,
1655
+ routes.music.find_by_merged_key.req,
1656
+ routes.music.find_by_merged_key.method,
1657
+ routes.music.find_by_merged_key.path,
1658
+ params,
1659
+ );
1660
+ },
1661
+
1662
+ // phase 22: synthesized first-order hubs. these are server-side
1663
+ // computed clusters that don't correspond to a stored taxon —
1664
+ // currently stubs (era_bins returns []), but the routes are
1665
+ // wired so the client can render hub nodes + degrade gracefully.
1666
+
1667
+ // greedy decade-aware year binning for the "era" hub. server
1668
+ // returns empty bins until the heuristic ships; clients should
1669
+ // treat zero bins as "feature not yet available".
1670
+ eraBins: (params: s.EraBinsRequest) => {
1671
+ return call(
1672
+ "music",
1673
+ "era_bins",
1674
+ routes.music.era_bins.resp,
1675
+ routes.music.era_bins.req,
1676
+ routes.music.era_bins.method,
1677
+ routes.music.era_bins.path,
1678
+ params,
1679
+ );
1680
+ },
1681
+
1682
+ // top-N most recently added albums, ordered by album_created_at
1683
+ // desc. enriched with artist + images + favorites so the graph
1684
+ // can render the hub's child albums without follow-up fetches.
1685
+ recentlyAddedAlbums: (params: s.RecentlyAddedAlbumsRequest) => {
1686
+ return call(
1687
+ "music",
1688
+ "recently_added_albums",
1689
+ routes.music.recently_added_albums.resp,
1690
+ routes.music.recently_added_albums.req,
1691
+ routes.music.recently_added_albums.method,
1692
+ routes.music.recently_added_albums.path,
1693
+ params,
1694
+ );
1695
+ },
1696
+
1697
+ // fan-out one era bin to its member albums (release_date year
1698
+ // inside [min_year, max_year]). enriched album shape mirrors
1699
+ // recentlyAddedAlbums/albumsByValue so a single adapter handles
1700
+ // all walk-pulled albums.
1701
+ eraAlbums: (params: s.EraAlbumsRequest) => {
1702
+ return call(
1703
+ "music",
1704
+ "era_albums",
1705
+ routes.music.era_albums.resp,
1706
+ routes.music.era_albums.req,
1707
+ routes.music.era_albums.method,
1708
+ routes.music.era_albums.path,
1709
+ params,
1710
+ );
1711
+ },
1712
+
1713
+ // fan-out the synthesized "unassigned" hub to its member albums.
1714
+ // when kind_slug is null/empty, returns albums with no taxon
1715
+ // assignments of any kind (fully untagged); otherwise restricts
1716
+ // the missing-check to the given kind only. paged (default 100,
1717
+ // max 500).
1718
+ unassignedAlbums: (params: s.UnassignedAlbumsRequest) => {
1719
+ return call(
1720
+ "music",
1721
+ "unassigned_albums",
1722
+ routes.music.unassigned_albums.resp,
1723
+ routes.music.unassigned_albums.req,
1724
+ routes.music.unassigned_albums.method,
1725
+ routes.music.unassigned_albums.path,
1726
+ params,
1727
+ );
1728
+ },
1729
+
1730
+ // blob metadata lookup by blake3 hash
1731
+ blobMetadataByBlake3: (params: s.GetBlobMetadataByBlake3Request) => {
1732
+ return call(
1733
+ "music",
1734
+ "blob_metadata_by_blake3",
1735
+ routes.music.blob_metadata_by_blake3.resp,
1736
+ routes.music.blob_metadata_by_blake3.req,
1737
+ routes.music.blob_metadata_by_blake3.method,
1738
+ routes.music.blob_metadata_by_blake3.path,
1739
+ params,
1740
+ );
1741
+ },
1742
+
1743
+ // build an atlas manifest for a set of blob ids
1744
+ buildAtlas: (params: s.BuildAtlasRequest) => {
1745
+ return call(
1746
+ "music",
1747
+ "build_atlas",
1748
+ routes.music.build_atlas.resp,
1749
+ routes.music.build_atlas.req,
1750
+ routes.music.build_atlas.method,
1751
+ routes.music.build_atlas.path,
1752
+ params,
1753
+ );
1754
+ },
1755
+
1756
+ // get a snapshot of current job events matching a filter
1757
+ eventsSnapshot: (params: s.EventFilter) => {
1758
+ return call(
1759
+ "music",
1760
+ "events_snapshot",
1761
+ routes.music.events_snapshot.resp,
1762
+ routes.music.events_snapshot.req,
1763
+ routes.music.events_snapshot.method,
1764
+ routes.music.events_snapshot.path,
1765
+ params,
1766
+ );
1767
+ },
1768
+
1769
+ // subscribe to job events matching a filter (sse stream)
1770
+ eventsSubscribe: (params: s.EventFilter) => {
1771
+ return call(
1772
+ "music",
1773
+ "events_subscribe",
1774
+ routes.music.events_subscribe.resp,
1775
+ routes.music.events_subscribe.req,
1776
+ routes.music.events_subscribe.method,
1777
+ routes.music.events_subscribe.path,
1778
+ params,
1779
+ );
1780
+ },
1781
+
1782
+ // check which of a set of blob ids are present on this server
1783
+ hasBlobs: (params: s.HasBlobsRequest) => {
1784
+ return call(
1785
+ "music",
1786
+ "has_blobs",
1787
+ routes.music.has_blobs.resp,
1788
+ routes.music.has_blobs.req,
1789
+ routes.music.has_blobs.method,
1790
+ routes.music.has_blobs.path,
1791
+ params,
1792
+ );
1793
+ },
1794
+
1795
+ // sync an album from a remote peer
1796
+ syncAlbum: (params: s.SyncAlbumRequest) => {
1797
+ return call(
1798
+ "music",
1799
+ "sync_album",
1800
+ routes.music.sync_album.resp,
1801
+ routes.music.sync_album.req,
1802
+ routes.music.sync_album.method,
1803
+ routes.music.sync_album.path,
1804
+ params,
1805
+ );
1806
+ },
1807
+
1808
+ // sync a playlist from a remote peer
1809
+ syncPlaylist: (params: s.SyncPlaylistRequest) => {
1810
+ return call(
1811
+ "music",
1812
+ "sync_playlist",
1813
+ routes.music.sync_playlist.resp,
1814
+ routes.music.sync_playlist.req,
1815
+ routes.music.sync_playlist.method,
1816
+ routes.music.sync_playlist.path,
1817
+ params,
1818
+ );
1819
+ },
1820
+
1821
+ // sync a song by blake3 hash from a remote peer
1822
+ syncSongByBlake3: (params: s.SyncSongByBlake3Request) => {
1823
+ return call(
1824
+ "music",
1825
+ "sync_song_by_blake3",
1826
+ routes.music.sync_song_by_blake3.resp,
1827
+ routes.music.sync_song_by_blake3.req,
1828
+ routes.music.sync_song_by_blake3.method,
1829
+ routes.music.sync_song_by_blake3.path,
1830
+ params,
1831
+ );
1832
+ },
1833
+
1834
+ // get the list of sha256 hashes known to this server for sync checks
1835
+ syncedSha256s: () => {
1836
+ return call(
1837
+ "music",
1838
+ "synced_sha256s",
1839
+ routes.music.synced_sha256s.resp,
1840
+ routes.music.synced_sha256s.req,
1841
+ routes.music.synced_sha256s.method,
1842
+ routes.music.synced_sha256s.path,
1843
+ undefined,
1844
+ );
1845
+ },
1846
+
1847
+ // upload music file identified by its blake3 hash
1848
+ uploadMusicByBlake3: (params: s.UploadMusicByBlake3Request) => {
1849
+ return call(
1850
+ "music",
1851
+ "upload_music_by_blake3",
1852
+ routes.music.upload_music_by_blake3.resp,
1853
+ routes.music.upload_music_by_blake3.req,
1854
+ routes.music.upload_music_by_blake3.method,
1855
+ routes.music.upload_music_by_blake3.path,
1856
+ params,
1857
+ );
1858
+ },
1859
+
1860
+ // import review
1861
+ listPendingImportReview: (params: s.ListPendingReviewRequest) => {
1862
+ return call(
1863
+ "music",
1864
+ "list_pending_import_review",
1865
+ routes.music.list_pending_import_review.resp,
1866
+ routes.music.list_pending_import_review.req,
1867
+ routes.music.list_pending_import_review.method,
1868
+ routes.music.list_pending_import_review.path,
1869
+ params,
1870
+ );
1871
+ },
1872
+
1873
+ markAlbumReviewed: (params: s.MarkAlbumReviewedRequest) => {
1874
+ return call(
1875
+ "music",
1876
+ "mark_album_reviewed",
1877
+ routes.music.mark_album_reviewed.resp,
1878
+ routes.music.mark_album_reviewed.req,
1879
+ routes.music.mark_album_reviewed.method,
1880
+ routes.music.mark_album_reviewed.path,
1881
+ params,
1882
+ );
1883
+ },
1884
+
1885
+ mergeAlbumsReview: (params: s.MergeAlbumsReviewRequest) => {
1886
+ return call(
1887
+ "music",
1888
+ "merge_albums_review",
1889
+ routes.music.merge_albums_review.resp,
1890
+ routes.music.merge_albums_review.req,
1891
+ routes.music.merge_albums_review.method,
1892
+ routes.music.merge_albums_review.path,
1893
+ params,
1894
+ );
1895
+ },
1896
+
1897
+ moveSongReview: (params: s.MoveSongReviewRequest) => {
1898
+ return call(
1899
+ "music",
1900
+ "move_song_review",
1901
+ routes.music.move_song_review.resp,
1902
+ routes.music.move_song_review.req,
1903
+ routes.music.move_song_review.method,
1904
+ routes.music.move_song_review.path,
1905
+ params,
1906
+ );
1907
+ },
1908
+
1909
+ patchAlbumReview: (params: s.PatchAlbumReviewRequest) => {
1910
+ return call(
1911
+ "music",
1912
+ "patch_album_review",
1913
+ routes.music.patch_album_review.resp,
1914
+ routes.music.patch_album_review.req,
1915
+ routes.music.patch_album_review.method,
1916
+ routes.music.patch_album_review.path,
1917
+ params,
1918
+ );
1919
+ },
1920
+
1921
+ albumPending: (params: s.AlbumPendingRequest) => {
1922
+ return call(
1923
+ "music",
1924
+ "album_pending",
1925
+ routes.music.album_pending.resp,
1926
+ routes.music.album_pending.req,
1927
+ routes.music.album_pending.method,
1928
+ routes.music.album_pending.path,
1929
+ params,
1930
+ );
1931
+ },
1932
+ };
1933
+ }
1934
+
1935
+ export type MusicMethods = ReturnType<typeof createMusicMethods>;