Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions packages/firestore/lib/FirestoreCollectionReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ import type FirestorePath from './FirestorePath';
import type { DocumentData, FirestoreDataConverter } from './types/firestore';
import type { FirestoreInternal } from './types/internal';

export default class CollectionReference extends Query {
export default class CollectionReference<
AppModelType = DocumentData,
DbModelType extends DocumentData = DocumentData,
> extends Query<AppModelType, DbModelType> {
readonly type = 'collection' as const;
constructor(
firestore: FirestoreInternal,
collectionPath: FirestorePath,
converter?: FirestoreDataConverter<DocumentData, DocumentData> | null,
converter?: FirestoreDataConverter<AppModelType, DbModelType> | null,
) {
super(firestore, collectionPath, new QueryModifiers(), undefined, converter);
}
Expand All @@ -43,19 +47,19 @@ export default class CollectionReference extends Query {
return this._collectionPath.id;
}

get parent(): DocumentReference | null {
get parent(): DocumentReference<DocumentData, DocumentData> | null {
const parent = this._collectionPath.parent();
if (!parent) {
return null;
}
return new DocumentReference(this._firestore, parent);
return new DocumentReference<DocumentData, DocumentData>(this._firestore, parent);
}

get path(): string {
return this._collectionPath.relativeName;
}

add(data: Record<string, unknown>): Promise<DocumentReference> {
add(data: AppModelType): Promise<DocumentReference<AppModelType, DbModelType>> {
if (!isObject(data)) {
throw new Error("firebase.firestore().collection().add(*) 'data' must be an object.");
}
Expand All @@ -64,7 +68,7 @@ export default class CollectionReference extends Query {
return documentRef.set(data).then(() => Promise.resolve(documentRef));
}

doc(documentPath?: string): DocumentReference {
doc(documentPath?: string): DocumentReference<AppModelType, DbModelType> {
const newPath = documentPath ?? generateFirestoreId();
const path = this._collectionPath.child(newPath);

Expand All @@ -74,12 +78,24 @@ export default class CollectionReference extends Query {
);
}

return new DocumentReference(this._firestore, path, this._converter);
return new DocumentReference<AppModelType, DbModelType>(this._firestore, path, this._converter);
}

withConverter(converter: unknown): CollectionReference {
withConverter(converter: null): CollectionReference<DocumentData, DocumentData>;
withConverter<NewAppModelType, NewDbModelType extends DocumentData = DocumentData>(
converter: FirestoreDataConverter<NewAppModelType, NewDbModelType>,
): CollectionReference<NewAppModelType, NewDbModelType>;
withConverter<NewAppModelType, NewDbModelType extends DocumentData = DocumentData>(
converter: FirestoreDataConverter<NewAppModelType, NewDbModelType> | null | unknown,
):
| CollectionReference<DocumentData, DocumentData>
| CollectionReference<NewAppModelType, NewDbModelType> {
if (isUndefined(converter) || isNull(converter)) {
return new CollectionReference(this._firestore, this._collectionPath, null);
return new CollectionReference<DocumentData, DocumentData>(
this._firestore,
this._collectionPath,
null,
);
}

try {
Expand All @@ -88,10 +104,10 @@ export default class CollectionReference extends Query {
throw new Error(`firebase.firestore().collection().withConverter() ${(e as Error).message}`);
}

return new CollectionReference(
return new CollectionReference<NewAppModelType, NewDbModelType>(
this._firestore,
this._collectionPath,
converter as FirestoreDataConverter<DocumentData, DocumentData>,
converter as FirestoreDataConverter<NewAppModelType, NewDbModelType>,
);
}
}
Expand Down
77 changes: 57 additions & 20 deletions packages/firestore/lib/FirestoreDocumentReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,29 @@ export function provideDocumentSnapshotClass(

let _id = 0;

export default class DocumentReference {
export default class DocumentReference<
AppModelType = DocumentData,
DbModelType extends DocumentData = DocumentData,
> {
readonly type = 'document' as const;
_firestore: FirestoreInternal;
_documentPath: FirestorePath;
_converter: FirestoreDataConverter<DocumentData, DocumentData> | null;
_converter: FirestoreDataConverter<AppModelType, DbModelType> | null;

constructor(firestore: FirestoreInternal, documentPath: FirestorePath, converter?: unknown) {
this._firestore = firestore;
this._documentPath = documentPath;
this._converter = (converter === undefined ? null : converter) as FirestoreDataConverter<
DocumentData,
DocumentData
AppModelType,
DbModelType
> | null;
}

get firestore(): FirestoreInternal {
return this._firestore;
}

get converter(): unknown {
get converter(): FirestoreDataConverter<AppModelType, DbModelType> | null {
return this._converter;
}

Expand All @@ -106,7 +110,11 @@ export default class DocumentReference {

get parent(): FirestoreCollectionReferenceClass {
const parentPath = this._documentPath.parent();
return new FirestoreCollectionReference!(this._firestore, parentPath!, this._converter);
return new FirestoreCollectionReference!(
this._firestore,
parentPath!,
this._converter as unknown as FirestoreDataConverter<DocumentData, DocumentData> | null,
);
}

get path(): string {
Expand Down Expand Up @@ -141,7 +149,9 @@ export default class DocumentReference {
return this._firestore.native.documentDelete(this.path);
}

get(options?: { source?: 'default' | 'server' | 'cache' }): Promise<DocumentSnapshot> {
get(options?: {
source?: 'default' | 'server' | 'cache';
}): Promise<DocumentSnapshot<AppModelType, DbModelType>> {
if (!isUndefined(options) && !isObject(options)) {
throw new Error("firebase.firestore().doc().get(*) 'options' must be an object is provided.");
}
Expand All @@ -166,13 +176,16 @@ export default class DocumentReference {
new FirestoreDocumentSnapshotClass!(
this._firestore,
data as DocumentSnapshotNativeData,
this._converter,
this._converter as unknown as FirestoreDataConverter<
DocumentData,
DocumentData
> | null,
),
) as DocumentSnapshot,
) as DocumentSnapshot<AppModelType, DbModelType>,
);
}

isEqual(other: DocumentReference): boolean {
isEqual(other: DocumentReference<AppModelType, DbModelType>): boolean {
if (!(other instanceof DocumentReference)) {
throw new Error(
"firebase.firestore().doc().isEqual(*) 'other' expected a DocumentReference instance.",
Expand All @@ -189,8 +202,11 @@ export default class DocumentReference {

onSnapshot(...args: unknown[]): () => void {
let snapshotListenOptions: { includeMetadataChanges?: boolean };
let callback: (snapshot: DocumentSnapshot | null, error: Error | null) => void;
let onNext: (snapshot: DocumentSnapshot) => void;
let callback: (
snapshot: DocumentSnapshot<AppModelType, DbModelType> | null,
error: Error | null,
) => void;
let onNext: (snapshot: DocumentSnapshot<AppModelType, DbModelType>) => void;
let onError: (error: Error) => void;

try {
Expand All @@ -203,7 +219,7 @@ export default class DocumentReference {
throw new Error(`firebase.firestore().doc().onSnapshot(*) ${(e as Error).message}`);
}

function handleSuccess(documentSnapshot: DocumentSnapshot): void {
function handleSuccess(documentSnapshot: DocumentSnapshot<AppModelType, DbModelType>): void {
callback(documentSnapshot, null);
onNext(documentSnapshot);
}
Expand All @@ -224,8 +240,15 @@ export default class DocumentReference {
const snapshot = event.body.snapshot;
if (!snapshot) return;
const documentSnapshot = createDeprecationProxy(
new FirestoreDocumentSnapshotClass!(this._firestore, snapshot, this._converter),
);
new FirestoreDocumentSnapshotClass!(
this._firestore,
snapshot,
this._converter as unknown as FirestoreDataConverter<
DocumentData,
DocumentData
> | null,
),
) as DocumentSnapshot<AppModelType, DbModelType>;
handleSuccess(documentSnapshot);
}
},
Expand Down Expand Up @@ -290,11 +313,21 @@ export default class DocumentReference {
);
}

withConverter(
converter: FirestoreDataConverter<DocumentData, DocumentData> | null,
): DocumentReference {
withConverter(converter: null): DocumentReference<DocumentData, DocumentData>;
withConverter<NewAppModelType, NewDbModelType extends DocumentData = DocumentData>(
converter: FirestoreDataConverter<NewAppModelType, NewDbModelType>,
): DocumentReference<NewAppModelType, NewDbModelType>;
withConverter<NewAppModelType, NewDbModelType extends DocumentData = DocumentData>(
converter: FirestoreDataConverter<NewAppModelType, NewDbModelType> | null,
):
| DocumentReference<DocumentData, DocumentData>
| DocumentReference<NewAppModelType, NewDbModelType> {
if (isUndefined(converter) || isNull(converter)) {
return new DocumentReference(this._firestore, this._documentPath, null);
return new DocumentReference<DocumentData, DocumentData>(
this._firestore,
this._documentPath,
null,
);
}

try {
Expand All @@ -303,7 +336,11 @@ export default class DocumentReference {
throw new Error(`firebase.firestore().doc().withConverter() ${(e as Error).message}`);
}

return new DocumentReference(this._firestore, this._documentPath, converter);
return new DocumentReference<NewAppModelType, NewDbModelType>(
this._firestore,
this._documentPath,
converter,
);
}
}

Expand Down
34 changes: 21 additions & 13 deletions packages/firestore/lib/FirestoreDocumentSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
DocumentData,
DocumentSnapshot as DocumentSnapshotDeclare,
FirestoreDataConverter,
QueryDocumentSnapshot,
} from './types/firestore';

export interface DocumentSnapshotNativeData {
Expand All @@ -41,27 +42,34 @@ export interface DocumentSnapshotNativeData {
exists?: boolean;
}

export default class DocumentSnapshot {
export default class DocumentSnapshot<
AppModelType = DocumentData,
DbModelType extends DocumentData = DocumentData,
> {
_firestore: FirestoreInternal;
_nativeData: DocumentSnapshotNativeData;
_data: Record<string, unknown> | undefined;
_metadata: SnapshotMetadata;
_ref: DocumentReference;
_ref: DocumentReference<AppModelType, DbModelType>;
_exists: boolean;
_converter: FirestoreDataConverter<DocumentData, DocumentData> | null;
_converter: FirestoreDataConverter<AppModelType, DbModelType> | null;

constructor(
firestore: FirestoreInternal,
nativeData: DocumentSnapshotNativeData,
converter: FirestoreDataConverter<DocumentData, DocumentData> | null,
converter: FirestoreDataConverter<AppModelType, DbModelType> | null,
) {
this._firestore = firestore;
this._nativeData = nativeData;
this._converter = converter;
this._data = parseNativeMap(firestore, nativeData.data as Record<string, unknown> | undefined);
this._metadata = new SnapshotMetadata(nativeData.metadata ?? [false, false]);
this._ref = new DocumentReference(firestore, FirestorePath.fromName(nativeData.path));
this._ref = new DocumentReference<AppModelType, DbModelType>(
firestore,
FirestorePath.fromName(nativeData.path),
this._converter,
);
this._exists = nativeData.exists ?? false;
this._converter = converter;
}

get id(): string {
Expand All @@ -72,32 +80,32 @@ export default class DocumentSnapshot {
return this._metadata;
}

get ref(): DocumentReference {
get ref(): DocumentReference<AppModelType, DbModelType> {
return this._ref;
}

exists(): boolean {
exists(): this is QueryDocumentSnapshot<AppModelType, DbModelType> {
return this._exists;
}

data(options?: SnapshotOptions): DocumentData | undefined {
data(options?: SnapshotOptions): AppModelType | undefined {
if (this._converter) {
try {
return (this._converter as ConverterWithFromFirestoreInternal).fromFirestore(
new DocumentSnapshot(
new DocumentSnapshot<DocumentData, DocumentData>(
this._firestore,
this._nativeData,
null,
) as unknown as DocumentSnapshotDeclare<DocumentData, DocumentData>,
options,
) as DocumentData;
) as AppModelType;
} catch (e) {
throw new Error(
`firebase.firestore() DocumentSnapshot.data(*) 'withConverter.fromFirestore' threw an error: ${(e as Error).message}.`,
);
}
}
return this._data;
return this._data as AppModelType | undefined;
}

get(fieldPath: string | FieldPath, _options?: SnapshotOptions): DocumentFieldValueInternal {
Expand All @@ -124,7 +132,7 @@ export default class DocumentSnapshot {
return extractFieldPathData(this._data, path._segments) as DocumentFieldValueInternal;
}

isEqual(other: DocumentSnapshot): boolean {
isEqual(other: DocumentSnapshot<AppModelType, DbModelType>): boolean {
if (!(other instanceof DocumentSnapshot)) {
throw new Error(
"firebase.firestore() DocumentSnapshot.isEqual(*) 'other' expected a DocumentSnapshot instance.",
Expand Down
Loading
Loading