Skip to content

Commit df66d6e

Browse files
committed
feat(database): allow passing a vue ref
1 parent e20ed6b commit df66d6e

File tree

8 files changed

+325
-102
lines changed

8 files changed

+325
-102
lines changed

Diff for: src/database/index.ts

+117-49
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
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'
212
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'
420
import { rtdbUnbinds } from './optionsApi'
521
import { bindAsArray, bindAsObject, _DatabaseRefOptions } from './subscribe'
622

@@ -14,68 +30,98 @@ const ops: OperationsType = {
1430
remove: (array, index) => array.splice(index, 1),
1531
}
1632

17-
export interface _UseDatabaseRefOptions extends _DatabaseRefOptions {
33+
export interface UseDatabaseRefOptions extends _DatabaseRefOptions {
1834
target?: Ref<unknown>
1935
}
2036

2137
type UnbindType = ReturnType<typeof bindAsArray | typeof bindAsObject>
2238

2339
export function _useDatabaseRef(
24-
reference: DatabaseReference | Query,
25-
options: _UseDatabaseRefOptions = {}
40+
reference: _MaybeRef<DatabaseReference | Query>,
41+
options: UseDatabaseRefOptions = {}
2642
) {
27-
let unbind!: UnbindType
43+
let _unbind!: UnbindType
2844

2945
const data = options.target || ref<unknown | null>(options.initialValue)
3046
const error = ref<Error>()
3147
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+
})
3280

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)
5685
}
57-
})
86+
createdPromises.add(p)
87+
promise.value = p
5888

59-
promise
60-
.catch((reason) => {
89+
p.catch((reason) => {
6190
error.value = reason
62-
})
63-
.finally(() => {
91+
}).finally(() => {
6492
pending.value = false
6593
})
6694

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+
}
72108

73-
if (getCurrentScope()) {
109+
if (hasCurrentScope) {
74110
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)
76116
})
77117
}
78118

119+
// TODO: rename to stop
120+
function unbind() {
121+
stopWatcher()
122+
_unbind(options.reset)
123+
}
124+
79125
return Object.defineProperties(data, {
80126
data: { get: () => data },
81127
error: { get: () => error },
@@ -100,36 +146,58 @@ export function internalUnbind(
100146
// delete vm._firebaseUnbinds[key]
101147
}
102148

149+
export type UseListOptions = UseDatabaseRefOptions
150+
export type UseObjectOptions = UseDatabaseRefOptions
151+
103152
/**
104153
* Creates a reactive variable connected to the database.
105154
*
106155
* @param reference - Reference or query to the database
107156
* @param options - optional options
108157
*/
109158
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>> {
113162
const unbinds = {}
114163
const data = ref<T[]>([]) as Ref<T[]>
115164
return _useDatabaseRef(reference, {
116165
target: data,
117166
...options,
118-
}) as _RefDatabase<T[]>
167+
}) as _RefDatabase<VueDatabaseQueryData<T>>
119168
}
120169

121170
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> {
125174
const data = ref<T>() as Ref<T | undefined>
126175
return _useDatabaseRef(reference, {
127176
target: data,
128177
...options,
129-
}) as _RefDatabase<T | undefined>
178+
}) as _RefDatabase<VueDatabaseDocumentData<T> | undefined>
130179
}
131180

132181
export const unbind = (target: Ref, reset?: _DatabaseRefOptions['reset']) =>
133182
internalUnbind('', rtdbUnbinds.get(target), reset)
134183

135184
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+
>

Diff for: src/firestore/index.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from 'vue-demi'
1919
import {
2020
isDocumentRef,
21+
noop,
2122
OperationsType,
2223
walkSet,
2324
_MaybeRef,
@@ -101,9 +102,11 @@ export function _useFirestoreRef(
101102
})
102103
}
103104

104-
let unwatch: ReturnType<typeof watch> | undefined
105+
let stopWatcher: ReturnType<typeof watch> = noop
105106
if (isRef(docOrCollectionRef)) {
106-
unwatch = watch(docOrCollectionRef, bindFirestoreRef, { immediate: true })
107+
stopWatcher = watch(docOrCollectionRef, bindFirestoreRef, {
108+
immediate: true,
109+
})
107110
} else {
108111
bindFirestoreRef()
109112
}
@@ -126,9 +129,7 @@ export function _useFirestoreRef(
126129

127130
// TODO: rename to stop
128131
function unbind() {
129-
if (unwatch) {
130-
unwatch()
131-
}
132+
stopWatcher()
132133
_unbind(options.reset)
133134
}
134135

Diff for: src/index.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
export { useList, useObject } from './database'
2+
export type {
3+
UseListOptions,
4+
UseObjectOptions,
5+
_RefDatabase,
6+
UseDatabaseRefOptions,
7+
VueDatabaseDocumentData,
8+
VueDatabaseQueryData,
9+
} from './database'
210

311
export { databasePlugin } from './database/optionsApi'
412
export type { DatabasePluginOptions } from './database/optionsApi'
513

614
export { useCollection, useDocument } from './firestore'
7-
815
export type {
916
UseCollectionOptions,
1017
UseDocumentOptions,

Diff for: src/shared.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type {
77
} from 'firebase/firestore'
88
import type { Ref, ShallowRef } from 'vue-demi'
99

10+
export const noop = () => {}
11+
1012
// FIXME: replace any with unknown or T generics
1113

1214
export interface OperationsType {

0 commit comments

Comments
 (0)