Skip to content

Commit b887735

Browse files
Add ProtoJS types to generated proto types
1 parent 1a0b08d commit b887735

File tree

6 files changed

+61
-69
lines changed

6 files changed

+61
-69
lines changed

packages/firestore/src/api/user_data_writer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ export class UserDataWriter<T> {
7878
private convertServerTimestamp(value: ServerTimestampValue): unknown {
7979
switch (this.serverTimestampBehavior) {
8080
case 'previous':
81-
return value.previousValue ? this.convertValue(value.previousValue) : null;
81+
return value.previousValue
82+
? this.convertValue(value.previousValue)
83+
: null;
8284
case 'estimate':
8385
return this.convertTimestamp(value.localWriteTime);
8486
default:

packages/firestore/src/model/proto_values.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,13 @@ import {
2626
numericEquals,
2727
primitiveComparator
2828
} from '../util/misc';
29+
import { TimestampValue } from '../remote/serializer';
2930

3031
// A RegExp matching ISO 8601 UTC timestamps with optional fraction.
3132
const ISO_TIMESTAMP_REG_EXP = new RegExp(
3233
/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.(\d+))?Z$/
3334
);
3435

35-
// Denotes the possible representations for timestamps in the Value type.
36-
type ProtoTimestampValue =
37-
| string
38-
| { seconds?: string | number; nanos?: number };
39-
4036
/** Extracts the backend's type order for the provided value. */
4137
export function typeOrder(value: api.Value): TypeOrder {
4238
if ('nullValue' in value) {
@@ -236,8 +232,8 @@ function compareNumbers(left: api.Value, right: api.Value): number {
236232
}
237233

238234
function compareTimestamps(
239-
left: ProtoTimestampValue,
240-
right: ProtoTimestampValue
235+
left: TimestampValue,
236+
right: TimestampValue
241237
): number {
242238
if (typeof left === 'string' && typeof right === 'string') {
243239
// Use string ordering for ISO 8601 timestamps, but strip the timezone
@@ -376,7 +372,7 @@ function canonifyByteString(byteString: string | Uint8Array): string {
376372
return normalizeByteString(byteString).toBase64();
377373
}
378374

379-
function canonifyTimestamp(timestamp: ProtoTimestampValue): string {
375+
function canonifyTimestamp(timestamp: TimestampValue): string {
380376
const normalizedTimestamp = normalizeTimestamp(timestamp);
381377
return `time(${normalizedTimestamp.seconds},${normalizedTimestamp.nanos})`;
382378
}
@@ -478,7 +474,7 @@ function estimateArrayByteSize(arrayValue: api.ArrayValue): number {
478474
* nanos" representation.
479475
*/
480476
export function normalizeTimestamp(
481-
date: ProtoTimestampValue
477+
date: TimestampValue
482478
): { seconds: number; nanos: number } {
483479
assert(!!date, 'Cannot normalize null or undefined timestamp.');
484480

packages/firestore/src/protos/firestore_proto_api.d.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export declare namespace firestoreV1ApiClientInterfaces {
180180
name?: string;
181181
fields?: ApiClientObjectMap<Value>;
182182
createTime?: string;
183-
updateTime?: string;
183+
updateTime?: string | { seconds?: string | number; nanos?: number };
184184
}
185185
interface DocumentChange {
186186
document?: Document;
@@ -190,7 +190,7 @@ export declare namespace firestoreV1ApiClientInterfaces {
190190
interface DocumentDelete {
191191
document?: string;
192192
removedTargetIds?: number[];
193-
readTime?: string;
193+
readTime?: string | { seconds?: string | number; nanos?: number };
194194
}
195195
interface DocumentMask {
196196
fieldPaths?: string[];
@@ -290,7 +290,7 @@ export declare namespace firestoreV1ApiClientInterfaces {
290290
}
291291
interface Precondition {
292292
exists?: boolean;
293-
updateTime?: string;
293+
updateTime?: string | { seconds?: string | number; nanos?: number };
294294
}
295295
interface Projection {
296296
fields?: FieldReference[];
@@ -333,12 +333,12 @@ export declare namespace firestoreV1ApiClientInterfaces {
333333
startAt?: Cursor;
334334
endAt?: Cursor;
335335
offset?: number;
336-
limit?: number;
336+
limit?: number | { value: number };
337337
}
338338
interface Target {
339339
query?: QueryTarget;
340340
documents?: DocumentsTarget;
341-
resumeToken?: string;
341+
resumeToken?: string | Uint8Array;
342342
readTime?: string;
343343
targetId?: number;
344344
once?: boolean;
@@ -347,8 +347,8 @@ export declare namespace firestoreV1ApiClientInterfaces {
347347
targetChangeType?: TargetChangeTargetChangeType;
348348
targetIds?: number[];
349349
cause?: Status;
350-
resumeToken?: string;
351-
readTime?: string;
350+
resumeToken?: string | Uint8Array;
351+
readTime?: string | { seconds?: string | number; nanos?: number };
352352
}
353353
interface TransactionOptions {
354354
readOnly?: ReadOnly;
@@ -382,17 +382,17 @@ export declare namespace firestoreV1ApiClientInterfaces {
382382
interface WriteRequest {
383383
streamId?: string;
384384
writes?: Write[];
385-
streamToken?: string;
385+
streamToken?: string | Uint8Array;
386386
labels?: ApiClientObjectMap<string>;
387387
}
388388
interface WriteResponse {
389389
streamId?: string;
390390
streamToken?: string;
391391
writeResults?: WriteResult[];
392-
commitTime?: string;
392+
commitTime?: string | { seconds?: string | number; nanos?: number };
393393
}
394394
interface WriteResult {
395-
updateTime?: string;
395+
updateTime?: string | { seconds?: string | number; nanos?: number };
396396
transformResults?: Value[];
397397
}
398398
}

packages/firestore/src/remote/serializer.ts

Lines changed: 22 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import { assert, fail } from '../util/assert';
5454
import { Code, FirestoreError } from '../util/error';
5555
import * as obj from '../util/obj';
5656
import { ByteString } from '../util/byte_string';
57-
import * as typeUtils from '../util/types';
57+
import { isNullOrUndefined } from '../util/types';
5858

5959
import {
6060
ArrayRemoveTransformOperation,
@@ -99,15 +99,13 @@ const OPERATORS = (() => {
9999
})();
100100

101101
function assertPresent(value: unknown, description: string): asserts value {
102-
assert(!typeUtils.isNullOrUndefined(value), description + ' is missing');
102+
assert(!isNullOrUndefined(value), description + ' is missing');
103103
}
104104

105-
// This is a supplement to the generated proto interfaces, which fail to account
106-
// for the fact that a timestamp may be encoded as either a string OR this.
107-
interface TimestampProto {
108-
seconds?: string | number;
109-
nanos?: number;
110-
}
105+
// Denotes the possible representations for timestamps in the Value type.
106+
export type TimestampValue =
107+
| string
108+
| { seconds?: string | number; nanos?: number };
111109

112110
export interface SerializerOptions {
113111
/**
@@ -148,46 +146,33 @@ export class JsonProtoSerializer {
148146
* our generated proto interfaces say Int32Value must be. But GRPC actually
149147
* expects a { value: <number> } struct.
150148
*/
151-
private toInt32Value(val: number | null): number | null {
152-
if (this.options.useProto3Json || typeUtils.isNullOrUndefined(val)) {
149+
private toInt32Value(val: number | null): number | { value: number } | null {
150+
if (this.options.useProto3Json || isNullOrUndefined(val)) {
153151
return val;
154152
} else {
155-
// ProtobufJS requires that we wrap Int32Values.
156-
// Use any because we need to match generated Proto types.
157-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
158-
return { value: val } as any;
153+
return { value: val };
159154
}
160155
}
161156

162157
/**
163158
* Returns a number (or null) from a google.protobuf.Int32Value proto.
164-
* DO NOT USE THIS FOR ANYTHING ELSE.
165-
* This method cheats. It's typed as accepting "number" because that's what
166-
* our generated proto interfaces say Int32Value must be, but it actually
167-
* accepts { value: number } to match our serialization in toInt32Value().
168159
*/
169-
private fromInt32Value(val: number | undefined): number | null {
160+
private fromInt32Value(
161+
val: number | { value: number } | undefined
162+
): number | null {
170163
let result;
171164
if (typeof val === 'object') {
172-
// Use any because we need to match generated Proto types.
173-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
174-
result = (val as any).value;
165+
result = val.value;
175166
} else {
176-
// We accept raw numbers (without the {value: ... } wrapper) for
177-
// compatibility with legacy persisted data.
178167
result = val;
179168
}
180-
return typeUtils.isNullOrUndefined(result) ? null : result;
169+
return isNullOrUndefined(result) ? null : result;
181170
}
182171

183172
/**
184173
* Returns a value for a Date that's appropriate to put into a proto.
185-
* DO NOT USE THIS FOR ANYTHING ELSE.
186-
* This method cheats. It's typed as returning "string" because that's what
187-
* our generated proto interfaces say dates must be. But it's easier and safer
188-
* to actually return a Timestamp proto.
189174
*/
190-
private toTimestamp(timestamp: Timestamp): string {
175+
private toTimestamp(timestamp: Timestamp): TimestampValue {
191176
if (this.options.useProto3Json) {
192177
// Serialize to ISO-8601 date format, but with full nano resolution.
193178
// Since JS Date has only millis, let's only use it for the seconds and
@@ -208,25 +193,21 @@ export class JsonProtoSerializer {
208193
}
209194
}
210195

211-
private fromTimestamp(date: string | TimestampProto): Timestamp {
196+
private fromTimestamp(date: TimestampValue): Timestamp {
212197
const timestamp = normalizeTimestamp(date);
213198
return new Timestamp(timestamp.seconds, timestamp.nanos);
214199
}
215200

216201
/**
217202
* Returns a value for bytes that's appropriate to put in a proto.
218-
* DO NOT USE THIS FOR ANYTHING ELSE.
219-
* This method cheats. It's typed as returning "string" because that's what
220-
* our generated proto interfaces say bytes must be. But it should return
221-
* an Uint8Array in Node.
222203
*
223204
* Visible for testing.
224205
*/
225-
toBytes(bytes: Blob | ByteString): string {
206+
toBytes(bytes: Blob | ByteString): string | Uint8Array {
226207
if (this.options.useProto3Json) {
227208
return bytes.toBase64();
228209
} else {
229-
return (bytes.toUint8Array() as unknown) as string;
210+
return bytes.toUint8Array();
230211
}
231212
}
232213

@@ -249,11 +230,11 @@ export class JsonProtoSerializer {
249230
}
250231
}
251232

252-
toVersion(version: SnapshotVersion): string {
233+
toVersion(version: SnapshotVersion): TimestampValue {
253234
return this.toTimestamp(version.toTimestamp());
254235
}
255236

256-
fromVersion(version: string): SnapshotVersion {
237+
fromVersion(version: TimestampValue): SnapshotVersion {
257238
assert(!!version, "Trying to deserialize version that isn't set");
258239
return SnapshotVersion.fromTimestamp(this.fromTimestamp(version));
259240
}
@@ -844,7 +825,7 @@ export class JsonProtoSerializer {
844825

845826
private fromWriteResult(
846827
proto: api.WriteResult,
847-
commitTime: string
828+
commitTime: TimestampValue
848829
): MutationResult {
849830
// NOTE: Deletes don't have an updateTime.
850831
let version = proto.updateTime
@@ -871,7 +852,7 @@ export class JsonProtoSerializer {
871852

872853
fromWriteResults(
873854
protos: api.WriteResult[] | undefined,
874-
commitTime?: string
855+
commitTime?: TimestampValue
875856
): MutationResult[] {
876857
if (protos && protos.length > 0) {
877858
assert(

packages/firestore/src/util/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export const isInteger: (value: unknown) => boolean =
5454
/**
5555
* Returns whether a variable is either undefined or null.
5656
*/
57-
export function isNullOrUndefined(value: unknown): boolean {
57+
export function isNullOrUndefined(value: unknown): value is null | undefined {
5858
return value === null || value === undefined;
5959
}
6060

packages/firestore/test/unit/specs/spec_test_runner.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import { DocumentOptions } from '../../../src/model/document';
6161
import { DocumentKey } from '../../../src/model/document_key';
6262
import { JsonObject } from '../../../src/model/field_value';
6363
import { Mutation } from '../../../src/model/mutation';
64+
import { normalizeByteString } from '../../../src/model/proto_values';
6465
import { PlatformSupport } from '../../../src/platform/platform';
6566
import * as api from '../../../src/protos/firestore_proto_api';
6667
import { Connection, Stream } from '../../../src/remote/connection';
@@ -69,7 +70,10 @@ import { ExistenceFilter } from '../../../src/remote/existence_filter';
6970
import { WriteRequest } from '../../../src/remote/persistent_stream';
7071
import { RemoteStore } from '../../../src/remote/remote_store';
7172
import { mapCodeFromRpcCode } from '../../../src/remote/rpc_error';
72-
import { JsonProtoSerializer } from '../../../src/remote/serializer';
73+
import {
74+
JsonProtoSerializer,
75+
TimestampValue
76+
} from '../../../src/remote/serializer';
7377
import { StreamBridge } from '../../../src/remote/stream_bridge';
7478
import {
7579
DocumentWatchChange,
@@ -196,7 +200,10 @@ class MockConnection implements Connection {
196200
return this.watchOpen.promise;
197201
}
198202

199-
ackWrite(commitTime?: string, mutationResults?: api.WriteResult[]): void {
203+
ackWrite(
204+
commitTime?: TimestampValue,
205+
mutationResults?: api.WriteResult[]
206+
): void {
200207
this.writeStream!.callOnMessage({
201208
// Convert to base64 string so it can later be parsed into ByteString.
202209
streamToken: PlatformSupport.getPlatform().btoa(
@@ -1151,11 +1158,17 @@ abstract class TestRunner {
11511158
expect(actualTarget.query).to.deep.equal(expectedTarget.query);
11521159
expect(actualTarget.targetId).to.equal(expectedTarget.targetId);
11531160
expect(actualTarget.readTime).to.equal(expectedTarget.readTime);
1154-
// actualTarget's resumeToken is a string, but the serialized
1155-
// resumeToken will be a base64 string, so we need to convert it back.
1156-
expect(actualTarget.resumeToken || '').to.equal(
1157-
this.platform.atob(expectedTarget.resumeToken || '')
1158-
);
1161+
if (expectedTarget.resumeToken !== undefined) {
1162+
// actualTarget's resumeToken is a string, but the serialized
1163+
// resumeToken will be a base64 string, so we need to convert it back.
1164+
expect(actualTarget.resumeToken).to.equal(
1165+
this.platform.atob(
1166+
normalizeByteString(expectedTarget.resumeToken).toBase64()
1167+
)
1168+
);
1169+
} else {
1170+
expect(actualTarget.resumeToken).to.be.undefined;
1171+
}
11591172
delete actualTargets[targetId];
11601173
});
11611174
expect(obj.size(actualTargets)).to.equal(

0 commit comments

Comments
 (0)