Skip to content

Commit 1a0638e

Browse files
Merge
2 parents 2f701bd + 9d87d70 commit 1a0638e

29 files changed

+399
-438
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
],
2323
"scripts": {
2424
"dev": "lerna run --parallel --scope @firebase/* --scope firebase --scope rxfire dev",
25-
"build": "lerna run --scope @firebase/* --scope firebase --scope rxfire prepare",
25+
"build": "lerna run --scope @firebase/* --scope firebase --scope rxfire build",
2626
"build:exp": "lerna run --scope @firebase/*-exp --scope firebase-exp build",
27+
"build:release": "lerna run --scope @firebase/* --scope firebase --ignore @firebase/*-exp --ignore firebase-exp prepare",
28+
"build:exp:release": "lerna run --scope @firebase/*-exp --scope firebase-exp prepare && yarn --cwd packages-exp/app-exp typings:public",
2729
"link:packages": "lerna exec --scope @firebase/* --scope firebase --scope rxfire -- yarn link",
2830
"stage:packages": "./scripts/prepublish.sh",
2931
"repl": "node tools/repl.js",

packages-exp/app-exp/package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,21 @@
1515
"lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'",
1616
"lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'",
1717
"build": "rollup -c && yarn api-report",
18-
"build:release": "rollup -c rollup.config.release.js && node ./use_public_typings.js",
18+
"build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public",
1919
"build:deps": "lerna run --scope @firebase/app-exp --include-dependencies build",
2020
"dev": "rollup -c -w",
2121
"test": "yarn type-check && run-p lint test:browser test:node",
2222
"test:ci": "node ../../scripts/run_tests_in_ci.js",
2323
"test:browser": "karma start --single-run",
2424
"test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha src/**/*.test.ts --config ../../config/mocharc.node.js",
2525
"type-check": "tsc -p . --noEmit",
26-
"prepare": "yarn build:release",
26+
"prepare": "rollup -c rollup.config.release.js",
2727
"api-report": "api-extractor run --local --verbose",
2828
"predoc": "node ../../scripts/exp/remove-exp.js temp",
2929
"doc": "api-documenter markdown --input temp --output docs",
30-
"build:doc": "yarn build && yarn doc"
30+
"build:doc": "yarn build && yarn doc",
31+
"typings:public": "node ./use_public_typings.js --public",
32+
"typings:internal": "node ./use_public_typings.js"
3133
},
3234
"dependencies": {
3335
"@firebase/app-types-exp": "0.0.800",
@@ -52,7 +54,7 @@
5254
"bugs": {
5355
"url": "https://github.com/firebase/firebase-js-sdk/issues"
5456
},
55-
"typings": "./dist/app-exp-public.d.ts",
57+
"typings": "./dist/app-exp.d.ts",
5658
"nyc": {
5759
"extension": [
5860
".ts"

packages-exp/app-exp/use_public_typings.js renamed to packages-exp/app-exp/use_typings.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-require-imports */
12
/**
23
* @license
34
* Copyright 2020 Google LLC
@@ -16,16 +17,23 @@
1617
*/
1718

1819
const { writeFileSync } = require('fs');
20+
const { argv } = require('yargs');
21+
22+
const path = require('path');
23+
const packageJsonPath = path.resolve(__dirname, './package.json');
1924

2025
// point typings field to the public d.ts file in package.json
21-
const PUBLIC_TYPINGS_PATH = './dist/app-exp-public.d.ts';
26+
const TYPINGS_PATH = argv.public
27+
? './dist/app-exp-public.d.ts'
28+
: './dist/app-exp.d.ts';
2229
console.log(
23-
`Updating the typings field to the public d.ts file ${PUBLIC_TYPINGS_PATH}`
30+
`Updating the packages-exp/app-exp typings field to the ${
31+
argv.public ? 'public' : 'internal'
32+
} d.ts file ${TYPINGS_PATH}`
2433
);
34+
const packageJson = require(packageJsonPath);
35+
packageJson.typings = TYPINGS_PATH;
2536

26-
const packageJson = require('./package.json');
27-
packageJson.typings = PUBLIC_TYPINGS_PATH;
28-
29-
writeFileSync('./package.json', `${JSON.stringify(packageJson, null, 2)}\n`, {
37+
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, {
3038
encoding: 'utf-8'
3139
});

packages/firebase/index.d.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6920,15 +6920,15 @@ declare namespace firebase.database.ServerValue {
69206920
* ```
69216921
*/
69226922
var TIMESTAMP: Object;
6923-
6923+
69246924
/**
6925-
* Returns a placeholder value that can be used to atomically increment the
6925+
* Returns a placeholder value that can be used to atomically increment the
69266926
* current database value by the provided delta.
69276927
*
69286928
* @param delta the amount to modify the current value atomically.
69296929
* @return a placeholder value for modifying data atomically server-side.
69306930
*/
6931-
function increment(delta: number) : Object;
6931+
function increment(delta: number): Object;
69326932
}
69336933

69346934
/**
@@ -7743,6 +7743,14 @@ declare namespace firebase.firestore {
77437743
* @webonly
77447744
*/
77457745
experimentalForceLongPolling?: boolean;
7746+
7747+
/**
7748+
* Whether to skip nested properties that are set to `undefined` during
7749+
* object serialization. If set to `true`, these properties are skipped
7750+
* and not written to Firestore. If set `false` or omitted, the SDK throws
7751+
* an exception when it encounters properties of type `undefined`.
7752+
*/
7753+
ignoreUndefinedProperties?: boolean;
77467754
}
77477755

77487756
/**

packages/firestore-types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface Settings {
2929
timestampsInSnapshots?: boolean;
3030
cacheSizeBytes?: number;
3131
experimentalForceLongPolling?: boolean;
32+
ignoreUndefinedProperties?: boolean;
3233
}
3334

3435
export interface PersistenceSettings {

packages/firestore/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# Unreleased
2+
- [feature] Added support for calling `FirebaseFiresore.settings` with
3+
`{ ignoreUndefinedProperties: true }`. When set, Firestore ignores
4+
undefined properties inside objects rather than rejecting the API call.
5+
6+
# Released
27
- [fixed] Fixed a regression introduced in v7.14.2 that incorrectly applied
38
a `FieldValue.increment` in combination with `set({...}, {merge: true})`.
49
- [fixed] Firestore now rejects `onSnapshot()` listeners if they cannot be

packages/firestore/src/api/database.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ const DEFAULT_HOST = 'firestore.googleapis.com';
9898
const DEFAULT_SSL = true;
9999
const DEFAULT_TIMESTAMPS_IN_SNAPSHOTS = true;
100100
const DEFAULT_FORCE_LONG_POLLING = false;
101+
const DEFAULT_IGNORE_UNDEFINED_PROPERTIES = false;
101102

102103
/**
103104
* Constant used to indicate the LRU garbage collection should be disabled.
@@ -142,6 +143,8 @@ class FirestoreSettings {
142143

143144
readonly forceLongPolling: boolean;
144145

146+
readonly ignoreUndefinedProperties: boolean;
147+
145148
// Can be a google-auth-library or gapi client.
146149
// eslint-disable-next-line @typescript-eslint/no-explicit-any
147150
credentials?: any;
@@ -169,7 +172,8 @@ class FirestoreSettings {
169172
'credentials',
170173
'timestampsInSnapshots',
171174
'cacheSizeBytes',
172-
'experimentalForceLongPolling'
175+
'experimentalForceLongPolling',
176+
'ignoreUndefinedProperties'
173177
]);
174178

175179
validateNamedOptionalType(
@@ -187,6 +191,13 @@ class FirestoreSettings {
187191
settings.timestampsInSnapshots
188192
);
189193

194+
validateNamedOptionalType(
195+
'settings',
196+
'boolean',
197+
'ignoreUndefinedProperties',
198+
settings.ignoreUndefinedProperties
199+
);
200+
190201
// Nobody should set timestampsInSnapshots anymore, but the error depends on
191202
// whether they set it to true or false...
192203
if (settings.timestampsInSnapshots === true) {
@@ -202,6 +213,8 @@ class FirestoreSettings {
202213
}
203214
this.timestampsInSnapshots =
204215
settings.timestampsInSnapshots ?? DEFAULT_TIMESTAMPS_IN_SNAPSHOTS;
216+
this.ignoreUndefinedProperties =
217+
settings.ignoreUndefinedProperties ?? DEFAULT_IGNORE_UNDEFINED_PROPERTIES;
205218

206219
validateNamedOptionalType(
207220
'settings',
@@ -232,9 +245,7 @@ class FirestoreSettings {
232245
settings.experimentalForceLongPolling
233246
);
234247
this.forceLongPolling =
235-
settings.experimentalForceLongPolling === undefined
236-
? DEFAULT_FORCE_LONG_POLLING
237-
: settings.experimentalForceLongPolling;
248+
settings.experimentalForceLongPolling ?? DEFAULT_FORCE_LONG_POLLING;
238249
}
239250

240251
isEqual(other: FirestoreSettings): boolean {
@@ -244,7 +255,8 @@ class FirestoreSettings {
244255
this.timestampsInSnapshots === other.timestampsInSnapshots &&
245256
this.credentials === other.credentials &&
246257
this.cacheSizeBytes === other.cacheSizeBytes &&
247-
this.forceLongPolling === other.forceLongPolling
258+
this.forceLongPolling === other.forceLongPolling &&
259+
this.ignoreUndefinedProperties === other.ignoreUndefinedProperties
248260
);
249261
}
250262
}
@@ -275,7 +287,7 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
275287
// TODO(mikelehen): Use modularized initialization instead.
276288
readonly _queue = new AsyncQueue();
277289

278-
readonly _dataReader: UserDataReader;
290+
_userDataReader: UserDataReader | undefined;
279291

280292
// Note: We are using `MemoryComponentProvider` as a default
281293
// ComponentProvider to ensure backwards compatibility with the format
@@ -310,7 +322,21 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
310322

311323
this._componentProvider = componentProvider;
312324
this._settings = new FirestoreSettings({});
313-
this._dataReader = new UserDataReader(this._databaseId);
325+
}
326+
327+
get _dataReader(): UserDataReader {
328+
debugAssert(
329+
!!this._firestoreClient,
330+
'Cannot obtain UserDataReader before instance is intitialized'
331+
);
332+
if (!this._userDataReader) {
333+
// Lazy initialize UserDataReader once the settings are frozen
334+
this._userDataReader = new UserDataReader(
335+
this._databaseId,
336+
this._settings.ignoreUndefinedProperties
337+
);
338+
}
339+
return this._userDataReader;
314340
}
315341

316342
settings(settingsLiteral: firestore.Settings): void {

packages/firestore/src/api/field_value.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ export class ArrayUnionFieldValueImpl extends FieldValueImpl {
109109
arrayElement: true
110110
},
111111
context.databaseId,
112-
context.serializer
112+
context.serializer,
113+
context.ignoreUndefinedProperties
113114
);
114115
const parsedElements = this._elements.map(
115116
element => parseData(element, parseContext)!
@@ -140,7 +141,8 @@ export class ArrayRemoveFieldValueImpl extends FieldValueImpl {
140141
arrayElement: true
141142
},
142143
context.databaseId,
143-
context.serializer
144+
context.serializer,
145+
context.ignoreUndefinedProperties
144146
);
145147
const parsedElements = this._elements.map(
146148
element => parseData(element, parseContext)!
@@ -167,7 +169,8 @@ export class NumericIncrementFieldValueImpl extends FieldValueImpl {
167169
methodName: this._methodName
168170
},
169171
context.databaseId,
170-
context.serializer
172+
context.serializer,
173+
context.ignoreUndefinedProperties
171174
);
172175
const operand = parseData(this._operand, parseContext)!;
173176
const numericIncrement = new NumericIncrementTransformOperation(

packages/firestore/src/api/user_data_reader.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ export class ParseContext {
158158
* @param settings The settings for the parser.
159159
* @param databaseId The database ID of the Firestore instance.
160160
* @param serializer The serializer to use to generate the Value proto.
161+
* @param ignoreUndefinedProperties Whether to ignore undefined properties
162+
* rather than throw.
161163
* @param fieldTransforms A mutable list of field transforms encountered while
162164
* parsing the data.
163165
* @param fieldMask A mutable list of field paths encountered while parsing
@@ -172,6 +174,7 @@ export class ParseContext {
172174
readonly settings: ContextSettings,
173175
readonly databaseId: DatabaseId,
174176
readonly serializer: JsonProtoSerializer,
177+
readonly ignoreUndefinedProperties: boolean,
175178
fieldTransforms?: FieldTransform[],
176179
fieldMask?: FieldPath[]
177180
) {
@@ -198,6 +201,7 @@ export class ParseContext {
198201
{ ...this.settings, ...configuration },
199202
this.databaseId,
200203
this.serializer,
204+
this.ignoreUndefinedProperties,
201205
this.fieldTransforms,
202206
this.fieldMask
203207
);
@@ -276,6 +280,7 @@ export class UserDataReader {
276280

277281
constructor(
278282
private readonly databaseId: DatabaseId,
283+
private readonly ignoreUndefinedProperties: boolean,
279284
serializer?: JsonProtoSerializer
280285
) {
281286
this.serializer =
@@ -458,7 +463,8 @@ export class UserDataReader {
458463
arrayElement: false
459464
},
460465
this.databaseId,
461-
this.serializer
466+
this.serializer,
467+
this.ignoreUndefinedProperties
462468
);
463469
}
464470

@@ -613,7 +619,10 @@ function parseSentinelFieldValue(
613619
*
614620
* @return The parsed value
615621
*/
616-
function parseScalarValue(value: unknown, context: ParseContext): api.Value {
622+
function parseScalarValue(
623+
value: unknown,
624+
context: ParseContext
625+
): api.Value | null {
617626
if (value === null) {
618627
return { nullValue: 'NULL_VALUE' };
619628
} else if (typeof value === 'number') {
@@ -659,6 +668,8 @@ function parseScalarValue(value: unknown, context: ParseContext): api.Value {
659668
value.firestore._databaseId
660669
)
661670
};
671+
} else if (value === undefined && context.ignoreUndefinedProperties) {
672+
return null;
662673
} else {
663674
throw context.createError(
664675
`Unsupported field value: ${valueDescription(value)}`

packages/firestore/src/core/firestore_client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export class FirestoreClient {
178178
persistenceResult
179179
).then(initializationDone.resolve, initializationDone.reject);
180180
} else {
181-
this.asyncQueue.enqueueAndForget(() => {
181+
this.asyncQueue.enqueueRetryable(() => {
182182
return this.handleCredentialChange(user);
183183
});
184184
}
@@ -497,7 +497,10 @@ export class FirestoreClient {
497497
if (this.clientTerminated) {
498498
return;
499499
}
500-
this.eventMgr.removeSnapshotsInSyncListener(observer);
500+
this.asyncQueue.enqueueAndForget(() => {
501+
this.eventMgr.removeSnapshotsInSyncListener(observer);
502+
return Promise.resolve();
503+
});
501504
}
502505

503506
get clientTerminated(): boolean {

packages/firestore/src/core/sync_engine.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -860,15 +860,15 @@ export class SyncEngine implements RemoteSyncer {
860860

861861
async handleCredentialChange(user: User): Promise<void> {
862862
const userChanged = !this.currentUser.isEqual(user);
863-
this.currentUser = user;
864863

865864
if (userChanged) {
865+
const result = await this.localStore.handleUserChange(user);
866+
this.currentUser = user;
867+
866868
// Fails tasks waiting for pending writes requested by previous user.
867869
this.rejectOutstandingPendingWritesCallbacks(
868870
"'waitForPendingWrites' promise is rejected due to a user change."
869871
);
870-
871-
const result = await this.localStore.handleUserChange(user);
872872
// TODO(b/114226417): Consider calling this only in the primary tab.
873873
this.sharedClientState.handleUserChange(
874874
user,

packages/firestore/src/util/input_validation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ export function validatePositiveNumber(
461461
if (n <= 0) {
462462
throw new FirestoreError(
463463
Code.INVALID_ARGUMENT,
464-
`Function "${functionName}()" requires its ${ordinal(
464+
`Function ${functionName}() requires its ${ordinal(
465465
position
466466
)} argument to be a positive number, but it was: ${n}.`
467467
);

0 commit comments

Comments
 (0)