Skip to content

Commit 7231def

Browse files
committed
Migrate from the Node to the Web ReadableStream
1 parent 6bb2e89 commit 7231def

File tree

11 files changed

+71
-48
lines changed

11 files changed

+71
-48
lines changed

.changeset/itchy-boxes-try.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/storage': minor
3+
---
4+
5+
Migrate from the Node to Web ReadableStream interface

common/api-review/storage.api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { FirebaseError } from '@firebase/util';
1313
import { _FirebaseService } from '@firebase/app';
1414
import { NextFn } from '@firebase/util';
1515
import { Provider } from '@firebase/component';
16+
import { ReadableStream as ReadableStream_2 } from 'stream/web';
1617
import { Subscribe } from '@firebase/util';
1718
import { Unsubscribe } from '@firebase/util';
1819

@@ -132,7 +133,7 @@ export function getMetadata(ref: StorageReference): Promise<FullMetadata>;
132133
export function getStorage(app?: FirebaseApp, bucketUrl?: string): FirebaseStorage;
133134

134135
// @public
135-
export function getStream(ref: StorageReference, maxDownloadSizeBytes?: number): NodeJS.ReadableStream;
136+
export function getStream(ref: StorageReference, maxDownloadSizeBytes?: number): ReadableStream_2;
136137

137138
// @internal (undocumented)
138139
export function _invalidArgument(message: string): StorageError;

docs-devsite/storage.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ This API is only available in Node.
279279
<b>Signature:</b>
280280

281281
```typescript
282-
export declare function getStream(ref: StorageReference, maxDownloadSizeBytes?: number): NodeJS.ReadableStream;
282+
export declare function getStream(ref: StorageReference, maxDownloadSizeBytes?: number): ReadableStream;
283283
```
284284

285285
#### Parameters
@@ -291,7 +291,7 @@ export declare function getStream(ref: StorageReference, maxDownloadSizeBytes?:
291291

292292
<b>Returns:</b>
293293

294-
NodeJS.ReadableStream
294+
ReadableStream
295295

296296
A stream with the object's data as bytes
297297

packages/storage/src/api.browser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import { StorageReference } from './public-types';
1919
import { Reference, getBlobInternal } from './reference';
2020
import { getModularInstance } from '@firebase/util';
21+
import { ReadableStream } from 'stream/web';
2122

2223
/**
2324
* Downloads the data at the object's location. Returns an error if the object
@@ -58,6 +59,6 @@ export function getBlob(
5859
export function getStream(
5960
ref: StorageReference,
6061
maxDownloadSizeBytes?: number
61-
): NodeJS.ReadableStream {
62+
): ReadableStream {
6263
throw new Error('getStream() is only supported by NodeJS builds');
6364
}

packages/storage/src/api.node.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import { StorageReference } from './public-types';
1919
import { Reference, getStreamInternal } from './reference';
2020
import { getModularInstance } from '@firebase/util';
21+
import { ReadableStream } from 'stream/web';
2122

2223
/**
2324
* Downloads the data at the object's location. Returns an error if the object
@@ -58,7 +59,7 @@ export function getBlob(
5859
export function getStream(
5960
ref: StorageReference,
6061
maxDownloadSizeBytes?: number
61-
): NodeJS.ReadableStream {
62+
): ReadableStream {
6263
ref = getModularInstance(ref);
6364
return getStreamInternal(ref as Reference, maxDownloadSizeBytes);
6465
}

packages/storage/src/implementation/connection.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,13 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
import { ReadableStream } from 'stream/web';
1718

1819
/** Network headers */
1920
export type Headers = Record<string, string>;
2021

2122
/** Response type exposed by the networking APIs. */
22-
export type ConnectionType =
23-
| string
24-
| ArrayBuffer
25-
| Blob
26-
| NodeJS.ReadableStream;
23+
export type ConnectionType = string | ArrayBuffer | Blob | ReadableStream;
2724

2825
/**
2926
* A lightweight wrapper around XMLHttpRequest with a

packages/storage/src/platform/browser/connection.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Headers
2323
} from '../../implementation/connection';
2424
import { internalError } from '../../implementation/error';
25+
import { ReadableStream } from 'stream/web';
2526

2627
/** An override for the text-based Connection. Used in tests. */
2728
let textFactoryOverride: (() => Connection<string>) | null = null;
@@ -171,7 +172,7 @@ export function newBlobConnection(): Connection<Blob> {
171172
return new XhrBlobConnection();
172173
}
173174

174-
export function newStreamConnection(): Connection<NodeJS.ReadableStream> {
175+
export function newStreamConnection(): Connection<ReadableStream> {
175176
throw new Error('Streams are only supported on Node');
176177
}
177178

packages/storage/src/platform/connection.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
newStreamConnection as nodeNewStreamConnection,
2323
injectTestConnection as nodeInjectTestConnection
2424
} from './node/connection';
25+
import { ReadableStream } from 'stream/web';
2526

2627
export function injectTestConnection(
2728
factory: (() => Connection<string>) | null
@@ -45,7 +46,7 @@ export function newBlobConnection(): Connection<Blob> {
4546
return nodeNewBlobConnection();
4647
}
4748

48-
export function newStreamConnection(): Connection<NodeJS.ReadableStream> {
49+
export function newStreamConnection(): Connection<ReadableStream> {
4950
// This file is only used in Node.js tests using ts-node.
5051
return nodeNewStreamConnection();
5152
}

packages/storage/src/platform/node/connection.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from '../../implementation/connection';
2323
import { internalError } from '../../implementation/error';
2424
import { fetch as undiciFetch, Headers as undiciHeaders } from 'undici';
25+
import { ReadableStream } from 'stream/web';
2526

2627
/** An override for the text-based Connection. Used in tests. */
2728
let textFactoryOverride: (() => Connection<string>) | null = null;
@@ -50,7 +51,7 @@ abstract class FetchConnection<T extends ConnectionType>
5051
async send(
5152
url: string,
5253
method: string,
53-
body?: ArrayBufferView | Blob | string,
54+
body?: NodeJS.ArrayBufferView | Blob | string,
5455
headers?: Record<string, string>
5556
): Promise<void> {
5657
if (this.sent_) {
@@ -62,7 +63,7 @@ abstract class FetchConnection<T extends ConnectionType>
6263
const response = await this.fetch_(url, {
6364
method,
6465
headers: headers || {},
65-
body: body as ArrayBufferView | string
66+
body: body as NodeJS.ArrayBufferView | string
6667
});
6768
this.headers_ = response.headers;
6869
this.statusCode_ = response.status;
@@ -146,13 +147,13 @@ export function newBytesConnection(): Connection<ArrayBuffer> {
146147
return new FetchBytesConnection();
147148
}
148149

149-
export class FetchStreamConnection extends FetchConnection<NodeJS.ReadableStream> {
150-
private stream_: NodeJS.ReadableStream | null = null;
150+
export class FetchStreamConnection extends FetchConnection<ReadableStream> {
151+
private stream_: ReadableStream | null = null;
151152

152153
async send(
153154
url: string,
154155
method: string,
155-
body?: ArrayBufferView | Blob | string,
156+
body?: NodeJS.ArrayBufferView | Blob | string,
156157
headers?: Record<string, string>
157158
): Promise<void> {
158159
if (this.sent_) {
@@ -164,7 +165,7 @@ export class FetchStreamConnection extends FetchConnection<NodeJS.ReadableStream
164165
const response = await this.fetch_(url, {
165166
method,
166167
headers: headers || {},
167-
body: body as ArrayBufferView | string
168+
body: body as NodeJS.ArrayBufferView | string
168169
});
169170
this.headers_ = response.headers;
170171
this.statusCode_ = response.status;
@@ -178,15 +179,15 @@ export class FetchStreamConnection extends FetchConnection<NodeJS.ReadableStream
178179
}
179180
}
180181

181-
getResponse(): NodeJS.ReadableStream {
182+
getResponse(): ReadableStream {
182183
if (!this.stream_) {
183184
throw internalError('cannot .getResponse() before sending');
184185
}
185186
return this.stream_;
186187
}
187188
}
188189

189-
export function newStreamConnection(): Connection<NodeJS.ReadableStream> {
190+
export function newStreamConnection(): Connection<ReadableStream> {
190191
return new FetchStreamConnection();
191192
}
192193

packages/storage/src/reference.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* @fileoverview Defines the Firebase StorageReference class.
2020
*/
2121

22-
import { PassThrough, Transform, TransformOptions } from 'stream';
22+
import { ReadableStream, TransformStream, Transformer } from 'stream/web';
2323

2424
import { FbsBlob } from './implementation/blob';
2525
import { Location } from './implementation/location';
@@ -48,6 +48,7 @@ import {
4848
newStreamConnection,
4949
newTextConnection
5050
} from './platform/connection';
51+
import { RequestInfo } from './implementation/requestinfo';
5152

5253
/**
5354
* Provides methods to interact with a bucket in the Firebase Storage service.
@@ -203,42 +204,42 @@ export function getBlobInternal(
203204
export function getStreamInternal(
204205
ref: Reference,
205206
maxDownloadSizeBytes?: number
206-
): NodeJS.ReadableStream {
207+
): ReadableStream {
207208
ref._throwIfRoot('getStream');
208-
const requestInfo = getBytes(
209+
const requestInfo: RequestInfo<ReadableStream, ReadableStream> = getBytes(
209210
ref.storage,
210211
ref._location,
211212
maxDownloadSizeBytes
212213
);
213214

214-
/** A transformer that passes through the first n bytes. */
215-
const newMaxSizeTransform: (n: number) => TransformOptions = n => {
215+
// Transforms the stream so that only `maxDownloadSizeBytes` bytes are piped to the result
216+
const newMaxSizeTransform = (n: number): Transformer => {
216217
let missingBytes = n;
217218
return {
218-
transform(chunk, encoding, callback) {
219+
transform(chunk, controller: TransformStreamDefaultController) {
219220
// GCS may not honor the Range header for small files
220221
if (chunk.length < missingBytes) {
221-
this.push(chunk);
222+
controller.enqueue(chunk);
222223
missingBytes -= chunk.length;
223224
} else {
224-
this.push(chunk.slice(0, missingBytes));
225-
this.emit('end');
225+
controller.enqueue(chunk.slice(0, missingBytes));
226+
controller.terminate();
226227
}
227-
callback();
228228
}
229-
} as TransformOptions;
229+
};
230230
};
231231

232232
const result =
233233
maxDownloadSizeBytes !== undefined
234-
? new Transform(newMaxSizeTransform(maxDownloadSizeBytes))
235-
: new PassThrough();
234+
? new TransformStream(newMaxSizeTransform(maxDownloadSizeBytes))
235+
: new TransformStream(); // The default transformer forwards all chunks to its readable side
236236

237237
ref.storage
238238
.makeRequestWithTokens(requestInfo, newStreamConnection)
239-
.then(stream => (stream as NodeJS.ReadableStream).pipe(result))
240-
.catch(e => result.destroy(e));
241-
return result;
239+
.then(readableStream => readableStream.pipeThrough(result))
240+
.catch(err => result.writable.getWriter().abort(err));
241+
242+
return result.readable;
242243
}
243244

244245
/**

0 commit comments

Comments
 (0)