@ultimat3/storage 20.2.1 → 21.0.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/CLAUDE.md CHANGED
@@ -273,5 +273,16 @@ Gotchas:
273
273
  `sweepOrphans` cannot be tested against it with a frozen clock — `attachment.test.ts` uses a
274
274
  stub driver with authored timestamps, and `driver-local.test.ts` proves the disk half.
275
275
  - `upload-client.ts` defaults to `XMLHttpRequest`, not `fetch`: `fetch` reports no upload
276
- progress in any shipping browser, and a bar that jumps 0 → 100 is a bar that is lying.
276
+ progress in any shipping browser, and a bar that jumps 0 → 100 is a bar that is lying. It is
277
+ **the declared XHR seam** of plan 101 (decision 13): the one `XMLHttpRequest` the framework
278
+ ships, and the one file slice 15's `browser-transport` guard exempts for it — the header says so.
279
+ - **`fetchSignedPut` goes through `@ultimat3/core`'s `clientTransport`** (`As of 2026-09-22`,
280
+ plan 101 slice 05): `rawBody` sends the `Blob` verbatim under the grant's content type with no
281
+ default header, and `decodeError` keeps a refusal `X_STORAGE_UPLOAD_FAILED` with the disk's
282
+ status. A request with no response at all is the transport's `X_CLIENT_TRANSPORT_FAILED` — it was a
283
+ bare `TypeError` before. This file calls no `fetch`; the XHR above is the one exemption.
284
+ - **Bundle, `bun build --target=browser --minify`, `import { uploadFile } from '@ultimat3/storage'`**
285
+ (`As of 2026-09-22`): **11,822 B** at 20.2.1, **17,767 B** on `clientTransport`. The +5.9 kB is
286
+ core's transport graph, paid only where the chunk did not already carry it — an island that
287
+ also calls an action or a query already does.
277
288
  - `exactOptionalPropertyTypes` is on — declare optional fields as `x?: T | undefined`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/storage",
3
- "version": "20.2.1",
3
+ "version": "21.0.0",
4
4
  "description": "Named disks over Bun.file and Bun.s3: safe keys, signed URLs, sniffed uploads",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,6 +31,6 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/core": "20.2.1"
34
+ "@ultimat3/core": "21.0.0"
35
35
  }
36
36
  }
@@ -2,10 +2,14 @@
2
2
  // bytes at it, hand back the storage key. Browser-safe by construction: no `Bun.*`, no `node:`,
3
3
  // no driver import, so this file bundles into the client the way `@ultimat3/action`'s does.
4
4
  //
5
- // The default transport is `XMLHttpRequest`, not `fetch`, for exactly one reason: `fetch` reports
6
- // no upload progress in any shipping browser, and a progress bar that jumps 0 -> 100 is a
7
- // progress bar that is lying. `fetch` is the fallback where XHR does not exist.
8
-
5
+ // THE DECLARED XHR SEAM (plan 101, decision 13): every other browser request goes through
6
+ // `@ultimat3/core`'s `clientTransport`, and `xhrSignedPut` below is the one `XMLHttpRequest` the
7
+ // framework ships, for exactly one reason — `fetch` reports no upload progress in any shipping
8
+ // browser, and a progress bar that jumps 0 -> 100 is a progress bar that is lying. This file is
9
+ // the one path the `browser-transport` guard exempts for it. Where XHR does not exist the
10
+ // fallback is `clientTransport` with a raw body — never a `fetch` call of this file's own.
11
+
12
+ import { clientTransport } from '@ultimat3/core';
9
13
  import { tooLarge, uploadFailed } from './errors';
10
14
  import type { UploadGrant, UploadRequest } from './grant';
11
15
 
@@ -109,18 +113,23 @@ export const xhrSignedPut: SignedPut = (input) =>
109
113
  request.send(input.body);
110
114
  });
111
115
 
112
- /** No upload progress — `onProgress` fires once at 0 and once at 1, and says so by doing that. */
116
+ /**
117
+ * No upload progress — `onProgress` fires once at 0 and once at 1, and says so by doing that.
118
+ * Through `clientTransport`, the one browser HTTP function: `rawBody` sends the bytes verbatim
119
+ * under the grant's content type and adds no header of its own (a signed request may cover them),
120
+ * and `decodeError` keeps a refusal this package's `X_STORAGE_UPLOAD_FAILED`, with the disk's
121
+ * status. A request that got no response at all is the transport's `X_CLIENT_TRANSPORT_FAILED`.
122
+ */
113
123
  export const fetchSignedPut: SignedPut = async (input) => {
114
124
  input.onProgress?.(progressOf(0, input.body.size));
115
- const response = await fetch(input.url, {
125
+ await clientTransport({
116
126
  method: 'PUT',
127
+ url: input.url,
128
+ rawBody: input.body,
117
129
  headers: { 'content-type': input.contentType },
118
- body: input.body,
130
+ decodeError: (status, text) => uploadFailed(pathOf(input.url), status, text),
119
131
  ...(input.signal === undefined ? {} : { signal: input.signal }),
120
132
  });
121
- if (!response.ok) {
122
- throw uploadFailed(pathOf(input.url), response.status, await response.text());
123
- }
124
133
  input.onProgress?.(progressOf(input.body.size, input.body.size));
125
134
  };
126
135