@omelhorsite/sdk 0.3.0 → 0.4.1

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 (92) hide show
  1. package/README.md +113 -131
  2. package/dist/index.js +1049 -1246
  3. package/dist/types/auth/device.d.ts +1 -1
  4. package/dist/types/auth/index.d.ts +2 -2
  5. package/dist/types/auth/tokens.d.ts +15 -15
  6. package/dist/types/client.d.ts +10 -10
  7. package/dist/types/errors.d.ts +12 -15
  8. package/dist/types/http.d.ts +74 -118
  9. package/dist/types/index.d.ts +2 -2
  10. package/dist/types/internal/attachments.d.ts +17 -0
  11. package/dist/types/internal/helpers.d.ts +9 -0
  12. package/dist/types/listing.d.ts +90 -0
  13. package/dist/types/local/qr.d.ts +1 -1
  14. package/dist/types/local/wordlist.d.ts +2 -3
  15. package/dist/types/resources/account.d.ts +19 -19
  16. package/dist/types/resources/admin/authorizedApplications.d.ts +133 -0
  17. package/dist/types/resources/admin/chests.d.ts +51 -0
  18. package/dist/types/resources/admin/eventAlerts.d.ts +60 -0
  19. package/dist/types/resources/admin/identities.d.ts +99 -0
  20. package/dist/types/resources/admin/index.d.ts +77 -0
  21. package/dist/types/resources/admin/jobs.d.ts +97 -0
  22. package/dist/types/resources/admin/myOauthApplications.d.ts +282 -0
  23. package/dist/types/resources/admin/notepads.d.ts +49 -0
  24. package/dist/types/resources/admin/oauthApplications.d.ts +309 -0
  25. package/dist/types/resources/admin/quotas.d.ts +114 -0
  26. package/dist/types/resources/admin/shortLinks.d.ts +201 -0
  27. package/dist/types/resources/admin/types.d.ts +222 -0
  28. package/dist/types/resources/admin/vocalSeparations.d.ts +98 -0
  29. package/dist/types/resources/admin.d.ts +17 -13
  30. package/dist/types/resources/auth/index.d.ts +1 -1
  31. package/dist/types/resources/auth/passkeys.d.ts +127 -163
  32. package/dist/types/resources/auth/sessions.d.ts +116 -161
  33. package/dist/types/resources/chests.d.ts +27 -31
  34. package/dist/types/resources/content/analysis.d.ts +62 -0
  35. package/dist/types/resources/content/blogs.d.ts +471 -0
  36. package/dist/types/resources/content/feedbacks.d.ts +263 -0
  37. package/dist/types/resources/content/index.d.ts +52 -0
  38. package/dist/types/resources/content/intel/articles.d.ts +230 -0
  39. package/dist/types/resources/content/intel/config.d.ts +135 -0
  40. package/dist/types/resources/content/intel/index.d.ts +53 -0
  41. package/dist/types/resources/content/intel/items.d.ts +91 -0
  42. package/dist/types/resources/content/intel/reports.d.ts +108 -0
  43. package/dist/types/resources/content/intel/scripts.d.ts +152 -0
  44. package/dist/types/resources/content/intel/sources.d.ts +236 -0
  45. package/dist/types/resources/content/intel/stats.d.ts +105 -0
  46. package/dist/types/resources/content/intel/types.d.ts +86 -0
  47. package/dist/types/resources/content/jokes.d.ts +93 -0
  48. package/dist/types/resources/content/notifications.d.ts +147 -0
  49. package/dist/types/resources/content/serviceUsages.d.ts +73 -0
  50. package/dist/types/resources/content/servicesStatus.d.ts +194 -0
  51. package/dist/types/resources/content/siteConfig.d.ts +43 -0
  52. package/dist/types/resources/content/spaceInvaders.d.ts +154 -0
  53. package/dist/types/resources/content.d.ts +41 -236
  54. package/dist/types/resources/dynamicQrs.d.ts +29 -45
  55. package/dist/types/resources/forms.d.ts +37 -58
  56. package/dist/types/resources/jobs.d.ts +35 -44
  57. package/dist/types/resources/library/annotations.d.ts +162 -0
  58. package/dist/types/resources/library/books.d.ts +534 -0
  59. package/dist/types/resources/library/chat.d.ts +259 -0
  60. package/dist/types/resources/library/index.d.ts +39 -0
  61. package/dist/types/resources/library/shelves.d.ts +237 -0
  62. package/dist/types/resources/library/types.d.ts +120 -0
  63. package/dist/types/resources/library.d.ts +20 -22
  64. package/dist/types/resources/media.d.ts +48 -61
  65. package/dist/types/resources/movies/addons.d.ts +388 -0
  66. package/dist/types/resources/movies/collections.d.ts +352 -0
  67. package/dist/types/resources/movies/index.d.ts +29 -0
  68. package/dist/types/resources/movies/types.d.ts +50 -0
  69. package/dist/types/resources/movies/watchProgress.d.ts +277 -0
  70. package/dist/types/resources/movies.d.ts +27 -53
  71. package/dist/types/resources/music/artists.d.ts +182 -246
  72. package/dist/types/resources/music/imports.d.ts +190 -218
  73. package/dist/types/resources/music/index.d.ts +8 -7
  74. package/dist/types/resources/music/playlists.d.ts +85 -117
  75. package/dist/types/resources/music/social.d.ts +153 -228
  76. package/dist/types/resources/music/songs.d.ts +165 -227
  77. package/dist/types/resources/realtime.d.ts +75 -88
  78. package/dist/types/resources/shortLinks.d.ts +38 -47
  79. package/dist/types/resources/social/groupChats.d.ts +458 -0
  80. package/dist/types/resources/social/index.d.ts +19 -0
  81. package/dist/types/resources/social/messages.d.ts +424 -0
  82. package/dist/types/resources/social/relationships.d.ts +322 -0
  83. package/dist/types/resources/social/types.d.ts +37 -0
  84. package/dist/types/resources/social.d.ts +21 -3
  85. package/dist/types/resources/storage/upload.d.ts +42 -56
  86. package/dist/types/resources/storage.d.ts +94 -109
  87. package/dist/types/resources/tickets.d.ts +19 -10
  88. package/dist/types/resources/tools/backgroundRemoval.d.ts +11 -13
  89. package/dist/types/resources/tools/captions.d.ts +107 -135
  90. package/dist/types/resources/tools/upscale.d.ts +12 -16
  91. package/dist/types/types.d.ts +29 -38
  92. package/package.json +1 -1
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * The `music.songs` namespace: the library, its lyrics and its stems.
3
3
  *
4
- * Everything under here is strictly single-tenant. `Song.viewable_by` is
5
- * `where(user: current_user)` with no grant, no share and no public branch, so
6
- * there is no such thing as reading somebody else's track by id: a foreign id
7
- * is a `404 "Resource not found"`, never a `403`. The same is true of
4
+ * Everything under here is strictly single-tenant: there is no such thing as
5
+ * reading somebody else's track by id. A foreign id is a
6
+ * `404 "Resource not found"`, never a `403`. The same is true of
8
7
  * `liked_songs`, of `/artist_metadata/:name` (which resolves against YOUR
9
8
  * artist roster, not a global one) and of `/songs/artist_pictures`. Cross-user
10
9
  * song payloads exist in this API, but they arrive through jams and social
@@ -14,50 +13,42 @@
14
13
  *
15
14
  * **Song ids are integers.** So are `liked_songs` ids. The ids sitting NEXT to
16
15
  * them are not: `user_id` is a string uuid, every `*_media_id` is a string, and
17
- * a vocal separation's own `id` is a string. On ActionCable the same song ids
18
- * come back as strings. Compare with `===` against the right type or nothing
19
- * will ever match.
16
+ * a vocal separation's own `id` is a string. On the realtime stream the same
17
+ * song ids come back as strings. Compare with `===` against the right type or
18
+ * nothing will ever match.
20
19
  *
21
20
  * **Media never travels inline.** A song carries ids of storage nodes, not
22
21
  * bytes and not URLs. Resolve one with `oms.media` (`GET /media/:id/data_url`
23
- * hands back a short-lived signed URL, which is what you give a player). The
24
- * `oms.storage` route `GET /fs_nodes/:id/data_url` reaches the same bytes and
25
- * the web frontend still uses it, but the backend calls it a temporary alias
26
- * for that frontend, so new code should ask `oms.media`. Prefer the compressed
27
- * twin either way: `compressed_audio_media_id`
28
- * before `audio_media_id`, `compressed_artwork_media_id` before
29
- * `artwork_media_id`. The originals are lossless files on a Raspberry Pi and an
30
- * album grid that reaches for them takes seconds per tile.
22
+ * hands back a short-lived signed URL, which is what you give a player).
23
+ * `GET /fs_nodes/:id/data_url` is a legacy alias that reaches the same bytes;
24
+ * new code should ask `oms.media`. Prefer the compressed twin either way:
25
+ * `compressed_audio_media_id` before `audio_media_id`,
26
+ * `compressed_artwork_media_id` before `artwork_media_id`. The originals are
27
+ * lossless files on slow storage and an album grid that reaches for them takes
28
+ * seconds per tile.
31
29
  *
32
- * **Every key that names a media node is sent TWICE.** `ApplicationBlueprint.
33
- * media_id_fields` emits `<name>_media_id` and `<name>_fs_node_id` with the
34
- * identical value, the second being a temporary shim for the old web frontend.
35
- * Read the `_media_id` spelling; the twin is declared here only so that code
36
- * ported from the web app keeps type-checking, and it will be removed
37
- * server-side without a major version of this SDK.
30
+ * **Every key that names a media node is sent TWICE.** `<name>_media_id` and
31
+ * `<name>_fs_node_id` carry the identical value, the second being a legacy
32
+ * alias. Read the `_media_id` spelling; the twin is declared here only so that
33
+ * older code keeps type-checking, and it will be removed server-side without a
34
+ * major version of this SDK.
38
35
  *
39
36
  * ## An OAuth token cannot reach any of this
40
37
  *
41
- * None of the five controllers behind this namespace (`songs`, `liked_songs`,
42
- * `lyrics`, `artist_metadata`, `music/external_search`) declares an
43
- * `oauth_scope`, and `enforce_oauth_scope!` denies by omission: an OAuth access
44
- * token gets `403 {"error":"insufficient_scope"}` on EVERY method here, before
45
- * the action runs. Music needs a session cookie or a personal token. That is a
46
- * server-side gap rather than a design decision, so it may open later; until it
47
- * does, an {@link OmsAuthError} with status 403 and that body means "wrong kind
48
- * of credential", not "wrong user".
38
+ * An OAuth access token gets `403 {"error":"insufficient_scope"}` on EVERY
39
+ * method here. Music needs a session cookie or a personal token. That gap may
40
+ * close later; until it does, an {@link OmsAuthError} with status 403 and that
41
+ * body means "wrong kind of credential", not "wrong user".
49
42
  *
50
43
  * ## The 60-per-minute bucket nobody expects to share
51
44
  *
52
- * rack-attack throttles `/lyrics*`, `/artists/*`, `/artist_metadata/*` and
53
- * `/music_radios/*` through ONE rule - `external_proxy/by_session`, 60 requests
54
- * per minute keyed by the literal `Authorization` header - because all four
55
- * proxy to somebody else's servers (lrclib, Genius, Last.fm, Wikipedia, Deezer)
56
- * and getting our IP banned there breaks the feature for everyone. One counter,
57
- * four route families: fetching lyrics for sixty tracks in a minute leaves zero
58
- * budget for artist metadata, and the 429 lands on whichever call is unlucky
59
- * enough to be the sixty-first. Pace a backfill at roughly one request per
60
- * second and it will never be seen.
45
+ * `/lyrics*`, `/artists/*`, `/artist_metadata/*` and `/music_radios/*` share
46
+ * ONE budget of 60 requests per minute, keyed by the `Authorization` header,
47
+ * because all four proxy to somebody else's servers (lrclib, Genius, Last.fm,
48
+ * Wikipedia, Deezer). One counter, four route families: fetching lyrics for
49
+ * sixty tracks in a minute leaves zero budget for artist metadata, and the 429
50
+ * lands on whichever call is unlucky enough to be the sixty-first. Pace a
51
+ * backfill at roughly one request per second and it will never be seen.
61
52
  *
62
53
  * The rest of the namespace lives under the general ceiling (600/min
63
54
  * authenticated), with two exceptions that have their own budgets and their own
@@ -66,7 +57,8 @@
66
57
  * (30/min, and it does NOT answer 429 - read that method).
67
58
  */
68
59
  import { Resource } from "../../http";
69
- import { type BaseRecord, type FileInput, type FileOutput, type Id, type NativeFile, type PageParams, type Paginated, type QueryValue, type RequestOptions, type Timestamp } from "../../types";
60
+ import type { ListParams } from "../../listing";
61
+ import { type BaseRecord, type FileInput, type FileOutput, type Id, type NativeFile, type Paginated, type RequestOptions, type Timestamp } from "../../types";
70
62
  import type { VocalSeparation } from "../tools/vocalSeparation";
71
63
  /**
72
64
  * Primary key of a song. An **integer**, unlike most ids in this API.
@@ -76,16 +68,16 @@ import type { VocalSeparation } from "../tools/vocalSeparation";
76
68
  * primary keys; `users`, `sessions`, `fs_nodes`, `playback_states` and
77
69
  * `vocal_separations` are string uuids. Both spellings are accepted wherever a
78
70
  * song id is taken as an argument, because a caller that read the id off a
79
- * cable message is holding a string, but the JSON these methods RETURN always
80
- * carries a number.
71
+ * realtime message is holding a string, but the JSON these methods RETURN
72
+ * always carries a number.
81
73
  */
82
74
  export type SongId = number | string;
83
75
  /** How an artist is credited on a track. */
84
76
  export type SongArtistRole = "primary" | "featured" | "with";
85
77
  /**
86
- * One row of `song_artists`, nested under {@link Song.artists}.
78
+ * One artist credit, nested under {@link Song.artists}.
87
79
  *
88
- * `id` is the id of the JOIN row, not of the artist - the artist's own id is
80
+ * `id` is the id of the credit row, not of the artist - the artist's own id is
89
81
  * `artist_id`. Getting those two the wrong way round is how a link to an artist
90
82
  * page ends up pointing at a random other artist, so prefer `slug` for routing
91
83
  * and keep `artist_id` for lookups.
@@ -97,14 +89,14 @@ export type SongArtistRole = "primary" | "featured" | "with";
97
89
  * `picture_xl`) lives on the artist record.
98
90
  */
99
91
  export interface SongArtistCredit extends Omit<BaseRecord, "id"> {
100
- /** Id of the `song_artists` join row. NOT the artist id. */
92
+ /** Id of the credit row. NOT the artist id. */
101
93
  readonly id: number;
102
94
  readonly song_id: number;
103
95
  readonly artist_id: number;
104
96
  /** Ordering within the credits. Sort by it; the server does not. */
105
97
  readonly position: number;
106
98
  readonly role: SongArtistRole;
107
- /** `null` only if the join row outlived its artist, which the schema prevents. */
99
+ /** `null` only if the credit outlived its artist, which the server prevents. */
108
100
  readonly name: string | null;
109
101
  readonly slug: string | null;
110
102
  /** Artist avatar the user uploaded, as a storage node id. Usually `null`. */
@@ -125,15 +117,10 @@ export type SongSourceKind = "upload" | "yt_dlp" | "spotify_sync";
125
117
  /**
126
118
  * A track in the library.
127
119
  *
128
- * `SongBlueprint` declares no extra fields in its `:extended` view, so the
129
- * shape below is what every route in this namespace answers with - index,
120
+ * The shape below is what every route in this namespace answers with - index,
130
121
  * show, update and import alike. There is no narrower view to guard against.
131
- *
132
- * Two fields the web frontend's own type declares do NOT exist on the wire:
133
- * `track_number` and `disc_number` (they are optional there and always
134
- * `undefined`), and neither does the legacy `artist` string column, which was
135
- * dropped when `song_artists` landed. {@link Song.artists} is the only artist
136
- * source.
122
+ * There is no `track_number`, no `disc_number` and no legacy `artist` string
123
+ * on the wire; {@link Song.artists} is the only artist source.
137
124
  */
138
125
  export interface Song extends Omit<BaseRecord, "id"> {
139
126
  /** Integer primary key. See {@link SongId}. */
@@ -180,7 +167,7 @@ export interface Song extends Omit<BaseRecord, "id"> {
180
167
  /**
181
168
  * Stamped when a separation starts and cleared when it settles, so a
182
169
  * non-null value means "a separation is in flight". It can go stale if a
183
- * worker dies; `GET /songs/:id/separation` clears it as a side effect, which
170
+ * run dies; `GET /songs/:id/separation` clears it as a side effect, which
184
171
  * is one reason to poll that route rather than re-reading the song.
185
172
  */
186
173
  readonly vocal_separation_started_at: Timestamp | null;
@@ -219,8 +206,8 @@ export interface Song extends Omit<BaseRecord, "id"> {
219
206
  /**
220
207
  * A row of `GET /songs/albums`.
221
208
  *
222
- * Not a database entity: the endpoint groups the caller's songs in Ruby and
223
- * this is the summary it builds. `name: null` is the bucket of songs with no
209
+ * Not a stored record: the endpoint groups the caller's songs and this is the
210
+ * summary it builds. `name: null` is the bucket of songs with no
224
211
  * album, and it is a legitimate row rather than an error.
225
212
  */
226
213
  export interface SongAlbumSummary {
@@ -261,8 +248,7 @@ export interface SongSeparationStatus {
261
248
  * The run itself, or `null` when this song has never had one.
262
249
  *
263
250
  * The same `VocalSeparation` record the `tools.vocalSeparation` namespace
264
- * returns - one model, one blueprint, two ways in - so it is imported rather
265
- * than redeclared here. For a song-owned run `vocals_url` and
251
+ * returns, so it is imported rather than redeclared here. For a song-owned run `vocals_url` and
266
252
  * `instrumental_url` are permanently `null`: the stems are written onto the
267
253
  * song as storage nodes, not attached to this row.
268
254
  */
@@ -276,7 +262,7 @@ export interface LikedSong extends Omit<BaseRecord, "id"> {
276
262
  readonly song_id: number;
277
263
  /** The cursor {@link ListLikedSongsParams.before} pages on. */
278
264
  readonly liked_at: Timestamp;
279
- /** Always present: the route renders the `:extended` view, which inlines it. */
265
+ /** Always present. */
280
266
  readonly song: Song;
281
267
  }
282
268
  /** Lyrics for one track. */
@@ -316,10 +302,8 @@ export interface ArtistMetadataSimilar {
316
302
  /**
317
303
  * The legacy payload `GET /artist_metadata/:name` answers with.
318
304
  *
319
- * `ArtistMetadataBlueprint` inherits `Blueprinter::Base` directly rather than
320
- * `ApplicationBlueprint`, so this is one of the very few records in the API
321
- * with NO `created_at` / `updated_at`. It also never 404s - see
322
- * {@link MusicSongsNamespace.artistMetadata}.
305
+ * One of the very few records in the API with NO `created_at` / `updated_at`.
306
+ * It also never 404s - see {@link MusicSongsNamespace.artistMetadata}.
323
307
  */
324
308
  export interface ArtistMetadata {
325
309
  /** `null` on the not-found branch. Integer when the artist exists. */
@@ -357,10 +341,9 @@ export interface ArtistMetadata {
357
341
  /**
358
342
  * Where an external-search hit came from.
359
343
  *
360
- * The frontend's own union also lists `"soundcloud"` and `"bandcamp"`. The
361
- * current `Music::ExternalSearch` queries Spotify, iTunes and YouTube and
362
- * nothing else, so those two branches are dead - keep handling them if you
363
- * ported code that does, but do not wait for them.
344
+ * `"soundcloud"` and `"bandcamp"` are kept for compatibility only: the search
345
+ * queries Spotify, iTunes and YouTube and nothing else, so they never appear
346
+ * today.
364
347
  */
365
348
  export type MusicExternalSource = "spotify" | "itunes" | "youtube" | "soundcloud" | "bandcamp";
366
349
  /** What to ask the external providers for. */
@@ -419,7 +402,7 @@ export interface MusicExternalSearchResult {
419
402
  * `artist` is on the list but is not a column - see
420
403
  * {@link ListSongsParams.artist}.
421
404
  */
422
- export declare const SONG_FILTER_COLUMNS: readonly string[];
405
+ export declare const SONG_FILTER_COLUMNS: readonly ["id", "created_at", "updated_at", "title", "album", "position", "year", "artist"];
423
406
  /** Columns the backend will accept in `modifiers[order]`. */
424
407
  export declare const SONG_ORDER_COLUMNS: readonly string[];
425
408
  /** Filters shared by `GET /songs` and `GET /songs/albums`. */
@@ -427,9 +410,9 @@ export interface SongFilters {
427
410
  /**
428
411
  * Partial title match, sent as `search[title]`.
429
412
  *
430
- * The comparison is slug-shaped: the server lowercases, strips accents
431
- * through a `TRANSLATE`, replaces every run of non-alphanumerics with a
432
- * hyphen and then does a `LIKE %...%` on both sides. So `"cafe"` finds
413
+ * The comparison is slug-shaped: the server lowercases, strips accents,
414
+ * replaces every run of non-alphanumerics with a hyphen and then does a
415
+ * substring match on both sides. So `"cafe"` finds
433
416
  * "Café", `"nao quero"` finds "Não Quero", and punctuation is irrelevant. It
434
417
  * is not a full-text index and there is no ranking.
435
418
  *
@@ -440,9 +423,8 @@ export interface SongFilters {
440
423
  readonly title?: string;
441
424
  /**
442
425
  * Exact album, sent as `exact_search[album]`. Pass `null` for the no-album
443
- * bucket: the transport encodes it as the backend's `\b` sentinel and
444
- * `Searchable.exact_search` turns that into `WHERE album IS NULL`, which is
445
- * the only way to ask for it.
426
+ * bucket: the transport encodes it as the server's `\b` null sentinel, which
427
+ * is the only way to ask for it.
446
428
  *
447
429
  * Not a substring match. `search[album]` exists and IS partial, but it can
448
430
  * never express the null bucket, so this field takes the exact route and the
@@ -456,10 +438,9 @@ export interface SongFilters {
456
438
  /**
457
439
  * Narrow to one artist, by canonical name OR by slug.
458
440
  *
459
- * This one is not a column and does not behave like the others. The
460
- * controller reads it straight out of `params`, resolves it against YOUR
461
- * artist roster (canonical name first, then slug) and joins through
462
- * `song_artists`. Consequences:
441
+ * This one is not a column and does not behave like the others. The server
442
+ * resolves it against YOUR artist roster (canonical name first, then slug)
443
+ * and matches on credits. Consequences:
463
444
  *
464
445
  * - it is EXACT even though it is spelled like a search. `search[artist]`
465
446
  * and `exact_search[artist]` are the same code path; there is no partial
@@ -467,8 +448,7 @@ export interface SongFilters {
467
448
  * filter client-side or go through `oms.music.artists`;
468
449
  * - an artist you do not have resolves to nothing and yields an EMPTY list,
469
450
  * not a 404 and not an error. An empty page is genuinely ambiguous here;
470
- * - it changes what {@link MusicSongsNamespace.albums} deduplicates on, which
471
- * is the point of that endpoint's `filter_artist_id`.
451
+ * - it changes what {@link MusicSongsNamespace.albums} deduplicates on.
472
452
  */
473
453
  readonly artist?: string;
474
454
  /**
@@ -480,49 +460,30 @@ export interface SongFilters {
480
460
  * excluded even if they also appear as a feature.
481
461
  */
482
462
  readonly artistRole?: SongArtistRole;
483
- /**
484
- * Escape hatch for a partial match on a column {@link SongFilters} does not
485
- * name, sent verbatim as `search[...]`.
486
- *
487
- * Only the keys in {@link SONG_FILTER_COLUMNS} are accepted and an unknown
488
- * one is a 400, so do not forward user-controlled keys. Two behaviours
489
- * surprise people: on a NUMBER column (`year`, `position`, `id`) `search` is
490
- * an exact `IN`, not a range or a prefix; and any blank value is dropped
491
- * instead of matching nothing.
492
- */
493
- readonly search?: Readonly<Record<string, QueryValue>>;
494
- /**
495
- * Escape hatch for an equality filter, sent verbatim as `exact_search[...]`.
496
- * Same allowlist, same fail-closed 400. An array becomes `IN`, `null`
497
- * becomes `IS NULL`.
498
- */
499
- readonly exactSearch?: Readonly<Record<string, QueryValue>>;
500
463
  }
501
464
  /** Arguments for {@link MusicSongsNamespace.list}. */
502
- export interface ListSongsParams extends SongFilters, PageParams {
465
+ export interface ListSongsParams extends SongFilters, ListParams<(typeof SONG_FILTER_COLUMNS)[number]> {
503
466
  /**
504
467
  * `modifiers[order]`, as `"column:asc"` or `"column:desc"`.
505
468
  *
506
469
  * Defaults to `"created_at:asc"`, which is also the endpoint's own base
507
470
  * order and the one that makes paging stable. Two traps:
508
471
  *
509
- * - a column the model does not have is IGNORED, silently. `modifiers[order]`
510
- * is an allowed key so the request is not rejected, and `QueryModifier`
511
- * simply returns before ordering, handing back the base order. A typo
512
- * costs you nothing but the sort you asked for;
513
- * - a real column REPLACES the base order (`reorder`, not `order`), tie
514
- * breaker included. Ordering 4000 tracks by `title:asc` when several share
515
- * a title gives Postgres licence to return them in a different sequence per
516
- * page, which duplicates and drops rows across a paged walk. Prefer
517
- * `created_at` or `id` for anything you intend to page through.
472
+ * - a column the record does not have is IGNORED, silently. `modifiers[order]`
473
+ * is an allowed key so the request is not rejected; the base order is
474
+ * handed back instead. A typo costs you nothing but the sort you asked for;
475
+ * - a real column REPLACES the base order, tie breaker included. Ordering
476
+ * 4000 tracks by `title:asc` when several share a title lets the database
477
+ * return them in a different sequence per page, which duplicates and drops
478
+ * rows across a paged walk. Prefer `created_at` or `id` for anything you
479
+ * intend to page through.
518
480
  *
519
481
  * A third `:`-separated segment pins specific values first
520
- * (`"album:asc:Clube da Esquina,Acabou Chorare"`), at the cost of an extra
521
- * `SELECT DISTINCT` over the column.
482
+ * (`"album:asc:Clube da Esquina,Acabou Chorare"`).
522
483
  */
523
484
  readonly order?: string;
524
485
  /**
525
- * `modifiers[random]=true` - shuffle server-side with `RANDOM()`.
486
+ * `modifiers[random]=true` - shuffle server-side.
526
487
  *
527
488
  * Mutually destructive with paging: the ordering is re-evaluated per request,
528
489
  * so page 2 of a random listing is a fresh shuffle and shares rows with page
@@ -532,7 +493,7 @@ export interface ListSongsParams extends SongFilters, PageParams {
532
493
  readonly random?: boolean;
533
494
  }
534
495
  /** Arguments for {@link MusicSongsNamespace.albums}. */
535
- export interface ListSongAlbumsParams extends SongFilters {
496
+ export interface ListSongAlbumsParams extends SongFilters, ListParams<(typeof SONG_FILTER_COLUMNS)[number]> {
536
497
  /**
537
498
  * Page of SONGS to scan, not of albums. Read
538
499
  * {@link MusicSongsNamespace.albums} before setting it; omitting it is
@@ -545,7 +506,7 @@ export interface ListSongAlbumsParams extends SongFilters {
545
506
  /**
546
507
  * Fields {@link MusicSongsNamespace.update} can change.
547
508
  *
548
- * Four real columns and three virtual inputs. The virtual ones are the
509
+ * Four real fields and three virtual inputs. The virtual ones are the
549
510
  * complicated half; each carries its own note.
550
511
  */
551
512
  export interface UpdateSongInput {
@@ -561,14 +522,14 @@ export interface UpdateSongInput {
561
522
  * there is no comma-splitting heuristic, so a name that genuinely contains a
562
523
  * comma survives.
563
524
  *
564
- * An EMPTY array is not "remove every artist": it is `blank?` server-side,
565
- * which drops it back to `nil` and re-runs the legacy parser. There is no way
566
- * through this endpoint to leave a song with no artists at all.
525
+ * An EMPTY array is not "remove every artist": the server treats it as
526
+ * absent and re-runs the legacy parser. There is no way through this
527
+ * endpoint to leave a song with no artists at all.
567
528
  */
568
529
  readonly artistNames?: string[];
569
530
  /**
570
531
  * The featured credits, replacing whatever is there. Sending this key at all
571
- * is what switches the backend out of its legacy mode.
532
+ * is what switches the server out of its legacy mode.
572
533
  *
573
534
  * That legacy mode is a heuristic over the TITLE: with no `featured_artist_
574
535
  * names` key present, the server re-reads `"Song (feat. X)"` and rebuilds the
@@ -584,7 +545,7 @@ export interface UpdateSongInput {
584
545
  /**
585
546
  * Legacy single-line artist input, re-parsed server-side (it splits on
586
547
  * commas and on "feat."). Prefer {@link UpdateSongInput.artistNames}; this
587
- * exists for parity with the old web form.
548
+ * exists for compatibility.
588
549
  */
589
550
  readonly artist?: string;
590
551
  /**
@@ -609,16 +570,15 @@ export interface ListLikedSongsParams {
609
570
  *
610
571
  * Deliberately not an offset. The list is ordered by `liked_at` descending
611
572
  * and liking one track mid-scroll shifts every later offset page by one,
612
- * which shows a duplicate and hides a row. A `Date` is encoded as ISO-8601,
613
- * which is what `Time.zone.parse` wants; an unparseable string is a
614
- * `400 "Invalid before timestamp"`.
573
+ * which shows a duplicate and hides a row. A `Date` is encoded as ISO-8601;
574
+ * an unparseable string is a `400 "Invalid before timestamp"`.
615
575
  */
616
576
  readonly before?: string | Date;
617
577
  }
618
578
  /** Arguments for {@link MusicSongsNamespace.startSeparation}. */
619
579
  export interface StartSongSeparationInput {
620
580
  /**
621
- * Which sidecar model to run. Omit for the default. The selectable list is
581
+ * Which separation model to run. Omit for the default. The selectable list is
622
582
  * `oms.tools.vocalSeparation.models()`; an id that is not on it is a
623
583
  * `400 "Unknown model"`.
624
584
  */
@@ -640,9 +600,8 @@ export interface ModifySongMetadataInput {
640
600
  /** The file to retag. Hard cap 50 MiB, enforced before anything else happens. */
641
601
  readonly audio: FileInput | NativeFile;
642
602
  /**
643
- * At least one tag is REQUIRED. An empty bag raises server-side and comes
644
- * back as a 500 with a Discord page attached, so this method rejects it
645
- * locally instead.
603
+ * At least one tag is REQUIRED. An empty bag comes back as a 500, so this
604
+ * method rejects it locally instead.
646
605
  */
647
606
  readonly metadata: SongFileMetadata;
648
607
  }
@@ -684,14 +643,14 @@ export declare function isMusicExternalSearchRateLimited(error: unknown): boolea
684
643
  * `"Chico Buarque, Milton Nascimento (feat. Elis Regina)"`: primaries joined
685
644
  * with `", "`, then a `feat.` clause. Pure string building, no request.
686
645
  *
687
- * `with` credits are excluded unless `includeWith` is set, which mirrors what
688
- * the three clients do - they render only in a credits dialog and in media
689
- * session metadata, where completeness beats line length.
646
+ * `with` credits are excluded unless `includeWith` is set; they belong in a
647
+ * credits dialog and in media session metadata, where completeness beats line
648
+ * length.
690
649
  *
691
- * Written here because {@link Song.artists} arrives UNSORTED and every client
692
- * that forgot to sort by `position` printed the credits in insertion order,
693
- * which is roughly random. It also copes with a jam entry, whose `artist_names`
694
- * is a pre-joined string and whose `artists` array is empty.
650
+ * Written here because {@link Song.artists} arrives UNSORTED and forgetting to
651
+ * sort by `position` prints the credits in insertion order, which is roughly
652
+ * random. It also copes with a jam entry, whose `artist_names` is a pre-joined
653
+ * string and whose `artists` array is empty.
695
654
  */
696
655
  export declare function songArtistsLine(song: Pick<Song, "artists"> & Partial<Pick<Song, "artist_names">>, includeWith?: boolean): string;
697
656
  /** The `music.songs` namespace, reachable as `oms.music.songs`. */
@@ -699,15 +658,13 @@ export declare class MusicSongsNamespace extends Resource {
699
658
  /**
700
659
  * `GET /songs` - the caller's library, oldest first.
701
660
  *
702
- * Pagination is FORCED here and nowhere else in this namespace: the
703
- * controller overrides `modifiers_params` for `index` only, so a request with
704
- * no page modifier is given `1:500` and one asking for more than 500 is
705
- * clamped to it. That is a DoS guard, not tidiness - a five-thousand-track
706
- * library serialises megabytes of JSON with every credit inlined, and a
707
- * handful of concurrent unbounded listings used to exhaust the Puma threads.
708
- * The SDK sends a page modifier every time, so the clamp never surprises you
709
- * and {@link Paginated.pageSize} always reports the size the rows were
710
- * counted against.
661
+ * Pagination is FORCED here and nowhere else in this namespace: a request
662
+ * with no page modifier is given `1:500` and one asking for more than 500 is
663
+ * clamped to it. That is a guard, not tidiness - a five-thousand-track
664
+ * library serialises megabytes of JSON with every credit inlined. The SDK
665
+ * sends a page modifier every time, so the clamp never surprises you and
666
+ * {@link Paginated.pageSize} always reports the size the rows were counted
667
+ * against.
711
668
  *
712
669
  * Order defaults to `created_at:asc`, the endpoint's own base order, which is
713
670
  * what makes a paged walk stable. See {@link ListSongsParams.order} before
@@ -720,14 +677,12 @@ export declare class MusicSongsNamespace extends Resource {
720
677
  * @throws {OmsAuthError} 401 when anonymous, 403 for an OAuth token.
721
678
  * @throws {OmsApiError} 400 naming the offending key when a filter is not in
722
679
  * {@link SONG_FILTER_COLUMNS}. Filters fail closed on purpose: silently
723
- * dropping an unknown one used to answer with the UNFILTERED set.
680
+ * dropping an unknown one would answer with the UNFILTERED set.
724
681
  */
725
682
  list(params?: ListSongsParams, options?: RequestOptions): Promise<Paginated<Song>>;
726
683
  /**
727
- * `GET /songs/:id` - one track.
728
- *
729
- * Renders the `:extended` view, which `SongBlueprint` leaves identical to the
730
- * default one, so this returns exactly what a row of {@link list} carries.
684
+ * `GET /songs/:id` - one track. Returns exactly what a row of {@link list}
685
+ * carries.
731
686
  *
732
687
  * @throws {OmsApiError} 404 `"Resource not found"` - for an id that does not
733
688
  * exist AND for one that belongs to somebody else, indistinguishably. The
@@ -738,18 +693,16 @@ export declare class MusicSongsNamespace extends Resource {
738
693
  * `PATCH /songs/:id` - edits metadata, and optionally replaces the artwork.
739
694
  *
740
695
  * JSON normally; multipart as soon as {@link UpdateSongInput.artwork} is
741
- * present, because that is the only way to carry a file. Both encodings reach
742
- * the same code path server-side, and this method papers over the two places
743
- * where they would otherwise behave differently:
744
- *
745
- * - **clearing a column in multipart.** Every form field is a string, so
746
- * there is no `null` to send. The backend's `\b` sentinel is decoded for
747
- * update params exactly as it is for filters, so `album: null` is written
748
- * as that one character and clears the column. (The Expo app's own comment
749
- * claims multipart cannot express this and splits the request in two; it
750
- * can, and it does not need to.)
696
+ * present, because that is the only way to carry a file. Both encodings
697
+ * behave the same server-side, and this method papers over the two places
698
+ * where they would otherwise differ:
699
+ *
700
+ * - **clearing a field in multipart.** Every form field is a string, so
701
+ * there is no `null` to send. The server's `\b` null sentinel is decoded
702
+ * for update fields exactly as it is for filters, so `album: null` is
703
+ * written as that one character and clears the field.
751
704
  * - **an empty `featuredArtistNames`.** Appending an empty array appends
752
- * nothing, and an absent key is what puts the backend back into its
705
+ * nothing, and an absent key is what puts the server back into its
753
706
  * title-parsing legacy mode - the opposite of what "no featured artists"
754
707
  * means. This sends the single empty string the server reads as an explicit
755
708
  * empty list.
@@ -758,8 +711,7 @@ export declare class MusicSongsNamespace extends Resource {
758
711
  * credits you did not touch. Send `featuredArtistNames` whenever you care
759
712
  * about them.
760
713
  *
761
- * @throws {OmsAuthError} 401 when the song is not yours - `update` checks
762
- * `updatable_by?` and answers 401, not 403 or 404.
714
+ * @throws {OmsAuthError} 401 when the song is not yours - not 403 or 404.
763
715
  * @throws {OmsApiError} 400 `"Music storage quota exceeded"` when the artwork
764
716
  * would not fit in the music quota.
765
717
  */
@@ -784,20 +736,20 @@ export declare class MusicSongsNamespace extends Resource {
784
736
  *
785
737
  * Answers **200**, not 201, with the created song. Do not branch on the code.
786
738
  *
787
- * Two size limits and they are not the same one. Rails rejects anything over
788
- * {@link SONG_IMPORT_MAX_BYTES} (1 GiB) with a 400 - but production sits
789
- * behind Cloudflare, which refuses a request body over roughly 100 MB with
790
- * its own `413` before Rails ever sees it. A big FLAC therefore fails with an
791
- * HTML-ish 413 that says nothing about songs. There is no chunked import
792
- * route; that ceiling is real.
739
+ * Two size limits and they are not the same one. The API rejects anything
740
+ * over {@link SONG_IMPORT_MAX_BYTES} (1 GiB) with a 400 - but the CDN in
741
+ * front of it refuses a request body over roughly 100 MB with its own `413`
742
+ * before the API ever sees it. A big FLAC therefore fails with an HTML-ish
743
+ * 413 that says nothing about songs. There is no chunked import route; that
744
+ * ceiling is real.
793
745
  *
794
746
  * On React Native pass the picker's `{ uri, name, type }` object directly -
795
747
  * it is appended verbatim and streamed off disk by the native layer.
796
748
  *
797
749
  * @throws {OmsApiError} 400 for a missing file, a file over 1 GiB, an
798
750
  * extension outside {@link SONG_IMPORT_EXTENSIONS}, or
799
- * `"Music storage quota exceeded"`; 415 with the model's validation
800
- * messages when the audio itself will not import.
751
+ * `"Music storage quota exceeded"`; 415 with validation messages when the
752
+ * audio itself will not import.
801
753
  */
802
754
  import(file: FileInput | NativeFile, options?: RequestOptions): Promise<Song>;
803
755
  /**
@@ -806,10 +758,9 @@ export declare class MusicSongsNamespace extends Resource {
806
758
  * Takes the same filters as {@link list}, and is the endpoint every album
807
759
  * grid is built on. It is also the most expensive read in this namespace, for
808
760
  * a reason worth understanding: the forced pagination that protects
809
- * `GET /songs` is applied to the `index` action ONLY, so this action loads
810
- * the whole filtered library, eager-loads the credits and deduplicates in
811
- * Ruby by `[album, primary artist]`. On a five-thousand-track library that is
812
- * a full table scan per call.
761
+ * `GET /songs` does not apply here, so this route loads the whole filtered
762
+ * library and deduplicates by `[album, primary artist]`. On a
763
+ * five-thousand-track library that is a full scan per call.
813
764
  *
814
765
  * Paging it does not fix that and is usually a mistake:
815
766
  * {@link ListSongAlbumsParams.page} pages the SONGS that get scanned, and the
@@ -830,8 +781,8 @@ export declare class MusicSongsNamespace extends Resource {
830
781
  *
831
782
  * A flat array of strings, ordered by name, and that is the whole payload.
832
783
  *
833
- * It IGNORES every filter you could send it: the action never touches the
834
- * listing scope, it plucks names straight off the artists table. This method
784
+ * It IGNORES every filter you could send it: it reads names straight off the
785
+ * roster. This method
835
786
  * therefore takes no parameters at all rather than accepting some that would
836
787
  * do nothing. It also does not go through `/artists`, so despite the shared
837
788
  * subject it does NOT spend the 60/min external-proxy budget.
@@ -846,9 +797,8 @@ export declare class MusicSongsNamespace extends Resource {
846
797
  * artist you already have.
847
798
  *
848
799
  * Lookup only: an artist absent from your roster returns `[]` and no stub row
849
- * is created, which is deliberate - the old frontend used to send slugs and
850
- * joined display strings ("100 gecs, Lil West, Tony Velour") here and
851
- * polluted the artist table with them.
800
+ * is created, which is deliberate - a slug or a joined display string
801
+ * ("100 gecs, Lil West, Tony Velour") must not mint stub artists.
852
802
  *
853
803
  * The array holds zero or one entry. Zero means either "not your artist" or
854
804
  * "Deezer has never given us a picture for them", and the two are not
@@ -870,8 +820,8 @@ export declare class MusicSongsNamespace extends Resource {
870
820
  *
871
821
  * The odd one out in this namespace: it writes nothing to the library, reads
872
822
  * nothing from it, and answers with BINARY rather than JSON. The file is
873
- * remuxed with ffmpeg (`-c:a copy`, so the audio is never re-encoded) with the
874
- * new tags and, when given one, an embedded cover.
823
+ * remuxed with the new tags (the audio is never re-encoded) and, when given
824
+ * one, an embedded cover.
875
825
  *
876
826
  * The result is buffered fully into memory in every runtime. That is
877
827
  * unavoidable - there is no URL to hand a downloader - but it means a 50 MiB
@@ -879,17 +829,16 @@ export declare class MusicSongsNamespace extends Resource {
879
829
  * the server suggested in `Content-Disposition`.
880
830
  *
881
831
  * At least one tag must be present, and this method enforces that locally
882
- * because the server does not fail gracefully: an empty `metadata` hash
883
- * raises inside the service, escapes as a 500, and pages a human on Discord.
884
- * A missing audio file does the same. Both are avoided here.
832
+ * because the server does not fail gracefully: an empty `metadata` bag is a
833
+ * 500, and so is a missing audio file. Both are avoided here.
885
834
  *
886
- * The web frontend also sends `metadata[track_number]`; the backend's permit
887
- * list drops it in silence, so it is not offered here.
835
+ * `metadata[track_number]` is dropped in silence server-side, so it is not
836
+ * offered here.
888
837
  *
889
838
  * @throws {TypeError} when `metadata` carries no usable tag.
890
839
  * @throws {OmsApiError} 413 `"File too big"` above
891
840
  * {@link SONG_METADATA_MAX_BYTES}, checked before the body is read. Note
892
- * that Cloudflare's own ~100 MB body limit sits above it and never fires
841
+ * that the CDN's own ~100 MB body limit sits above it and never fires
893
842
  * first.
894
843
  */
895
844
  modifyMetadata(input: ModifySongMetadataInput, options?: RequestOptions): Promise<FileOutput>;
@@ -902,18 +851,14 @@ export declare class MusicSongsNamespace extends Resource {
902
851
  * enqueued. So there is no need to guard the call site - but also no way to
903
852
  * force a re-run without deleting the stems first.
904
853
  *
905
- * **Rate limit:** 20 requests per minute, from the `expensive_tools` bucket
906
- * SHARED with `POST /vocal_separations`, the upscaler, transcriptions,
907
- * caption jobs, jumpstyle and the yt-dlp previews. It is there because each
908
- * call schedules minutes of CPU and several gigabytes of RAM on the native
909
- * sidecar; a load generator that walked a library firing one separation per
910
- * song pushed the machine into swap, which is exactly what this budget now
911
- * prevents. Do not batch a library through it.
854
+ * **Rate limit:** 20 requests per minute, from a bucket SHARED with
855
+ * `POST /vocal_separations`, the upscaler, transcriptions, caption jobs,
856
+ * jumpstyle and the downloader previews. Each call schedules minutes of CPU
857
+ * and gigabytes of RAM. Do not batch a library through it.
912
858
  *
913
859
  * The run itself is asynchronous. Poll {@link separation} roughly every three
914
860
  * seconds; the returned row's `status` moves `pending` to `processing` to
915
- * `complete` or `failed`, and there is no `canceled` no matter what the web
916
- * frontend's own type says.
861
+ * `complete` or `failed`, and there is no `canceled`.
917
862
  *
918
863
  * @throws {OmsQuotaError} 429 when the shared expensive-tools budget is spent.
919
864
  * @throws {OmsAuthError} 401 when the song is not yours.
@@ -931,16 +876,15 @@ export declare class MusicSongsNamespace extends Resource {
931
876
  * `complete` a moment before the stems are attached, and a song can have
932
877
  * stems from a run that was swept long ago.
933
878
  *
934
- * Reading this has a SIDE EFFECT, and it is a useful one:
935
- * `ClearStaleSeparationFlag` runs first and clears
936
- * {@link Song.vocal_separation_started_at} when the flag outlived its job (a
937
- * worker that died mid-run leaves it set forever). A UI that decides "a
879
+ * Reading this has a SIDE EFFECT, and it is a useful one: the server first
880
+ * clears {@link Song.vocal_separation_started_at} when the flag outlived its
881
+ * run (a run that died midway leaves it set forever). A UI that decides "a
938
882
  * separation is in flight" from the song record alone can therefore be stuck
939
883
  * on a spinner that only this call will clear.
940
884
  *
941
885
  * Poll at about three seconds. Nothing on this route is throttled beyond the
942
- * general ceiling, but `progress_percent` is fetched live from the sidecar on
943
- * every call while the run is processing, so a tighter loop costs real work.
886
+ * general ceiling, but `progress_percent` is fetched live on every call
887
+ * while the run is processing, so a tighter loop costs real work.
944
888
  *
945
889
  * @throws {OmsApiError} 404 for a song that is not yours.
946
890
  */
@@ -969,18 +913,17 @@ export declare class MusicSongsNamespace extends Resource {
969
913
  * End of list is a short page, as everywhere in this API. There is no count.
970
914
  *
971
915
  * Each row carries a whole {@link Song} with its credits, so 500 likes is a
972
- * large response; the server preloads to avoid the N+1, but the bytes are
973
- * still bytes. 100 is the size the clients actually use.
916
+ * large response. 100 is a sensible size.
974
917
  *
975
- * @throws {OmsApiError} 400 `"Invalid before timestamp"` for a cursor
976
- * `Time.zone.parse` cannot read. Pass a `Date` and this cannot happen.
918
+ * @throws {OmsApiError} 400 `"Invalid before timestamp"` for a cursor the
919
+ * server cannot parse. Pass a `Date` and this cannot happen.
977
920
  */
978
921
  listLiked(params?: ListLikedSongsParams, options?: RequestOptions): Promise<LikedSong[]>;
979
922
  /**
980
923
  * `GET /liked_songs/ids` - just the song ids, as integers.
981
924
  *
982
- * The cheap way to render heart icons over a listing. It plucks a single
983
- * column with no pagination and no cap, so it returns EVERY like in one
925
+ * The cheap way to render heart icons over a listing. It is a single flat
926
+ * list with no pagination and no cap, so it returns EVERY like in one
984
927
  * array - which is the point, and also means it grows without bound. At a few
985
928
  * thousand likes it is still a handful of kilobytes.
986
929
  *
@@ -991,8 +934,8 @@ export declare class MusicSongsNamespace extends Resource {
991
934
  /**
992
935
  * `POST /liked_songs` - likes a track. Answers `201` with the new row.
993
936
  *
994
- * Genuinely idempotent: the controller is a `find_or_create_by!`, so liking
995
- * twice returns the same row rather than erroring or creating a duplicate.
937
+ * Genuinely idempotent: liking twice returns the same row rather than
938
+ * erroring or creating a duplicate.
996
939
  * That is why this is one of the few `POST`s in the SDK that opts INTO the
997
940
  * retry policy - a replay after a lost answer cannot produce a second like,
998
941
  * and the alternative is a heart that silently did nothing.
@@ -1049,17 +992,16 @@ export declare class MusicSongsNamespace extends Resource {
1049
992
  * view. Cached per song, target and lyrics digest, which means the second
1050
993
  * request for a translation is free and a lyrics refetch invalidates it.
1051
994
  *
1052
- * **Retries are disabled and this one really matters.** The hourly cap is
1053
- * enforced by an `increment`-then-compare counter, so a rejected call STILL
1054
- * INCREMENTS it: a client that retries a 429 three times has pushed itself
1055
- * three further past the cap without ever getting an answer. The 429 also
995
+ * **Retries are disabled and this one really matters.** The hourly cap
996
+ * counts a rejected call too: a client that retries a 429 three times has
997
+ * pushed itself three further past the cap without ever getting an answer. The 429 also
1056
998
  * carries no `Retry-After`, so the transport would back off by its own
1057
999
  * schedule - a few hundred milliseconds - into a limit measured in hours.
1058
1000
  * Wait out the window instead. Pass `retry` explicitly if you disagree.
1059
1001
  *
1060
- * **Two limits at once:** 60 fresh translations per user per HOUR (429,
1061
- * app-level), on top of the shared 60/min external-proxy bucket that every
1062
- * `/lyrics*` call draws on.
1002
+ * **Two limits at once:** 60 fresh translations per user per HOUR (429), on
1003
+ * top of the shared 60/min external-proxy bucket that every `/lyrics*` call
1004
+ * draws on.
1063
1005
  *
1064
1006
  * @throws {OmsQuotaError} 429 from either budget.
1065
1007
  * @throws {OmsApiError} 400 `"Unsupported target"` for a locale outside
@@ -1074,8 +1016,8 @@ export declare class MusicSongsNamespace extends Resource {
1074
1016
  * `POST /lyrics/sync` - generates LRC timestamps for plain-text lyrics.
1075
1017
  *
1076
1018
  * Answers `201 { job_id }` and does the work in the background: it separates
1077
- * the vocals if there are no stems, transcribes them on the Whisper sidecar
1078
- * and aligns the known lines against the segments. Minutes of machine time
1019
+ * the vocals if there are no stems, transcribes them and aligns the known
1020
+ * lines against the segments. Minutes of machine time
1079
1021
  * per call, which is what the 10-per-hour cap is protecting.
1080
1022
  *
1081
1023
  * Wait for it either way: `oms.jobs.wait({ id: job_id })`, or simply re-read
@@ -1101,8 +1043,7 @@ export declare class MusicSongsNamespace extends Resource {
1101
1043
  * exceeded"`, not `429`.** Every handler that routes by status code reads
1102
1044
  * that as a malformed query and does the worst possible thing - rewords it
1103
1045
  * and tries again, spending more of a budget that is already gone. The limit
1104
- * is 30 requests per minute per user, counted in the controller rather than
1105
- * in rack-attack, which is why it never reaches the 429 path.
1046
+ * is 30 requests per minute per user.
1106
1047
  * {@link isMusicExternalSearchRateLimited} is the check to use, and the
1107
1048
  * transport will not retry a 400 on its own, so nothing recovers silently.
1108
1049
  *
@@ -1120,7 +1061,7 @@ export declare class MusicSongsNamespace extends Resource {
1120
1061
  * A blank query short-circuits to three empty lists WITHOUT spending budget,
1121
1062
  * which makes it safe to wire straight to an input. A cached hit does spend
1122
1063
  * it: the rate check runs before the 15-minute cache is consulted. Debounce
1123
- * and require two characters, as the clients do.
1064
+ * and require two characters.
1124
1065
  *
1125
1066
  * Every failing upstream is swallowed server-side, so a partial answer and a
1126
1067
  * complete one are indistinguishable - an empty `tracks` may mean "no
@@ -1136,13 +1077,10 @@ export declare class MusicSongsNamespace extends Resource {
1136
1077
  * **It never 404s.** An artist outside your roster comes back as `200` with
1137
1078
  * every field `null` except `name`, echoed back verbatim, and
1138
1079
  * `similar: []`. There is no error to catch and no flag to read: check
1139
- * whether `id` is null. That branch also creates nothing - the shim used to
1140
- * be handed slugs and joined display strings and would mint stub artists from
1141
- * them.
1080
+ * whether `id` is null. That branch also creates nothing.
1142
1081
  *
1143
1082
  * The payload has no `created_at` / `updated_at`, unlike essentially every
1144
- * other record in this API: `ArtistMetadataBlueprint` inherits
1145
- * `Blueprinter::Base` directly to pin the exact legacy key set.
1083
+ * other record in this API.
1146
1084
  *
1147
1085
  * Reading a stale artist triggers a lazy background refresh from Last.fm and
1148
1086
  * MusicBrainz, so the first call after a while may be slower and the second
@@ -1164,7 +1102,7 @@ export declare class MusicSongsNamespace extends Resource {
1164
1102
  * Builds the query for `/songs` and `/songs/albums`.
1165
1103
  *
1166
1104
  * `artist_role` is deliberately TOP LEVEL and not inside `exact_search`: the
1167
- * controller reads it off bare `params`, and nesting it would both miss the
1105
+ * server reads it as a plain parameter, and nesting it would both miss the
1168
1106
  * filter and trip the unknown-key check.
1169
1107
  */
1170
1108
  private songQuery;
@@ -1173,9 +1111,9 @@ export declare class MusicSongsNamespace extends Resource {
1173
1111
  /**
1174
1112
  * Multipart fields for an update, with the two encoding differences fixed.
1175
1113
  *
1176
- * `null` becomes the `\b` sentinel, which `CrudActions` decodes back to `nil`
1177
- * for update params exactly as it does for filters - a form field has no
1178
- * other way to say "clear this column". An empty `featured_artist_names`
1114
+ * `null` becomes the `\b` sentinel, which the server decodes back to null
1115
+ * for update fields exactly as it does for filters - a form field has no
1116
+ * other way to say "clear this field". An empty `featured_artist_names`
1179
1117
  * becomes a single empty string, because appending an empty array appends
1180
1118
  * nothing and an absent key means the opposite thing.
1181
1119
  */