@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.
- package/README.md +112 -130
- package/dist/index.js +4800 -610
- package/dist/types/client.d.ts +60 -3
- package/dist/types/http.d.ts +444 -19
- package/dist/types/index.d.ts +5 -1
- package/dist/types/internal/attachments.d.ts +17 -0
- package/dist/types/internal/helpers.d.ts +9 -0
- package/dist/types/listing.d.ts +90 -0
- package/dist/types/resources/account.d.ts +71 -5
- package/dist/types/resources/admin/authorizedApplications.d.ts +133 -0
- package/dist/types/resources/admin/chests.d.ts +51 -0
- package/dist/types/resources/admin/eventAlerts.d.ts +60 -0
- package/dist/types/resources/admin/identities.d.ts +99 -0
- package/dist/types/resources/admin/index.d.ts +77 -0
- package/dist/types/resources/admin/jobs.d.ts +97 -0
- package/dist/types/resources/admin/myOauthApplications.d.ts +282 -0
- package/dist/types/resources/admin/notepads.d.ts +49 -0
- package/dist/types/resources/admin/oauthApplications.d.ts +309 -0
- package/dist/types/resources/admin/quotas.d.ts +114 -0
- package/dist/types/resources/admin/shortLinks.d.ts +201 -0
- package/dist/types/resources/admin/types.d.ts +222 -0
- package/dist/types/resources/admin/vocalSeparations.d.ts +98 -0
- package/dist/types/resources/admin.d.ts +1841 -0
- package/dist/types/resources/auth/index.d.ts +39 -0
- package/dist/types/resources/auth/passkeys.d.ts +652 -0
- package/dist/types/resources/auth/sessions.d.ts +844 -0
- package/dist/types/resources/chests.d.ts +54 -3
- package/dist/types/resources/content/analysis.d.ts +62 -0
- package/dist/types/resources/content/blogs.d.ts +471 -0
- package/dist/types/resources/content/feedbacks.d.ts +263 -0
- package/dist/types/resources/content/index.d.ts +52 -0
- package/dist/types/resources/content/intel/articles.d.ts +230 -0
- package/dist/types/resources/content/intel/config.d.ts +135 -0
- package/dist/types/resources/content/intel/index.d.ts +53 -0
- package/dist/types/resources/content/intel/items.d.ts +91 -0
- package/dist/types/resources/content/intel/reports.d.ts +108 -0
- package/dist/types/resources/content/intel/scripts.d.ts +152 -0
- package/dist/types/resources/content/intel/sources.d.ts +236 -0
- package/dist/types/resources/content/intel/stats.d.ts +105 -0
- package/dist/types/resources/content/intel/types.d.ts +86 -0
- package/dist/types/resources/content/jokes.d.ts +93 -0
- package/dist/types/resources/content/notifications.d.ts +147 -0
- package/dist/types/resources/content/serviceUsages.d.ts +73 -0
- package/dist/types/resources/content/servicesStatus.d.ts +194 -0
- package/dist/types/resources/content/siteConfig.d.ts +43 -0
- package/dist/types/resources/content/spaceInvaders.d.ts +154 -0
- package/dist/types/resources/content.d.ts +2775 -0
- package/dist/types/resources/dynamicQrs.d.ts +39 -3
- package/dist/types/resources/forms.d.ts +176 -35
- package/dist/types/resources/index.d.ts +19 -8
- package/dist/types/resources/ipLookup.d.ts +20 -4
- package/dist/types/resources/jobs.d.ts +69 -25
- package/dist/types/resources/library/annotations.d.ts +162 -0
- package/dist/types/resources/library/books.d.ts +534 -0
- package/dist/types/resources/library/chat.d.ts +259 -0
- package/dist/types/resources/library/index.d.ts +39 -0
- package/dist/types/resources/library/shelves.d.ts +237 -0
- package/dist/types/resources/library/types.d.ts +120 -0
- package/dist/types/resources/library.d.ts +1433 -0
- package/dist/types/resources/linkTrees.d.ts +142 -30
- package/dist/types/resources/media.d.ts +351 -0
- package/dist/types/resources/movies/addons.d.ts +388 -0
- package/dist/types/resources/movies/collections.d.ts +352 -0
- package/dist/types/resources/movies/index.d.ts +29 -0
- package/dist/types/resources/movies/types.d.ts +50 -0
- package/dist/types/resources/movies/watchProgress.d.ts +277 -0
- package/dist/types/resources/movies.d.ts +1160 -0
- package/dist/types/resources/music/artists.d.ts +1068 -0
- package/dist/types/resources/music/imports.d.ts +941 -0
- package/dist/types/resources/music/index.d.ts +61 -0
- package/dist/types/resources/music/playlists.d.ts +1027 -0
- package/dist/types/resources/music/social.d.ts +1132 -0
- package/dist/types/resources/music/songs.d.ts +1167 -0
- package/dist/types/resources/notepads.d.ts +4 -1
- package/dist/types/resources/quotas.d.ts +7 -1
- package/dist/types/resources/realtime.d.ts +855 -0
- package/dist/types/resources/shortLinks.d.ts +49 -5
- package/dist/types/resources/social/groupChats.d.ts +458 -0
- package/dist/types/resources/social/index.d.ts +19 -0
- package/dist/types/resources/social/messages.d.ts +424 -0
- package/dist/types/resources/social/relationships.d.ts +322 -0
- package/dist/types/resources/social/types.d.ts +37 -0
- package/dist/types/resources/social.d.ts +1348 -0
- package/dist/types/resources/storage/upload.d.ts +158 -11
- package/dist/types/resources/storage.d.ts +111 -27
- package/dist/types/resources/tickets.d.ts +101 -13
- package/dist/types/resources/tools/backgroundRemoval.d.ts +18 -3
- package/dist/types/resources/tools/captions.d.ts +448 -21
- package/dist/types/resources/tools/downloader.d.ts +21 -0
- package/dist/types/resources/tools/index.d.ts +57 -15
- package/dist/types/resources/tools/jumpstyle.d.ts +50 -17
- package/dist/types/resources/tools/transcription.d.ts +35 -13
- package/dist/types/resources/tools/upscale.d.ts +23 -3
- package/dist/types/resources/tools/vocalSeparation.d.ts +30 -13
- package/dist/types/types.d.ts +249 -17
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# `@omelhorsite/sdk`
|
|
2
2
|
|
|
3
|
-
The TypeScript client for the omelhorsite API.
|
|
4
|
-
clients of this package, not the other way round.
|
|
3
|
+
The TypeScript client for the omelhorsite API.
|
|
5
4
|
|
|
6
5
|
```ts
|
|
7
6
|
import { Oms } from "@omelhorsite/sdk";
|
|
@@ -12,33 +11,23 @@ const me = await oms.auth.whoami();
|
|
|
12
11
|
const link = await oms.shortLinks.create({ url: "https://example.com" });
|
|
13
12
|
```
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
no filesystem, no `console`. Only platform APIs: `fetch`, Web Streams,
|
|
19
|
-
WebCrypto, `Blob`, `FormData`, `AbortController`. This is enforced at compile
|
|
20
|
-
time - `src/isolate-guard.d.ts` declares a poisoned `process`, so reaching for
|
|
21
|
-
one is a type error, not a runtime surprise in production.
|
|
22
|
-
|
|
23
|
-
**2. Files are values, never paths.** `Blob`, `Uint8Array`, `ReadableStream` in;
|
|
24
|
-
`Blob` out. Turning a path into bytes is the host's job, because the isolate has
|
|
25
|
-
no path to turn.
|
|
26
|
-
|
|
27
|
-
**3. The types are the public interface.** Everything is re-exported flat from
|
|
28
|
-
the package root; there is no deep import into `src/`. In code mode a model
|
|
29
|
-
reads the `.d.ts` and nothing else, so the JSDoc carries the rate limits, the
|
|
30
|
-
quota units and the places the backend surprises you.
|
|
14
|
+
```sh
|
|
15
|
+
bun add @omelhorsite/sdk
|
|
16
|
+
```
|
|
31
17
|
|
|
32
|
-
##
|
|
18
|
+
## What shapes every signature
|
|
33
19
|
|
|
34
|
-
|
|
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.
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
42
31
|
|
|
43
32
|
## Constructing a client
|
|
44
33
|
|
|
@@ -57,15 +46,15 @@ const oms = new Oms({
|
|
|
57
46
|
```
|
|
58
47
|
|
|
59
48
|
Constructing does no I/O. `oms.withToken(other)` returns a copy under a
|
|
60
|
-
different identity rather than mutating one
|
|
61
|
-
finish under the wrong credential.
|
|
49
|
+
different identity rather than mutating one.
|
|
62
50
|
|
|
63
51
|
Omitting the token is legitimate: short links, notepads, chests, IP lookup and
|
|
64
|
-
the captcha-gated tools all work anonymously, at a smaller daily quota.
|
|
52
|
+
the captcha-gated tools all work anonymously, at a smaller daily quota. A
|
|
53
|
+
browser on the API's own site can use the session cookie instead with
|
|
54
|
+
`new Oms({ sessionCookie: true })`.
|
|
65
55
|
|
|
66
|
-
**Injecting `fetch` is the extension point.** A
|
|
67
|
-
|
|
68
|
-
global:
|
|
56
|
+
**Injecting `fetch` is the extension point.** A cache, a test double, a proxy:
|
|
57
|
+
none of them patch a global.
|
|
69
58
|
|
|
70
59
|
```ts
|
|
71
60
|
const oms = new Oms({
|
|
@@ -74,8 +63,7 @@ const oms = new Oms({
|
|
|
74
63
|
});
|
|
75
64
|
```
|
|
76
65
|
|
|
77
|
-
An endpoint the SDK has not wrapped yet is still reachable
|
|
78
|
-
the package to add one call:
|
|
66
|
+
An endpoint the SDK has not wrapped yet is still reachable:
|
|
79
67
|
|
|
80
68
|
```ts
|
|
81
69
|
const rows = await oms.http.get<{ id: string }[]>("/some/new/path");
|
|
@@ -85,22 +73,62 @@ const rows = await oms.http.get<{ id: string }[]>("/some/new/path");
|
|
|
85
73
|
|
|
86
74
|
| | |
|
|
87
75
|
| --- | --- |
|
|
88
|
-
| `oms.auth` |
|
|
89
|
-
| `oms.
|
|
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. |
|
|
90
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. |
|
|
91
86
|
| `oms.tools` | The metered media tools, each with its own daily quota. |
|
|
92
|
-
| `oms.
|
|
93
|
-
| `oms.
|
|
87
|
+
| `oms.jobs` | Background jobs: list, get, wait, watch. |
|
|
88
|
+
| `oms.quotas` | Every ceiling on the account in one call. |
|
|
94
89
|
| `oms.tickets` | Support tickets and their message threads. |
|
|
95
90
|
| `oms.shortLinks` `oms.notepads` `oms.dynamicQrs` `oms.chests` `oms.forms` `oms.linkTrees` | Everything that ends in a shareable URL. |
|
|
96
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. |
|
|
97
94
|
| `oms.local` | Pure client-side helpers. No network, no credential. |
|
|
98
95
|
|
|
96
|
+
## Listing and filtering
|
|
97
|
+
|
|
98
|
+
Every `list()` takes the same query language, typed per resource:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const page = await oms.library.books.list({
|
|
102
|
+
search: { title: "maias" }, // partial, accent-insensitive
|
|
103
|
+
exactSearch: { format: "epub" }, // equality; an array is IN, null is IS NULL
|
|
104
|
+
extraOptions: { scope: "mine" }, // endpoint-specific, only where declared
|
|
105
|
+
order: "created_at:desc", // "column:asc" | "column:desc"
|
|
106
|
+
page: 2,
|
|
107
|
+
pageSize: 50, // capped at 500 by the server
|
|
108
|
+
});
|
|
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
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { collect, pages } from "@omelhorsite/sdk";
|
|
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
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Always pass a limit to `collect`; a listing can be very long.
|
|
127
|
+
|
|
99
128
|
## Files
|
|
100
129
|
|
|
101
|
-
A `FileInput` always carries a filename, because the API derives the stored
|
|
102
|
-
and, for the media tools, the container format from it
|
|
103
|
-
exists so that requirement stays visible at the call site:
|
|
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:
|
|
104
132
|
|
|
105
133
|
```ts
|
|
106
134
|
import { file } from "@omelhorsite/sdk";
|
|
@@ -110,22 +138,21 @@ const bytes = file(new Uint8Array(buffer), "dump.sql", { contentType: "applicati
|
|
|
110
138
|
const streamed = file(response.body!, "big.mov", { size: contentLength });
|
|
111
139
|
```
|
|
112
140
|
|
|
113
|
-
Pass `size` when you know it: it lets `storage.upload`
|
|
114
|
-
(
|
|
115
|
-
without a size gets buffered, which for a 2 GB file is not what you want.
|
|
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.
|
|
116
143
|
|
|
117
144
|
Downloads come back as a `Blob`, or as a `FileOutput` when the server's
|
|
118
145
|
filename and content type matter:
|
|
119
146
|
|
|
120
147
|
```ts
|
|
121
|
-
const out = await oms.storage.download(nodeId);
|
|
122
|
-
const { stream } = await oms.storage.downloadStream(nodeId);
|
|
148
|
+
const out = await oms.storage.download(nodeId);
|
|
149
|
+
const { stream } = await oms.storage.downloadStream(nodeId);
|
|
123
150
|
```
|
|
124
151
|
|
|
125
152
|
## Uploading to storage
|
|
126
153
|
|
|
127
|
-
Bytes
|
|
128
|
-
|
|
154
|
+
Bytes go straight to object storage through a presigned URL and are bound to
|
|
155
|
+
the node at the end.
|
|
129
156
|
|
|
130
157
|
```ts
|
|
131
158
|
const roots = await oms.storage.roots();
|
|
@@ -136,66 +163,30 @@ const nodes = await oms.storage.upload(
|
|
|
136
163
|
);
|
|
137
164
|
```
|
|
138
165
|
|
|
139
|
-
Progress arrives per finished file or part, never per byte
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
array, so compare lengths when partial success matters.
|
|
143
|
-
|
|
144
|
-
## Pagination
|
|
145
|
-
|
|
146
|
-
Every listing returns a `Paginated<T>` with a `load` function, and two helpers
|
|
147
|
-
consume it:
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
import { collect, pages } from "@omelhorsite/sdk";
|
|
151
|
-
|
|
152
|
-
const first = await oms.storage.list({ parentId, pageSize: 500 });
|
|
153
|
-
|
|
154
|
-
const all = await collect(first, 5000); // flatten, up to a limit
|
|
155
|
-
for await (const page of pages(first)) { ... } // or one page at a time
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
Always pass a limit to `collect`. A directory with 300k nodes is a real thing
|
|
159
|
-
that has happened here.
|
|
166
|
+
Progress arrives per finished file or part, never per byte. A file rejected on
|
|
167
|
+
its own (quota, name collision) does not throw; it is missing from the
|
|
168
|
+
returned array, so compare lengths when partial success matters.
|
|
160
169
|
|
|
161
170
|
## Long jobs: `create` / `get`, and `run`
|
|
162
171
|
|
|
163
|
-
Every metered tool is asynchronous
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
So every tool namespace has the same three-part shape:
|
|
172
|
+
Every metered tool is asynchronous: the server enqueues the work and answers
|
|
173
|
+
with a row in `"pending"`, plus a `job_id` and, for an anonymous caller, a
|
|
174
|
+
`watch_token` scoped to that one job.
|
|
168
175
|
|
|
169
176
|
```ts
|
|
170
|
-
// start: returns as soon as the work is enqueued
|
|
171
177
|
const started = await oms.tools.transcription.create({ audio, language: "pt" });
|
|
172
|
-
|
|
173
|
-
// poll: one request, no waiting
|
|
174
178
|
const now = await oms.tools.transcription.get(started.id);
|
|
175
179
|
|
|
176
|
-
// or let the SDK poll for you
|
|
177
180
|
const done = await oms.tools.transcription.run(
|
|
178
181
|
{ audio, language: "pt" },
|
|
179
182
|
{ onProgress: (p) => report(p.status), waitTimeoutMs: 15 * 60_000 },
|
|
180
183
|
);
|
|
181
184
|
```
|
|
182
185
|
|
|
183
|
-
`run` is `create` plus `jobs.wait
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- **A Worker has a wall-clock budget.** Holding a poll loop open for a
|
|
188
|
-
five-minute transcription burns the invocation and then dies without the
|
|
189
|
-
result. Start the job, return the id, and pick it up on the next request.
|
|
190
|
-
- **A request/response host has nowhere to put the wait.** An HTTP handler, an
|
|
191
|
-
MCP tool call and a queue consumer all want to hand back an id now and answer
|
|
192
|
-
later. That is what `oms tools ... --no-wait` and `oms tools status` are built
|
|
193
|
-
on.
|
|
194
|
-
- **The polling policy belongs in one place.** `jobs.wait` starts at
|
|
195
|
-
`pollIntervalMs`, backs off towards a ceiling, honours the caller's `signal`,
|
|
196
|
-
and gives up at `waitTimeoutMs`. No tool module opens a second loop.
|
|
197
|
-
|
|
198
|
-
Waiting resolves for **both** `"completed"` and `"failed"`: a failed job is an
|
|
186
|
+
`run` is `create` plus `jobs.wait`. Use `create` on hosts with a wall-clock
|
|
187
|
+
budget or nowhere to hold a wait: start the job, keep the id, pick it up later.
|
|
188
|
+
|
|
189
|
+
Waiting resolves for both `"completed"` and `"failed"`: a failed job is an
|
|
199
190
|
answer, not a transport error. Check the status before reading the result.
|
|
200
191
|
|
|
201
192
|
```ts
|
|
@@ -203,13 +194,11 @@ const job = await oms.jobs.wait({ id: started.job_id!, watchToken: started.watch
|
|
|
203
194
|
if (job.status === "failed") throw new Error(job.error ?? "the job failed");
|
|
204
195
|
```
|
|
205
196
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Compare against the constants, never against a literal you typed from memory.
|
|
197
|
+
A finished tool row says `status: "complete"`; a finished row in the job table
|
|
198
|
+
says `"completed"`; the downloader says `"done"`. Compare against the exported
|
|
199
|
+
constants, never against a literal.
|
|
210
200
|
|
|
211
|
-
Check the quota before starting something expensive
|
|
212
|
-
seconds of media for the audio and video tools, edits for jumpstyle:
|
|
201
|
+
Check the quota before starting something expensive:
|
|
213
202
|
|
|
214
203
|
```ts
|
|
215
204
|
const quota = await oms.tools.transcription.quota();
|
|
@@ -218,7 +207,7 @@ if (!quota.unlimited && (quota.remaining_seconds ?? 0) < 60) return;
|
|
|
218
207
|
|
|
219
208
|
## Errors
|
|
220
209
|
|
|
221
|
-
Every failure is an `OmsError` subclass carrying
|
|
210
|
+
Every failure is an `OmsError` subclass carrying what is needed to decide
|
|
222
211
|
between retrying, re-scoping and giving up.
|
|
223
212
|
|
|
224
213
|
```ts
|
|
@@ -230,7 +219,7 @@ try {
|
|
|
230
219
|
if (thrown instanceof OmsQuotaError) return retryAfter(thrown.retryAfterMs);
|
|
231
220
|
|
|
232
221
|
const missing = readInsufficientScope(thrown);
|
|
233
|
-
if (missing) return askForScopes(missing.scope);
|
|
222
|
+
if (missing) return askForScopes(missing.scope);
|
|
234
223
|
|
|
235
224
|
if (thrown instanceof OmsAuthError) return signInAgain();
|
|
236
225
|
if (thrown instanceof OmsApiError) log(thrown.status, thrown.fieldErrors);
|
|
@@ -240,49 +229,48 @@ try {
|
|
|
240
229
|
|
|
241
230
|
`OmsNetworkError` means the API was never reached; `OmsTimeoutError` with
|
|
242
231
|
`code === "aborted"` means your own `signal` fired. Retries are on by default
|
|
243
|
-
for idempotent requests with backoff and jitter.
|
|
244
|
-
create you would rather see fail than duplicate
|
|
245
|
-
a second one under a different endpoint.
|
|
232
|
+
for idempotent requests, with backoff and jitter. Pass `retry: false` to any
|
|
233
|
+
create you would rather see fail than duplicate.
|
|
246
234
|
|
|
247
|
-
##
|
|
235
|
+
## OAuth
|
|
248
236
|
|
|
249
|
-
The device grant, in full.
|
|
250
|
-
|
|
251
|
-
|
|
237
|
+
The device grant, in full. `/oauth/token` authenticates with `client_id` in
|
|
238
|
+
the form body; a stray `Authorization` header breaks it, so start from a
|
|
239
|
+
client with no token.
|
|
252
240
|
|
|
253
241
|
```ts
|
|
254
242
|
import { Oms, OAuthTokenProvider, decodeIdToken } from "@omelhorsite/sdk";
|
|
255
243
|
|
|
256
|
-
const anon = new Oms({ baseUrl, fetch });
|
|
244
|
+
const anon = new Oms({ baseUrl, fetch });
|
|
257
245
|
|
|
258
|
-
const grant = await anon.auth.device.start({ clientId
|
|
246
|
+
const grant = await anon.auth.device.start({ clientId, scope: "openid storage:read" });
|
|
259
247
|
show(grant.verificationUriComplete ?? grant.verificationUri, grant.userCode);
|
|
260
248
|
|
|
261
249
|
const set = await anon.auth.device.wait({
|
|
262
|
-
clientId
|
|
250
|
+
clientId,
|
|
263
251
|
deviceCode: grant.deviceCode,
|
|
264
252
|
intervalMs: grant.intervalMs,
|
|
265
253
|
expiresAt: grant.expiresAt,
|
|
266
254
|
});
|
|
267
255
|
|
|
268
256
|
const tokens = new OAuthTokenProvider({
|
|
269
|
-
store: myTokenStore,
|
|
270
|
-
refresh: (refreshToken) => anon.auth.refresh(refreshToken, { clientId
|
|
257
|
+
store: myTokenStore,
|
|
258
|
+
refresh: (refreshToken) => anon.auth.refresh(refreshToken, { clientId }),
|
|
271
259
|
});
|
|
272
260
|
await tokens.set(set);
|
|
273
261
|
|
|
274
262
|
const oms = new Oms({ baseUrl, fetch, tokens }); // refreshes itself on a 401
|
|
275
263
|
```
|
|
276
264
|
|
|
277
|
-
`decodeIdToken(set.idToken)` reads the claims.
|
|
278
|
-
and the only identifier safe to key on
|
|
265
|
+
`decodeIdToken(set.idToken)` reads the claims. `sub` is the user id, stable
|
|
266
|
+
and the only identifier safe to key on; the handle and the email are mutable.
|
|
279
267
|
|
|
280
|
-
Access tokens live two hours. `OMS_SCOPES` is the full list the server
|
|
281
|
-
ask for the narrowest set that does the job.
|
|
268
|
+
Access tokens live two hours. `OMS_SCOPES` is the full list the server
|
|
269
|
+
defines; ask for the narrowest set that does the job.
|
|
282
270
|
|
|
283
271
|
## Local helpers
|
|
284
272
|
|
|
285
|
-
No network, no credential
|
|
273
|
+
No network, no credential:
|
|
286
274
|
|
|
287
275
|
```ts
|
|
288
276
|
import { generatePassphrase, generatePassword, passwordStrength, qrToSvg } from "@omelhorsite/sdk";
|
|
@@ -294,29 +282,23 @@ const svg = qrToSvg("https://example.com");
|
|
|
294
282
|
|
|
295
283
|
## Testing against it
|
|
296
284
|
|
|
297
|
-
Inject a fetch. There is no global to stub
|
|
298
|
-
level:
|
|
285
|
+
Inject a fetch. There is no global to stub:
|
|
299
286
|
|
|
300
287
|
```ts
|
|
301
288
|
const oms = new Oms({
|
|
302
289
|
token: "test",
|
|
303
|
-
fetch: async (
|
|
290
|
+
fetch: async () => new Response(JSON.stringify({ id: "1" }), {
|
|
304
291
|
status: 200,
|
|
305
292
|
headers: { "content-type": "application/json" },
|
|
306
293
|
}),
|
|
307
294
|
});
|
|
308
295
|
```
|
|
309
296
|
|
|
297
|
+
## Developing
|
|
298
|
+
|
|
310
299
|
```sh
|
|
311
300
|
bun test
|
|
312
301
|
bun run typecheck
|
|
302
|
+
bun run check:isolate
|
|
303
|
+
bun run build
|
|
313
304
|
```
|
|
314
|
-
|
|
315
|
-
**After any change to `src/`, regenerate the MCP server's type catalogue:**
|
|
316
|
-
|
|
317
|
-
```sh
|
|
318
|
-
bun run --filter '@omelhorsite/mcp' build:types
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
The MCP server serves these declarations to models as its entire interface. A
|
|
322
|
-
stale catalogue means the model is reading a signature that no longer exists.
|