import { from , Observable } from 'rxjs' ; import { filter , map , pairwise , scan , startWith } from 'rxjs/operators' ; import firebase from 'firebase/compat/app' ; import { keepUnstableUntilFirst } from '@angular/fire' ; import { CollectionReference , DocumentChangeAction , DocumentChangeType , DocumentData , DocumentReference , Query } from '../interfaces' ; import { docChanges , sortedChanges } from './changes' ; import { AngularFirestoreDocument } from '../document/document' ; import { fromCollectionRef } from '../observable/fromRef' ; import { AngularFirestore } from '../firestore' ; export function validateEventsArray ( events ? ) { if ( ! events || events . length === 0 ) { events = [ 'added' , 'removed' , 'modified' ] ; } return events ; } /**
 * AngularFirestoreCollection service
 *
 * This class creates a reference to a Firestore Collection. A reference and a query are provided in
 * in the constructor. The query can be the unqueried reference if no query is desired.The class
 * is generic which gives you type safety for data update methods and data streaming.
 *
 * This class uses Symbol.observable to transform into Observable using Observable.from().
 *
 * This class is rarely used directly and should be created from the AngularFirestore service.
 *
 * Example:
 *
 * const collectionRef = firebase.firestore.collection('stocks');
 * const query = collectionRef.where('price', '>', '0.01');
 * const fakeStock = new AngularFirestoreCollection<Stock>(collectionRef, query);
 *
 * // NOTE!: the updates are performed on the reference not the query
 * await fakeStock.add({ name: 'FAKE', price: 0.01 });
 *
 * // Subscribe to changes as snapshots. This provides you data updates as well as delta updates.
 * fakeStock.valueChanges().subscribe(value => console.log(value));
 */ export class AngularFirestoreCollection < T = DocumentData > { /**
     * The constructor takes in a CollectionReference and Query to provide wrapper methods
     * for data operations and data streaming.
     *
     * Note: Data operation methods are done on the reference not the query. This means
     * when you update data it is not updating data to the window of your query unless
     * the data fits the criteria of the query. See the AssociatedRefence type for details
     * on this implication.
     */ constructor ( public readonly ref , private readonly query , private readonly afs ) { } /**
     * Listen to the latest change in the stream. This method returns changes
     * as they occur and they are not sorted by query order. This allows you to construct
     * your own data structure.
     */ stateChanges ( events ? ) { let source = docChanges < T > ( this . query , this . afs . schedulers . outsideAngular ) ; if ( events && events . length > 0 ) { source = source . pipe ( map ( actions => actions . filter ( change => events . indexOf ( change . type ) > - 1 ) ) ) ; } return source . pipe ( startWith < DocumentChangeAction < T > [ ] , undefined > ( undefined ) , pairwise ( ) , filter ( ( [ prior , current ] ) => current . length > 0 || ! prior ) , map ( ( [ prior , current ] ) => current ) , keepUnstableUntilFirst ) ; } /**
     * Create a stream of changes as they occur it time. This method is similar to stateChanges()
     * but it collects each event in an array over time.
     */ auditTrail ( events ? ) { return this . stateChanges ( events ) . pipe ( scan ( ( current , action ) => [ ... current , ... action ] , [ ] ) ) ; } /**
     * Create a stream of synchronized changes. This method keeps the local array in sorted
     * query order.
     */ snapshotChanges ( events ? ) { const validatedEvents = validateEventsArray ( events ) ; const scheduledSortedChanges$ = sortedChanges < T > ( this . query , validatedEvents , this . afs . schedulers . outsideAngular ) ; return scheduledSortedChanges$ . pipe ( keepUnstableUntilFirst ) ; } /**
     * Listen to all documents in the collection and its possible query as an Observable.
     *
     * If the `idField` option is provided, document IDs are included and mapped to the
     * provided `idField` property name.
     */ valueChanges ( ) ; valueChanges ( { } ) ; valueChanges < K extends string > ( options ) ; valueChanges < K extends string > ( options = { } ) { return fromCollectionRef < T > ( this . query , this . afs . schedulers . outsideAngular ) . pipe ( map ( actions => actions . payload . docs . map ( a => { if ( options . idField ) { return { ... a . data ( ) as { } , ... { [ options . idField ] : a . id } } as T & { [ T in K ] : string ; } ; } else { return a . data ( ) ; } } ) ) , keepUnstableUntilFirst ) ; } /**
     * Retrieve the results of the query once.
     */ get ( options ? ) { return from ( this . query . get ( options ) ) . pipe ( keepUnstableUntilFirst ) ; } /**
     * Add data to a collection reference.
     *
     * Note: Data operation methods are done on the reference not the query. This means
     * when you update data it is not updating data to the window of your query unless
     * the data fits the criteria of the query.
     */ add ( data ) { return this . ref . add ( data ) ; } /**
     * Create a reference to a single document in a collection.
     */ doc < T2 = T > ( path ? ) { return new AngularFirestoreDocument ( this . ref . doc ( path ) as any , this . afs ) ; } }