@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,7 +1,7 @@
1
1
  /**
2
2
  * OAuth 2.0 Device Authorization Grant (RFC 8628).
3
3
  *
4
- * This is how a CLI or an MCP server signs a person in without ever handling
4
+ * This is how a terminal or a headless client signs a person in without ever handling
5
5
  * their password: the client asks for a code, the person opens a URL in a real
6
6
  * browser and approves, and the client polls the token endpoint until the
7
7
  * approval lands.
@@ -36,7 +36,7 @@ export * from "./device";
36
36
  export * from "./tokens";
37
37
  /** Who the current credential belongs to. */
38
38
  export interface WhoAmI {
39
- /** `users.id`. The stable identifier; matches the OIDC `sub` claim. */
39
+ /** The stable user identifier; matches the OIDC `sub` claim. */
40
40
  readonly id: string;
41
41
  /** Current handle. Mutable - never key anything on it. */
42
42
  readonly handle: string;
@@ -107,7 +107,7 @@ export declare class AuthNamespace extends Resource {
107
107
  }, options?: RequestOptions): Promise<void>;
108
108
  /**
109
109
  * Reads the OIDC claims of the current credential from
110
- * `GET /oauth/userinfo`. `sub` is `users.id`.
110
+ * `GET /oauth/userinfo`. `sub` is the user's stable `id`.
111
111
  *
112
112
  * Needs the `openid` scope; without it the answer is 403. Members whose
113
113
  * value is null or empty are omitted from the response, so read defensively.
@@ -2,15 +2,15 @@
2
2
  * Token providers: where the transport gets a bearer token from.
3
3
  *
4
4
  * The core has no storage. A provider is either a constant, or a thing the
5
- * host wired to its own store (the CLI's config file, a Worker KV namespace, a
5
+ * host wired to its own store (a config file, a Worker KV namespace, a
6
6
  * browser's memory). Nothing here reads a file or an environment variable.
7
7
  *
8
8
  * Two credential shapes exist today and both are just bearer tokens on the
9
9
  * wire:
10
- * - a legacy opaque session token (a UUID minted by `Session`), which never
11
- * expires and carries no scopes;
12
- * - an OAuth 2 / OIDC access token from doorkeeper, which does expire and does
13
- * carry scopes, and which comes with a refresh token.
10
+ * - a legacy opaque session token (a UUID), which never expires and carries no
11
+ * scopes;
12
+ * - an OAuth 2 / OIDC access token, which does expire and does carry scopes,
13
+ * and which comes with a refresh token.
14
14
  *
15
15
  * {@link TokenSet} models the second. The first is just a string.
16
16
  *
@@ -23,7 +23,7 @@ import { OmsError } from "../errors";
23
23
  import { type ApiClient, type TokenProvider } from "../http";
24
24
  import type { RequestOptions } from "../types";
25
25
  /**
26
- * An OAuth 2 token response, as doorkeeper returns it.
26
+ * An OAuth 2 token response, as the token endpoint returns it.
27
27
  *
28
28
  * `expiresAt` is absolute epoch milliseconds, not the `expires_in` seconds the
29
29
  * server sends, so a stored set stays correct across a restart.
@@ -45,8 +45,8 @@ export interface TokenSet {
45
45
  /** Claims the SDK reads out of an OIDC id token. */
46
46
  export interface IdentityClaims {
47
47
  /**
48
- * Stable user identifier: `users.id`. Never the handle and never the email,
49
- * both of which the user can change.
48
+ * Stable user identifier: the user's `id`, as `oms.account.me()` reports it.
49
+ * Never the handle and never the email, both of which the user can change.
50
50
  */
51
51
  readonly sub: string;
52
52
  readonly iss?: string;
@@ -87,8 +87,8 @@ export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
87
87
  /**
88
88
  * Wraps a constant token. This is what `new Oms({ token })` builds.
89
89
  *
90
- * Accepts both credential kinds: an opaque session UUID and a doorkeeper
91
- * access token look identical on the wire.
90
+ * Accepts both credential kinds: an opaque session UUID and an OAuth access
91
+ * token look identical on the wire.
92
92
  */
93
93
  export declare function staticToken(token: string | null): TokenProvider;
94
94
  /**
@@ -304,8 +304,8 @@ export declare class OmsOAuthError extends OmsError {
304
304
  * Recognises an OAuth error inside whatever the transport threw.
305
305
  *
306
306
  * Deliberately narrow: only 400 and 401 bodies are read as OAuth errors. A 429
307
- * comes from rack-attack with the body `{"error":"rate_limited"}`, and that
308
- * `error` key is NOT an OAuth code - reading it as one abandons a perfectly
307
+ * carries the body `{"error":"rate_limited"}`, and that `error` key is NOT an
308
+ * OAuth code - reading it as one abandons a perfectly
309
309
  * live device flow. It stays an {@link OmsQuotaError} and the caller handles
310
310
  * it as a rate limit.
311
311
  *
@@ -316,8 +316,8 @@ export declare function oauthErrorFrom(thrown: unknown): OmsOAuthError | undefin
316
316
  export interface InsufficientScope {
317
317
  /**
318
318
  * The scopes the endpoint needed. EMPTY when the server named none, which
319
- * means the endpoint has not been opened to OAuth clients at all - a backend
320
- * gap, not a client bug. The two cases must read differently to the user.
319
+ * means the endpoint accepts no OAuth token at all - a server-side gap, not
320
+ * a client bug. The two cases must read differently to the user.
321
321
  */
322
322
  readonly required: string[];
323
323
  /** The `realm` parameter, when present. */
@@ -342,7 +342,7 @@ export declare function readInsufficientScope(error: unknown): InsufficientScope
342
342
  *
343
343
  * The OAuth endpoints do not take JSON, which is why this exists next to
344
344
  * `ApiClient.post` rather than using it. Blank values are dropped rather than
345
- * sent empty, because doorkeeper reads `""` as a present-but-invalid parameter.
345
+ * sent empty, because the server reads `""` as a present-but-invalid parameter.
346
346
  *
347
347
  * Retries are OFF and stay off. Replaying `POST /oauth/token` after a lost
348
348
  * response is not safe: the server may have rotated the refresh token already,
@@ -41,7 +41,7 @@ import type { FetchLike, RetryOptions } from "./types";
41
41
  * Anything accepted as a credential by {@link Oms}.
42
42
  *
43
43
  * A bare string is either kind of bearer token the API takes: a legacy opaque
44
- * `Session` UUID or an OAuth access token. A function is called on every
44
+ * session UUID or an OAuth access token. A function is called on every
45
45
  * request. A {@link TokenProvider} additionally gets a chance to refresh on a
46
46
  * 401 - see `auth/tokens.ts`.
47
47
  */
@@ -66,7 +66,7 @@ export interface OmsOptions {
66
66
  * by name.
67
67
  *
68
68
  * ```ts
69
- * // in the omelhorsite web app, served from the same site as the API
69
+ * // on a first-party page, served from the same site as the API
70
70
  * const oms = new Oms({ sessionCookie: true });
71
71
  * ```
72
72
  */
@@ -75,13 +75,13 @@ export interface OmsOptions {
75
75
  readonly baseUrl?: string;
76
76
  /**
77
77
  * The fetch to talk through. Defaults to `globalThis.fetch`. Injecting one is
78
- * how a Worker adds a cache, how a test swaps in a double, and how the CLI
78
+ * how a Worker adds a cache, how a test swaps in a double, and how a host
79
79
  * adds a proxy - the SDK never patches a global.
80
80
  */
81
81
  readonly fetch?: FetchLike;
82
82
  /** Headers merged into every request, below per-call headers. */
83
83
  readonly headers?: Record<string, string>;
84
- /** Default deadline for one call including its retries. `0` disables it. */
84
+ /** Default deadline for one attempt; a retry gets a fresh one. `0` disables it. */
85
85
  readonly timeoutMs?: number;
86
86
  /** Default backoff policy, or `false` to never retry. */
87
87
  readonly retry?: RetryOptions | false;
@@ -103,10 +103,10 @@ export interface OmsOptions {
103
103
  *
104
104
  * ## Two credentials, two namespaces
105
105
  *
106
- * `oms.sessions` mints the opaque `Session` token the website and the mobile
107
- * apps use, which carries the whole account. `oms.auth` runs OAuth, whose
108
- * tokens are scoped and, because `enforce_oauth_scope!` denies by omission,
109
- * cannot reach most of the API at all. `oms.realtime` accepts only the first
106
+ * `oms.sessions` mints the opaque session token, which carries the whole
107
+ * account. `oms.auth` runs OAuth, whose tokens are scoped and, because an
108
+ * endpoint with no declared scope refuses every OAuth token, cannot reach most
109
+ * of the API at all. `oms.realtime` accepts only the first
110
110
  * kind. Picking the wrong one shows up as a `403 insufficient_scope`, or on the
111
111
  * cable as a connection that is silently anonymous.
112
112
  *
@@ -178,9 +178,9 @@ export declare class Oms {
178
178
  /** OAuth client registration for anyone, plus the `/admin/*` routes. */
179
179
  readonly admin: AdminNamespace;
180
180
  /**
181
- * The ActionCable connection: playback handoff, jams, notifications, job
181
+ * The WebSocket connection: playback handoff, jams, notifications, job
182
182
  * progress. Opens nothing until {@link RealtimeNamespace.connect} is called,
183
- * and wants a `Session` token rather than the client's own credential - see
183
+ * and wants a session token rather than the client's own credential - see
184
184
  * that method for why.
185
185
  */
186
186
  readonly realtime: RealtimeNamespace;
@@ -8,15 +8,15 @@
8
8
  * The API answers with at least four different error body shapes, so anything
9
9
  * that reads an error body must go through {@link normalizeErrorBody}:
10
10
  *
11
- * - a bare JSON string: `"Image too large"` (ResponseHelpers)
12
- * - a sentence from ActiveModel: `"Name can't be blank and ..."` (error_messages)
11
+ * - a bare JSON string: `"Image too large"`
12
+ * - a validation sentence: `"Name can't be blank and ..."`
13
13
  * - an object with `error`: `{"error":"rate_limited","retry_after":37}`
14
14
  * - an object of field errors: `{"errors":{"url":["is invalid"]}}`
15
15
  * - plain text / HTML: short-link 404 pages, proxy errors
16
16
  */
17
17
  /** Machine-readable code carried by every SDK error. */
18
18
  export type OmsErrorCode = "api_error" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "invalid_request" | "quota_exceeded" | "rate_limited" | "server_error" | "timeout" | "aborted" | "network" | "unsupported" | "unknown";
19
- /** Extra context attached to an error, useful for logs and for the CLI. */
19
+ /** Extra context attached to an error, useful for logs. */
20
20
  export interface OmsErrorContext {
21
21
  /** HTTP method of the failing request, when there was one. */
22
22
  readonly method?: string;
@@ -38,9 +38,9 @@ export declare class OmsError extends Error {
38
38
  * This class's own name, as a LITERAL.
39
39
  *
40
40
  * `this.name = new.target.name` would be the obvious way to do this and it
41
- * is wrong here: `bun build --minify` renames the classes, so the shipped
42
- * `oms` binary reported `name: "A"` in its JSON error envelope while a dev
43
- * run reported `OmsNetworkError`. A minifier renames identifiers, never
41
+ * is wrong here: `bun build --minify` renames the classes, so a minified
42
+ * build reported `name: "A"` in its JSON error envelope while a dev run
43
+ * reported `OmsNetworkError`. A minifier renames identifiers, never
44
44
  * string literals or property names, so a static literal survives the build.
45
45
  *
46
46
  * Every subclass shadows this, and `new.target` is the constructor that was
@@ -95,9 +95,6 @@ export declare class OmsApiError extends OmsError {
95
95
  }
96
96
  /**
97
97
  * 401 or 403: the credential is missing, expired, or not allowed to do this.
98
- *
99
- * The CLI turns this into "run `oms auth login`"; the MCP server turns it into
100
- * a device-flow prompt.
101
98
  */
102
99
  export declare class OmsAuthError extends OmsApiError {
103
100
  static readonly errorName: string;
@@ -107,10 +104,10 @@ export declare class OmsAuthError extends OmsApiError {
107
104
  /**
108
105
  * 429, or a documented daily-quota rejection.
109
106
  *
110
- * Two different producers land here and they do not look alike:
111
- * rack-attack answers `{"error":"rate_limited","retry_after":37}` with a
112
- * `Retry-After` header, while a controller quota gate answers a bare string
113
- * such as `"Daily edit quota reached (5/day)"` with no header at all. Read
107
+ * Two different producers land here and they do not look alike: a rate limit
108
+ * answers `{"error":"rate_limited","retry_after":37}` with a `Retry-After`
109
+ * header, while a daily quota answers a bare string such as
110
+ * `"Daily edit quota reached (5/day)"` with no header at all. Read
114
111
  * {@link retryAfterMs}; it is `undefined` in the second case.
115
112
  */
116
113
  export declare class OmsQuotaError extends OmsApiError {
@@ -160,7 +157,7 @@ export interface NormalizedError {
160
157
  readonly message: string;
161
158
  /** A code the server named itself (`{"error":"rate_limited"}`), if any. */
162
159
  readonly serverCode: string | undefined;
163
- /** Per-field messages, when the body was shaped like ActiveModel errors. */
160
+ /** Per-field messages, when the body was a field-to-messages map. */
164
161
  readonly fieldErrors: Record<string, string[]> | undefined;
165
162
  }
166
163
  /**
@@ -179,7 +176,7 @@ export interface NormalizedError {
179
176
  export declare function normalizeErrorBody(body: unknown, fallback?: string): NormalizedError;
180
177
  /**
181
178
  * Reads a `Retry-After` header. RFC 9110 allows both delay-seconds and an
182
- * HTTP-date; rack-attack sends seconds, but a proxy in front may not.
179
+ * HTTP-date; the API sends seconds, but a proxy in front may not.
183
180
  *
184
181
  * @returns Milliseconds to wait, or `undefined` when the header is absent or junk.
185
182
  */
@@ -8,11 +8,10 @@
8
8
  *
9
9
  * ## Three runtimes, and where they stop agreeing
10
10
  *
11
- * The same transport runs in a browser (the Next.js frontend), in React Native
12
- * (the Expo app) and in Bun (the CLI and the MCP server). They agree on
13
- * `fetch`, `AbortController`, `Headers`, `FormData` and `setTimeout`. They do
14
- * NOT agree on three things, each of which has a named capability check here
15
- * rather than a `try` and a shrug:
11
+ * The same transport runs in a browser, in React Native and in Bun or a Worker
12
+ * isolate. They agree on `fetch`, `AbortController`, `Headers`, `FormData` and
13
+ * `setTimeout`. They do NOT agree on three things, each of which has a named
14
+ * capability check here rather than a `try` and a shrug:
16
15
  *
17
16
  * | capability | browser | React Native | Worker / Bun |
18
17
  * | --------------------------------- | ------- | ------------ | ------------ |
@@ -32,7 +31,7 @@ export declare const DEFAULT_BASE_URL = "https://backend.omelhorsite.pt";
32
31
  *
33
32
  * Answered by probing the globals, never by sniffing a platform name, and
34
33
  * recomputed on every call so a polyfill installed after import is seen. See
35
- * the table in the module note for how the three clients score.
34
+ * the table in the module note for how the three runtimes score.
36
35
  */
37
36
  export interface TransportCapabilities {
38
37
  /** `fetch` hands back a readable body that can be consumed as it arrives. */
@@ -51,9 +50,7 @@ export declare function transportCapabilities(): TransportCapabilities;
51
50
  * UI, and it has a hard "no" on React Native. RN's `fetch` is its
52
51
  * `XMLHttpRequest` with a whatwg-fetch shim over it: the whole body is
53
52
  * accumulated by the native layer and handed over at the end, `response.body`
54
- * is `undefined`, and there is no `ReadableStream` in the runtime at all -
55
- * confirmed by grep over `oms-music/src`, which mentions neither
56
- * `ReadableStream` nor `getReader` anywhere.
53
+ * is `undefined`, and there is no `ReadableStream` in the runtime at all.
57
54
  *
58
55
  * That is not a gap to polyfill. Nothing that runs in JS can make the native
59
56
  * networking layer emit partial bodies, and the shims that claim to (RN's
@@ -79,10 +76,9 @@ export declare function transportCapabilities(): TransportCapabilities;
79
76
  * is to call it and use this only to decide what the UI PROMISES: a typing
80
77
  * indicator that never types is worse than a spinner that admits it is waiting.
81
78
  *
82
- * The consumer this was designed against is the frontend's `BookChatService`,
83
- * which reads an SSE-shaped body (`data: {"delta": "..."}` lines) off
84
- * `POST /books/:id/chat`. Framing the lines is the domain's job; this and
85
- * `streamText` only promise decoded text in order.
79
+ * The typical consumer reads an SSE-shaped body (`data: {"delta": "..."}`
80
+ * lines) off `POST /books/:id/chat`. Framing the lines is the caller's job;
81
+ * this and `streamText` only promise decoded text in order.
86
82
  *
87
83
  * The three probes are all needed. `ReadableStream` existing does not mean
88
84
  * `fetch` produces one (a polyfilled global over RN's fetch is exactly that
@@ -93,7 +89,7 @@ export declare function transportCapabilities(): TransportCapabilities;
93
89
  export declare function supportsResponseStreaming(): boolean;
94
90
  /**
95
91
  * Whether `XMLHttpRequest` is available, which is the only route to byte-level
96
- * UPLOAD progress in any of the three clients.
92
+ * UPLOAD progress on any runtime.
97
93
  *
98
94
  * True in a browser and in React Native (RN's networking is XHR underneath and
99
95
  * `xhr.upload.onprogress` fires there); false in a Worker-class isolate and in
@@ -209,7 +205,7 @@ export type JsonBody = unknown;
209
205
  export type FormFieldValue = string | number | boolean | FileInput | NativeFile | null | undefined;
210
206
  /**
211
207
  * Fields of a multipart form. An array value is appended once per entry with a
212
- * `[]` suffix, which is how Rails reads a list (`clips[]`).
208
+ * `[]` suffix (`clips[]`), which is how the API reads a list.
213
209
  */
214
210
  export type FormFields = Record<string, FormFieldValue | FormFieldValue[]>;
215
211
  /** Options for a request that carries a query string. */
@@ -238,12 +234,11 @@ export interface GetOptions extends RequestOptions {
238
234
  * it never saw, so replaying it is how one `create` call becomes two records.
239
235
  * Safe methods carry no such risk, which is the whole reason the split exists.
240
236
  *
241
- * `429` is the exception, and it is safe on every method because of how this
242
- * particular backend produces one. It comes either from `Rack::Attack`, which
243
- * answers from middleware before the router is reached, or from a
244
- * `too_many_requests!` guard that every controller places BEFORE the write it
245
- * protects. A 429 is therefore proof the request was refused rather than
246
- * performed, and waiting out `Retry-After` and trying again is exactly right.
237
+ * `429` is the exception, and it is safe on every method because this API only
238
+ * ever answers one BEFORE performing the request: a rate-limited request is
239
+ * refused, never carried out. A 429 is therefore proof the request was refused
240
+ * rather than performed, and waiting out `Retry-After` and trying again is
241
+ * exactly right.
247
242
  *
248
243
  * ## Opting a mutator back in
249
244
  *
@@ -262,45 +257,18 @@ export interface GetOptions extends RequestOptions {
262
257
  * notepad) under a fresh random identifier on each attempt.
263
258
  *
264
259
  * This is narrower than the SDK's 0.2.0 behaviour, which retried 5xx on every
265
- * verb. Nothing in `resources/` relied on that: every call site that mentions
266
- * retry is turning it OFF.
267
- *
268
- * ## Why this and not the mobile app's rule
269
- *
270
- * `oms-music/src/api/retryPolicy.ts` states the rule the app arrived at after
271
- * the empty-Home bug: **retry for TIME, never for a RESPONSE**. A transport
272
- * failure (a `fetch` that throws, DNS, the iOS radio still waking) is time and
273
- * is worth asking again; a `4xx` is an answer and asking again does not change
274
- * it; a `5xx` gets one extra go for a deploy or a blinking proxy; `429` gets
275
- * none. Two attempts, `error.status >= 500` as the whole predicate.
276
- *
277
- * That rule is right for what it governs and wrong to copy here, for one
278
- * reason: it sits under react-query, where every retried operation is a QUERY.
279
- * "Repeat any transport error" is safe when the thing being repeated is a read.
280
- * This SDK carries the writes too, and the app's own mutations do NOT go
281
- * through that policy. Applying it verbatim would replay a `POST /songs` whose
282
- * answer was lost on a lift ride and mint the record twice, which is precisely
283
- * the failure the method split above exists to prevent. So the SDK keeps the
284
- * axis the app does not have to think about (the METHOD) and agrees with it on
285
- * the axis it does: a `4xx` is never retried anywhere.
286
- *
287
- * On `429` the two genuinely disagree, and the disagreement is deliberate on
288
- * both sides. The app declines because its ceiling-aware screens would rather
289
- * show "slow down" at once than sit on a hidden `Retry-After` sleep, and
290
- * because react-query re-throws a parked error before the network is even
291
- * consulted. The SDK retries because it also serves the CLI and the MCP server,
292
- * where the right move on a rate limit is to wait the header out and continue,
293
- * and because this backend's `429` provably precedes the write (Rack::Attack in
294
- * middleware, `too_many_requests!` ahead of every guarded action). A mobile
295
- * host that wants the app's behaviour asks for it per call - `retry: false` -
296
- * or, at the client, gets the closest match with `new Oms({ retry: { maxAttempts: 2 } })`:
297
- * one extra attempt, safe methods only, exactly the app's shape.
298
- *
299
- * One consequence to design around rather than discover: a retried `429` obeys
300
- * `Retry-After`, this API sets it from a one-minute window, and `timeoutMs`
301
- * bounds ONE attempt rather than the call. A rate-limited call can therefore
302
- * take minutes of wall clock. Anything with a user watching it should pass a
303
- * `signal` it can abort, or turn retrying off.
260
+ * verb.
261
+ *
262
+ * ## Rate limits and wall clock
263
+ *
264
+ * A retried `429` obeys `Retry-After`, this API sets it from a one-minute
265
+ * window, and `timeoutMs` bounds ONE attempt rather than the call. A
266
+ * rate-limited call can therefore take minutes of wall clock. A UI with a
267
+ * person watching it may rather show "slow down" at once than sit on a hidden
268
+ * sleep: pass a `signal` it can abort, turn retrying off per call with
269
+ * `retry: false`, or cap it at the client with
270
+ * `new Oms({ retry: { maxAttempts: 2 } })` - one extra attempt, safe methods
271
+ * only.
304
272
  */
305
273
  export declare class ApiClient {
306
274
  /** API root with no trailing slash. */
@@ -328,7 +296,7 @@ export declare class ApiClient {
328
296
  delete<T>(path: string, options?: GetOptions): Promise<T>;
329
297
  /**
330
298
  * `POST` a `multipart/form-data` body, parsed as JSON. Use this for every
331
- * endpoint that takes an upload through Rails (the tools). Storage uploads
299
+ * endpoint that takes a multipart upload (the tools). Storage uploads
332
300
  * do NOT go through here - they are presigned and go straight to the object
333
301
  * store; see `resources/storage/upload.ts`.
334
302
  *
@@ -345,7 +313,7 @@ export declare class ApiClient {
345
313
  *
346
314
  * `response.body` is NOT a promise this method can make: React Native has no
347
315
  * `ReadableStream` and leaves it undefined, so any code reaching for
348
- * `.body.getReader()` here works in the browser and the CLI and throws on a
316
+ * `.body.getReader()` here works in a browser and in Bun and throws on a
349
317
  * phone. Test with {@link supportsResponseStreaming} first, or use
350
318
  * {@link ApiClient.streamText}, which contains that fork already.
351
319
  */
@@ -359,7 +327,7 @@ export declare class ApiClient {
359
327
  * This is the primitive a streaming endpoint is built on. It promises exactly
360
328
  * two things - decoded text, in order, with nothing lost - and deliberately
361
329
  * promises nothing about chunk boundaries, because they are not the same on
362
- * the three clients:
330
+ * the three runtimes:
363
331
  *
364
332
  * - browser and Bun: one yield per network chunk, as it lands;
365
333
  * - React Native: ONE yield, containing everything, after the server has
@@ -370,20 +338,18 @@ export declare class ApiClient {
370
338
  * So a caller must not assume a chunk is a frame, a line, or a whole
371
339
  * anything: a `data:` line can arrive split across two chunks, and on RN a
372
340
  * hundred of them arrive as one string. Buffer, then split on your own
373
- * delimiter. `BookChatService` in the frontend is the reference reader (SSE
374
- * `data:` lines carrying `{ delta, done, error }`) and shows the shape of it;
375
- * framing belongs to the domain module, not here.
341
+ * delimiter. The book chat endpoint, for instance, answers SSE `data:` lines
342
+ * carrying `{ delta, done, error }`; framing them belongs to the caller, not
343
+ * here.
376
344
  *
377
- * ## The silence limit exists because of an outage
345
+ * ## The silence limit
378
346
  *
379
- * A stalled sidecar answered `200` and then said nothing for two minutes at a
380
- * time, and `await reader.read()` has no deadline of its own, so the chat
381
- * panel span for as long as the tab stayed open. `timeoutMs` does not help:
382
- * it is disposed once the headers arrive (it has to be, or no stream could
383
- * outlive it). `silenceTimeoutMs` bounds the gap BETWEEN chunks instead and
384
- * raises {@link OmsTimeoutError} when nothing arrives for that long. It has
385
- * to sit well clear of a cold model's first token; 45s is the number the
386
- * frontend settled on and the default here.
347
+ * A server can answer `200` and then say nothing for minutes, and
348
+ * `await reader.read()` has no deadline of its own. `timeoutMs` does not
349
+ * help: it is disposed once the headers arrive (it has to be, or no stream
350
+ * could outlive it). `silenceTimeoutMs` bounds the gap BETWEEN chunks instead
351
+ * and raises {@link OmsTimeoutError} when nothing arrives for that long. It
352
+ * has to sit well clear of a cold model's first token; 45s is the default.
387
353
  *
388
354
  * Pass `0` to disable it, and mean it: an unbounded read is a spinner with no
389
355
  * way out. It does not apply on the buffered path, where the single `text()`
@@ -437,29 +403,25 @@ export declare abstract class Resource {
437
403
  constructor(http: ApiClient);
438
404
  }
439
405
  /**
440
- * The backend's null sentinel: a single backspace character, `U+0008`.
406
+ * The API's null sentinel: a single backspace character, `U+0008`.
441
407
  *
442
408
  * A query string has no way to say `null`. `?parent_id=` is the empty string,
443
409
  * `?parent_id=null` is the four-letter word "null", and omitting the key
444
410
  * entirely says something else again. So the API picked a character no real
445
- * value ever contains and decodes it back to `nil` on arrival:
446
- * `CrudActions#define_option_param_getter` runs
447
- * `value.transform_values! { |v| v == "\b" ? nil : v }` over every filter
448
- * bucket, and `GroupChatsController`, `GroupChatMessagesController` and
449
- * `BookServices::Creator` each repeat the same test on the fields they read by
450
- * hand.
451
- *
452
- * Where that `nil` lands is what makes it worth having. `Searchable.exact_search`
453
- * is `where(params)`, so `exact_search[parent_id]` set to the sentinel becomes
454
- * `WHERE parent_id IS NULL` - the only way to ask for the storage root nodes,
455
- * for a comment with no parent, for anything unassigned.
411
+ * value ever contains and decodes it back to null on arrival, inside every
412
+ * filter bucket and in the handful of body fields that accept it.
413
+ *
414
+ * Where that null lands is what makes it worth having: `exact_search[parent_id]`
415
+ * set to the sentinel becomes `WHERE parent_id IS NULL` - the only way to ask
416
+ * for the storage root nodes, for a comment with no parent, for anything
417
+ * unassigned.
456
418
  *
457
419
  * Exported because it is part of the wire format, not because you should need
458
420
  * it: {@link encodeQuery} writes it for you whenever a query value is `null`.
459
421
  */
460
422
  export declare const NULL_SENTINEL = "\b";
461
423
  /**
462
- * Encodes query parameters the way Rails parses them.
424
+ * Encodes query parameters the way the API parses them.
463
425
  *
464
426
  * - `{ page: 2 }` -> `page=2`
465
427
  * - `{ ids: ["a", "b"] }` -> `ids%5B%5D=a&ids%5B%5D=b`
@@ -476,16 +438,14 @@ export declare const NULL_SENTINEL = "\b";
476
438
  * - `undefined` means "I am not filtering on this column". Dropping the key is
477
439
  * the correct encoding.
478
440
  * - `null` means "filter where this column IS NULL". There is no literal for
479
- * that in a URL, so it goes out as {@link NULL_SENTINEL} and the backend
480
- * turns it back into `nil`.
441
+ * that in a URL, so it goes out as {@link NULL_SENTINEL} and the server
442
+ * turns it back into null.
481
443
  *
482
- * Encoding `null` as "drop the key" - which this function used to do - is the
483
- * dangerous direction. The filter simply vanishes and `Searchable.exact_search`
484
- * returns early (`return self unless params.present?`), so the endpoint answers
485
- * with the UNFILTERED set: a request for the root of somebody's drive comes
486
- * back as their entire tree. That failure has bitten this API before, from the
487
- * other end, and is why `CrudActions#reject_unknown_filter_keys!` now 400s on a
488
- * key it does not recognise rather than quietly widening the query.
444
+ * Encoding `null` as "drop the key" is the dangerous direction. The filter
445
+ * simply vanishes and the endpoint answers with the UNFILTERED set: a request
446
+ * for the root of somebody's drive comes back as their entire tree. For the
447
+ * same reason the API answers `400` to a filter key it does not recognise
448
+ * rather than quietly widening the query.
489
449
  *
490
450
  * The sentinel is written at every depth, even though the server only decodes
491
451
  * it one level inside a filter bucket (`search`, `exact_search`, `modifiers`,
@@ -495,20 +455,17 @@ export declare const NULL_SENTINEL = "\b";
495
455
  *
496
456
  * ## Dates
497
457
  *
498
- * A `Date` reaches `typeof value === "object"` like anything else, and
499
- * `Object.entries(new Date())` is `[]`, so before this branch existed a date
500
- * filter did not merely arrive malformed - the key disappeared and the listing
501
- * came back unfiltered. ISO-8601 is what the server reads:
502
- * `QuerySearcher#date_search` runs `String#to_date_safe` (`Date.parse`) over
503
- * the value and turns an unparseable one into `nil`, which silently drops that
504
- * side of the range. Bodies need no equivalent branch, because `JSON.stringify`
505
- * already calls `Date.prototype.toJSON` and emits the same string.
458
+ * A `Date` is sent as its ISO-8601 string, which is what the server's date
459
+ * filters parse. An unparseable value is silently treated as absent, which
460
+ * drops that side of the range. Bodies need no equivalent branch, because
461
+ * `JSON.stringify` already calls `Date.prototype.toJSON` and emits the same
462
+ * string.
506
463
  *
507
464
  * ## The brackets are percent-encoded, and that is load-bearing
508
465
  *
509
466
  * `search[title]` goes out as `search%5Btitle%5D`, never as raw `[` and `]`.
510
- * Both parse identically in Rails, so this looks like the kind of noise someone
511
- * tidies away on a quiet afternoon. It is not, and the reason is iOS.
467
+ * Both parse identically on the server, so this looks like the kind of noise
468
+ * someone tidies away on a quiet afternoon. It is not, and the reason is iOS.
512
469
  *
513
470
  * `[` and `]` are not legal in a URI query (RFC 3986 reserves them for the host
514
471
  * component). Every browser tolerates them; Apple's URL stack does not. Give
@@ -534,16 +491,16 @@ export declare function encodeQuery(params: QueryParams): string;
534
491
  * asked for again.
535
492
  *
536
493
  * `PUT` and `DELETE` are idempotent by RFC 9110 and are still absent, because
537
- * idempotent is not the same as harmless to replay HERE. Rails' `destroy` is
538
- * behind a `find_by` that answers `404` the second time, so a `DELETE` retried
539
- * after a torn connection reports "not found" for a row it deleted perfectly
540
- * well - a success turned into an error the caller then acts on.
494
+ * idempotent is not the same as harmless to replay HERE. A `DELETE` answers
495
+ * `404` the second time, so one retried after a torn connection reports "not
496
+ * found" for a row it deleted perfectly well - a success turned into an error
497
+ * the caller then acts on.
541
498
  */
542
499
  export declare const SAFE_METHODS: ReadonlySet<string>;
543
500
  /** True when {@link SAFE_METHODS} contains `method`, case-insensitively. */
544
501
  export declare function isSafeMethod(method: string): boolean;
545
502
  /**
546
- * Builds the `modifiers[page]` string the backend expects (`"2:100"`).
503
+ * Builds the `modifiers[page]` string the API expects (`"2:100"`).
547
504
  *
548
505
  * Both halves go through `resolvePageSize` / `resolvePageNumber`, which is the
549
506
  * same pair `createPage` uses. That is the point: the size on the wire and the
@@ -552,9 +509,8 @@ export declare function isSafeMethod(method: string): boolean;
552
509
  * told `hasMore: false` while 700 rows sit behind it.
553
510
  *
554
511
  * @throws {TypeError} for a page size that is not a finite number of at least
555
- * 1. `"1:NaN"` on the wire reads as size `0` in
556
- * `QueryModifier#apply_pagination`, which skips `limit`/`offset` altogether
557
- * and returns the entire table.
512
+ * 1. `"1:NaN"` on the wire reads as size `0` on the server, which then skips
513
+ * pagination altogether and returns the entire table.
558
514
  */
559
515
  export declare function pageModifier(page?: number, pageSize?: number): string;
560
516
  /**
@@ -563,8 +519,8 @@ export declare function pageModifier(page?: number, pageSize?: number): string;
563
519
  * `null` and `undefined` fields are OMITTED, which is the opposite of what
564
520
  * {@link encodeQuery} does with `null` and is right for the same reason it is
565
521
  * right there: what the receiver reads from an absent field. A multipart body
566
- * only ever feeds a create here, and an absent field means `params[:x]` is
567
- * `nil` - already the value a sentinel would decode to, so writing one would
522
+ * only ever feeds a create here, and an absent field is read as null on the
523
+ * server - already the value a sentinel would decode to, so writing one would
568
524
  * add a step that changes nothing. In a query the absent key means "no filter",
569
525
  * which is a different answer entirely.
570
526
  *
@@ -609,7 +565,7 @@ export declare function backoffDelay(attempt: number, retry: ResolvedRetry): num
609
565
  export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
610
566
  /**
611
567
  * How long {@link ApiClient.streamText} will wait for the NEXT chunk before it
612
- * gives up: 45 seconds, the number the frontend's `BookChatService` arrived at.
568
+ * gives up: 45 seconds.
613
569
  *
614
570
  * It is a silence limit, not a total: a stream that keeps producing runs as
615
571
  * long as it likes. The value has to clear a cold model's first token (the
@@ -8,8 +8,7 @@
8
8
  * const link = await oms.shortLinks.create({ url: "https://example.com" });
9
9
  * ```
10
10
  *
11
- * This package is the product; the CLI and the MCP server are two clients of
12
- * it. Three rules make that work, and they are not negotiable:
11
+ * Three rules shape the package, and they are not negotiable:
13
12
  *
14
13
  * 1. It runs in a Cloudflare-Worker-class isolate. No `node:*`, no `process`,
15
14
  * no filesystem, no stdout. `fetch` is injectable through the constructor.
@@ -29,6 +28,7 @@ export * from "./auth/index";
29
28
  export * from "./client";
30
29
  export * from "./errors";
31
30
  export * from "./http";
31
+ export * from "./listing";
32
32
  export * from "./local/index";
33
33
  export * from "./resources/index";
34
34
  export * from "./types";
@@ -0,0 +1,17 @@
1
+ /** Attachment helpers shared by the message families. Not part of the public surface. */
2
+ import type { ApiClient } from "../http";
3
+ import type { FileInput, NativeFile, RequestOptions } from "../types";
4
+ /**
5
+ * Byte length of an attachment, when it is knowable without reading anything.
6
+ * `undefined` for a stream with no declared `size` and for a picker that
7
+ * reported none.
8
+ */
9
+ export declare function attachmentSize(file: FileInput | NativeFile): number | undefined;
10
+ /**
11
+ * Follows an attachment redirect. In a browser in cookie mode the redirect
12
+ * to object storage fails CORS and surfaces as a bare network error; that
13
+ * case is turned into an error that says what to do instead.
14
+ */
15
+ export declare function followAttachment(http: ApiClient, path: string, what: string, options: RequestOptions): Promise<Blob>;
16
+ /** The bearer token the transport would send, or `null` when there is none (cookie mode). */
17
+ export declare function tokenOf(http: ApiClient): Promise<string | null>;