Skip to content

Commit 1c8012e

Browse files
committed
feat(types): deprecate serializer in favor of converter
1 parent b411802 commit 1c8012e

File tree

6 files changed

+99
-20
lines changed

6 files changed

+99
-20
lines changed

src/firestore/index.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { createSnapshot, extractRefs, FirestoreSerializer } from './utils'
1+
import {
2+
createSnapshot,
3+
extractRefs,
4+
firestoreDefaultConverter,
5+
FirestoreSerializer,
6+
} from './utils'
27
import { walkGet, callOnceWithArg, OperationsType } from '../shared'
38
import { ref, Ref, unref } from 'vue-demi'
49
import type {
@@ -7,21 +12,29 @@ import type {
712
DocumentData,
813
DocumentReference,
914
DocumentSnapshot,
15+
FirestoreDataConverter,
1016
Query,
1117
} from 'firebase/firestore'
1218
import { onSnapshot } from 'firebase/firestore'
1319

1420
export interface FirestoreOptions {
1521
maxRefDepth?: number
1622
reset?: boolean | (() => any)
23+
/**
24+
* @deprecated use `converter` instead
25+
*/
1726
serialize?: FirestoreSerializer
27+
28+
converter?: FirestoreDataConverter<unknown>
29+
1830
wait?: boolean
1931
}
2032

2133
const DEFAULT_OPTIONS: Required<FirestoreOptions> = {
2234
maxRefDepth: 2,
2335
reset: true,
2436
serialize: createSnapshot,
37+
converter: firestoreDefaultConverter,
2538
wait: false,
2639
}
2740
export { DEFAULT_OPTIONS as firestoreOptions }

src/firestore/utils.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import type {
1+
import {
22
Query,
33
DocumentReference,
44
CollectionReference,
55
DocumentSnapshot,
66
DocumentData,
77
GeoPoint,
8+
doc,
9+
FirestoreDataConverter,
810
} from 'firebase/firestore'
911
import { isTimestamp, isObject, isDocumentRef, TODO } from '../shared'
1012

@@ -21,6 +23,28 @@ export function createSnapshot<T = DocumentData>(
2123
return Object.defineProperty(doc.data() || {}, 'id', { value: doc.id })
2224
}
2325

26+
export const firestoreDefaultConverter: FirestoreDataConverter<unknown> = {
27+
toFirestore(data) {
28+
// this is okay because we declare other properties as non-enumerable
29+
return data as DocumentData
30+
},
31+
fromFirestore(snapshot, options) {
32+
return snapshot.exists()
33+
? Object.defineProperties(snapshot.data(options)!, {
34+
id: {
35+
// TODO: can the `id` change? If so this should be a get
36+
value: () => snapshot.id,
37+
},
38+
// TODO: check if worth adding or should be through an option
39+
// $meta: {
40+
// value: snapshot.metadata,
41+
// },
42+
// $ref: { get: () => snapshot.ref },
43+
})
44+
: null
45+
},
46+
}
47+
2448
export type FirestoreSerializer = typeof createSnapshot
2549

2650
export function extractRefs(
@@ -66,6 +90,7 @@ export function extractRefs(
6690
if (
6791
// primitives
6892
ref == null ||
93+
// TODO: check and remove
6994
// Firestore < 4.13
7095
ref instanceof Date ||
7196
isTimestamp(ref) ||

src/shared.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type {
22
CollectionReference,
33
DocumentData,
44
DocumentReference,
5+
DocumentSnapshot,
6+
QuerySnapshot,
57
} from 'firebase/firestore'
68
import type { Ref } from 'vue-demi'
79

@@ -18,7 +20,8 @@ export interface OperationsType {
1820
remove<T extends unknown = unknown>(array: T[], index: number): T[]
1921
}
2022

21-
export type ResetOption = boolean | (() => any)
23+
// TODO: probably boolean | void or just boolean
24+
export type ResetOption = boolean | (() => TODO)
2225

2326
export type TODO = any
2427
/**
@@ -92,8 +95,6 @@ export function isCollectionRef(o: any): o is CollectionReference {
9295
return isObject(o) && o.type === 'collection'
9396
}
9497

95-
type ReferenceType = 'collection' | 'document' | 'query'
96-
9798
/**
9899
* Wraps a function so it gets called only once
99100
* @param fn Function to be called once

src/vuefire/firestore.ts

+27-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
DocumentReference,
1111
Query,
1212
FirestoreError,
13+
DocumentData,
1314
} from 'firebase/firestore'
1415
import {
1516
getCurrentInstance,
@@ -22,8 +23,6 @@ import {
2223
import { isDocumentRef, _RefWithState } from '../shared'
2324
import { firestoreUnbinds } from './optionsApi'
2425

25-
export interface _RefFirestore<T> extends _RefWithState<T, FirestoreError> {}
26-
2726
export const ops: OperationsType = {
2827
set: (target, key, value) => walkSet(target, key, value),
2928
add: (array, index, data) => array.splice(index, 0, data),
@@ -162,7 +161,7 @@ export function useCollection<
162161
>(
163162
collectionRef: R,
164163
options?: UseCollectionOptions
165-
): Ref<_InferReferenceType<R>[]>
164+
): _RefFirestore<_InferReferenceType<R>[]>
166165

167166
/**
168167
* Creates a reactive collection (usually an array) of documents from a collection ref or a query from Firestore.
@@ -175,13 +174,15 @@ export function useCollection<
175174
export function useCollection<T>(
176175
collectionRef: CollectionReference | Query,
177176
options?: UseCollectionOptions
178-
): Ref<T[]>
177+
): _RefFirestore<VueFireQueryData<T>>
179178

180179
export function useCollection<T>(
181180
collectionRef: CollectionReference<unknown> | Query<unknown>,
182181
options?: UseCollectionOptions
183-
): Ref<_InferReferenceType<T>[]> | Ref<T[]> {
184-
return _useFirestoreRef(collectionRef, options) as _RefFirestore<T[]>
182+
): _RefFirestore<VueFireQueryData<T>> {
183+
return _useFirestoreRef(collectionRef, options) as _RefFirestore<
184+
VueFireQueryData<T>
185+
>
185186
}
186187

187188
export interface UseDocumentOptions {}
@@ -198,7 +199,7 @@ export function useDocument<
198199
>(
199200
documentRef: R,
200201
options?: UseDocumentOptions
201-
): _RefFirestore<_InferReferenceType<R> | null>
202+
): _RefFirestore<_InferReferenceType<R>> // this one can't be null or should be specified in the converter
202203

203204
/**
204205
* Creates a reactive collection (usually an array) of documents from a collection ref or a query from Firestore.
@@ -211,7 +212,7 @@ export function useDocument<
211212
export function useDocument<T>(
212213
documentRef: DocumentReference,
213214
options?: UseDocumentOptions
214-
): _RefFirestore<T | null>
215+
): _RefFirestore<VueFireDocumentData<T>>
215216

216217
export function useDocument<T>(
217218
documentRef: DocumentReference<unknown>,
@@ -248,3 +249,21 @@ export type _InferReferenceType<R> = R extends
248249
| DocumentReference<infer T>
249250
? T
250251
: R
252+
253+
/**
254+
* Type used by default by the `firestoreDefaultConverter`.
255+
*/
256+
export type VueFireDocumentData<T = DocumentData> =
257+
| null
258+
| (T & {
259+
/**
260+
* id of the document
261+
*/
262+
readonly id: string
263+
})
264+
265+
export type VueFireQueryData<T = DocumentData> = Array<
266+
Exclude<VueFireDocumentData<T>, null>
267+
>
268+
269+
export interface _RefFirestore<T> extends _RefWithState<T, FirestoreError> {}

tests/firestore/collection.spec.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,36 @@ describe('Firestore collections', () => {
4646
})
4747

4848
tds(() => {
49+
interface TodoI {
50+
text: string
51+
finished: boolean
52+
}
53+
4954
const db = firestore
5055
const collection = originalCollection
5156
expectType<Ref<DocumentData[]>>(useCollection(collection(db, 'todos')))
52-
// @ts-expect-error
57+
// @ts-expect-error: document data by default
5358
expectType<Ref<number[]>>(useCollection(collection(db, 'todos')))
5459

55-
expectType<Ref<number[]>>(useCollection<number>(collection(db, 'todos')))
56-
// @ts-expect-error
57-
expectType<Ref<string[]>>(useCollection<number>(collection(db, 'todos')))
60+
expectType<Ref<TodoI[]>>(useCollection<TodoI>(collection(db, 'todos')))
61+
expectType<Ref<TodoI[]>>(useCollection<TodoI>(collection(db, 'todos')).data)
62+
expectType<string>(
63+
useCollection<TodoI>(collection(db, 'todos')).value.at(0)!.id
64+
)
65+
expectType<string>(
66+
useCollection<TodoI>(collection(db, 'todos')).data.value.at(0)!.id
67+
)
68+
// @ts-expect-error: wrong type
69+
expectType<Ref<string[]>>(useCollection<TodoI>(collection(db, 'todos')))
5870

5971
const refWithConverter = collection(db, 'todos').withConverter<number>({
6072
toFirestore: (data) => ({ n: data }),
6173
fromFirestore: (snap, options) => snap.data(options).n as number,
6274
})
6375
expectType<Ref<number[]>>(useCollection(refWithConverter))
76+
expectType<Ref<number[]>>(useCollection(refWithConverter).data)
77+
// @ts-expect-error: no id with converter
78+
expectType<Ref<number[]>>(useCollection(refWithConverter).data.value.id)
6479
// @ts-expect-error
6580
expectType<Ref<string[]>>(useCollection(refWithConverter))
6681
})

tests/firestore/document.spec.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,22 @@ describe('Firestore documents', () => {
4848
expectType<Ref<number | null>>(useDocument(itemRef))
4949

5050
expectType<Ref<number | null>>(useDocument<number>(itemRef))
51+
expectType<Ref<number | null>>(useDocument<number>(itemRef).data)
5152
// @ts-expect-error
5253
expectType<Ref<string | null>>(useDocument<number>(itemRef))
5354

5455
const refWithConverter = itemRef.withConverter<number>({
5556
toFirestore: (data) => ({ n: data }),
5657
fromFirestore: (snap, options) => snap.data(options).n as number,
5758
})
58-
expectType<Ref<number | null>>(useDocument(refWithConverter))
59-
// @ts-expect-error
60-
expectType<Ref<string | null>>(useDocument(refWithConverter))
59+
expectType<Ref<number>>(useDocument(refWithConverter))
60+
expectType<Ref<number>>(useDocument(refWithConverter).data)
61+
// should not be null
62+
useDocument(refWithConverter).value.toFixed(14)
63+
// @ts-expect-error: string is not assignable to number
64+
expectType<Ref<string>>(useDocument(refWithConverter))
65+
// @ts-expect-error: no id when a custom converter is used
66+
useDocument(refWithConverter).value.id
6167

6268
// destructuring
6369
expectType<Ref<DocumentData | null>>(useDocument(itemRef).data)

0 commit comments

Comments
 (0)