Skip to content

Commit a888667

Browse files
committed
Sync bundle reader stands alone
1 parent fdaa937 commit a888667

File tree

3 files changed

+151
-88
lines changed

3 files changed

+151
-88
lines changed

packages/firestore/src/util/bundle_reader.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export class SizedBundleElement {
3131
// How many bytes this element takes to store in the bundle.
3232
public readonly byteLength: number
3333
) {
34-
console.log('DEDB new sizedBundle element.');
35-
console.log('payload: ', payload);
36-
console.log('byteLength: ', byteLength);
34+
console.log("DEDB new sizedBundle element.");
35+
console.log("payload: ", payload);
36+
console.log("byteLength: ", byteLength);
3737
}
3838

3939
isBundleMetadata(): boolean {
@@ -69,3 +69,25 @@ export interface BundleReader {
6969
*/
7070
nextElement(): Promise<SizedBundleElement | null>;
7171
}
72+
73+
/**
74+
* A class representing a bundle.
75+
*
76+
* Takes a bundle stream or buffer, and presents abstractions to read bundled
77+
* elements out of the underlying content.
78+
*/
79+
export interface BundleReaderSync {
80+
serializer: JsonProtoSerializer;
81+
82+
/**
83+
* Returns the metadata of the bundle.
84+
*/
85+
getMetadata(): BundleMetadata;
86+
87+
/**
88+
* Returns the next BundleElement (together with its byte size in the bundle)
89+
* that has not been read from underlying ReadableStream. Returns null if we
90+
* have reached the end of the stream.
91+
*/
92+
getElements(): Array<SizedBundleElement>;
93+
}

packages/firestore/src/util/bundle_reader_impl.ts

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -23,91 +23,6 @@ import { debugAssert } from './assert';
2323
import { BundleReader, SizedBundleElement } from './bundle_reader';
2424
import { Deferred } from './promise';
2525

26-
class SyncBundleReaderImpl {
27-
private metadata: BundleMetadata | null;
28-
private elements: Array<SizedBundleElement>;
29-
private cursor: number;
30-
constructor(
31-
/** The reader to read from underlying binary bundle data source. */
32-
private bundleData: string,
33-
readonly serializer: JsonProtoSerializer
34-
) {
35-
this.metadata = null;
36-
this.cursor = 0;
37-
this.elements = new Array<SizedBundleElement>();
38-
}
39-
40-
parse(): void {
41-
let element = this.nextElement();
42-
if (element && element.isBundleMetadata()) {
43-
this.metadata = element as BundleMetadata;
44-
} else {
45-
throw new Error(`The first element of the bundle is not a metadata, it is
46-
${JSON.stringify(element?.payload)}`);
47-
}
48-
}
49-
50-
getMetadata(): BundleMetadata | null {
51-
return this.metadata;
52-
}
53-
54-
private nextElement(): SizedBundleElement | null {
55-
if (this.cursor === this.bundleData.length) {
56-
return null;
57-
}
58-
59-
const length = this.readLength();
60-
const jsonString = this.readJsonString(length);
61-
62-
return new SizedBundleElement(JSON.parse(jsonString), jsonString.length);
63-
}
64-
65-
/**
66-
* Reads from a specified position from the bundleData string, for a specified
67-
* number of bytes.
68-
*
69-
* Returns a string parsed from the bundle.
70-
*/
71-
private readJsonString(length: number): string {
72-
if (this.cursor + length > this.bundleData.length) {
73-
throw new Error('Reached the end of bundle when more is expected.');
74-
}
75-
const result = this.bundleData.slice(this.cursor, length);
76-
this.cursor += length;
77-
return result;
78-
}
79-
80-
/** First index of '{' from the bundle starting at the optionally provided offset. */
81-
private indexOfOpenBracket(offset?: number): number {
82-
let buffer: string = this.bundleData;
83-
if (offset) {
84-
buffer = this.bundleData.substring(offset);
85-
}
86-
return buffer.indexOf('{');
87-
}
88-
89-
/**
90-
* Reads from the current cursor until the first '{', returns number value
91-
*
92-
* If reached end of the stream, or the value isn't a number, then throws.
93-
*/
94-
private readLength(): number {
95-
const startIndex = this.cursor;
96-
let curIndex = this.cursor;
97-
while (curIndex < this.bundleData.length) {
98-
if (this.bundleData[curIndex] === '{') {
99-
if (curIndex === startIndex) {
100-
throw new Error('First character is a bracket and not a number');
101-
}
102-
this.cursor = curIndex;
103-
return Number(this.bundleData.slice(startIndex, curIndex));
104-
}
105-
curIndex++;
106-
}
107-
throw new Error('Reached the end of bundle when more is expected.');
108-
}
109-
}
110-
11126
/**
11227
* A class representing a bundle.
11328
*
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { BundleMetadata } from '../protos/firestore_bundle_proto';
19+
import { JsonProtoSerializer } from '../remote/serializer';
20+
21+
import { BundleReaderSync, SizedBundleElement } from './bundle_reader';
22+
23+
/**
24+
* A class that can parse a bundle form the string serialization of a bundle.
25+
*/
26+
class BundleReaderSyncImpl implements BundleReaderSync {
27+
private metadata: BundleMetadata;
28+
private elements: Array<SizedBundleElement>;
29+
private cursor: number;
30+
constructor(
31+
private bundleData: string,
32+
readonly serializer: JsonProtoSerializer
33+
) {
34+
this.cursor = 0;
35+
this.elements = new Array<SizedBundleElement>();
36+
37+
let element = this.nextElement();
38+
if (element && element.isBundleMetadata()) {
39+
this.metadata = element as BundleMetadata;
40+
} else {
41+
throw new Error(`The first element of the bundle is not a metadata, it is
42+
${JSON.stringify(element?.payload)}`);
43+
}
44+
45+
element = this.nextElement();
46+
while(element !== null) {
47+
this.elements.push(element);
48+
}
49+
}
50+
51+
/* Returns the parsed metadata of the bundle. */
52+
getMetadata() : BundleMetadata {
53+
return this.metadata;
54+
}
55+
56+
/* Returns the DocumentSnapshot or NamedQuery elements of the bundle. */
57+
getElements() : Array<SizedBundleElement> {
58+
return this.elements;
59+
}
60+
61+
/**
62+
* Parses the next element of the bundle.
63+
*
64+
* @returns a SizedBundleElement representation of the next element in the bundle, or null if
65+
* no more elements exist.
66+
*/
67+
private nextElement() : SizedBundleElement | null {
68+
if(this.cursor === this.bundleData.length) {
69+
return null;
70+
}
71+
72+
const length = this.readLength();
73+
const jsonString = this.readJsonString(length);
74+
75+
return new SizedBundleElement(
76+
JSON.parse(jsonString),
77+
length
78+
);
79+
}
80+
81+
/**
82+
* Reads from a specified position from the bundleData string, for a specified
83+
* number of bytes.
84+
*
85+
* @param length how many characters to read.
86+
* @returns a string parsed from the bundle.
87+
*/
88+
private readJsonString(length: number): string {
89+
if(this.cursor + length > this.bundleData.length) {
90+
throw new Error('Reached the end of bundle when more is expected.');
91+
}
92+
const result = this.bundleData.slice(this.cursor, length);
93+
this.cursor += length;
94+
return result;
95+
}
96+
97+
/**
98+
* Reads from the current cursor until the first '{'.
99+
*
100+
* @returns A string to integer represention of the parsed value.
101+
* @throws An {@link Error} if the cursor has reached the end of the stream, since lengths
102+
* prefix bundle objects.
103+
*/
104+
private readLength(): number {
105+
const startIndex = this.cursor;
106+
let curIndex = this.cursor;
107+
while(curIndex < this.bundleData.length) {
108+
if(this.bundleData[curIndex] === '{') {
109+
if(curIndex === startIndex) {
110+
throw new Error('First character is a bracket and not a number');
111+
}
112+
this.cursor = curIndex;
113+
return Number(this.bundleData.slice(startIndex, curIndex));
114+
}
115+
curIndex++;
116+
}
117+
throw new Error('Reached the end of bundle when more is expected.');
118+
}
119+
}
120+
121+
export function newBundleReaderSync(
122+
bundleData: string,
123+
serializer: JsonProtoSerializer
124+
): BundleReaderSync {
125+
return new BundleReaderSyncImpl(bundleData, serializer);
126+
}

0 commit comments

Comments
 (0)