@omelhorsite/sdk 0.2.0 → 0.3.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 (44) hide show
  1. package/dist/index.js +4939 -552
  2. package/dist/types/client.d.ts +60 -3
  3. package/dist/types/http.d.ts +444 -19
  4. package/dist/types/index.d.ts +4 -1
  5. package/dist/types/resources/account.d.ts +66 -3
  6. package/dist/types/resources/admin.d.ts +1837 -0
  7. package/dist/types/resources/auth/index.d.ts +39 -0
  8. package/dist/types/resources/auth/passkeys.d.ts +652 -0
  9. package/dist/types/resources/auth/sessions.d.ts +847 -0
  10. package/dist/types/resources/chests.d.ts +54 -3
  11. package/dist/types/resources/content.d.ts +2970 -0
  12. package/dist/types/resources/dynamicQrs.d.ts +39 -3
  13. package/dist/types/resources/forms.d.ts +176 -35
  14. package/dist/types/resources/index.d.ts +19 -8
  15. package/dist/types/resources/ipLookup.d.ts +20 -4
  16. package/dist/types/resources/jobs.d.ts +62 -21
  17. package/dist/types/resources/library.d.ts +1435 -0
  18. package/dist/types/resources/linkTrees.d.ts +142 -30
  19. package/dist/types/resources/media.d.ts +351 -0
  20. package/dist/types/resources/movies.d.ts +1186 -0
  21. package/dist/types/resources/music/artists.d.ts +1066 -0
  22. package/dist/types/resources/music/imports.d.ts +940 -0
  23. package/dist/types/resources/music/index.d.ts +61 -0
  24. package/dist/types/resources/music/playlists.d.ts +1026 -0
  25. package/dist/types/resources/music/social.d.ts +1132 -0
  26. package/dist/types/resources/music/songs.d.ts +1183 -0
  27. package/dist/types/resources/notepads.d.ts +4 -1
  28. package/dist/types/resources/quotas.d.ts +7 -1
  29. package/dist/types/resources/realtime.d.ts +855 -0
  30. package/dist/types/resources/shortLinks.d.ts +45 -4
  31. package/dist/types/resources/social.d.ts +1330 -0
  32. package/dist/types/resources/storage/upload.d.ts +158 -11
  33. package/dist/types/resources/storage.d.ts +88 -22
  34. package/dist/types/resources/tickets.d.ts +82 -3
  35. package/dist/types/resources/tools/backgroundRemoval.d.ts +18 -3
  36. package/dist/types/resources/tools/captions.d.ts +448 -21
  37. package/dist/types/resources/tools/downloader.d.ts +21 -0
  38. package/dist/types/resources/tools/index.d.ts +57 -15
  39. package/dist/types/resources/tools/jumpstyle.d.ts +50 -17
  40. package/dist/types/resources/tools/transcription.d.ts +35 -13
  41. package/dist/types/resources/tools/upscale.d.ts +23 -3
  42. package/dist/types/resources/tools/vocalSeparation.d.ts +30 -13
  43. package/dist/types/types.d.ts +249 -17
  44. package/package.json +2 -1
@@ -17,6 +17,14 @@
17
17
  import { Resource } from "../http";
18
18
  import type { BaseRecord, Id, JsonObject, RequestOptions } from "../types";
19
19
  import { type ShortLinkId, type ShortLinkStats } from "./shortLinks";
20
+ /**
21
+ * The reserved short-link namespace every dynamic QR lives in.
22
+ *
23
+ * `ShortLink::DYNAMIC_QR_NAMESPACE`. It is not a user choice and it never
24
+ * changes on a record, which is why {@link DynamicQr.namespace} is typed as
25
+ * this literal rather than as a string.
26
+ */
27
+ export declare const DYNAMIC_QR_NAMESPACE = "qr";
20
28
  /** Public prefix a dynamic QR resolves under. */
21
29
  export declare const DYNAMIC_QR_BASE_URL = "https://omelhor.site/qr";
22
30
  /** Module shapes `DynamicQrs::SettingsSanitizer` accepts. Anything else is dropped. */
@@ -72,7 +80,24 @@ export interface DynamicQrSettings {
72
80
  */
73
81
  readonly [key: string]: unknown;
74
82
  }
75
- /** A dynamic QR code. */
83
+ /**
84
+ * A dynamic QR code.
85
+ *
86
+ * `DynamicQrBlueprint` renders `ApplicationBlueprint`'s three automatic keys
87
+ * (`id`, `created_at`, `updated_at`) plus exactly five more, and that is the
88
+ * whole record. Two keys a client migrating off the old web service will
89
+ * expect are NOT here and never were on this endpoint: `website_id` and
90
+ * `website_managed`. They are residue of the websites feature, which was
91
+ * extracted out of this backend entirely - there is no such column on
92
+ * `short_links` and no such field on any blueprint, so anything declaring them
93
+ * has been reading `undefined`.
94
+ *
95
+ * The blueprint is also never resolved automatically. A dynamic QR IS a
96
+ * `ShortLink`, and `ShortLinkBlueprint` already owns that name with a
97
+ * different shape (associations, no `settings`), so every call site passes
98
+ * this blueprint explicitly. That is why the two records disagree about which
99
+ * fields exist even though they are rows in one table.
100
+ */
76
101
  export interface DynamicQr extends Omit<BaseRecord, "id"> {
77
102
  /** Integer primary key: a dynamic QR is a `short_links` row. See {@link ShortLinkId}. */
78
103
  readonly id: number;
@@ -80,8 +105,19 @@ export interface DynamicQr extends Omit<BaseRecord, "id"> {
80
105
  readonly url: string;
81
106
  /** Server-assigned UUID the QR image encodes. Not choosable, not renameable. */
82
107
  readonly endpoint: string;
83
- /** Always `"qr"`. */
84
- readonly namespace: string;
108
+ /**
109
+ * Always {@link DYNAMIC_QR_NAMESPACE}. The controller writes it on create
110
+ * and nothing can change it afterwards, and the listing scope filters on it,
111
+ * so a record that reached you through this namespace cannot hold anything
112
+ * else.
113
+ */
114
+ readonly namespace: typeof DYNAMIC_QR_NAMESPACE;
115
+ /**
116
+ * Owner. The column is nullable because anonymous short links exist, but
117
+ * every route on this resource requires a credential and the controller
118
+ * always sets the owner, so in practice this is never `null` for a dynamic
119
+ * QR.
120
+ */
85
121
  readonly user_id: Id | null;
86
122
  /** Never `null`: the blueprint substitutes `{}` for an unset bag. */
87
123
  readonly settings: DynamicQrSettings & JsonObject;
@@ -13,6 +13,16 @@
13
13
  */
14
14
  import { type ApiClient, Resource } from "../http";
15
15
  import type { BaseRecord, FileInput, Id, Json, RequestOptions, Timestamp } from "../types";
16
+ /**
17
+ * The reserved short-link namespace a published form is served under.
18
+ * `Form::NAMESPACE` / `ShortLink::FORM_NAMESPACE`.
19
+ */
20
+ export declare const FORM_NAMESPACE = "f";
21
+ /**
22
+ * Public prefix a published form resolves under, and the prefix
23
+ * {@link Form.published_url} is built from server-side.
24
+ */
25
+ export declare const FORM_BASE_URL = "https://omelhor.site/f";
16
26
  /**
17
27
  * Publication state. Only `"published"` answers on the public side; `"draft"`
18
28
  * and `"archived"` both 404 there.
@@ -20,41 +30,96 @@ import type { BaseRecord, FileInput, Id, Json, RequestOptions, Timestamp } from
20
30
  export type FormStatus = "draft" | "published" | "archived";
21
31
  /** Kinds of field the builder understands. Anything else is dropped. */
22
32
  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. */
33
+ /**
34
+ * One choice of a `single_choice`, `multi_choice` or `dropdown` field, AS
35
+ * READ back off a form.
36
+ *
37
+ * `id` is not optional here even though it is optional when writing: the
38
+ * sanitiser mints `SecureRandom.uuid` for any option that arrives without one,
39
+ * so a stored option always has one. Write with
40
+ * {@link FormSchemaFieldOptionInput}.
41
+ */
24
42
  export interface FormSchemaFieldOption {
25
- /** Stable within the form. The server mints one when you leave it out. */
43
+ /** Stable within the form. */
44
+ readonly id: string;
45
+ readonly label: string;
46
+ }
47
+ /** One choice as WRITTEN. Omit `id` and the server mints a UUID. */
48
+ export interface FormSchemaFieldOptionInput {
26
49
  readonly id?: string;
27
50
  readonly label: string;
28
51
  }
29
52
  /**
30
- * One field of a form.
53
+ * One field of a form, AS READ back.
54
+ *
55
+ * `id` is the key answers are filed under - NOT the label.
56
+ *
57
+ * Four of these keys are non-optional because `Forms::InputSanitizer#field`
58
+ * writes them on EVERY field it keeps, whatever arrived: `id` (minted when
59
+ * absent), `type`, `label` and `description` (both coerced with `.to_s`, so an
60
+ * absent one is stored as `""`, not dropped) and `required` (cast to a real
61
+ * boolean). The three that stay optional are genuinely absent from the stored
62
+ * object when unused: `placeholder` is only kept when present, `options` only
63
+ * for the three choice types, and `min`/`max` only for `number`.
31
64
  *
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.
65
+ * Write with {@link FormSchemaFieldInput}, where all of that is optional.
35
66
  */
36
67
  export interface FormSchemaField {
37
- readonly id?: string;
68
+ readonly id: string;
38
69
  readonly type: FormSchemaFieldType;
70
+ /** Never `null`; `""` for a field saved without one. */
39
71
  readonly label: string;
72
+ /** Never `null`; `""` for a field saved without one. */
73
+ readonly description: string;
74
+ readonly required: boolean;
75
+ readonly placeholder?: string;
76
+ /** Only on the three choice types. */
77
+ readonly options?: FormSchemaFieldOption[];
78
+ /** Only on `number`, and only when one was set. */
79
+ readonly min?: number;
80
+ /** Only on `number`, and only when one was set. */
81
+ readonly max?: number;
82
+ }
83
+ /**
84
+ * One field as WRITTEN.
85
+ *
86
+ * Every key but `type` may be omitted. `type` may not: a field whose type is
87
+ * not one of `Form::FIELD_TYPES` is dropped from the schema in silence, which
88
+ * looks exactly like a field that was never sent.
89
+ *
90
+ * A {@link FormSchemaField} read off a form is assignable here, so the
91
+ * read-edit-write round trip needs no mapping.
92
+ */
93
+ export interface FormSchemaFieldInput {
94
+ /** Omit and the server mints a UUID - which you then have to read back
95
+ * before you can file an answer under it. */
96
+ readonly id?: string;
97
+ readonly type: FormSchemaFieldType;
98
+ readonly label?: string;
40
99
  readonly description?: string;
41
100
  readonly required?: boolean;
42
101
  readonly placeholder?: string;
43
- /** Only meaningful on the three choice types. */
44
- readonly options?: FormSchemaFieldOption[];
45
- /** Only meaningful on `number`. */
102
+ readonly options?: FormSchemaFieldOptionInput[];
46
103
  readonly min?: number;
47
- /** Only meaningful on `number`. */
48
104
  readonly max?: number;
49
105
  }
50
106
  /**
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.
107
+ * The field definition of a form, as read. Always `{ fields: [...] }`, never
108
+ * `null` and never a bare array: the column is `NOT NULL DEFAULT
109
+ * '{"fields":[]}'` and the sanitiser rebuilds the envelope on every write.
54
110
  */
55
111
  export interface FormSchema {
56
112
  readonly fields: FormSchemaField[];
57
113
  }
114
+ /**
115
+ * The field definition as written. The server rebuilds it from scratch keeping
116
+ * only the keys {@link FormSchemaFieldInput} names, so anything extra is lost
117
+ * without a word - and a write that is not a Hash at all is silently read as
118
+ * `{ fields: [] }`, which empties the form rather than failing.
119
+ */
120
+ export interface FormSchemaInput {
121
+ readonly fields: FormSchemaFieldInput[];
122
+ }
58
123
  /**
59
124
  * Styling of a form. Colours must be `#RRGGBB`; images must be `data:image/`
60
125
  * URIs under 2 MB. Anything else in here is dropped silently.
@@ -85,9 +150,17 @@ export interface FormSettings {
85
150
  readonly thank_you_title?: string;
86
151
  readonly thank_you_subtitle?: string;
87
152
  }
88
- /** A hosted form, owner view. */
153
+ /**
154
+ * A hosted form, owner view.
155
+ *
156
+ * One shape, not two: `FormsController` renders `form.render` with no view on
157
+ * index, show, create AND update, so the `:extended` view `ApplicationBlueprint`
158
+ * declares is never reached here and there is no richer variant to ask for.
159
+ * Every key below is therefore on every response.
160
+ */
89
161
  export interface Form extends BaseRecord {
90
162
  readonly user_id: Id;
163
+ /** Never `null`; `""` for a form saved without one. Capped at 200 characters. */
91
164
  readonly title: string;
92
165
  readonly status: FormStatus;
93
166
  readonly schema: FormSchema;
@@ -97,12 +170,32 @@ export interface Form extends BaseRecord {
97
170
  readonly endpoint: string | null;
98
171
  /** Shareable short URL, or `null` while there is no endpoint. */
99
172
  readonly published_url: string | null;
100
- /** Set the first time the form is published, and never cleared. */
101
- readonly published_at?: Timestamp | null;
173
+ /**
174
+ * Set the first time the form is published, and never cleared - archiving or
175
+ * returning a form to draft leaves it standing, so this is "was ever
176
+ * published", not "is published". Read `status` for that.
177
+ *
178
+ * The key is always present; the value is `null` until the first publish.
179
+ */
180
+ readonly published_at: Timestamp | null;
181
+ /** Bumped by every `getPublic` call, the SDK's included. Never `null`. */
102
182
  readonly views_count: number;
183
+ /**
184
+ * Answers recorded. Computed per request - batched into one grouped COUNT
185
+ * for a whole listing, one COUNT for a single render - so it is always
186
+ * current and never cached.
187
+ */
103
188
  readonly submissions_count: number;
104
189
  }
105
- /** The reduced form a public respondent is allowed to see. */
190
+ /**
191
+ * The reduced form a public respondent is allowed to see.
192
+ *
193
+ * NOT a subset of {@link Form}: the controller builds this hash by hand rather
194
+ * than rendering a blueprint view, so it carries `require_login` - which is not
195
+ * a field on `Form` at all, only a key inside `settings` - and carries no
196
+ * timestamps, no `user_id`, no counts and no `status`. Seven keys, always all
197
+ * seven.
198
+ */
106
199
  export interface PublicForm {
107
200
  readonly id: Id;
108
201
  readonly title: string;
@@ -115,46 +208,77 @@ export interface PublicForm {
115
208
  /**
116
209
  * One answered form.
117
210
  *
118
- * Deliberately has no `updated_at`: a submission is never edited, and the
119
- * blueprint leaves the field out rather than render a lie.
211
+ * Deliberately has no `updated_at`: a submission is never edited, and
212
+ * `FormSubmissionBlueprint` is the one blueprint in the API that inherits
213
+ * `Blueprinter::Base` directly rather than `ApplicationBlueprint`, precisely so
214
+ * that the automatic `updated_at` cannot creep in. Do not add it here on the
215
+ * assumption that every record has one.
216
+ *
217
+ * The other seven keys are all declared unconditionally, so all seven are
218
+ * always present; four of them are nullable columns.
120
219
  */
121
220
  export interface FormSubmission {
122
221
  readonly id: Id;
123
222
  readonly form_id: Id;
124
- /** The respondent, when they were signed in. */
125
- readonly user_id?: Id | null;
223
+ /** The respondent, or `null` when they answered anonymously. */
224
+ readonly user_id: Id | null;
126
225
  /**
127
226
  * Answers keyed by {@link FormSchemaField.id}. An `image` answer is
128
227
  * `{ attachment_id, filename }`; a `number` is a float; a `multi_choice` is
129
228
  * an array of strings; everything else is a string.
229
+ *
230
+ * Only keys that match a field in the schema survive - anything else is
231
+ * dropped in silence - so a submission can hold FEWER keys than were sent,
232
+ * and an optional field nobody filled in is simply absent.
130
233
  */
131
234
  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;
235
+ /**
236
+ * ISO 3166-1 alpha-2, lowercase, resolved from the respondent's IP.
237
+ * `null` when the lookup found nothing.
238
+ */
239
+ readonly country: string | null;
240
+ /** Parsed from the respondent's user agent. `null` when unparseable. */
241
+ readonly device_name: string | null;
242
+ /**
243
+ * When the answers were recorded. The controller writes `Time.current` on
244
+ * every submission it creates, so this is `null` only for a row predating
245
+ * that - but the column is nullable, so check before formatting it.
246
+ */
247
+ readonly completed_at: Timestamp | null;
137
248
  readonly created_at: Timestamp;
138
249
  }
139
- /** What `POST /form_attachments` answers with. */
250
+ /**
251
+ * What `POST /form_attachments` answers with.
252
+ *
253
+ * Built by hand in the controller rather than by a blueprint, so this is the
254
+ * literal four-key hash it renders and there is no `created_at` to read.
255
+ */
140
256
  export interface FormAttachment {
141
257
  readonly id: Id;
142
258
  readonly filename: string;
259
+ /** One of `FormAttachment::ALLOWED_TYPES`; a save with anything else is a 400. */
143
260
  readonly content_type: string;
144
261
  /** Absolute URL that serves the bytes inline, no credential required. */
145
262
  readonly url: string;
146
263
  }
147
264
  /**
148
- * `GET /forms/endpoint_availability`. Read `available`; `suggestions` is only
149
- * present when the endpoint is well formed but taken.
265
+ * `GET /forms/endpoint_availability`. Read `available`; the other two keys are
266
+ * conditional and mutually exclusive.
267
+ *
268
+ * `reason` appears only on the rejected branch and only ever holds
269
+ * `"invalid"` - the controller has a single rejection reason for forms,
270
+ * covering both a bad shape and a reserved word. (Link trees, which look
271
+ * identical, do distinguish the two; see `LinkTreeSlugAvailability`.)
272
+ * `suggestions` appears only on the well-formed-but-taken branch.
150
273
  */
151
274
  export interface FormEndpointAvailability {
152
275
  readonly endpoint: string;
153
276
  /** Whether it matches the format and is not reserved. */
154
277
  readonly valid: boolean;
155
278
  readonly available: boolean;
156
- /** `"invalid"` when the format or the reserved list rejected it. */
157
- readonly reason?: string;
279
+ /** Only when `valid` is `false`. */
280
+ readonly reason?: "invalid";
281
+ /** Only when `valid` is `true` and `available` is `false`. */
158
282
  readonly suggestions?: string[];
159
283
  }
160
284
  /**
@@ -164,14 +288,20 @@ export interface FormEndpointAvailability {
164
288
  * through {@link UpdateFormInput}. `require_login` lives in `settings`.
165
289
  */
166
290
  export interface CreateFormInput {
167
- readonly title: string;
291
+ /**
292
+ * Optional, despite being the thing a person names the form by: the
293
+ * controller runs it through `.to_s.strip` and the model only validates its
294
+ * LENGTH (200 maximum), so an omitted title saves an untitled form rather
295
+ * than failing. Pass one.
296
+ */
297
+ readonly title?: string;
168
298
  /**
169
299
  * Public path segment. Lowercased, 1 to 64 characters of `[a-z0-9_-]`
170
300
  * starting and ending alphanumeric, and never one of `new create index
171
301
  * admin api login signup help`.
172
302
  */
173
303
  readonly endpoint: string;
174
- readonly schema?: FormSchema;
304
+ readonly schema?: FormSchemaInput;
175
305
  readonly theme?: FormTheme;
176
306
  readonly settings?: FormSettings;
177
307
  }
@@ -184,7 +314,7 @@ export interface UpdateFormInput {
184
314
  readonly title?: string;
185
315
  /** Renames the paired short link, so the old public URL stops working. */
186
316
  readonly endpoint?: string;
187
- readonly schema?: FormSchema;
317
+ readonly schema?: FormSchemaInput;
188
318
  readonly theme?: FormTheme;
189
319
  readonly settings?: FormSettings;
190
320
  /**
@@ -328,4 +458,15 @@ export declare class FormsNamespace extends Resource {
328
458
  * anyone holding the id.
329
459
  */
330
460
  attachment(attachmentId: Id, options?: RequestOptions): Promise<Blob>;
461
+ /**
462
+ * The public URL an endpoint is served from. Pure string building, no
463
+ * request, and the same string the server puts in {@link Form.published_url}.
464
+ *
465
+ * Prefer `form.published_url` when you are holding a record - it is the
466
+ * server's own answer and it is `null` exactly when the pairing is broken,
467
+ * which this cannot know. Reach for this when all you have is an endpoint:
468
+ * after {@link endpointAvailability}, or in a client that stores endpoints
469
+ * rather than forms.
470
+ */
471
+ publicUrl(endpoint: string): string;
331
472
  }
@@ -1,30 +1,41 @@
1
1
  /**
2
2
  * Every resource namespace, in one place.
3
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.
4
+ * `client.ts` imports the namespace classes from the modules themselves rather
5
+ * than from here, so adding a resource does not serialise on this file;
6
+ * consumers import the record and input types from here, flattened through
7
+ * `src/index.ts`.
7
8
  *
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.
9
+ * Re-exports every sibling module so nobody has to touch this file twice: a new
10
+ * resource means one new file, one line here, and one line in {@link Oms}.
11
11
  *
12
12
  * Naming rule that keeps `export *` unambiguous: every exported name is
13
13
  * prefixed by its domain (`ShortLink`, `ShortLinkStats`, `CreateShortLinkInput`).
14
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.
15
+ * wanted one, and an `export *` collision does NOT fail the build - TypeScript
16
+ * drops the ambiguous name from the barrel in silence, so the symbol simply
17
+ * stops existing for consumers. `test/exports.test.ts` walks the re-export
18
+ * graph and fails on a duplicate, because the compiler will not.
17
19
  */
18
20
  export * from "./account";
21
+ export * from "./admin";
22
+ export * from "./auth/index";
19
23
  export * from "./chests";
24
+ export * from "./content";
20
25
  export * from "./dynamicQrs";
21
26
  export * from "./forms";
22
27
  export * from "./ipLookup";
23
28
  export * from "./jobs";
29
+ export * from "./library";
24
30
  export * from "./linkTrees";
31
+ export * from "./media";
32
+ export * from "./movies";
33
+ export * from "./music/index";
25
34
  export * from "./notepads";
26
35
  export * from "./quotas";
36
+ export * from "./realtime";
27
37
  export * from "./shortLinks";
38
+ export * from "./social";
28
39
  export * from "./storage";
29
40
  export * from "./storage/upload";
30
41
  export * from "./tickets";
@@ -17,9 +17,16 @@ import type { RequestOptions } from "../types";
17
17
  /**
18
18
  * What the lookup service knows about an address.
19
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
20
+ * Exactly five keys, always all five. `IpLookuper` answers `{ ip:, **record }`
21
+ * and nothing else: `country`, `asn` and `organization` are the three the MMDB
22
+ * builder writes per range, and `network` is stamped on by the reader from the
23
+ * prefix that matched. Nothing is computed and nothing is filled in for a
22
24
  * missing row - a miss is a 400, not a half-empty object.
25
+ *
26
+ * The name promises a GeoIP payload and this is not one, so there is no
27
+ * `city`, no `latitude`/`longitude`, no `timezone` and no `postal`. Those keys
28
+ * exist in the MaxMind gem's own result API and are all `nil` here, because
29
+ * the database behind it is the iptoasn.com IP-to-ASN table.
23
30
  */
24
31
  export interface IpLookupResult {
25
32
  /** The address that was looked up, echoed back. For `"mine"`, the resolved one. */
@@ -30,9 +37,18 @@ export interface IpLookupResult {
30
37
  * it yourself before feeding it to a flag or a locale lookup.
31
38
  */
32
39
  readonly country: string;
33
- /** Autonomous system number, or `0` when the range is not announced. */
40
+ /**
41
+ * Autonomous system number, or `0` when the range is not announced. `0` is
42
+ * the builder's substitute for a non-numeric column, not a real AS, so treat
43
+ * it as "unknown" rather than looking it up.
44
+ */
34
45
  readonly asn: number;
35
- /** Network operator name as the AS registry spells it, e.g. `"GOOGLE"`. */
46
+ /**
47
+ * Network operator name as the AS registry spells it, e.g. `"GOOGLE"`.
48
+ * Whitespace-squished, and the EMPTY STRING when the source table had no
49
+ * name for the range - never `null`, so test it for length rather than for
50
+ * presence.
51
+ */
36
52
  readonly organization: string;
37
53
  /** The CIDR block the address fell in, e.g. `"8.8.8.0/24"`. */
38
54
  readonly network: string;
@@ -67,35 +67,70 @@ export declare const JOB_STATUS: Readonly<{
67
67
  export declare const JOB_TERMINAL_STATUSES: readonly JobStatus[];
68
68
  /** True once this status can never change again. */
69
69
  export declare function isJobTerminal(status: string): boolean;
70
- /** A background job. */
70
+ /**
71
+ * A background job.
72
+ *
73
+ * `JobBlueprint` declares thirteen fields and nothing else, so EVERY key below
74
+ * is present on every response. A `?` here would mean "the server sometimes
75
+ * leaves this out", and it never does; what varies is the VALUE, because most
76
+ * of these are nullable columns that fill in as the job moves.
77
+ *
78
+ * Two keys are deliberately NOT here, and a client migrating off the old web
79
+ * service will expect them: `updater_id` and `destroyer_id`. Both columns
80
+ * exist on the `jobs` table and both are indexed, but `JobBlueprint` renders
81
+ * NEITHER, so anything declaring them has been reading `undefined` for as long
82
+ * as it has existed. Declaring them here would only move the lie.
83
+ */
71
84
  export interface Job extends BaseRecord {
72
85
  readonly status: JobStatus;
73
86
  /**
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.
87
+ * Feature-level kind of the run. `Job::JOB_TYPES` holds exactly two strings:
88
+ * `"omsvs"` (vocal separation) and `"unknown"`, which is the column default
89
+ * every generic enqueue gets - the upscale and background-removal proxies
90
+ * included. It is NOT the worker's class name, and a third value cannot
91
+ * appear without a model change, because an inclusion validation rejects it.
78
92
  */
79
93
  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
94
  /**
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".
95
+ * Enqueue-time arguments. The column is `jsonb DEFAULT '{}'`, so a row whose
96
+ * enqueuer wrote nothing carries `{}` rather than `null`. Shape depends on
97
+ * `job_type`.
89
98
  */
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;
99
+ readonly payload: Json;
100
+ /** Set when a worker claimed the job; `null` while it is still `"pending"`. */
101
+ readonly started_at: Timestamp | null;
102
+ /** Set when the job reached a terminal state, cancellation included. */
103
+ readonly finished_at: Timestamp | null;
104
+ /**
105
+ * Percentage, an integer in `[0, 100]`. The column is `NOT NULL DEFAULT 0`
106
+ * and the model validates the range, so this is a real number from the
107
+ * moment the row exists: `0` means "not started", never "unknown".
108
+ */
109
+ readonly progress: number;
110
+ /**
111
+ * Failure message once `status === "failed"` - and ALSO the reason once
112
+ * `"canceled"`, because `Job#cancel!` writes it into this same column. A
113
+ * non-null `error` therefore does not by itself mean the work failed. Read
114
+ * `status`.
115
+ */
116
+ readonly error: string | null;
117
+ /**
118
+ * Whatever the worker returned, stored by `ApplicationJob`'s
119
+ * `around_perform`. Shape depends on `job_type`, and the two proxies worth
120
+ * naming both answer an object carrying a signed download link:
121
+ *
122
+ * - upscale: `{ upscale_id, result_url }`;
123
+ * - background removal: `{ background_removal_id, result_url }`.
124
+ *
125
+ * That `result_url` is the same string the tool's own row carries once the
126
+ * run completes, which is why `awaitToolJob` waits on the job and then
127
+ * re-reads the typed row instead of digging a URL out of here.
128
+ */
129
+ readonly result: Json;
95
130
  /** 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;
131
+ readonly creator_id: Id | null;
132
+ /** Which worker claimed it. `null` until one does. */
133
+ readonly worker_id: string | null;
99
134
  }
100
135
  /**
101
136
  * How to address a job.
@@ -229,5 +264,11 @@ export declare function jobRef(ref: JobRef | Id): JobRef;
229
264
  * `total` is 100 rather than `undefined` because `progress` is a percentage the
230
265
  * server always has: the column is `NOT NULL DEFAULT 0`, so there is no
231
266
  * "unknown" to be honest about.
267
+ *
268
+ * The `typeof` guard is not defensive typing for its own sake. `Job.progress`
269
+ * is declared non-nullable because the column is, but this function is also
270
+ * handed rows that came off `JobChannel` and rows a host deserialised itself,
271
+ * and reading `undefined` as `NaN%` would put a broken number on a progress
272
+ * bar rather than a zero.
232
273
  */
233
274
  export declare function jobProgress(job: Job): Progress;