-
Notifications
You must be signed in to change notification settings - Fork 938
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
Conversation
# Conflicts: # packages/firestore/src/util/bundle_reader.ts # packages/firestore/test/unit/util/bundle.test.ts
# Conflicts: # packages/firestore/src/util/bundle_reader.ts # packages/firestore/test/unit/util/bundle.test.ts
Binary Size ReportAffected SDKs
Test Logs |
* This can be used as an abstraction to mimic `ReadableStream` where it is not | ||
* available. | ||
*/ | ||
export interface ByteStreamReader { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
releaseLock()
is not added there because it is not used. I dont have a strong preference though.- 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? cancel
takes a string becauseReadableStreamDefaultReader
takes a reason for its cancel (https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/cancel).
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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")
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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.
There was a problem hiding this 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; |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
releaseLock()
is not added there because it is not used. I dont have a strong preference though.- 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? cancel
takes a string becauseReadableStreamDefaultReader
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
/** | ||
* Forcing to set the platform instance, testing only! | ||
*/ | ||
private static _forceSetPlatform(platform: Platform): void { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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."
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
source: Uint8Array, | ||
bytesPerRead: number | ||
): ReadableStreamReader<Uint8Array> { | ||
validatePositiveNumber('toByteStreamReader', 2, bytesPerRead); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
No description provided.