@@ -18,9 +18,11 @@ import type {
18
18
DocumentSnapshot ,
19
19
FirestoreDataConverter ,
20
20
Query ,
21
+ QuerySnapshot ,
21
22
SnapshotListenOptions ,
22
23
SnapshotOptions ,
23
24
} from 'firebase/firestore'
25
+ import { getDoc , getDocs } from 'firebase/firestore'
24
26
import { onSnapshot } from 'firebase/firestore'
25
27
26
28
/**
@@ -33,6 +35,15 @@ export interface FirestoreRefOptions extends _DataSourceOptions {
33
35
*/
34
36
maxRefDepth ?: number
35
37
38
+ /**
39
+ * Should the data be fetched once rather than subscribing to changes.
40
+ * @experimental Still under development
41
+ */
42
+ once ?: boolean
43
+
44
+ /**
45
+ * @inheritDoc {SnapshotOptions}
46
+ */
36
47
snapshotOptions ?: SnapshotOptions
37
48
38
49
/**
@@ -102,7 +113,8 @@ function updateDataFromDocumentSnapshot<T>(
102
113
subs : Record < string , FirestoreSubscription > ,
103
114
ops : OperationsType ,
104
115
depth : number ,
105
- resolve : _ResolveRejectFn
116
+ resolve : _ResolveRejectFn ,
117
+ reject : _ResolveRejectFn
106
118
) {
107
119
const [ data , refs ] = extractRefs (
108
120
// @ts -expect-error: FIXME: use better types
@@ -112,40 +124,84 @@ function updateDataFromDocumentSnapshot<T>(
112
124
subs
113
125
)
114
126
ops . set ( target , path , data )
115
- subscribeToRefs ( options , target , path , subs , refs , ops , depth , resolve )
127
+ subscribeToRefs (
128
+ options ,
129
+ target ,
130
+ path ,
131
+ subs ,
132
+ refs ,
133
+ ops ,
134
+ depth ,
135
+ resolve ,
136
+ reject
137
+ )
116
138
}
117
139
118
140
interface SubscribeToDocumentParameter {
119
141
target : Ref < unknown >
120
142
path : string
121
143
depth : number
122
144
resolve : ( ) => void
145
+ reject : _ResolveRejectFn
123
146
ops : OperationsType
124
147
ref : DocumentReference
125
148
}
126
149
127
150
function subscribeToDocument (
128
- { ref, target, path, depth, resolve, ops } : SubscribeToDocumentParameter ,
151
+ {
152
+ ref,
153
+ target,
154
+ path,
155
+ depth,
156
+ resolve,
157
+ reject,
158
+ ops,
159
+ } : SubscribeToDocumentParameter ,
129
160
options : _DefaultsFirestoreRefOptions
130
161
) {
131
162
const subs = Object . create ( null )
132
- const unbind = onSnapshot ( ref , ( snapshot ) => {
133
- if ( snapshot . exists ( ) ) {
134
- updateDataFromDocumentSnapshot (
135
- options ,
136
- target ,
137
- path ,
138
- snapshot ,
139
- subs ,
140
- ops ,
141
- depth ,
142
- resolve
143
- )
144
- } else {
145
- ops . set ( target , path , null )
146
- resolve ( )
147
- }
148
- } )
163
+ let unbind = noop
164
+
165
+ if ( options . once ) {
166
+ getDoc ( ref ) . then ( ( snapshot ) => {
167
+ if ( snapshot . exists ( ) ) {
168
+ updateDataFromDocumentSnapshot (
169
+ options ,
170
+ target ,
171
+ path ,
172
+ snapshot ,
173
+ subs ,
174
+ ops ,
175
+ depth ,
176
+ resolve ,
177
+ reject
178
+ )
179
+ } else {
180
+ ops . set ( target , path , null )
181
+ resolve ( )
182
+ }
183
+ } )
184
+ // TODO: catch?
185
+ } else {
186
+ unbind = onSnapshot ( ref , ( snapshot ) => {
187
+ if ( snapshot . exists ( ) ) {
188
+ updateDataFromDocumentSnapshot (
189
+ options ,
190
+ target ,
191
+ path ,
192
+ snapshot ,
193
+ subs ,
194
+ ops ,
195
+ depth ,
196
+ resolve ,
197
+ reject
198
+ )
199
+ } else {
200
+ ops . set ( target , path , null )
201
+ resolve ( )
202
+ }
203
+ } )
204
+ }
149
205
150
206
return ( ) => {
151
207
unbind ( )
@@ -164,7 +220,8 @@ function subscribeToRefs(
164
220
refs : Record < string , DocumentReference > ,
165
221
ops : OperationsType ,
166
222
depth : number ,
167
- resolve : _ResolveRejectFn
223
+ resolve : _ResolveRejectFn ,
224
+ reject : _ResolveRejectFn
168
225
) {
169
226
const refKeys = Object . keys ( refs )
170
227
const missingKeys = Object . keys ( subs ) . filter (
@@ -210,6 +267,7 @@ function subscribeToRefs(
210
267
depth,
211
268
ops,
212
269
resolve : deepResolve . bind ( null , docPath ) ,
270
+ reject,
213
271
} ,
214
272
options
215
273
) ,
@@ -236,6 +294,7 @@ export function bindCollection<T = unknown>(
236
294
let arrayRef = ref ( wait ? [ ] : target [ key ] )
237
295
const originalResolve = resolve
238
296
let isResolved : boolean
297
+ let stopOnSnapshot = noop
239
298
240
299
// contain ref subscriptions of objects
241
300
// arraySubs is a mirror of array
@@ -260,15 +319,20 @@ export function bindCollection<T = unknown>(
260
319
refs ,
261
320
ops ,
262
321
0 ,
263
- resolve . bind ( null , doc )
322
+ resolve . bind ( null , doc ) ,
323
+ reject
264
324
)
265
325
} ,
266
326
modified : ( { oldIndex, newIndex, doc } : DocumentChange < T > ) => {
267
327
const array = unref ( arrayRef )
268
328
const subs = arraySubs [ oldIndex ]
269
329
const oldData = array [ oldIndex ]
270
- // @ts -expect-error: FIXME: Better types
271
- const [ data , refs ] = extractRefs ( doc . data ( snapshotOptions ) , oldData , subs )
330
+ const [ data , refs ] = extractRefs (
331
+ // @ts -expect-error: FIXME: Better types
332
+ doc . data ( snapshotOptions ) ,
333
+ oldData ,
334
+ subs
335
+ )
272
336
// only move things around after extracting refs
273
337
// only move things around after extracting refs
274
338
arraySubs . splice ( newIndex , 0 , subs )
@@ -282,7 +346,8 @@ export function bindCollection<T = unknown>(
282
346
refs ,
283
347
ops ,
284
348
0 ,
285
- resolve
349
+ resolve ,
350
+ reject
286
351
)
287
352
} ,
288
353
removed : ( { oldIndex } : DocumentChange < T > ) => {
@@ -292,61 +357,63 @@ export function bindCollection<T = unknown>(
292
357
} ,
293
358
}
294
359
295
- const stopOnSnapshot = onSnapshot (
296
- collection ,
297
- ( snapshot ) => {
298
- // console.log('pending', metadata.hasPendingWrites)
299
- // docs.forEach(d => console.log('doc', d, '\n', 'data', d.data()))
300
- // NOTE: this will only be triggered once and it will be with all the documents
301
- // from the query appearing as added
302
- // (https://firebase.google.com/docs/firestore/query-data/listen#view_changes_between_snapshots)
303
-
304
- const docChanges = snapshot . docChanges ( snapshotListenOptions )
305
-
306
- if ( ! isResolved && docChanges . length ) {
307
- // isResolved is only meant to make sure we do the check only once
308
- isResolved = true
309
- let count = 0
310
- const expectedItems = docChanges . length
311
- const validDocs = Object . create ( null )
312
- for ( let i = 0 ; i < expectedItems ; i ++ ) {
313
- validDocs [ docChanges [ i ] . doc . id ] = true
314
- }
360
+ function onSnapshotCallback ( snapshot : QuerySnapshot < T > ) {
361
+ // console.log('pending', metadata.hasPendingWrites)
362
+ // docs.forEach(d => console.log('doc', d, '\n', 'data', d.data()))
363
+ // NOTE: this will only be triggered once and it will be with all the documents
364
+ // from the query appearing as added
365
+ // (https://firebase.google.com/docs/firestore/query-data/listen#view_changes_between_snapshots)
366
+
367
+ const docChanges = snapshot . docChanges ( snapshotListenOptions )
368
+
369
+ if ( ! isResolved && docChanges . length ) {
370
+ // isResolved is only meant to make sure we do the check only once
371
+ isResolved = true
372
+ let count = 0
373
+ const expectedItems = docChanges . length
374
+ const validDocs = Object . create ( null )
375
+ for ( let i = 0 ; i < expectedItems ; i ++ ) {
376
+ validDocs [ docChanges [ i ] . doc . id ] = true
377
+ }
315
378
316
- resolve = ( data ) => {
317
- if ( data && ( data as any ) . id in validDocs ) {
318
- if ( ++ count >= expectedItems ) {
319
- // if wait is true, finally set the array
320
- if ( options . wait ) {
321
- ops . set ( target , key , unref ( arrayRef ) )
322
- // use the proxy object
323
- // arrayRef = target.value
324
- }
325
- originalResolve ( unref ( arrayRef ) )
326
- // reset resolve to noop
327
- resolve = noop
379
+ resolve = ( data ) => {
380
+ if ( data && ( data as any ) . id in validDocs ) {
381
+ if ( ++ count >= expectedItems ) {
382
+ // if wait is true, finally set the array
383
+ if ( options . wait ) {
384
+ ops . set ( target , key , unref ( arrayRef ) )
385
+ // use the proxy object
386
+ // arrayRef = target.value
328
387
}
388
+ originalResolve ( unref ( arrayRef ) )
389
+ // reset resolve to noop
390
+ resolve = noop
329
391
}
330
392
}
331
393
}
332
- docChanges . forEach ( ( c ) => {
333
- change [ c . type ] ( c )
334
- } )
335
-
336
- // resolves when array is empty
337
- // since this can only happen once, there is no need to guard against it
338
- // being called multiple times
339
- if ( ! docChanges . length ) {
340
- if ( options . wait ) {
341
- ops . set ( target , key , unref ( arrayRef ) )
342
- // use the proxy object
343
- // arrayRef = target.value
344
- }
345
- resolve ( unref ( arrayRef ) )
394
+ }
395
+ docChanges . forEach ( ( c ) => {
396
+ change [ c . type ] ( c )
397
+ } )
398
+
399
+ // resolves when array is empty
400
+ // since this can only happen once, there is no need to guard against it
401
+ // being called multiple times
402
+ if ( ! docChanges . length ) {
403
+ if ( options . wait ) {
404
+ ops . set ( target , key , unref ( arrayRef ) )
405
+ // use the proxy object
406
+ // arrayRef = target.value
346
407
}
347
- } ,
348
- reject
349
- )
408
+ resolve ( unref ( arrayRef ) )
409
+ }
410
+ }
411
+
412
+ if ( options . once ) {
413
+ getDocs ( collection ) . then ( onSnapshotCallback ) . catch ( reject )
414
+ } else {
415
+ stopOnSnapshot = onSnapshot ( collection , onSnapshotCallback , reject )
416
+ }
350
417
351
418
return ( reset ?: FirestoreRefOptions [ 'reset' ] ) => {
352
419
stopOnSnapshot ( )
@@ -378,27 +445,32 @@ export function bindDocument<T>(
378
445
// bind here the function so it can be resolved anywhere
379
446
// this is specially useful for refs
380
447
resolve = callOnceWithArg ( resolve , ( ) => walkGet ( target , key ) )
381
- const stopOnSnapshot = onSnapshot (
382
- document ,
383
- ( snapshot ) => {
384
- if ( snapshot . exists ( ) ) {
385
- updateDataFromDocumentSnapshot (
386
- options ,
387
- target ,
388
- key ,
389
- snapshot ,
390
- subs ,
391
- ops ,
392
- 0 ,
393
- resolve
394
- )
395
- } else {
396
- ops . set ( target , key , null )
397
- resolve ( null )
398
- }
399
- } ,
400
- reject
401
- )
448
+ let stopOnSnapshot = noop
449
+
450
+ function onSnapshotCallback ( snapshot : DocumentSnapshot < T > ) {
451
+ if ( snapshot . exists ( ) ) {
452
+ updateDataFromDocumentSnapshot (
453
+ options ,
454
+ target ,
455
+ key ,
456
+ snapshot ,
457
+ subs ,
458
+ ops ,
459
+ 0 ,
460
+ resolve ,
461
+ reject
462
+ )
463
+ } else {
464
+ ops . set ( target , key , null )
465
+ resolve ( null )
466
+ }
467
+ }
468
+
469
+ if ( options . once ) {
470
+ getDoc ( document ) . then ( onSnapshotCallback ) . catch ( reject )
471
+ } else {
472
+ stopOnSnapshot = onSnapshot ( document , onSnapshotCallback , reject )
473
+ }
402
474
403
475
return ( reset ?: FirestoreRefOptions [ 'reset' ] ) => {
404
476
stopOnSnapshot ( )
0 commit comments