@pipelex/sdk 0.49.0 → 0.50.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.
@@ -1,21 +1,93 @@
1
1
  /**
2
- * `uploadWithGrant` — send one file straight to Pipelex storage with an upload
3
- * grant (`PipelexApiClient.requestUploadGrant`, `POST /v1/upload/grant`). The
4
- * side that holds the credential requests the grant; the side that holds the
5
- * bytes, typically a browser page, sends them with this. See
2
+ * `describeUpload` and `uploadWithGrant` — the two halves of an upload grant
3
+ * (`PipelexApiClient.requestUploadGrant`, `POST /v1/upload/grant`) that the side
4
+ * holding the bytes runs, typically a browser page. That side describes the file,
5
+ * its SHA-256 included, with `describeUpload`; the side that holds the credential
6
+ * requests the grant for that description; and the bytes go to storage with
7
+ * `uploadWithGrant`, unless the grant says the organization already holds them. See
6
8
  * `docs/input-preparation.md`.
7
9
  *
8
10
  * **This module is the browser-safe entry, `@pipelex/sdk/upload`.** Its runtime
9
11
  * import graph must never reach a Node builtin, `undici` or the client: a page
10
12
  * bundles it as is, with nothing marked external. It imports the error classes,
11
- * the timer bound in `timers.ts` and the wire types only, and
12
- * `tests/upload-grant.test.ts` bundles it for the browser with esbuild to hold it
13
- * to that.
13
+ * the timer bound in `timers.ts` and the wire types only, hashes with Web Crypto,
14
+ * and `tests/upload-grant.test.ts` bundles it for the browser with esbuild to hold
15
+ * it to that.
14
16
  */
15
17
  import type { UploadGrant } from "./product-models.js";
16
- export type { UploadGrant, UploadGrantInput } from "./product-models.js";
18
+ export type { UploadGrant, UploadGrantInput, UploadKind } from "./product-models.js";
17
19
  export { InputPreparationError, RejectedAssetError, UploadTransportError } from "./errors.js";
18
20
  export type { RejectedAssetCode, UploadTransportCode } from "./errors.js";
21
+ /** Per-call options for {@link describeUpload}. */
22
+ export interface DescribeUploadOptions {
23
+ /** The file's name, replacing a `File`'s own; required for a nameless `Blob`. */
24
+ filename?: string;
25
+ /**
26
+ * The largest file to hash, in bytes, 52428800 (50 MiB, the platform's default limit on
27
+ * one upload) when omitted. Hashing reads the whole file into memory, so a larger file
28
+ * is described without its SHA-256 and never read. Where the deployment's limit is no
29
+ * larger, its grant request then meets the grant route's own `413`. On a deployment
30
+ * whose grants report a larger `max_bytes`, a file between the two is granted without
31
+ * a hash: it is uploaded under a fresh name, with no deduplication and no checksum for
32
+ * storage to hold the body to, and the description's absent `sha256` is how a caller
33
+ * sees it. Pass that `max_bytes` to hash up to it. It must be a positive number.
34
+ */
35
+ maxBytes?: number;
36
+ }
37
+ /**
38
+ * A file described for `requestUploadGrant`, as {@link describeUpload} reads it: every
39
+ * field the grant request takes to describe a file, ready to send as is.
40
+ */
41
+ export interface UploadDescription {
42
+ /** The file's name: the `File`'s own, or the `filename` option for a nameless `Blob`. */
43
+ filename: string;
44
+ /** The type the runtime gives the file, `Blob.type`: empty when it knows none. */
45
+ content_type: string;
46
+ /** The file's exact size in bytes. */
47
+ size: number;
48
+ /**
49
+ * The SHA-256 of the file's bytes, 64 lowercase hexadecimal digits. Absent in two
50
+ * cases, where the grant then names a new object and asks for the bytes, as it does
51
+ * for every request with no hash: where the runtime offers no Web Crypto digest, which
52
+ * a browser withholds from a page served over plain `http` from any host but the local
53
+ * one, and for a file larger than `describeUpload`'s `maxBytes`, which is never read.
54
+ * The grant route refuses such a file's size with its own `413` only where the
55
+ * deployment's limit is no larger than `maxBytes`; under a larger limit the file is
56
+ * granted and uploaded without its hash, so without deduplication.
57
+ */
58
+ sha256?: string;
59
+ }
60
+ /**
61
+ * Describe a file for `requestUploadGrant`: its name, its type, its size and the
62
+ * SHA-256 of its bytes, computed with Web Crypto. A grant asked for with the hash
63
+ * names the object after the bytes, so a file the organization already holds is
64
+ * answered `stored: true` and never sent again, and storage refuses a body whose
65
+ * bytes differ from the ones described.
66
+ *
67
+ * The side holding the bytes runs this, so a page describes the file and hands the
68
+ * description to the server that holds the key, which passes it to
69
+ * `requestUploadGrant` unchanged; the bytes never cross that server. A nameless
70
+ * `Blob` takes its name from `options.filename`, which also replaces a `File`'s own.
71
+ *
72
+ * The whole file is read into memory once to hash it, so only a file no larger than
73
+ * `options.maxBytes` is, 50 MiB by default: a larger one, such as a video of several
74
+ * gigabytes picked by mistake, would freeze or crash a browser tab before the grant
75
+ * route could refuse its size. It is described without `sha256` and never read. Where
76
+ * the deployment's limit is no larger than `maxBytes`, as with the default on a
77
+ * deployment at the platform's default limit, the grant route's `413` refuses it with its
78
+ * own message. On a deployment whose grants report a larger `max_bytes`, a file between
79
+ * the two is granted without the hash and uploaded under a fresh name, with no
80
+ * deduplication and no checksum for storage to hold the body to: the absent `sha256` on
81
+ * the description is how the caller sees it, and passing that `max_bytes` as `maxBytes`
82
+ * hashes up to it. Where the runtime offers no Web
83
+ * Crypto digest, the file is described without `sha256` too, and the upload goes on
84
+ * without the hash.
85
+ *
86
+ * A file that cannot be read, such as one changed on disk since it was picked, throws an
87
+ * `InputPreparationError` whose `cause` is the runtime's error; a file with no name and a
88
+ * `maxBytes` that is not a positive number throw one before anything is read.
89
+ */
90
+ export declare function describeUpload(file: Blob, options?: DescribeUploadOptions): Promise<UploadDescription>;
19
91
  /** Per-call options for {@link uploadWithGrant}. */
20
92
  export interface UploadWithGrantOptions {
21
93
  /**
@@ -33,56 +105,78 @@ export interface UploadWithGrantOptions {
33
105
  */
34
106
  timeoutMs?: number;
35
107
  }
36
- /** What a successful {@link uploadWithGrant} stored. */
108
+ /** What a successful {@link uploadWithGrant} leaves in storage. */
37
109
  export interface GrantedUpload {
38
- /** The grant's `pipelex-storage://` reference, which now names the stored object. */
110
+ /** The grant's `pipelex-storage://` reference, which now names the stored file. */
39
111
  uri: string;
112
+ /**
113
+ * The file's record, the grant's `file_id`. Absent when the grant carried none, as from a
114
+ * deployment that predates file records.
115
+ */
116
+ file_id?: string;
40
117
  }
41
118
  /**
42
- * `PUT` a file to storage with an upload grant, and return the reference it now
43
- * carries. The file goes as the raw body with the grant's signed headers
44
- * unchanged; the body sets `Content-Length`, which the grant signed, so the file
45
- * must be exactly the size the grant was requested for.
119
+ * Store a file with an upload grant, and return the reference it now carries.
120
+ *
121
+ * **A grant that says `stored: true` sends nothing**: the organization already holds
122
+ * these bytes, so the call returns the grant's `uri` and `file_id` at once, without
123
+ * reading the file or reaching storage. Otherwise the file goes to the grant's `url` as
124
+ * the raw body of a `PUT`, with the grant's signed headers unchanged; the body sets
125
+ * `Content-Length`, which the grant signed, so the file must be exactly the size the
126
+ * grant was requested for, and when the grant was requested with the file's SHA-256,
127
+ * storage holds the body to it.
128
+ *
129
+ * Two answers from storage are no failure. A `412` refuses the create-only `PUT`
130
+ * because the object already exists: when the grant names the file by its bytes (it
131
+ * signs `x-amz-checksum-sha256`), that object can only hold those bytes, stored
132
+ * meanwhile by another upload of them or by the platform's own copy of a run's file
133
+ * that holds them, so the call succeeds with the grant's `uri` and `file_id`, the answer
134
+ * a `stored: true` grant gives. A `409 ConditionalRequestConflict`
135
+ * says another write of the object was in flight, so the `PUT` is sent again after a
136
+ * short pause, a few times at most.
46
137
  *
47
138
  * Storage's refusals map onto the input-preparation errors, so a caller that
48
139
  * already handles `uploadFile` needs no new case:
49
140
  *
50
- * - `RejectedAssetError` (`status` is storage's, `code` says why) — a `412` when
51
- * the grant was already used (`grant_used`: a grant writes one object, once); a
52
- * `403` when the file's size, type or metadata differ from what the grant signed
53
- * (`signature_mismatch`), when the request carried a header the grant did not sign
54
- * (`unsigned_header`), or when the grant has expired (`grant_expired`); any other
55
- * `4xx` but a `408` or a `429` (`store_refused`). Each asks for a new grant, or for
56
- * the file the grant was requested for.
141
+ * - `RejectedAssetError` (`status` is storage's, `code` says why) — a `412` for a
142
+ * grant that names no bytes, when the grant was already used (`grant_used`: such a
143
+ * grant writes one object, once); a `403` when the file's size, type or metadata
144
+ * differ from what the grant signed (`signature_mismatch`), when the request carried
145
+ * a header the grant did not sign (`unsigned_header`), or when the grant has expired
146
+ * (`grant_expired`); any other `4xx` but a `408` or a `429` (`store_refused`), a
147
+ * `400 BadDigest` among them, for bytes other than the ones whose SHA-256 the grant
148
+ * was requested with. Each asks for a new grant, or for the file the grant was
149
+ * requested for.
57
150
  * - `UploadTransportError` (`status` is storage's when it answered, `code` says
58
151
  * which) — the time limit running out (`timeout`), storage unreachable
59
152
  * (`unreachable`), a `5xx` (`server_error`), storage timing out on the body
60
- * (`storage_timeout`, a `400 RequestTimeout`, which wrote nothing), another upload
61
- * with the same grant still in progress (`conflict`, a `409
153
+ * (`storage_timeout`, a `400 RequestTimeout`, which wrote nothing), another write of
154
+ * the object still in progress after every attempt (`conflict`, a `409
62
155
  * ConditionalRequestConflict`), storage timing out or throttling the request
63
156
  * (`unexpected`, a `408` or a `429`, refused for its timing rather than for the
64
157
  * file), or a redirect, which is refused rather than followed (`redirected`). In a
65
158
  * browser a refused cross-origin request looks like an unreachable host, so the
66
- * message names that too. A grant whose `url` is not an absolute `http(s)` URL free
67
- * of user info is refused the same way, before anything is sent
68
- * (`invalid_grant_url`).
159
+ * message names that too. A grant that is not stored and whose `url` is not an
160
+ * absolute `http(s)` URL free of user info is refused the same way, before anything
161
+ * is sent (`invalid_grant_url`).
69
162
  *
70
- * The whole exchange runs under a time limit: `timeoutMs` when given, else 60 s plus
71
- * 1 s for every started 128 KiB of the file. A caller's `signal` can end it sooner,
72
- * and its abort propagates as the signal's own reason, unwrapped, even when the time
73
- * limit ran out too. Only the first 16 KiB of storage's error body are read, and when
74
- * the limit runs out while that body is still arriving, storage has already answered:
75
- * the call settles as that answer, classified from its status and whatever part of the
76
- * body arrived.
163
+ * The whole exchange runs under a time limit, the pauses after a conflict included:
164
+ * `timeoutMs` when given, else 60 s plus 1 s for every started 128 KiB of the file. A
165
+ * caller's `signal` can end it sooner, and its abort propagates as the signal's own
166
+ * reason, unwrapped, even when the time limit ran out too. Only the first 16 KiB of
167
+ * storage's error body are read, and when the limit runs out while that body is still
168
+ * arriving, or during the pause after a conflict, storage has already answered: the
169
+ * call settles as that answer, classified from its status and whatever part of the body
170
+ * arrived.
77
171
  *
78
172
  * A timeout, a `5xx`, a conflict and a connection lost after the file went out leave
79
173
  * it unknown whether the object was written: retrying with the same grant before it
80
- * expires either stores it or answers the `412` of a used grant, and then the
81
- * grant's `uri` already names the file. After a `408` or a `429` the same retry
82
- * applies, later, since storage refused the request for its timing. A `501` is the
83
- * exception: storage does not implement the request it was sent, stored nothing, and
84
- * answers a retry the same way, so its error is not retryable. The grant is a bearer
85
- * capability: nothing here logs it, and no error this throws carries its URL,
86
- * storage's error body or a runtime error that could hold either.
174
+ * expires either stores it or finds it stored, and then the grant's `uri` names the
175
+ * file. After a `408` or a `429` the same retry applies, later, since storage refused
176
+ * the request for its timing. A `501` is the exception: storage does not implement the
177
+ * request it was sent, stored nothing, and answers a retry the same way, so its error
178
+ * is not retryable. The grant is a bearer capability: nothing here logs it, and no
179
+ * error this throws carries its URL, storage's error body or a runtime error that
180
+ * could hold either.
87
181
  */
88
182
  export declare function uploadWithGrant(grant: UploadGrant, file: Blob, options?: UploadWithGrantOptions): Promise<GrantedUpload>;