@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.
- package/README.md +113 -131
- package/dist/index.js +1049 -1246
- package/dist/types/auth/device.d.ts +1 -1
- package/dist/types/auth/index.d.ts +2 -2
- package/dist/types/auth/tokens.d.ts +15 -15
- package/dist/types/client.d.ts +10 -10
- package/dist/types/errors.d.ts +12 -15
- package/dist/types/http.d.ts +74 -118
- package/dist/types/index.d.ts +2 -2
- package/dist/types/internal/attachments.d.ts +17 -0
- package/dist/types/internal/helpers.d.ts +9 -0
- package/dist/types/listing.d.ts +90 -0
- package/dist/types/local/qr.d.ts +1 -1
- package/dist/types/local/wordlist.d.ts +2 -3
- package/dist/types/resources/account.d.ts +19 -19
- package/dist/types/resources/admin/authorizedApplications.d.ts +133 -0
- package/dist/types/resources/admin/chests.d.ts +51 -0
- package/dist/types/resources/admin/eventAlerts.d.ts +60 -0
- package/dist/types/resources/admin/identities.d.ts +99 -0
- package/dist/types/resources/admin/index.d.ts +77 -0
- package/dist/types/resources/admin/jobs.d.ts +97 -0
- package/dist/types/resources/admin/myOauthApplications.d.ts +282 -0
- package/dist/types/resources/admin/notepads.d.ts +49 -0
- package/dist/types/resources/admin/oauthApplications.d.ts +309 -0
- package/dist/types/resources/admin/quotas.d.ts +114 -0
- package/dist/types/resources/admin/shortLinks.d.ts +201 -0
- package/dist/types/resources/admin/types.d.ts +222 -0
- package/dist/types/resources/admin/vocalSeparations.d.ts +98 -0
- package/dist/types/resources/admin.d.ts +17 -13
- package/dist/types/resources/auth/index.d.ts +1 -1
- package/dist/types/resources/auth/passkeys.d.ts +127 -163
- package/dist/types/resources/auth/sessions.d.ts +116 -161
- package/dist/types/resources/chests.d.ts +27 -31
- package/dist/types/resources/content/analysis.d.ts +62 -0
- package/dist/types/resources/content/blogs.d.ts +471 -0
- package/dist/types/resources/content/feedbacks.d.ts +263 -0
- package/dist/types/resources/content/index.d.ts +52 -0
- package/dist/types/resources/content/intel/articles.d.ts +230 -0
- package/dist/types/resources/content/intel/config.d.ts +135 -0
- package/dist/types/resources/content/intel/index.d.ts +53 -0
- package/dist/types/resources/content/intel/items.d.ts +91 -0
- package/dist/types/resources/content/intel/reports.d.ts +108 -0
- package/dist/types/resources/content/intel/scripts.d.ts +152 -0
- package/dist/types/resources/content/intel/sources.d.ts +236 -0
- package/dist/types/resources/content/intel/stats.d.ts +105 -0
- package/dist/types/resources/content/intel/types.d.ts +86 -0
- package/dist/types/resources/content/jokes.d.ts +93 -0
- package/dist/types/resources/content/notifications.d.ts +147 -0
- package/dist/types/resources/content/serviceUsages.d.ts +73 -0
- package/dist/types/resources/content/servicesStatus.d.ts +194 -0
- package/dist/types/resources/content/siteConfig.d.ts +43 -0
- package/dist/types/resources/content/spaceInvaders.d.ts +154 -0
- package/dist/types/resources/content.d.ts +41 -236
- package/dist/types/resources/dynamicQrs.d.ts +29 -45
- package/dist/types/resources/forms.d.ts +37 -58
- package/dist/types/resources/jobs.d.ts +35 -44
- package/dist/types/resources/library/annotations.d.ts +162 -0
- package/dist/types/resources/library/books.d.ts +534 -0
- package/dist/types/resources/library/chat.d.ts +259 -0
- package/dist/types/resources/library/index.d.ts +39 -0
- package/dist/types/resources/library/shelves.d.ts +237 -0
- package/dist/types/resources/library/types.d.ts +120 -0
- package/dist/types/resources/library.d.ts +20 -22
- package/dist/types/resources/media.d.ts +48 -61
- package/dist/types/resources/movies/addons.d.ts +388 -0
- package/dist/types/resources/movies/collections.d.ts +352 -0
- package/dist/types/resources/movies/index.d.ts +29 -0
- package/dist/types/resources/movies/types.d.ts +50 -0
- package/dist/types/resources/movies/watchProgress.d.ts +277 -0
- package/dist/types/resources/movies.d.ts +27 -53
- package/dist/types/resources/music/artists.d.ts +182 -246
- package/dist/types/resources/music/imports.d.ts +190 -218
- package/dist/types/resources/music/index.d.ts +8 -7
- package/dist/types/resources/music/playlists.d.ts +85 -117
- package/dist/types/resources/music/social.d.ts +153 -228
- package/dist/types/resources/music/songs.d.ts +165 -227
- package/dist/types/resources/realtime.d.ts +75 -88
- package/dist/types/resources/shortLinks.d.ts +38 -47
- package/dist/types/resources/social/groupChats.d.ts +458 -0
- package/dist/types/resources/social/index.d.ts +19 -0
- package/dist/types/resources/social/messages.d.ts +424 -0
- package/dist/types/resources/social/relationships.d.ts +322 -0
- package/dist/types/resources/social/types.d.ts +37 -0
- package/dist/types/resources/social.d.ts +21 -3
- package/dist/types/resources/storage/upload.d.ts +42 -56
- package/dist/types/resources/storage.d.ts +94 -109
- package/dist/types/resources/tickets.d.ts +19 -10
- package/dist/types/resources/tools/backgroundRemoval.d.ts +11 -13
- package/dist/types/resources/tools/captions.d.ts +107 -135
- package/dist/types/resources/tools/upscale.d.ts +12 -16
- package/dist/types/types.d.ts +29 -38
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** Intel stats: the dashboard counters. */
|
|
2
|
+
import { Resource } from "../../../http";
|
|
3
|
+
import type { RequestOptions } from "../../../types";
|
|
4
|
+
/** One category bucket of {@link IntelStats}. */
|
|
5
|
+
export interface IntelCategoryCount {
|
|
6
|
+
readonly category: string;
|
|
7
|
+
/** Named `c`, not `count`. */
|
|
8
|
+
readonly c: number;
|
|
9
|
+
}
|
|
10
|
+
/** One day of the {@link IntelStats} histogram. */
|
|
11
|
+
export interface IntelDayCount {
|
|
12
|
+
/** `YYYY-MM-DD`, from Postgres `DATE(last_seen_at)`. Not a full timestamp. */
|
|
13
|
+
readonly day: string;
|
|
14
|
+
readonly c: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The importance histogram, with the backend's own Portuguese bucket names.
|
|
18
|
+
*
|
|
19
|
+
* The boundaries are hard-coded server-side and are not
|
|
20
|
+
* configurable: `critico` >=9, `alta` 7-8, `media` 5-6, `baixa` 3-4, `ruido`
|
|
21
|
+
* <3. Note they are NOT the same thresholds as
|
|
22
|
+
* {@link IntelConfig.report_min_importance} or
|
|
23
|
+
* {@link IntelConfig.enrich_min_importance} - those are yours, these are the
|
|
24
|
+
* dashboard's.
|
|
25
|
+
*/
|
|
26
|
+
export interface IntelImportanceBuckets {
|
|
27
|
+
readonly critico: number;
|
|
28
|
+
readonly alta: number;
|
|
29
|
+
readonly media: number;
|
|
30
|
+
readonly baixa: number;
|
|
31
|
+
readonly ruido: number;
|
|
32
|
+
}
|
|
33
|
+
/** Row counts on the {@link IntelStats} answer. */
|
|
34
|
+
export interface IntelStatsTotals {
|
|
35
|
+
/** Stories you own. */
|
|
36
|
+
readonly articles: number;
|
|
37
|
+
/**
|
|
38
|
+
* **Not the number of feeds you have configured.** This counts the
|
|
39
|
+
* story-to-item POINTER rows, so this is "how many citations exist across
|
|
40
|
+
* all my stories" and it grows without bound as
|
|
41
|
+
* stories accumulate. If you want the number of configured sources, read the
|
|
42
|
+
* length of {@link IntelSourcesNamespace.list}. The name is the backend's and
|
|
43
|
+
* the SDK does not rename it, but do not put it under a "Sources" label.
|
|
44
|
+
*/
|
|
45
|
+
readonly sources: number;
|
|
46
|
+
readonly reports: number;
|
|
47
|
+
/** Raw items you own, processed or not. */
|
|
48
|
+
readonly items: number;
|
|
49
|
+
/**
|
|
50
|
+
* Items the analysis pipeline has not consumed yet (`processed_at IS NULL`).
|
|
51
|
+
*
|
|
52
|
+
* The one number worth watching: a figure that climbs and never falls means
|
|
53
|
+
* the pipeline is not running - most often because `Intel::LlmClient` is
|
|
54
|
+
* disabled for want of an API key, in which case `AnalysisDispatcherJob`
|
|
55
|
+
* returns immediately and silently.
|
|
56
|
+
*/
|
|
57
|
+
readonly pending_items: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `GET /intel_stats` - counters for the intel dashboard.
|
|
61
|
+
*
|
|
62
|
+
* Not a record: it has no `id`, no timestamps and no `:extended` view.
|
|
63
|
+
*/
|
|
64
|
+
export interface IntelStats {
|
|
65
|
+
readonly totals: IntelStatsTotals;
|
|
66
|
+
/**
|
|
67
|
+
* Categories by story count, descending. Stories with a `null` category are
|
|
68
|
+
* EXCLUDED, so these do not sum to `totals.articles`.
|
|
69
|
+
*/
|
|
70
|
+
readonly by_category: IntelCategoryCount[];
|
|
71
|
+
/**
|
|
72
|
+
* The last 30 days by `last_seen_at`, ascending.
|
|
73
|
+
*
|
|
74
|
+
* Sparse: a day with no activity is simply ABSENT, not present with zero.
|
|
75
|
+
* Fill the gaps before plotting or the line will lie about its own x-axis.
|
|
76
|
+
*/
|
|
77
|
+
readonly by_day: IntelDayCount[];
|
|
78
|
+
readonly by_importance: IntelImportanceBuckets;
|
|
79
|
+
/** Stories touched in the last 24 hours. */
|
|
80
|
+
readonly last24h: number;
|
|
81
|
+
}
|
|
82
|
+
/** `/intel_stats` - the dashboard counters. One route, one verb. */
|
|
83
|
+
export declare class IntelStatsNamespace extends Resource {
|
|
84
|
+
/**
|
|
85
|
+
* `GET /intel_stats` - every counter the intel dashboard shows, in one call.
|
|
86
|
+
*
|
|
87
|
+
* Also a Rails singular resource, so the path is `/intel_stats` with no id
|
|
88
|
+
* despite the plural spelling.
|
|
89
|
+
*
|
|
90
|
+
* **Cost, and the reason not to poll this.** The controller does
|
|
91
|
+
* `articles.pluck(:importance)` - it loads the importance of EVERY story you
|
|
92
|
+
* own into Ruby memory to build {@link IntelStats.by_importance} - and then
|
|
93
|
+
* runs five more aggregate queries beside it. There is no cache, no `ETag`
|
|
94
|
+
* (the hand-written action never calls `stale?`, unlike every list in this
|
|
95
|
+
* file) and therefore no `304`. Cost grows linearly with your story count for
|
|
96
|
+
* ever. Fetch it on a dashboard open, not on a timer.
|
|
97
|
+
*
|
|
98
|
+
* Read {@link IntelStatsTotals.sources} before you label it: it does not
|
|
99
|
+
* count your feeds.
|
|
100
|
+
*
|
|
101
|
+
* @throws {OmsApiError} 403 `"Intel access is restricted."` outside the
|
|
102
|
+
* allowlist.
|
|
103
|
+
*/
|
|
104
|
+
get(options?: RequestOptions): Promise<IntelStats>;
|
|
105
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Vocabulary shared across the intel families. */
|
|
2
|
+
/**
|
|
3
|
+
* ## Access: this is effectively a one-user feature
|
|
4
|
+
*
|
|
5
|
+
* Every intel route is gated by a fixed allowlist of one handle plus admins.
|
|
6
|
+
* So: anonymous is `401`, any other signed-in account is
|
|
7
|
+
* `403 "Intel access is restricted."`, and no amount of correct request
|
|
8
|
+
* shaping changes that. Do not build a shared feature on it, and do not
|
|
9
|
+
* treat a 403 here as a bug in the caller.
|
|
10
|
+
*
|
|
11
|
+
* An OAuth access token is `403 {"error":"insufficient_scope"}` on every
|
|
12
|
+
* route below. Session credential only.
|
|
13
|
+
*
|
|
14
|
+
* ## Ids are STRINGS here
|
|
15
|
+
*
|
|
16
|
+
* Articles, reports, sources, scripts, items and the config row all carry
|
|
17
|
+
* opaque string ids, unlike blogs, notifications, jokes and Space Invaders
|
|
18
|
+
* games, whose ids are integers. Nothing in intel is ever a number you can
|
|
19
|
+
* compare or sort by.
|
|
20
|
+
*
|
|
21
|
+
* ## Ceilings
|
|
22
|
+
*
|
|
23
|
+
* None of their own. Every route rides the general bucket: 600 requests per
|
|
24
|
+
* minute for an authenticated caller. Two of them are still expensive and are
|
|
25
|
+
* documented as such - {@link IntelStatsNamespace.get} and
|
|
26
|
+
* {@link IntelSourcesNamespace.run}.
|
|
27
|
+
*/
|
|
28
|
+
/** Categories a story can carry. `null` when the classifier declined to pick one. */
|
|
29
|
+
export declare const INTEL_ARTICLE_CATEGORIES: readonly ["incidente", "politica", "comunidade", "sociedade", "internacional", "economia", "outro"];
|
|
30
|
+
/**
|
|
31
|
+
* A story's category.
|
|
32
|
+
*
|
|
33
|
+
* Widened with `string & {}` deliberately: the list is a Ruby constant that a
|
|
34
|
+
* migration can extend without the SDK noticing, and a `switch` that fails to
|
|
35
|
+
* compile on a new category is worse than one that falls through to a default.
|
|
36
|
+
* The backend DOES validate inclusion, so a value outside the list can only
|
|
37
|
+
* mean the constant moved.
|
|
38
|
+
*/
|
|
39
|
+
export type IntelArticleCategory = (typeof INTEL_ARTICLE_CATEGORIES)[number] | (string & {});
|
|
40
|
+
/** Report windows a report can cover. */
|
|
41
|
+
export declare const INTEL_REPORT_KINDS: readonly ["6h", "day", "week", "month"];
|
|
42
|
+
/** Which window a report covers. */
|
|
43
|
+
export type IntelReportKind = (typeof INTEL_REPORT_KINDS)[number] | (string & {});
|
|
44
|
+
/**
|
|
45
|
+
* Base of the third-party image proxy {@link intelArticleImageUrl} builds on.
|
|
46
|
+
*
|
|
47
|
+
* `wsrv.nl` is a free public image CDN. It is NOT this API and NOT our
|
|
48
|
+
* infrastructure.
|
|
49
|
+
*/
|
|
50
|
+
export declare const INTEL_IMAGE_PROXY_BASE_URL = "https://wsrv.nl/";
|
|
51
|
+
/** Knobs for {@link intelArticleImageUrl}. */
|
|
52
|
+
export interface IntelImageOptions {
|
|
53
|
+
/** Target width in pixels. Default 480. Height follows the aspect ratio. */
|
|
54
|
+
readonly width?: number;
|
|
55
|
+
/** Quality, 1-100. Default 45 - low on purpose; these are thumbnails. */
|
|
56
|
+
readonly quality?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Builds a resized, re-compressed URL for {@link IntelArticle.image_url}.
|
|
60
|
+
*
|
|
61
|
+
* Pure string building, no request, isolate-safe. Returns `""` for a story with
|
|
62
|
+
* no image so it can be dropped straight into an `<img src>` without a
|
|
63
|
+
* conditional - though a real client should test the field and render nothing.
|
|
64
|
+
*
|
|
65
|
+
* ## What this actually does, and why you might not want it
|
|
66
|
+
*
|
|
67
|
+
* `image_url` is the raw `og:image` of a news site: full size, arbitrary
|
|
68
|
+
* format, arbitrary weight, and served from that site's own host. Nothing in
|
|
69
|
+
* this API resizes it. This helper routes it through `wsrv.nl`, a free public
|
|
70
|
+
* image CDN, which fetches the origin image and hands back a width-limited
|
|
71
|
+
* WebP.
|
|
72
|
+
*
|
|
73
|
+
* The trade is explicit and it is not the SDK's to make silently:
|
|
74
|
+
*
|
|
75
|
+
* - the ORIGIN URL is sent to a third party in a query string, so wsrv.nl
|
|
76
|
+
* learns which article your user is looking at, and so does anyone reading
|
|
77
|
+
* the request. There is no credential involved - the images are public - but
|
|
78
|
+
* it is still a referrer-shaped leak;
|
|
79
|
+
* - availability is theirs, not ours. A wsrv.nl outage is a page of broken
|
|
80
|
+
* images, and there is no fallback in the URL;
|
|
81
|
+
* - `&we` asks it not to enlarge images smaller than `width`.
|
|
82
|
+
*
|
|
83
|
+
* If neither trade suits you, use {@link IntelArticle.image_url} directly and
|
|
84
|
+
* size it in CSS.
|
|
85
|
+
*/
|
|
86
|
+
export declare function intelArticleImageUrl(imageUrl: string | null | undefined, options?: IntelImageOptions): string;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** The joke table behind the loading screens. */
|
|
2
|
+
import { Resource } from "../../http";
|
|
3
|
+
import type { ListParams } from "../../listing";
|
|
4
|
+
import type { Paginated, RequestOptions, Timestamp } from "../../types";
|
|
5
|
+
/** `GET /jokes` filters on {@link BASE_FILTER_COLUMNS} only. */
|
|
6
|
+
export declare const JOKE_FILTER_COLUMNS: readonly [];
|
|
7
|
+
/** Filters for {@link JokesNamespace.list}. */
|
|
8
|
+
export type ListJokesParams = ListParams<(typeof JOKE_FILTER_COLUMNS)[number]>;
|
|
9
|
+
/** Primary key of a joke. An INTEGER. */
|
|
10
|
+
export type JokeId = number;
|
|
11
|
+
/**
|
|
12
|
+
* A joke.
|
|
13
|
+
*
|
|
14
|
+
* Unlike the blog records this one really does carry all three base fields.
|
|
15
|
+
*/
|
|
16
|
+
export interface Joke {
|
|
17
|
+
readonly id: JokeId;
|
|
18
|
+
/**
|
|
19
|
+
* Language tag, as whoever typed it wrote it. Free text with a presence
|
|
20
|
+
* validation and NOTHING else - no inclusion list, no normalisation - so the
|
|
21
|
+
* table can and does hold `"pt"` next to `"PT"` next to `"pt-PT"`. Compare
|
|
22
|
+
* case-insensitively, and see {@link JokesNamespace.list} for why you cannot
|
|
23
|
+
* make the server do the filtering.
|
|
24
|
+
*/
|
|
25
|
+
readonly lang: string;
|
|
26
|
+
/**
|
|
27
|
+
* The joke. A `varchar` with no database limit and no model validation; the
|
|
28
|
+
* web composer caps input at 255 characters as a house rule, which nothing
|
|
29
|
+
* server-side enforces.
|
|
30
|
+
*/
|
|
31
|
+
readonly content: string;
|
|
32
|
+
readonly created_at: Timestamp;
|
|
33
|
+
readonly updated_at: Timestamp;
|
|
34
|
+
}
|
|
35
|
+
/** Arguments for {@link JokesNamespace.create}. Both fields are required by the model. */
|
|
36
|
+
export interface JokeInput {
|
|
37
|
+
readonly lang: string;
|
|
38
|
+
readonly content: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The `jokes` namespace: the joke table behind the site's loading screens.
|
|
42
|
+
*
|
|
43
|
+
* Reading is fully public; writing is admin-only. `Joke.viewable_by` is `all`,
|
|
44
|
+
* so every joke is visible to every caller including anonymous ones, and
|
|
45
|
+
* `creatable_by?`/`updatable_by?`/`destroyable_by?` all reduce to
|
|
46
|
+
* `user.admin?`.
|
|
47
|
+
*/
|
|
48
|
+
export declare class JokesNamespace extends Resource {
|
|
49
|
+
/**
|
|
50
|
+
* `GET /jokes` - the joke table, paged. Anonymous callers welcome.
|
|
51
|
+
*
|
|
52
|
+
* **You cannot filter by language.** The allowlist is only the three
|
|
53
|
+
* defaults - `id`, `created_at`, `updated_at` - and `lang` is not on it.
|
|
54
|
+
* `search: { lang: "pt" }`
|
|
55
|
+
* is `400 "Unknown search filter: lang"`, not a wider result: the DSL fails
|
|
56
|
+
* closed. Pull a page and filter client-side, which is what every caller
|
|
57
|
+
* ends up doing.
|
|
58
|
+
*
|
|
59
|
+
* For "give me a joke", `random: true` with `pageSize: 1` is the whole
|
|
60
|
+
* recipe: the server applies `ORDER BY RANDOM()` and the pagination is
|
|
61
|
+
* applied after it. Note that a random listing carries no `ETag` and can
|
|
62
|
+
* never answer `304`, which is exactly what you want here and exactly what
|
|
63
|
+
* you do not want on a normal page.
|
|
64
|
+
*
|
|
65
|
+
* `modifiers[order]` also accepts a third segment for an explicit value
|
|
66
|
+
* ordering (`"lang:asc:pt,en"` puts those languages first), which the rest
|
|
67
|
+
* of the SDK does not advertise because almost nothing needs it.
|
|
68
|
+
*/
|
|
69
|
+
list(params?: ListJokesParams, options?: RequestOptions): Promise<Paginated<Joke>>;
|
|
70
|
+
/**
|
|
71
|
+
* `POST /jokes` - adds a joke. `201`. **Admin only.**
|
|
72
|
+
*
|
|
73
|
+
* A signed-in non-admin gets `401 "You are not authorized to create this
|
|
74
|
+
* resource"`; an anonymous caller gets `401 "Session required to access
|
|
75
|
+
* this resource."` from the authentication filter first.
|
|
76
|
+
*/
|
|
77
|
+
create(input: JokeInput, options?: RequestOptions): Promise<Joke>;
|
|
78
|
+
/**
|
|
79
|
+
* `PATCH /jokes/:id` - edits a joke. **Admin only.**
|
|
80
|
+
*
|
|
81
|
+
* Both fields are permitted and both are optional; the model requires each
|
|
82
|
+
* to be present, so sending `content: ""` is `400`, not a clear.
|
|
83
|
+
*/
|
|
84
|
+
update(id: JokeId, input: Partial<JokeInput>, options?: RequestOptions): Promise<Joke>;
|
|
85
|
+
/**
|
|
86
|
+
* `DELETE /jokes/:id` - removes a joke. `204`. **Admin only.**
|
|
87
|
+
*
|
|
88
|
+
* There is no `GET /jokes/:id`: the resource is declared
|
|
89
|
+
* `only: [:create, :index, :update, :destroy]`, so a single fetch by id is a
|
|
90
|
+
* routing 404.
|
|
91
|
+
*/
|
|
92
|
+
destroy(id: JokeId, options?: RequestOptions): Promise<void>;
|
|
93
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/** The per-user notification inbox. */
|
|
2
|
+
import { Resource } from "../../http";
|
|
3
|
+
import type { ListParams } from "../../listing";
|
|
4
|
+
import type { Id, Paginated, RequestOptions, Timestamp } from "../../types";
|
|
5
|
+
/** Filter columns of `GET /notifications`, on top of {@link BASE_FILTER_COLUMNS}. `read` and `kind` are not filterable. */
|
|
6
|
+
export declare const NOTIFICATION_FILTER_COLUMNS: readonly ["user_id"];
|
|
7
|
+
/** Filters for {@link NotificationsNamespace.list}. */
|
|
8
|
+
export type ListNotificationsParams = ListParams<(typeof NOTIFICATION_FILTER_COLUMNS)[number]>;
|
|
9
|
+
/** Primary key of a notification. An INTEGER. */
|
|
10
|
+
export type NotificationId = number;
|
|
11
|
+
/**
|
|
12
|
+
* The `kind` strings the backend emits today.
|
|
13
|
+
*
|
|
14
|
+
* NOT a closed set and not validated anywhere - `Notification` only requires
|
|
15
|
+
* `kind` to be present, so a new feature can add one without a migration. The
|
|
16
|
+
* union is here so the kinds you handle autocomplete; keep a default branch
|
|
17
|
+
* for the ones you do not, and never let an unknown kind break the inbox.
|
|
18
|
+
*
|
|
19
|
+
* Each kind implies a different {@link Notification.context} shape, which is
|
|
20
|
+
* why `context` is typed as an open record rather than a discriminated union:
|
|
21
|
+
* the backend guarantees a JSON object and nothing about its keys.
|
|
22
|
+
*/
|
|
23
|
+
export type NotificationKind = "friendship_request" | "friendship_accepted" | "user_followed" | "message_received" | "fs_grant_received" | "jam_invite" | "vocal_separation_done" | "vocal_separation_failed" | (string & {});
|
|
24
|
+
/**
|
|
25
|
+
* One notification in a user's inbox.
|
|
26
|
+
*
|
|
27
|
+
* Unlike most of this file it IS a full record, so it carries `id`,
|
|
28
|
+
* `created_at` and `updated_at`. The `:extended` view adds
|
|
29
|
+
* nothing, so a notification arriving over the cable and one arriving over
|
|
30
|
+
* HTTP have the same fields.
|
|
31
|
+
*/
|
|
32
|
+
export interface Notification {
|
|
33
|
+
/** An integer, and a JSON number, not a string. */
|
|
34
|
+
readonly id: NotificationId;
|
|
35
|
+
/** What happened. See {@link NotificationKind}. */
|
|
36
|
+
readonly kind: NotificationKind;
|
|
37
|
+
/**
|
|
38
|
+
* Free-form JSON payload, whose keys depend entirely on `kind` - these are
|
|
39
|
+
* the i18n interpolation values the client renders the sentence with.
|
|
40
|
+
*
|
|
41
|
+
* Never `null` (the column is `NOT NULL DEFAULT '{}'`), and never large: the
|
|
42
|
+
* emitter runs user-supplied text through a 120-character preview before
|
|
43
|
+
* storing it, so a message-received notification carries a truncated
|
|
44
|
+
* snippet, not the message.
|
|
45
|
+
*
|
|
46
|
+
* One shape is documented outside the code and worth having here:
|
|
47
|
+
* `jam_invite` carries `{ jam_id, host_id, host_handle, inviter_id,
|
|
48
|
+
* inviter_handle }`. The rest you learn by reading a row.
|
|
49
|
+
*/
|
|
50
|
+
readonly context: Record<string, unknown>;
|
|
51
|
+
/** Whether it has been marked read. See {@link NotificationsNamespace.markAllRead}. */
|
|
52
|
+
readonly read: boolean;
|
|
53
|
+
/** Owner. Always the caller: the scope is `user.notifications`. A STRING. */
|
|
54
|
+
readonly user_id: Id;
|
|
55
|
+
readonly created_at: Timestamp;
|
|
56
|
+
readonly updated_at: Timestamp;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The `notifications` namespace: the per-user inbox.
|
|
60
|
+
*
|
|
61
|
+
* ## This covers the HTTP half only
|
|
62
|
+
*
|
|
63
|
+
* Notifications are pushed in real time over ActionCable, on the per-user
|
|
64
|
+
* `NotificationsChannel` stream, and that is where a live inbox gets its
|
|
65
|
+
* updates from: the channel transmits `{type: "unread_count", unread_count}`
|
|
66
|
+
* on subscribe, `{type: "created", notification, unread_count}` when one
|
|
67
|
+
* arrives, and `{type: "unread_count", unread_count}` again whenever the read
|
|
68
|
+
* state or the row count changes. The SDK does not open that socket and does
|
|
69
|
+
* not wrap it - it has no cable client - so these methods are the polling
|
|
70
|
+
* fallback and the write path, not the way to keep a badge live. A host with a
|
|
71
|
+
* socket should subscribe and use {@link unreadCount} only for the first
|
|
72
|
+
* paint.
|
|
73
|
+
*
|
|
74
|
+
* ## You cannot mark ONE notification read
|
|
75
|
+
*
|
|
76
|
+
* `PATCH /notifications/:id` is routed, and it cannot succeed for anybody: it
|
|
77
|
+
* answers `401 "You are not authorized to update this resource"` on every
|
|
78
|
+
* call - for the owner, for an admin, for everyone. The SDK therefore exposes
|
|
79
|
+
* no `markRead(id)`: there is nothing honest to put behind it. Mark the whole
|
|
80
|
+
* inbox with {@link markAllRead}, or remove the row with {@link dismiss}.
|
|
81
|
+
*
|
|
82
|
+
* There is also no `GET /notifications/:id`: the resource is declared
|
|
83
|
+
* `only: [:index, :update, :destroy]`, so a single fetch by id is a routing
|
|
84
|
+
* 404. Read one out of {@link list}.
|
|
85
|
+
*
|
|
86
|
+
* Everything here needs a session and rides the general 600/min ceiling.
|
|
87
|
+
*/
|
|
88
|
+
export declare class NotificationsNamespace extends Resource {
|
|
89
|
+
/**
|
|
90
|
+
* `GET /notifications` - the caller's inbox, one page at a time.
|
|
91
|
+
*
|
|
92
|
+
* Scoped to the caller by `viewable_by` (`user.notifications`), so there is
|
|
93
|
+
* no way to read anybody else's and the `user_id` filter below is redundant.
|
|
94
|
+
*
|
|
95
|
+
* **The filterable columns are almost none.** The controller declares
|
|
96
|
+
* `search_params :user_id`, which the DSL merges with the three defaults, so
|
|
97
|
+
* the complete allowlist is `id`, `created_at`, `updated_at` and `user_id`.
|
|
98
|
+
* `read` and `kind` are NOT on it, and filters fail closed: asking for
|
|
99
|
+
* `exact_search: { read: false }` - the obvious way to fetch the unread ones -
|
|
100
|
+
* is `400 "Unknown exact_search filter: read"`, not an unfiltered list.
|
|
101
|
+
* Fetch a page and filter client-side, or read {@link unreadCount} for the
|
|
102
|
+
* badge.
|
|
103
|
+
*
|
|
104
|
+
* No default ordering is declared, so rows come back in whatever order
|
|
105
|
+
* Postgres chooses. Pass `order: "created_at:desc"` for an inbox; there is
|
|
106
|
+
* an index on `(user_id, created_at)` behind it.
|
|
107
|
+
*
|
|
108
|
+
* Sends an `ETag`, so an unchanged page answers `304` and costs nothing -
|
|
109
|
+
* except with `random: true`, which disables the check.
|
|
110
|
+
*/
|
|
111
|
+
list(params?: ListNotificationsParams, options?: RequestOptions): Promise<Paginated<Notification>>;
|
|
112
|
+
/**
|
|
113
|
+
* `GET /notifications/unread_count` - how many unread notifications the
|
|
114
|
+
* caller has. Unwraps the `{"count": n}` the server sends.
|
|
115
|
+
*
|
|
116
|
+
* One indexed `COUNT` behind a partial index (`WHERE read = false`), so it is
|
|
117
|
+
* cheap - but it is still a request per call, and the cable already pushes
|
|
118
|
+
* this number on subscribe and on every change. Poll it only where there is
|
|
119
|
+
* no socket.
|
|
120
|
+
*/
|
|
121
|
+
unreadCount(options?: RequestOptions): Promise<number>;
|
|
122
|
+
/**
|
|
123
|
+
* `POST /notifications/read_all` - marks every unread notification read.
|
|
124
|
+
*
|
|
125
|
+
* Returns how many rows changed, which is the unread count from an instant
|
|
126
|
+
* ago; calling it twice returns `0` the second time. `200`, not `201` - it
|
|
127
|
+
* creates nothing.
|
|
128
|
+
*
|
|
129
|
+
* Runs as a single `update_all`, so no model callback fires and the per-row
|
|
130
|
+
* broadcast is skipped; the controller pushes the new count over the cable
|
|
131
|
+
* by hand afterwards, which is why every device still updates.
|
|
132
|
+
*
|
|
133
|
+
* Idempotent, so a retry is harmless. It is not enabled by default (the
|
|
134
|
+
* transport does not replay a `POST`); pass `retry: {}` if you want one.
|
|
135
|
+
*/
|
|
136
|
+
markAllRead(options?: RequestOptions): Promise<number>;
|
|
137
|
+
/**
|
|
138
|
+
* `DELETE /notifications/:id` - removes one notification. `204`, no body.
|
|
139
|
+
*
|
|
140
|
+
* This is the closest thing to "mark as read" the API has: the row is gone,
|
|
141
|
+
* so the unread count drops and the new count is pushed over the cable.
|
|
142
|
+
*
|
|
143
|
+
* Owner only - `viewable_by` scopes the lookup to the caller, so somebody
|
|
144
|
+
* else's id is `404 "Resource not found"` rather than a 401.
|
|
145
|
+
*/
|
|
146
|
+
dismiss(id: NotificationId, options?: RequestOptions): Promise<void>;
|
|
147
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Per-user service visit counters. */
|
|
2
|
+
import { Resource } from "../../http";
|
|
3
|
+
import type { RequestOptions } from "../../types";
|
|
4
|
+
/**
|
|
5
|
+
* The twelve service ids the counter accepts. A closed set: anything else is
|
|
6
|
+
* `400 "Unknown service_id"`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SERVICE_USAGE_IDS: readonly ["storage", "tools", "games", "music", "movies", "ai", "account", "tickets", "messages", "blogs", "status", "administration"];
|
|
9
|
+
/** One of {@link SERVICE_USAGE_IDS}. */
|
|
10
|
+
export type ServiceUsageId = (typeof SERVICE_USAGE_IDS)[number];
|
|
11
|
+
/**
|
|
12
|
+
* A per-user visit counter.
|
|
13
|
+
*
|
|
14
|
+
* Note what is NOT here: no `id`, no `user_id`, no timestamps. Both routes
|
|
15
|
+
* answer a bare `{ service_id, count }`, so this is one of the very few
|
|
16
|
+
* payloads in the API that is not a record.
|
|
17
|
+
*/
|
|
18
|
+
export interface ServiceUsage {
|
|
19
|
+
readonly service_id: ServiceUsageId;
|
|
20
|
+
/** Lifetime visit count for this user and service. Monotonic, never reset. */
|
|
21
|
+
readonly count: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The `service_usages` namespace: which parts of the site a user opens, so the
|
|
25
|
+
* home screen can put their favourites first.
|
|
26
|
+
*
|
|
27
|
+
* Both routes need a session - there is no `allow_unauthenticated_access` on
|
|
28
|
+
* this controller - and both are pure bookkeeping. The music app calls
|
|
29
|
+
* {@link record} with `"music"` on launch, fire and forget.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ServiceUsagesNamespace extends Resource {
|
|
32
|
+
/**
|
|
33
|
+
* `POST /service_usages` - increments the caller's counter for one service.
|
|
34
|
+
*
|
|
35
|
+
* Answers **`200`, not `201`**, even on the very first call that creates the
|
|
36
|
+
* row: the controller uses `ok!` rather than `created!`, so this is one of
|
|
37
|
+
* the handful of creates in the API that breaks the 201 convention. The body
|
|
38
|
+
* is the updated `{ service_id, count }`.
|
|
39
|
+
*
|
|
40
|
+
* ## Not idempotent, and it can page somebody
|
|
41
|
+
*
|
|
42
|
+
* Every call does `count += 1` and stamps `last_visited_at`, so a retry
|
|
43
|
+
* inflates the number. That is why the transport's default of never
|
|
44
|
+
* replaying a `POST` is the right default here: do not pass `retry: {}`.
|
|
45
|
+
*
|
|
46
|
+
* It can also fire a Discord `service_opened` alert - on the first ever
|
|
47
|
+
* visit, and again whenever more than an hour has passed since the last one.
|
|
48
|
+
* A client that calls this on every route change inside an app is fine (the
|
|
49
|
+
* hour gap suppresses the alert), but a client that calls it from a
|
|
50
|
+
* background poller is a pager, not telemetry.
|
|
51
|
+
*
|
|
52
|
+
* Fire and forget: nothing in a UI should wait on this, and nothing should
|
|
53
|
+
* fail because it failed.
|
|
54
|
+
*
|
|
55
|
+
* @throws {OmsApiError} 400 `"Unknown service_id"` for anything outside
|
|
56
|
+
* {@link SERVICE_USAGE_IDS}; 401 without a session.
|
|
57
|
+
*/
|
|
58
|
+
record(serviceId: ServiceUsageId, options?: RequestOptions): Promise<ServiceUsage>;
|
|
59
|
+
/**
|
|
60
|
+
* `GET /service_usages/top` - the caller's most-used services, busiest
|
|
61
|
+
* first, tie-broken by most recently visited.
|
|
62
|
+
*
|
|
63
|
+
* Not the list DSL: `limit` is the only parameter, it is clamped to
|
|
64
|
+
* `1..10` (silently - asking for 50 returns 10), and it defaults to 3. There
|
|
65
|
+
* is no paging and no way to read the full set.
|
|
66
|
+
*
|
|
67
|
+
* Only services the caller has actually opened appear, so a fresh account
|
|
68
|
+
* gets an empty array rather than every id with a zero.
|
|
69
|
+
*/
|
|
70
|
+
top(input?: {
|
|
71
|
+
readonly limit?: number;
|
|
72
|
+
}, options?: RequestOptions): Promise<ServiceUsage[]>;
|
|
73
|
+
}
|