1
1
/**
2
2
* @license
3
- * Copyright 2017 Google Inc.
3
+ * Copyright 2017 Google LLC
4
4
*
5
5
* Licensed under the Apache License, Version 2.0 (the "License");
6
6
* you may not use this file except in compliance with the License.
@@ -23,8 +23,53 @@ import { nodeFromJSON } from '../snap/nodeFromJSON';
23
23
import { PRIORITY_INDEX } from '../snap/indexes/PriorityIndex' ;
24
24
import { Node } from '../snap/Node' ;
25
25
import { ChildrenNode } from '../snap/ChildrenNode' ;
26
+ import { SyncTree } from '../SyncTree' ;
26
27
import { Indexable } from './misc' ;
27
28
29
+ /* It's critical for performance that we do not calculate actual values from a SyncTree
30
+ * unless and until the value is needed. Because we expose both a SyncTree and Node
31
+ * version of deferred value resolution, we ned a wrapper class that will let us share
32
+ * code.
33
+ *
34
+ * @see https://github.com/firebase/firebase-js-sdk/issues/2487
35
+ */
36
+ interface ValueProvider {
37
+ getImmediateChild ( childName : string ) : ValueProvider ;
38
+ node ( ) : Node ;
39
+ }
40
+
41
+ class ExistingValueProvider implements ValueProvider {
42
+ constructor ( readonly node_ : Node ) { }
43
+
44
+ getImmediateChild ( childName : string ) : ValueProvider {
45
+ const child = this . node_ . getImmediateChild ( childName ) ;
46
+ return new ExistingValueProvider ( child ) ;
47
+ }
48
+
49
+ node ( ) : Node {
50
+ return this . node_ ;
51
+ }
52
+ }
53
+
54
+ class DeferredValueProvider implements ValueProvider {
55
+ private syncTree_ : SyncTree ;
56
+ private path_ : Path ;
57
+
58
+ constructor ( syncTree : SyncTree , path : Path ) {
59
+ this . syncTree_ = syncTree ;
60
+ this . path_ = path ;
61
+ }
62
+
63
+ getImmediateChild ( childName : string ) : ValueProvider {
64
+ const childPath = this . path_ . child ( childName ) ;
65
+ return new DeferredValueProvider ( this . syncTree_ , childPath ) ;
66
+ }
67
+
68
+ node ( ) : Node {
69
+ return this . syncTree_ . calcCompleteEventCache ( this . path_ ) ;
70
+ }
71
+ }
72
+
28
73
/**
29
74
* Generate placeholders for deferred values.
30
75
* @param {?Object } values
@@ -47,39 +92,92 @@ export const generateWithValues = function(
47
92
* @param {!Object } serverValues
48
93
* @return {!(string|number|boolean) }
49
94
*/
50
- export const resolveDeferredValue = function (
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- value : { [ k : string ] : any } | string | number | boolean ,
53
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
- serverValues : { [ k : string ] : any }
95
+ export const resolveDeferredLeafValue = function (
96
+ value : { [ k : string ] : unknown } | string | number | boolean ,
97
+ existingVal : ValueProvider ,
98
+ serverValues : { [ k : string ] : unknown }
55
99
) : string | number | boolean {
56
100
if ( ! value || typeof value !== 'object' ) {
57
101
return value as string | number | boolean ;
102
+ }
103
+ assert ( '.sv' in value , 'Unexpected leaf node or priority contents' ) ;
104
+
105
+ if ( typeof value [ '.sv' ] === 'string' ) {
106
+ return resolveScalarDeferredValue ( value [ '.sv' ] , existingVal , serverValues ) ;
107
+ } else if ( typeof value [ '.sv' ] === 'object' ) {
108
+ return resolveComplexDeferredValue ( value [ '.sv' ] , existingVal , serverValues ) ;
58
109
} else {
59
- assert ( '.sv' in value , 'Unexpected leaf node or priority contents' ) ;
60
- return serverValues [ value [ '.sv' ] ] ;
110
+ assert ( false , 'Unexpected server value: ' + JSON . stringify ( value , null , 2 ) ) ;
111
+ }
112
+ } ;
113
+
114
+ const resolveScalarDeferredValue = function (
115
+ op : string ,
116
+ existing : ValueProvider ,
117
+ serverValues : { [ k : string ] : unknown }
118
+ ) : string | number | boolean {
119
+ switch ( op ) {
120
+ case 'timestamp' :
121
+ return serverValues [ 'timestamp' ] as string | number | boolean ;
122
+ default :
123
+ assert ( false , 'Unexpected server value: ' + op ) ;
61
124
}
62
125
} ;
63
126
127
+ const resolveComplexDeferredValue = function (
128
+ op : object ,
129
+ existing : ValueProvider ,
130
+ unused : { [ k : string ] : unknown }
131
+ ) : string | number | boolean {
132
+ if ( ! op . hasOwnProperty ( 'increment' ) ) {
133
+ assert ( false , 'Unexpected server value: ' + JSON . stringify ( op , null , 2 ) ) ;
134
+ }
135
+ const delta = op [ 'increment' ] ;
136
+ if ( typeof delta !== 'number' ) {
137
+ assert ( false , 'Unexpected increment value: ' + delta ) ;
138
+ }
139
+
140
+ const existingNode = existing . node ( ) ;
141
+ assert (
142
+ existingNode !== null && typeof existingNode !== 'undefined' ,
143
+ 'Expected ChildrenNode.EMPTY_NODE for nulls'
144
+ ) ;
145
+
146
+ // Incrementing a non-number sets the value to the incremented amount
147
+ if ( ! existingNode . isLeafNode ( ) ) {
148
+ return delta ;
149
+ }
150
+
151
+ const leaf = existingNode as LeafNode ;
152
+ const existingVal = leaf . getValue ( ) ;
153
+ if ( typeof existingVal !== 'number' ) {
154
+ return delta ;
155
+ }
156
+
157
+ // No need to do over/underflow arithmetic here because JS only handles floats under the covers
158
+ return existingVal + delta ;
159
+ } ;
160
+
64
161
/**
65
162
* Recursively replace all deferred values and priorities in the tree with the
66
163
* specified generated replacement values.
67
- * @param {!SparseSnapshotTree } tree
164
+ * @param {!Path } path path to which write is relative
165
+ * @param {!Node } node new data written at path
166
+ * @param {!SyncTree } syncTree current data
68
167
* @param {!Object } serverValues
69
168
* @return {!SparseSnapshotTree }
70
169
*/
71
170
export const resolveDeferredValueTree = function (
72
- tree : SparseSnapshotTree ,
73
- serverValues : object
74
- ) : SparseSnapshotTree {
75
- const resolvedTree = new SparseSnapshotTree ( ) ;
76
- tree . forEachTree ( new Path ( '' ) , ( path , node ) => {
77
- resolvedTree . remember (
78
- path ,
79
- resolveDeferredValueSnapshot ( node , serverValues )
80
- ) ;
81
- } ) ;
82
- return resolvedTree ;
171
+ path : Path ,
172
+ node : Node ,
173
+ syncTree : SyncTree ,
174
+ serverValues : Indexable
175
+ ) : Node {
176
+ return resolveDeferredValue (
177
+ node ,
178
+ new DeferredValueProvider ( syncTree , path ) ,
179
+ serverValues
180
+ ) ;
83
181
} ;
84
182
85
183
/**
@@ -92,20 +190,41 @@ export const resolveDeferredValueTree = function(
92
190
*/
93
191
export const resolveDeferredValueSnapshot = function (
94
192
node : Node ,
95
- serverValues : object
193
+ existing : Node ,
194
+ serverValues : Indexable
195
+ ) : Node {
196
+ return resolveDeferredValue (
197
+ node ,
198
+ new ExistingValueProvider ( existing ) ,
199
+ serverValues
200
+ ) ;
201
+ } ;
202
+
203
+ function resolveDeferredValue (
204
+ node : Node ,
205
+ existingVal : ValueProvider ,
206
+ serverValues : Indexable
96
207
) : Node {
97
208
const rawPri = node . getPriority ( ) . val ( ) as
98
209
| Indexable
99
210
| boolean
100
211
| null
101
212
| number
102
213
| string ;
103
- const priority = resolveDeferredValue ( rawPri , serverValues ) ;
214
+ const priority = resolveDeferredLeafValue (
215
+ rawPri ,
216
+ existingVal . getImmediateChild ( '.priority' ) ,
217
+ serverValues
218
+ ) ;
104
219
let newNode : Node ;
105
220
106
221
if ( node . isLeafNode ( ) ) {
107
222
const leafNode = node as LeafNode ;
108
- const value = resolveDeferredValue ( leafNode . getValue ( ) , serverValues ) ;
223
+ const value = resolveDeferredLeafValue (
224
+ leafNode . getValue ( ) ,
225
+ existingVal ,
226
+ serverValues
227
+ ) ;
109
228
if (
110
229
value !== leafNode . getValue ( ) ||
111
230
priority !== leafNode . getPriority ( ) . val ( )
@@ -121,8 +240,9 @@ export const resolveDeferredValueSnapshot = function(
121
240
newNode = newNode . updatePriority ( new LeafNode ( priority ) ) ;
122
241
}
123
242
childrenNode . forEachChild ( PRIORITY_INDEX , ( childName , childNode ) => {
124
- const newChildNode = resolveDeferredValueSnapshot (
243
+ const newChildNode = resolveDeferredValue (
125
244
childNode ,
245
+ existingVal . getImmediateChild ( childName ) ,
126
246
serverValues
127
247
) ;
128
248
if ( newChildNode !== childNode ) {
@@ -131,4 +251,4 @@ export const resolveDeferredValueSnapshot = function(
131
251
} ) ;
132
252
return newNode ;
133
253
}
134
- } ;
254
+ }
0 commit comments