@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
|
@@ -33,11 +33,76 @@
|
|
|
33
33
|
* Multipart is not an optimisation. The object store sits behind Cloudflare on
|
|
34
34
|
* a plan that caps a request body at roughly 100 MB, so anything larger has no
|
|
35
35
|
* other way in.
|
|
36
|
+
*
|
|
37
|
+
* ## Progress, and why it ticks per transfer rather than per byte
|
|
38
|
+
*
|
|
39
|
+
* The bytes go to a presigned MinIO URL. Rails is never in the data path, and
|
|
40
|
+
* neither is rack-attack - so a progress bar here is a pure client-side
|
|
41
|
+
* problem, and the SDK solves it as far as `fetch` allows and no further.
|
|
42
|
+
*
|
|
43
|
+
* `fetch` has no upload-progress event. The two ways out were weighed and both
|
|
44
|
+
* were rejected for the core:
|
|
45
|
+
*
|
|
46
|
+
* - **XHR**, which is what the web frontend's axios uses today, is the only API
|
|
47
|
+
* that reports request bytes as they leave. It does not exist in a
|
|
48
|
+
* Cloudflare-Worker-class isolate, and this package must load there.
|
|
49
|
+
* - **A counting `ReadableStream` request body** would run in an isolate, and
|
|
50
|
+
* still does not work HERE. A stream body forces chunked transfer encoding,
|
|
51
|
+
* while a presigned PUT is signed over a fixed `Content-Length` (and, on the
|
|
52
|
+
* direct tier, over a `Content-MD5` covering the whole body), so the store
|
|
53
|
+
* answers a signature error. It also needs `duplex: "half"` and HTTP/2, which
|
|
54
|
+
* Safari and Firefox do not have. And it would count bytes handed to the
|
|
55
|
+
* runtime rather than bytes the store acknowledged - the lie that makes a bar
|
|
56
|
+
* sit at 100% for a minute.
|
|
57
|
+
*
|
|
58
|
+
* So: every `onProgress` in this module reports a COMPLETED transfer. On the
|
|
59
|
+
* multipart tier that is one tick per part, which the server sizes at 32 MiB,
|
|
60
|
+
* so a 1 GB file moves the bar 32 times. On the direct tier it is one tick for
|
|
61
|
+
* the whole file, which is at most 32 MiB by construction.
|
|
62
|
+
*
|
|
63
|
+
* A host that wants true byte-level granularity does not have to give up the
|
|
64
|
+
* driver: {@link UploadManagerOptions.fetch} takes the transport used for the
|
|
65
|
+
* presigned PUTs, so a browser can hand in an XHR-backed {@link FetchLike} and
|
|
66
|
+
* keep everything else. See the note there for the recipe. Building the whole
|
|
67
|
+
* flow by hand is also supported - {@link UploadManager.createBatch},
|
|
68
|
+
* {@link UploadManager.putDirect}, {@link UploadManager.attachBlobs} and the
|
|
69
|
+
* `multipart*` methods are public precisely so that a caller can drive the
|
|
70
|
+
* three phases itself and count bytes however it likes.
|
|
36
71
|
*/
|
|
37
72
|
import { Resource, type ApiClient } from "../../http";
|
|
38
73
|
import { type FetchLike, type FileInput, type Id, type OperationOptions, type ProgressCallback, type RequestOptions } from "../../types";
|
|
39
74
|
import type { FsNode } from "../storage";
|
|
40
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Files at or above this size take the multipart path: 32 MiB, exactly.
|
|
77
|
+
*
|
|
78
|
+
* This is a PROTOCOL constant, not a tuning knob, and it is the SDK's public
|
|
79
|
+
* copy of it. Import this rather than writing `32 * 1024 * 1024` again:
|
|
80
|
+
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* import { MULTIPART_THRESHOLD } from "@omelhorsite/sdk";
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* The number was verified in all three places it is currently written down, and
|
|
86
|
+
* as of this writing they agree:
|
|
87
|
+
*
|
|
88
|
+
* - `FsServices::NodeBatchCreator::MULTIPART_THRESHOLD = 32.megabytes`, which
|
|
89
|
+
* is the only one that decides anything. The server compares
|
|
90
|
+
* `entry[:size] >= MULTIPART_THRESHOLD` and puts `strategy` in the plan.
|
|
91
|
+
* - the web frontend's `lib/upload_core.ts`, which keeps its own literal.
|
|
92
|
+
* - here.
|
|
93
|
+
*
|
|
94
|
+
* The client-side copies exist because the CHECKSUM has to be in the manifest
|
|
95
|
+
* before the server has decided anything: below the threshold the digest is
|
|
96
|
+
* mandatory (it is Content-MD5-bound into the presigned signature), at or above
|
|
97
|
+
* it is pointless (multipart verifies per-part ETags). So the comparison is
|
|
98
|
+
* made twice, and {@link manifestEntryFor} uses `<` against exactly the
|
|
99
|
+
* server's `>=`.
|
|
100
|
+
*
|
|
101
|
+
* Drift is expensive and silent in one direction: a client that thinks the
|
|
102
|
+
* threshold is HIGHER than the server's omits the checksum on a file the server
|
|
103
|
+
* still plans as direct, and the upload dies at MinIO with a signature error
|
|
104
|
+
* that never mentions checksums. That is the whole reason this is exported.
|
|
105
|
+
*/
|
|
41
106
|
export declare const MULTIPART_THRESHOLD: number;
|
|
42
107
|
/**
|
|
43
108
|
* Part size for the multipart path. Mirrors the backend, but it is only a
|
|
@@ -190,6 +255,12 @@ export interface UploadInput {
|
|
|
190
255
|
* Read the finished nodes back with one extra listing per batch. On by
|
|
191
256
|
* default, and worth it: a normal listing hides nodes whose bytes never
|
|
192
257
|
* landed, so a node coming back at all is proof the blob is bound.
|
|
258
|
+
*
|
|
259
|
+
* Turning it off costs more than the listing. `UploadResult.node` is then
|
|
260
|
+
* `null` for every file, and `oms.storage.upload()` - which returns exactly
|
|
261
|
+
* the nodes it read back - answers with an EMPTY array on a run where every
|
|
262
|
+
* byte landed. Use {@link UploadManager.upload} directly if you switch this
|
|
263
|
+
* off, and read `fs_node_id` instead.
|
|
193
264
|
*/
|
|
194
265
|
readonly verify?: boolean;
|
|
195
266
|
/** Replacement for the built-in MD5. See {@link Md5Base64Fn}. */
|
|
@@ -238,11 +309,40 @@ export declare class StorageRateGate {
|
|
|
238
309
|
/** Constructor options for {@link UploadManager}. */
|
|
239
310
|
export interface UploadManagerOptions {
|
|
240
311
|
/**
|
|
241
|
-
* The fetch used for the presigned PUTs.
|
|
312
|
+
* The fetch used for the presigned PUTs at the object store.
|
|
242
313
|
*
|
|
243
314
|
* Defaults to the one the {@link ApiClient} was built with, so bytes travel
|
|
244
|
-
* on the same transport as everything else.
|
|
245
|
-
*
|
|
315
|
+
* on the same transport as everything else. Only the PUTs go through it; the
|
|
316
|
+
* control plane always uses the client's own transport.
|
|
317
|
+
*
|
|
318
|
+
* This is also the supported way to get BYTE-LEVEL upload progress in a
|
|
319
|
+
* browser, which `fetch` cannot give (see the module note). Wrap XHR in a
|
|
320
|
+
* {@link FetchLike}, hand it in here, and keep the rest of the driver:
|
|
321
|
+
*
|
|
322
|
+
* ```ts
|
|
323
|
+
* const uploads = new UploadManager(oms.http, {
|
|
324
|
+
* fetch: (url, init) =>
|
|
325
|
+
* new Promise((resolve, reject) => {
|
|
326
|
+
* const xhr = new XMLHttpRequest();
|
|
327
|
+
* xhr.open(init?.method ?? "PUT", url);
|
|
328
|
+
* for (const [k, v] of Object.entries(init?.headers ?? {})) xhr.setRequestHeader(k, v as string);
|
|
329
|
+
* xhr.upload.onprogress = (event) => onBytes(url, event.loaded, event.total);
|
|
330
|
+
* xhr.onload = () =>
|
|
331
|
+
* resolve(new Response(xhr.response, { status: xhr.status, headers: parseXhrHeaders(xhr) }));
|
|
332
|
+
* xhr.onerror = () => reject(new Error("network"));
|
|
333
|
+
* xhr.send(init?.body as XMLHttpRequestBodyInit);
|
|
334
|
+
* }),
|
|
335
|
+
* });
|
|
336
|
+
* await uploads.upload({ parentId, files });
|
|
337
|
+
* ```
|
|
338
|
+
*
|
|
339
|
+
* Two things the wrapper MUST get right, both of which the SDK's own
|
|
340
|
+
* transport already does. It must not add an `Authorization` header or a
|
|
341
|
+
* cookie: the presigned signature is the credential and MinIO rejects a
|
|
342
|
+
* request carrying two authentication schemes. And it must expose `ETag` on
|
|
343
|
+
* the `Response` it builds, or the multipart tier has nothing to complete
|
|
344
|
+
* with - in a browser that additionally needs `ETag` in the bucket's
|
|
345
|
+
* `Access-Control-Expose-Headers`.
|
|
246
346
|
*/
|
|
247
347
|
readonly fetch?: FetchLike;
|
|
248
348
|
/** Replacement for the built-in MD5. See {@link Md5Base64Fn}. */
|
|
@@ -273,10 +373,16 @@ export declare class UploadManager extends Resource {
|
|
|
273
373
|
* the server reports them. Only a structural failure (bad parent, empty or
|
|
274
374
|
* oversized batch) throws.
|
|
275
375
|
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
376
|
+
* `options.onProgress` reports the WHOLE RUN: `loaded` counts bytes across
|
|
377
|
+
* every file and `total` is their sum, so the number only ever climbs. It
|
|
378
|
+
* ticks once per finished direct PUT and once per finished multipart part -
|
|
379
|
+
* never per byte, for the reasons in the module note - and it fires once with
|
|
380
|
+
* `loaded: 0` before anything is sent, so a bar can render immediately.
|
|
381
|
+
*
|
|
382
|
+
* That callback is deliberately NOT forwarded to the per-file drivers. They
|
|
383
|
+
* take an `onProgress` of their own that talks about ONE file, and letting
|
|
384
|
+
* the run-level callback reach them would interleave "3 MB of 3 MB" with
|
|
385
|
+
* "12 MB of 40 MB" on the same bar.
|
|
280
386
|
*
|
|
281
387
|
* Every multipart session this call opened is aborted before the error
|
|
282
388
|
* leaves, so a torn run does not strand parts (and reserved quota) on the
|
|
@@ -292,6 +398,14 @@ export declare class UploadManager extends Resource {
|
|
|
292
398
|
* (invalid parent, empty batch, over {@link MAX_BATCH}, a `..` segment)
|
|
293
399
|
* is a 400.
|
|
294
400
|
*
|
|
401
|
+
* Public because it is phase 1 of the protocol: a caller that wants its own
|
|
402
|
+
* progress accounting drives `createBatch` -> {@link putDirect} /
|
|
403
|
+
* {@link uploadMultipart} -> {@link attachBlobs} itself, and this is where it
|
|
404
|
+
* learns the per-file strategy and the byte counts it will be reporting
|
|
405
|
+
* against. Whatever it does, it must pace itself against `fs_upload` (300
|
|
406
|
+
* requests a minute, shared by every call in this class) - {@link gate} is
|
|
407
|
+
* exposed for exactly that.
|
|
408
|
+
*
|
|
295
409
|
* @throws {OmsApiError} 400 on a structural problem, 404 when the parent is
|
|
296
410
|
* not a directory the caller may write to.
|
|
297
411
|
*/
|
|
@@ -310,6 +424,16 @@ export declare class UploadManager extends Resource {
|
|
|
310
424
|
* expired signature or a checksum mismatch is deterministic and a retry only
|
|
311
425
|
* costs the bytes again.
|
|
312
426
|
*
|
|
427
|
+
* Phase 2 of the protocol for a file below {@link MULTIPART_THRESHOLD}, and
|
|
428
|
+
* public so a caller can drive its own flow. `onProgress` fires EXACTLY ONCE
|
|
429
|
+
* here, after the store has accepted the body, with `loaded === total`. It is
|
|
430
|
+
* not a byte counter and cannot be one: see the module note, and
|
|
431
|
+
* {@link UploadManagerOptions.fetch} for the XHR escape hatch that can.
|
|
432
|
+
*
|
|
433
|
+
* Between this and {@link attachBlobs} the node exists with no bytes bound and
|
|
434
|
+
* is hidden from every listing, so a caller that stops here leaves a pending
|
|
435
|
+
* node behind.
|
|
436
|
+
*
|
|
313
437
|
* @returns The ETag the store reported, or `null` when it was not readable -
|
|
314
438
|
* which happens in a browser whose bucket CORS policy does not expose
|
|
315
439
|
* `ETag`. The direct tier does not need it; multipart does.
|
|
@@ -322,8 +446,11 @@ export declare class UploadManager extends Resource {
|
|
|
322
446
|
* their nodes. Until this lands the node exists with no bytes and is hidden
|
|
323
447
|
* from every listing.
|
|
324
448
|
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
449
|
+
* Phase 3 of the protocol, and the last one for the direct tier. Answers 200
|
|
450
|
+
* with per-item results even when every item failed: read `attached` on each
|
|
451
|
+
* one, and never infer success from the status. A caller driving the flow by
|
|
452
|
+
* hand must not skip this - a node with no blob is invisible in listings and
|
|
453
|
+
* is eventually swept by `FsNodeUploadReaperJob`.
|
|
327
454
|
*/
|
|
328
455
|
attachBlobs(attachments: Array<{
|
|
329
456
|
fs_node_id: Id;
|
|
@@ -380,11 +507,30 @@ export declare class UploadManager extends Resource {
|
|
|
380
507
|
* {@link MULTIPART_PART_SIZE}, so a change on the backend does not silently
|
|
381
508
|
* corrupt an upload here.
|
|
382
509
|
*
|
|
510
|
+
* Phases 2 and 3 at once for a file at or above {@link MULTIPART_THRESHOLD}:
|
|
511
|
+
* `multipart/complete` creates the blob row itself, so there is no
|
|
512
|
+
* {@link attachBlobs} call on this tier.
|
|
513
|
+
*
|
|
514
|
+
* This is the tier where a progress bar is actually useful, because the parts
|
|
515
|
+
* are 32 MiB and each one that lands is a real tick. `onProgress` reports
|
|
516
|
+
* THIS file cumulatively - `loaded` climbing towards `total === body.size` -
|
|
517
|
+
* and fires once up front so a bar can render before the first part is sent.
|
|
518
|
+
* On a resumed session it starts at the bytes already stored rather than at
|
|
519
|
+
* zero, which is the difference between "resuming at 60%" and a bar that
|
|
520
|
+
* appears to lose an hour of work. `onPart` is the lower-level twin: one call
|
|
521
|
+
* per part, with that part's byte count and its ETag.
|
|
522
|
+
*
|
|
523
|
+
* NOTHING here is per-byte; see the module note for why, and
|
|
524
|
+
* {@link UploadManagerOptions.fetch} for the way around it.
|
|
525
|
+
*
|
|
383
526
|
* @param options.resume A session from a previous attempt plus the parts that
|
|
384
527
|
* already landed. The matching parts are skipped.
|
|
385
528
|
* @param options.onSession Called with the upload token as soon as there is
|
|
386
|
-
* one, so a caller can persist it and abort later.
|
|
529
|
+
* one, so a caller can persist it and abort later. A failed run MUST reach
|
|
530
|
+
* {@link multipartAbort} with that token or the parts stay charged against
|
|
531
|
+
* the quota until a reaper notices.
|
|
387
532
|
* @param options.onPart Called with the byte count of each finished part.
|
|
533
|
+
* @param options.onProgress Called with this file's cumulative byte count.
|
|
388
534
|
*/
|
|
389
535
|
uploadMultipart(fsNodeId: Id, body: Blob, options?: RequestOptions & {
|
|
390
536
|
concurrency?: number;
|
|
@@ -395,6 +541,7 @@ export declare class UploadManager extends Resource {
|
|
|
395
541
|
};
|
|
396
542
|
onSession?: (uploadToken: string, partSize: number) => void;
|
|
397
543
|
onPart?: (bytes: number, part: MultipartPart) => void;
|
|
544
|
+
onProgress?: ProgressCallback;
|
|
398
545
|
}): Promise<MultipartCompletion>;
|
|
399
546
|
/**
|
|
400
547
|
* PUTs bytes at the object store on the injected transport, with no
|
|
@@ -27,20 +27,52 @@
|
|
|
27
27
|
* - the general 600/min for everything else.
|
|
28
28
|
* - the direct PUTs at the object store, which rack-attack never sees at all.
|
|
29
29
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The server's null sentinel: U+0008, a literal backspace.
|
|
32
|
+
*
|
|
33
|
+
* `CrudActions` rewrites any filter value equal to it before the query layer
|
|
34
|
+
* ever sees it - `transform_values! { |v| v == "\b" ? nil : v }`, applied to
|
|
35
|
+
* every option bag, not just to `exact_search`. Through `exact_search` that is
|
|
36
|
+
* exactly what is wanted: `where(parent_id: nil)`, the root nodes.
|
|
37
|
+
*
|
|
38
|
+
* Through `extra_options` the same rewrite is a trap, because
|
|
39
|
+
* `QueryExtraOptions::FsNodes` opens with
|
|
40
|
+
* `return unless params[:parent_id].present?` and `nil` is not present, so the
|
|
41
|
+
* filter is dropped without a word. {@link StorageNamespace.list} is built
|
|
42
|
+
* around that difference; do not collapse the two filters back together.
|
|
43
|
+
*/
|
|
44
|
+
import { type ApiClient, Resource } from "../http";
|
|
45
|
+
import type { ListParams } from "../listing";
|
|
46
|
+
import type { BaseRecord, FileOutput, Id, OperationOptions, Paginated, RequestOptions } from "../types";
|
|
32
47
|
import type { User } from "./account";
|
|
33
48
|
import { StorageRateGate, UploadManager } from "./storage/upload";
|
|
49
|
+
import type { UploadInput } from "./storage/upload";
|
|
34
50
|
/** What a node is. */
|
|
35
51
|
export type FsNodeKind = "file" | "directory";
|
|
36
52
|
/**
|
|
37
53
|
* A node in the virtual filesystem.
|
|
38
54
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* `
|
|
43
|
-
*
|
|
55
|
+
* This is the WHOLE record, and it was checked against the blueprint CHAIN
|
|
56
|
+
* rather than against the table, because those two disagree.
|
|
57
|
+
* `FsNodeBlueprint` declares `name`, `parent_id`, `kind`, `size`,
|
|
58
|
+
* `max_size`; it extends `ApplicationBlueprint`, which contributes `id`,
|
|
59
|
+
* `created_at` and `updated_at`; and its `:extended` view has an EMPTY body,
|
|
60
|
+
* which in Blueprinter inherits the base fields rather than emitting nothing.
|
|
61
|
+
* So `list`, `get`, `create` and `update` all answer the same eight fields.
|
|
62
|
+
* Reading a view name and assuming it adds something has already produced bugs
|
|
63
|
+
* in this repo - follow the `<` before deciding a field does or does not exist.
|
|
64
|
+
*
|
|
65
|
+
* The `fs_nodes` TABLE is wider than that, and the difference is never sent:
|
|
66
|
+
* `creator_id`, `updater_id`, `destroyer_id`, `signed_url_generated`,
|
|
67
|
+
* `is_vault_root` and the `id_path` ltree are all real columns that appear in
|
|
68
|
+
* no view. Declaring them client-side is worse than leaving them out, because
|
|
69
|
+
* every later reader then believes they arrive.
|
|
70
|
+
*
|
|
71
|
+
* There is no `data` and no `url` field either: bytes are reached through
|
|
72
|
+
* {@link StorageNamespace.download}, {@link StorageNamespace.downloadStream} or
|
|
73
|
+
* {@link StorageNamespace.downloadUrl}, never off the record. And no
|
|
74
|
+
* `content_type` and no `path` - the type is decided from the name at download
|
|
75
|
+
* time, and the `path` column was dropped in the ltree migration.
|
|
44
76
|
*/
|
|
45
77
|
export interface FsNode extends BaseRecord {
|
|
46
78
|
readonly name: string;
|
|
@@ -94,12 +126,27 @@ export interface FsStream {
|
|
|
94
126
|
readonly size: number | undefined;
|
|
95
127
|
}
|
|
96
128
|
/** Filters for {@link StorageNamespace.list}. */
|
|
97
|
-
|
|
129
|
+
/** Filter columns of `GET /fs_nodes`. */
|
|
130
|
+
export declare const FS_NODE_FILTER_COLUMNS: readonly ["id", "created_at", "updated_at", "name", "parent_id"];
|
|
131
|
+
/** `extra_options` keys of `GET /fs_nodes`. */
|
|
132
|
+
export declare const FS_NODE_EXTRA_OPTION_KEYS: readonly ["parent_id", "include_pending"];
|
|
133
|
+
/**
|
|
134
|
+
* `extra_options` of `GET /fs_nodes`. `parent_id` here returns the folder's
|
|
135
|
+
* children AND the folder itself; the `parent_id` column in `exactSearch`
|
|
136
|
+
* returns the children only, and is the only one that can say `null`.
|
|
137
|
+
*/
|
|
138
|
+
export interface FsNodeExtraOptions {
|
|
139
|
+
readonly parent_id?: Id;
|
|
140
|
+
readonly include_pending?: boolean;
|
|
141
|
+
}
|
|
142
|
+
/** Filters for {@link StorageNamespace.list}. */
|
|
143
|
+
export interface ListFsNodesParams extends ListParams<(typeof FS_NODE_FILTER_COLUMNS)[number], FsNodeExtraOptions> {
|
|
98
144
|
/**
|
|
99
145
|
* Directory to list. `null` lists the caller's ROOT nodes (home, trash,
|
|
100
|
-
* vault),
|
|
146
|
+
* vault). Omitted, no parent filter is applied and the listing spans the
|
|
147
|
+
* caller's whole tree, which is what a name search across folders wants.
|
|
101
148
|
*/
|
|
102
|
-
readonly parentId
|
|
149
|
+
readonly parentId?: Id | null;
|
|
103
150
|
/**
|
|
104
151
|
* Include nodes whose bytes never landed. Off by default, because a
|
|
105
152
|
* half-finished upload is not something a user wants to see - and because a
|
|
@@ -109,6 +156,12 @@ export interface ListFsNodesParams extends PageParams {
|
|
|
109
156
|
/**
|
|
110
157
|
* Also return the parent itself, so one call gets both the folder's metadata
|
|
111
158
|
* and its children. It occupies a slot on the page like any other row.
|
|
159
|
+
*
|
|
160
|
+
* IGNORED when `parentId` is `null`, and that is a correction rather than a
|
|
161
|
+
* convenience: the server-side filter behind this flag cannot express "no
|
|
162
|
+
* parent" at all, and asking it to used to answer with the caller's whole
|
|
163
|
+
* tree. The roots have no folder to fold in anyway. The detail is on
|
|
164
|
+
* {@link StorageNamespace.list}.
|
|
112
165
|
*/
|
|
113
166
|
readonly includeSelf?: boolean;
|
|
114
167
|
}
|
|
@@ -128,6 +181,8 @@ export interface CopyFsNodesInput {
|
|
|
128
181
|
readonly ids: Id[];
|
|
129
182
|
readonly newParentId: Id;
|
|
130
183
|
}
|
|
184
|
+
/** `GET /fs_grants` filters on {@link BASE_FILTER_COLUMNS} only. */
|
|
185
|
+
export type ListFsGrantsParams = ListParams<never>;
|
|
131
186
|
/**
|
|
132
187
|
* A sharing grant on a node.
|
|
133
188
|
*
|
|
@@ -192,7 +247,7 @@ export declare class FsGrantsNamespace extends Resource {
|
|
|
192
247
|
*
|
|
193
248
|
* @throws {OmsAuthError} 401 when anonymous.
|
|
194
249
|
*/
|
|
195
|
-
list(params?:
|
|
250
|
+
list(params?: ListFsGrantsParams, options?: RequestOptions): Promise<Paginated<FsGrant>>;
|
|
196
251
|
/** `GET /fs_grants/:id` - one grant, with its node, grantor and grantee expanded. */
|
|
197
252
|
get(id: Id, options?: RequestOptions): Promise<FsGrant>;
|
|
198
253
|
/**
|
|
@@ -252,19 +307,45 @@ export declare class StorageNamespace extends Resource {
|
|
|
252
307
|
*/
|
|
253
308
|
roots(options?: RequestOptions): Promise<FsRoots>;
|
|
254
309
|
/**
|
|
255
|
-
* `GET /fs_nodes` - the children of a directory
|
|
310
|
+
* `GET /fs_nodes` - the children of a directory, or the roots when
|
|
311
|
+
* `parentId` is `null`.
|
|
256
312
|
*
|
|
257
313
|
* Anonymous callers get an empty listing, always: the listing scope is the
|
|
258
314
|
* caller's own tree plus what was explicitly shared with them, and a public
|
|
259
315
|
* grant is deliberately NOT enumerable. Reach a publicly-shared node by id
|
|
260
316
|
* with {@link get} or {@link shared} instead.
|
|
261
317
|
*
|
|
318
|
+
* TWO server-side filters address a directory and they are NOT
|
|
319
|
+
* interchangeable. That asymmetry is the whole subtlety of this method:
|
|
320
|
+
*
|
|
321
|
+
* - `exact_search[parent_id]` reaches `Searchable.exact_search`, which is a
|
|
322
|
+
* bare `where(params)`. The controller has already rewritten a `\b` value
|
|
323
|
+
* to `nil` by then, so the sentinel lands as `WHERE parent_id IS NULL` -
|
|
324
|
+
* and since `FsNode.root_nodes` is exactly `where(parent_id: nil)`, this is
|
|
325
|
+
* the only filter in the API that can say "the roots". The ltree
|
|
326
|
+
* `id_path` is the source of truth for ANCESTRY, but rootness is still a
|
|
327
|
+
* null `parent_id`.
|
|
328
|
+
* - `extra_options[parent_id]` reaches `QueryExtraOptions::FsNodes`, which
|
|
329
|
+
* runs `where(parent_id: x).or(where(id: x))` and so folds the folder
|
|
330
|
+
* itself back into its own listing. Convenient - and guarded by
|
|
331
|
+
* `return unless params[:parent_id].present?`, with that same `\b` -> `nil`
|
|
332
|
+
* rewrite happening first. Hand it the sentinel and the guard drops the
|
|
333
|
+
* filter IN SILENCE. The request still answers 200; it just answers with
|
|
334
|
+
* the caller's ENTIRE listable tree, page after page, instead of three
|
|
335
|
+
* rows. It is the same failure shape as the `inside_path` incident that
|
|
336
|
+
* `reject_unknown_filter_keys!` was written for, except this key IS known,
|
|
337
|
+
* so nothing rejects it.
|
|
338
|
+
*
|
|
339
|
+
* Hence `includeSelf` is honoured under a real directory and ignored at the
|
|
340
|
+
* top of the tree. Not client-side taste: it is the only combination the
|
|
341
|
+
* server can actually express.
|
|
342
|
+
*
|
|
262
343
|
* The endpoint is conditional-GET aware and answers 304 to a matching
|
|
263
344
|
* `If-None-Match`. The SDK never sends one, and asks the runtime not to
|
|
264
345
|
* revalidate on its own, because a 304 has no body and would surface here as
|
|
265
346
|
* an error rather than as an empty page.
|
|
266
347
|
*/
|
|
267
|
-
list(params
|
|
348
|
+
list(params?: ListFsNodesParams, options?: RequestOptions): Promise<Paginated<FsNode>>;
|
|
268
349
|
/**
|
|
269
350
|
* `GET /fs_nodes/:id` - one node.
|
|
270
351
|
*
|
|
@@ -312,25 +393,28 @@ export declare class StorageNamespace extends Resource {
|
|
|
312
393
|
* intake, presigns, sends the bytes straight to object storage, binds the
|
|
313
394
|
* blobs and reads the finished nodes back.
|
|
314
395
|
*
|
|
315
|
-
* Files at or above 32 MiB
|
|
316
|
-
*
|
|
317
|
-
*
|
|
396
|
+
* Files at or above `MULTIPART_THRESHOLD` (32 MiB, exported from this
|
|
397
|
+
* package) take the multipart path automatically, and that is not tuning:
|
|
398
|
+
* the object store sits behind Cloudflare with a request-body cap around
|
|
399
|
+
* 100 MB, so it is the only way a large file gets in at all.
|
|
318
400
|
*
|
|
319
401
|
* A per-file rejection - a quota that ran out, a name that collides with a
|
|
320
402
|
* directory - does not throw. It comes back in
|
|
321
403
|
* {@link UploadManager.upload}'s results, which is why that method is the one
|
|
322
404
|
* to call when partial success matters; this wrapper returns only the nodes
|
|
323
|
-
* that landed.
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
405
|
+
* that landed, and throws only when EVERY file was refused.
|
|
406
|
+
*
|
|
407
|
+
* `options.onProgress` counts bytes across the whole run and only ever
|
|
408
|
+
* climbs. It ticks once per finished direct PUT and once per finished
|
|
409
|
+
* multipart part, never per byte - `fetch` has no upload-progress event, and
|
|
410
|
+
* the alternatives are argued out in `storage/upload.ts`. A caller that needs
|
|
411
|
+
* true byte granularity in a browser builds an `UploadManager` with an
|
|
412
|
+
* XHR-backed transport; a caller that wants to drive the three phases itself
|
|
413
|
+
* has {@link UploadManager.createBatch}, {@link UploadManager.putDirect},
|
|
414
|
+
* {@link UploadManager.attachBlobs} and the `multipart*` methods, all public
|
|
415
|
+
* for that purpose.
|
|
416
|
+
*/
|
|
417
|
+
upload(input: UploadInput, options?: OperationOptions): Promise<FsNode[]>;
|
|
334
418
|
/**
|
|
335
419
|
* `POST /fs_nodes/create_directories` - creates a subtree in one call.
|
|
336
420
|
*
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* in the same request.
|
|
12
12
|
*/
|
|
13
13
|
import { type ApiClient, Resource } from "../http";
|
|
14
|
-
import type {
|
|
14
|
+
import type { ListParams } from "../listing";
|
|
15
|
+
import type { FileInput, Id, Paginated, RequestOptions, Timestamp } from "../types";
|
|
15
16
|
/** Lifecycle of a ticket. */
|
|
16
17
|
export type TicketStatus = "open" | "closed";
|
|
17
18
|
/**
|
|
@@ -99,29 +100,37 @@ export interface CreateTicketInput {
|
|
|
99
100
|
readonly initialMessage?: string;
|
|
100
101
|
readonly context?: TicketContext;
|
|
101
102
|
/**
|
|
102
|
-
* Files to attach
|
|
103
|
-
*
|
|
103
|
+
* Files to attach, as bytes or as an already-encoded data URL. Bytes are
|
|
104
|
+
* base64-encoded here, so every attachment is buffered in memory.
|
|
104
105
|
*
|
|
105
|
-
*
|
|
106
|
+
* Server caps: {@link TICKET_MAX_ATTACHMENTS} files,
|
|
106
107
|
* {@link TICKET_MAX_ATTACHMENT_BYTES} in total, `image/*` and `video/*`
|
|
107
|
-
* only
|
|
108
|
-
*
|
|
109
|
-
* sending and raises rather than let a screenshot disappear.
|
|
108
|
+
* only. An attachment over a cap is dropped server-side with the ticket
|
|
109
|
+
* still answering 201, so the SDK validates first and throws instead.
|
|
110
110
|
*/
|
|
111
|
-
readonly attachments?: FileInput
|
|
111
|
+
readonly attachments?: ReadonlyArray<FileInput | TicketAttachmentDataUrl>;
|
|
112
|
+
}
|
|
113
|
+
/** An attachment that is already a `data:<mime>;base64,...` URL. */
|
|
114
|
+
export interface TicketAttachmentDataUrl {
|
|
115
|
+
readonly dataUrl: string;
|
|
116
|
+
readonly filename: string;
|
|
112
117
|
}
|
|
113
118
|
/** Fields that can change after a ticket exists. */
|
|
114
119
|
export interface UpdateTicketInput {
|
|
115
120
|
readonly status?: TicketStatus;
|
|
116
121
|
}
|
|
122
|
+
/** Filter columns of `GET /tickets`, on top of {@link BASE_FILTER_COLUMNS}. */
|
|
123
|
+
export declare const TICKET_FILTER_COLUMNS: readonly ["status", "user_id"];
|
|
117
124
|
/** Filters for {@link TicketsNamespace.list}. */
|
|
118
|
-
export interface ListTicketsParams extends
|
|
125
|
+
export interface ListTicketsParams extends ListParams<(typeof TICKET_FILTER_COLUMNS)[number]> {
|
|
119
126
|
readonly status?: TicketStatus;
|
|
120
127
|
/** Administrators only: someone else's tickets. */
|
|
121
128
|
readonly userId?: Id;
|
|
122
129
|
}
|
|
130
|
+
/** Filter columns of `GET /ticket_messages`, on top of {@link BASE_FILTER_COLUMNS}. */
|
|
131
|
+
export declare const TICKET_MESSAGE_FILTER_COLUMNS: readonly ["ticket_id", "sender_id", "created_at"];
|
|
123
132
|
/** Filters for {@link TicketMessagesNamespace.list}. */
|
|
124
|
-
export interface ListTicketMessagesParams extends
|
|
133
|
+
export interface ListTicketMessagesParams extends ListParams<(typeof TICKET_MESSAGE_FILTER_COLUMNS)[number]> {
|
|
125
134
|
readonly ticketId: TicketId;
|
|
126
135
|
}
|
|
127
136
|
/** Arguments for replying to a ticket. */
|
|
@@ -213,15 +222,94 @@ export declare class TicketsNamespace extends Resource {
|
|
|
213
222
|
/**
|
|
214
223
|
* `GET /tickets/:id/attachment/:blob_id` - one attachment's bytes.
|
|
215
224
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
225
|
+
* Unlike `account.picture` and `chests.entries.download`, this one CANNOT be
|
|
226
|
+
* asked for anonymously. The action resolves the ticket through
|
|
227
|
+
* `Ticket.viewable_by(Current.user)` and declares `oauth_scope
|
|
228
|
+
* "tickets:write"`, so a request with no credential is a 404 on someone
|
|
229
|
+
* else's ticket and a 401 on your own. The credential has to ride the first
|
|
230
|
+
* hop, which is exactly what makes this awkward.
|
|
231
|
+
*
|
|
232
|
+
* The awkward part. Rails answers `302` to `minio.omelhorsite.pt` and `fetch`
|
|
233
|
+
* follows it. Two different things then happen depending on how the client
|
|
234
|
+
* was built:
|
|
235
|
+
*
|
|
236
|
+
* - **token mode** (`credentials: "omit"`). The Fetch standard strips
|
|
237
|
+
* `Authorization` on a cross-origin redirect, so the store gets a clean
|
|
238
|
+
* presigned request. The hop also swaps the origin for an opaque one, so
|
|
239
|
+
* MinIO sees `Origin: null` and answers `Access-Control-Allow-Origin: *` -
|
|
240
|
+
* which an uncredentialed request accepts. This works, in a browser and
|
|
241
|
+
* out of one.
|
|
242
|
+
* - **cookie mode** (`sessionCookie: true`, the production web app). Same
|
|
243
|
+
* `Origin: null`, same `*` back, but now the request carries credentials,
|
|
244
|
+
* and wildcard plus credentials is illegal in CORS regardless of
|
|
245
|
+
* `Access-Control-Allow-Credentials`. The browser rejects the response and
|
|
246
|
+
* `fetch` rejects with an opaque "Failed to fetch".
|
|
247
|
+
*
|
|
248
|
+
* And it cannot be repaired the way the other two were, because the obvious
|
|
249
|
+
* repair does not exist in a browser: `redirect: "manual"` there yields an
|
|
250
|
+
* OPAQUE-REDIRECT response - status 0, empty header list - so `Location` is
|
|
251
|
+
* unreadable and the second hop cannot be re-issued without credentials.
|
|
252
|
+
* `redirect: "error"` and `XMLHttpRequest` are no better. Reading `Location`
|
|
253
|
+
* works only off-browser, where nothing was broken to begin with. The other
|
|
254
|
+
* two endpoints escape by needing no credential at all; this one has no such
|
|
255
|
+
* escape, and the backend exposes no `..._url` companion route the way
|
|
256
|
+
* `fs_nodes` does with `data_url`.
|
|
257
|
+
*
|
|
258
|
+
* So in a browser in cookie mode, use {@link attachmentUrl} and let the
|
|
259
|
+
* platform fetch the bytes - an `<img>`, a `<video>`, an `<a download>` make
|
|
260
|
+
* no-cors requests and follow the redirect with no CORS check at all. This
|
|
261
|
+
* method stays for every other context, and turns the browser failure into an
|
|
262
|
+
* error that says so instead of an unexplained network fault.
|
|
219
263
|
*
|
|
220
264
|
* Take `blobId` from {@link TicketAttachment.blob_id}; the filename and
|
|
221
265
|
* content type are on the same record, which is why this hands back bare
|
|
222
266
|
* bytes.
|
|
267
|
+
*
|
|
268
|
+
* @throws {OmsError} `unsupported` when the redirect was blocked by CORS,
|
|
269
|
+
* naming {@link attachmentUrl} as the way through.
|
|
270
|
+
* @throws {OmsApiError} 404 when the blob is not on that ticket, or the
|
|
271
|
+
* ticket is not yours.
|
|
223
272
|
*/
|
|
224
273
|
attachment(id: TicketId, blobId: number, options?: RequestOptions): Promise<Blob>;
|
|
274
|
+
/**
|
|
275
|
+
* Absolute URL for one attachment, for an `<img>`, an `<a download>` or a new
|
|
276
|
+
* tab. In a browser in cookie mode this is the ONLY way to get at the bytes;
|
|
277
|
+
* see {@link attachment} for why.
|
|
278
|
+
*
|
|
279
|
+
* Asynchronous, unlike `account.pictureUrl` and
|
|
280
|
+
* `chests.entries.downloadUrl`, and the difference is not an accident: those
|
|
281
|
+
* two routes are anonymous, this one is not, so a credential has to be
|
|
282
|
+
* resolved before the URL means anything. Resolving it may involve a token
|
|
283
|
+
* refresh, which is why it cannot be a getter.
|
|
284
|
+
*
|
|
285
|
+
* How the URL authenticates depends on the client, matching what the backend
|
|
286
|
+
* accepts (`Session.candidate_tokens` reads the `Authorization` header, then
|
|
287
|
+
* `?token=`, then the `oms_session` cookie):
|
|
288
|
+
*
|
|
289
|
+
* - **cookie mode**: no credential in the URL. The browser attaches the
|
|
290
|
+
* `oms_session` cookie itself. It is host-only on the API host and
|
|
291
|
+
* `SameSite=Lax`, so this works from a page on `omelhorsite.pt` or another
|
|
292
|
+
* host under it, and from nowhere else. Do not add `crossorigin` to the
|
|
293
|
+
* element: it turns a no-cors load into a CORS one and re-creates the exact
|
|
294
|
+
* failure this method exists to avoid.
|
|
295
|
+
* - **token mode**: the token is appended as `?token=`. Rails accepts a query
|
|
296
|
+
* credential on API routes precisely because a native client has no cookie
|
|
297
|
+
* jar and an `<img>` cannot carry a header.
|
|
298
|
+
*
|
|
299
|
+
* THE TOKEN-MODE URL CONTAINS A LIVE CREDENTIAL. It goes into the DOM, into
|
|
300
|
+
* the server access log, and into anything that records URLs. Build it at the
|
|
301
|
+
* moment of use, never store it, never log it, and never put it somewhere
|
|
302
|
+
* another person can read - anyone holding it holds the session until it is
|
|
303
|
+
* revoked. When the URL is destined for something outside your own page,
|
|
304
|
+
* fetch the bytes with {@link attachment} and hand over a `blob:` URL
|
|
305
|
+
* instead.
|
|
306
|
+
*
|
|
307
|
+
* ```tsx
|
|
308
|
+
* const src = await oms.tickets.attachmentUrl(ticket.id, file.blob_id);
|
|
309
|
+
* <img src={src} alt={file.filename} />
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
312
|
+
attachmentUrl(id: TicketId, blobId: number): Promise<string>;
|
|
225
313
|
}
|
|
226
314
|
/**
|
|
227
315
|
* Encodes a file as a `data:<mime>;base64,<...>` URI.
|
|
@@ -14,10 +14,25 @@
|
|
|
14
14
|
import { Resource } from "../../http";
|
|
15
15
|
import type { FileInput, Id, RequestOptions } from "../../types";
|
|
16
16
|
import { type ToolCaptcha, type ToolJobHandle, type ToolRecord, type ToolRunOptions } from "./index";
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* A background removal run.
|
|
19
|
+
*
|
|
20
|
+
* Both routes that answer with one - `POST /background_removals` and
|
|
21
|
+
* `GET /background_removals/:id` - render the `:extended` view, so `result_url`
|
|
22
|
+
* is always PRESENT and simply `null` until the run completes.
|
|
23
|
+
*
|
|
24
|
+
* `progress_percent`, inherited from {@link ToolRecord}, is never sent for this
|
|
25
|
+
* tool: `BackgroundRemovalBlueprint` has no such field. Progress lives on the
|
|
26
|
+
* {@link Job} row that {@link BackgroundRemovalCreated.job_id} names.
|
|
27
|
+
*/
|
|
18
28
|
export interface BackgroundRemoval extends ToolRecord {
|
|
19
|
-
/**
|
|
20
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Signed URL of the cut-out PNG, or `null` - for the run not having
|
|
31
|
+
* finished, for it having failed, or for the 24-hour sweep having taken the
|
|
32
|
+
* attachment. {@link BackgroundRemovalNamespace.resultUrl} tells the three
|
|
33
|
+
* apart.
|
|
34
|
+
*/
|
|
35
|
+
readonly result_url: string | null;
|
|
21
36
|
}
|
|
22
37
|
/** What `POST /background_removals` answers with. */
|
|
23
38
|
export type BackgroundRemovalCreated = BackgroundRemoval & ToolJobHandle;
|