Skip to content

Commit

Permalink
fix(types): infer type from target firestore
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Dec 14, 2023
1 parent d231a75 commit 8814646
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 30 deletions.
3 changes: 2 additions & 1 deletion src/firestore/bind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import { onSnapshot } from 'firebase/firestore'
/**
* Options when binding a Firestore document or collection.
*/
export interface FirestoreRefOptions extends _DataSourceOptions {
export interface FirestoreRefOptions<TData = unknown>
extends _DataSourceOptions<TData> {
/**
* The maximum depth to bind nested refs. A nested ref that isn't bound will stay as the ref path while a bound ref
* will contain the same data as if the ref was bound directly.
Expand Down
9 changes: 5 additions & 4 deletions src/firestore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export function useCollection<T>(
}) as _RefFirestore<VueFirestoreQueryData<T>>
}

export interface UseDocumentOptions extends _UseFirestoreRefOptions {}
export interface UseDocumentOptions<TData = unknown>
extends _UseFirestoreRefOptions<TData> {}

/**
* Creates a reactive document from a document ref from Firestore. Automatically extracts the type of the converter or
Expand All @@ -73,7 +74,7 @@ export function useDocument<
R extends DocumentReference<unknown>
>(
documentRef: MaybeRefOrGetter<_Nullable<R>>,
options?: UseDocumentOptions
options?: UseDocumentOptions<_InferReferenceType<R>>
): _RefFirestore<_InferReferenceType<R> | undefined> // this one can't be null or should be specified in the converter

/**
Expand All @@ -86,12 +87,12 @@ export function useDocument<
*/
export function useDocument<T>(
documentRef: MaybeRefOrGetter<_Nullable<DocumentReference>>,
options?: UseDocumentOptions
options?: UseDocumentOptions<T>
): _RefFirestore<VueFirestoreDocumentData<T> | undefined>

export function useDocument<T>(
documentRef: MaybeRefOrGetter<_Nullable<DocumentReference<unknown>>>,
options?: UseDocumentOptions
options?: UseDocumentOptions<T>
): _RefFirestore<VueFirestoreDocumentData<T> | undefined> {
// no unwrapRef to have a simpler type
return _useFirestoreRef(documentRef, options) as _RefFirestore<
Expand Down
3 changes: 2 additions & 1 deletion src/firestore/useFirestoreRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ import {
_FirestoreRefOptionsWithDefaults,
} from './bind'

export interface _UseFirestoreRefOptions extends FirestoreRefOptions {
export interface _UseFirestoreRefOptions<TData = unknown>
extends FirestoreRefOptions<TData> {
/**
* @deprecated: use `.withConverter()` instead
*/
Expand Down
11 changes: 9 additions & 2 deletions src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ export interface OperationsType {
*/
export type ResetOption = boolean | (() => unknown)

/**
* Flattens out a type.
*
* @internal
*/
export type _Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {}

/**
* Return type of `$databaseBind()` and `$firestoreBind()`
*/
Expand Down Expand Up @@ -228,11 +235,11 @@ export interface _RefWithState<T, E = Error> extends Ref<T> {
*
* @internal
*/
export interface _DataSourceOptions {
export interface _DataSourceOptions<DataT = unknown> {
/**
* Use the `target` ref instead of creating one.
*/
target?: Ref<unknown>
target?: Ref<DataT>

/**
* Optional key to handle SSR hydration. **Necessary for Queries** or when the same source is used in multiple places
Expand Down
62 changes: 40 additions & 22 deletions tests/firestore/document.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it } from 'vitest'
import { beforeEach, describe, expect, expectTypeOf, it } from 'vitest'
import {
addDoc,
doc as originalDoc,
Expand Down Expand Up @@ -46,7 +46,7 @@ describe(
options,
ref = doc(),
}: {
options?: UseDocumentOptions
options?: UseDocumentOptions<T>
ref?: MaybeRefOrGetter<DocumentReference<T>>
} = {}) {
let data!: _RefFirestore<VueFirestoreDocumentData<T>>
Expand Down Expand Up @@ -341,18 +341,28 @@ describe(
finished: boolean
}

expectType<Ref<DocumentData | null | undefined>>(useDocument(itemRef))
// @ts-expect-error
expectType<Ref<number | null>>(useDocument(itemRef))
expectTypeOf(useDocument(itemRef).value).toEqualTypeOf<
DocumentData | undefined
>()

// Adds the id

// FIXME: this one is any but the test passes
expectType<string>(useDocument(doc(db, 'todos', '1')).value?.id)
expectType<string>(useDocument<TodoI>(doc(db, 'todos', '1')).value!.id)
expectType<_Nullable<TodoI>>(
expectTypeOf(
useDocument(doc(db, 'todos', '1')).value!.id
// @ts-expect-error:
).toEqualTypeOf<string>()

expectTypeOf(
useDocument<TodoI>(doc(db, 'todos', '1')).value
)
expectType<string>(useDocument<unknown>(doc(db, 'todos', '1')).value!.id)
).toMatchTypeOf<_Nullable<TodoI>>()
expectTypeOf(
useDocument<unknown>(doc(db, 'todos', '1')).value!.id
).toBeString()
expectTypeOf(
useDocument<TodoI>(doc(db, 'todos', '1')).value!.id
).toBeString()

useDocument(
doc(db, 'todos').withConverter<TodoI, DocumentData>({
fromFirestore: (snapshot) => {
Expand All @@ -364,7 +374,10 @@ describe(
// @ts-expect-error: no id with custom converter
).value?.id

expectType<Ref<number | null | undefined>>(useDocument<number>(itemRef))
expectTypeOf(useDocument<number>(itemRef)).toMatchTypeOf<
Ref<_Nullable<number | null | undefined>>
>()

expectType<Ref<number | null | undefined>>(
useDocument<number>(itemRef).data
)
Expand All @@ -375,12 +388,15 @@ describe(
toFirestore: (data) => ({ n: data }),
fromFirestore: (snap, options) => snap.data(options).n as number,
})
expectType<Ref<number | number | undefined>>(
useDocument(refWithConverter)
)
expectType<Ref<number | number | undefined>>(
useDocument(refWithConverter).data
)
expectTypeOf(useDocument(refWithConverter)).toMatchTypeOf<
Ref<number | undefined>
>()
expectTypeOf(useDocument(refWithConverter).value).toEqualTypeOf<
number | undefined
>()
expectTypeOf(useDocument(refWithConverter).data).toEqualTypeOf<
Ref<number | undefined>
>()
// should not be null
useDocument(refWithConverter).value?.toFixed(14)
// @ts-expect-error: string is not assignable to number
Expand All @@ -389,11 +405,13 @@ describe(
useDocument(refWithConverter).value.id

// destructuring
expectType<Ref<DocumentData | null | undefined>>(
useDocument(itemRef).data
)
expectType<Ref<FirestoreError | undefined>>(useDocument(itemRef).error)
expectType<Ref<boolean>>(useDocument(itemRef).pending)
expectTypeOf(useDocument(itemRef).data).toEqualTypeOf<
Ref<DocumentData | undefined>
>()
expectTypeOf(useDocument(itemRef).error).toEqualTypeOf<
Ref<FirestoreError | undefined>
>()
expectTypeOf(useDocument(itemRef).pending).toEqualTypeOf<Ref<boolean>>()
})
},
{ retry: 3 }
Expand Down

0 comments on commit 8814646

Please sign in to comment.