@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,13 +1,12 @@
1
1
  /**
2
- * The `realtime` namespace: ActionCable over a raw WebSocket.
2
+ * The `realtime` namespace: Action Cable over a raw WebSocket.
3
3
  *
4
- * This is the only transport in the SDK that is not HTTP, and it is the reason
5
- * "the frontend uses 100% of the SDK" can be true rather than nearly true.
6
- * Everything else here talks to `/`-rooted JSON endpoints through
7
- * {@link ApiClient}; this talks the ActionCable v1 wire protocol to `/cable`,
8
- * and it deliberately does NOT depend on `@rails/actioncable` - that package
9
- * assumes a browser, pulls in its own global logger, and is bigger than the
10
- * eighty lines of protocol it implements.
4
+ * This is the only transport in the SDK that is not HTTP. Everything else
5
+ * talks to `/`-rooted JSON endpoints through {@link ApiClient}; this talks the
6
+ * Action Cable v1 wire protocol to `/cable`, and it deliberately does NOT
7
+ * depend on `@rails/actioncable` - that package assumes a browser, pulls in
8
+ * its own global logger, and is bigger than the eighty lines of protocol it
9
+ * implements.
11
10
  *
12
11
  * ## The protocol, in full
13
12
  *
@@ -36,46 +35,41 @@
36
35
  * ## The identifier is a string, and it is compared as one
37
36
  *
38
37
  * `identifier` is a JSON-encoded string such as `'{"channel":"PlaybackChannel"}'`.
39
- * The server echoes back the exact bytes it received and Rails routes incoming
40
- * frames by looking that string up in a map. Re-serialising with the keys in a
38
+ * The server echoes back the exact bytes it received and routes incoming
39
+ * frames by looking that string up. Re-serialising with the keys in a
41
40
  * different order produces a different string, the lookup misses, and the
42
41
  * symptom is not an error: it is a subscription that confirms and then never
43
42
  * receives anything. So this module builds the identifier ONCE per
44
43
  * subscription, keys its own registry by that same string, and never rebuilds
45
44
  * it - including across a reconnect.
46
45
  *
47
- * ## Four traps, all of them confirmed against the Rails source
46
+ * ## Four traps
48
47
  *
49
48
  * 1. **The handshake authenticates on the FIRST candidate, with no fallback.**
50
- * `ApplicationCable::Connection#find_session` calls
51
- * `Session.token_from_request`, which is `candidate_tokens(request).first`:
52
- * the `Authorization` header, then `?token=`, then the session cookie, and
53
- * it takes the first one that is merely NON-BLANK. Contrast the HTTP path,
54
- * which uses `Session.resolve_from_request` and tries each candidate until
55
- * one resolves to a live session. A stale `Authorization` header therefore
56
- * shadows a perfectly good cookie on the cable and on the cable only. This
57
- * module never sends an `Authorization` header on the handshake (browsers
58
- * cannot attach one to a WebSocket anyway) and puts the token in the query
59
- * string, exactly as `oms-music` does.
60
- * 2. **Anonymous connections are ACCEPTED.** `connect` sets `current_user` to
61
- * `nil` and returns; it does not `reject_unauthorized_connection`. A bad
62
- * token, an expired token, or no token at all produces a perfectly healthy
63
- * socket that says `welcome` and then pings forever. The identity failure
64
- * surfaces ONE LEVEL DOWN, as `reject_subscription` on each channel that
65
- * needs a user. A client that only handles connection errors will sit there
66
- * believing it is signed in. Handle {@link CableHandlers.onReject}, always.
67
- * 3. **Only legacy `Session` tokens work.** `find_session` ends in
68
- * `Session.find_by(token:)` - it never looks at `oauth_access_tokens`. An
69
- * OAuth access token that authenticates every REST call in this SDK
70
- * produces an ANONYMOUS cable connection, i.e. trap 2. There is no scope
71
- * that fixes this and no error that says it; you get silent rejections on
72
- * every channel. Cable access needs a session token or the session cookie.
73
- * 4. **`/cable` is exempt from the rate limits.** It is on rack-attack's
74
- * allowlist, so neither the 600/min authed ceiling nor the 120/min anonymous
75
- * one applies to the handshake or to anything sent over the socket. That is
76
- * a convenience for `position_tick` at 1 Hz, not a licence: the reconnect
77
- * backoff below exists because an unthrottled reconnect loop against a
78
- * restarting server is a self-inflicted denial of service.
49
+ * The `Authorization` header, then `?token=`, then the session cookie: the
50
+ * handshake takes the first one that is merely NON-BLANK. The HTTP API
51
+ * tries each candidate until one resolves to a live session; the cable does
52
+ * not. A stale `Authorization` header therefore shadows a perfectly good
53
+ * cookie on the cable and on the cable only. This module never sends an
54
+ * `Authorization` header on the handshake (browsers cannot attach one to a
55
+ * WebSocket anyway) and puts the token in the query string.
56
+ * 2. **Anonymous connections are ACCEPTED.** A bad token, an expired token, or
57
+ * no token at all produces a perfectly healthy socket that says `welcome`
58
+ * and then pings forever. The identity failure surfaces ONE LEVEL DOWN, as
59
+ * `reject_subscription` on each channel that needs a user. A client that
60
+ * only handles connection errors will sit there believing it is signed in.
61
+ * Handle {@link CableHandlers.onReject}, always.
62
+ * 3. **Only session tokens work.** An OAuth access token that authenticates
63
+ * every REST call in this SDK produces an ANONYMOUS cable connection, i.e.
64
+ * trap 2. There is no scope that fixes this and no error that says it; you
65
+ * get silent rejections on every channel. Cable access needs a session
66
+ * token or the session cookie.
67
+ * 4. **`/cable` is exempt from the rate limits.** Neither the 600 requests a
68
+ * minute authed ceiling nor the 120 a minute anonymous one applies to the
69
+ * handshake or to anything sent over the socket. That is a convenience for
70
+ * `position_tick` at 1 Hz, not a licence: the reconnect backoff below
71
+ * exists because an unthrottled reconnect loop against a restarting server
72
+ * is a self-inflicted denial of service.
79
73
  *
80
74
  * ## Injecting the socket
81
75
  *
@@ -254,7 +248,7 @@ export interface CableSubscription {
254
248
  readonly active: boolean;
255
249
  }
256
250
  /**
257
- * Every channel class under `backend/app/channels`, as of this writing.
251
+ * Every channel the server exposes, as of this writing.
258
252
  *
259
253
  * There are exactly five, and none of them is generic: each has its own
260
254
  * subscription params, its own rejection rule, and its own message vocabulary.
@@ -273,26 +267,22 @@ export type CableChannelName = (typeof CABLE_CHANNELS)[number];
273
267
  /**
274
268
  * The playback state as the cable serialises it.
275
269
  *
276
- * **The id types here are not the ones the app docs promise.** `oms-music`'s
277
- * `API.md` says flatly that "on the cable, song ids and queue entries are
278
- * STRINGS". Half of that is true and the Rails source says which half:
270
+ * **Two id fields, two types.**
279
271
  *
280
- * - `queue` is a `jsonb` column that the channel writes as
281
- * `Array(attrs["queue"]).map(&:to_s)`, so it really is `string[]` coming
282
- * back, whatever the client sent;
283
- * - `song_id` is a `bigint` COLUMN (`db/schema.rb`, `playback_states`). You
284
- * send it as a string, Rails casts it on write, and it comes back as a JSON
285
- * NUMBER. It is not a string, and code that does `state.song_id === queue[i]`
286
- * compares a number to a string and silently never matches.
272
+ * - `queue` really is `string[]` coming back, whatever the client sent: the
273
+ * server stringifies it on write;
274
+ * - `song_id` is sent as a string and comes back as a JSON NUMBER. Code that
275
+ * does `state.song_id === queue[i]` compares a number to a string and
276
+ * silently never matches.
287
277
  *
288
278
  * The rule that does hold everywhere is: normalise with `String(...)` at the
289
279
  * boundary and compare strings. This SDK types the fields as the wire actually
290
- * carries them rather than as the doc wishes they were, so the mismatch is a
291
- * type error at your call site instead of a bug at runtime.
280
+ * carries them, so the mismatch is a type error at your call site instead of a
281
+ * bug at runtime.
292
282
  */
293
283
  export interface PlaybackSnapshotState {
294
284
  readonly active_device_id: string | null;
295
- /** v1 shim, dies with the column-prune migration. */
285
+ /** v1 shim, scheduled for removal. */
296
286
  readonly active_session_id: string | null;
297
287
  /** A NUMBER, not a string. See the note on this interface. */
298
288
  readonly song_id: number | null;
@@ -321,7 +311,7 @@ export interface PlaybackSnapshotState {
321
311
  readonly vocal_volume: number;
322
312
  readonly instrumental_volume: number;
323
313
  /**
324
- * The full blueprint of every queued song - by far the heaviest part of the
314
+ * The full record of every queued song - by far the heaviest part of the
325
315
  * payload, and OMITTED whenever the queue itself did not change.
326
316
  *
327
317
  * This is the single most misread field on the cable. `state_changed` for a
@@ -439,11 +429,11 @@ export type PlaybackMessage = {
439
429
  readonly reason: string;
440
430
  };
441
431
  /**
442
- * The remote-control vocabulary, exactly as `COMMAND_SCHEMAS` lists it.
432
+ * The remote-control vocabulary.
443
433
  *
444
434
  * A name outside this set is answered with `error: unknown_command` and never
445
- * reaches a broadcast. `jam_add_song` is missing on purpose: it is server-built
446
- * in `JamsController` so a client cannot forge a jam proposal.
435
+ * reaches a broadcast. `jam_add_song` is missing on purpose: the server builds
436
+ * it itself so a client cannot forge a jam proposal.
447
437
  */
448
438
  export type PlaybackCommandName = "play" | "pause" | "toggle" | "next" | "previous" | "seek" | "set_queue_index" | "set_queue_order" | "set_shuffle" | "set_loop_mode" | "set_volume" | "add_to_queue" | "play_next" | "remove_from_queue" | "reorder_queue";
449
439
  /** Subscription params for `PlaybackChannel`. */
@@ -477,7 +467,7 @@ export interface PlaybackSubscribeParams {
477
467
  * `perform` is still there for anything added server-side before this file
478
468
  * catches up, but every action the channel defines today has a method, because
479
469
  * the argument shapes are validated strictly and a typo in an action name is
480
- * silently ignored by ActionCable rather than reported.
470
+ * silently ignored by the server rather than reported.
481
471
  */
482
472
  export interface PlaybackSubscription extends CableSubscription {
483
473
  /**
@@ -580,7 +570,7 @@ export type JamMessage = {
580
570
  readonly jam: unknown;
581
571
  } | {
582
572
  readonly type: "song_proposed";
583
- /** `song.id` is an INTEGER here - this payload is built by the controller. */
573
+ /** `song.id` is an INTEGER here. */
584
574
  readonly song: {
585
575
  readonly id: number;
586
576
  readonly title: string;
@@ -593,7 +583,7 @@ export type JamMessage = {
593
583
  };
594
584
  } | {
595
585
  readonly type: "skip_votes";
596
- /** The host's `PlaybackState#song_id`: a NUMBER, not a string. */
586
+ /** The host's playback `song_id`: a NUMBER, not a string. */
597
587
  readonly song_id: number | null;
598
588
  readonly count: number;
599
589
  readonly needed: number;
@@ -646,7 +636,7 @@ export type FriendListeningMessage = {
646
636
  export interface JobMessage {
647
637
  /** Always `"snapshot"`, on subscribe and on every change alike. */
648
638
  readonly type: "snapshot";
649
- /** The `:extended` job blueprint. Finished when `finished_at` is non-null. */
639
+ /** The job record, as `oms.jobs.get` answers it. Finished when `finished_at` is non-null. */
650
640
  readonly job: unknown;
651
641
  }
652
642
  /** Subscription params for `JobChannel`. */
@@ -670,16 +660,16 @@ export type NotificationsMessage = {
670
660
  readonly unread_count: number;
671
661
  } | {
672
662
  readonly type: "created";
673
- /** The `:extended` notification blueprint. */
663
+ /** The notification record. */
674
664
  readonly notification: unknown;
675
665
  readonly unread_count: number;
676
666
  };
677
667
  /**
678
668
  * One multiplexed cable connection.
679
669
  *
680
- * All five channels share a single WebSocket, which is what ActionCable is for:
681
- * the `identifier` is the demultiplexing key. Open one of these per identity
682
- * and keep it for the lifetime of the session.
670
+ * All five channels share a single WebSocket; the `identifier` is the
671
+ * demultiplexing key. Open one of these per identity and keep it for the
672
+ * lifetime of the session.
683
673
  *
684
674
  * ## What a reconnect does to your subscriptions
685
675
  *
@@ -694,13 +684,13 @@ export type NotificationsMessage = {
694
684
  * valid across all of it - you never re-subscribe by hand and you must not, or
695
685
  * you will end up with the same identifier registered twice.
696
686
  *
697
- * The thing to expect is that server-side subscribe-time work RUNS AGAIN,
698
- * because `subscribed` runs again. Concretely: `PlaybackChannel` re-registers
699
- * the device and re-broadcasts `devices_changed`, every channel re-transmits
700
- * its snapshot, and `FriendListeningChannel` re-reads the friend roster. That
701
- * last one is load-bearing in the other direction too - the roster is fixed at
702
- * subscribe time, so a new friend or a privacy flip only appears after a
703
- * resubscribe, which is why the app resubscribes it on foreground.
687
+ * The thing to expect is that server-side subscribe-time work RUNS AGAIN.
688
+ * Concretely: `PlaybackChannel` re-registers the device and re-broadcasts
689
+ * `devices_changed`, every channel re-transmits its snapshot, and
690
+ * `FriendListeningChannel` re-reads the friend roster. That last one is
691
+ * load-bearing in the other direction too - the roster is fixed at subscribe
692
+ * time, so a new friend or a privacy flip only appears after a resubscribe;
693
+ * resubscribe it on foreground.
704
694
  *
705
695
  * The thing that does NOT happen is a replay of anything you sent. Frames sent
706
696
  * while disconnected are dropped, not queued: see {@link CableSubscription.perform}.
@@ -774,9 +764,9 @@ export declare function cableEndpoint(baseUrl: string, path?: string): string;
774
764
  *
775
765
  * The token goes in the query and NOWHERE else. A browser cannot put a header
776
766
  * on a WebSocket handshake at all, and on the runtimes that can, doing so would
777
- * hit trap 1: `Session.token_from_request` takes the first non-blank candidate,
778
- * header first, so any header at all - including a stale one - decides the
779
- * identity and the query param is never consulted.
767
+ * hit trap 1: the handshake takes the first non-blank candidate, header first,
768
+ * so any header at all - including a stale one - decides the identity and the
769
+ * query param is never consulted.
780
770
  *
781
771
  * An empty string is treated as no credential, which is the cookie-auth case:
782
772
  * the handshake goes out bare and the browser attaches the httpOnly session
@@ -789,29 +779,26 @@ export declare function handshakeUrl(endpoint: string, token: string | null | un
789
779
  * It holds no socket of its own. {@link connect} builds one and hands it back,
790
780
  * so a host that needs two identities (a signed-in user and an anonymous job
791
781
  * watcher) gets two connections rather than a hidden singleton it cannot
792
- * separate. That is the one place this differs from both existing
793
- * implementations, which each keep a module-level singleton - fine for one app,
794
- * wrong for a library.
782
+ * separate.
795
783
  *
796
784
  * ## Why the credential is passed in and not taken from the client
797
785
  *
798
786
  * `Oms` already holds a credential, and this namespace deliberately does not
799
- * reach into it. Two reasons, both from the Rails source rather than from
800
- * taste:
787
+ * reach into it. Two reasons:
801
788
  *
802
- * - the cable resolves ONLY `Session` tokens (`Session.find_by(token:)`), so
803
- * the OAuth access token an `Oms` may be carrying is not a cable credential
804
- * at all - silently reusing it would hand you an anonymous connection;
789
+ * - the cable resolves ONLY session tokens, so the OAuth access token an `Oms`
790
+ * may be carrying is not a cable credential at all - silently reusing it
791
+ * would hand you an anonymous connection;
805
792
  * - `sessionCookie: true` clients have no token to reuse, and their handshake
806
793
  * needs no `?token=` because the browser sends the cookie. For those, pass
807
794
  * nothing.
808
795
  *
809
796
  * ## Rate limits
810
797
  *
811
- * None. `/cable` is on rack-attack's allowlist, so neither the 600/min authed
812
- * ceiling nor the 120/min anonymous one applies to the handshake or to any
813
- * frame after it. The backoff in {@link CableConnectOptions.reconnectMaxMs} is
814
- * therefore the only thing standing between a restarting server and a
798
+ * None. `/cable` is exempt, so neither the 600 requests a minute authed
799
+ * ceiling nor the 120 a minute anonymous one applies to the handshake or to
800
+ * any frame after it. The backoff in {@link CableConnectOptions.reconnectMaxMs}
801
+ * is therefore the only thing standing between a restarting server and a
815
802
  * reconnect storm; it is not decorative.
816
803
  */
817
804
  export declare class RealtimeNamespace extends Resource {
@@ -1,33 +1,28 @@
1
1
  /**
2
2
  * The `shortLinks` namespace: URL shortening plus per-link click statistics.
3
3
  *
4
- * This module is the reference implementation of the resource pattern. Every
5
- * other resource in the SDK is shaped exactly like it, and CONTRACT.md quotes
6
- * it verbatim. Change the pattern here and change it everywhere, or do not
7
- * change it at all.
8
- *
9
4
  * Creating a link works anonymously; listing, editing and statistics need a
10
5
  * credential. Creation is the most tightly throttled write in the whole API -
11
6
  * see {@link ShortLinksNamespace.create} before you spend one.
12
7
  *
13
8
  * There is deliberately no `get(id)` here, and its absence is the API's, not
14
- * an omission: the route file declares `resources :short_links, only: [:create,
15
- * :index, :update, :destroy]`, so `GET /short_links/:id` is not routed at all
16
- * and answers 404 for every id, including your own links. To read one link,
17
- * find it in {@link ShortLinksNamespace.list} - or by endpoint with
9
+ * an omission: `GET /short_links/:id` does not exist and answers 404 for every
10
+ * id, including your own links. To read one link, find it in
11
+ * {@link ShortLinksNamespace.list} - or by endpoint with
18
12
  * {@link ShortLinksNamespace.resolve}, which explains what that costs.
19
13
  */
20
14
  import { Resource } from "../http";
21
- import { type BaseRecord, type Id, type Json, type PageParams, type Paginated, type RequestOptions, type Timestamp } from "../types";
22
- /** Public host that fronts `short_links#follow`. See {@link ShortLinksNamespace.shortUrl}. */
15
+ import type { ListParams } from "../listing";
16
+ import { type BaseRecord, type Id, type Json, type Paginated, type RequestOptions, type Timestamp } from "../types";
17
+ /** Public host short links are served from. See {@link ShortLinksNamespace.shortUrl}. */
23
18
  export declare const SHORT_LINK_BASE_URL = "https://omelhor.site";
24
19
  /**
25
20
  * Primary key of a short link.
26
21
  *
27
- * Short links predate the string ids the rest of the API uses: the table still
28
- * has an integer primary key, so the JSON carries a **number** here while the
29
- * `user_id` right next to it is a string. Every method accepts either form and
30
- * interpolates it into the path, so a caller never has to care.
22
+ * Short links predate the string ids the rest of the API uses: the JSON
23
+ * carries a **number** here while the `user_id` right next to it is a string.
24
+ * Every method accepts either form and interpolates it into the path, so a
25
+ * caller never has to care.
31
26
  */
32
27
  export type ShortLinkId = Id | number;
33
28
  /** One recorded visit to a short link. */
@@ -41,15 +36,15 @@ export interface ShortLinkClick {
41
36
  readonly device_name: string | null;
42
37
  }
43
38
  /**
44
- * The owner of a link, rendered inline by `UserBlueprint`'s default view.
39
+ * The owner of a link, rendered inline.
45
40
  *
46
- * Only the keys that view declares unconditionally are named. `UserBlueprint`
47
- * also renders `group`, `email`, `gender`, `last_seen_at`, `sessions_count`,
48
- * `deactivated_at`, `allowed_to_use_spotify` and `share_listening` behind `if:`
49
- * predicates that test who is ASKING, so whether they appear depends on the
50
- * caller's own privileges rather than on the record. That is why they are
51
- * reached through the index signature instead of being declared here as
52
- * optionals: an optional would suggest the server decides, and it does not.
41
+ * Only the keys that are always present are named. `group`, `email`,
42
+ * `gender`, `last_seen_at`, `sessions_count`, `deactivated_at`,
43
+ * `allowed_to_use_spotify` and `share_listening` appear or not depending on
44
+ * who is ASKING - the caller's own privileges rather than the record. That is
45
+ * why they are reached through the index signature instead of being declared
46
+ * here as optionals: an optional would suggest the record decides, and it does
47
+ * not.
53
48
  */
54
49
  export interface ShortLinkOwner {
55
50
  readonly id: Id;
@@ -68,11 +63,6 @@ export interface ShortLinkOwner {
68
63
  * {@link ShortLinkId} for why the identifier is a number on this one table.
69
64
  *
70
65
  * Every key below is present on every response this namespace can produce.
71
- * `ShortLinkBlueprint` does declare a second, narrower `:admin` view - it drops
72
- * `updated_at`, both associations, and adds `owner`, `click_count` and
73
- * `last_click_at` - but that view is only ever rendered by the admin
74
- * shortlinks tool under `/admin/short_links`, which is not this resource. A
75
- * record that arrived here has the full default shape.
76
66
  */
77
67
  export interface ShortLink extends Omit<BaseRecord, "id"> {
78
68
  /** Integer primary key. See {@link ShortLinkId}. */
@@ -86,22 +76,21 @@ export interface ShortLink extends Omit<BaseRecord, "id"> {
86
76
  * internal tools reserve `"n"` (notepads), `"c"` (chests), `"ss"` (storage
87
77
  * shares), `"qr"` (dynamic QR), `"f"` (forms) and `"t"` (link trees). That
88
78
  * is why {@link ShortLink} and `DynamicQr` are separate resources even
89
- * though both are rows in the same table.
79
+ * though both are short links.
90
80
  *
91
81
  * A link that reached you through {@link ShortLinksNamespace.list} always
92
- * holds `null` or `""`, because the listing scope is `user_managed`.
82
+ * holds `null` or `""`, because the listing only returns user-managed links.
93
83
  */
94
84
  readonly namespace: string | null;
95
85
  /** Owner, or `null` for a link created anonymously. */
96
86
  readonly user_id: Id | null;
97
87
  /**
98
- * EVERY click ever recorded, inlined by the blueprint - not a count, not a
99
- * page. A link with 50 000 visits sends 50 000 objects here. Use
88
+ * EVERY click ever recorded, inlined - not a count, not a page. A link with
89
+ * 50 000 visits sends 50 000 objects here. Use
100
90
  * {@link ShortLinksNamespace.stats} for anything analytical and treat this
101
91
  * field as a payload hazard, not as a feature.
102
92
  *
103
- * Always present, and `[]` rather than `null` for a link nobody has clicked:
104
- * a Blueprinter association over an empty `has_many` renders an empty array.
93
+ * Always present, and `[]` rather than `null` for a link nobody has clicked.
105
94
  */
106
95
  readonly short_link_clicks: ShortLinkClick[];
107
96
  /** The owner rendered inline, or `null` for a link created anonymously. */
@@ -129,7 +118,7 @@ export interface ShortLinkDeviceClicks {
129
118
  * `GET /dynamic_qrs/:id/stats` returns.
130
119
  *
131
120
  * Five keys, always all five. There is no referrer breakdown and no unique
132
- * visitor count: the backend stores neither.
121
+ * visitor count: the server stores neither.
133
122
  */
134
123
  export interface ShortLinkStats {
135
124
  readonly total_clicks: number;
@@ -150,8 +139,8 @@ export interface ShortLinkStats {
150
139
  /** Arguments for creating a short link. */
151
140
  export interface CreateShortLinkInput {
152
141
  /**
153
- * Absolute destination URL. Must be `http` or `https` and must parse against
154
- * `URI::DEFAULT_PARSER`; anything else is a 400, not a silent rewrite.
142
+ * Absolute destination URL. Must be `http` or `https` and must parse as a
143
+ * URL; anything else is a 400, not a silent rewrite.
155
144
  */
156
145
  readonly url: string;
157
146
  /**
@@ -169,14 +158,16 @@ export interface UpdateShortLinkInput {
169
158
  readonly url?: string;
170
159
  readonly endpoint?: string;
171
160
  }
161
+ /** Filter columns of `GET /short_links`, on top of {@link BASE_FILTER_COLUMNS}. */
162
+ export declare const SHORT_LINK_FILTER_COLUMNS: readonly ["user_id"];
172
163
  /** Filters for {@link ShortLinksNamespace.list}. */
173
- export interface ListShortLinksParams extends PageParams {
164
+ export interface ListShortLinksParams extends ListParams<(typeof SHORT_LINK_FILTER_COLUMNS)[number]> {
174
165
  /**
175
166
  * Narrow to one owner, sent as `exact_search[user_id]`.
176
167
  *
177
168
  * The listing is already scoped to the caller server-side, so this can only
178
- * ever narrow your own links to yourself or to nothing. It exists because
179
- * the web app sends it; there is no admin escape hatch here.
169
+ * ever narrow your own links to yourself or to nothing. There is no admin
170
+ * escape hatch here.
180
171
  */
181
172
  readonly userId?: Id;
182
173
  }
@@ -203,10 +194,10 @@ export declare class ShortLinksNamespace extends Resource {
203
194
  /**
204
195
  * `POST /short_links` - shortens a URL.
205
196
  *
206
- * **Rate limit, read this before you spend one:** rack-attack allows
207
- * **10 creations per hour per IP** (`short_links_create/ip`), and the rule
208
- * is keyed by IP for EVERYONE - being signed in does not buy you a bigger
209
- * budget. The 11th call in an hour answers `429` with a `Retry-After` header
197
+ * **Rate limit, read this before you spend one:** the server allows
198
+ * **10 creations per hour per IP**, and the rule is keyed by IP for
199
+ * EVERYONE - being signed in does not buy you a bigger budget. The 11th call
200
+ * in an hour answers `429` with a `Retry-After` header
210
201
  * measured in whatever is left of that hour, which arrives here as an
211
202
  * {@link OmsQuotaError} with `retryAfterMs` set. An agent that shortens URLs
212
203
  * in a loop will burn the whole hour's budget in about a second, so batch the
@@ -247,7 +238,7 @@ export declare class ShortLinksNamespace extends Resource {
247
238
  /**
248
239
  * `GET /short_links/:id/stats` - totals plus a fixed 30-day daily histogram.
249
240
  *
250
- * The window is not configurable: the backend always buckets the last 30
241
+ * The window is not configurable: the server always buckets the last 30
251
242
  * days. For anything else, read {@link ShortLink.short_link_clicks} off the
252
243
  * record and bucket it yourself - at the cost noted on that field.
253
244
  *
@@ -259,8 +250,8 @@ export declare class ShortLinksNamespace extends Resource {
259
250
  /**
260
251
  * Finds one of YOUR links by its endpoint, without counting a click.
261
252
  *
262
- * The public `follow` route records a visit before it redirects, so using it
263
- * to read a destination would quietly corrupt the owner's statistics. The
253
+ * The public redirect records a visit before it redirects, so using it to
254
+ * read a destination would quietly corrupt the owner's statistics. The
264
255
  * API offers no read-by-endpoint, and `search[endpoint]` is not on the
265
256
  * allowlist (an unknown filter key is a 400, not a wider result), so the only
266
257
  * honest implementation is to page through your own listing and match