1
- import { Ref , ref , getCurrentScope , onScopeDispose } from 'vue-demi'
1
+ import {
2
+ Ref ,
3
+ ref ,
4
+ getCurrentScope ,
5
+ onScopeDispose ,
6
+ shallowRef ,
7
+ ShallowRef ,
8
+ unref ,
9
+ watch ,
10
+ isRef ,
11
+ } from 'vue-demi'
2
12
import type { DatabaseReference , Query } from 'firebase/database'
3
- import { OperationsType , walkSet , _RefWithState } from '../shared'
13
+ import {
14
+ noop ,
15
+ OperationsType ,
16
+ walkSet ,
17
+ _MaybeRef ,
18
+ _RefWithState ,
19
+ } from '../shared'
4
20
import { rtdbUnbinds } from './optionsApi'
5
21
import { bindAsArray , bindAsObject , _DatabaseRefOptions } from './subscribe'
6
22
@@ -14,68 +30,98 @@ const ops: OperationsType = {
14
30
remove : ( array , index ) => array . splice ( index , 1 ) ,
15
31
}
16
32
17
- export interface _UseDatabaseRefOptions extends _DatabaseRefOptions {
33
+ export interface UseDatabaseRefOptions extends _DatabaseRefOptions {
18
34
target ?: Ref < unknown >
19
35
}
20
36
21
37
type UnbindType = ReturnType < typeof bindAsArray | typeof bindAsObject >
22
38
23
39
export function _useDatabaseRef (
24
- reference : DatabaseReference | Query ,
25
- options : _UseDatabaseRefOptions = { }
40
+ reference : _MaybeRef < DatabaseReference | Query > ,
41
+ options : UseDatabaseRefOptions = { }
26
42
) {
27
- let unbind ! : UnbindType
43
+ let _unbind ! : UnbindType
28
44
29
45
const data = options . target || ref < unknown | null > ( options . initialValue )
30
46
const error = ref < Error > ( )
31
47
const pending = ref ( true )
48
+ // force the type since its value is set right after and undefined isn't possible
49
+ const promise = shallowRef ( ) as ShallowRef < Promise < unknown | null > >
50
+ const createdPromises = new Set < Promise < unknown | null > > ( )
51
+ const hasCurrentScope = getCurrentScope ( )
52
+
53
+ function bindDatabaseRef ( ) {
54
+ const p = new Promise < unknown | null > ( ( resolve , reject ) => {
55
+ const referenceValue = unref ( reference )
56
+ if ( Array . isArray ( data . value ) ) {
57
+ _unbind = bindAsArray (
58
+ {
59
+ target : data ,
60
+ collection : referenceValue ,
61
+ resolve,
62
+ reject,
63
+ ops,
64
+ } ,
65
+ options
66
+ )
67
+ } else {
68
+ _unbind = bindAsObject (
69
+ {
70
+ target : data ,
71
+ document : referenceValue ,
72
+ resolve,
73
+ reject,
74
+ ops,
75
+ } ,
76
+ options
77
+ )
78
+ }
79
+ } )
32
80
33
- const promise = new Promise ( ( resolve , reject ) => {
34
- if ( Array . isArray ( data . value ) ) {
35
- unbind = bindAsArray (
36
- {
37
- target : data ,
38
- collection : reference ,
39
- resolve,
40
- reject,
41
- ops,
42
- } ,
43
- options
44
- )
45
- } else {
46
- unbind = bindAsObject (
47
- {
48
- target : data ,
49
- document : reference ,
50
- resolve,
51
- reject,
52
- ops,
53
- } ,
54
- options
55
- )
81
+ // only add the first promise to the pending ones
82
+ if ( ! createdPromises . size ) {
83
+ // TODO: add the pending promise like in firestore
84
+ // pendingPromises.add(p)
56
85
}
57
- } )
86
+ createdPromises . add ( p )
87
+ promise . value = p
58
88
59
- promise
60
- . catch ( ( reason ) => {
89
+ p . catch ( ( reason ) => {
61
90
error . value = reason
62
- } )
63
- . finally ( ( ) => {
91
+ } ) . finally ( ( ) => {
64
92
pending . value = false
65
93
} )
66
94
67
- // TODO: SSR serialize the values for Nuxt to expose them later and use them
68
- // as initial values while specifying a wait: true to only swap objects once
69
- // Firebase has done its initial sync. Also, on server, you don't need to
70
- // create sync, you can read only once the whole thing so maybe _useDatabaseRef
71
- // should take an option like once: true to not setting up any listener
95
+ // TODO: SSR serialize the values for Nuxt to expose them later and use them
96
+ // as initial values while specifying a wait: true to only swap objects once
97
+ // Firebase has done its initial sync. Also, on server, you don't need to
98
+ // create sync, you can read only once the whole thing so maybe _useDatabaseRef
99
+ // should take an option like once: true to not setting up any listener
100
+ }
101
+
102
+ let stopWatcher : ReturnType < typeof watch > = noop
103
+ if ( isRef ( reference ) ) {
104
+ stopWatcher = watch ( reference , bindDatabaseRef , { immediate : true } )
105
+ } else {
106
+ bindDatabaseRef ( )
107
+ }
72
108
73
- if ( getCurrentScope ( ) ) {
109
+ if ( hasCurrentScope ) {
74
110
onScopeDispose ( ( ) => {
75
- unbind ( options . reset )
111
+ // TODO: clear pending promises
112
+ // for (const p of createdPromises) {
113
+ // pendingPromises.delete(p)
114
+ // }
115
+ _unbind ( options . reset )
76
116
} )
77
117
}
78
118
119
+ // TODO: rename to stop
120
+ function unbind ( ) {
121
+ stopWatcher ( )
122
+ _unbind ( options . reset )
123
+ }
124
+
79
125
return Object . defineProperties ( data , {
80
126
data : { get : ( ) => data } ,
81
127
error : { get : ( ) => error } ,
@@ -100,36 +146,58 @@ export function internalUnbind(
100
146
// delete vm._firebaseUnbinds[key]
101
147
}
102
148
149
+ export type UseListOptions = UseDatabaseRefOptions
150
+ export type UseObjectOptions = UseDatabaseRefOptions
151
+
103
152
/**
104
153
* Creates a reactive variable connected to the database.
105
154
*
106
155
* @param reference - Reference or query to the database
107
156
* @param options - optional options
108
157
*/
109
158
export function useList < T = unknown > (
110
- reference : DatabaseReference | Query ,
111
- options ?: _DatabaseRefOptions
112
- ) : _RefDatabase < T [ ] > {
159
+ reference : _MaybeRef < DatabaseReference | Query > ,
160
+ options ?: UseListOptions
161
+ ) : _RefDatabase < VueDatabaseQueryData < T > > {
113
162
const unbinds = { }
114
163
const data = ref < T [ ] > ( [ ] ) as Ref < T [ ] >
115
164
return _useDatabaseRef ( reference , {
116
165
target : data ,
117
166
...options ,
118
- } ) as _RefDatabase < T [ ] >
167
+ } ) as _RefDatabase < VueDatabaseQueryData < T > >
119
168
}
120
169
121
170
export function useObject < T = unknown > (
122
- reference : DatabaseReference ,
123
- options ?: _DatabaseRefOptions
124
- ) : _RefDatabase < T | undefined > {
171
+ reference : _MaybeRef < DatabaseReference > ,
172
+ options ?: UseObjectOptions
173
+ ) : _RefDatabase < VueDatabaseDocumentData < T > | undefined > {
125
174
const data = ref < T > ( ) as Ref < T | undefined >
126
175
return _useDatabaseRef ( reference , {
127
176
target : data ,
128
177
...options ,
129
- } ) as _RefDatabase < T | undefined >
178
+ } ) as _RefDatabase < VueDatabaseDocumentData < T > | undefined >
130
179
}
131
180
132
181
export const unbind = ( target : Ref , reset ?: _DatabaseRefOptions [ 'reset' ] ) =>
133
182
internalUnbind ( '' , rtdbUnbinds . get ( target ) , reset )
134
183
135
184
export interface _RefDatabase < T > extends _RefWithState < T , Error > { }
185
+
186
+ /**
187
+ * Type used by default by the `serialize` option.
188
+ */
189
+ export type VueDatabaseDocumentData < T = unknown > =
190
+ | null
191
+ | ( T & {
192
+ /**
193
+ * id of the document
194
+ */
195
+ readonly id : string
196
+ } )
197
+
198
+ /**
199
+ * Same as VueDatabaseDocumentData but for a query.
200
+ */
201
+ export type VueDatabaseQueryData < T = unknown > = Array <
202
+ Exclude < VueDatabaseDocumentData < T > , null >
203
+ >
0 commit comments