@omelhorsite/sdk 0.1.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 +321 -0
- package/dist/index.js +11589 -0
- package/dist/types/auth/device.d.ts +156 -0
- package/dist/types/auth/index.d.ts +127 -0
- package/dist/types/auth/tokens.d.ts +356 -0
- package/dist/types/client.d.ts +133 -0
- package/dist/types/errors.d.ts +202 -0
- package/dist/types/http.d.ts +204 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/local/index.d.ts +42 -0
- package/dist/types/local/password.d.ts +169 -0
- package/dist/types/local/qr.d.ts +127 -0
- package/dist/types/local/wordlist.d.ts +26 -0
- package/dist/types/resources/account.d.ts +296 -0
- package/dist/types/resources/chests.d.ts +194 -0
- package/dist/types/resources/dynamicQrs.d.ts +172 -0
- package/dist/types/resources/forms.d.ts +331 -0
- package/dist/types/resources/index.d.ts +30 -0
- package/dist/types/resources/ipLookup.d.ts +63 -0
- package/dist/types/resources/jobs.d.ts +233 -0
- package/dist/types/resources/linkTrees.d.ts +249 -0
- package/dist/types/resources/notepads.d.ts +96 -0
- package/dist/types/resources/shortLinks.d.ts +248 -0
- package/dist/types/resources/storage/upload.d.ts +459 -0
- package/dist/types/resources/storage.d.ts +527 -0
- package/dist/types/resources/tickets.d.ts +236 -0
- package/dist/types/resources/tools/backgroundRemoval.d.ts +99 -0
- package/dist/types/resources/tools/captions.d.ts +318 -0
- package/dist/types/resources/tools/downloader.d.ts +397 -0
- package/dist/types/resources/tools/index.d.ts +215 -0
- package/dist/types/resources/tools/jumpstyle.d.ts +194 -0
- package/dist/types/resources/tools/transcription.d.ts +178 -0
- package/dist/types/resources/tools/upscale.d.ts +94 -0
- package/dist/types/resources/tools/vocalSeparation.d.ts +183 -0
- package/dist/types/types.d.ts +245 -0
- package/package.json +37 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `forms` namespace: hosted forms and the submissions they collect.
|
|
3
|
+
*
|
|
4
|
+
* A form has an owner-facing side (create, edit the schema, read submissions)
|
|
5
|
+
* and a public side addressed by ENDPOINT rather than by id
|
|
6
|
+
* (`GET /forms/by_endpoint/:endpoint`, `POST /form_submissions`), which is what
|
|
7
|
+
* an anonymous respondent hits. The SDK exposes both; the public calls work
|
|
8
|
+
* without a credential unless the form turns `settings.require_login` on.
|
|
9
|
+
*
|
|
10
|
+
* The endpoint is not a column on the form: it lives on a short link paired
|
|
11
|
+
* with it, which is why renaming it is a real operation and why availability
|
|
12
|
+
* has its own lookup.
|
|
13
|
+
*/
|
|
14
|
+
import { type ApiClient, Resource } from "../http";
|
|
15
|
+
import type { BaseRecord, FileInput, Id, Json, RequestOptions, Timestamp } from "../types";
|
|
16
|
+
/**
|
|
17
|
+
* Publication state. Only `"published"` answers on the public side; `"draft"`
|
|
18
|
+
* and `"archived"` both 404 there.
|
|
19
|
+
*/
|
|
20
|
+
export type FormStatus = "draft" | "published" | "archived";
|
|
21
|
+
/** Kinds of field the builder understands. Anything else is dropped. */
|
|
22
|
+
export type FormSchemaFieldType = "short_text" | "long_text" | "email" | "number" | "single_choice" | "multi_choice" | "dropdown" | "statement" | "image";
|
|
23
|
+
/** One choice of a `single_choice`, `multi_choice` or `dropdown` field. */
|
|
24
|
+
export interface FormSchemaFieldOption {
|
|
25
|
+
/** Stable within the form. The server mints one when you leave it out. */
|
|
26
|
+
readonly id?: string;
|
|
27
|
+
readonly label: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* One field of a form.
|
|
31
|
+
*
|
|
32
|
+
* `id` is the key answers are filed under - NOT the label. Leave it out on
|
|
33
|
+
* create and the server mints a UUID, which you then have to read back before
|
|
34
|
+
* you can submit anything.
|
|
35
|
+
*/
|
|
36
|
+
export interface FormSchemaField {
|
|
37
|
+
readonly id?: string;
|
|
38
|
+
readonly type: FormSchemaFieldType;
|
|
39
|
+
readonly label: string;
|
|
40
|
+
readonly description?: string;
|
|
41
|
+
readonly required?: boolean;
|
|
42
|
+
readonly placeholder?: string;
|
|
43
|
+
/** Only meaningful on the three choice types. */
|
|
44
|
+
readonly options?: FormSchemaFieldOption[];
|
|
45
|
+
/** Only meaningful on `number`. */
|
|
46
|
+
readonly min?: number;
|
|
47
|
+
/** Only meaningful on `number`. */
|
|
48
|
+
readonly max?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The field definition of a form. The server rebuilds this from scratch on
|
|
52
|
+
* every write, keeping only the keys above, so anything extra is lost without
|
|
53
|
+
* a word.
|
|
54
|
+
*/
|
|
55
|
+
export interface FormSchema {
|
|
56
|
+
readonly fields: FormSchemaField[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Styling of a form. Colours must be `#RRGGBB`; images must be `data:image/`
|
|
60
|
+
* URIs under 2 MB. Anything else in here is dropped silently.
|
|
61
|
+
*/
|
|
62
|
+
export interface FormTheme {
|
|
63
|
+
readonly bg_color?: string;
|
|
64
|
+
readonly fg_color?: string;
|
|
65
|
+
readonly accent_color?: string;
|
|
66
|
+
readonly card_color?: string;
|
|
67
|
+
readonly mode?: "light" | "dark" | "custom";
|
|
68
|
+
readonly font?: "inter" | "cantarell" | "system";
|
|
69
|
+
/** `data:image/...` or `null` to clear. */
|
|
70
|
+
readonly bg_image?: string | null;
|
|
71
|
+
/** `data:image/...` or `null` to clear. */
|
|
72
|
+
readonly logo_image?: string | null;
|
|
73
|
+
}
|
|
74
|
+
/** Behaviour switches. Like the theme, unknown keys are dropped. */
|
|
75
|
+
export interface FormSettings {
|
|
76
|
+
readonly layout?: "one_per_screen" | "single_page";
|
|
77
|
+
/** Makes the public form and its submissions require a credential. */
|
|
78
|
+
readonly require_login?: boolean;
|
|
79
|
+
readonly show_progress?: boolean;
|
|
80
|
+
readonly collect_email?: boolean;
|
|
81
|
+
/** These five are truncated to 500 characters. */
|
|
82
|
+
readonly welcome_title?: string;
|
|
83
|
+
readonly welcome_subtitle?: string;
|
|
84
|
+
readonly submit_label?: string;
|
|
85
|
+
readonly thank_you_title?: string;
|
|
86
|
+
readonly thank_you_subtitle?: string;
|
|
87
|
+
}
|
|
88
|
+
/** A hosted form, owner view. */
|
|
89
|
+
export interface Form extends BaseRecord {
|
|
90
|
+
readonly user_id: Id;
|
|
91
|
+
readonly title: string;
|
|
92
|
+
readonly status: FormStatus;
|
|
93
|
+
readonly schema: FormSchema;
|
|
94
|
+
readonly theme: FormTheme;
|
|
95
|
+
readonly settings: FormSettings;
|
|
96
|
+
/** Public path segment, e.g. `"contacto"`. `null` if the pairing is broken. */
|
|
97
|
+
readonly endpoint: string | null;
|
|
98
|
+
/** Shareable short URL, or `null` while there is no endpoint. */
|
|
99
|
+
readonly published_url: string | null;
|
|
100
|
+
/** Set the first time the form is published, and never cleared. */
|
|
101
|
+
readonly published_at?: Timestamp | null;
|
|
102
|
+
readonly views_count: number;
|
|
103
|
+
readonly submissions_count: number;
|
|
104
|
+
}
|
|
105
|
+
/** The reduced form a public respondent is allowed to see. */
|
|
106
|
+
export interface PublicForm {
|
|
107
|
+
readonly id: Id;
|
|
108
|
+
readonly title: string;
|
|
109
|
+
readonly schema: FormSchema;
|
|
110
|
+
readonly theme: FormTheme;
|
|
111
|
+
readonly settings: FormSettings;
|
|
112
|
+
readonly endpoint: string;
|
|
113
|
+
readonly require_login: boolean;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* One answered form.
|
|
117
|
+
*
|
|
118
|
+
* Deliberately has no `updated_at`: a submission is never edited, and the
|
|
119
|
+
* blueprint leaves the field out rather than render a lie.
|
|
120
|
+
*/
|
|
121
|
+
export interface FormSubmission {
|
|
122
|
+
readonly id: Id;
|
|
123
|
+
readonly form_id: Id;
|
|
124
|
+
/** The respondent, when they were signed in. */
|
|
125
|
+
readonly user_id?: Id | null;
|
|
126
|
+
/**
|
|
127
|
+
* Answers keyed by {@link FormSchemaField.id}. An `image` answer is
|
|
128
|
+
* `{ attachment_id, filename }`; a `number` is a float; a `multi_choice` is
|
|
129
|
+
* an array of strings; everything else is a string.
|
|
130
|
+
*/
|
|
131
|
+
readonly answers: Record<string, Json>;
|
|
132
|
+
/** Resolved from the respondent's IP. */
|
|
133
|
+
readonly country?: string | null;
|
|
134
|
+
/** Parsed from the respondent's user agent. */
|
|
135
|
+
readonly device_name?: string | null;
|
|
136
|
+
readonly completed_at?: Timestamp | null;
|
|
137
|
+
readonly created_at: Timestamp;
|
|
138
|
+
}
|
|
139
|
+
/** What `POST /form_attachments` answers with. */
|
|
140
|
+
export interface FormAttachment {
|
|
141
|
+
readonly id: Id;
|
|
142
|
+
readonly filename: string;
|
|
143
|
+
readonly content_type: string;
|
|
144
|
+
/** Absolute URL that serves the bytes inline, no credential required. */
|
|
145
|
+
readonly url: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* `GET /forms/endpoint_availability`. Read `available`; `suggestions` is only
|
|
149
|
+
* present when the endpoint is well formed but taken.
|
|
150
|
+
*/
|
|
151
|
+
export interface FormEndpointAvailability {
|
|
152
|
+
readonly endpoint: string;
|
|
153
|
+
/** Whether it matches the format and is not reserved. */
|
|
154
|
+
readonly valid: boolean;
|
|
155
|
+
readonly available: boolean;
|
|
156
|
+
/** `"invalid"` when the format or the reserved list rejected it. */
|
|
157
|
+
readonly reason?: string;
|
|
158
|
+
readonly suggestions?: string[];
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Arguments for creating a form.
|
|
162
|
+
*
|
|
163
|
+
* The form always starts as a `"draft"`: `status` is not writable here, only
|
|
164
|
+
* through {@link UpdateFormInput}. `require_login` lives in `settings`.
|
|
165
|
+
*/
|
|
166
|
+
export interface CreateFormInput {
|
|
167
|
+
readonly title: string;
|
|
168
|
+
/**
|
|
169
|
+
* Public path segment. Lowercased, 1 to 64 characters of `[a-z0-9_-]`
|
|
170
|
+
* starting and ending alphanumeric, and never one of `new create index
|
|
171
|
+
* admin api login signup help`.
|
|
172
|
+
*/
|
|
173
|
+
readonly endpoint: string;
|
|
174
|
+
readonly schema?: FormSchema;
|
|
175
|
+
readonly theme?: FormTheme;
|
|
176
|
+
readonly settings?: FormSettings;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Fields that can change afterwards. A true PATCH: only the keys you pass are
|
|
180
|
+
* touched, and each of `schema`, `theme` and `settings` is replaced wholesale
|
|
181
|
+
* rather than merged.
|
|
182
|
+
*/
|
|
183
|
+
export interface UpdateFormInput {
|
|
184
|
+
readonly title?: string;
|
|
185
|
+
/** Renames the paired short link, so the old public URL stops working. */
|
|
186
|
+
readonly endpoint?: string;
|
|
187
|
+
readonly schema?: FormSchema;
|
|
188
|
+
readonly theme?: FormTheme;
|
|
189
|
+
readonly settings?: FormSettings;
|
|
190
|
+
/**
|
|
191
|
+
* An unrecognised status is IGNORED in silence and still answers 200 with
|
|
192
|
+
* the old value, so read the returned form rather than trusting the code.
|
|
193
|
+
*/
|
|
194
|
+
readonly status?: FormStatus;
|
|
195
|
+
}
|
|
196
|
+
/** Arguments for answering a form. */
|
|
197
|
+
export interface SubmitFormInput {
|
|
198
|
+
/** The form's public endpoint, not its id. */
|
|
199
|
+
readonly endpoint: string;
|
|
200
|
+
/**
|
|
201
|
+
* Answers keyed by {@link FormSchemaField.id}. Keys that are not in the
|
|
202
|
+
* schema are discarded in silence, so send ids, never labels.
|
|
203
|
+
*/
|
|
204
|
+
readonly answers: Record<string, Json>;
|
|
205
|
+
/**
|
|
206
|
+
* Files for `image` fields, keyed by the same field id. The SDK uploads each
|
|
207
|
+
* one to `POST /form_attachments` first and folds the resulting
|
|
208
|
+
* `{ attachment_id }` into `answers`, which is the shape the endpoint reads.
|
|
209
|
+
*
|
|
210
|
+
* Authenticated callers, in practice. Each of those uploads is a separate
|
|
211
|
+
* request an anonymous caller has to pass a captcha for, and a Turnstile
|
|
212
|
+
* token is single-use: one token cannot cover both an upload and the
|
|
213
|
+
* submission. An anonymous caller uploads each file through
|
|
214
|
+
* {@link FormSubmissionsNamespace.uploadAttachment} with its own fresh
|
|
215
|
+
* token, then writes `{ attachment_id }` into `answers` by hand.
|
|
216
|
+
*/
|
|
217
|
+
readonly files?: Record<string, FileInput>;
|
|
218
|
+
/**
|
|
219
|
+
* Turnstile token. Required while the caller is anonymous, ignored
|
|
220
|
+
* otherwise. Single-use: one token pays for one request.
|
|
221
|
+
*/
|
|
222
|
+
readonly captchaToken?: string;
|
|
223
|
+
}
|
|
224
|
+
/** Arguments for staging one file against a public form. */
|
|
225
|
+
export interface UploadFormAttachmentInput {
|
|
226
|
+
/** The form's public endpoint. */
|
|
227
|
+
readonly endpoint: string;
|
|
228
|
+
/** JPEG, PNG, WebP, GIF or HEIC, up to 8 MB. */
|
|
229
|
+
readonly file: FileInput;
|
|
230
|
+
/**
|
|
231
|
+
* Turnstile token. Required while the caller is anonymous, ignored
|
|
232
|
+
* otherwise. Single-use: one token pays for one upload.
|
|
233
|
+
*/
|
|
234
|
+
readonly captchaToken?: string;
|
|
235
|
+
}
|
|
236
|
+
/** Filters for {@link FormSubmissionsNamespace.list}. */
|
|
237
|
+
export interface ListFormSubmissionsParams {
|
|
238
|
+
readonly formId: Id;
|
|
239
|
+
}
|
|
240
|
+
/** Submissions of a form, reachable as `oms.forms.submissions`. */
|
|
241
|
+
export declare class FormSubmissionsNamespace extends Resource {
|
|
242
|
+
/**
|
|
243
|
+
* `GET /form_submissions?form_id=...` - the answers, newest first. Owner
|
|
244
|
+
* only.
|
|
245
|
+
*
|
|
246
|
+
* Not paginated: the endpoint takes no page modifier and hard-caps itself at
|
|
247
|
+
* the 500 most recent, so a busy form silently loses its tail. That is why
|
|
248
|
+
* this returns an array rather than a `Paginated` that could never advance.
|
|
249
|
+
*/
|
|
250
|
+
list(params: ListFormSubmissionsParams, options?: RequestOptions): Promise<FormSubmission[]>;
|
|
251
|
+
/**
|
|
252
|
+
* `POST /form_submissions` - answers a published form.
|
|
253
|
+
*
|
|
254
|
+
* Anonymous unless the form sets `settings.require_login`, in which case an
|
|
255
|
+
* anonymous caller gets 401. Answers are coerced to the type each field
|
|
256
|
+
* declares and the whole payload is capped at 200 000 bytes of JSON.
|
|
257
|
+
*
|
|
258
|
+
* Answers only the new submission's id: the server does not echo the record
|
|
259
|
+
* back, and the respondent has no permission to read it afterwards.
|
|
260
|
+
*
|
|
261
|
+
* Not retried by default: a replayed submission would be recorded twice.
|
|
262
|
+
*/
|
|
263
|
+
create(input: SubmitFormInput, options?: RequestOptions): Promise<{
|
|
264
|
+
id: Id;
|
|
265
|
+
}>;
|
|
266
|
+
/**
|
|
267
|
+
* `POST /form_attachments` - stages one image against a published form and
|
|
268
|
+
* hands back the id an `image` answer refers to.
|
|
269
|
+
*
|
|
270
|
+
* Called for you by {@link create} when you pass `files`; reach for it
|
|
271
|
+
* directly only to upload before the rest of the answers are ready.
|
|
272
|
+
* Throttled to 30 an hour per IP for anonymous callers.
|
|
273
|
+
*/
|
|
274
|
+
uploadAttachment(input: UploadFormAttachmentInput, options?: RequestOptions): Promise<FormAttachment>;
|
|
275
|
+
}
|
|
276
|
+
/** The `forms` namespace, reachable as `oms.forms`. */
|
|
277
|
+
export declare class FormsNamespace extends Resource {
|
|
278
|
+
/** Answers to a form, and the files staged against one. */
|
|
279
|
+
readonly submissions: FormSubmissionsNamespace;
|
|
280
|
+
constructor(http: ApiClient);
|
|
281
|
+
/**
|
|
282
|
+
* `GET /forms` - the forms you own, most recently edited first, each with
|
|
283
|
+
* its submission count.
|
|
284
|
+
*
|
|
285
|
+
* Not paginated and not filterable: the endpoint takes no modifiers and
|
|
286
|
+
* answers with the whole set.
|
|
287
|
+
*/
|
|
288
|
+
list(options?: RequestOptions): Promise<Form[]>;
|
|
289
|
+
/** `GET /forms/:id` - the full definition, owner view. */
|
|
290
|
+
get(id: Id, options?: RequestOptions): Promise<Form>;
|
|
291
|
+
/**
|
|
292
|
+
* `GET /forms/by_endpoint/:endpoint` - the respondent's view. Answers only
|
|
293
|
+
* for a published form; a draft is a 404.
|
|
294
|
+
*
|
|
295
|
+
* Every call INCREMENTS the form's `views_count`. Do not use it to poll for
|
|
296
|
+
* changes, and do not call it on behalf of the owner: use {@link get}.
|
|
297
|
+
*
|
|
298
|
+
* @throws {OmsAuthError} 401 when the form requires a login and there is none.
|
|
299
|
+
*/
|
|
300
|
+
getPublic(endpoint: string, options?: RequestOptions): Promise<PublicForm>;
|
|
301
|
+
/**
|
|
302
|
+
* `GET /forms/endpoint_availability` - whether an endpoint is free, with
|
|
303
|
+
* alternatives when it is not. The server is still the authority: create can
|
|
304
|
+
* lose a race and answer 400.
|
|
305
|
+
*/
|
|
306
|
+
endpointAvailability(endpoint: string, options?: RequestOptions): Promise<FormEndpointAvailability>;
|
|
307
|
+
/** {@link endpointAvailability} reduced to its verdict. */
|
|
308
|
+
endpointAvailable(endpoint: string, options?: RequestOptions): Promise<boolean>;
|
|
309
|
+
/**
|
|
310
|
+
* `POST /forms` - creates the form and reserves its endpoint in one
|
|
311
|
+
* transaction. The form starts as a draft; publish it with
|
|
312
|
+
* {@link update}.
|
|
313
|
+
*
|
|
314
|
+
* Not retried by default: a replayed create would fail on the endpoint being
|
|
315
|
+
* taken by its own first attempt.
|
|
316
|
+
*/
|
|
317
|
+
create(input: CreateFormInput, options?: RequestOptions): Promise<Form>;
|
|
318
|
+
/** `PATCH /forms/:id`. Only the keys you pass are touched. */
|
|
319
|
+
update(id: Id, input: UpdateFormInput, options?: RequestOptions): Promise<Form>;
|
|
320
|
+
/** Convenience over {@link update} with `status: "published"`. */
|
|
321
|
+
publish(id: Id, options?: RequestOptions): Promise<Form>;
|
|
322
|
+
/** `DELETE /forms/:id`. Takes the submissions and the endpoint with it. */
|
|
323
|
+
delete(id: Id, options?: RequestOptions): Promise<void>;
|
|
324
|
+
/**
|
|
325
|
+
* `GET /form_attachments/:id` - one file a respondent uploaded.
|
|
326
|
+
*
|
|
327
|
+
* Served inline with `X-Content-Type-Options: nosniff`, and readable by
|
|
328
|
+
* anyone holding the id.
|
|
329
|
+
*/
|
|
330
|
+
attachment(attachmentId: Id, options?: RequestOptions): Promise<Blob>;
|
|
331
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every resource namespace, in one place.
|
|
3
|
+
*
|
|
4
|
+
* `client.ts` imports the namespace classes from here indirectly (it imports
|
|
5
|
+
* the modules themselves, so a namespace can be dropped in without editing an
|
|
6
|
+
* aggregator), and consumers import the record and input types from here.
|
|
7
|
+
*
|
|
8
|
+
* Re-exports every sibling module so nobody has to touch this file again: a new
|
|
9
|
+
* resource means one new file plus one line in {@link Oms}, and no edit to a
|
|
10
|
+
* file another agent may also be holding.
|
|
11
|
+
*
|
|
12
|
+
* Naming rule that keeps `export *` unambiguous: every exported name is
|
|
13
|
+
* prefixed by its domain (`ShortLink`, `ShortLinkStats`, `CreateShortLinkInput`).
|
|
14
|
+
* A bare `Stats` or `CreateInput` would collide the moment a second resource
|
|
15
|
+
* wanted one, and TypeScript reports that as an error at this file, not at the
|
|
16
|
+
* file that caused it.
|
|
17
|
+
*/
|
|
18
|
+
export * from "./account";
|
|
19
|
+
export * from "./chests";
|
|
20
|
+
export * from "./dynamicQrs";
|
|
21
|
+
export * from "./forms";
|
|
22
|
+
export * from "./ipLookup";
|
|
23
|
+
export * from "./jobs";
|
|
24
|
+
export * from "./linkTrees";
|
|
25
|
+
export * from "./notepads";
|
|
26
|
+
export * from "./shortLinks";
|
|
27
|
+
export * from "./storage";
|
|
28
|
+
export * from "./storage/upload";
|
|
29
|
+
export * from "./tickets";
|
|
30
|
+
export * from "./tools/index";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `ipLookup` namespace: network metadata for an IP address.
|
|
3
|
+
*
|
|
4
|
+
* Works anonymously. The special argument `"mine"` asks the backend what IP it
|
|
5
|
+
* sees the caller coming from, which is the only way for a client behind NAT
|
|
6
|
+
* or a proxy to learn its own public address.
|
|
7
|
+
*
|
|
8
|
+
* The name promises more than the service delivers, and it is worth knowing
|
|
9
|
+
* before you build a UI on it: the database behind `IpLookuper` is built by
|
|
10
|
+
* `backend/bin/create_mmdb` from the iptoasn.com IP-to-ASN table, NOT from a
|
|
11
|
+
* GeoIP City database. It knows the country, the autonomous system and the
|
|
12
|
+
* network - it does not know the city, the coordinates or the timezone, and no
|
|
13
|
+
* amount of asking will make those appear.
|
|
14
|
+
*/
|
|
15
|
+
import { Resource } from "../http";
|
|
16
|
+
import type { RequestOptions } from "../types";
|
|
17
|
+
/**
|
|
18
|
+
* What the lookup service knows about an address.
|
|
19
|
+
*
|
|
20
|
+
* Every field comes straight out of the MMDB record, so the shape is exactly
|
|
21
|
+
* what the backend read: no field is computed and none is filled in for a
|
|
22
|
+
* missing row - a miss is a 400, not a half-empty object.
|
|
23
|
+
*/
|
|
24
|
+
export interface IpLookupResult {
|
|
25
|
+
/** The address that was looked up, echoed back. For `"mine"`, the resolved one. */
|
|
26
|
+
readonly ip: string;
|
|
27
|
+
/**
|
|
28
|
+
* ISO 3166-1 alpha-2, **lowercase** (`"pt"`, `"us"`), or the literal
|
|
29
|
+
* `"unknown"` when the source table had no country for the range. Uppercase
|
|
30
|
+
* it yourself before feeding it to a flag or a locale lookup.
|
|
31
|
+
*/
|
|
32
|
+
readonly country: string;
|
|
33
|
+
/** Autonomous system number, or `0` when the range is not announced. */
|
|
34
|
+
readonly asn: number;
|
|
35
|
+
/** Network operator name as the AS registry spells it, e.g. `"GOOGLE"`. */
|
|
36
|
+
readonly organization: string;
|
|
37
|
+
/** The CIDR block the address fell in, e.g. `"8.8.8.0/24"`. */
|
|
38
|
+
readonly network: string;
|
|
39
|
+
}
|
|
40
|
+
/** The `ipLookup` namespace, reachable as `oms.ipLookup`. */
|
|
41
|
+
export declare class IpLookupNamespace extends Resource {
|
|
42
|
+
/**
|
|
43
|
+
* `GET /ip_lookup/:ip` - looks up any IPv4 or IPv6 address.
|
|
44
|
+
*
|
|
45
|
+
* Anonymous. Counts against the general anonymous ceiling (120/min per IP)
|
|
46
|
+
* or the authenticated one (600/min per session) when a token is set.
|
|
47
|
+
*
|
|
48
|
+
* @param ip The address, or the literal `"mine"` (prefer {@link mine} for
|
|
49
|
+
* that, it reads better at the call site).
|
|
50
|
+
* @throws {OmsApiError} 400 `Invalid IP address` when the argument does not
|
|
51
|
+
* parse as an address, and also when it parses but the database has no row
|
|
52
|
+
* for it. The two cases are not distinguishable from the response.
|
|
53
|
+
*/
|
|
54
|
+
get(ip: string, options?: RequestOptions): Promise<IpLookupResult>;
|
|
55
|
+
/**
|
|
56
|
+
* `GET /ip_lookup/mine` - the public address the backend sees this client
|
|
57
|
+
* arriving from.
|
|
58
|
+
*
|
|
59
|
+
* Behind Cloudflare the backend reads `CF-Connecting-IP`, so this is the
|
|
60
|
+
* client's real address, not the edge's.
|
|
61
|
+
*/
|
|
62
|
+
mine(options?: RequestOptions): Promise<IpLookupResult>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `jobs` namespace: the one place a background job is polled.
|
|
3
|
+
*
|
|
4
|
+
* Every asynchronous tool (upscale, background removal, transcription, vocal
|
|
5
|
+
* separation, captions, jumpstyle) enqueues a job and hands back a `job_id`
|
|
6
|
+
* plus, for an anonymous caller, a `watch_token`. That token grants read access
|
|
7
|
+
* to exactly one job and is the reason `GET /jobs/:id` is reachable without a
|
|
8
|
+
* credential.
|
|
9
|
+
*
|
|
10
|
+
* Every tool namespace that returns a job delegates its `wait()` to
|
|
11
|
+
* {@link JobsNamespace.wait}, so the polling policy lives here once. Do not
|
|
12
|
+
* write a second polling loop inside a tool module.
|
|
13
|
+
*
|
|
14
|
+
* Only two tools enqueue through the generic `jobs` table - background removal
|
|
15
|
+
* and upscale. The other five are polled by re-reading their own row, which is
|
|
16
|
+
* still not a reason to write a loop there: {@link pollUntilTerminal} is the
|
|
17
|
+
* same engine with a different `poll` function, and that is what those modules
|
|
18
|
+
* call.
|
|
19
|
+
*
|
|
20
|
+
* The loop is deliberately dumb and bounded:
|
|
21
|
+
*
|
|
22
|
+
* - the first poll happens immediately, with no initial pause;
|
|
23
|
+
* - each pause is {@link POLL_BACKOFF_FACTOR} times the last, capped at
|
|
24
|
+
* {@link MAX_POLL_INTERVAL_MS}, so a five-second job costs a handful of
|
|
25
|
+
* requests and a ninety-minute one does not cost eleven thousand;
|
|
26
|
+
* - there is no jitter. A poll loop is one client watching one job; the place a
|
|
27
|
+
* herd actually forms is a 429, and `Retry-After` is honoured by the
|
|
28
|
+
* transport, which is where that belongs;
|
|
29
|
+
* - `waitTimeoutMs` is the caller's deadline and has NO default. A job that the
|
|
30
|
+
* server never terminalises would otherwise hang an isolate forever, so pass
|
|
31
|
+
* one - or a `signal` - whenever the caller is not a person watching a
|
|
32
|
+
* terminal.
|
|
33
|
+
*
|
|
34
|
+
* Three outcomes, and they are different types on purpose:
|
|
35
|
+
*
|
|
36
|
+
* | What happened | How it arrives |
|
|
37
|
+
* |---|---|
|
|
38
|
+
* | The work finished, well or badly | resolves with a {@link Job}; read `status` |
|
|
39
|
+
* | The client gave up first | throws {@link OmsTimeoutError} with `code: "timeout"` |
|
|
40
|
+
* | The caller aborted | throws {@link OmsTimeoutError} with `code: "aborted"` |
|
|
41
|
+
*
|
|
42
|
+
* A job that ends `"failed"` is an ANSWER, not a transport error: the request
|
|
43
|
+
* cycle worked perfectly and the work did not. Only the caller knows whether
|
|
44
|
+
* that deserves an exception.
|
|
45
|
+
*/
|
|
46
|
+
import { Resource } from "../http";
|
|
47
|
+
import type { BaseRecord, Id, Json, JobStatus, Paginated, PageParams, Progress, RequestOptions, Timestamp, WaitOptions } from "../types";
|
|
48
|
+
/**
|
|
49
|
+
* The five status strings, spelled the way the backend spells them.
|
|
50
|
+
*
|
|
51
|
+
* `complete`, not `completed`. `canceled`, one L. Reach for this object instead
|
|
52
|
+
* of typing the literal: a loop that waits for `"completed"` waits forever.
|
|
53
|
+
*/
|
|
54
|
+
export declare const JOB_STATUS: Readonly<{
|
|
55
|
+
readonly pending: "pending";
|
|
56
|
+
readonly processing: "processing";
|
|
57
|
+
readonly complete: "complete";
|
|
58
|
+
readonly failed: "failed";
|
|
59
|
+
readonly canceled: "canceled";
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Statuses a job never leaves.
|
|
63
|
+
*
|
|
64
|
+
* Note `canceled` is here and has no equivalent on a tool row: a job can be
|
|
65
|
+
* cancelled out from under a tool whose own row is still `"pending"`.
|
|
66
|
+
*/
|
|
67
|
+
export declare const JOB_TERMINAL_STATUSES: readonly JobStatus[];
|
|
68
|
+
/** True once this status can never change again. */
|
|
69
|
+
export declare function isJobTerminal(status: string): boolean;
|
|
70
|
+
/** A background job. */
|
|
71
|
+
export interface Job extends BaseRecord {
|
|
72
|
+
readonly status: JobStatus;
|
|
73
|
+
/**
|
|
74
|
+
* Feature-level kind of the run. Today the backend only ever writes
|
|
75
|
+
* `"omsvs"` (vocal separation) or `"unknown"`, which is the column default
|
|
76
|
+
* every generic enqueue gets - including the upscale and background-removal
|
|
77
|
+
* proxies. It is NOT the worker's class name.
|
|
78
|
+
*/
|
|
79
|
+
readonly job_type: string;
|
|
80
|
+
/** Enqueue-time arguments, when the enqueuer wrote any. Shape depends on `job_type`. */
|
|
81
|
+
readonly payload?: Json;
|
|
82
|
+
/** Set when a worker claimed the job. */
|
|
83
|
+
readonly started_at?: Timestamp | null;
|
|
84
|
+
readonly finished_at?: Timestamp | null;
|
|
85
|
+
/**
|
|
86
|
+
* Percentage, an integer in `[0, 100]`. The column is `NOT NULL DEFAULT 0`,
|
|
87
|
+
* so it is a real number from the moment the row exists and 0 means "not
|
|
88
|
+
* started", never "unknown".
|
|
89
|
+
*/
|
|
90
|
+
readonly progress?: number | null;
|
|
91
|
+
/** Failure message, set once `status === "failed"` (or a cancellation reason). */
|
|
92
|
+
readonly error?: string | null;
|
|
93
|
+
/** Worker-specific payload; shape depends on `job_type`. */
|
|
94
|
+
readonly result?: Json;
|
|
95
|
+
/** Who enqueued it. `null` for a job with no owner, e.g. an anonymous tool run. */
|
|
96
|
+
readonly creator_id?: Id | null;
|
|
97
|
+
/** Which worker claimed it. */
|
|
98
|
+
readonly worker_id?: string | null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* How to address a job.
|
|
102
|
+
*
|
|
103
|
+
* An authenticated caller needs only the id. An anonymous one must also present
|
|
104
|
+
* the `watchToken` the tool handed back at enqueue time.
|
|
105
|
+
*/
|
|
106
|
+
export interface JobRef {
|
|
107
|
+
readonly id: Id;
|
|
108
|
+
/** Signed token scoped to this one job. Required when anonymous. */
|
|
109
|
+
readonly watchToken?: string;
|
|
110
|
+
}
|
|
111
|
+
/** Filters for {@link JobsNamespace.list}. */
|
|
112
|
+
export interface ListJobsParams extends PageParams {
|
|
113
|
+
readonly status?: JobStatus | JobStatus[];
|
|
114
|
+
readonly jobType?: string | string[];
|
|
115
|
+
}
|
|
116
|
+
/** Pause before the second poll, in milliseconds. */
|
|
117
|
+
export declare const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
118
|
+
/** Ceiling for one pause, in milliseconds. */
|
|
119
|
+
export declare const MAX_POLL_INTERVAL_MS = 15000;
|
|
120
|
+
/** Each pause is this many times the last one, until the ceiling. */
|
|
121
|
+
export declare const POLL_BACKOFF_FACTOR = 1.5;
|
|
122
|
+
/**
|
|
123
|
+
* Everything {@link pollUntilTerminal} needs to watch something finish.
|
|
124
|
+
*
|
|
125
|
+
* Generic over the record because the seven tools are polled two different
|
|
126
|
+
* ways - two through `GET /jobs/:id`, five through their own `show` - and only
|
|
127
|
+
* `poll` and `terminal` differ between them. Nothing else about the loop does,
|
|
128
|
+
* which is exactly why there is one loop.
|
|
129
|
+
*/
|
|
130
|
+
export interface PollUntilTerminalOptions<T> extends WaitOptions {
|
|
131
|
+
/**
|
|
132
|
+
* Reads the current state. Called once immediately, then again after every
|
|
133
|
+
* pause. It is handed the request-shaped half of these options (signal,
|
|
134
|
+
* per-request timeout, headers, retry), never the wait-shaped half.
|
|
135
|
+
*/
|
|
136
|
+
readonly poll: (options: RequestOptions) => Promise<T>;
|
|
137
|
+
/** True once `state` can never change again. */
|
|
138
|
+
readonly terminal: (state: T) => boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Maps a state onto a {@link Progress} for `onProgress`. Its `status` is also
|
|
141
|
+
* what the timeout message quotes, so a run that gave up says what it was
|
|
142
|
+
* last doing.
|
|
143
|
+
*/
|
|
144
|
+
readonly progress?: (state: T) => Progress;
|
|
145
|
+
/** What is being waited on, for the timeout message: `"job 3f2a"`. */
|
|
146
|
+
readonly label?: string;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Polls until `terminal` says so, then returns the final state.
|
|
150
|
+
*
|
|
151
|
+
* This is THE polling loop of the SDK. A resource module supplies `poll` and
|
|
152
|
+
* `terminal`; it does not supply a `while`.
|
|
153
|
+
*
|
|
154
|
+
* @throws {OmsTimeoutError} `code: "timeout"` when `waitTimeoutMs` elapsed,
|
|
155
|
+
* `code: "aborted"` when the caller's signal fired.
|
|
156
|
+
* @throws {OmsApiError} whatever `poll` throws - a 404 once a finished job's
|
|
157
|
+
* 24-hour retention expires, a 401 for an anonymous caller with no watch
|
|
158
|
+
* token. A polling loop is not the place to swallow those.
|
|
159
|
+
*/
|
|
160
|
+
export declare function pollUntilTerminal<T>(options: PollUntilTerminalOptions<T>): Promise<T>;
|
|
161
|
+
/**
|
|
162
|
+
* Same loop as {@link pollUntilTerminal}, but yields every state it observes -
|
|
163
|
+
* including the terminal one, which is also the generator's return value.
|
|
164
|
+
*
|
|
165
|
+
* For a host that would rather render each step than take a callback. Breaking
|
|
166
|
+
* out of the `for await` stops the loop; nothing is left running.
|
|
167
|
+
*/
|
|
168
|
+
export declare function watchUntilTerminal<T>(options: PollUntilTerminalOptions<T>): AsyncGenerator<T, T, undefined>;
|
|
169
|
+
/** The `jobs` namespace, reachable as `oms.jobs`. */
|
|
170
|
+
export declare class JobsNamespace extends Resource {
|
|
171
|
+
/**
|
|
172
|
+
* `GET /jobs` - your jobs. Requires a credential; a watch token cannot list.
|
|
173
|
+
*
|
|
174
|
+
* An admin sees every job, a signed-in user sees the ones they created, and
|
|
175
|
+
* an anonymous caller sees an empty page - never a 401, because the scope is
|
|
176
|
+
* empty rather than forbidden.
|
|
177
|
+
*
|
|
178
|
+
* @throws {OmsApiError} 400 naming the key when a filter is not on the
|
|
179
|
+
* controller's allowlist (`id`, `job_type`, `status`, `created_at`,
|
|
180
|
+
* `updated_at`, `finished_at`).
|
|
181
|
+
*/
|
|
182
|
+
list(params?: ListJobsParams, options?: RequestOptions): Promise<Paginated<Job>>;
|
|
183
|
+
/**
|
|
184
|
+
* `GET /jobs/:id` - one poll, no waiting.
|
|
185
|
+
*
|
|
186
|
+
* A `watchToken` is sent as `?watch_token=`, which is the only way an
|
|
187
|
+
* anonymous caller reaches a job. The server checks the signature resolves to
|
|
188
|
+
* the job named in the path, so the token cannot be pointed at another id.
|
|
189
|
+
*
|
|
190
|
+
* @throws {OmsApiError} 404 when the job is gone, which for a finished job
|
|
191
|
+
* also happens once its retention window expires. A wrong, expired or
|
|
192
|
+
* missing watch token is the same 404, not a 401: the controller never says
|
|
193
|
+
* whether the id exists.
|
|
194
|
+
*/
|
|
195
|
+
get(ref: JobRef | Id, options?: RequestOptions): Promise<Job>;
|
|
196
|
+
/**
|
|
197
|
+
* Polls until the job reaches a terminal state, then returns it.
|
|
198
|
+
*
|
|
199
|
+
* The polling policy belongs here, not in the callers: start at
|
|
200
|
+
* `pollIntervalMs`, back off towards a ceiling, stop at `waitTimeoutMs`, and
|
|
201
|
+
* abort immediately if the caller's `signal` fires. `onProgress` is called on
|
|
202
|
+
* every poll so a host can drive a spinner.
|
|
203
|
+
*
|
|
204
|
+
* Resolves for `"complete"`, `"failed"` AND `"canceled"` - a job that ended
|
|
205
|
+
* badly is an answer, not a transport error. Check `job.status` before
|
|
206
|
+
* reading `job.result`.
|
|
207
|
+
*
|
|
208
|
+
* @throws {OmsTimeoutError} `code: "timeout"` when `waitTimeoutMs` elapses
|
|
209
|
+
* first, `code: "aborted"` when the caller's signal fires.
|
|
210
|
+
*/
|
|
211
|
+
wait(ref: JobRef | Id, options?: WaitOptions): Promise<Job>;
|
|
212
|
+
/**
|
|
213
|
+
* Yields the job's state on every poll until it finishes, for a host that
|
|
214
|
+
* wants to render each step rather than take a callback.
|
|
215
|
+
*
|
|
216
|
+
* The terminal state is both the last value yielded and the generator's
|
|
217
|
+
* return value, so neither `for await` nor a manual `next()` loop can miss
|
|
218
|
+
* it.
|
|
219
|
+
*/
|
|
220
|
+
watch(ref: JobRef | Id, options?: WaitOptions): AsyncGenerator<Job, Job, undefined>;
|
|
221
|
+
/** The one description of "watching a job", shared by `wait` and `watch`. */
|
|
222
|
+
private pollPlan;
|
|
223
|
+
}
|
|
224
|
+
/** Normalises the two ways a job can be addressed into the object form. */
|
|
225
|
+
export declare function jobRef(ref: JobRef | Id): JobRef;
|
|
226
|
+
/**
|
|
227
|
+
* Renders a job as a {@link Progress}.
|
|
228
|
+
*
|
|
229
|
+
* `total` is 100 rather than `undefined` because `progress` is a percentage the
|
|
230
|
+
* server always has: the column is `NOT NULL DEFAULT 0`, so there is no
|
|
231
|
+
* "unknown" to be honest about.
|
|
232
|
+
*/
|
|
233
|
+
export declare function jobProgress(job: Job): Progress;
|