Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 7174947

Browse files
authored
Implement R2 multipart upload bindings (#486)
* Implement R2 multipart upload bindings * fixup! Implement R2 multipart upload bindings
1 parent 547fda9 commit 7174947

File tree

18 files changed

+1808
-125
lines changed

18 files changed

+1808
-125
lines changed

packages/core/src/standards/http.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export function _headersFromIncomingRequest(
158158
export const _kInner = Symbol("kInner");
159159

160160
const kBodyStream = Symbol("kBodyStream");
161+
const kBodyStreamBrand = Symbol("kBodyStreamBrand");
161162
const kInputGated = Symbol("kInputGated");
162163
const kFormDataFiles = Symbol("kFormDataFiles");
163164
const kCloned = Symbol("kCloned");
@@ -197,7 +198,7 @@ export class Body<Inner extends BaseRequest | BaseResponse> {
197198

198199
if (body === null) return body;
199200
// Only transform body stream once
200-
const bodyStream = this[kBodyStream];
201+
let bodyStream = this[kBodyStream];
201202
if (bodyStream) return bodyStream;
202203
assert(body instanceof ReadableStream);
203204

@@ -263,7 +264,9 @@ export class Body<Inner extends BaseRequest | BaseResponse> {
263264
cancel: (reason) => reader.cancel(reason),
264265
};
265266
// TODO: maybe set { highWaterMark: 0 } as a strategy here?
266-
return (this[kBodyStream] = new ReadableStream(source));
267+
bodyStream = new ReadableStream(source);
268+
Object.defineProperty(bodyStream, kBodyStreamBrand, { value: true });
269+
return (this[kBodyStream] = bodyStream);
267270
}
268271
get bodyUsed(): boolean {
269272
return this[_kInner].bodyUsed;
@@ -357,6 +360,13 @@ export function withStringFormDataFiles<
357360
return body;
358361
}
359362

363+
/** @internal */
364+
export function _isBodyStream(stream: ReadableStream): boolean {
365+
return (
366+
(stream as { [kBodyStreamBrand]?: unknown })[kBodyStreamBrand] === true
367+
);
368+
}
369+
360370
export type RequestInfo = BaseRequestInfo | Request;
361371

362372
export interface RequestInit extends BaseRequestInit {

packages/core/src/standards/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export {
1010
Body,
1111
withInputGating,
1212
withStringFormDataFiles,
13+
_isBodyStream,
1314
Request,
1415
withImmutableHeaders,
1516
Response,
@@ -48,6 +49,8 @@ export {
4849
CompressionStream,
4950
DecompressionStream,
5051
_isByteStream,
52+
_isDisturbedStream,
53+
_isFixedLengthStream,
5154
} from "./streams";
5255
export type { ArrayBufferViewConstructor } from "./streams";
5356
export * from "./navigator";

packages/core/src/standards/streams.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ export function _isByteStream(
3434
return false;
3535
}
3636

37+
/** @internal */
38+
export function _isDisturbedStream(stream: ReadableStream): boolean {
39+
// Try to determine if stream is a stream has been consumed at all by
40+
// inspecting its state.
41+
for (const symbol of Object.getOwnPropertySymbols(stream)) {
42+
if (symbol.description === "kState") {
43+
// @ts-expect-error symbol properties are not included in type definitions
44+
return !!stream[symbol].disturbed;
45+
}
46+
}
47+
return false;
48+
}
49+
3750
function convertToByteStream(
3851
stream: ReadableStream<Uint8Array>,
3952
clone = false
@@ -250,6 +263,13 @@ export class FixedLengthStream extends IdentityTransformStream {
250263
};
251264
}
252265

266+
/** @internal */
267+
export function _isFixedLengthStream(stream: ReadableStream): boolean {
268+
return (
269+
(stream as { [kContentLength]?: unknown })[kContentLength] !== undefined
270+
);
271+
}
272+
253273
function createTransformerFromTransform(transform: Transform): Transformer {
254274
// TODO: backpressure? see https://github.com/nodejs/node/blob/440d95a878a1a19bf72a2685fc8fc0f47100b510/lib/internal/webstreams/adapters.js#L538
255275
return {

packages/core/test/standards/streams.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Request,
1515
Response,
1616
_isByteStream,
17+
_isDisturbedStream,
1718
} from "@miniflare/core";
1819
import { utf8Decode, utf8Encode } from "@miniflare/shared-test";
1920
import test, { Macro, ThrowsExpectation } from "ava";
@@ -74,6 +75,35 @@ test("_isByteStream: determines if a ReadableStream is a byte stream", (t) => {
7475
t.false(_isByteStream(regularStream));
7576
t.true(_isByteStream(byteStream));
7677
});
78+
test("_isDisturbedStream: determines if a ReadableStream is disturbed", async (t) => {
79+
const regularStream = new ReadableStream({
80+
pull(controller) {
81+
controller.enqueue(new Uint8Array([1, 2, 3]));
82+
controller.enqueue("thing");
83+
controller.close();
84+
},
85+
});
86+
const byteStream = new ReadableStream({
87+
type: "bytes",
88+
pull(controller) {
89+
controller.enqueue(new Uint8Array([1, 2, 3]));
90+
controller.close();
91+
},
92+
});
93+
t.false(_isDisturbedStream(regularStream));
94+
t.false(_isDisturbedStream(byteStream));
95+
96+
// @ts-expect-error ReadableStream types are incompatible
97+
await arrayBuffer(regularStream);
98+
t.true(_isDisturbedStream(regularStream));
99+
100+
const reader = byteStream.getReader();
101+
t.false(_isDisturbedStream(byteStream));
102+
await reader.read();
103+
t.true(_isDisturbedStream(byteStream));
104+
await reader.releaseLock();
105+
t.true(_isDisturbedStream(byteStream));
106+
});
77107

78108
test("ReadableStreamBYOBReader: readAtLeast: reads at least n bytes", async (t) => {
79109
const stream = chunkedStream([[1, 2, 3], [4], [5, 6]]);

0 commit comments

Comments
 (0)