@omelhorsite/sdk 0.5.1 → 0.11.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.
- package/README.md +79 -238
- package/dist/index.js +455 -1
- package/dist/types/client.d.ts +6 -0
- package/dist/types/resources/account.d.ts +68 -2
- package/dist/types/resources/admin/index.d.ts +10 -0
- package/dist/types/resources/admin/llm.d.ts +247 -0
- package/dist/types/resources/admin/myOauthApplications.d.ts +2 -2
- package/dist/types/resources/content/notifications.d.ts +41 -8
- package/dist/types/resources/content/serviceUsages.d.ts +1 -1
- package/dist/types/resources/index.d.ts +2 -0
- package/dist/types/resources/llm.d.ts +296 -0
- package/dist/types/resources/search.d.ts +116 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,308 +1,149 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @omelhorsite/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@omelhorsite/sdk)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const oms = new Oms({ token: myToken });
|
|
9
|
-
|
|
10
|
-
const me = await oms.auth.whoami();
|
|
11
|
-
const link = await oms.shortLinks.create({ url: "https://example.com" });
|
|
12
|
-
```
|
|
5
|
+
TypeScript client for the [omelhorsite](https://omelhorsite.pt) API. Works in
|
|
6
|
+
browsers, Bun, Node 18+, React Native and Cloudflare Workers.
|
|
13
7
|
|
|
14
8
|
```sh
|
|
15
9
|
bun add @omelhorsite/sdk
|
|
16
10
|
```
|
|
17
11
|
|
|
18
|
-
## What shapes every signature
|
|
19
|
-
|
|
20
|
-
**It runs anywhere `fetch` runs.** Browsers, Bun, Node 18+, React Native and
|
|
21
|
-
Cloudflare-Worker-class isolates. Nothing in the package touches `node:*`,
|
|
22
|
-
`process`, the filesystem or `console`; `fetch` is injectable.
|
|
23
|
-
|
|
24
|
-
**Files are values, never paths.** `Blob`, `Uint8Array` or `ReadableStream`
|
|
25
|
-
in; `Blob` out. On React Native a picked `{ uri, name, type }` is accepted as
|
|
26
|
-
it is and streamed by the platform.
|
|
27
|
-
|
|
28
|
-
**The types are the documentation.** Everything is exported flat from the
|
|
29
|
-
package root, and the JSDoc on each method carries the rate limits, quota
|
|
30
|
-
units and traps of the endpoint behind it.
|
|
31
|
-
|
|
32
|
-
## Constructing a client
|
|
33
|
-
|
|
34
12
|
```ts
|
|
35
13
|
import { Oms } from "@omelhorsite/sdk";
|
|
36
14
|
|
|
37
|
-
const oms = new Oms({
|
|
38
|
-
token: "...", // string, function, TokenProvider, or omitted
|
|
39
|
-
baseUrl: "http://localhost:3000", // defaults to https://backend.omelhorsite.pt
|
|
40
|
-
fetch: myFetch, // defaults to globalThis.fetch
|
|
41
|
-
headers: { "X-Trace": id }, // merged under per-call headers
|
|
42
|
-
timeoutMs: 30_000, // per attempt; 0 disables
|
|
43
|
-
retry: { maxAttempts: 3 }, // or false to never retry
|
|
44
|
-
clientName: "my-worker/1.0", // becomes X-Oms-Client
|
|
45
|
-
});
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Constructing does no I/O. `oms.withToken(other)` returns a copy under a
|
|
49
|
-
different identity rather than mutating one.
|
|
15
|
+
const oms = new Oms({ token: "..." });
|
|
50
16
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`new Oms({ sessionCookie: true })`.
|
|
17
|
+
const me = await oms.account.me();
|
|
18
|
+
const link = await oms.shortLinks.create({ url: "https://example.com" });
|
|
19
|
+
```
|
|
55
20
|
|
|
56
|
-
|
|
57
|
-
none of them patch a global.
|
|
21
|
+
## Client
|
|
58
22
|
|
|
59
23
|
```ts
|
|
60
24
|
const oms = new Oms({
|
|
61
|
-
token,
|
|
62
|
-
|
|
25
|
+
token: "...", // omit for anonymous access
|
|
26
|
+
baseUrl: "http://localhost:3000", // default: https://backend.omelhorsite.pt
|
|
27
|
+
fetch: myFetch, // default: globalThis.fetch
|
|
28
|
+
timeoutMs: 30_000,
|
|
29
|
+
retry: { maxAttempts: 3 }, // or false
|
|
63
30
|
});
|
|
64
31
|
```
|
|
65
32
|
|
|
66
|
-
|
|
33
|
+
`token` can be a string, a function returning one, or a `TokenProvider` that
|
|
34
|
+
refreshes itself. On a first-party page use `new Oms({ sessionCookie: true })`
|
|
35
|
+
instead.
|
|
67
36
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```
|
|
37
|
+
Some things work without a token (short links, notepads, chests, IP lookup,
|
|
38
|
+
the captcha-gated tools), at a smaller daily quota.
|
|
71
39
|
|
|
72
|
-
##
|
|
40
|
+
## Examples
|
|
73
41
|
|
|
74
|
-
|
|
75
|
-
| --- | --- |
|
|
76
|
-
| `oms.auth` | OAuth: device grant, refresh, revoke, `whoami`, `userinfo`. |
|
|
77
|
-
| `oms.sessions` `oms.passkeys` | Session sign-in, sign-up, OTP, passkeys. |
|
|
78
|
-
| `oms.account` | The signed-in user, their profile, sessions and usage. |
|
|
79
|
-
| `oms.storage` | The virtual filesystem: nodes, uploads, downloads, grants. |
|
|
80
|
-
| `oms.media` | Resolving stored media to URLs. |
|
|
81
|
-
| `oms.music` | Songs, artists, playlists, imports, likes, jams, the social feed. |
|
|
82
|
-
| `oms.movies` | Addons, collections, watch progress. |
|
|
83
|
-
| `oms.library` | Books, shelves, annotations, the study assistant. |
|
|
84
|
-
| `oms.social` | Direct messages, relationships, group chats. |
|
|
85
|
-
| `oms.content` | Blogs, notifications, feedbacks, jokes, site status, intel. |
|
|
86
|
-
| `oms.tools` | The metered media tools, each with its own daily quota. |
|
|
87
|
-
| `oms.jobs` | Background jobs: list, get, wait, watch. |
|
|
88
|
-
| `oms.quotas` | Every ceiling on the account in one call. |
|
|
89
|
-
| `oms.tickets` | Support tickets and their message threads. |
|
|
90
|
-
| `oms.shortLinks` `oms.notepads` `oms.dynamicQrs` `oms.chests` `oms.forms` `oms.linkTrees` | Everything that ends in a shareable URL. |
|
|
91
|
-
| `oms.ipLookup` | Geolocation and network metadata for an IP. |
|
|
92
|
-
| `oms.admin` | Administrator-only views and actions. |
|
|
93
|
-
| `oms.realtime` | The WebSocket channel: notifications, jobs, jams. |
|
|
94
|
-
| `oms.local` | Pure client-side helpers. No network, no credential. |
|
|
95
|
-
|
|
96
|
-
## Listing and filtering
|
|
97
|
-
|
|
98
|
-
Every `list()` takes the same query language, typed per resource:
|
|
42
|
+
Listing and paging:
|
|
99
43
|
|
|
100
44
|
```ts
|
|
45
|
+
import { collect } from "@omelhorsite/sdk";
|
|
46
|
+
|
|
101
47
|
const page = await oms.library.books.list({
|
|
102
|
-
search: { title: "maias" },
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
order: "created_at:desc", // "column:asc" | "column:desc"
|
|
106
|
-
page: 2,
|
|
107
|
-
pageSize: 50, // capped at 500 by the server
|
|
48
|
+
search: { title: "maias" },
|
|
49
|
+
order: "created_at:desc",
|
|
50
|
+
pageSize: 50,
|
|
108
51
|
});
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
The columns each resource accepts are string-literal unions, so a key the
|
|
112
|
-
server would reject with `400` is a compile error instead. Most resources also
|
|
113
|
-
offer camelCased shortcuts (`userId`, `withUser`, `ownerHandle`) that write
|
|
114
|
-
into the same buckets.
|
|
115
|
-
|
|
116
|
-
The result is a `Paginated<T>`: `items`, `page`, `pageSize`, `hasMore` and
|
|
117
|
-
`next()`. Two helpers walk it:
|
|
118
52
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const all = await collect(page, 5000); // flatten, up to a limit
|
|
123
|
-
for await (const p of pages(page)) { ... } // or one page at a time
|
|
53
|
+
page.items; // this page
|
|
54
|
+
await page.next(); // the next one, or null
|
|
55
|
+
await collect(page, 1000); // flatten up to a limit
|
|
124
56
|
```
|
|
125
57
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
## Files
|
|
129
|
-
|
|
130
|
-
A `FileInput` always carries a filename, because the API derives the stored
|
|
131
|
-
name and, for the media tools, the container format from it:
|
|
58
|
+
Uploading a file:
|
|
132
59
|
|
|
133
60
|
```ts
|
|
134
61
|
import { file } from "@omelhorsite/sdk";
|
|
135
62
|
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
Pass `size` when you know it: it lets `storage.upload` choose the multipart
|
|
142
|
-
path (32 MiB and up) without buffering the stream to measure it.
|
|
143
|
-
|
|
144
|
-
Downloads come back as a `Blob`, or as a `FileOutput` when the server's
|
|
145
|
-
filename and content type matter:
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
const out = await oms.storage.download(nodeId);
|
|
149
|
-
const { stream } = await oms.storage.downloadStream(nodeId);
|
|
63
|
+
const roots = await oms.storage.roots();
|
|
64
|
+
const [node] = await oms.storage.upload({
|
|
65
|
+
parentId: roots.home!,
|
|
66
|
+
files: [file(blob, "relatorio.pdf")],
|
|
67
|
+
});
|
|
150
68
|
```
|
|
151
69
|
|
|
152
|
-
|
|
70
|
+
Files are values (`Blob`, `Uint8Array`, `ReadableStream`), never paths. On
|
|
71
|
+
React Native pass the picker's `{ uri, name, type }` directly.
|
|
153
72
|
|
|
154
|
-
|
|
155
|
-
the node at the end.
|
|
73
|
+
Running a tool (transcription, upscale, background removal, ...):
|
|
156
74
|
|
|
157
75
|
```ts
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
{ parentId: roots.home!, files: [file(blob, "relatorio.pdf")], concurrency: 4 },
|
|
162
|
-
{ onProgress: (p) => report(p.loaded, p.total) },
|
|
76
|
+
const result = await oms.tools.transcription.run(
|
|
77
|
+
{ audio: file(blob, "entrevista.m4a"), language: "pt" },
|
|
78
|
+
{ onProgress: (p) => console.log(p.status) },
|
|
163
79
|
);
|
|
164
80
|
```
|
|
165
81
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
part. The same goes for every other upload: pass `onUploadProgress` in the
|
|
169
|
-
options of `tools.*.create`, `library.books.create` or
|
|
170
|
-
`chests.entries.createWithUpload`. A file rejected on its own (quota, name
|
|
171
|
-
collision) does not throw; it is missing from the returned array, so compare
|
|
172
|
-
lengths when partial success matters.
|
|
82
|
+
`run` starts the job and waits for it. Use `create` + `oms.jobs.wait` if you
|
|
83
|
+
would rather come back to it later.
|
|
173
84
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
Every metered tool is asynchronous: the server enqueues the work and answers
|
|
177
|
-
with a row in `"pending"`, plus a `job_id` and, for an anonymous caller, a
|
|
178
|
-
`watch_token` scoped to that one job.
|
|
85
|
+
Talking to the assistant:
|
|
179
86
|
|
|
180
87
|
```ts
|
|
181
|
-
const
|
|
182
|
-
const now = await oms.tools.transcription.get(started.id);
|
|
88
|
+
const chat = await oms.llm.chats.create();
|
|
183
89
|
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
);
|
|
90
|
+
for await (const event of oms.llm.chats.send(chat.id, { content: "Olá!" })) {
|
|
91
|
+
if (event.type === "delta") process.stdout.write(event.delta);
|
|
92
|
+
}
|
|
188
93
|
```
|
|
189
94
|
|
|
190
|
-
|
|
191
|
-
budget or nowhere to hold a wait: start the job, keep the id, pick it up later.
|
|
192
|
-
|
|
193
|
-
Waiting resolves for both `"complete"` and `"failed"`: a failed job is an
|
|
194
|
-
answer, not a transport error. Check the status before reading the result.
|
|
95
|
+
An endpoint the SDK does not wrap yet:
|
|
195
96
|
|
|
196
97
|
```ts
|
|
197
|
-
const
|
|
198
|
-
if (job.status === "failed") throw new Error(job.error ?? "the job failed");
|
|
98
|
+
const rows = await oms.http.get<{ id: string }[]>("/some/path");
|
|
199
99
|
```
|
|
200
100
|
|
|
201
|
-
|
|
202
|
-
downloader spells its own terminal state `"done"`. Compare against the exported
|
|
203
|
-
constants, never against a literal.
|
|
204
|
-
|
|
205
|
-
Check the quota before starting something expensive:
|
|
101
|
+
## Namespaces
|
|
206
102
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
103
|
+
- `oms.auth` - OAuth: device grant, refresh, revoke, `whoami`.
|
|
104
|
+
- `oms.sessions`, `oms.passkeys` - sign-in, sign-up, OTP, passkeys.
|
|
105
|
+
- `oms.account` - the signed-in user, profile, sessions, usage. `oms.account.notificationPreferences` decides, per notification kind, whether it shows in the inbox and whether it is emailed; the security kinds always email, unless the master switch is off.
|
|
106
|
+
- `oms.storage` - files and folders: upload, download, share.
|
|
107
|
+
- `oms.music` - songs, artists, playlists, jams.
|
|
108
|
+
- `oms.movies` - addons, collections, watch progress.
|
|
109
|
+
- `oms.library` - books, shelves, annotations.
|
|
110
|
+
- `oms.llm` - models and assistant chats.
|
|
111
|
+
- `oms.search` - web, image, news and video search.
|
|
112
|
+
- `oms.social` - direct messages, friends, group chats.
|
|
113
|
+
- `oms.content` - blogs, notifications, feedback, site status. `oms.content.notifications.unsubscribe(token)` honours the link at the foot of a notification email and needs no credential.
|
|
114
|
+
- `oms.tools` - media tools, each with its own daily quota.
|
|
115
|
+
- `oms.jobs`, `oms.quotas` - background jobs and account limits.
|
|
116
|
+
- `oms.tickets` - support tickets.
|
|
117
|
+
- `oms.shortLinks`, `oms.notepads`, `oms.dynamicQrs`, `oms.chests`, `oms.forms`, `oms.linkTrees` - things that end in a shareable URL.
|
|
118
|
+
- `oms.ipLookup` - geolocation for an IP.
|
|
119
|
+
- `oms.realtime` - WebSocket: notifications, jobs, jams.
|
|
120
|
+
- `oms.admin` - administrator-only.
|
|
121
|
+
- `oms.local` - client-side helpers (passwords, QR codes). No network.
|
|
211
122
|
|
|
212
123
|
## Errors
|
|
213
124
|
|
|
214
|
-
|
|
215
|
-
between retrying, re-scoping and giving up.
|
|
125
|
+
Everything thrown is an `OmsError`:
|
|
216
126
|
|
|
217
127
|
```ts
|
|
218
|
-
import { OmsApiError, OmsAuthError, OmsQuotaError
|
|
128
|
+
import { OmsApiError, OmsAuthError, OmsQuotaError } from "@omelhorsite/sdk";
|
|
219
129
|
|
|
220
130
|
try {
|
|
221
131
|
await oms.storage.delete(id);
|
|
222
|
-
} catch (
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (thrown instanceof OmsAuthError) return signInAgain();
|
|
229
|
-
if (thrown instanceof OmsApiError) log(thrown.status, thrown.fieldErrors);
|
|
230
|
-
throw thrown;
|
|
132
|
+
} catch (e) {
|
|
133
|
+
if (e instanceof OmsQuotaError) wait(e.retryAfterMs);
|
|
134
|
+
else if (e instanceof OmsAuthError) signIn();
|
|
135
|
+
else if (e instanceof OmsApiError) console.log(e.status, e.message);
|
|
136
|
+
else throw e;
|
|
231
137
|
}
|
|
232
138
|
```
|
|
233
139
|
|
|
234
|
-
`OmsNetworkError` means the API was never reached
|
|
235
|
-
|
|
236
|
-
for idempotent requests, with backoff and jitter. Pass `retry: false` to any
|
|
237
|
-
create you would rather see fail than duplicate.
|
|
238
|
-
|
|
239
|
-
## OAuth
|
|
240
|
-
|
|
241
|
-
The device grant, in full. `/oauth/token` authenticates with `client_id` in
|
|
242
|
-
the form body; a stray `Authorization` header breaks it, so start from a
|
|
243
|
-
client with no token.
|
|
244
|
-
|
|
245
|
-
```ts
|
|
246
|
-
import { Oms, OAuthTokenProvider, decodeIdToken } from "@omelhorsite/sdk";
|
|
247
|
-
|
|
248
|
-
const anon = new Oms({ baseUrl, fetch });
|
|
249
|
-
|
|
250
|
-
const grant = await anon.auth.device.start({ clientId, scope: "openid storage:read" });
|
|
251
|
-
show(grant.verificationUriComplete ?? grant.verificationUri, grant.userCode);
|
|
252
|
-
|
|
253
|
-
const set = await anon.auth.device.wait({
|
|
254
|
-
clientId,
|
|
255
|
-
deviceCode: grant.deviceCode,
|
|
256
|
-
intervalMs: grant.intervalMs,
|
|
257
|
-
expiresAt: grant.expiresAt,
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
const tokens = new OAuthTokenProvider({
|
|
261
|
-
store: myTokenStore,
|
|
262
|
-
refresh: (refreshToken) => anon.auth.refresh(refreshToken, { clientId }),
|
|
263
|
-
});
|
|
264
|
-
await tokens.set(set);
|
|
265
|
-
|
|
266
|
-
const oms = new Oms({ baseUrl, fetch, tokens }); // refreshes itself on a 401
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
`decodeIdToken(set.idToken)` reads the claims. `sub` is the user id, stable
|
|
270
|
-
and the only identifier safe to key on; the handle and the email are mutable.
|
|
271
|
-
|
|
272
|
-
Access tokens live two hours. `OMS_SCOPES` is the full list the server
|
|
273
|
-
defines; ask for the narrowest set that does the job.
|
|
274
|
-
|
|
275
|
-
## Local helpers
|
|
276
|
-
|
|
277
|
-
No network, no credential:
|
|
278
|
-
|
|
279
|
-
```ts
|
|
280
|
-
import { generatePassphrase, generatePassword, passwordStrength, qrToSvg } from "@omelhorsite/sdk";
|
|
281
|
-
|
|
282
|
-
const phrase = generatePassphrase({ words: 5, capitalize: true });
|
|
283
|
-
const score = passwordStrength(generatePassword({ length: 20 }));
|
|
284
|
-
const svg = qrToSvg("https://example.com");
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## Testing against it
|
|
288
|
-
|
|
289
|
-
Inject a fetch. There is no global to stub:
|
|
290
|
-
|
|
291
|
-
```ts
|
|
292
|
-
const oms = new Oms({
|
|
293
|
-
token: "test",
|
|
294
|
-
fetch: async () => new Response(JSON.stringify({ id: "1" }), {
|
|
295
|
-
status: 200,
|
|
296
|
-
headers: { "content-type": "application/json" },
|
|
297
|
-
}),
|
|
298
|
-
});
|
|
299
|
-
```
|
|
140
|
+
`OmsNetworkError` means the API was never reached, `OmsTimeoutError` that the
|
|
141
|
+
request took too long.
|
|
300
142
|
|
|
301
143
|
## Developing
|
|
302
144
|
|
|
303
145
|
```sh
|
|
304
146
|
bun test
|
|
305
147
|
bun run typecheck
|
|
306
|
-
bun run check:isolate
|
|
307
148
|
bun run build
|
|
308
149
|
```
|