diff --git a/dev/conformance/runner.ts b/dev/conformance/runner.ts index 02b1a7595..947142897 100644 --- a/dev/conformance/runner.ts +++ b/dev/conformance/runner.ts @@ -193,7 +193,7 @@ const convertInput = { } return args; }, - snapshot: (snapshot: ConformanceProto) => { + snapshot: (snapshot: ConformanceProto): QuerySnapshot => { const docs: QueryDocumentSnapshot[] = []; const changes: DocumentChange[] = []; const readTime = Timestamp.fromProto(snapshot.readTime); diff --git a/dev/src/bulk-writer.ts b/dev/src/bulk-writer.ts index 1a7270bef..981063f1f 100644 --- a/dev/src/bulk-writer.ts +++ b/dev/src/bulk-writer.ts @@ -28,7 +28,6 @@ import { MAX_RETRY_ATTEMPTS, } from './backoff'; import {RateLimiter} from './rate-limiter'; -import {DocumentReference} from './reference'; import {Timestamp} from './timestamp'; import { Deferred, @@ -337,7 +336,7 @@ export class BulkWriterError extends Error { readonly message: string, /** The document reference the operation was performed on. */ - readonly documentRef: firestore.DocumentReference, + readonly documentRef: firestore.DocumentReference, /** The type of operation performed. */ readonly operationType: 'create' | 'set' | 'update' | 'delete', @@ -576,9 +575,9 @@ export class BulkWriter { * }); * ``` */ - create( - documentRef: firestore.DocumentReference, - data: firestore.WithFieldValue + create( + documentRef: firestore.DocumentReference, + data: firestore.WithFieldValue ): Promise { this._verifyNotClosed(); return this._enqueue(documentRef, 'create', bulkCommitBatch => @@ -616,8 +615,8 @@ export class BulkWriter { * }); * ``` */ - delete( - documentRef: firestore.DocumentReference, + delete( + documentRef: firestore.DocumentReference, precondition?: firestore.Precondition ): Promise { this._verifyNotClosed(); @@ -626,14 +625,14 @@ export class BulkWriter { ); } - set( - documentRef: firestore.DocumentReference, - data: Partial, + set( + documentRef: firestore.DocumentReference, + data: Partial, options: firestore.SetOptions ): Promise; - set( - documentRef: firestore.DocumentReference, - data: T + set( + documentRef: firestore.DocumentReference, + data: AppModelType ): Promise; /** * Write to the document referred to by the provided @@ -675,9 +674,9 @@ export class BulkWriter { * }); * ``` */ - set( - documentRef: firestore.DocumentReference, - data: firestore.PartialWithFieldValue, + set( + documentRef: firestore.DocumentReference, + data: firestore.PartialWithFieldValue, options?: firestore.SetOptions ): Promise { this._verifyNotClosed(); @@ -687,7 +686,7 @@ export class BulkWriter { } else { return bulkCommitBatch.set( documentRef, - data as firestore.WithFieldValue + data as firestore.WithFieldValue ); } }); @@ -737,9 +736,9 @@ export class BulkWriter { * }); * ``` */ - update( - documentRef: firestore.DocumentReference, - dataOrField: firestore.UpdateData | string | FieldPath, + update( + documentRef: firestore.DocumentReference, + dataOrField: firestore.UpdateData | string | FieldPath, ...preconditionOrValues: Array< {lastUpdateTime?: Timestamp} | unknown | string | FieldPath > @@ -783,7 +782,7 @@ export class BulkWriter { */ onWriteResult( successCallback: ( - documentRef: firestore.DocumentReference, + documentRef: firestore.DocumentReference, result: WriteResult ) => void ): void { diff --git a/dev/src/collection-group.ts b/dev/src/collection-group.ts index 80df4420d..6ae784bd3 100644 --- a/dev/src/collection-group.ts +++ b/dev/src/collection-group.ts @@ -35,15 +35,20 @@ import {compareArrays} from './order'; * * @class CollectionGroup */ -export class CollectionGroup - extends Query - implements firestore.CollectionGroup +export class CollectionGroup< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData + > + extends Query + implements firestore.CollectionGroup { /** @private */ constructor( firestore: Firestore, collectionId: string, - converter: firestore.FirestoreDataConverter | undefined + converter: + | firestore.FirestoreDataConverter + | undefined ) { super( firestore, @@ -74,7 +79,7 @@ export class CollectionGroup */ async *getPartitions( desiredPartitionCount: number - ): AsyncIterable> { + ): AsyncIterable> { validateInteger('desiredPartitionCount', desiredPartitionCount, { minValue: 1, }); @@ -141,7 +146,8 @@ export class CollectionGroup * Applies a custom data converter to this `CollectionGroup`, allowing you * to use your own custom model objects with Firestore. When you call get() * on the returned `CollectionGroup`, the provided converter will convert - * between Firestore data and your custom type U. + * between Firestore data of type `NewDbModelType` and your custom type + * `NewAppModelType`. * * Using the converter allows you to specify generic type arguments when * storing and retrieving objects from Firestore. @@ -185,17 +191,26 @@ export class CollectionGroup * ``` * @param {FirestoreDataConverter | null} converter Converts objects to and * from Firestore. Passing in `null` removes the current converter. - * @return {CollectionGroup} A `CollectionGroup` that uses the provided + * @return {CollectionGroup} A `CollectionGroup` that uses the provided * converter. */ - withConverter(converter: null): CollectionGroup; - withConverter( - converter: firestore.FirestoreDataConverter - ): CollectionGroup; - withConverter( - converter: firestore.FirestoreDataConverter | null - ): CollectionGroup { - return new CollectionGroup( + withConverter(converter: null): CollectionGroup; + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData + >( + converter: firestore.FirestoreDataConverter + ): CollectionGroup; + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData + >( + converter: firestore.FirestoreDataConverter< + NewAppModelType, + NewDbModelType + > | null + ): CollectionGroup { + return new CollectionGroup( this.firestore, this._queryOptions.collectionId, converter ?? defaultConverter() diff --git a/dev/src/document-change.ts b/dev/src/document-change.ts index b91ee00a6..22822702e 100644 --- a/dev/src/document-change.ts +++ b/dev/src/document-change.ts @@ -26,11 +26,13 @@ export type DocumentChangeType = 'added' | 'removed' | 'modified'; * * @class DocumentChange */ -export class DocumentChange - implements firestore.DocumentChange +export class DocumentChange< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> implements firestore.DocumentChange { private readonly _type: DocumentChangeType; - private readonly _document: QueryDocumentSnapshot; + private readonly _document: QueryDocumentSnapshot; private readonly _oldIndex: number; private readonly _newIndex: number; @@ -46,7 +48,7 @@ export class DocumentChange */ constructor( type: DocumentChangeType, - document: QueryDocumentSnapshot, + document: QueryDocumentSnapshot, oldIndex: number, newIndex: number ) { @@ -103,7 +105,7 @@ export class DocumentChange * unsubscribe(); * ``` */ - get doc(): QueryDocumentSnapshot { + get doc(): QueryDocumentSnapshot { return this._document; } @@ -181,7 +183,7 @@ export class DocumentChange * @param {*} other The value to compare against. * @return true if this `DocumentChange` is equal to the provided value. */ - isEqual(other: firestore.DocumentChange): boolean { + isEqual(other: firestore.DocumentChange): boolean { if (this === other) { return true; } diff --git a/dev/src/document-reader.ts b/dev/src/document-reader.ts index e8d54214c..425d192fc 100644 --- a/dev/src/document-reader.ts +++ b/dev/src/document-reader.ts @@ -31,7 +31,7 @@ import api = google.firestore.v1; * @private * @internal */ -export class DocumentReader { +export class DocumentReader { /** An optional field mask to apply to this read. */ fieldMask?: FieldPath[]; /** An optional transaction ID to use for this read. */ @@ -49,7 +49,7 @@ export class DocumentReader { */ constructor( private firestore: Firestore, - private allDocuments: Array> + private allDocuments: Array> ) { for (const docRef of this.allDocuments) { this.outstandingDocuments.add(docRef.formattedName); @@ -61,12 +61,15 @@ export class DocumentReader { * * @param requestTag A unique client-assigned identifier for this request. */ - async get(requestTag: string): Promise>> { + async get( + requestTag: string + ): Promise>> { await this.fetchDocuments(requestTag); // BatchGetDocuments doesn't preserve document order. We use the request // order to sort the resulting documents. - const orderedDocuments: Array> = []; + const orderedDocuments: Array> = + []; for (const docRef of this.allDocuments) { const document = this.retrievedDocuments.get(docRef.formattedName); @@ -74,7 +77,7 @@ export class DocumentReader { // Recreate the DocumentSnapshot with the DocumentReference // containing the original converter. const finalDoc = new DocumentSnapshotBuilder( - docRef as DocumentReference + docRef as DocumentReference ); finalDoc.fieldsProto = document._fieldsProto; finalDoc.readTime = document.readTime; diff --git a/dev/src/document.ts b/dev/src/document.ts index 3be78e16b..84a77f2a4 100644 --- a/dev/src/document.ts +++ b/dev/src/document.ts @@ -38,7 +38,10 @@ import api = google.firestore.v1; * @private * @internal */ -export class DocumentSnapshotBuilder { +export class DocumentSnapshotBuilder< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> { /** The fields of the Firestore `Document` Protobuf backing this document. */ fieldsProto?: ApiMapValue; @@ -52,8 +55,9 @@ export class DocumentSnapshotBuilder { updateTime?: Timestamp; // We include the DocumentReference in the constructor in order to allow the - // DocumentSnapshotBuilder to be typed with when it is constructed. - constructor(readonly ref: DocumentReference) {} + // DocumentSnapshotBuilder to be typed with when + // it is constructed. + constructor(readonly ref: DocumentReference) {} /** * Builds the DocumentSnapshot. @@ -63,7 +67,9 @@ export class DocumentSnapshotBuilder { * @returns Returns either a QueryDocumentSnapshot (if `fieldsProto` was * provided) or a DocumentSnapshot. */ - build(): QueryDocumentSnapshot | DocumentSnapshot { + build(): + | QueryDocumentSnapshot + | DocumentSnapshot { assert( (this.fieldsProto !== undefined) === (this.createTime !== undefined), 'Create time should be set iff document exists.' @@ -73,14 +79,18 @@ export class DocumentSnapshotBuilder { 'Update time should be set iff document exists.' ); return this.fieldsProto - ? new QueryDocumentSnapshot( - this.ref!, + ? new QueryDocumentSnapshot( + this.ref, this.fieldsProto!, this.readTime!, this.createTime!, this.updateTime! ) - : new DocumentSnapshot(this.ref!, undefined, this.readTime!); + : new DocumentSnapshot( + this.ref, + undefined, + this.readTime! + ); } } @@ -98,10 +108,12 @@ export class DocumentSnapshotBuilder { * * @class DocumentSnapshot */ -export class DocumentSnapshot - implements firestore.DocumentSnapshot +export class DocumentSnapshot< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> implements firestore.DocumentSnapshot { - private _ref: DocumentReference; + private _ref: DocumentReference; private _serializer: Serializer; private _readTime: Timestamp | undefined; private _createTime: Timestamp | undefined; @@ -121,7 +133,7 @@ export class DocumentSnapshot * if the document does not exist). */ constructor( - ref: DocumentReference, + ref: DocumentReference, /** @private */ readonly _fieldsProto?: ApiMapValue, readTime?: Timestamp, @@ -144,15 +156,12 @@ export class DocumentSnapshot * @param obj The object to store in the DocumentSnapshot. * @return The created DocumentSnapshot. */ - static fromObject( - ref: firestore.DocumentReference, + static fromObject( + ref: DocumentReference, obj: firestore.DocumentData - ): DocumentSnapshot { - const serializer = (ref as DocumentReference).firestore._serializer!; - return new DocumentSnapshot( - ref as DocumentReference, - serializer.encodeFields(obj) - ); + ): DocumentSnapshot { + const serializer = ref.firestore._serializer!; + return new DocumentSnapshot(ref, serializer.encodeFields(obj)); } /** * Creates a DocumentSnapshot from an UpdateMap. @@ -166,11 +175,15 @@ export class DocumentSnapshot * @param data The field/value map to expand. * @return The created DocumentSnapshot. */ - static fromUpdateMap( - ref: firestore.DocumentReference, + static fromUpdateMap< + AppModelType, + DbModelType extends firestore.DocumentData + >( + ref: firestore.DocumentReference, data: UpdateMap - ): DocumentSnapshot { - const serializer = (ref as DocumentReference).firestore._serializer!; + ): DocumentSnapshot { + const serializer = (ref as DocumentReference) + .firestore._serializer!; /** * Merges 'value' at the field path specified by the path array into @@ -240,7 +253,10 @@ export class DocumentSnapshot merge(res, value, path, 0); } - return new DocumentSnapshot(ref as DocumentReference, res); + return new DocumentSnapshot( + ref as DocumentReference, + res + ); } /** @@ -284,7 +300,7 @@ export class DocumentSnapshot * }); * ``` */ - get ref(): DocumentReference { + get ref(): DocumentReference { return this._ref; } @@ -399,7 +415,7 @@ export class DocumentSnapshot * }); * ``` */ - data(): T | undefined { + data(): AppModelType | undefined { const fields = this._fieldsProto; if (fields === undefined) { @@ -414,7 +430,7 @@ export class DocumentSnapshot this.ref._path ); return this.ref._converter.fromFirestore( - new QueryDocumentSnapshot( + new QueryDocumentSnapshot( untypedReference, this._fieldsProto!, this.readTime, @@ -427,7 +443,7 @@ export class DocumentSnapshot for (const prop of Object.keys(fields)) { obj[prop] = this._serializer.decodeValue(fields[prop]); } - return obj as T; + return obj as AppModelType; } } @@ -535,13 +551,17 @@ export class DocumentSnapshot * @return {boolean} true if this `DocumentSnapshot` is equal to the provided * value. */ - isEqual(other: firestore.DocumentSnapshot): boolean { + isEqual( + other: firestore.DocumentSnapshot + ): boolean { // Since the read time is different on every document read, we explicitly // ignore all document metadata in this comparison. return ( this === other || (other instanceof DocumentSnapshot && - this._ref.isEqual((other as DocumentSnapshot)._ref) && + this._ref.isEqual( + (other as DocumentSnapshot)._ref + ) && deepEqual(this._fieldsProto, other._fieldsProto)) ); } @@ -562,9 +582,12 @@ export class DocumentSnapshot * @class QueryDocumentSnapshot * @extends DocumentSnapshot */ -export class QueryDocumentSnapshot - extends DocumentSnapshot - implements firestore.QueryDocumentSnapshot +export class QueryDocumentSnapshot< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData + > + extends DocumentSnapshot + implements firestore.QueryDocumentSnapshot { /** * The time the document was created. @@ -626,7 +649,7 @@ export class QueryDocumentSnapshot * }); * ``` */ - data(): T { + data(): AppModelType { const data = super.data(); if (!data) { throw new Error( @@ -925,7 +948,10 @@ export class DocumentMask { * @internal * @class */ -export class DocumentTransform { +export class DocumentTransform< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> { /** * @private * @internal @@ -935,7 +961,7 @@ export class DocumentTransform { * @param transforms A Map of FieldPaths to FieldTransforms. */ constructor( - private readonly ref: DocumentReference, + private readonly ref: DocumentReference, private readonly transforms: Map ) {} @@ -948,17 +974,20 @@ export class DocumentTransform { * @param obj The object to extract the transformations from. * @returns The Document Transform. */ - static fromObject( - ref: firestore.DocumentReference, + static fromObject( + ref: firestore.DocumentReference, obj: firestore.DocumentData - ): DocumentTransform { + ): DocumentTransform { const updateMap = new Map(); for (const prop of Object.keys(obj)) { updateMap.set(new FieldPath(prop), obj[prop]); } - return DocumentTransform.fromUpdateMap(ref, updateMap); + return DocumentTransform.fromUpdateMap( + ref, + updateMap + ); } /** @@ -970,10 +999,13 @@ export class DocumentTransform { * @param data The update data to extract the transformations from. * @returns The Document Transform. */ - static fromUpdateMap( - ref: firestore.DocumentReference, + static fromUpdateMap< + AppModelType, + DbModelType extends firestore.DocumentData + >( + ref: firestore.DocumentReference, data: UpdateMap - ): DocumentTransform { + ): DocumentTransform { const transforms = new Map(); function encode_( @@ -1005,7 +1037,10 @@ export class DocumentTransform { encode_(value, FieldPath.fromArgument(key), true); }); - return new DocumentTransform(ref as DocumentReference, transforms); + return new DocumentTransform( + ref as DocumentReference, + transforms + ); } /** diff --git a/dev/src/index.ts b/dev/src/index.ts index c62bd7dc4..6448e475b 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -172,7 +172,8 @@ const MAX_CONCURRENT_REQUESTS_PER_CLIENT = 100; /** * Converter used by [withConverter()]{@link Query#withConverter} to transform - * user objects of type T into Firestore data. + * user objects of type `AppModelType` into Firestore data of type + * `DbModelType`. * * Using the converter allows you to specify generic type arguments when storing * and retrieving objects from Firestore. @@ -212,10 +213,10 @@ const MAX_CONCURRENT_REQUESTS_PER_CLIENT = 100; * * ``` * @property {Function} toFirestore Called by the Firestore SDK to convert a - * custom model object of type T into a plain Javascript object (suitable for - * writing directly to the Firestore database). + * custom model object of type `AppModelType` into a plain Javascript object + * (suitable for writing directly to the Firestore database). * @property {Function} fromFirestore Called by the Firestore SDK to convert - * Firestore data into an object of type T. + * Firestore data into an object of type `AppModelType`. * @typedef {Object} FirestoreDataConverter */ @@ -1285,11 +1286,12 @@ export class Firestore implements firestore.Firestore { * }); * ``` */ - getAll( + getAll( ...documentRefsOrReadOptions: Array< - firestore.DocumentReference | firestore.ReadOptions + | firestore.DocumentReference + | firestore.ReadOptions > - ): Promise>> { + ): Promise>> { validateMinNumberOfArguments( 'Firestore.getAll', documentRefsOrReadOptions, @@ -1400,8 +1402,8 @@ export class Firestore implements firestore.Firestore { */ recursiveDelete( ref: - | firestore.CollectionReference - | firestore.DocumentReference, + | firestore.CollectionReference + | firestore.DocumentReference, bulkWriter?: BulkWriter ): Promise { return this._recursiveDelete( diff --git a/dev/src/query-partition.ts b/dev/src/query-partition.ts index d24221002..bd913154a 100644 --- a/dev/src/query-partition.ts +++ b/dev/src/query-partition.ts @@ -32,8 +32,10 @@ import api = protos.google.firestore.v1; * * @class QueryPartition */ -export class QueryPartition - implements firestore.QueryPartition +export class QueryPartition< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> implements firestore.QueryPartition { private readonly _serializer: Serializer; private _memoizedStartAt: unknown[] | undefined; @@ -43,7 +45,10 @@ export class QueryPartition constructor( private readonly _firestore: Firestore, private readonly _collectionId: string, - private readonly _converter: firestore.FirestoreDataConverter, + private readonly _converter: firestore.FirestoreDataConverter< + AppModelType, + DbModelType + >, private readonly _startAt: api.IValue[] | undefined, private readonly _endBefore: api.IValue[] | undefined ) { @@ -138,7 +143,7 @@ export class QueryPartition * @return {Query} A query partitioned by a {@link Query#startAt} and * {@link Query#endBefore} cursor. */ - toQuery(): Query { + toQuery(): Query { // Since the api.Value to JavaScript type conversion can be lossy (unless // `useBigInt` is used), we pass the original protobuf representation to the // created query. diff --git a/dev/src/reference.ts b/dev/src/reference.ts index 503be5852..fb4c1738b 100644 --- a/dev/src/reference.ts +++ b/dev/src/reference.ts @@ -127,8 +127,12 @@ const NOOP_MESSAGE = Symbol('a noop message'); * * @class DocumentReference */ -export class DocumentReference - implements Serializable, firestore.DocumentReference +export class DocumentReference< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> implements + Serializable, + firestore.DocumentReference { /** * @private @@ -143,7 +147,7 @@ export class DocumentReference /** @private */ readonly _path: ResourcePath, /** @private */ - readonly _converter = defaultConverter() + readonly _converter = defaultConverter() ) {} /** @@ -249,8 +253,8 @@ export class DocumentReference * }): * ``` */ - get parent(): CollectionReference { - return new CollectionReference( + get parent(): CollectionReference { + return new CollectionReference( this._firestore, this._path.parent()!, this._converter @@ -276,7 +280,7 @@ export class DocumentReference * }); * ``` */ - get(): Promise> { + get(): Promise> { return this._firestore.getAll(this).then(([result]) => result); } @@ -325,9 +329,7 @@ export class DocumentReference * }); * ``` */ - listCollections(): Promise< - Array> - > { + listCollections(): Promise> { const tag = requestTag(); return this.firestore.initializeIfNeeded(tag).then(() => { const request: api.IListCollectionIdsRequest = { @@ -344,9 +346,7 @@ export class DocumentReference tag ) .then(collectionIds => { - const collections: Array< - CollectionReference - > = []; + const collections: Array = []; // We can just sort this list using the default comparator since it // will only contain collection ids. @@ -382,7 +382,7 @@ export class DocumentReference * }); * ``` */ - create(data: firestore.WithFieldValue): Promise { + create(data: firestore.WithFieldValue): Promise { const writeBatch = new WriteBatch(this._firestore); return writeBatch .create(this, data) @@ -424,17 +424,18 @@ export class DocumentReference } set( - data: firestore.PartialWithFieldValue, + data: firestore.PartialWithFieldValue, options: firestore.SetOptions ): Promise; - set(data: firestore.WithFieldValue): Promise; + set(data: firestore.WithFieldValue): Promise; /** * Writes to the document referred to by this DocumentReference. If the * document does not yet exist, it will be created. If you pass * [SetOptions]{@link SetOptions}, the provided data can be merged into an * existing document. * - * @param {T|Partial} data A map of the fields and values for the document. + * @param {T|Partial} data A map of the fields and values for + * the document. * @param {SetOptions=} options An object to configure the set behavior. * @param {boolean=} options.merge If true, set() merges the values specified * in its data argument. Fields omitted from this set() call remain untouched. @@ -458,14 +459,17 @@ export class DocumentReference * ``` */ set( - data: firestore.PartialWithFieldValue, + data: firestore.PartialWithFieldValue, options?: firestore.SetOptions ): Promise { let writeBatch = new WriteBatch(this._firestore); if (options) { writeBatch = writeBatch.set(this, data, options); } else { - writeBatch = writeBatch.set(this, data as firestore.WithFieldValue); + writeBatch = writeBatch.set( + this, + data as firestore.WithFieldValue + ); } return writeBatch.commit().then(([writeResult]) => writeResult); } @@ -503,7 +507,10 @@ export class DocumentReference * ``` */ update( - dataOrField: firestore.UpdateData | string | firestore.FieldPath, + dataOrField: + | firestore.UpdateData + | string + | firestore.FieldPath, ...preconditionOrValues: Array< unknown | string | firestore.FieldPath | firestore.Precondition > @@ -547,16 +554,16 @@ export class DocumentReference * ``` */ onSnapshot( - onNext: (snapshot: firestore.DocumentSnapshot) => void, + onNext: ( + snapshot: firestore.DocumentSnapshot + ) => void, onError?: (error: Error) => void ): () => void { validateFunction('onNext', onNext); validateFunction('onError', onError, {optional: true}); - const watch: DocumentWatch = new (require('./watch').DocumentWatch)( - this.firestore, - this - ); + const watch: DocumentWatch = + new (require('./watch').DocumentWatch)(this.firestore, this); return watch.onSnapshot((readTime, size, docs) => { for (const document of docs()) { if (document.ref.path === this.path) { @@ -571,7 +578,9 @@ export class DocumentReference this._path, this._converter ); - const document = new DocumentSnapshotBuilder(ref); + const document = new DocumentSnapshotBuilder( + ref + ); document.readTime = readTime; onNext(document.build()); }, onError || console.error); @@ -584,7 +593,9 @@ export class DocumentReference * @return {boolean} true if this `DocumentReference` is equal to the provided * value. */ - isEqual(other: firestore.DocumentReference): boolean { + isEqual( + other: firestore.DocumentReference + ): boolean { return ( this === other || (other instanceof DocumentReference && @@ -604,15 +615,19 @@ export class DocumentReference return {referenceValue: this.formattedName}; } - withConverter(converter: null): DocumentReference; - withConverter( - converter: firestore.FirestoreDataConverter - ): DocumentReference; + withConverter(converter: null): DocumentReference; + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData + >( + converter: firestore.FirestoreDataConverter + ): DocumentReference; /** * Applies a custom data converter to this DocumentReference, allowing you to * use your own custom model objects with Firestore. When you call set(), * get(), etc. on the returned DocumentReference instance, the provided - * converter will convert between Firestore data and your custom type U. + * converter will convert between Firestore data of type `NewDbModelType` and + * your custom type `NewAppModelType`. * * Using the converter allows you to specify generic type arguments when * storing and retrieving objects from Firestore. @@ -656,12 +671,18 @@ export class DocumentReference * ``` * @param {FirestoreDataConverter | null} converter Converts objects to and * from Firestore. Passing in `null` removes the current converter. - * @return A DocumentReference that uses the provided converter. - */ - withConverter( - converter: firestore.FirestoreDataConverter | null - ): DocumentReference { - return new DocumentReference( + * @return A DocumentReference that uses the provided converter. + */ + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData + >( + converter: firestore.FirestoreDataConverter< + NewAppModelType, + NewDbModelType + > | null + ): DocumentReference { + return new DocumentReference( this.firestore, this._path, converter ?? defaultConverter() @@ -895,13 +916,23 @@ class FieldFilterInternal extends FilterInternal { * * @class QuerySnapshot */ -export class QuerySnapshot - implements firestore.QuerySnapshot +export class QuerySnapshot< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> implements firestore.QuerySnapshot { - private _materializedDocs: Array> | null = null; - private _materializedChanges: Array> | null = null; - private _docs: (() => Array>) | null = null; - private _changes: (() => Array>) | null = null; + private _materializedDocs: Array< + QueryDocumentSnapshot + > | null = null; + private _materializedChanges: Array< + DocumentChange + > | null = null; + private _docs: + | (() => Array>) + | null = null; + private _changes: + | (() => Array>) + | null = null; /** * @private @@ -915,11 +946,11 @@ export class QuerySnapshot * events for this snapshot. */ constructor( - private readonly _query: Query, + private readonly _query: Query, private readonly _readTime: Timestamp, private readonly _size: number, - docs: () => Array>, - changes: () => Array> + docs: () => Array>, + changes: () => Array> ) { this._docs = docs; this._changes = changes; @@ -946,7 +977,7 @@ export class QuerySnapshot * }); * ``` */ - get query(): Query { + get query(): Query { return this._query; } @@ -969,7 +1000,7 @@ export class QuerySnapshot * }); * ``` */ - get docs(): Array> { + get docs(): Array> { if (this._materializedDocs) { return this._materializedDocs!; } @@ -1059,7 +1090,7 @@ export class QuerySnapshot * }); * ``` */ - docChanges(): Array> { + docChanges(): Array> { if (this._materializedChanges) { return this._materializedChanges!; } @@ -1090,7 +1121,9 @@ export class QuerySnapshot * ``` */ forEach( - callback: (result: firestore.QueryDocumentSnapshot) => void, + callback: ( + result: firestore.QueryDocumentSnapshot + ) => void, thisArg?: unknown ): void { validateFunction('callback', callback); @@ -1108,7 +1141,7 @@ export class QuerySnapshot * @return {boolean} true if this `QuerySnapshot` is equal to the provided * value. */ - isEqual(other: firestore.QuerySnapshot): boolean { + isEqual(other: firestore.QuerySnapshot): boolean { // Since the read time is different on every query read, we explicitly // ignore all metadata in this comparison. @@ -1166,11 +1199,17 @@ enum LimitType { * @private * @internal */ -export class QueryOptions { +export class QueryOptions< + AppModelType, + DbModelType extends firestore.DocumentData +> { constructor( readonly parentPath: ResourcePath, readonly collectionId: string, - readonly converter: firestore.FirestoreDataConverter, + readonly converter: firestore.FirestoreDataConverter< + AppModelType, + DbModelType + >, readonly allDescendants: boolean, readonly filters: FilterInternal[], readonly fieldOrders: FieldOrder[], @@ -1194,11 +1233,14 @@ export class QueryOptions { * @private * @internal */ - static forCollectionGroupQuery( + static forCollectionGroupQuery< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData + >( collectionId: string, - converter = defaultConverter() - ): QueryOptions { - return new QueryOptions( + converter = defaultConverter() + ): QueryOptions { + return new QueryOptions( /*parentPath=*/ ResourcePath.EMPTY, collectionId, converter, @@ -1213,11 +1255,14 @@ export class QueryOptions { * @private * @internal */ - static forCollectionQuery( + static forCollectionQuery< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData + >( collectionRef: ResourcePath, - converter = defaultConverter() - ): QueryOptions { - return new QueryOptions( + converter = defaultConverter() + ): QueryOptions { + return new QueryOptions( collectionRef.parent()!, collectionRef.id!, converter, @@ -1234,12 +1279,15 @@ export class QueryOptions { * @private * @internal */ - static forKindlessAllDescendants( + static forKindlessAllDescendants( parent: ResourcePath, id: string, requireConsistency = true - ): QueryOptions { - let options = new QueryOptions( + ): QueryOptions { + let options = new QueryOptions< + firestore.DocumentData, + firestore.DocumentData + >( parent, id, defaultConverter(), @@ -1260,7 +1308,11 @@ export class QueryOptions { * @private * @internal */ - with(settings: Partial, 'converter'>>): QueryOptions { + with( + settings: Partial< + Omit, 'converter'> + > + ): QueryOptions { return new QueryOptions( coalesce(settings.parentPath, this.parentPath)!, coalesce(settings.collectionId, this.collectionId)!, @@ -1279,10 +1331,13 @@ export class QueryOptions { ); } - withConverter( - converter: firestore.FirestoreDataConverter - ): QueryOptions { - return new QueryOptions( + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData + >( + converter: firestore.FirestoreDataConverter + ): QueryOptions { + return new QueryOptions( this.parentPath, this.collectionId, converter, @@ -1302,7 +1357,7 @@ export class QueryOptions { return this.fieldOrders.length > 0; } - isEqual(other: QueryOptions): boolean { + isEqual(other: QueryOptions): boolean { if (this === other) { return true; } @@ -1345,7 +1400,11 @@ export class QueryOptions { * * @class Query */ -export class Query implements firestore.Query { +export class Query< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> implements firestore.Query +{ private readonly _serializer: Serializer; /** @private */ protected readonly _allowUndefined: boolean; @@ -1360,7 +1419,7 @@ export class Query implements firestore.Query { /** @private */ readonly _firestore: Firestore, /** @private */ - protected readonly _queryOptions: QueryOptions + protected readonly _queryOptions: QueryOptions ) { this._serializer = new Serializer(_firestore); this._allowUndefined = @@ -1455,10 +1514,10 @@ export class Query implements firestore.Query { * ``` */ where( - fieldPath: string | firestore.FieldPath, + fieldPath: string | FieldPath, opStr: firestore.WhereFilterOp, value: unknown - ): Query; + ): Query; /** * Creates and returns a new [Query]{@link Query} with the additional filter @@ -1482,13 +1541,13 @@ export class Query implements firestore.Query { * }); * ``` */ - where(filter: Filter): Query; + where(filter: Filter): Query; where( fieldPathOrFilter: string | firestore.FieldPath | Filter, opStr?: firestore.WhereFilterOp, value?: unknown - ): Query { + ): Query { let filter: Filter; if (fieldPathOrFilter instanceof Filter) { @@ -1606,9 +1665,7 @@ export class Query implements firestore.Query { * }); * ``` */ - select( - ...fieldPaths: Array - ): Query { + select(...fieldPaths: Array): Query { const fields: api.StructuredQuery.IFieldReference[] = []; if (fieldPaths.length === 0) { @@ -1626,7 +1683,7 @@ export class Query implements firestore.Query { // `T`. We there return `Query`; const options = this._queryOptions.with({ projection: {fields}, - }) as QueryOptions; + }) as QueryOptions; return new Query(this._firestore, options); } @@ -1657,7 +1714,7 @@ export class Query implements firestore.Query { orderBy( fieldPath: string | firestore.FieldPath, directionStr?: firestore.OrderByDirection - ): Query { + ): Query { validateFieldPath('fieldPath', fieldPath); directionStr = validateQueryOrder('directionStr', directionStr); @@ -1700,7 +1757,7 @@ export class Query implements firestore.Query { * }); * ``` */ - limit(limit: number): Query { + limit(limit: number): Query { validateInteger('limit', limit); const options = this._queryOptions.with({ @@ -1733,7 +1790,7 @@ export class Query implements firestore.Query { * }); * ``` */ - limitToLast(limit: number): Query { + limitToLast(limit: number): Query { validateInteger('limitToLast', limit); const options = this._queryOptions.with({limit, limitType: LimitType.Last}); @@ -1761,7 +1818,7 @@ export class Query implements firestore.Query { * }); * ``` */ - offset(offset: number): Query { + offset(offset: number): Query { validateInteger('offset', offset); const options = this._queryOptions.with({offset}); @@ -1785,7 +1842,11 @@ export class Query implements firestore.Query { * `snapshot` is the `AggregateQuerySnapshot` resulting from running the * returned query. */ - count(): AggregateQuery<{count: firestore.AggregateField}> { + count(): AggregateQuery< + {count: firestore.AggregateField}, + AppModelType, + DbModelType + > { return new AggregateQuery(this, {count: {}}); } @@ -1795,7 +1856,7 @@ export class Query implements firestore.Query { * @param {*} other The value to compare against. * @return {boolean} true if this `Query` is equal to the provided value. */ - isEqual(other: firestore.Query): boolean { + isEqual(other: firestore.Query): boolean { if (this === other) { return true; } @@ -1834,7 +1895,9 @@ export class Query implements firestore.Query { * @returns The implicit ordering semantics. */ private createImplicitOrderBy( - cursorValuesOrDocumentSnapshot: Array | unknown> + cursorValuesOrDocumentSnapshot: Array< + DocumentSnapshot | unknown + > ): FieldOrder[] { // Add an implicit orderBy if the only cursor value is a DocumentSnapshot // or a DocumentReference. @@ -1962,11 +2025,13 @@ export class Query implements firestore.Query { * @private * @internal */ - private validateReference(val: unknown): DocumentReference { + private validateReference( + val: unknown + ): DocumentReference { const basePath = this._queryOptions.allDescendants ? this._queryOptions.parentPath : this._queryOptions.parentPath.append(this._queryOptions.collectionId); - let reference: DocumentReference; + let reference: DocumentReference; if (typeof val === 'string') { const path = basePath.append(val); @@ -2043,9 +2108,9 @@ export class Query implements firestore.Query { */ startAt( ...fieldValuesOrDocumentSnapshot: Array< - firestore.DocumentSnapshot | unknown + firestore.DocumentSnapshot | unknown > - ): Query { + ): Query { validateMinNumberOfArguments( 'Query.startAt', fieldValuesOrDocumentSnapshot, @@ -2089,9 +2154,9 @@ export class Query implements firestore.Query { */ startAfter( ...fieldValuesOrDocumentSnapshot: Array< - firestore.DocumentSnapshot | unknown + firestore.DocumentSnapshot | unknown > - ): Query { + ): Query { validateMinNumberOfArguments( 'Query.startAfter', fieldValuesOrDocumentSnapshot, @@ -2134,9 +2199,9 @@ export class Query implements firestore.Query { */ endBefore( ...fieldValuesOrDocumentSnapshot: Array< - firestore.DocumentSnapshot | unknown + firestore.DocumentSnapshot | unknown > - ): Query { + ): Query { validateMinNumberOfArguments( 'Query.endBefore', fieldValuesOrDocumentSnapshot, @@ -2179,9 +2244,9 @@ export class Query implements firestore.Query { */ endAt( ...fieldValuesOrDocumentSnapshot: Array< - firestore.DocumentSnapshot | unknown + firestore.DocumentSnapshot | unknown > - ): Query { + ): Query { validateMinNumberOfArguments( 'Query.endAt', fieldValuesOrDocumentSnapshot, @@ -2219,7 +2284,7 @@ export class Query implements firestore.Query { * }); * ``` */ - get(): Promise> { + get(): Promise> { return this._get(); } @@ -2230,8 +2295,10 @@ export class Query implements firestore.Query { * @internal * @param {bytes=} transactionId A transaction ID. */ - _get(transactionId?: Uint8Array): Promise> { - const docs: Array> = []; + _get( + transactionId?: Uint8Array + ): Promise> { + const docs: Array> = []; // Capture the error stack to preserve stack tracing across async calls. const stack = Error().stack!; @@ -2264,7 +2331,9 @@ export class Query implements firestore.Query { docs.length, () => docs, () => { - const changes: Array> = []; + const changes: Array< + DocumentChange + > = []; for (let i = 0; i < docs.length; ++i) { changes.push(new DocumentChange('added', docs[i], -1, i)); } @@ -2488,7 +2557,10 @@ export class Query implements firestore.Query { _stream(transactionId?: Uint8Array): NodeJS.ReadableStream { const tag = requestTag(); - let lastReceivedDocument: QueryDocumentSnapshot | null = null; + let lastReceivedDocument: QueryDocumentSnapshot< + AppModelType, + DbModelType + > | null = null; let backendStream: Duplex; const stream = new Transform({ @@ -2504,16 +2576,20 @@ export class Query implements firestore.Query { proto.document, proto.readTime ); - const finalDoc = new DocumentSnapshotBuilder( - document.ref.withConverter(this._queryOptions.converter) - ); + const finalDoc = new DocumentSnapshotBuilder< + AppModelType, + DbModelType + >(document.ref.withConverter(this._queryOptions.converter)); // Recreate the QueryDocumentSnapshot with the DocumentReference // containing the original converter. finalDoc.fieldsProto = document._fieldsProto; finalDoc.readTime = document.readTime; finalDoc.createTime = document.createTime; finalDoc.updateTime = document.updateTime; - lastReceivedDocument = finalDoc.build() as QueryDocumentSnapshot; + lastReceivedDocument = finalDoc.build() as QueryDocumentSnapshot< + AppModelType, + DbModelType + >; callback(undefined, {document: lastReceivedDocument, readTime}); if (proto.done) { logger('Query._stream', tag, 'Trigger Logical Termination.'); @@ -2626,17 +2702,18 @@ export class Query implements firestore.Query { * ``` */ onSnapshot( - onNext: (snapshot: firestore.QuerySnapshot) => void, + onNext: (snapshot: QuerySnapshot) => void, onError?: (error: Error) => void ): () => void { validateFunction('onNext', onNext); validateFunction('onError', onError, {optional: true}); - const watch: QueryWatch = new (require('./watch').QueryWatch)( - this.firestore, - this, - this._queryOptions.converter - ); + const watch: QueryWatch = + new (require('./watch').QueryWatch)( + this.firestore, + this, + this._queryOptions.converter + ); return watch.onSnapshot((readTime, size, docs, changes) => { onNext(new QuerySnapshot(this, readTime, size, docs, changes)); @@ -2651,8 +2728,8 @@ export class Query implements firestore.Query { * @internal */ comparator(): ( - s1: QueryDocumentSnapshot, - s2: QueryDocumentSnapshot + s1: QueryDocumentSnapshot, + s2: QueryDocumentSnapshot ) => number { return (doc1, doc2) => { // Add implicit sorting by name, using the last specified direction. @@ -2692,13 +2769,18 @@ export class Query implements firestore.Query { }; } - withConverter(converter: null): Query; - withConverter(converter: firestore.FirestoreDataConverter): Query; + withConverter(converter: null): Query; + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData + >( + converter: firestore.FirestoreDataConverter + ): Query; /** * Applies a custom data converter to this Query, allowing you to use your * own custom model objects with Firestore. When you call get() on the * returned Query, the provided converter will convert between Firestore - * data and your custom type U. + * data of type `NewDbModelType` and your custom type `NewAppModelType`. * * Using the converter allows you to specify generic type arguments when * storing and retrieving objects from Firestore. @@ -2742,12 +2824,18 @@ export class Query implements firestore.Query { * ``` * @param {FirestoreDataConverter | null} converter Converts objects to and * from Firestore. Passing in `null` removes the current converter. - * @return A Query that uses the provided converter. - */ - withConverter( - converter: firestore.FirestoreDataConverter | null - ): Query { - return new Query( + * @return A Query that uses the provided converter. + */ + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData + >( + converter: firestore.FirestoreDataConverter< + NewAppModelType, + NewDbModelType + > | null + ): Query { + return new Query( this.firestore, this._queryOptions.withConverter(converter ?? defaultConverter()) ); @@ -2762,9 +2850,12 @@ export class Query implements firestore.Query { * @class CollectionReference * @extends Query */ -export class CollectionReference - extends Query - implements firestore.CollectionReference +export class CollectionReference< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData + > + extends Query + implements firestore.CollectionReference { /** * @private @@ -2775,7 +2866,7 @@ export class CollectionReference constructor( firestore: Firestore, path: ResourcePath, - converter?: firestore.FirestoreDataConverter + converter?: firestore.FirestoreDataConverter ) { super(firestore, QueryOptions.forCollectionQuery(path, converter)); } @@ -2823,7 +2914,7 @@ export class CollectionReference * console.log(`Parent name: ${documentRef.path}`); * ``` */ - get parent(): DocumentReference | null { + get parent(): DocumentReference | null { if (this._queryOptions.parentPath.isDocument) { return new DocumentReference( this.firestore, @@ -2881,7 +2972,9 @@ export class CollectionReference * }); * ``` */ - listDocuments(): Promise>> { + listDocuments(): Promise< + Array> + > { const tag = requestTag(); return this.firestore.initializeIfNeeded(tag).then(() => { const parentPath = this._queryOptions.parentPath.toQualifiedResourcePath( @@ -2919,8 +3012,8 @@ export class CollectionReference }); } - doc(): DocumentReference; - doc(documentPath: string): DocumentReference; + doc(): DocumentReference; + doc(documentPath: string): DocumentReference; /** * Gets a [DocumentReference]{@link DocumentReference} instance that * refers to the document at the specified path. If no path is specified, an @@ -2940,7 +3033,7 @@ export class CollectionReference * console.log(`Reference with auto-id: ${documentRefWithAutoId.path}`); * ``` */ - doc(documentPath?: string): DocumentReference { + doc(documentPath?: string): DocumentReference { if (arguments.length === 0) { documentPath = autoId(); } else { @@ -2980,7 +3073,9 @@ export class CollectionReference * }); * ``` */ - add(data: firestore.WithFieldValue): Promise> { + add( + data: firestore.WithFieldValue + ): Promise> { const firestoreData = this._queryOptions.converter.toFirestore(data); validateDocumentData( 'data', @@ -3000,22 +3095,28 @@ export class CollectionReference * @return {boolean} true if this `CollectionReference` is equal to the * provided value. */ - isEqual(other: firestore.CollectionReference): boolean { + isEqual( + other: firestore.CollectionReference + ): boolean { return ( this === other || (other instanceof CollectionReference && super.isEqual(other)) ); } - withConverter(converter: null): CollectionReference; - withConverter( - converter: firestore.FirestoreDataConverter - ): CollectionReference; + withConverter(converter: null): CollectionReference; + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData + >( + converter: firestore.FirestoreDataConverter + ): CollectionReference; /** * Applies a custom data converter to this CollectionReference, allowing you * to use your own custom model objects with Firestore. When you call add() on * the returned CollectionReference instance, the provided converter will - * convert between Firestore data and your custom type U. + * convert between Firestore data of type `NewDbModelType` and your custom + * type `NewAppModelType`. * * Using the converter allows you to specify generic type arguments when * storing and retrieving objects from Firestore. @@ -3059,12 +3160,18 @@ export class CollectionReference * ``` * @param {FirestoreDataConverter | null} converter Converts objects to and * from Firestore. Passing in `null` removes the current converter. - * @return A CollectionReference that uses the provided converter. - */ - withConverter( - converter: firestore.FirestoreDataConverter | null - ): CollectionReference { - return new CollectionReference( + * @return A CollectionReference that uses the provided converter. + */ + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData + >( + converter: firestore.FirestoreDataConverter< + NewAppModelType, + NewDbModelType + > | null + ): CollectionReference { + return new CollectionReference( this.firestore, this._resourcePath, converter ?? defaultConverter() @@ -3075,8 +3182,12 @@ export class CollectionReference /** * A query that calculates aggregations over an underlying query. */ -export class AggregateQuery - implements firestore.AggregateQuery +export class AggregateQuery< + AggregateSpecType extends firestore.AggregateSpec, + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> implements + firestore.AggregateQuery { /** * @private @@ -3088,12 +3199,12 @@ export class AggregateQuery */ constructor( // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readonly _query: Query, - private readonly _aggregates: T + private readonly _query: Query, + private readonly _aggregates: AggregateSpecType ) {} /** The query whose aggregations will be calculated by this object. */ - get query(): firestore.Query { + get query(): Query { return this._query; } @@ -3102,7 +3213,9 @@ export class AggregateQuery * * @return A promise that will be resolved with the results of the query. */ - get(): Promise> { + get(): Promise< + AggregateQuerySnapshot + > { return this._get(); } @@ -3113,7 +3226,11 @@ export class AggregateQuery * @internal * @param {bytes=} transactionId A transaction ID. */ - _get(transactionId?: Uint8Array): Promise> { + _get( + transactionId?: Uint8Array + ): Promise< + AggregateQuerySnapshot + > { // Capture the error stack to preserve stack tracing across async calls. const stack = Error().stack!; @@ -3150,10 +3267,7 @@ export class AggregateQuery if (proto.result) { const readTime = Timestamp.fromProto(proto.readTime!); const data = this.decodeResult(proto.result); - callback( - undefined, - new AggregateQuerySnapshot(this, readTime, data) - ); + callback(undefined, new AggregateQuerySnapshot(this, readTime, data)); } else { callback(Error('RunAggregationQueryResponse is missing result')); } @@ -3222,7 +3336,7 @@ export class AggregateQuery */ private decodeResult( proto: api.IAggregationResult - ): firestore.AggregateSpecData { + ): firestore.AggregateSpecData { // eslint-disable-next-line @typescript-eslint/no-explicit-any const data: any = {}; const fields = proto.aggregateFields; @@ -3284,7 +3398,13 @@ export class AggregateQuery * @return `true` if this object is "equal" to the given object, as * defined above, or `false` otherwise. */ - isEqual(other: firestore.AggregateQuery): boolean { + isEqual( + other: firestore.AggregateQuery< + AggregateSpecType, + AppModelType, + DbModelType + > + ): boolean { if (this === other) { return true; } @@ -3301,8 +3421,16 @@ export class AggregateQuery /** * The results of executing an aggregation query. */ -export class AggregateQuerySnapshot - implements firestore.AggregateQuerySnapshot +export class AggregateQuerySnapshot< + AggregateSpecType extends firestore.AggregateSpec, + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> implements + firestore.AggregateQuerySnapshot< + AggregateSpecType, + AppModelType, + DbModelType + > { /** * @private @@ -3314,18 +3442,22 @@ export class AggregateQuerySnapshot * query. */ constructor( - private readonly _query: AggregateQuery, + private readonly _query: AggregateQuery< + AggregateSpecType, + AppModelType, + DbModelType + >, private readonly _readTime: Timestamp, - private readonly _data: firestore.AggregateSpecData + private readonly _data: firestore.AggregateSpecData ) {} /** The query that was executed to produce this result. */ - get query(): firestore.AggregateQuery { + get query(): AggregateQuery { return this._query; } /** The time this snapshot was read. */ - get readTime(): firestore.Timestamp { + get readTime(): Timestamp { return this._readTime; } @@ -3340,7 +3472,7 @@ export class AggregateQuerySnapshot * @returns The results of the aggregations performed over the underlying * query. */ - data(): firestore.AggregateSpecData { + data(): firestore.AggregateSpecData { return this._data; } @@ -3355,7 +3487,13 @@ export class AggregateQuerySnapshot * @return `true` if this object is "equal" to the given object, as * defined above, or `false` otherwise. */ - isEqual(other: firestore.AggregateQuerySnapshot): boolean { + isEqual( + other: firestore.AggregateQuerySnapshot< + AggregateSpecType, + AppModelType, + DbModelType + > + ): boolean { if (this === other) { return true; } @@ -3447,10 +3585,13 @@ export function validateQueryOperator( * @param value The argument to validate. * @return the DocumentReference if valid */ -export function validateDocumentReference( +export function validateDocumentReference< + AppModelType, + DbModelType extends firestore.DocumentData +>( arg: string | number, - value: unknown -): DocumentReference { + value: firestore.DocumentReference +): DocumentReference { if (!(value instanceof DocumentReference)) { throw new Error(invalidArgumentMessage(arg, 'DocumentReference')); } diff --git a/dev/src/transaction.ts b/dev/src/transaction.ts index 3f01d433a..230e793cd 100644 --- a/dev/src/transaction.ts +++ b/dev/src/transaction.ts @@ -88,7 +88,9 @@ export class Transaction implements firestore.Transaction { * @param {Query} query A query to execute. * @return {Promise} A QuerySnapshot for the retrieved data. */ - get(query: Query): Promise>; + get( + query: firestore.Query + ): Promise>; /** * Reads the document referenced by the provided `DocumentReference.` @@ -97,7 +99,9 @@ export class Transaction implements firestore.Transaction { * @param {DocumentReference} documentRef A reference to the document to be read. * @return {Promise} A DocumentSnapshot for the read data. */ - get(documentRef: DocumentReference): Promise>; + get( + documentRef: firestore.DocumentReference + ): Promise>; /** * Retrieves an aggregate query result. Holds a pessimistic lock on all @@ -106,9 +110,19 @@ export class Transaction implements firestore.Transaction { * @param aggregateQuery An aggregate query to execute. * @return An AggregateQuerySnapshot for the retrieved data. */ - get( - aggregateQuery: firestore.AggregateQuery - ): Promise>; + get< + AppModelType, + DbModelType extends firestore.DocumentData, + AggregateSpecType extends firestore.AggregateSpec + >( + aggregateQuery: firestore.AggregateQuery< + AggregateSpecType, + AppModelType, + DbModelType + > + ): Promise< + AggregateQuerySnapshot + >; /** * Retrieve a document or a query result from the database. Holds a @@ -133,13 +147,19 @@ export class Transaction implements firestore.Transaction { * }); * ``` */ - get( + get< + AppModelType, + DbModelType extends firestore.DocumentData, + AggregateSpecType extends firestore.AggregateSpec + >( refOrQuery: - | firestore.DocumentReference - | firestore.Query - | firestore.AggregateQuery + | firestore.DocumentReference + | firestore.Query + | firestore.AggregateQuery ): Promise< - DocumentSnapshot | QuerySnapshot | AggregateQuerySnapshot + | DocumentSnapshot + | QuerySnapshot + | AggregateQuerySnapshot > { if (!this._writeBatch.isEmpty) { throw new Error(READ_AFTER_WRITE_ERROR_MSG); @@ -155,7 +175,7 @@ export class Transaction implements firestore.Transaction { return refOrQuery._get(this._transactionId); } - if (refOrQuery instanceof AggregateQuery) { + if (refOrQuery instanceof AggregateQuery) { return refOrQuery._get(this._transactionId); } @@ -192,11 +212,12 @@ export class Transaction implements firestore.Transaction { * }); * ``` */ - getAll( + getAll( ...documentRefsOrReadOptions: Array< - firestore.DocumentReference | firestore.ReadOptions + | firestore.DocumentReference + | firestore.ReadOptions > - ): Promise>> { + ): Promise>> { if (!this._writeBatch.isEmpty) { throw new Error(READ_AFTER_WRITE_ERROR_MSG); } @@ -240,22 +261,22 @@ export class Transaction implements firestore.Transaction { * }); * ``` */ - create( - documentRef: firestore.DocumentReference, - data: firestore.WithFieldValue + create( + documentRef: firestore.DocumentReference, + data: firestore.WithFieldValue ): Transaction { this._writeBatch.create(documentRef, data); return this; } - set( - documentRef: firestore.DocumentReference, - data: firestore.PartialWithFieldValue, + set( + documentRef: firestore.DocumentReference, + data: firestore.PartialWithFieldValue, options: firestore.SetOptions ): Transaction; - set( - documentRef: firestore.DocumentReference, - data: firestore.WithFieldValue + set( + documentRef: firestore.DocumentReference, + data: firestore.WithFieldValue ): Transaction; /** * Writes to the document referred to by the provided @@ -289,15 +310,18 @@ export class Transaction implements firestore.Transaction { * }); * ``` */ - set( - documentRef: firestore.DocumentReference, - data: firestore.PartialWithFieldValue, + set( + documentRef: firestore.DocumentReference, + data: firestore.PartialWithFieldValue, options?: firestore.SetOptions ): Transaction { if (options) { this._writeBatch.set(documentRef, data, options); } else { - this._writeBatch.set(documentRef, data as firestore.WithFieldValue); + this._writeBatch.set( + documentRef, + data as firestore.WithFieldValue + ); } return this; } @@ -343,9 +367,12 @@ export class Transaction implements firestore.Transaction { * }); * ``` */ - update( - documentRef: firestore.DocumentReference, - dataOrField: firestore.UpdateData | string | firestore.FieldPath, + update( + documentRef: firestore.DocumentReference, + dataOrField: + | firestore.UpdateData + | string + | firestore.FieldPath, ...preconditionOrValues: Array< firestore.Precondition | unknown | string | firestore.FieldPath > @@ -382,8 +409,8 @@ export class Transaction implements firestore.Transaction { * }); * ``` */ - delete( - documentRef: DocumentReference, + delete( + documentRef: DocumentReference, precondition?: firestore.Precondition ): this { this._writeBatch.delete(documentRef, precondition); @@ -555,12 +582,19 @@ export class Transaction implements firestore.Transaction { * @param documentRefsOrReadOptions An array of document references followed by * an optional ReadOptions object. */ -export function parseGetAllArguments( +export function parseGetAllArguments< + AppModelType, + DbModelType extends firestore.DocumentData +>( documentRefsOrReadOptions: Array< - firestore.DocumentReference | firestore.ReadOptions + | firestore.DocumentReference + | firestore.ReadOptions > -): {documents: Array>; fieldMask: FieldPath[] | null} { - let documents: Array>; +): { + documents: Array>; + fieldMask: FieldPath[] | null; +} { + let documents: Array>; let readOptions: firestore.ReadOptions | undefined = undefined; if (Array.isArray(documentRefsOrReadOptions[0])) { @@ -577,13 +611,17 @@ export function parseGetAllArguments( ) ) { readOptions = documentRefsOrReadOptions.pop() as firestore.ReadOptions; - documents = documentRefsOrReadOptions as Array>; + documents = documentRefsOrReadOptions as Array< + DocumentReference + >; } else { - documents = documentRefsOrReadOptions as Array>; + documents = documentRefsOrReadOptions as Array< + DocumentReference + >; } for (let i = 0; i < documents.length; ++i) { - validateDocumentReference(i, documents[i]); + validateDocumentReference(i, documents[i]); } validateReadOptions('options', readOptions, {optional: true}); diff --git a/dev/src/types.ts b/dev/src/types.ts index 07dd5733b..ec6cf45d4 100644 --- a/dev/src/types.ts +++ b/dev/src/types.ts @@ -16,6 +16,7 @@ import { FirestoreDataConverter, + QueryDocumentSnapshot, DocumentData, WithFieldValue, } from '@google-cloud/firestore'; @@ -27,7 +28,6 @@ import {google} from '../protos/firestore_v1_proto_api'; import {FieldPath} from './path'; import api = google.firestore.v1; -import {QueryDocumentSnapshot} from './document'; /** * A map in the format of the Proto API @@ -136,8 +136,14 @@ const defaultConverterObj: FirestoreDataConverter = { * @private * @internal */ -export function defaultConverter(): FirestoreDataConverter { - return defaultConverterObj as FirestoreDataConverter; +export function defaultConverter< + AppModelType, + DbModelType extends DocumentData +>(): FirestoreDataConverter { + return defaultConverterObj as FirestoreDataConverter< + AppModelType, + DbModelType + >; } /** diff --git a/dev/src/watch.ts b/dev/src/watch.ts index d1871ae23..3337962cc 100644 --- a/dev/src/watch.ts +++ b/dev/src/watch.ts @@ -33,6 +33,7 @@ import {defaultConverter, RBTree} from './types'; import {requestTag} from './util'; import api = google.firestore.v1; +import {DocumentData} from '@google-cloud/firestore'; /*! * Target ID used by watch. Watch uses a fixed target id since we only support @@ -53,7 +54,7 @@ export const WATCH_IDLE_TIMEOUT_MS = 120 * 1000; /*! * Sentinel value for a document remove. */ -const REMOVED = {} as DocumentSnapshotBuilder; +const REMOVED = {} as DocumentSnapshotBuilder; /*! * The change type for document change events. @@ -69,9 +70,12 @@ const ChangeType: {[k: string]: DocumentChangeType} = { * The comparator used for document watches (which should always get called with * the same document). */ -const DOCUMENT_WATCH_COMPARATOR: ( - doc1: QueryDocumentSnapshot, - doc2: QueryDocumentSnapshot +const DOCUMENT_WATCH_COMPARATOR: < + AppModelType, + DbModelType extends DocumentData +>( + doc1: QueryDocumentSnapshot, + doc2: QueryDocumentSnapshot ) => number = (doc1, doc2) => { assert(doc1 === doc2, 'Document watches only support one document.'); return 0; @@ -109,15 +113,21 @@ const EMPTY_FUNCTION: () => void = () => {}; * changed documents since the last snapshot delivered for this watch. */ -type DocumentComparator = ( - l: QueryDocumentSnapshot, - r: QueryDocumentSnapshot +type DocumentComparator< + AppModelType, + DbModelType extends firestore.DocumentData +> = ( + l: QueryDocumentSnapshot, + r: QueryDocumentSnapshot ) => number; -interface DocumentChangeSet { +interface DocumentChangeSet< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> { deletes: string[]; - adds: Array>; - updates: Array>; + adds: Array>; + updates: Array>; } /** @@ -128,7 +138,10 @@ interface DocumentChangeSet { * @private * @internal */ -abstract class Watch { +abstract class Watch< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> { protected readonly firestore: Firestore; private readonly backoff: ExponentialBackoff; private readonly requestTag: string; @@ -160,7 +173,10 @@ abstract class Watch { * @private * @internal */ - private docMap = new Map>(); + private docMap = new Map< + string, + QueryDocumentSnapshot + >(); /** * The accumulated map of document changes (keyed by document name) for the @@ -168,7 +184,10 @@ abstract class Watch { * @private * @internal */ - private changeMap = new Map>(); + private changeMap = new Map< + string, + DocumentSnapshotBuilder + >(); /** * The current state of the query results. * @@ -203,8 +222,8 @@ abstract class Watch { private onNext: ( readTime: Timestamp, size: number, - docs: () => Array>, - changes: () => Array> + docs: () => Array>, + changes: () => Array> ) => void; private onError: (error: Error) => void; @@ -217,7 +236,7 @@ abstract class Watch { */ constructor( firestore: Firestore, - readonly _converter = defaultConverter() + readonly _converter = defaultConverter() ) { this.firestore = firestore; this.backoff = new ExponentialBackoff(); @@ -233,7 +252,10 @@ abstract class Watch { * Returns a comparator for QueryDocumentSnapshots that is used to order the * document snapshots returned by this watch. */ - protected abstract getComparator(): DocumentComparator; + protected abstract getComparator(): DocumentComparator< + AppModelType, + DbModelType + >; /** * Starts a watch and attaches a listener for document change events. @@ -252,8 +274,8 @@ abstract class Watch { onNext: ( readTime: Timestamp, size: number, - docs: () => Array>, - changes: () => Array> + docs: () => Array>, + changes: () => Array> ) => void, onError: (error: Error) => void ): () => void { @@ -302,10 +324,12 @@ abstract class Watch { * @private * @internal */ - private extractCurrentChanges(readTime: Timestamp): DocumentChangeSet { + private extractCurrentChanges( + readTime: Timestamp + ): DocumentChangeSet { const deletes: string[] = []; - const adds: Array> = []; - const updates: Array> = []; + const adds: Array> = []; + const updates: Array> = []; this.changeMap.forEach((value, name) => { if (value === REMOVED) { @@ -314,10 +338,14 @@ abstract class Watch { } } else if (this.docMap.has(name)) { value.readTime = readTime; - updates.push(value.build() as QueryDocumentSnapshot); + updates.push( + value.build() as QueryDocumentSnapshot + ); } else { value.readTime = readTime; - adds.push(value.build() as QueryDocumentSnapshot); + adds.push( + value.build() as QueryDocumentSnapshot + ); } }); @@ -339,7 +367,7 @@ abstract class Watch { // will be send again by the server. this.changeMap.set( snapshot.ref.path, - REMOVED as DocumentSnapshotBuilder + REMOVED as DocumentSnapshotBuilder ); }); @@ -577,7 +605,7 @@ abstract class Watch { if (changed) { logger('Watch.onData', this.requestTag, 'Received document change'); const ref = this.firestore.doc(relativeName); - const snapshot = new DocumentSnapshotBuilder( + const snapshot = new DocumentSnapshotBuilder( ref.withConverter(this._converter) ); snapshot.fieldsProto = document.fields || {}; @@ -586,14 +614,20 @@ abstract class Watch { this.changeMap.set(relativeName, snapshot); } else if (removed) { logger('Watch.onData', this.requestTag, 'Received document remove'); - this.changeMap.set(relativeName, REMOVED as DocumentSnapshotBuilder); + this.changeMap.set( + relativeName, + REMOVED as DocumentSnapshotBuilder + ); } } else if (proto.documentDelete || proto.documentRemove) { logger('Watch.onData', this.requestTag, 'Processing remove event'); const name = (proto.documentDelete || proto.documentRemove)!.document!; const relativeName = QualifiedResourcePath.fromSlashSeparatedString(name).relativeName; - this.changeMap.set(relativeName, REMOVED as DocumentSnapshotBuilder); + this.changeMap.set( + relativeName, + REMOVED as DocumentSnapshotBuilder + ); } else if (proto.filter) { logger('Watch.onData', this.requestTag, 'Processing filter update'); if (proto.filter.count !== this.currentSize()) { @@ -673,7 +707,7 @@ abstract class Watch { * @private * @internal */ - private deleteDoc(name: string): DocumentChange { + private deleteDoc(name: string): DocumentChange { assert(this.docMap.has(name), 'Document to delete does not exist'); const oldDocument = this.docMap.get(name)!; const existing = this.docTree.find(oldDocument); @@ -689,7 +723,9 @@ abstract class Watch { * @private * @internal */ - private addDoc(newDocument: QueryDocumentSnapshot): DocumentChange { + private addDoc( + newDocument: QueryDocumentSnapshot + ): DocumentChange { const name = newDocument.ref.path; assert(!this.docMap.has(name), 'Document to add already exists'); this.docTree = this.docTree.insert(newDocument, null); @@ -705,8 +741,8 @@ abstract class Watch { * @internal */ private modifyDoc( - newDocument: QueryDocumentSnapshot - ): DocumentChange | null { + newDocument: QueryDocumentSnapshot + ): DocumentChange | null { const name = newDocument.ref.path; assert(this.docMap.has(name), 'Document to modify does not exist'); const oldDocument = this.docMap.get(name)!; @@ -730,9 +766,11 @@ abstract class Watch { * @private * @internal */ - private computeSnapshot(readTime: Timestamp): Array> { + private computeSnapshot( + readTime: Timestamp + ): Array> { const changeSet = this.extractCurrentChanges(readTime); - const appliedChanges: Array> = []; + const appliedChanges: Array> = []; // Process the sorted changes in the order that is expected by our clients // (removals, additions, and then modifications). We also need to sort the @@ -843,15 +881,18 @@ abstract class Watch { * @private * @internal */ -export class DocumentWatch extends Watch { +export class DocumentWatch< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> extends Watch { constructor( firestore: Firestore, - private readonly ref: DocumentReference + private readonly ref: DocumentReference ) { super(firestore, ref._converter); } - getComparator(): DocumentComparator { + getComparator(): DocumentComparator { return DOCUMENT_WATCH_COMPARATOR; } @@ -873,19 +914,22 @@ export class DocumentWatch extends Watch { * @private * @internal */ -export class QueryWatch extends Watch { - private comparator: DocumentComparator; +export class QueryWatch< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData +> extends Watch { + private comparator: DocumentComparator; constructor( firestore: Firestore, - private readonly query: Query, - converter?: firestore.FirestoreDataConverter + private readonly query: Query, + converter?: firestore.FirestoreDataConverter ) { super(firestore, converter); this.comparator = query.comparator(); } - getComparator(): DocumentComparator { + getComparator(): DocumentComparator { return this.query.comparator(); } diff --git a/dev/src/write-batch.ts b/dev/src/write-batch.ts index b77eede51..f970f3de5 100644 --- a/dev/src/write-batch.ts +++ b/dev/src/write-batch.ts @@ -189,13 +189,13 @@ export class WriteBatch implements firestore.WriteBatch { * }); * ``` */ - create( - documentRef: firestore.DocumentReference, - data: firestore.WithFieldValue + create( + documentRef: firestore.DocumentReference, + data: firestore.WithFieldValue ): WriteBatch { const ref = validateDocumentReference('documentRef', documentRef); const firestoreData = ref._converter.toFirestore( - data as firestore.WithFieldValue + data as firestore.WithFieldValue ); validateDocumentData( 'data', @@ -253,8 +253,8 @@ export class WriteBatch implements firestore.WriteBatch { * }); * ``` */ - delete( - documentRef: firestore.DocumentReference, + delete( + documentRef: firestore.DocumentReference, precondition?: firestore.Precondition ): WriteBatch { const ref = validateDocumentReference('documentRef', documentRef); @@ -277,14 +277,14 @@ export class WriteBatch implements firestore.WriteBatch { return this; } - set( - documentRef: firestore.DocumentReference, - data: firestore.PartialWithFieldValue, + set( + documentRef: firestore.DocumentReference, + data: firestore.PartialWithFieldValue, options: firestore.SetOptions ): WriteBatch; - set( - documentRef: firestore.DocumentReference, - data: firestore.WithFieldValue + set( + documentRef: firestore.DocumentReference, + data: firestore.WithFieldValue ): WriteBatch; /** * Write to the document referred to by the provided @@ -320,9 +320,9 @@ export class WriteBatch implements firestore.WriteBatch { * }); * ``` */ - set( - documentRef: firestore.DocumentReference, - data: firestore.PartialWithFieldValue, + set( + documentRef: firestore.DocumentReference, + data: firestore.PartialWithFieldValue, options?: firestore.SetOptions ): WriteBatch { validateSetOptions('options', options, {optional: true}); @@ -331,12 +331,9 @@ export class WriteBatch implements firestore.WriteBatch { const ref = validateDocumentReference('documentRef', documentRef); let firestoreData: firestore.DocumentData; if (mergeLeaves || mergePaths) { - // Cast to any in order to satisfy the union type constraint on - // toFirestore(). - // eslint-disable-next-line @typescript-eslint/no-explicit-any - firestoreData = (ref._converter as any).toFirestore(data, options); + firestoreData = ref._converter.toFirestore(data, options); } else { - firestoreData = ref._converter.toFirestore(data as T); + firestoreData = ref._converter.toFirestore(data as AppModelType); } validateDocumentData( 'data', @@ -356,11 +353,11 @@ export class WriteBatch implements firestore.WriteBatch { firestoreData = documentMask.applyTo(firestoreData); } - const transform = DocumentTransform.fromObject(documentRef, firestoreData); + const transform = DocumentTransform.fromObject(ref, firestoreData); transform.validate(); const op: PendingWriteOp = () => { - const document = DocumentSnapshot.fromObject(documentRef, firestoreData); + const document = DocumentSnapshot.fromObject(ref, firestoreData); if (mergePaths) { documentMask!.removeFields(transform.fields); @@ -422,9 +419,15 @@ export class WriteBatch implements firestore.WriteBatch { * }); * ``` */ - update( - documentRef: firestore.DocumentReference, - dataOrField: firestore.UpdateData | string | firestore.FieldPath, + update< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData + >( + documentRef: firestore.DocumentReference, + dataOrField: + | firestore.UpdateData + | string + | firestore.FieldPath, ...preconditionOrValues: Array< | {lastUpdateTime?: firestore.Timestamp} | unknown @@ -489,16 +492,16 @@ export class WriteBatch implements firestore.WriteBatch { // eslint-disable-next-line prefer-rest-params validateMaxNumberOfArguments('update', arguments, 3); - Object.entries(dataOrField as firestore.UpdateData).forEach( - ([key, value]) => { - // Skip `undefined` values (can be hit if `ignoreUndefinedProperties` - // is set) - if (value !== undefined) { - validateFieldPath(key, key); - updateMap.set(FieldPath.fromArgument(key), value); - } + Object.entries( + dataOrField as firestore.UpdateData + ).forEach(([key, value]) => { + // Skip `undefined` values (can be hit if `ignoreUndefinedProperties` + // is set) + if (value !== undefined) { + validateFieldPath(key, key); + updateMap.set(FieldPath.fromArgument(key), value); } - ); + }); if (preconditionOrValues.length > 0) { validateUpdatePrecondition( diff --git a/dev/system-test/firestore.ts b/dev/system-test/firestore.ts index 4a8153e7e..428db3305 100644 --- a/dev/system-test/firestore.ts +++ b/dev/system-test/firestore.ts @@ -97,7 +97,7 @@ if (process.env.NODE_ENV === 'DEBUG') { setLogFunction(console.log); } -function getTestRoot(settings: Settings = {}) { +function getTestRoot(settings: Settings = {}): CollectionReference { const internalSettings: Settings = {}; if (process.env.FIRESTORE_NAMED_DATABASE) { internalSettings.databaseId = process.env.FIRESTORE_NAMED_DATABASE; @@ -3769,7 +3769,7 @@ describe('BulkWriter class', () => { it('can retry failed writes with a provided callback', async () => { let retryCount = 0; - let code: Status = -1; + let code: Status = -1 as Status; writer.onWriteError(error => { retryCount = error.failedAttempts; return error.failedAttempts < 3; diff --git a/dev/test/bulk-writer.ts b/dev/test/bulk-writer.ts index ce9faf426..86a01c50a 100644 --- a/dev/test/bulk-writer.ts +++ b/dev/test/bulk-writer.ts @@ -949,7 +949,7 @@ describe('BulkWriter', () => { response: failedResponse(Status.INTERNAL), }, ]); - let code: Status = -1; + let code: Status = -1 as Status; bulkWriter.onWriteError(error => { return error.failedAttempts < 3; }); diff --git a/dev/test/document.ts b/dev/test/document.ts index 07bf1effc..f37bf0050 100644 --- a/dev/test/document.ts +++ b/dev/test/document.ts @@ -49,6 +49,7 @@ import { verifyInstance, writeResult, } from './util/helpers'; +import {Precondition} from '@google-cloud/firestore'; const PROJECT_ID = 'test-project'; @@ -1703,12 +1704,12 @@ describe('update document', () => { it('with invalid last update time precondition', () => { expect(() => { - firestore.doc('collectionId/documentId').update( - {foo: 'bar'}, - { - lastUpdateTime: 'foo', - } - ); + const malformedPrecondition: Precondition = { + lastUpdateTime: 'foo', + } as unknown as Precondition; + firestore + .doc('collectionId/documentId') + .update({foo: 'bar'}, malformedPrecondition); }).to.throw('"lastUpdateTime" is not a Firestore Timestamp.'); }); diff --git a/dev/test/query.ts b/dev/test/query.ts index 1e8458f61..4f4b24c3e 100644 --- a/dev/test/query.ts +++ b/dev/test/query.ts @@ -898,7 +898,7 @@ describe('query interface', () => { return createInstance(overrides).then(async firestoreInstance => { firestore = firestoreInstance; - const coll = await firestore + const coll = firestore .collection('collectionId') .withConverter(postConverter); diff --git a/dev/test/types.ts b/dev/test/types.ts new file mode 100644 index 000000000..1d99616b4 --- /dev/null +++ b/dev/test/types.ts @@ -0,0 +1,180 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {expect} from 'chai'; +import { + QueryDocumentSnapshot, + DocumentReference, + WithFieldValue, + DocumentData, + PartialWithFieldValue, + FirestoreDataConverter, + SetOptions, +} from '@google-cloud/firestore'; +describe('FirestoreTypeConverter', () => { + it('converter has the minimal typing information', () => { + interface MyModelType { + stringProperty: string; + numberProperty: number; + } + const converter = { + toFirestore(obj: MyModelType) { + return {a: obj.stringProperty, b: obj.numberProperty}; + }, + fromFirestore(snapshot: QueryDocumentSnapshot) { + return { + stringProperty: snapshot.data().a, + numberProperty: snapshot.data().b, + }; + }, + }; + async function _(docRef: DocumentReference): Promise { + const newDocRef = docRef.withConverter(converter); + await newDocRef.set({stringProperty: 'foo', numberProperty: 42}); + await newDocRef.update({a: 'newFoo', b: 43}); + const snapshot = await newDocRef.get(); + const data: MyModelType = snapshot.data()!; + expect(data.stringProperty).to.equal('newFoo'); + expect(data.numberProperty).to.equal(43); + } + }); + + it('converter has the minimal typing information plus return types', () => { + interface MyModelType { + stringProperty: string; + numberProperty: number; + } + const converter = { + toFirestore(obj: WithFieldValue): DocumentData { + return {a: obj.stringProperty, b: obj.numberProperty}; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): MyModelType { + return { + stringProperty: snapshot.data().a, + numberProperty: snapshot.data().b, + }; + }, + }; + async function _(docRef: DocumentReference): Promise { + const newDocRef = docRef.withConverter(converter); + await newDocRef.set({stringProperty: 'foo', numberProperty: 42}); + await newDocRef.update({a: 'newFoo', b: 43}); + const snapshot = await newDocRef.get(); + const data: MyModelType = snapshot.data()!; + expect(data.stringProperty).to.equal('newFoo'); + expect(data.numberProperty).to.equal(43); + } + }); + + it("has the additional 'merge' version of toFirestore()", () => { + interface MyModelType { + stringProperty: string; + numberProperty: number; + } + const converter = { + toFirestore( + modelObject: PartialWithFieldValue, + options?: SetOptions + ): DocumentData { + if (options === undefined) { + return { + a: modelObject.stringProperty, + b: modelObject.numberProperty, + }; + } + const result: DocumentData = {}; + if ('stringProperty' in modelObject) { + result.a = modelObject.stringProperty; + } + if ('numberProperty' in modelObject) { + result.b = modelObject.numberProperty; + } + return result; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): MyModelType { + return { + stringProperty: snapshot.data().a, + numberProperty: snapshot.data().b, + }; + }, + }; + async function _(docRef: DocumentReference): Promise { + const newDocRef = docRef.withConverter(converter); + await newDocRef.set({stringProperty: 'foo', numberProperty: 42}); + await newDocRef.update({a: 'newFoo', b: 43}); + const snapshot = await newDocRef.get(); + const data: MyModelType = snapshot.data()!; + expect(data.stringProperty).to.equal('newFoo'); + expect(data.numberProperty).to.equal(43); + } + }); + + it('converter is explicitly typed as FirestoreDataConverter', () => { + interface MyModelType { + stringProperty: string; + numberProperty: number; + } + const converter: FirestoreDataConverter = { + toFirestore(obj: WithFieldValue) { + return {a: obj.stringProperty, b: obj.numberProperty}; + }, + fromFirestore(snapshot: QueryDocumentSnapshot) { + return { + stringProperty: snapshot.data().a, + numberProperty: snapshot.data().b, + }; + }, + }; + async function _(docRef: DocumentReference): Promise { + const newDocRef = docRef.withConverter(converter); + await newDocRef.set({stringProperty: 'foo', numberProperty: 42}); + await newDocRef.update({a: 'newFoo', b: 43}); + const snapshot = await newDocRef.get(); + const data: MyModelType = snapshot.data()!; + expect(data.stringProperty).to.equal('newFoo'); + expect(data.numberProperty).to.equal(43); + } + }); + + it('converter is explicitly typed as FirestoreDataConverter', () => { + interface MyModelType { + stringProperty: string; + numberProperty: number; + } + interface MyDbType { + a: string; + b: number; + } + const converter: FirestoreDataConverter = { + toFirestore(obj: WithFieldValue) { + return {a: obj.stringProperty, b: obj.numberProperty}; + }, + fromFirestore(snapshot: QueryDocumentSnapshot) { + return { + stringProperty: snapshot.data().a, + numberProperty: snapshot.data().b, + }; + }, + }; + async function _(docRef: DocumentReference): Promise { + const newDocRef = docRef.withConverter(converter); + await newDocRef.set({stringProperty: 'foo', numberProperty: 42}); + await newDocRef.update({a: 'newFoo', b: 43}); + const snapshot = await newDocRef.get(); + const data: MyModelType = snapshot.data()!; + expect(data.stringProperty).to.equal('newFoo'); + expect(data.numberProperty).to.equal(43); + } + }); +}); diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 5f97d8e04..cf57db042 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -144,8 +144,8 @@ declare namespace FirebaseFirestore { function setLogFunction(logger: ((msg: string) => void) | null): void; /** - * Converter used by `withConverter()` to transform user objects of type T - * into Firestore data. + * Converter used by `withConverter()` to transform user objects of type + * `AppModelType` into Firestore data of type `DbModelType`. * * Using the converter allows you to specify generic type arguments when * storing and retrieving objects from Firestore. @@ -159,14 +159,19 @@ declare namespace FirebaseFirestore { * } * } * + * interface PostDbModel { + * title: string; + * author: string; + * } + * * const postConverter = { - * toFirestore(post: Post): FirebaseFirestore.DocumentData { + * toFirestore(post: Post): PostDbModel { * return {title: post.title, author: post.author}; * }, * fromFirestore( * snapshot: FirebaseFirestore.QueryDocumentSnapshot * ): Post { - * const data = snapshot.data(); + * const data = snapshot.data() as PostDbModel; * return new Post(data.title, data.author); * } * }; @@ -182,22 +187,31 @@ declare namespace FirebaseFirestore { * post.someNonExistentProperty; // TS error * } */ - export interface FirestoreDataConverter { + export interface FirestoreDataConverter< + AppModelType, + DbModelType extends DocumentData = DocumentData + > { /** - * Called by the Firestore SDK to convert a custom model object of type T - * into a plain Javascript object (suitable for writing directly to the - * Firestore database). To use set() with `merge` and `mergeFields`, + * Called by the Firestore SDK to convert a custom model object of type + * `AppModelType` into a plain Javascript object (suitable for writing + * directly to the Firestore database) of type `DbModelType`. + * + * To use set() with `merge` and `mergeFields`, * toFirestore() must be defined with `Partial`. * * The `WithFieldValue` type extends `T` to also allow FieldValues such * as `FieldValue.delete()` to be used as property values. */ - toFirestore(modelObject: WithFieldValue): DocumentData; + toFirestore( + modelObject: WithFieldValue + ): WithFieldValue; /** - * Called by the Firestore SDK to convert a custom model object of type T - * into a plain Javascript object (suitable for writing directly to the - * Firestore database). To use set() with `merge` and `mergeFields`, + * Called by the Firestore SDK to convert a custom model object of type + * `AppModelType` into a plain Javascript object (suitable for writing + * directly to the Firestore database) of type `DbModelType`. + * + * To use set() with `merge` and `mergeFields`, * toFirestore() must be defined with `Partial`. * * The `PartialWithFieldValue` type extends `Partial` to allow @@ -206,15 +220,20 @@ declare namespace FirebaseFirestore { * omitted. */ toFirestore( - modelObject: PartialWithFieldValue, + modelObject: PartialWithFieldValue, options: SetOptions - ): DocumentData; + ): PartialWithFieldValue; /** * Called by the Firestore SDK to convert Firestore data into an object of - * type T. + * type `AppModelType`. You can access your data by calling: + * `snapshot.data()`. + * + * Generally, the data returned from `snapshot.data()` can be cast to + * `DbModelType`; however, this is not guaranteed as writes to the database + * may have occurred without a type converter enforcing this specific layout. */ - fromFirestore(snapshot: QueryDocumentSnapshot): T; + fromFirestore(snapshot: QueryDocumentSnapshot): AppModelType; } /** @@ -354,7 +373,7 @@ declare namespace FirebaseFirestore { * @param collectionPath A slash-separated path to a collection. * @return The `CollectionReference` instance. */ - collection(collectionPath: string): CollectionReference; + collection(collectionPath: string): CollectionReference; /** * Gets a `DocumentReference` instance that refers to the document at the @@ -363,7 +382,7 @@ declare namespace FirebaseFirestore { * @param documentPath A slash-separated path to a document. * @return The `DocumentReference` instance. */ - doc(documentPath: string): DocumentReference; + doc(documentPath: string): DocumentReference; /** * Creates and returns a new Query that includes all documents in the @@ -375,7 +394,7 @@ declare namespace FirebaseFirestore { * will be included. Cannot contain a slash. * @return The created `CollectionGroup`. */ - collectionGroup(collectionId: string): CollectionGroup; + collectionGroup(collectionId: string): CollectionGroup; /** * Retrieves multiple documents from Firestore. @@ -391,10 +410,8 @@ declare namespace FirebaseFirestore { * snapshots. */ getAll( - ...documentRefsOrReadOptions: Array< - DocumentReference | ReadOptions - > - ): Promise>>; + ...documentRefsOrReadOptions: Array + ): Promise>; /** * Recursively deletes all documents and subcollections at and under the @@ -432,7 +449,7 @@ declare namespace FirebaseFirestore { * await firestore.recursiveDelete(docRef, bulkWriter); */ recursiveDelete( - ref: CollectionReference | DocumentReference, + ref: CollectionReference | DocumentReference, bulkWriter?: BulkWriter ): Promise; @@ -449,7 +466,7 @@ declare namespace FirebaseFirestore { * * @returns A Promise that resolves with an array of CollectionReferences. */ - listCollections(): Promise>>; + listCollections(): Promise>; /** * Executes the given updateFunction and commits the changes applied within @@ -580,7 +597,9 @@ declare namespace FirebaseFirestore { * @param query A query to execute. * @return A QuerySnapshot for the retrieved data. */ - get(query: Query): Promise>; + get( + query: Query + ): Promise>; /** * Reads the document referenced by the provided `DocumentReference.` @@ -589,7 +608,9 @@ declare namespace FirebaseFirestore { * @param documentRef A reference to the document to be read. * @return A DocumentSnapshot for the read data. */ - get(documentRef: DocumentReference): Promise>; + get( + documentRef: DocumentReference + ): Promise>; /** * Retrieves an aggregate query result. Holds a pessimistic lock on all @@ -598,9 +619,19 @@ declare namespace FirebaseFirestore { * @param aggregateQuery An aggregate query to execute. * @return An AggregateQuerySnapshot for the retrieved data. */ - get( - aggregateQuery: AggregateQuery - ): Promise>; + get< + AppModelType, + DbModelType extends DocumentData, + AggregateSpecType extends AggregateSpec + >( + aggregateQuery: AggregateQuery< + AggregateSpecType, + AppModelType, + DbModelType + > + ): Promise< + AggregateQuerySnapshot + >; /** * Retrieves multiple documents from Firestore. Holds a pessimistic lock on @@ -616,9 +647,11 @@ declare namespace FirebaseFirestore { * @return A Promise that resolves with an array of resulting document * snapshots. */ - getAll( - ...documentRefsOrReadOptions: Array | ReadOptions> - ): Promise>>; + getAll( + ...documentRefsOrReadOptions: Array< + DocumentReference | ReadOptions + > + ): Promise>>; /** * Create the document referred to by the provided `DocumentReference`. @@ -630,9 +663,9 @@ declare namespace FirebaseFirestore { * @throws Error If the provided input is not a valid Firestore document. * @return This `Transaction` instance. Used for chaining method calls. */ - create( - documentRef: DocumentReference, - data: WithFieldValue + create( + documentRef: DocumentReference, + data: WithFieldValue ): Transaction; /** @@ -654,14 +687,14 @@ declare namespace FirebaseFirestore { * @throws Error If the provided input is not a valid Firestore document. * @return This `Transaction` instance. Used for chaining method calls. */ - set( - documentRef: DocumentReference, - data: PartialWithFieldValue, + set( + documentRef: DocumentReference, + data: PartialWithFieldValue, options: SetOptions ): Transaction; - set( - documentRef: DocumentReference, - data: WithFieldValue + set( + documentRef: DocumentReference, + data: WithFieldValue ): Transaction; /** @@ -679,9 +712,9 @@ declare namespace FirebaseFirestore { * @throws Error If the provided input is not valid Firestore data. * @return This `Transaction` instance. Used for chaining method calls. */ - update( - documentRef: DocumentReference, - data: UpdateData, + update( + documentRef: DocumentReference, + data: UpdateData, precondition?: Precondition ): Transaction; @@ -706,7 +739,7 @@ declare namespace FirebaseFirestore { * @return This `Transaction` instance. Used for chaining method calls. */ update( - documentRef: DocumentReference, + documentRef: DocumentReference, field: string | FieldPath, value: any, ...fieldsOrPrecondition: any[] @@ -720,7 +753,7 @@ declare namespace FirebaseFirestore { * @return This `Transaction` instance. Used for chaining method calls. */ delete( - documentRef: DocumentReference, + documentRef: DocumentReference, precondition?: Precondition ): Transaction; } @@ -746,9 +779,9 @@ declare namespace FirebaseFirestore { * write fails, the promise is rejected with a * [BulkWriterError]{@link BulkWriterError}. */ - create( - documentRef: DocumentReference, - data: WithFieldValue + create( + documentRef: DocumentReference, + data: WithFieldValue ): Promise; /** @@ -768,7 +801,7 @@ declare namespace FirebaseFirestore { * [BulkWriterError]{@link BulkWriterError}. */ delete( - documentRef: DocumentReference, + documentRef: DocumentReference, precondition?: Precondition ): Promise; @@ -796,14 +829,14 @@ declare namespace FirebaseFirestore { * write fails, the promise is rejected with a * [BulkWriterError]{@link BulkWriterError}. */ - set( - documentRef: DocumentReference, - data: PartialWithFieldValue, + set( + documentRef: DocumentReference, + data: PartialWithFieldValue, options: SetOptions ): Promise; - set( - documentRef: DocumentReference, - data: WithFieldValue + set( + documentRef: DocumentReference, + data: WithFieldValue ): Promise; /** @@ -830,9 +863,9 @@ declare namespace FirebaseFirestore { * write fails, the promise is rejected with a * [BulkWriterError]{@link BulkWriterError}. */ - update( - documentRef: DocumentReference, - data: UpdateData, + update( + documentRef: DocumentReference, + data: UpdateData, precondition?: Precondition ): Promise; @@ -863,7 +896,7 @@ declare namespace FirebaseFirestore { * [BulkWriterError]{@link BulkWriterError}. */ update( - documentRef: DocumentReference, + documentRef: DocumentReference, field: string | FieldPath, value: any, ...fieldsOrPrecondition: any[] @@ -878,7 +911,7 @@ declare namespace FirebaseFirestore { */ onWriteResult( callback: ( - documentRef: DocumentReference, + documentRef: DocumentReference, result: WriteResult ) => void ): void; @@ -971,7 +1004,7 @@ declare namespace FirebaseFirestore { readonly message: string; /** The document reference the operation was performed on. */ - readonly documentRef: DocumentReference; + readonly documentRef: DocumentReference; /** The type of operation performed. */ readonly operationType: 'create' | 'set' | 'update' | 'delete'; @@ -1004,9 +1037,9 @@ declare namespace FirebaseFirestore { * @throws Error If the provided input is not a valid Firestore document. * @return This `WriteBatch` instance. Used for chaining method calls. */ - create( - documentRef: DocumentReference, - data: WithFieldValue + create( + documentRef: DocumentReference, + data: WithFieldValue ): WriteBatch; /** @@ -1028,14 +1061,14 @@ declare namespace FirebaseFirestore { * @throws Error If the provided input is not a valid Firestore document. * @return This `WriteBatch` instance. Used for chaining method calls. */ - set( - documentRef: DocumentReference, - data: PartialWithFieldValue, + set( + documentRef: DocumentReference, + data: PartialWithFieldValue, options: SetOptions ): WriteBatch; - set( - documentRef: DocumentReference, - data: WithFieldValue + set( + documentRef: DocumentReference, + data: WithFieldValue ): WriteBatch; /** @@ -1053,9 +1086,9 @@ declare namespace FirebaseFirestore { * @throws Error If the provided input is not valid Firestore data. * @return This `WriteBatch` instance. Used for chaining method calls. */ - update( - documentRef: DocumentReference, - data: UpdateData, + update( + documentRef: DocumentReference, + data: UpdateData, precondition?: Precondition ): WriteBatch; @@ -1080,7 +1113,7 @@ declare namespace FirebaseFirestore { * @return This `WriteBatch` instance. Used for chaining method calls. */ update( - documentRef: DocumentReference, + documentRef: DocumentReference, field: string | FieldPath, value: any, ...fieldsOrPrecondition: any[] @@ -1094,7 +1127,7 @@ declare namespace FirebaseFirestore { * @return This `WriteBatch` instance. Used for chaining method calls. */ delete( - documentRef: DocumentReference, + documentRef: DocumentReference, precondition?: Precondition ): WriteBatch; @@ -1193,7 +1226,10 @@ declare namespace FirebaseFirestore { * the referenced location may or may not exist. A `DocumentReference` can * also be used to create a `CollectionReference` to a subcollection. */ - export class DocumentReference { + export class DocumentReference< + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > { private constructor(); /** The identifier of the document within its collection. */ @@ -1208,7 +1244,7 @@ declare namespace FirebaseFirestore { /** * A reference to the Collection to which this DocumentReference belongs. */ - readonly parent: CollectionReference; + readonly parent: CollectionReference; /** * A string representing the path of the referenced document (relative @@ -1223,14 +1259,14 @@ declare namespace FirebaseFirestore { * @param collectionPath A slash-separated path to a collection. * @return The `CollectionReference` instance. */ - collection(collectionPath: string): CollectionReference; + collection(collectionPath: string): CollectionReference; /** * Fetches the subcollections that are direct children of this document. * * @returns A Promise that resolves with an array of CollectionReferences. */ - listCollections(): Promise>>; + listCollections(): Promise>; /** * Creates a document referred to by this `DocumentReference` with the @@ -1240,7 +1276,7 @@ declare namespace FirebaseFirestore { * @throws Error If the provided input is not a valid Firestore document. * @return A Promise resolved with the write time of this create. */ - create(data: WithFieldValue): Promise; + create(data: WithFieldValue): Promise; /** * Writes to the document referred to by this `DocumentReference`. If the @@ -1261,10 +1297,10 @@ declare namespace FirebaseFirestore { * @return A Promise resolved with the write time of this set. */ set( - data: PartialWithFieldValue, + data: PartialWithFieldValue, options: SetOptions ): Promise; - set(data: WithFieldValue): Promise; + set(data: WithFieldValue): Promise; /** * Updates fields in the document referred to by this `DocumentReference`. @@ -1280,7 +1316,7 @@ declare namespace FirebaseFirestore { * @return A Promise resolved with the write time of this update. */ update( - data: UpdateData, + data: UpdateData, precondition?: Precondition ): Promise; @@ -1322,7 +1358,7 @@ declare namespace FirebaseFirestore { * @return A Promise resolved with a DocumentSnapshot containing the * current document contents. */ - get(): Promise>; + get(): Promise>; /** * Attaches a listener for DocumentSnapshot events. @@ -1335,7 +1371,7 @@ declare namespace FirebaseFirestore { * the snapshot listener. */ onSnapshot( - onNext: (snapshot: DocumentSnapshot) => void, + onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: Error) => void ): () => void; @@ -1345,23 +1381,26 @@ declare namespace FirebaseFirestore { * @param other The `DocumentReference` to compare against. * @return true if this `DocumentReference` is equal to the provided one. */ - isEqual(other: DocumentReference): boolean; + isEqual(other: DocumentReference): boolean; /** * Applies a custom data converter to this DocumentReference, allowing you * to use your own custom model objects with Firestore. When you call * set(), get(), etc. on the returned DocumentReference instance, the - * provided converter will convert between Firestore data and your custom - * type U. + * provided converter will convert between Firestore data of type + * `NewDbModelType` and your custom type `NewAppModelType`. * * @param converter Converts objects to and from Firestore. Passing in * `null` removes the current converter. * @return A DocumentReference that uses the provided converter. */ - withConverter( - converter: FirestoreDataConverter - ): DocumentReference; - withConverter(converter: null): DocumentReference; + withConverter< + NewAppModelType, + NewDbModelType extends DocumentData = DocumentData + >( + converter: FirestoreDataConverter + ): DocumentReference; + withConverter(converter: null): DocumentReference; } /** @@ -1373,14 +1412,17 @@ declare namespace FirebaseFirestore { * access will return 'undefined'. You can use the `exists` property to * explicitly verify a document's existence. */ - export class DocumentSnapshot { + export class DocumentSnapshot< + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > { protected constructor(); /** True if the document exists. */ readonly exists: boolean; /** A `DocumentReference` to the document location. */ - readonly ref: DocumentReference; + readonly ref: DocumentReference; /** * The ID of the document for which this `DocumentSnapshot` contains data. @@ -1410,7 +1452,7 @@ declare namespace FirebaseFirestore { * * @return An Object containing all fields in the document. */ - data(): T | undefined; + data(): AppModelType | undefined; /** * Retrieves the field specified by `fieldPath`. @@ -1428,7 +1470,7 @@ declare namespace FirebaseFirestore { * @param other The `DocumentSnapshot` to compare against. * @return true if this `DocumentSnapshot` is equal to the provided one. */ - isEqual(other: DocumentSnapshot): boolean; + isEqual(other: DocumentSnapshot): boolean; } /** @@ -1443,8 +1485,9 @@ declare namespace FirebaseFirestore { * 'undefined'. */ export class QueryDocumentSnapshot< - T = DocumentData - > extends DocumentSnapshot { + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > extends DocumentSnapshot { private constructor(); /** @@ -1464,7 +1507,7 @@ declare namespace FirebaseFirestore { * @override * @return An Object containing all fields in the document. */ - data(): T; + data(): AppModelType; } /** @@ -1494,7 +1537,10 @@ declare namespace FirebaseFirestore { * A `Query` refers to a Query which you can read or listen to. You can also * construct refined `Query` objects by adding filters and ordering. */ - export class Query { + export class Query< + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > { protected constructor(); /** @@ -1520,7 +1566,7 @@ declare namespace FirebaseFirestore { fieldPath: string | FieldPath, opStr: WhereFilterOp, value: any - ): Query; + ): Query; /** * Creates and returns a new [Query]{@link Query} with the additional filter @@ -1533,7 +1579,7 @@ declare namespace FirebaseFirestore { * @param {Filter} filter A filter to apply to the Query. * @returns {Query} The created Query. */ - where(filter: Filter): Query; + where(filter: Filter): Query; /** * Creates and returns a new Query that's additionally sorted by the @@ -1550,7 +1596,7 @@ declare namespace FirebaseFirestore { orderBy( fieldPath: string | FieldPath, directionStr?: OrderByDirection - ): Query; + ): Query; /** * Creates and returns a new Query that only returns the first matching @@ -1562,7 +1608,7 @@ declare namespace FirebaseFirestore { * @param limit The maximum number of items to return. * @return The created Query. */ - limit(limit: number): Query; + limit(limit: number): Query; /** * Creates and returns a new Query that only returns the last matching @@ -1577,7 +1623,7 @@ declare namespace FirebaseFirestore { * @param limit The maximum number of items to return. * @return The created Query. */ - limitToLast(limit: number): Query; + limitToLast(limit: number): Query; /** * Specifies the offset of the returned results. @@ -1588,7 +1634,7 @@ declare namespace FirebaseFirestore { * @param offset The offset to apply to the Query results. * @return The created Query. */ - offset(offset: number): Query; + offset(offset: number): Query; /** * Creates and returns a new Query instance that applies a field mask to @@ -1605,7 +1651,7 @@ declare namespace FirebaseFirestore { * @param field The field paths to return. * @return The created Query. */ - select(...field: (string | FieldPath)[]): Query; + select(...field: (string | FieldPath)[]): Query; /** * Creates and returns a new Query that starts at the provided document @@ -1616,7 +1662,9 @@ declare namespace FirebaseFirestore { * @param snapshot The snapshot of the document to start after. * @return The created Query. */ - startAt(snapshot: DocumentSnapshot): Query; + startAt( + snapshot: DocumentSnapshot + ): Query; /** * Creates and returns a new Query that starts at the provided fields @@ -1627,7 +1675,7 @@ declare namespace FirebaseFirestore { * of the query's order by. * @return The created Query. */ - startAt(...fieldValues: any[]): Query; + startAt(...fieldValues: any[]): Query; /** * Creates and returns a new Query that starts after the provided document @@ -1638,7 +1686,9 @@ declare namespace FirebaseFirestore { * @param snapshot The snapshot of the document to start after. * @return The created Query. */ - startAfter(snapshot: DocumentSnapshot): Query; + startAfter( + snapshot: DocumentSnapshot + ): Query; /** * Creates and returns a new Query that starts after the provided fields @@ -1649,7 +1699,7 @@ declare namespace FirebaseFirestore { * of the query's order by. * @return The created Query. */ - startAfter(...fieldValues: any[]): Query; + startAfter(...fieldValues: any[]): Query; /** * Creates and returns a new Query that ends before the provided document @@ -1660,7 +1710,9 @@ declare namespace FirebaseFirestore { * @param snapshot The snapshot of the document to end before. * @return The created Query. */ - endBefore(snapshot: DocumentSnapshot): Query; + endBefore( + snapshot: DocumentSnapshot + ): Query; /** * Creates and returns a new Query that ends before the provided fields @@ -1671,7 +1723,7 @@ declare namespace FirebaseFirestore { * of the query's order by. * @return The created Query. */ - endBefore(...fieldValues: any[]): Query; + endBefore(...fieldValues: any[]): Query; /** * Creates and returns a new Query that ends at the provided document @@ -1682,7 +1734,9 @@ declare namespace FirebaseFirestore { * @param snapshot The snapshot of the document to end at. * @return The created Query. */ - endAt(snapshot: DocumentSnapshot): Query; + endAt( + snapshot: DocumentSnapshot + ): Query; /** * Creates and returns a new Query that ends at the provided fields @@ -1693,14 +1747,14 @@ declare namespace FirebaseFirestore { * of the query's order by. * @return The created Query. */ - endAt(...fieldValues: any[]): Query; + endAt(...fieldValues: any[]): Query; /** * Executes the query and returns the results as a `QuerySnapshot`. * * @return A Promise that will be resolved with the results of the Query. */ - get(): Promise>; + get(): Promise>; /* * Executes the query and returns the results as Node Stream. @@ -1720,7 +1774,7 @@ declare namespace FirebaseFirestore { * the snapshot listener. */ onSnapshot( - onNext: (snapshot: QuerySnapshot) => void, + onNext: (snapshot: QuerySnapshot) => void, onError?: (error: Error) => void ): () => void; @@ -1741,7 +1795,11 @@ declare namespace FirebaseFirestore { * `snapshot` is the `AggregateQuerySnapshot` resulting from running the * returned query. */ - count(): AggregateQuery<{count: AggregateField}>; + count(): AggregateQuery< + {count: AggregateField}, + AppModelType, + DbModelType + >; /** * Returns true if this `Query` is equal to the provided one. @@ -1749,20 +1807,25 @@ declare namespace FirebaseFirestore { * @param other The `Query` to compare against. * @return true if this `Query` is equal to the provided one. */ - isEqual(other: Query): boolean; + isEqual(other: Query): boolean; /** * Applies a custom data converter to this Query, allowing you to use your * own custom model objects with Firestore. When you call get() on the * returned Query, the provided converter will convert between Firestore - * data and your custom type U. + * data of type `NewDbModelType` and your custom type `NewAppModelType`. * * @param converter Converts objects to and from Firestore. Passing in * `null` removes the current converter. - * @return A Query that uses the provided converter. - */ - withConverter(converter: FirestoreDataConverter): Query; - withConverter(converter: null): Query; + * @return A Query that uses the provided converter. + */ + withConverter< + NewAppModelType, + NewDbModelType extends DocumentData = DocumentData + >( + converter: FirestoreDataConverter + ): Query; + withConverter(converter: null): Query; } /** @@ -1772,17 +1835,20 @@ declare namespace FirebaseFirestore { * number of documents can be determined via the `empty` and `size` * properties. */ - export class QuerySnapshot { + export class QuerySnapshot< + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > { private constructor(); /** * The query on which you called `get` or `onSnapshot` in order to get this * `QuerySnapshot`. */ - readonly query: Query; + readonly query: Query; /** An array of all the documents in the QuerySnapshot. */ - readonly docs: Array>; + readonly docs: Array>; /** The number of documents in the QuerySnapshot. */ readonly size: number; @@ -1798,7 +1864,7 @@ declare namespace FirebaseFirestore { * this is the first snapshot, all documents will be in the list as added * changes. */ - docChanges(): DocumentChange[]; + docChanges(): DocumentChange[]; /** * Enumerates all of the documents in the QuerySnapshot. @@ -1808,7 +1874,9 @@ declare namespace FirebaseFirestore { * @param thisArg The `this` binding for the callback. */ forEach( - callback: (result: QueryDocumentSnapshot) => void, + callback: ( + result: QueryDocumentSnapshot + ) => void, thisArg?: any ): void; @@ -1819,11 +1887,11 @@ declare namespace FirebaseFirestore { * @param other The `QuerySnapshot` to compare against. * @return true if this `QuerySnapshot` is equal to the provided one. */ - isEqual(other: QuerySnapshot): boolean; + isEqual(other: QuerySnapshot): boolean; } /** - * The type of of a `DocumentChange` may be 'added', 'removed', or 'modified'. + * The type of `DocumentChange` may be 'added', 'removed', or 'modified'. */ export type DocumentChangeType = 'added' | 'removed' | 'modified'; @@ -1831,12 +1899,15 @@ declare namespace FirebaseFirestore { * A `DocumentChange` represents a change to the documents matching a query. * It contains the document affected and the type of change that occurred. */ - export interface DocumentChange { + export interface DocumentChange< + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > { /** The type of change ('added', 'modified', or 'removed'). */ readonly type: DocumentChangeType; /** The document affected by this change. */ - readonly doc: QueryDocumentSnapshot; + readonly doc: QueryDocumentSnapshot; /** * The index of the changed document in the result set immediately prior to @@ -1860,7 +1931,7 @@ declare namespace FirebaseFirestore { * @param other The `DocumentChange` to compare against. * @return true if this `DocumentChange` is equal to the provided one. */ - isEqual(other: DocumentChange): boolean; + isEqual(other: DocumentChange): boolean; } /** @@ -1868,7 +1939,10 @@ declare namespace FirebaseFirestore { * document references, and querying for documents (using the methods * inherited from `Query`). */ - export class CollectionReference extends Query { + export class CollectionReference< + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > extends Query { private constructor(); /** The identifier of the collection. */ @@ -1878,7 +1952,7 @@ declare namespace FirebaseFirestore { * A reference to the containing Document if this is a subcollection, else * null. */ - readonly parent: DocumentReference | null; + readonly parent: DocumentReference | null; /** * A string representing the path of the referenced collection (relative @@ -1898,7 +1972,9 @@ declare namespace FirebaseFirestore { * @return {Promise} The list of documents in this * collection. */ - listDocuments(): Promise>>; + listDocuments(): Promise< + Array> + >; /** * Get a `DocumentReference` for a randomly-named document within this @@ -1907,7 +1983,7 @@ declare namespace FirebaseFirestore { * * @return The `DocumentReference` instance. */ - doc(): DocumentReference; + doc(): DocumentReference; /** * Get a `DocumentReference` for the document within the collection at the @@ -1916,7 +1992,7 @@ declare namespace FirebaseFirestore { * @param documentPath A slash-separated path to a document. * @return The `DocumentReference` instance. */ - doc(documentPath: string): DocumentReference; + doc(documentPath: string): DocumentReference; /** * Add a new document to this collection with the specified data, assigning @@ -1927,7 +2003,9 @@ declare namespace FirebaseFirestore { * @return A Promise resolved with a `DocumentReference` pointing to the * newly created document after it has been written to the backend. */ - add(data: WithFieldValue): Promise>; + add( + data: WithFieldValue + ): Promise>; /** * Returns true if this `CollectionReference` is equal to the provided one. @@ -1935,29 +2013,36 @@ declare namespace FirebaseFirestore { * @param other The `CollectionReference` to compare against. * @return true if this `CollectionReference` is equal to the provided one. */ - isEqual(other: CollectionReference): boolean; + isEqual(other: CollectionReference): boolean; /** * Applies a custom data converter to this CollectionReference, allowing you * to use your own custom model objects with Firestore. When you call add() * on the returned CollectionReference instance, the provided converter will - * convert between Firestore data and your custom type U. + * convert between Firestore data of type `NewDbModelType` and your custom + * type `NewAppModelType`. * * @param converter Converts objects to and from Firestore. Passing in * `null` removes the current converter. - * @return A CollectionReference that uses the provided converter. - */ - withConverter( - converter: FirestoreDataConverter - ): CollectionReference; - withConverter(converter: null): CollectionReference; + * @return A CollectionReference that uses the provided converter. + */ + withConverter< + NewAppModelType, + NewDbModelType extends DocumentData = DocumentData + >( + converter: FirestoreDataConverter + ): CollectionReference; + withConverter(converter: null): CollectionReference; } /** * A `CollectionGroup` refers to all documents that are contained in a * collection or subcollection with a specific collection ID. */ - export class CollectionGroup extends Query { + export class CollectionGroup< + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > extends Query { private constructor(); /** @@ -1972,13 +2057,14 @@ declare namespace FirebaseFirestore { */ getPartitions( desiredPartitionCount: number - ): AsyncIterable>; + ): AsyncIterable>; /** * Applies a custom data converter to this `CollectionGroup`, allowing you * to use your own custom model objects with Firestore. When you call get() * on the returned `CollectionGroup`, the provided converter will convert - * between Firestore data and your custom type U. + * between Firestore data of type `NewDbModelType` and your custom type + * `NewAppModelType`. * * Using the converter allows you to specify generic type arguments when * storing and retrieving objects from Firestore. @@ -2017,10 +2103,15 @@ declare namespace FirebaseFirestore { * * @param converter Converts objects to and from Firestore. Passing in * `null` removes the current converter. - * @return A `CollectionGroup` that uses the provided converter. - */ - withConverter(converter: FirestoreDataConverter): CollectionGroup; - withConverter(converter: null): CollectionGroup; + * @return A `CollectionGroup` that uses the provided converter. + */ + withConverter< + NewAppModelType, + NewDbModelType extends DocumentData = DocumentData + >( + converter: FirestoreDataConverter + ): CollectionGroup; + withConverter(converter: null): CollectionGroup; } /** @@ -2029,7 +2120,10 @@ declare namespace FirebaseFirestore { * #endBefore} can only be used in a query that matches the constraint of query * that produced this partition. */ - export class QueryPartition { + export class QueryPartition< + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > { private constructor(); /** @@ -2060,7 +2154,7 @@ declare namespace FirebaseFirestore { * @return A query partitioned by a {@link Query#startAt} and {@link * Query#endBefore} cursor. */ - toQuery(): Query; + toQuery(): Query; } /** @@ -2095,18 +2189,24 @@ declare namespace FirebaseFirestore { /** * A query that calculates aggregations over an underlying query. */ - export class AggregateQuery { + export class AggregateQuery< + AggregateSpecType extends AggregateSpec, + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > { private constructor(); /** The query whose aggregations will be calculated by this object. */ - readonly query: Query; + readonly query: Query; /** * Executes this query. * * @return A promise that will be resolved with the results of the query. */ - get(): Promise>; + get(): Promise< + AggregateQuerySnapshot + >; /** * Compares this object with the given object for equality. @@ -2120,17 +2220,27 @@ declare namespace FirebaseFirestore { * @return `true` if this object is "equal" to the given object, as * defined above, or `false` otherwise. */ - isEqual(other: AggregateQuery): boolean; + isEqual( + other: AggregateQuery + ): boolean; } /** * The results of executing an aggregation query. */ - export class AggregateQuerySnapshot { + export class AggregateQuerySnapshot< + AggregateSpecType extends AggregateSpec, + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData + > { private constructor(); /** The query that was executed to produce this result. */ - readonly query: AggregateQuery; + readonly query: AggregateQuery< + AggregateSpecType, + AppModelType, + DbModelType + >; /** The time this snapshot was read. */ readonly readTime: Timestamp; @@ -2146,7 +2256,7 @@ declare namespace FirebaseFirestore { * @returns The results of the aggregations performed over the underlying * query. */ - data(): AggregateSpecData; + data(): AggregateSpecData; /** * Compares this object with the given object for equality. @@ -2159,7 +2269,13 @@ declare namespace FirebaseFirestore { * @return `true` if this object is "equal" to the given object, as * defined above, or `false` otherwise. */ - isEqual(other: AggregateQuerySnapshot): boolean; + isEqual( + other: AggregateQuerySnapshot< + AggregateSpecType, + AppModelType, + DbModelType + > + ): boolean; } /** @@ -2377,7 +2493,9 @@ declare namespace FirebaseFirestore { * @param documentSnapshot A `DocumentSnapshot` to add. * @returns This instance. */ - add(documentSnapshot: DocumentSnapshot): BundleBuilder; + add( + documentSnapshot: DocumentSnapshot + ): BundleBuilder; /** * Adds a Firestore `QuerySnapshot` to the bundle. Both the documents in the query results and @@ -2387,7 +2505,10 @@ declare namespace FirebaseFirestore { * @param querySnapshot A `QuerySnapshot` to add to the bundle. * @returns This instance. */ - add(queryName: string, querySnapshot: QuerySnapshot): BundleBuilder; + add( + queryName: string, + querySnapshot: QuerySnapshot + ): BundleBuilder; /** * Builds the bundle and returns the result as a `Buffer` instance.