Skip to content

Enable bundle reader for Node. #3167

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jun 10, 2020
Merged

Enable bundle reader for Node. #3167

merged 15 commits into from
Jun 10, 2020

Conversation

wu-hui
Copy link
Contributor

@wu-hui wu-hui commented Jun 5, 2020

No description provided.

@google-oss-bot
Copy link
Contributor

Binary Size Report

Affected SDKs

  • @firebase/firestore

    Type Base (b0c8299) Head (ab6d188) Diff
    browser 252 kB 253 kB +915 B (+0.4%)
    esm2017 195 kB 196 kB +538 B (+0.3%)
    main 494 kB 495 kB +943 B (+0.2%)
    module 250 kB 250 kB +871 B (+0.3%)
  • @firebase/firestore/memory

    Type Base (b0c8299) Head (ab6d188) Diff
    browser 192 kB 193 kB +915 B (+0.5%)
    esm2017 149 kB 150 kB +538 B (+0.4%)
    main 369 kB 370 kB +943 B (+0.3%)
    module 190 kB 191 kB +871 B (+0.5%)
  • firebase

    Type Base (b0c8299) Head (ab6d188) Diff
    firebase-firestore.js 290 kB 291 kB +823 B (+0.3%)
    firebase-firestore.memory.js 232 kB 232 kB +823 B (+0.4%)
    firebase.js 823 kB 824 kB +827 B (+0.1%)

Test Logs

* This can be used as an abstraction to mimic `ReadableStream` where it is not
* available.
*/
export interface ByteStreamReader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you take a look at using the type ReadableStreamReader directly?

interface ReadableStreamReader<R = any> {
    cancel(): Promise<void>;
    read(): Promise<ReadableStreamReadResult<R>>;
    releaseLock(): void;
}

(from lib.dom.d.ts)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the document, it seems ReadableStreamDefaultReader is the interface? There are several differences.

  1. releaseLock() is not added there because it is not used. I dont have a strong preference though.
  2. The DOM one is more generic, but given we only intend to use it to read bundle source which is ArrayBuffer | ReadableStream<UInt8Array> I think it is fine?
  3. cancel takes a string because ReadableStreamDefaultReader takes a reason for its cancel (https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/cancel).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we would be better off if we re-used existing types. It makes our code smaller, easier to understand for outsiders and is more future proof. You are already using most of the idioms of the existing API, and re-using the type would be a good explanation of why you are doing this ("citing the source").

The change here is actually pretty simple: https://gist.github.com/schmidt-sebastian/62cabbbcab7f78f3a6e1aee7809f1b73
If we need a more complicated type, we could keep ByteStreamReader but make it:

export type ByteStreamReader = ReadableStreamReader<ArrayBuffer | ReadableStream<UInt8Array>>

As for 3: Right now, we ignore the reason for cancelling. Could we just make it part of the error message?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

I had the assumption that ReadableStreamReader will not be available for Node, turns out the interface does exist with d.ts. Thanks for the patch!

bytesPerRead = 10240
): ByteStreamReader {
let readFrom = 0;
return new (class implements ByteStreamReader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you are using the IIFE pattern here to enforce the ByteStreamReader type?

You could just use:

const reader : ByteStreamReader = {
...
}
return reader;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
if (source instanceof ReadableStream) {
const reader = source.getReader();
return new (class implements ByteStreamReader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just return reader here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my surprise, yes! Thanks.

})();
}
throw new Error(
'Source of `toByteStreamReader` has to be Uint8Array, ArrayBuffer or ReadableStream'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a ("has to be a Uint8Array")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

/**
* Builds a `ByteStreamReader` from a data source.
*/
toByteStreamReader(source: unknown): ByteStreamReader;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the source type be Uint8Array | ArrayBuffer | ReadableStream<Uint8Array>?

And since this type combination is used widely now, should we add a local type alias?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* On Node, only supported data source is a `Uint8Array` for now.
*/
toByteStreamReader(source: unknown): ByteStreamReader {
if (source instanceof Uint8Array) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest you use this pattern here:

if (!(array instanceof Uint8Array)) {

If it doesn't apply here, then you should invoke it at the callsite (the API that the end user calls) and add a debugAssert here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


it('toByteStreamReader() steps underlying data', async () => {
const encoder = new TextEncoder();
const r = toByteStreamReader(encoder.encode('0123456789'), 4);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add inline comment to explain what 4 is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

): ReadableStream {
return toReadableStream(new TextEncoder().encode(content), bytesPerRead);
bytesPerRead = 10240
): ReadableStream<Uint8Array | ArrayBuffer> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to convert the input to a UInt8Array and re-use toByteStreamReader?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with some tweak.

@@ -270,6 +270,10 @@ export class TestPlatform implements Platform {
randomBytes(nBytes: number): Uint8Array {
return this.basePlatform.randomBytes(nBytes);
}

toByteStreamReader(source: unknown): ByteStreamReader {
return this.basePlatform.toByteStreamReader(source);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that if you move your Node-specifc genericBundleReadingTests logic here, then you can use the TestPlatform throughout and won't have to force override it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above. It now works for both platforms without hacking.

platform.toByteStreamReader = source =>
toByteStreamReader(source as Uint8Array, bytesPerRead);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(PlatformSupport as any)._forceSetPlatform(platform);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should try really hard to to have to do this. Options:

  • Have a Node specific test file (bundle.node.test.ts similar to serializer.node.test.ts)
  • Implement this logic in TestPlatform's toByteStreamReader.

A very strong argument against this pattern is that guaranteeing cleanup for this type of global state modification is hard. It even looks like you opted not do to it at all :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually making toByteStreamReader take a bytesPerRead eliminates the problem.

Copy link
Contributor Author

@wu-hui wu-hui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!

/**
* Builds a `ByteStreamReader` from a data source.
*/
toByteStreamReader(source: unknown): ByteStreamReader;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* This can be used as an abstraction to mimic `ReadableStream` where it is not
* available.
*/
export interface ByteStreamReader {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the document, it seems ReadableStreamDefaultReader is the interface? There are several differences.

  1. releaseLock() is not added there because it is not used. I dont have a strong preference though.
  2. The DOM one is more generic, but given we only intend to use it to read bundle source which is ArrayBuffer | ReadableStream<UInt8Array> I think it is fine?
  3. cancel takes a string because ReadableStreamDefaultReader takes a reason for its cancel (https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/cancel).

bytesPerRead = 10240
): ByteStreamReader {
let readFrom = 0;
return new (class implements ByteStreamReader {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
if (source instanceof ReadableStream) {
const reader = source.getReader();
return new (class implements ByteStreamReader {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my surprise, yes! Thanks.

})();
}
throw new Error(
'Source of `toByteStreamReader` has to be Uint8Array, ArrayBuffer or ReadableStream'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

): ReadableStream {
return toReadableStream(new TextEncoder().encode(content), bytesPerRead);
bytesPerRead = 10240
): ReadableStream<Uint8Array | ArrayBuffer> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with some tweak.

* On Node, only supported data source is a `Uint8Array` for now.
*/
toByteStreamReader(source: unknown): ByteStreamReader {
if (source instanceof Uint8Array) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


it('toByteStreamReader() steps underlying data', async () => {
const encoder = new TextEncoder();
const r = toByteStreamReader(encoder.encode('0123456789'), 4);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

platform.toByteStreamReader = source =>
toByteStreamReader(source as Uint8Array, bytesPerRead);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(PlatformSupport as any)._forceSetPlatform(platform);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually making toByteStreamReader take a bytesPerRead eliminates the problem.

@@ -270,6 +270,10 @@ export class TestPlatform implements Platform {
randomBytes(nBytes: number): Uint8Array {
return this.basePlatform.randomBytes(nBytes);
}

toByteStreamReader(source: unknown): ByteStreamReader {
return this.basePlatform.toByteStreamReader(source);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above. It now works for both platforms without hacking.

@wu-hui wu-hui assigned schmidt-sebastian and unassigned wu-hui Jun 10, 2020
@wu-hui wu-hui changed the title Enable bundler reader for Node. Enable bundle reader for Node. Jun 10, 2020
/**
* Forcing to set the platform instance, testing only!
*/
private static _forceSetPlatform(platform: Platform): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can drop this now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* @param data: Underlying buffer.
* @param bytesPerRead: How many bytes to read from the underlying buffer from
* each read through the stream.
* It is not application when we don't really have control, for example, when
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/application/applicable

But it could just be: "Not applicable for ReadableStreams."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* This can be used as an abstraction to mimic `ReadableStream` where it is not
* available.
*/
export interface ByteStreamReader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we would be better off if we re-used existing types. It makes our code smaller, easier to understand for outsiders and is more future proof. You are already using most of the idioms of the existing API, and re-using the type would be a good explanation of why you are doing this ("citing the source").

The change here is actually pretty simple: https://gist.github.com/schmidt-sebastian/62cabbbcab7f78f3a6e1aee7809f1b73
If we need a more complicated type, we could keep ByteStreamReader but make it:

export type ByteStreamReader = ReadableStreamReader<ArrayBuffer | ReadableStream<UInt8Array>>

As for 3: Right now, we ignore the reason for cancelling. Could we just make it part of the error message?

@wu-hui wu-hui assigned schmidt-sebastian and unassigned wu-hui Jun 10, 2020
source: Uint8Array,
bytesPerRead: number
): ReadableStreamReader<Uint8Array> {
validatePositiveNumber('toByteStreamReader', 2, bytesPerRead);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is not a user facing method, this should be a debugAssert, which will get removed in the final build and drops the dependency on validatePositiveNumber in code that doesn't otherwise depend on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@wu-hui wu-hui merged commit 2f179bc into wuandy/Bundles Jun 10, 2020
@firebase firebase locked and limited conversation to collaborators Jul 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants