@omelhorsite/sdk 0.2.0 → 0.4.0

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 (96) hide show
  1. package/README.md +112 -130
  2. package/dist/index.js +4800 -610
  3. package/dist/types/client.d.ts +60 -3
  4. package/dist/types/http.d.ts +444 -19
  5. package/dist/types/index.d.ts +5 -1
  6. package/dist/types/internal/attachments.d.ts +17 -0
  7. package/dist/types/internal/helpers.d.ts +9 -0
  8. package/dist/types/listing.d.ts +90 -0
  9. package/dist/types/resources/account.d.ts +71 -5
  10. package/dist/types/resources/admin/authorizedApplications.d.ts +133 -0
  11. package/dist/types/resources/admin/chests.d.ts +51 -0
  12. package/dist/types/resources/admin/eventAlerts.d.ts +60 -0
  13. package/dist/types/resources/admin/identities.d.ts +99 -0
  14. package/dist/types/resources/admin/index.d.ts +77 -0
  15. package/dist/types/resources/admin/jobs.d.ts +97 -0
  16. package/dist/types/resources/admin/myOauthApplications.d.ts +282 -0
  17. package/dist/types/resources/admin/notepads.d.ts +49 -0
  18. package/dist/types/resources/admin/oauthApplications.d.ts +309 -0
  19. package/dist/types/resources/admin/quotas.d.ts +114 -0
  20. package/dist/types/resources/admin/shortLinks.d.ts +201 -0
  21. package/dist/types/resources/admin/types.d.ts +222 -0
  22. package/dist/types/resources/admin/vocalSeparations.d.ts +98 -0
  23. package/dist/types/resources/admin.d.ts +1841 -0
  24. package/dist/types/resources/auth/index.d.ts +39 -0
  25. package/dist/types/resources/auth/passkeys.d.ts +652 -0
  26. package/dist/types/resources/auth/sessions.d.ts +844 -0
  27. package/dist/types/resources/chests.d.ts +54 -3
  28. package/dist/types/resources/content/analysis.d.ts +62 -0
  29. package/dist/types/resources/content/blogs.d.ts +471 -0
  30. package/dist/types/resources/content/feedbacks.d.ts +263 -0
  31. package/dist/types/resources/content/index.d.ts +52 -0
  32. package/dist/types/resources/content/intel/articles.d.ts +230 -0
  33. package/dist/types/resources/content/intel/config.d.ts +135 -0
  34. package/dist/types/resources/content/intel/index.d.ts +53 -0
  35. package/dist/types/resources/content/intel/items.d.ts +91 -0
  36. package/dist/types/resources/content/intel/reports.d.ts +108 -0
  37. package/dist/types/resources/content/intel/scripts.d.ts +152 -0
  38. package/dist/types/resources/content/intel/sources.d.ts +236 -0
  39. package/dist/types/resources/content/intel/stats.d.ts +105 -0
  40. package/dist/types/resources/content/intel/types.d.ts +86 -0
  41. package/dist/types/resources/content/jokes.d.ts +93 -0
  42. package/dist/types/resources/content/notifications.d.ts +147 -0
  43. package/dist/types/resources/content/serviceUsages.d.ts +73 -0
  44. package/dist/types/resources/content/servicesStatus.d.ts +194 -0
  45. package/dist/types/resources/content/siteConfig.d.ts +43 -0
  46. package/dist/types/resources/content/spaceInvaders.d.ts +154 -0
  47. package/dist/types/resources/content.d.ts +2775 -0
  48. package/dist/types/resources/dynamicQrs.d.ts +39 -3
  49. package/dist/types/resources/forms.d.ts +176 -35
  50. package/dist/types/resources/index.d.ts +19 -8
  51. package/dist/types/resources/ipLookup.d.ts +20 -4
  52. package/dist/types/resources/jobs.d.ts +69 -25
  53. package/dist/types/resources/library/annotations.d.ts +162 -0
  54. package/dist/types/resources/library/books.d.ts +534 -0
  55. package/dist/types/resources/library/chat.d.ts +259 -0
  56. package/dist/types/resources/library/index.d.ts +39 -0
  57. package/dist/types/resources/library/shelves.d.ts +237 -0
  58. package/dist/types/resources/library/types.d.ts +120 -0
  59. package/dist/types/resources/library.d.ts +1433 -0
  60. package/dist/types/resources/linkTrees.d.ts +142 -30
  61. package/dist/types/resources/media.d.ts +351 -0
  62. package/dist/types/resources/movies/addons.d.ts +388 -0
  63. package/dist/types/resources/movies/collections.d.ts +352 -0
  64. package/dist/types/resources/movies/index.d.ts +29 -0
  65. package/dist/types/resources/movies/types.d.ts +50 -0
  66. package/dist/types/resources/movies/watchProgress.d.ts +277 -0
  67. package/dist/types/resources/movies.d.ts +1160 -0
  68. package/dist/types/resources/music/artists.d.ts +1068 -0
  69. package/dist/types/resources/music/imports.d.ts +941 -0
  70. package/dist/types/resources/music/index.d.ts +61 -0
  71. package/dist/types/resources/music/playlists.d.ts +1027 -0
  72. package/dist/types/resources/music/social.d.ts +1132 -0
  73. package/dist/types/resources/music/songs.d.ts +1167 -0
  74. package/dist/types/resources/notepads.d.ts +4 -1
  75. package/dist/types/resources/quotas.d.ts +7 -1
  76. package/dist/types/resources/realtime.d.ts +855 -0
  77. package/dist/types/resources/shortLinks.d.ts +49 -5
  78. package/dist/types/resources/social/groupChats.d.ts +458 -0
  79. package/dist/types/resources/social/index.d.ts +19 -0
  80. package/dist/types/resources/social/messages.d.ts +424 -0
  81. package/dist/types/resources/social/relationships.d.ts +322 -0
  82. package/dist/types/resources/social/types.d.ts +37 -0
  83. package/dist/types/resources/social.d.ts +1348 -0
  84. package/dist/types/resources/storage/upload.d.ts +158 -11
  85. package/dist/types/resources/storage.d.ts +111 -27
  86. package/dist/types/resources/tickets.d.ts +101 -13
  87. package/dist/types/resources/tools/backgroundRemoval.d.ts +18 -3
  88. package/dist/types/resources/tools/captions.d.ts +448 -21
  89. package/dist/types/resources/tools/downloader.d.ts +21 -0
  90. package/dist/types/resources/tools/index.d.ts +57 -15
  91. package/dist/types/resources/tools/jumpstyle.d.ts +50 -17
  92. package/dist/types/resources/tools/transcription.d.ts +35 -13
  93. package/dist/types/resources/tools/upscale.d.ts +23 -3
  94. package/dist/types/resources/tools/vocalSeparation.d.ts +30 -13
  95. package/dist/types/types.d.ts +249 -17
  96. package/package.json +2 -1
@@ -0,0 +1,259 @@
1
+ /** The `library.chat` namespace: the streaming study assistant, its payloads, limits and helpers. */
2
+ import { OmsError } from "../../errors";
3
+ import { Resource } from "../../http";
4
+ import type { RequestOptions } from "../../types";
5
+ import type { BookId } from "./types";
6
+ /**
7
+ * Requests per minute allowed against `POST /books/:id/chat`.
8
+ *
9
+ * Keyed on the `Authorization` header when there is one, and on the client IP
10
+ * when there is not - so a cookie-authenticated web client shares one budget
11
+ * with every other visitor behind the same address. Over it: `429`.
12
+ */
13
+ export declare const BOOK_CHAT_RATE_LIMIT_PER_MINUTE = 15;
14
+ /**
15
+ * Largest chat request body the server agrees to parse: 512 KiB. Over it,
16
+ * `413`.
17
+ *
18
+ * This is a bound on what is PARSED, not on what reaches the model - the model
19
+ * caps below are applied afterwards.
20
+ */
21
+ export declare const BOOK_CHAT_MAX_BODY_BYTES = 524288;
22
+ /**
23
+ * Characters of {@link BookChatContext.text} the assistant will read.
24
+ *
25
+ * Silently truncated server-side, not refused. Sending a whole chapter costs
26
+ * the bytes and buys nothing past this point.
27
+ */
28
+ export declare const BOOK_CHAT_MAX_CONTEXT_CHARS = 12000;
29
+ /** Turns of history the assistant reads, newest kept. */
30
+ export declare const BOOK_CHAT_MAX_HISTORY_MESSAGES = 12;
31
+ /** Characters kept per history turn. */
32
+ export declare const BOOK_CHAT_MAX_MESSAGE_CHARS = 4000;
33
+ /** Characters kept across the whole history. */
34
+ export declare const BOOK_CHAT_MAX_HISTORY_CHARS = 8000;
35
+ /** One turn of the conversation, as the client remembers it. */
36
+ export interface BookChatTurn {
37
+ /**
38
+ * Only `"user"` and `"assistant"` reach the model: everything else is
39
+ * dropped, precisely so a client cannot inject a `"system"` turn into our
40
+ * own framing.
41
+ */
42
+ readonly role: "user" | "assistant";
43
+ readonly content: string;
44
+ }
45
+ /**
46
+ * The passage the question is about. The CLIENT decides what that is - the
47
+ * current selection, the chapter on screen, or whatever its search index
48
+ * retrieved - and the server only frames it.
49
+ */
50
+ export interface BookChatContext {
51
+ readonly kind: "selection" | "chapter" | "passages";
52
+ /** Chapter or section title, for the framing line. Truncated to 200 chars. */
53
+ readonly title?: string;
54
+ /** Truncated to {@link BOOK_CHAT_MAX_CONTEXT_CHARS} server-side, in silence. */
55
+ readonly text: string;
56
+ }
57
+ /** Arguments for {@link BookChatNamespace.stream} and {@link BookChatNamespace.ask}. */
58
+ export interface BookChatInput {
59
+ /**
60
+ * The whole conversation, oldest first, INCLUDING the question being asked.
61
+ * There is no server-side session: what you do not send did not happen.
62
+ */
63
+ readonly messages: readonly BookChatTurn[];
64
+ /** The passage in front of the reader. Omit it and the assistant is told so. */
65
+ readonly context?: BookChatContext | null;
66
+ }
67
+ /**
68
+ * One decoded SSE frame from the chat stream.
69
+ *
70
+ * Exactly one field is set per frame. `delta` carries text to append, `done`
71
+ * marks a complete answer, and `error` is a failure that happened AFTER the
72
+ * `200` was written.
73
+ */
74
+ export interface BookChatEvent {
75
+ readonly delta?: string;
76
+ readonly done?: boolean;
77
+ readonly error?: string;
78
+ }
79
+ /** Why a chat stream failed. See {@link BookChatError}. */
80
+ export type BookChatFailureReason =
81
+ /** The server wrote an `error` frame: generation failed after the 200. */
82
+ "assistant_unavailable"
83
+ /** The stream ended without a `done` frame, so the answer is cut short. */
84
+ | "truncated";
85
+ /**
86
+ * The chat stream failed AFTER the response status was written.
87
+ *
88
+ * This is not an {@link OmsApiError}, and that is the whole point of it
89
+ * existing. The server writes `200` and the SSE headers before the
90
+ * model has produced a single token, so anything that goes wrong from then on -
91
+ * the inference sidecar erroring, the proxy dropping a stream that went quiet -
92
+ * arrives INSIDE a successful response. Code that decides success by status
93
+ * alone reports a truncated answer, or an empty one, as a complete reply.
94
+ *
95
+ * `code` is `"server_error"`, so `error.retryable` is `true`: asking again is
96
+ * the right move for both reasons, subject to
97
+ * {@link BOOK_CHAT_RATE_LIMIT_PER_MINUTE}.
98
+ */
99
+ export declare class BookChatError extends OmsError {
100
+ static readonly errorName: string;
101
+ /** Which of the two post-200 failures this is. */
102
+ readonly reason: BookChatFailureReason;
103
+ /** Text already yielded before the failure. Usually worth showing, marked as incomplete. */
104
+ readonly partial: string;
105
+ constructor(message: string, reason: BookChatFailureReason, partial: string, url?: string);
106
+ toJSON(): Record<string, unknown>;
107
+ }
108
+ /**
109
+ * True when this error is the assistant refusing because every generation slot
110
+ * is taken.
111
+ *
112
+ * The server fails closed rather than queueing - a caller parked waiting for
113
+ * a slot would be holding the very server thread the slots exist to protect -
114
+ * so the answer is `503` with `"The assistant is busy, try again in a moment"`.
115
+ * It is transient and it is NOT a rate limit: the caller did nothing wrong and
116
+ * `Retry-After` is not sent, so back off on your own and try again.
117
+ */
118
+ export declare function isBookChatBusy(error: unknown): boolean;
119
+ /**
120
+ * Whether {@link BookChatNamespace.stream} will deliver this answer in pieces
121
+ * on THIS runtime.
122
+ *
123
+ * `true` in a browser and in Bun, `false` in React Native, whose `fetch` hands
124
+ * the whole body over at the end and has no `ReadableStream` at all. Both
125
+ * paths reach the same endpoint and produce the same final text; only the
126
+ * arrival differs, and this is the question to ask before a UI promises a
127
+ * typing indicator. A typing indicator that never types is worse than a
128
+ * spinner that admits it is waiting.
129
+ *
130
+ * Read it at the point of use, never cached at module load: a host may install
131
+ * a polyfill after the SDK is imported.
132
+ */
133
+ export declare function bookChatIsIncremental(): boolean;
134
+ /** Options for {@link BookChatNamespace.stream} and {@link BookChatNamespace.ask}. */
135
+ export interface BookChatStreamOptions extends RequestOptions {
136
+ /**
137
+ * How long to wait for the NEXT piece of the answer before giving up, in
138
+ * milliseconds. Defaults to the transport's 45 seconds; `0` disables it.
139
+ *
140
+ * A silence limit, not a total: an answer that keeps producing runs as long
141
+ * as it likes. It exists because a stalled sidecar once answered `200` and
142
+ * then said nothing for two minutes at a time, and `await reader.read()` has
143
+ * no deadline of its own, so the chat panel span for as long as the tab
144
+ * stayed open. `timeoutMs` cannot cover this: it is disposed the moment the
145
+ * headers arrive, or no stream could outlive it.
146
+ *
147
+ * It has to sit well clear of a cold model's first token - the 8B model has
148
+ * to be paged in before it can answer - which is why 45 seconds and not five.
149
+ *
150
+ * IGNORED on the buffered path (React Native), where there is one read and
151
+ * the caller's `signal` is what bounds it.
152
+ */
153
+ readonly silenceTimeoutMs?: number;
154
+ }
155
+ /**
156
+ * The `library.chat` namespace: the reader's study assistant.
157
+ *
158
+ * `POST /books/:id/chat` is the only STREAMING endpoint in the whole API.
159
+ *
160
+ * ## What it costs, and why it refuses rather than queues
161
+ *
162
+ * One request parks a server thread for the whole generation, tens of seconds
163
+ * on the local model. The server caps how many generations run at once and
164
+ * FAILS CLOSED when they are all taken - a caller parked waiting for a slot
165
+ * would be holding the very thread the slots exist to protect. That refusal is
166
+ * a `503`; see {@link isBookChatBusy}. On top of it, one client may only START
167
+ * {@link BOOK_CHAT_RATE_LIMIT_PER_MINUTE} generations a minute, so a loop
168
+ * cannot take whatever slot frees up next.
169
+ *
170
+ * ## The client owns the conversation
171
+ *
172
+ * There is no server-side session and no history storage. Every request carries
173
+ * the whole conversation and the passage the question is about; what you do not
174
+ * send did not happen. The server owns the framing and the caps, and it drops
175
+ * any role other than `user` and `assistant` so a client cannot inject a
176
+ * `system` turn into our own prompt.
177
+ */
178
+ export declare class BookChatNamespace extends Resource {
179
+ /**
180
+ * `POST /books/:id/chat` - asks the assistant and yields the answer as it
181
+ * arrives.
182
+ *
183
+ * Yields TEXT DELTAS, already unwrapped from their SSE frames: concatenating
184
+ * everything this yields is the complete answer. Framing is handled here
185
+ * because a chunk is not a frame - a `data:` line can arrive split across two
186
+ * network chunks - and every client that tried to parse the raw stream itself
187
+ * got that wrong at least once.
188
+ *
189
+ * ## The two runtimes do not deliver the same way, and the difference is real
190
+ *
191
+ * - **browser and Bun**: one yield per delta, as the model produces it. A
192
+ * typing UI works, and abandoning the loop (a `break`, or an early
193
+ * `return`) cancels the reader, closes the connection, and the server
194
+ * notices the disconnect and RELEASES ITS GENERATION SLOT. Stopping early
195
+ * genuinely stops the work.
196
+ * - **React Native**: ONE yield, containing the entire answer, after the
197
+ * server has finished generating. RN's `fetch` is `XMLHttpRequest` under a
198
+ * shim, the whole body is accumulated natively and handed over at the end,
199
+ * and `response.body` does not exist. This is not a gap to polyfill.
200
+ * Consequences to design around: a typing indicator will sit still and then
201
+ * snap to the finished answer, so ask {@link bookChatIsIncremental} and
202
+ * show a spinner instead; and `break`ing out of the loop cancels NOTHING,
203
+ * because the request already completed - only an `AbortSignal` can stop
204
+ * the work, and only by tearing down the whole request.
205
+ *
206
+ * The final text is identical on both. Nothing is lost, nothing is
207
+ * reordered, and no chunk boundary is invented to make one look like the
208
+ * other.
209
+ *
210
+ * ## A `200` does not mean the answer worked
211
+ *
212
+ * The status and the SSE headers are written before the model has produced a
213
+ * token, so every later failure is in-band. Two of them, both raised as
214
+ * {@link BookChatError} with the text already yielded attached:
215
+ *
216
+ * - an `error` frame (`reason: "assistant_unavailable"`) - the inference
217
+ * client raised, and the upstream message is deliberately withheld because
218
+ * it can carry the sidecar's internals;
219
+ * - the stream ending without a `done` frame (`reason: "truncated"`) - the
220
+ * proxy dropped a stream that went quiet, or the connection was cut
221
+ * mid-answer. Returning quietly here would report a half-answer as a
222
+ * complete one, which is the whole reason the `done` frame exists.
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * let answer = "";
227
+ * for await (const delta of oms.library.chat.stream(bookId, { messages, context })) {
228
+ * answer += delta;
229
+ * render(answer); // one paint per delta, or one paint on RN
230
+ * }
231
+ * ```
232
+ *
233
+ * @throws {OmsAuthError} 401 `"Session required"` when anonymous.
234
+ * @throws {OmsApiError} 404 `"Book not found"` when the book is not viewable;
235
+ * 413 `"Request too big"` over {@link BOOK_CHAT_MAX_BODY_BYTES}; 429 over
236
+ * {@link BOOK_CHAT_RATE_LIMIT_PER_MINUTE}; 503 when every generation slot
237
+ * is taken ({@link isBookChatBusy}).
238
+ * @throws {BookChatError} for either post-`200` failure. Check `reason` and
239
+ * `partial`.
240
+ * @throws {OmsTimeoutError} when the stream goes quiet for
241
+ * {@link BookChatStreamOptions.silenceTimeoutMs}. Streaming path only.
242
+ */
243
+ stream(bookId: BookId, input: BookChatInput, options?: BookChatStreamOptions): AsyncGenerator<string, void, undefined>;
244
+ /**
245
+ * The same call as {@link stream}, waited out and handed back as one string.
246
+ *
247
+ * For anywhere a partial answer has nowhere to go. It is exactly `stream`
248
+ * drained into a buffer, so it raises the same
249
+ * errors for the same reasons - including {@link BookChatError} on a
250
+ * truncated stream, which is the case a naive `await response.text()` would
251
+ * hand back as a successful half-answer.
252
+ *
253
+ * It does NOT make the request cheaper or faster anywhere. On a browser and
254
+ * on Bun the answer still streams in and is merely accumulated here; on React
255
+ * Native it was going to arrive in one piece regardless. Choose it because
256
+ * the CALLER has no use for pieces, never as a way to avoid streaming.
257
+ */
258
+ ask(bookId: BookId, input: BookChatInput, options?: BookChatStreamOptions): Promise<string>;
259
+ }
@@ -0,0 +1,39 @@
1
+ /** The `library` domain: books, shelves, annotations and the study assistant, reachable as `oms.library`. */
2
+ import { Resource } from "../../http";
3
+ import type { RequestOptions } from "../../types";
4
+ import { LibraryAnnotationsNamespace } from "./annotations";
5
+ import { LibraryBooksNamespace } from "./books";
6
+ import { BookChatNamespace } from "./chat";
7
+ import { LibraryShelvesNamespace } from "./shelves";
8
+ import type { PublicLibrary } from "./types";
9
+ export * from "./types";
10
+ export * from "./books";
11
+ export * from "./shelves";
12
+ export * from "./annotations";
13
+ export * from "./chat";
14
+ /**
15
+ * The `library` namespace, reachable as `oms.library`.
16
+ *
17
+ * A thin holder for the four sub-namespaces. Each of them is exported on its
18
+ * own too, so a host that would rather write `oms.books` can construct
19
+ * {@link LibraryBooksNamespace} directly with the same `ApiClient`.
20
+ */
21
+ export declare class LibraryNamespace extends Resource {
22
+ /** Books: upload, browse, share, read the bytes. */
23
+ readonly books: LibraryBooksNamespace;
24
+ /** Shelves: named, ordered, shareable groups of your own books. */
25
+ readonly shelves: LibraryShelvesNamespace;
26
+ /** Highlights, notes, bookmarks and reading progress. Always private. */
27
+ readonly annotations: LibraryAnnotationsNamespace;
28
+ /** The streaming study assistant. */
29
+ readonly chat: BookChatNamespace;
30
+ /**
31
+ * `GET /books/libraries` - the public-library directory.
32
+ *
33
+ * Delegates to {@link LibraryBooksNamespace.libraries}. It lives on the books
34
+ * route because that is where Rails mounted it, and it is surfaced here
35
+ * because it is a directory of READERS, which is what a caller looking for it
36
+ * expects `oms.library` to have.
37
+ */
38
+ publicLibraries(options?: RequestOptions): Promise<PublicLibrary[]>;
39
+ }
@@ -0,0 +1,237 @@
1
+ /** The `library.shelves` namespace, with the record, inputs and limits only it uses. */
2
+ import { Resource } from "../../http";
3
+ import type { ListParams } from "../../listing";
4
+ import type { Id, Paginated, RequestOptions, Timestamp } from "../../types";
5
+ import type { Book, BookId, BookVisibility } from "./types";
6
+ /** Primary key of a shelf. A number, like every id in this file. */
7
+ export type BookShelfId = number;
8
+ /** Longest shelf name accepted. */
9
+ export declare const BOOK_SHELF_NAME_MAX_LENGTH = 120;
10
+ /** Longest shelf description accepted. */
11
+ export declare const BOOK_SHELF_DESCRIPTION_MAX_LENGTH = 1000;
12
+ /**
13
+ * Most shelves one user may own.
14
+ *
15
+ * Checked on creation only, so the 101st shelf fails with a `400` carrying
16
+ * `"You have reached the maximum of 100 shelves"`; existing shelves keep saving.
17
+ */
18
+ export declare const BOOK_SHELVES_MAX_PER_USER = 100;
19
+ /**
20
+ * A shelf: a named, ordered group of one reader's own books.
21
+ *
22
+ * `books` is present ONLY on the `:extended` view, which means
23
+ * {@link LibraryShelvesNamespace.get} and the three arrange calls carry it and
24
+ * {@link LibraryShelvesNamespace.list} does not. That is not an oversight to
25
+ * work around by listing and hoping - it is why `get` exists.
26
+ */
27
+ export interface BookShelf {
28
+ readonly id: BookShelfId;
29
+ readonly created_at: Timestamp;
30
+ readonly updated_at: Timestamp;
31
+ readonly name: string;
32
+ readonly description: string | null;
33
+ readonly visibility: BookVisibility;
34
+ /** The owner's ordering of their own shelves. Ascending. */
35
+ readonly position: number;
36
+ /** Owner. A string id. */
37
+ readonly user_id: Id;
38
+ readonly owner_handle: string | null;
39
+ readonly owner_name: string | null;
40
+ /** Whether the ASKING user may edit the shelf. */
41
+ readonly editable: boolean;
42
+ /**
43
+ * How many of this shelf's books the ASKING user may actually open, which is
44
+ * not how many are on it: a shelf can hold a book its owner later made
45
+ * private again, and counting those would promise a stranger rows they
46
+ * cannot see. So `book_count` can be smaller than `books.length` would be
47
+ * for the owner, and it costs one extra query PER SHELF in a listing.
48
+ */
49
+ readonly book_count: number;
50
+ /**
51
+ * The shelf's books, in `position` order, filtered to what the asking user
52
+ * may see. Present on the `:extended` view only - see the note above.
53
+ */
54
+ readonly books?: Book[];
55
+ }
56
+ /** Filter columns of `GET /book_shelves`, on top of {@link BASE_FILTER_COLUMNS}. */
57
+ export declare const BOOK_SHELF_FILTER_COLUMNS: readonly ["user_id", "visibility"];
58
+ /** Arguments for {@link LibraryShelvesNamespace.list}. */
59
+ export interface ListBookShelvesParams extends ListParams<(typeof BOOK_SHELF_FILTER_COLUMNS)[number]> {
60
+ /** Exact owner id. The only way to ask for one person's shelves. */
61
+ readonly userId?: Id;
62
+ /** Exact visibility. */
63
+ readonly visibility?: BookVisibility;
64
+ /** Exact ids. An array becomes `IN (...)`. */
65
+ readonly ids?: readonly BookShelfId[];
66
+ /** `"column:asc"` / `"column:desc"`. Defaults to the endpoint's own ordering. */
67
+ readonly order?: string;
68
+ }
69
+ /** Arguments for {@link LibraryShelvesNamespace.create}. */
70
+ export interface CreateBookShelfInput {
71
+ readonly name: string;
72
+ readonly description?: string | null;
73
+ /** Defaults to `"private"` server-side. */
74
+ readonly visibility?: BookVisibility;
75
+ }
76
+ /**
77
+ * Arguments for {@link LibraryShelvesNamespace.update}.
78
+ *
79
+ * `position` is writable here and nowhere else: it orders the owner's SHELVES
80
+ * against each other, and has nothing to do with the order of books ON a shelf
81
+ * (that is {@link LibraryShelvesNamespace.reorder}).
82
+ */
83
+ export interface UpdateBookShelfInput {
84
+ readonly name?: string;
85
+ readonly description?: string | null;
86
+ readonly visibility?: BookVisibility;
87
+ readonly position?: number;
88
+ }
89
+ /** The `library.shelves` namespace, reachable as `oms.library.shelves`. */
90
+ export declare class LibraryShelvesNamespace extends Resource {
91
+ /**
92
+ * `GET /book_shelves` - browse shelves.
93
+ *
94
+ * Same two-axis visibility rule as books: every `public` shelf plus, for a
95
+ * signed-in caller, their own whatever their visibility. An `unlisted`
96
+ * shelf opens by its link and appears in no listing.
97
+ *
98
+ * Ordered by `(position, created_at)` unless you override it, so the owner's
99
+ * own arrangement is the default and a paged walk is stable without asking.
100
+ *
101
+ * **The rows here carry no `books`.** `index` renders the default view and
102
+ * only the `:extended` one inlines the association. Every other method in
103
+ * this namespace returns the extended shape; this one does not. Do not build
104
+ * a shelf screen by listing and reading `books` - it is `undefined`.
105
+ *
106
+ * Note also that `book_count` costs one query per shelf (per row, because
107
+ * it depends on who is asking). A 100-shelf page is 100 extra counts. Page
108
+ * it.
109
+ *
110
+ * @throws {OmsApiError} 400 naming the offending key when a filter is not
111
+ * one of `user_id`, `visibility`, `id`, `created_at`, `updated_at`. There
112
+ * are no `extra_options` on this endpoint at all, so ANY key in that
113
+ * bucket is a 400.
114
+ */
115
+ list(params?: ListBookShelvesParams, options?: RequestOptions): Promise<Paginated<BookShelf>>;
116
+ /**
117
+ * `GET /book_shelves/:id` - one shelf, WITH its books.
118
+ *
119
+ * The `:extended` view, so {@link BookShelf.books} is populated here and
120
+ * nowhere in {@link list}. The books are filtered to what the ASKING user may
121
+ * open, in shelf order: a shelf that holds a book its owner later made
122
+ * private again shows that book to the owner and silently omits it for
123
+ * everyone else, which is also why `book_count` and `books.length` can
124
+ * disagree between two viewers of the same shelf.
125
+ *
126
+ * Works anonymously, through the wider visibility scope, which is how an
127
+ * `unlisted` shelf link resolves.
128
+ *
129
+ * @throws {OmsApiError} 404 for an unknown id and for someone else's private
130
+ * shelf, indistinguishably.
131
+ */
132
+ get(id: BookShelfId, options?: RequestOptions): Promise<BookShelf>;
133
+ /**
134
+ * `POST /book_shelves` - creates an empty shelf owned by the caller.
135
+ *
136
+ * The owner is taken from the session and cannot be sent. Visibility
137
+ * defaults to `private`.
138
+ *
139
+ * Not retried by default, like every create: a replay after a lost answer
140
+ * makes a second identical shelf, and shelves are capped per user.
141
+ *
142
+ * @throws {OmsAuthError} 401 when anonymous.
143
+ * @throws {OmsApiError} 400 `"You have reached the maximum of 100 shelves"`
144
+ * at {@link BOOK_SHELVES_MAX_PER_USER}, or the model's validation messages
145
+ * for a missing or over-long name.
146
+ */
147
+ create(input: CreateBookShelfInput, options?: RequestOptions): Promise<BookShelf>;
148
+ /**
149
+ * `PATCH /book_shelves/:id` - renames, re-describes, re-shares, or moves the
150
+ * shelf among the owner's other shelves.
151
+ *
152
+ * {@link UpdateBookShelfInput.position} orders SHELVES, not the books on one.
153
+ * Nothing renumbers the others, so two shelves can share a position and the
154
+ * `(position, created_at)` sort then decides between them by age.
155
+ *
156
+ * @throws {OmsAuthError} 401 when the shelf is not yours.
157
+ * @throws {OmsApiError} 404 for an unknown id or someone else's private
158
+ * shelf; 400 with the model's validation messages.
159
+ */
160
+ update(id: BookShelfId, input: UpdateBookShelfInput, options?: RequestOptions): Promise<BookShelf>;
161
+ /**
162
+ * `DELETE /book_shelves/:id` - removes the shelf.
163
+ *
164
+ * The BOOKS SURVIVE. Only the `book_shelf_items` join rows are destroyed, so
165
+ * this un-files the books rather than deleting them - the opposite of
166
+ * {@link LibraryBooksNamespace.delete}, and worth saying in the confirmation
167
+ * dialog.
168
+ *
169
+ * @throws {OmsAuthError} 401 when the shelf is not yours.
170
+ * @throws {OmsApiError} 404 the second time round.
171
+ */
172
+ delete(id: BookShelfId, options?: RequestOptions): Promise<void>;
173
+ /**
174
+ * `POST /book_shelves/:id/add_book` - puts one of YOUR books on the shelf.
175
+ *
176
+ * IDEMPOTENT: adding a book that is already there is a no-op with a `200`,
177
+ * not a duplicate and not a 422. Position is assigned automatically at the
178
+ * end of the shelf.
179
+ *
180
+ * The book must be the shelf owner's own: somebody else's book - public or
181
+ * not - is a `404 "Book not found"` rather than a permission error, and a
182
+ * shelf can never hold a book whose owner might later make it private and
183
+ * leak it through the shelf's visibility.
184
+ *
185
+ * Answers the whole shelf in its `:extended` view, so the response already
186
+ * contains the new arrangement and there is no need to re-fetch.
187
+ *
188
+ * @throws {OmsApiError} 404 `"Shelf not found"` for an unknown shelf,
189
+ * `"Book not found"` for a book that is not yours; 403 `"Not your shelf"`
190
+ * for someone else's shelf - note this action answers **403** where
191
+ * {@link update} answers 401 for the same offence.
192
+ * @throws {OmsApiError} 422 with the item's validation messages, which in
193
+ * practice only fires if the ownership check is ever relaxed.
194
+ */
195
+ addBook(id: BookShelfId, bookId: BookId, options?: RequestOptions): Promise<BookShelf>;
196
+ /**
197
+ * `DELETE /book_shelves/:id/remove_book` - takes a book off the shelf.
198
+ *
199
+ * The book itself is untouched; only the join row goes.
200
+ *
201
+ * IDEMPOTENT to the point of being silent: removing a book that was never
202
+ * on the shelf answers `200` with the unchanged shelf. There is no way to
203
+ * tell the two
204
+ * apart from the response - compare `book_count` yourself if you need to.
205
+ *
206
+ * The book id travels as a QUERY PARAMETER rather than in a body. The server
207
+ * reads it from either, and a `DELETE` with a body is the shape
208
+ * that intermediaries are least reliable about forwarding; the transport
209
+ * also has no body slot on `delete`. Nothing here is a filter bucket, so the
210
+ * unknown-key guard does not apply.
211
+ *
212
+ * @throws {OmsApiError} 404 `"Shelf not found"`; 403 `"Not your shelf"`.
213
+ */
214
+ removeBook(id: BookShelfId, bookId: BookId, options?: RequestOptions): Promise<BookShelf>;
215
+ /**
216
+ * `POST /book_shelves/:id/reorder` - sets the order of the books on a shelf.
217
+ *
218
+ * SEND THE COMPLETE LIST, in the order you want, every time. The action
219
+ * writes `position = index + 1` for each id it was given and touches nothing
220
+ * else, so a partial list leaves the omitted books on their OLD positions -
221
+ * which now collide with the new ones, and the shelf comes back interleaved
222
+ * in an order nobody chose. There is no error: it is a `200` and a shelf that
223
+ * looks shuffled.
224
+ *
225
+ * Ids that are not on this shelf are ignored in silence, and so are ids of
226
+ * books that do not exist. So this cannot
227
+ * be used to add a book - use {@link addBook} first, then reorder.
228
+ *
229
+ * The whole thing runs in one transaction, so a shelf is never left half
230
+ * renumbered.
231
+ *
232
+ * @throws {TypeError} before any request for an empty list, which the server
233
+ * would answer `400 "No order given"`.
234
+ * @throws {OmsApiError} 404 `"Shelf not found"`; 403 `"Not your shelf"`.
235
+ */
236
+ reorder(id: BookShelfId, bookIds: readonly BookId[], options?: RequestOptions): Promise<BookShelf>;
237
+ }
@@ -0,0 +1,120 @@
1
+ /** Identifiers, enumerations and records shared by every library namespace. */
2
+ import type { Id, Timestamp } from "../../types";
3
+ /**
4
+ * Primary key of a book. A NUMBER: `books` never moved to the opaque string
5
+ * ids the account-side tables use, even though its `user_id` and its three
6
+ * `*_fs_node_id` columns are strings.
7
+ */
8
+ export type BookId = number;
9
+ /** Primary key of an annotation. A number. */
10
+ export type BookAnnotationId = number;
11
+ /** The two file formats the library accepts. */
12
+ export declare const BOOK_FORMATS: readonly ["pdf", "epub"];
13
+ /** One of {@link BOOK_FORMATS}. */
14
+ export type BookFormat = (typeof BOOK_FORMATS)[number];
15
+ /**
16
+ * Visibility of a book or a shelf. The same three levels apply to both.
17
+ *
18
+ * - `private` - owner only.
19
+ * - `unlisted` - anyone holding the link. Reachable by `GET /books/:id`,
20
+ * absent from every listing. This is the level a "share" button sets.
21
+ * - `public` - anyone, AND listed in the owner's library and in explore.
22
+ */
23
+ export declare const BOOK_VISIBILITIES: readonly ["private", "unlisted", "public"];
24
+ /** One of {@link BOOK_VISIBILITIES}. */
25
+ export type BookVisibility = (typeof BOOK_VISIBILITIES)[number];
26
+ /** Kinds of annotation. */
27
+ export declare const BOOK_ANNOTATION_KINDS: readonly ["highlight", "note", "bookmark", "progress"];
28
+ /** One of {@link BOOK_ANNOTATION_KINDS}. */
29
+ export type BookAnnotationKind = (typeof BOOK_ANNOTATION_KINDS)[number];
30
+ /**
31
+ * A book.
32
+ *
33
+ * `editable` is computed against the ASKING user, which is what lets one
34
+ * listing mix your own books with strangers' public ones and still know which
35
+ * ones offer an edit affordance. It is also the answer for "may I delete
36
+ * this".
37
+ */
38
+ export interface Book {
39
+ /** Integer primary key. See {@link BookId}. */
40
+ readonly id: BookId;
41
+ readonly created_at: Timestamp;
42
+ readonly updated_at: Timestamp;
43
+ readonly title: string;
44
+ readonly author: string | null;
45
+ readonly description: string | null;
46
+ /** Detected at upload from the extension or the content type, never from the caller. */
47
+ readonly format: BookFormat;
48
+ readonly isbn: string | null;
49
+ readonly language: string | null;
50
+ readonly page_count: number | null;
51
+ /** Size of the stored file. `null` only on rows that predate the column. */
52
+ readonly file_size_bytes: number | null;
53
+ /** Always an array, never `null`: the column is `jsonb NOT NULL DEFAULT '[]'`. */
54
+ readonly tags: string[];
55
+ readonly visibility: BookVisibility;
56
+ /** Owner. A string id, next to an integer `id`. */
57
+ readonly user_id: Id;
58
+ /** Storage node of the cover the owner uploaded. A STRING id. */
59
+ readonly cover_fs_node_id: Id | null;
60
+ /** Storage node of the WebP thumbnail the server derived from it. A STRING id. */
61
+ readonly compressed_cover_fs_node_id: Id | null;
62
+ /** True when either cover node is set. Branch on this, not on the two ids. */
63
+ readonly has_cover: boolean;
64
+ readonly owner_handle: string | null;
65
+ readonly owner_name: string | null;
66
+ /** Whether the ASKING user may edit the book. Also answers "may I delete this". */
67
+ readonly editable: boolean;
68
+ }
69
+ /**
70
+ * One row of the public-library directory: a person, not a book.
71
+ *
72
+ * Unlike every other payload in the namespace it carries no `id`, no
73
+ * `created_at` and no `updated_at`, and the owner is addressed by `handle`.
74
+ * Feed that handle back as {@link ListBooksParams.ownerHandle}.
75
+ */
76
+ export interface PublicLibrary {
77
+ readonly handle: string;
78
+ readonly name: string;
79
+ /** What the owner named their library, or `null` if they never did. */
80
+ readonly library_name: string | null;
81
+ readonly library_description: string | null;
82
+ /** PUBLIC books only. An owner's unlisted and private books are not counted. */
83
+ readonly book_count: number;
84
+ }
85
+ /**
86
+ * Where in the book an annotation sits. Format-specific and opaque to the API:
87
+ * the column is `jsonb NOT NULL DEFAULT '{}'` and nothing server-side reads
88
+ * inside it.
89
+ *
90
+ * The two shapes the readers agree on today:
91
+ * - EPUB: `{ cfiRange }` for a range, `{ cfi }` for a point;
92
+ * - PDF: `{ pageNumber, rects: [{ x, y, w, h }] }`, page-normalised to `[0,1]`
93
+ * so a highlight survives a zoom.
94
+ *
95
+ * A new reader may write a new shape. Read defensively.
96
+ */
97
+ export type BookAnnotationLocation = Record<string, unknown>;
98
+ /**
99
+ * A highlight, a note, a bookmark, or a reading-progress marker.
100
+ *
101
+ * ALWAYS PRIVATE to the reader who made it, even on a public book: an
102
+ * anonymous caller sees none, and a signed-in caller sees only their own.
103
+ * There is no sharing of highlights, and `GET /book_annotations` therefore
104
+ * needs no user filter - it can only ever return your own.
105
+ */
106
+ export interface BookAnnotation {
107
+ readonly id: BookAnnotationId;
108
+ readonly created_at: Timestamp;
109
+ readonly updated_at: Timestamp;
110
+ readonly book_id: BookId;
111
+ /** Author. Always the caller, since nobody else's are visible. */
112
+ readonly user_id: Id;
113
+ readonly kind: BookAnnotationKind;
114
+ readonly location: BookAnnotationLocation;
115
+ /** Free-form, usually a CSS colour. Not validated server-side. */
116
+ readonly color: string | null;
117
+ readonly note: string | null;
118
+ /** The passage the reader selected, kept so a highlight can be listed without opening the book. */
119
+ readonly selected_text: string | null;
120
+ }