Skip to content

Commit 0207dbf

Browse files
author
Brian Chen
committed
Add toJSON() support for Firestore classes
1 parent 02dcd92 commit 0207dbf

File tree

8 files changed

+98
-3
lines changed

8 files changed

+98
-3
lines changed

.changeset/clean-meals-double.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@firebase/app-compat': patch
3+
'@firebase/app': patch
4+
'@firebase/app-types': patch
5+
'@firebase/firestore': patch
6+
---
7+
8+
Firestore classes like DocumentReference and Query can now be serialized to JSON (#4258)

packages-exp/app-compat/src/firebaseApp.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ export class FirebaseAppImpl implements FirebaseApp {
134134
_addOrOverwriteComponent(component: Component): void {
135135
_addOrOverwriteComponent(this.app, component);
136136
}
137+
138+
toJSON(): object {
139+
return {
140+
name: this.name,
141+
automaticDataCollectionEnabled: this.automaticDataCollectionEnabled,
142+
options: this.options
143+
};
144+
}
137145
}
138146

139147
// TODO: investigate why the following needs to be commented out

packages/app-types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ export interface FirebaseNamespace {
112112
// Sets log handler for all Firebase components.
113113
onLog(logCallback: LogCallback, options?: LogOptions): void;
114114

115+
toJSON(): object;
116+
115117
// The current SDK version.
116118
SDK_VERSION: string;
117119
}

packages/app/src/firebaseApp.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ export class FirebaseAppImpl implements FirebaseApp {
163163
this.container.addOrOverwriteComponent(component);
164164
}
165165

166+
toJSON(): object {
167+
return {
168+
name: this.name,
169+
automaticDataCollectionEnabled: this.automaticDataCollectionEnabled,
170+
options: this.options
171+
};
172+
}
173+
166174
/**
167175
* This function will throw an Error if the App has already been deleted -
168176
* use before performing API actions on the App.

packages/firestore/src/remote/backoff.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ const LOG_TAG = 'ExponentialBackoff';
2424
* Initial backoff time in milliseconds after an error.
2525
* Set to 1s according to https://cloud.google.com/apis/design/errors.
2626
*/
27-
const DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000;
27+
export const DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000;
2828

29-
const DEFAULT_BACKOFF_FACTOR = 1.5;
29+
export const DEFAULT_BACKOFF_FACTOR = 1.5;
3030

3131
/** Maximum backoff time in milliseconds */
32-
const DEFAULT_BACKOFF_MAX_DELAY_MS = 60 * 1000;
32+
export const DEFAULT_BACKOFF_MAX_DELAY_MS = 60 * 1000;
3333

3434
/**
3535
* A helper for running delayed tasks following an exponential backoff curve
@@ -163,6 +163,15 @@ export class ExponentialBackoff {
163163
}
164164
}
165165

166+
toJSON(): object {
167+
return {
168+
timerId: this.timerId,
169+
initialDelayMs: this.initialDelayMs,
170+
backoffFactor: this.backoffFactor,
171+
maxDelayMs: this.maxDelayMs
172+
};
173+
}
174+
166175
/** Returns a random value in the range [-currentBaseMs/2, currentBaseMs/2] */
167176
private jitterDelayMs(): number {
168177
return (Math.random() - 0.5) * this.currentBaseMs;

packages/firestore/src/util/async_queue_impl.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,15 @@ export class AsyncQueueImpl implements AsyncQueue {
295295
debugAssert(index >= 0, 'Delayed operation not found.');
296296
this.delayedOperations.splice(index, 1);
297297
}
298+
299+
toJSON(): object {
300+
return {
301+
retryableOperationsCount: this.retryableOps.length,
302+
delayedOperationsCount: this.delayedOperations.length,
303+
operationsInProgress: this.operationInProgress,
304+
backoff: this.backoff
305+
};
306+
}
298307
}
299308

300309
export function newAsyncQueue(): AsyncQueue {

packages/firestore/test/unit/api/database.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ describe('CollectionReference', () => {
3232
expectEqual(collectionReference('foo'), collectionReference('foo'));
3333
expectNotEqual(collectionReference('foo'), collectionReference('bar'));
3434
});
35+
36+
it('JSON.stringify() does not throw', () => {
37+
JSON.stringify(collectionReference('foo'));
38+
});
3539
});
3640

3741
describe('DocumentReference', () => {
@@ -42,6 +46,10 @@ describe('DocumentReference', () => {
4246
documentReference('rooms/bar')
4347
);
4448
});
49+
50+
it('JSON.stringify() does not throw', () => {
51+
JSON.stringify(documentReference('foo/bar'));
52+
});
4553
});
4654

4755
describe('DocumentSnapshot', () => {
@@ -72,13 +80,24 @@ describe('DocumentSnapshot', () => {
7280
documentSnapshot('rooms/bar', { a: 1 }, false)
7381
);
7482
});
83+
84+
it('JSON.stringify() does not throw', () => {
85+
JSON.stringify(documentSnapshot('foo/bar', { a: 1 }, true));
86+
JSON.stringify(documentSnapshot('foo/bar', { a: 1 }, false));
87+
JSON.stringify(documentSnapshot('foo/bar', null, true));
88+
JSON.stringify(documentSnapshot('foo/bar', null, false));
89+
});
7590
});
7691

7792
describe('Query', () => {
7893
it('support equality checking with isEqual()', () => {
7994
expectEqual(query('foo'), query('foo'));
8095
expectNotEqual(query('foo'), query('bar'));
8196
});
97+
98+
it('JSON.stringify() does not throw', () => {
99+
JSON.stringify(query('foo'));
100+
});
82101
});
83102

84103
describe('QuerySnapshot', () => {
@@ -123,6 +142,12 @@ describe('QuerySnapshot', () => {
123142
querySnapshot('foo', {}, { a: { a: 1 } }, keys('foo/a'), false, true)
124143
);
125144
});
145+
146+
it('JSON.stringify() does not throw', () => {
147+
JSON.stringify(
148+
querySnapshot('foo', {}, { a: { a: 1 } }, keys(), false, false)
149+
);
150+
});
126151
});
127152

128153
describe('SnapshotMetadata', () => {

packages/firestore/test/unit/util/async_queue.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ import { expect, use } from 'chai';
2020
import * as chaiAsPromised from 'chai-as-promised';
2121

2222
import { IndexedDbTransactionError } from '../../../src/local/simple_db';
23+
import {
24+
DEFAULT_BACKOFF_FACTOR,
25+
DEFAULT_BACKOFF_INITIAL_DELAY_MS,
26+
DEFAULT_BACKOFF_MAX_DELAY_MS
27+
} from '../../../src/remote/backoff';
2328
import { fail } from '../../../src/util/assert';
2429
import { TimerId } from '../../../src/util/async_queue';
2530
import {
@@ -417,6 +422,27 @@ describe('AsyncQueue', () => {
417422
await queue.drain();
418423
expect(completedSteps).to.deep.equal([1, 2, 4]);
419424
});
425+
426+
it('serializes to JSON', async () => {
427+
const queue = newAsyncQueue() as AsyncQueueImpl;
428+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
429+
queue.enqueueAfterDelay(timerId1, 20000, () => Promise.resolve());
430+
// toJSON() does not recursively call itself when serializing elements.
431+
// We use JSON.stringify() here in order to check that `backoff` is
432+
// serialized properly.
433+
expect(JSON.parse(JSON.stringify(queue.toJSON()))).to.deep.equal({
434+
delayedOperationsCount: 1,
435+
operationsInProgress: false,
436+
retryableOperationsCount: 0,
437+
backoff: {
438+
timerId: TimerId.AsyncQueueRetry,
439+
initialDelayMs: DEFAULT_BACKOFF_INITIAL_DELAY_MS,
440+
backoffFactor: DEFAULT_BACKOFF_FACTOR,
441+
maxDelayMs: DEFAULT_BACKOFF_MAX_DELAY_MS
442+
}
443+
});
444+
await queue.drain();
445+
});
420446
});
421447

422448
function defer<T>(op: () => T): Promise<T> {

0 commit comments

Comments
 (0)