@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.
- package/dist/index.js +4939 -552
- package/dist/types/client.d.ts +60 -3
- package/dist/types/http.d.ts +444 -19
- package/dist/types/index.d.ts +4 -1
- package/dist/types/resources/account.d.ts +66 -3
- package/dist/types/resources/admin.d.ts +1837 -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 +847 -0
- package/dist/types/resources/chests.d.ts +54 -3
- package/dist/types/resources/content.d.ts +2970 -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 +62 -21
- package/dist/types/resources/library.d.ts +1435 -0
- package/dist/types/resources/linkTrees.d.ts +142 -30
- package/dist/types/resources/media.d.ts +351 -0
- package/dist/types/resources/movies.d.ts +1186 -0
- package/dist/types/resources/music/artists.d.ts +1066 -0
- package/dist/types/resources/music/imports.d.ts +940 -0
- package/dist/types/resources/music/index.d.ts +61 -0
- package/dist/types/resources/music/playlists.d.ts +1026 -0
- package/dist/types/resources/music/social.d.ts +1132 -0
- package/dist/types/resources/music/songs.d.ts +1183 -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 +45 -4
- package/dist/types/resources/social.d.ts +1330 -0
- package/dist/types/resources/storage/upload.d.ts +158 -11
- package/dist/types/resources/storage.d.ts +88 -22
- package/dist/types/resources/tickets.d.ts +82 -3
- 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,51 @@
|
|
|
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 { BaseRecord, FileOutput, Id, OperationOptions, Paginated, PageParams, RequestOptions } from "../types";
|
|
32
46
|
import type { User } from "./account";
|
|
33
47
|
import { StorageRateGate, UploadManager } from "./storage/upload";
|
|
48
|
+
import type { UploadInput } from "./storage/upload";
|
|
34
49
|
/** What a node is. */
|
|
35
50
|
export type FsNodeKind = "file" | "directory";
|
|
36
51
|
/**
|
|
37
52
|
* A node in the virtual filesystem.
|
|
38
53
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* `
|
|
43
|
-
*
|
|
54
|
+
* This is the WHOLE record, and it was checked against the blueprint CHAIN
|
|
55
|
+
* rather than against the table, because those two disagree.
|
|
56
|
+
* `FsNodeBlueprint` declares `name`, `parent_id`, `kind`, `size`,
|
|
57
|
+
* `max_size`; it extends `ApplicationBlueprint`, which contributes `id`,
|
|
58
|
+
* `created_at` and `updated_at`; and its `:extended` view has an EMPTY body,
|
|
59
|
+
* which in Blueprinter inherits the base fields rather than emitting nothing.
|
|
60
|
+
* So `list`, `get`, `create` and `update` all answer the same eight fields.
|
|
61
|
+
* Reading a view name and assuming it adds something has already produced bugs
|
|
62
|
+
* in this repo - follow the `<` before deciding a field does or does not exist.
|
|
63
|
+
*
|
|
64
|
+
* The `fs_nodes` TABLE is wider than that, and the difference is never sent:
|
|
65
|
+
* `creator_id`, `updater_id`, `destroyer_id`, `signed_url_generated`,
|
|
66
|
+
* `is_vault_root` and the `id_path` ltree are all real columns that appear in
|
|
67
|
+
* no view. Declaring them client-side is worse than leaving them out, because
|
|
68
|
+
* every later reader then believes they arrive.
|
|
69
|
+
*
|
|
70
|
+
* There is no `data` and no `url` field either: bytes are reached through
|
|
71
|
+
* {@link StorageNamespace.download}, {@link StorageNamespace.downloadStream} or
|
|
72
|
+
* {@link StorageNamespace.downloadUrl}, never off the record. And no
|
|
73
|
+
* `content_type` and no `path` - the type is decided from the name at download
|
|
74
|
+
* time, and the `path` column was dropped in the ltree migration.
|
|
44
75
|
*/
|
|
45
76
|
export interface FsNode extends BaseRecord {
|
|
46
77
|
readonly name: string;
|
|
@@ -109,6 +140,12 @@ export interface ListFsNodesParams extends PageParams {
|
|
|
109
140
|
/**
|
|
110
141
|
* Also return the parent itself, so one call gets both the folder's metadata
|
|
111
142
|
* and its children. It occupies a slot on the page like any other row.
|
|
143
|
+
*
|
|
144
|
+
* IGNORED when `parentId` is `null`, and that is a correction rather than a
|
|
145
|
+
* convenience: the server-side filter behind this flag cannot express "no
|
|
146
|
+
* parent" at all, and asking it to used to answer with the caller's whole
|
|
147
|
+
* tree. The roots have no folder to fold in anyway. The detail is on
|
|
148
|
+
* {@link StorageNamespace.list}.
|
|
112
149
|
*/
|
|
113
150
|
readonly includeSelf?: boolean;
|
|
114
151
|
}
|
|
@@ -252,13 +289,39 @@ export declare class StorageNamespace extends Resource {
|
|
|
252
289
|
*/
|
|
253
290
|
roots(options?: RequestOptions): Promise<FsRoots>;
|
|
254
291
|
/**
|
|
255
|
-
* `GET /fs_nodes` - the children of a directory
|
|
292
|
+
* `GET /fs_nodes` - the children of a directory, or the roots when
|
|
293
|
+
* `parentId` is `null`.
|
|
256
294
|
*
|
|
257
295
|
* Anonymous callers get an empty listing, always: the listing scope is the
|
|
258
296
|
* caller's own tree plus what was explicitly shared with them, and a public
|
|
259
297
|
* grant is deliberately NOT enumerable. Reach a publicly-shared node by id
|
|
260
298
|
* with {@link get} or {@link shared} instead.
|
|
261
299
|
*
|
|
300
|
+
* TWO server-side filters address a directory and they are NOT
|
|
301
|
+
* interchangeable. That asymmetry is the whole subtlety of this method:
|
|
302
|
+
*
|
|
303
|
+
* - `exact_search[parent_id]` reaches `Searchable.exact_search`, which is a
|
|
304
|
+
* bare `where(params)`. The controller has already rewritten a `\b` value
|
|
305
|
+
* to `nil` by then, so the sentinel lands as `WHERE parent_id IS NULL` -
|
|
306
|
+
* and since `FsNode.root_nodes` is exactly `where(parent_id: nil)`, this is
|
|
307
|
+
* the only filter in the API that can say "the roots". The ltree
|
|
308
|
+
* `id_path` is the source of truth for ANCESTRY, but rootness is still a
|
|
309
|
+
* null `parent_id`.
|
|
310
|
+
* - `extra_options[parent_id]` reaches `QueryExtraOptions::FsNodes`, which
|
|
311
|
+
* runs `where(parent_id: x).or(where(id: x))` and so folds the folder
|
|
312
|
+
* itself back into its own listing. Convenient - and guarded by
|
|
313
|
+
* `return unless params[:parent_id].present?`, with that same `\b` -> `nil`
|
|
314
|
+
* rewrite happening first. Hand it the sentinel and the guard drops the
|
|
315
|
+
* filter IN SILENCE. The request still answers 200; it just answers with
|
|
316
|
+
* the caller's ENTIRE listable tree, page after page, instead of three
|
|
317
|
+
* rows. It is the same failure shape as the `inside_path` incident that
|
|
318
|
+
* `reject_unknown_filter_keys!` was written for, except this key IS known,
|
|
319
|
+
* so nothing rejects it.
|
|
320
|
+
*
|
|
321
|
+
* Hence `includeSelf` is honoured under a real directory and ignored at the
|
|
322
|
+
* top of the tree. Not client-side taste: it is the only combination the
|
|
323
|
+
* server can actually express.
|
|
324
|
+
*
|
|
262
325
|
* The endpoint is conditional-GET aware and answers 304 to a matching
|
|
263
326
|
* `If-None-Match`. The SDK never sends one, and asks the runtime not to
|
|
264
327
|
* revalidate on its own, because a 304 has no body and would surface here as
|
|
@@ -312,25 +375,28 @@ export declare class StorageNamespace extends Resource {
|
|
|
312
375
|
* intake, presigns, sends the bytes straight to object storage, binds the
|
|
313
376
|
* blobs and reads the finished nodes back.
|
|
314
377
|
*
|
|
315
|
-
* Files at or above 32 MiB
|
|
316
|
-
*
|
|
317
|
-
*
|
|
378
|
+
* Files at or above `MULTIPART_THRESHOLD` (32 MiB, exported from this
|
|
379
|
+
* package) take the multipart path automatically, and that is not tuning:
|
|
380
|
+
* the object store sits behind Cloudflare with a request-body cap around
|
|
381
|
+
* 100 MB, so it is the only way a large file gets in at all.
|
|
318
382
|
*
|
|
319
383
|
* A per-file rejection - a quota that ran out, a name that collides with a
|
|
320
384
|
* directory - does not throw. It comes back in
|
|
321
385
|
* {@link UploadManager.upload}'s results, which is why that method is the one
|
|
322
386
|
* 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
|
-
|
|
387
|
+
* that landed, and throws only when EVERY file was refused.
|
|
388
|
+
*
|
|
389
|
+
* `options.onProgress` counts bytes across the whole run and only ever
|
|
390
|
+
* climbs. It ticks once per finished direct PUT and once per finished
|
|
391
|
+
* multipart part, never per byte - `fetch` has no upload-progress event, and
|
|
392
|
+
* the alternatives are argued out in `storage/upload.ts`. A caller that needs
|
|
393
|
+
* true byte granularity in a browser builds an `UploadManager` with an
|
|
394
|
+
* XHR-backed transport; a caller that wants to drive the three phases itself
|
|
395
|
+
* has {@link UploadManager.createBatch}, {@link UploadManager.putDirect},
|
|
396
|
+
* {@link UploadManager.attachBlobs} and the `multipart*` methods, all public
|
|
397
|
+
* for that purpose.
|
|
398
|
+
*/
|
|
399
|
+
upload(input: UploadInput, options?: OperationOptions): Promise<FsNode[]>;
|
|
334
400
|
/**
|
|
335
401
|
* `POST /fs_nodes/create_directories` - creates a subtree in one call.
|
|
336
402
|
*
|
|
@@ -213,15 +213,94 @@ export declare class TicketsNamespace extends Resource {
|
|
|
213
213
|
/**
|
|
214
214
|
* `GET /tickets/:id/attachment/:blob_id` - one attachment's bytes.
|
|
215
215
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
216
|
+
* Unlike `account.picture` and `chests.entries.download`, this one CANNOT be
|
|
217
|
+
* asked for anonymously. The action resolves the ticket through
|
|
218
|
+
* `Ticket.viewable_by(Current.user)` and declares `oauth_scope
|
|
219
|
+
* "tickets:write"`, so a request with no credential is a 404 on someone
|
|
220
|
+
* else's ticket and a 401 on your own. The credential has to ride the first
|
|
221
|
+
* hop, which is exactly what makes this awkward.
|
|
222
|
+
*
|
|
223
|
+
* The awkward part. Rails answers `302` to `minio.omelhorsite.pt` and `fetch`
|
|
224
|
+
* follows it. Two different things then happen depending on how the client
|
|
225
|
+
* was built:
|
|
226
|
+
*
|
|
227
|
+
* - **token mode** (`credentials: "omit"`). The Fetch standard strips
|
|
228
|
+
* `Authorization` on a cross-origin redirect, so the store gets a clean
|
|
229
|
+
* presigned request. The hop also swaps the origin for an opaque one, so
|
|
230
|
+
* MinIO sees `Origin: null` and answers `Access-Control-Allow-Origin: *` -
|
|
231
|
+
* which an uncredentialed request accepts. This works, in a browser and
|
|
232
|
+
* out of one.
|
|
233
|
+
* - **cookie mode** (`sessionCookie: true`, the production web app). Same
|
|
234
|
+
* `Origin: null`, same `*` back, but now the request carries credentials,
|
|
235
|
+
* and wildcard plus credentials is illegal in CORS regardless of
|
|
236
|
+
* `Access-Control-Allow-Credentials`. The browser rejects the response and
|
|
237
|
+
* `fetch` rejects with an opaque "Failed to fetch".
|
|
238
|
+
*
|
|
239
|
+
* And it cannot be repaired the way the other two were, because the obvious
|
|
240
|
+
* repair does not exist in a browser: `redirect: "manual"` there yields an
|
|
241
|
+
* OPAQUE-REDIRECT response - status 0, empty header list - so `Location` is
|
|
242
|
+
* unreadable and the second hop cannot be re-issued without credentials.
|
|
243
|
+
* `redirect: "error"` and `XMLHttpRequest` are no better. Reading `Location`
|
|
244
|
+
* works only off-browser, where nothing was broken to begin with. The other
|
|
245
|
+
* two endpoints escape by needing no credential at all; this one has no such
|
|
246
|
+
* escape, and the backend exposes no `..._url` companion route the way
|
|
247
|
+
* `fs_nodes` does with `data_url`.
|
|
248
|
+
*
|
|
249
|
+
* So in a browser in cookie mode, use {@link attachmentUrl} and let the
|
|
250
|
+
* platform fetch the bytes - an `<img>`, a `<video>`, an `<a download>` make
|
|
251
|
+
* no-cors requests and follow the redirect with no CORS check at all. This
|
|
252
|
+
* method stays for every other context, and turns the browser failure into an
|
|
253
|
+
* error that says so instead of an unexplained network fault.
|
|
219
254
|
*
|
|
220
255
|
* Take `blobId` from {@link TicketAttachment.blob_id}; the filename and
|
|
221
256
|
* content type are on the same record, which is why this hands back bare
|
|
222
257
|
* bytes.
|
|
258
|
+
*
|
|
259
|
+
* @throws {OmsError} `unsupported` when the redirect was blocked by CORS,
|
|
260
|
+
* naming {@link attachmentUrl} as the way through.
|
|
261
|
+
* @throws {OmsApiError} 404 when the blob is not on that ticket, or the
|
|
262
|
+
* ticket is not yours.
|
|
223
263
|
*/
|
|
224
264
|
attachment(id: TicketId, blobId: number, options?: RequestOptions): Promise<Blob>;
|
|
265
|
+
/**
|
|
266
|
+
* Absolute URL for one attachment, for an `<img>`, an `<a download>` or a new
|
|
267
|
+
* tab. In a browser in cookie mode this is the ONLY way to get at the bytes;
|
|
268
|
+
* see {@link attachment} for why.
|
|
269
|
+
*
|
|
270
|
+
* Asynchronous, unlike `account.pictureUrl` and
|
|
271
|
+
* `chests.entries.downloadUrl`, and the difference is not an accident: those
|
|
272
|
+
* two routes are anonymous, this one is not, so a credential has to be
|
|
273
|
+
* resolved before the URL means anything. Resolving it may involve a token
|
|
274
|
+
* refresh, which is why it cannot be a getter.
|
|
275
|
+
*
|
|
276
|
+
* How the URL authenticates depends on the client, matching what the backend
|
|
277
|
+
* accepts (`Session.candidate_tokens` reads the `Authorization` header, then
|
|
278
|
+
* `?token=`, then the `oms_session` cookie):
|
|
279
|
+
*
|
|
280
|
+
* - **cookie mode**: no credential in the URL. The browser attaches the
|
|
281
|
+
* `oms_session` cookie itself. It is host-only on the API host and
|
|
282
|
+
* `SameSite=Lax`, so this works from a page on `omelhorsite.pt` or another
|
|
283
|
+
* host under it, and from nowhere else. Do not add `crossorigin` to the
|
|
284
|
+
* element: it turns a no-cors load into a CORS one and re-creates the exact
|
|
285
|
+
* failure this method exists to avoid.
|
|
286
|
+
* - **token mode**: the token is appended as `?token=`. Rails accepts a query
|
|
287
|
+
* credential on API routes precisely because a native client has no cookie
|
|
288
|
+
* jar and an `<img>` cannot carry a header.
|
|
289
|
+
*
|
|
290
|
+
* THE TOKEN-MODE URL CONTAINS A LIVE CREDENTIAL. It goes into the DOM, into
|
|
291
|
+
* the server access log, and into anything that records URLs. Build it at the
|
|
292
|
+
* moment of use, never store it, never log it, and never put it somewhere
|
|
293
|
+
* another person can read - anyone holding it holds the session until it is
|
|
294
|
+
* revoked. When the URL is destined for something outside your own page,
|
|
295
|
+
* fetch the bytes with {@link attachment} and hand over a `blob:` URL
|
|
296
|
+
* instead.
|
|
297
|
+
*
|
|
298
|
+
* ```tsx
|
|
299
|
+
* const src = await oms.tickets.attachmentUrl(ticket.id, file.blob_id);
|
|
300
|
+
* <img src={src} alt={file.filename} />
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
attachmentUrl(id: TicketId, blobId: number): Promise<string>;
|
|
225
304
|
}
|
|
226
305
|
/**
|
|
227
306
|
* 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;
|