import { Inject , Injectable , PLATFORM_ID } from '@angular/core' ; import { AngularFireLiteApp } from '../core.service' ; import { HttpClient } from '@angular/common/http' ; import { FirebaseAppConfig } from '../core.config' ; import { makeStateKey , TransferState } from '@angular/platform-browser' ; import { isPlatformBrowser , isPlatformServer } from '@angular/common' ; import { BehaviorSubject , from , Observable , of , Subject } from 'rxjs' ; import { map , tap } from 'rxjs/operators' ; import { FirebaseFirestore } from '@firebase/firestore-types' ; import 'firebase/firestore' ; @ Injectable ( ) export class AngularFireLiteFirestore { private readonly firestore : FirebaseFirestore ; private readonly config : FirebaseAppConfig ; private readonly browser = isPlatformBrowser ( this . platformId ) ; constructor ( private app , private http , private state , @ Inject ( PLATFORM_ID ) private platformId ) { this . firestore = app . instance ( ) . firestore ( ) ; this . config = app . config ( ) ; } read ( ref ) { const dataStateKey = makeStateKey < Object > ( ref + 'firestore:read' ) ; const refArray = ref . split ( '/' ) ; if ( refArray [ 0 ] === '/' ) { refArray . shift ( ) ; } if ( refArray [ refArray . length - 1 ] === '/' ) { refArray . pop ( ) ; } const slashes = refArray . length - 1 ; if ( isPlatformServer ( this . platformId ) ) { return this . http . get ( `https://firestore.googleapis.com/v1beta1/projects/${ this . config . projectId }/databases/(default)/documents/${ ref }` ) . pipe ( map ( ( res ) => { const docData = { } ; if ( slashes % 2 !== 0 ) { Object . keys ( res . fields ) . forEach ( ( key ) => { for ( const keyValue in res . fields [ key ] ) { if ( keyValue ) { docData [ key ] = res . fields [ key ] [ keyValue ] ; } } } ) ; return docData ; } else { const colData = [ ] ; res . documents . forEach ( ( doc ) => { const singleDocData = { } ; Object . keys ( doc . fields ) . forEach ( ( key ) => { for ( const keyValue in doc . fields [ key ] ) { if ( keyValue ) { singleDocData [ key ] = doc . fields [ key ] [ keyValue ] ; } } } ) ; colData . push ( singleDocData ) ; } ) ; return colData ; } } ) , tap ( ( pl ) => { this . state . set ( dataStateKey , pl ) ; } ) ) ; } if ( this . browser ) { const data = [ ] ; const SSRedValue = this . state . get ( dataStateKey , [ ] ) ; const DATA = new BehaviorSubject < any > ( SSRedValue ) ; if ( slashes % 2 === 0 ) { this . firestore . collection ( ref ) . onSnapshot ( ( snapshot ) => { snapshot . docs . forEach ( ( doc ) => { data . push ( doc . data ( ) ) ; } ) ; DATA . next ( data ) ; } ) ; } else { this . firestore . doc ( ref ) . onSnapshot ( ( snapshot ) => { DATA . next ( snapshot . data ( ) ) ; } ) ; } return DATA ; } } write ( ref , data , merge ? ) { if ( this . browser ) { return from ( this . firestore . doc ( ref ) . set ( data , { merge : merge } ) ) ; } } push ( ref , data ) { if ( this . browser ) { return from ( this . firestore . collection ( ref ) . add ( data ) ) ; } } update ( ref , data ) { if ( this . browser ) { return from ( this . firestore . doc ( ref ) . update ( data ) ) ; } } remove ( DocumentRef ) { if ( isPlatformServer ( this . platformId ) ) { return this . http . delete ( `https://firestore.googleapis.com/v1beta1/projects/${ this . config . projectId }/databases/(default)/documents/${ DocumentRef }` ) ; } if ( this . browser ) { return from ( this . firestore . doc ( DocumentRef ) . delete ( ) ) ; } } removeField ( ref , ... fields ) { if ( this . browser ) { return from ( this . firestore . doc ( ref ) . update ( fields ) ) ; } } removeCollection ( collectionRef ) { if ( this . browser ) { return from ( this . firestore . collection ( collectionRef ) . get ( ) . then ( ( snapshot ) => { snapshot . docs . forEach ( ( doc ) => { this . firestore . batch ( ) . delete ( doc . ref ) ; } ) ; } ) ) ; } } query ( ref ) { const PID = this . platformId ; const HTTP = this . http ; const CONFIG = this . config ; const state = this . state ; const fs = this . firestore ; const SSQ = { 'from' : [ { 'collectionId' : ref } ] } ; const SQ = { 'structuredQuery' : SSQ } ; function SQHFS ( DSK ) { const data = [ ] ; return HTTP . post ( `https://firestore.googleapis.com/v1beta1/projects/${ CONFIG . projectId }/databases/(default)/documents:runQuery` , JSON . stringify ( SQ ) ) . pipe ( map ( ( res ) => { for ( const doc of res ) { const documentData = { } ; Object . keys ( doc . document . fields ) . forEach ( ( fieldName ) => { const fieldType = Object . keys ( doc . document . fields [ fieldName ] ) ; documentData [ fieldName ] = doc . document . fields [ fieldName ] [ fieldType [ 0 ] ] ; } ) ; data . push ( documentData ) ; } return data ; } ) , tap ( ( pl ) => { state . set ( DSK , pl ) ; } ) ) ; } function FSD ( value ) { switch ( typeof value ) { case 'boolean' : return { 'booleanValue' : value } ; case 'string' : return { 'stringValue' : value } ; case 'number' : return { 'doubleValue' : value } ; case 'object' : return { 'arrayValue' : value } ; } } const SQOB = [ ] ; let BQ = fs . collection ( ref ) as any ; return { where ( document , comparison , value ) { let SOP = '' ; switch ( comparison ) { case '<' : SOP = 'LESS_THAN' ; break ; case '<=' : SOP = 'LESS_THAN_OR_EQUAL' ; break ; case '>' : SOP = 'GREATER_THAN' ; break ; case '>=' : SOP = 'GREATER_THAN_OR_EQUAL' ; break ; case '==' : SOP = 'EQUAL' ; break ; } SSQ [ 'where' ] = { 'fieldFilter' : { 'field' : { 'fieldPath' : document } , 'op' : SOP , 'value' : FSD ( value ) } } ; BQ = BQ . where ( document , comparison , value ) ; return this ; } , startAt ( ... startValue ) { const SV = [ ] ; startValue . forEach ( ( value ) => { SV . push ( FSD ( value ) ) ; } ) ; SSQ . startAt = { } ; SSQ . startAt . before = true ; SSQ . startAt . values = SV ; BQ = BQ . startAt ( ... startValue ) ; return this ; } , startAfter ( ... startValue ) { const SV = [ ] ; startValue . forEach ( ( value ) => { SV . push ( FSD ( value ) ) ; } ) ; SSQ . startAt = { } ; SSQ . startAt . before = false ; SSQ . startAt . values = SV ; BQ = BQ . startAfter ( ... startValue ) ; return this ; } , endAt ( ... endValue ) { const SV = [ ] ; endValue . forEach ( ( value ) => { SV . push ( FSD ( value ) ) ; } ) ; SSQ . endAt = { } ; SSQ . endAt . before = false ; SSQ . endAt . values = SV ; BQ = BQ . endAt ( ... endValue ) ; return this ; } , endBefore ( ... endValue ) { const SV = [ ] ; endValue . forEach ( ( value ) => { SV . push ( FSD ( value ) ) ; } ) ; SSQ . endAt = { } ; SSQ . endAt . before = true ; SSQ . endAt . values = SV ; BQ = BQ . endBefore ( ... endValue ) ; return this ; } , limit ( limit ) { SSQ . limit = limit ; BQ = BQ . limit ( limit ) ; return this ; } , orderBy ( path , order ? ) { const orderBy = { field : { fieldPath : '' } } ; orderBy . field . fieldPath = path ; switch ( order ) { case 'asc' : orderBy [ 'direction' ] = 'ASCENDING' ; break ; case 'desc' : orderBy [ 'direction' ] = 'DESCENDING' ; break ; } SQOB . push ( orderBy ) ; BQ = BQ . orderBy ( path , order ) ; SSQ . orderBy = SQOB as any ; return this ; } , on ( ) { const ONDSK = makeStateKey < Object | Array < any > > ( ref + 'firestore:query' ) ; if ( isPlatformServer ( PID ) ) { return SQHFS ( ONDSK ) ; } if ( isPlatformBrowser ( PID ) ) { const SSRedValue = state . get ( ONDSK , [ ] ) ; const VALUE = new BehaviorSubject < any > ( SSRedValue ) ; BQ . onSnapshot ( ( snapshot ) => { const data = [ ] ; snapshot . forEach ( ( doc ) => { data . push ( doc . data ( ) ) ; } ) ; VALUE . next ( data ) ; } ) ; return VALUE ; } } , get ( ) { const GETDSK = makeStateKey < Object | Array < any > > ( ref + 'firestore:query' ) ; if ( isPlatformServer ( PID ) ) { return SQHFS ( GETDSK ) ; } if ( isPlatformBrowser ( PID ) ) { const data = [ ] ; const SSRedValue = state . get ( GETDSK , [ ] ) ; const VALUE = new BehaviorSubject < any > ( SSRedValue ) ; BQ . get ( ) . then ( ( snapshot ) => { snapshot . forEach ( ( doc ) => { data . push ( doc ) ; } ) ; VALUE . next ( data ) ; } ) ; return VALUE ; } } } ; } transaction ( ) { if ( this . browser ) { const fs = this . firestore ; let transactionToRun ; let readCount = 0 ; const transactions = { get ( ref ) { return fs . doc ( ref ) . get ( ) ; } , set ( ref , data ) { return fs . doc ( ref ) . set ( data ) ; } } ; return { set ( ref , data ) { transactionToRun = transactionToRun . then ( ( ) => { transactions . set ( ref , data ) ; } ) ; return this ; } , get ( ref ) { const getSubject = new Subject ( ) ; if ( readCount > 0 ) { transactionToRun = transactionToRun . then ( ( ) => { transactions . get ( ref ) . then ( ( value ) => { getSubject . next ( { data : value . data ( ) , next : this } ) ; } ) ; } ) ; } else if ( readCount === 0 ) { transactionToRun = transactions . get ( ref ) . then ( ( value ) => { getSubject . next ( { data : value . data ( ) , next : this } ) ; } ) ; } readCount ++ ; return getSubject ; } , run ( ) { return from ( fs . runTransaction ( ( ) => { return transactionToRun ; } ) ) ; } } ; } else { return { set ( ref , data ) { return this ; } , get ( ref ) { return new Subject ( ) ; } , run ( ) { return of ( ) ; } } ; } } batch ( ) { if ( this . browser ) { const fs = this . firestore ; const b = this . firestore . batch ( ) ; return { set ( ref , data ) { b . set ( fs . doc ( ref ) , data ) ; return this ; } , update ( ref , data ) { b . update ( fs . doc ( ref ) , data ) ; return this ; } , delete ( ref ) { b . delete ( fs . doc ( ref ) ) ; return this ; } , commit ( ) { return from ( b . commit ( ) ) ; } } ; } else { return { set ( ref , data ) { return this ; } , update ( ref , data ) { return this ; } , delete ( ref ) { return this ; } , commit ( ) { return of ( ) ; } } ; } } } export interface Batch { set ( ref , data ) : Batch ; update ( ref , data ) : Batch ; delete ( ref ) : Batch ; commit ( ) : Observable < any > ; } export interface Transaction { set ( ref , data ) : Transaction ; get ( ref ) : Subject < any > ; run ( ) : Observable < any > ; } export interface Query { where ( document , comparison , value ) : Query ; startAt ( ... startValue ) : Query ; endAt ( ... endValue ) : Query ; startAfter ( ... startValue ) : Query ; endBefore ( ... endValue ) : Query ; limit ( limit ) : Query ; orderBy ( path , order ? ) : Query ; on ( ) : BehaviorSubject < any > | Observable < any > ; get ( ) : BehaviorSubject < any > | Observable < any > ; }