1
1
import { firestore } from 'firebase'
2
- import { isTimestamp , isObject , isDocumentRef } from '../shared'
2
+ import { isTimestamp , isObject , isDocumentRef , TODO } from '../shared'
3
3
4
4
export type FirestoreReference =
5
5
| firestore . Query
6
6
| firestore . DocumentReference
7
7
| firestore . CollectionReference
8
8
9
- export function createSnapshot ( doc : firestore . DocumentSnapshot ) {
9
+ // TODO: fix type not to be any
10
+ export function createSnapshot ( doc : firestore . DocumentSnapshot ) : TODO {
10
11
// TODO: it should create a deep copy instead because otherwise we will modify internal data
11
12
// defaults everything to false, so no need to set
12
13
return Object . defineProperty ( doc . data ( ) || { } , 'id' , { value : doc . id } )
@@ -16,62 +17,80 @@ export type FirestoreSerializer = typeof createSnapshot
16
17
17
18
export function extractRefs (
18
19
doc : firestore . DocumentData ,
19
- oldDoc : firestore . DocumentData = { } ,
20
- subs : Record < string , { path : string ; data : ( ) => firestore . DocumentData } > = { } ,
21
- path = '' ,
22
- result : [ firestore . DocumentData , Record < string , firestore . DocumentReference > ] = [ { } , { } ]
20
+ oldDoc : firestore . DocumentData | void ,
21
+ subs : Record < string , { path : string ; data : ( ) => firestore . DocumentData } >
23
22
) : [ firestore . DocumentData , Record < string , firestore . DocumentReference > ] {
24
- // must be set here because walkGet can return null or undefined
25
- oldDoc = oldDoc || { }
26
- const [ data , refs ] = result
27
- // Add all properties that are not enumerable (not visible in the for loop)
28
- // getOwnPropertyDescriptors does not exist on IE
29
- // Object.getOwnPropertyNames(doc).forEach(propertyName => {
30
- // const descriptor = Object.getOwnPropertyDescriptor(doc, propertyName)
31
- // if (descriptor && !descriptor.enumerable) {
32
- // Object.defineProperty(data, propertyName, descriptor)
33
- // }
34
- // })
35
- const idDescriptor = Object . getOwnPropertyDescriptor ( doc , 'id' )
36
- if ( idDescriptor && ! idDescriptor . enumerable ) {
37
- Object . defineProperty ( data , 'id' , idDescriptor )
38
- }
23
+ const dataAndRefs : [ firestore . DocumentData , Record < string , firestore . DocumentReference > ] = [
24
+ { } ,
25
+ { } ,
26
+ ]
27
+
28
+ const subsByPath = Object . keys ( subs ) . reduce ( ( resultSubs , subKey ) => {
29
+ const sub = subs [ subKey ]
30
+ resultSubs [ sub . path ] = sub . data ( )
31
+ return resultSubs
32
+ } , { } as Record < string , firestore . DocumentData > )
33
+
34
+ function recursiveExtract (
35
+ doc : firestore . DocumentData ,
36
+ oldDoc : firestore . DocumentData | void ,
37
+ path : string ,
38
+ result : [ firestore . DocumentData , Record < string , firestore . DocumentReference > ]
39
+ ) : void {
40
+ // make it easier to later on access the value
41
+ oldDoc = oldDoc || { }
42
+ const [ data , refs ] = result
43
+ // Add all properties that are not enumerable (not visible in the for loop)
44
+ // getOwnPropertyDescriptors does not exist on IE
45
+ // Object.getOwnPropertyNames(doc).forEach(propertyName => {
46
+ // const descriptor = Object.getOwnPropertyDescriptor(doc, propertyName)
47
+ // if (descriptor && !descriptor.enumerable) {
48
+ // Object.defineProperty(data, propertyName, descriptor)
49
+ // }
50
+ // })
51
+ // TODO: add test for the code above and remove this one
52
+ const idDescriptor = Object . getOwnPropertyDescriptor ( doc , 'id' )
53
+ if ( idDescriptor && ! idDescriptor . enumerable ) {
54
+ Object . defineProperty ( data , 'id' , idDescriptor )
55
+ }
39
56
40
- for ( const key in doc ) {
41
- const ref = doc [ key ]
42
- if ( isDocumentRef ( ref ) ) {
43
- // allow values to be null (like non-existant refs)
44
- // TODO: better typing since this isObject shouldn't be necessary but it doesn't work
45
- data [ key ] = typeof oldDoc === 'object' && key in oldDoc ? oldDoc [ key ] : ref . path
46
- // TODO handle subpathes?
47
- refs [ path + key ] = ref
48
- } else if ( Array . isArray ( ref ) ) {
49
- data [ key ] = Array ( ref . length )
50
- // fill existing refs into data but leave the rest empty
51
- for ( let i = 0 ; i < ref . length ; i ++ ) {
52
- const newRef = ref [ i ]
53
- const existingSub =
54
- subs [ Object . keys ( subs ) . find ( subName => subs [ subName ] . path === newRef . path ) ! ]
55
- if ( existingSub ) {
56
- data [ key ] [ i ] = existingSub . data ( )
57
+ // recursively traverse doc to copy values and extract references
58
+ for ( const key in doc ) {
59
+ const ref = doc [ key ]
60
+ if (
61
+ // primitives
62
+ ref == null ||
63
+ // Firestore < 4.13
64
+ ref instanceof Date ||
65
+ isTimestamp ( ref ) ||
66
+ ( ref . longitude && ref . latitude ) // GeoPoint
67
+ ) {
68
+ data [ key ] = ref
69
+ } else if ( isDocumentRef ( ref ) ) {
70
+ // allow values to be null (like non-existant refs)
71
+ // TODO: better typing since this isObject shouldn't be necessary but it doesn't work
72
+ data [ key ] = typeof oldDoc === 'object' && key in oldDoc ? oldDoc [ key ] : ref . path
73
+ // TODO: handle subpathes?
74
+ refs [ path + key ] = ref
75
+ } else if ( Array . isArray ( ref ) ) {
76
+ data [ key ] = Array ( ref . length )
77
+ // fill existing refs into data but leave the rest empty
78
+ for ( let i = 0 ; i < ref . length ; i ++ ) {
79
+ const newRef = ref [ i ]
80
+ if ( newRef . path in subsByPath ) data [ key ] [ i ] = subsByPath [ newRef . path ]
57
81
}
82
+ // the oldArray is in this case the same array with holes
83
+ recursiveExtract ( ref , data [ key ] , path + key + '.' , [ data [ key ] , refs ] )
84
+ } else if ( isObject ( ref ) ) {
85
+ data [ key ] = { }
86
+ recursiveExtract ( ref , oldDoc [ key ] , path + key + '.' , [ data [ key ] , refs ] )
87
+ } else {
88
+ data [ key ] = ref
58
89
}
59
- // the oldArray is in this case the same array with holes
60
- extractRefs ( ref , data [ key ] , subs , path + key + '.' , [ data [ key ] , refs ] )
61
- } else if (
62
- ref == null ||
63
- // Firestore < 4.13
64
- ref instanceof Date ||
65
- isTimestamp ( ref ) ||
66
- ( ref . longitude && ref . latitude ) // GeoPoint
67
- ) {
68
- data [ key ] = ref
69
- } else if ( isObject ( ref ) ) {
70
- data [ key ] = { }
71
- extractRefs ( ref , oldDoc [ key ] , subs , path + key + '.' , [ data [ key ] , refs ] )
72
- } else {
73
- data [ key ] = ref
74
90
}
75
91
}
76
- return result
92
+
93
+ recursiveExtract ( doc , oldDoc , '' , dataAndRefs )
94
+
95
+ return dataAndRefs
77
96
}
0 commit comments