Skip to content

Commit aa455bf

Browse files
committed
Remove generator.
1 parent 18f0be1 commit aa455bf

File tree

2 files changed

+134
-142
lines changed

2 files changed

+134
-142
lines changed

packages/firestore/src/util/bundle.ts renamed to packages/firestore/src/util/bundle_reader.ts

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import * as api from '../protos/firestore_proto_api';
1918
import {
20-
BundledDocumentMetadata,
21-
BundledQuery,
2219
BundleElement,
2320
BundleMetadata
2421
} from '../protos/firestore_bundle_proto';
@@ -29,9 +26,14 @@ import {
2926
*/
3027
export class SizedBundleElement {
3128
constructor(
32-
public payload: BundledQuery | api.Document | BundledDocumentMetadata,
33-
public byteLength: number
29+
public readonly payload: BundleElement,
30+
// How many bytes this element takes to store in the bundle.
31+
public readonly byteLength: number
3432
) {}
33+
34+
isBundleMetadata(): boolean {
35+
return 'metadata' in this.payload;
36+
}
3537
}
3638

3739
/**
@@ -63,7 +65,7 @@ export function toReadableStream(
6365
* Takes a bundle stream or buffer, and presents abstractions to read bundled
6466
* elements out of the underlying content.
6567
*/
66-
export class Bundle {
68+
export class BundleReader {
6769
// Cached bundle metadata.
6870
private metadata?: BundleMetadata | null;
6971
// The reader instance of the given ReadableStream.
@@ -92,87 +94,91 @@ export class Bundle {
9294
*/
9395
async getMetadata(): Promise<BundleMetadata> {
9496
if (!this.metadata) {
95-
const result = await this.nextElement();
96-
if (result === null || result instanceof SizedBundleElement) {
97-
throw new Error(`The first element is not metadata, it is ${result}`);
98-
}
99-
this.metadata = (result as BundleElement).metadata;
97+
await this.nextElement();
10098
}
10199

102100
return this.metadata!;
103101
}
104102

105103
/**
106-
* Asynchronously iterate through all bundle elements (except bundle metadata).
104+
* Returns the next BundleElement (together with its byte size in the bundle)
105+
* that has not been read from underlying ReadableStream. Returns null if we
106+
* have reached the end of the stream.
107+
*
108+
* Throws an error if the first element is not a BundleMetadata.
107109
*/
108-
async *elements(): AsyncIterableIterator<SizedBundleElement> {
109-
let element = await this.nextElement();
110-
while (element !== null) {
111-
if (element instanceof SizedBundleElement) {
112-
yield element;
110+
async nextElement(): Promise<SizedBundleElement | null> {
111+
const element = await this.readNextElement();
112+
if (!element) {
113+
return element;
114+
}
115+
116+
if (!this.metadata) {
117+
if (element.isBundleMetadata()) {
118+
this.metadata = element.payload.metadata;
113119
} else {
114-
this.metadata = element.metadata;
120+
this.raiseError(
121+
`The first element of the bundle is not a metadata, it is ${JSON.stringify(
122+
element.payload
123+
)}`
124+
);
115125
}
116-
element = await this.nextElement();
117126
}
127+
128+
return element;
118129
}
119130

120-
// Reads from the head of internal buffer, and pulling more data from underlying stream if a complete element
121-
// cannot be found, until an element(including the prefixed length and the JSON string) is found.
122-
//
123-
// Once a complete element is read, it is dropped from internal buffer.
124-
//
125-
// Returns either the bundled element, or null if we have reached the end of the stream.
126-
private async nextElement(): Promise<
127-
BundleElement | SizedBundleElement | null
128-
> {
131+
/**
132+
* Reads from the head of internal buffer, and pulling more data from underlying stream if a complete element
133+
* cannot be found, until an element(including the prefixed length and the JSON string) is found.
134+
*
135+
* Once a complete element is read, it is dropped from internal buffer.
136+
*
137+
* Returns either the bundled element, or null if we have reached the end of the stream.
138+
*/
139+
private async readNextElement(): Promise<SizedBundleElement | null> {
129140
const lengthBuffer = await this.readLength();
130141
if (lengthBuffer === null) {
131142
return null;
132143
}
133144

134145
const lengthString = this.textDecoder.decode(lengthBuffer);
135-
const length = parseInt(lengthString, 10);
146+
const length = Number(lengthString);
136147
if (isNaN(length)) {
137-
throw new Error(`length string (${lengthString}) is not valid number`);
148+
this.raiseError(`length string (${lengthString}) is not valid number`);
138149
}
139150

140151
const jsonString = await this.readJsonString(lengthBuffer.length, length);
141152
// Update the internal buffer to drop the read length and json string.
142153
this.buffer = this.buffer.slice(lengthBuffer.length + length);
143154

144-
if (!this.metadata) {
145-
const element = JSON.parse(jsonString) as BundleElement;
146-
return element;
147-
} else {
148-
return new SizedBundleElement(
149-
JSON.parse(jsonString),
150-
lengthBuffer.length + length
151-
);
152-
}
155+
return new SizedBundleElement(
156+
JSON.parse(jsonString),
157+
lengthBuffer.length + length
158+
);
153159
}
154160

155161
// First index of '{' from the underlying buffer.
156162
private indexOfOpenBracket(): number {
157-
return this.buffer.findIndex(v => v === 123);
163+
return this.buffer.findIndex(v => v === '{'.charCodeAt(0));
158164
}
159165

160-
// Reads from the beginning of the inernal buffer, until the first '{', and return
166+
// Reads from the beginning of the internal buffer, until the first '{', and return
161167
// the content.
162168
// If reached end of the stream, returns a null.
163169
private async readLength(): Promise<Uint8Array | null> {
164170
let position = this.indexOfOpenBracket();
165171
while (position < 0) {
166-
const done = await this.pullMoreDataToBuffer();
167-
if (done) {
172+
const bytesRead = await this.pullMoreDataToBuffer();
173+
if (bytesRead < 0) {
168174
if (this.buffer.length === 0) {
169175
return null;
170176
}
171177
position = this.indexOfOpenBracket();
172178
// Underlying stream is closed, and we still cannot find a '{'.
173179
if (position < 0) {
174-
throw new Error(
175-
'Reach to the end of bundle when a length string is expected.'
180+
this.raiseError(
181+
'Reached the end of bundle when a length string is expected.'
176182
);
177183
}
178184
} else {
@@ -189,27 +195,35 @@ export class Bundle {
189195
// Returns a string decoded from the read bytes.
190196
private async readJsonString(start: number, length: number): Promise<string> {
191197
while (this.buffer.length < start + length) {
192-
const done = await this.pullMoreDataToBuffer();
193-
if (done) {
194-
throw new Error('Reach to the end of bundle when more is expected.');
198+
const bytesRead = await this.pullMoreDataToBuffer();
199+
if (bytesRead < 0) {
200+
this.raiseError('Reached the end of bundle when more is expected.');
195201
}
196202
}
197203

198204
return this.textDecoder.decode(this.buffer.slice(start, start + length));
199205
}
200206

207+
private raiseError(message: string): void {
208+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
209+
this.reader.cancel('Invalid bundle format.');
210+
throw new Error(message);
211+
}
212+
201213
// Pulls more data from underlying stream to internal buffer.
202214
// Returns a boolean indicating whether the stream is finished.
203-
private async pullMoreDataToBuffer(): Promise<boolean> {
215+
private async pullMoreDataToBuffer(): Promise<number> {
204216
const result = await this.reader.read();
217+
let bytesRead = -1;
205218
if (!result.done) {
219+
bytesRead = result.value.length;
206220
const newBuffer = new Uint8Array(
207221
this.buffer.length + result.value.length
208222
);
209223
newBuffer.set(this.buffer);
210224
newBuffer.set(result.value, this.buffer.length);
211225
this.buffer = newBuffer;
212226
}
213-
return result.done;
227+
return bytesRead;
214228
}
215229
}

0 commit comments

Comments
 (0)