index.node.cjs.js 1.2 MB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334253352533625337253382533925340253412534225343253442534525346253472534825349253502535125352253532535425355253562535725358253592536025361253622536325364253652536625367253682536925370253712537225373253742537525376253772537825379253802538125382253832538425385253862538725388253892539025391253922539325394253952539625397253982539925400254012540225403254042540525406254072540825409254102541125412254132541425415254162541725418254192542025421254222542325424254252542625427254282542925430254312543225433254342543525436254372543825439254402544125442254432544425445254462544725448254492545025451254522545325454254552545625457254582545925460254612546225463254642546525466254672546825469254702547125472254732547425475254762547725478254792548025481254822548325484254852548625487254882548925490254912549225493254942549525496254972549825499255002550125502255032550425505255062550725508255092551025511255122551325514255152551625517255182551925520255212552225523255242552525526255272552825529255302553125532255332553425535255362553725538255392554025541255422554325544255452554625547255482554925550255512555225553255542555525556255572555825559255602556125562255632556425565255662556725568255692557025571255722557325574255752557625577255782557925580255812558225583255842558525586255872558825589255902559125592255932559425595255962559725598255992560025601256022560325604256052560625607256082560925610256112561225613256142561525616256172561825619256202562125622256232562425625256262562725628256292563025631256322563325634256352563625637256382563925640256412564225643256442564525646256472564825649256502565125652256532565425655256562565725658256592566025661256622566325664256652566625667256682566925670256712567225673256742567525676256772567825679256802568125682256832568425685256862568725688256892569025691256922569325694256952569625697256982569925700257012570225703257042570525706257072570825709257102571125712257132571425715257162571725718257192572025721257222572325724257252572625727257282572925730257312573225733257342573525736257372573825739257402574125742257432574425745257462574725748257492575025751257522575325754257552575625757257582575925760257612576225763257642576525766257672576825769257702577125772257732577425775257762577725778257792578025781257822578325784257852578625787257882578925790257912579225793257942579525796257972579825799258002580125802258032580425805258062580725808258092581025811258122581325814258152581625817258182581925820258212582225823258242582525826258272582825829258302583125832258332583425835258362583725838258392584025841258422584325844258452584625847258482584925850258512585225853258542585525856258572585825859258602586125862258632586425865258662586725868258692587025871258722587325874258752587625877258782587925880258812588225883258842588525886258872588825889258902589125892258932589425895258962589725898258992590025901259022590325904259052590625907259082590925910259112591225913259142591525916259172591825919259202592125922259232592425925259262592725928259292593025931259322593325934259352593625937259382593925940259412594225943259442594525946259472594825949259502595125952259532595425955259562595725958259592596025961259622596325964259652596625967259682596925970259712597225973259742597525976259772597825979259802598125982259832598425985259862598725988259892599025991259922599325994259952599625997259982599926000260012600226003260042600526006260072600826009260102601126012260132601426015260162601726018260192602026021260222602326024260252602626027260282602926030260312603226033260342603526036260372603826039260402604126042260432604426045260462604726048260492605026051260522605326054260552605626057260582605926060260612606226063260642606526066260672606826069260702607126072260732607426075260762607726078260792608026081260822608326084260852608626087260882608926090260912609226093260942609526096260972609826099261002610126102261032610426105261062610726108261092611026111261122611326114261152611626117261182611926120261212612226123261242612526126261272612826129261302613126132261332613426135261362613726138261392614026141261422614326144261452614626147261482614926150261512615226153261542615526156261572615826159261602616126162261632616426165261662616726168261692617026171261722617326174261752617626177261782617926180261812618226183261842618526186261872618826189261902619126192261932619426195261962619726198261992620026201262022620326204262052620626207262082620926210262112621226213262142621526216262172621826219262202622126222262232622426225262262622726228262292623026231262322623326234262352623626237262382623926240262412624226243262442624526246262472624826249262502625126252262532625426255262562625726258262592626026261262622626326264262652626626267262682626926270262712627226273262742627526276262772627826279262802628126282262832628426285262862628726288262892629026291262922629326294262952629626297262982629926300263012630226303263042630526306263072630826309263102631126312263132631426315263162631726318263192632026321263222632326324263252632626327263282632926330263312633226333263342633526336263372633826339263402634126342263432634426345263462634726348263492635026351263522635326354263552635626357263582635926360263612636226363263642636526366263672636826369263702637126372263732637426375263762637726378263792638026381263822638326384263852638626387263882638926390263912639226393263942639526396263972639826399264002640126402264032640426405264062640726408264092641026411264122641326414264152641626417264182641926420264212642226423264242642526426264272642826429264302643126432264332643426435264362643726438264392644026441264422644326444264452644626447264482644926450264512645226453264542645526456264572645826459264602646126462264632646426465264662646726468264692647026471264722647326474264752647626477264782647926480264812648226483264842648526486264872648826489264902649126492264932649426495264962649726498264992650026501265022650326504265052650626507265082650926510265112651226513265142651526516265172651826519265202652126522265232652426525265262652726528265292653026531265322653326534265352653626537265382653926540265412654226543265442654526546265472654826549265502655126552265532655426555265562655726558265592656026561265622656326564265652656626567265682656926570265712657226573265742657526576265772657826579265802658126582265832658426585265862658726588265892659026591265922659326594265952659626597265982659926600266012660226603266042660526606266072660826609266102661126612266132661426615266162661726618266192662026621266222662326624266252662626627266282662926630266312663226633266342663526636266372663826639266402664126642266432664426645266462664726648266492665026651266522665326654266552665626657266582665926660266612666226663266642666526666266672666826669266702667126672266732667426675266762667726678266792668026681266822668326684266852668626687266882668926690266912669226693266942669526696266972669826699267002670126702267032670426705267062670726708267092671026711267122671326714267152671626717267182671926720267212672226723267242672526726267272672826729267302673126732267332673426735267362673726738267392674026741267422674326744267452674626747267482674926750267512675226753267542675526756267572675826759267602676126762267632676426765267662676726768267692677026771267722677326774267752677626777267782677926780267812678226783267842678526786267872678826789267902679126792267932679426795267962679726798267992680026801268022680326804268052680626807268082680926810268112681226813268142681526816268172681826819268202682126822268232682426825268262682726828268292683026831268322683326834268352683626837268382683926840268412684226843268442684526846268472684826849268502685126852268532685426855268562685726858268592686026861268622686326864268652686626867268682686926870268712687226873268742687526876268772687826879268802688126882268832688426885268862688726888268892689026891268922689326894268952689626897268982689926900269012690226903269042690526906269072690826909269102691126912269132691426915269162691726918269192692026921269222692326924269252692626927269282692926930269312693226933269342693526936269372693826939269402694126942269432694426945269462694726948269492695026951269522695326954269552695626957269582695926960269612696226963269642696526966269672696826969269702697126972269732697426975269762697726978269792698026981269822698326984269852698626987269882698926990269912699226993269942699526996269972699826999270002700127002270032700427005270062700727008270092701027011270122701327014270152701627017270182701927020270212702227023270242702527026270272702827029270302703127032270332703427035270362703727038270392704027041270422704327044270452704627047270482704927050270512705227053270542705527056270572705827059270602706127062270632706427065270662706727068270692707027071270722707327074270752707627077270782707927080270812708227083270842708527086270872708827089270902709127092270932709427095270962709727098270992710027101271022710327104271052710627107271082710927110271112711227113271142711527116271172711827119271202712127122271232712427125271262712727128271292713027131271322713327134271352713627137271382713927140271412714227143271442714527146271472714827149271502715127152271532715427155271562715727158271592716027161271622716327164271652716627167271682716927170271712717227173271742717527176271772717827179271802718127182271832718427185271862718727188271892719027191271922719327194271952719627197271982719927200272012720227203272042720527206272072720827209272102721127212272132721427215272162721727218272192722027221272222722327224272252722627227272282722927230272312723227233272342723527236272372723827239272402724127242272432724427245272462724727248272492725027251272522725327254272552725627257272582725927260272612726227263272642726527266272672726827269272702727127272272732727427275272762727727278272792728027281272822728327284272852728627287272882728927290272912729227293272942729527296272972729827299273002730127302273032730427305273062730727308273092731027311273122731327314273152731627317273182731927320273212732227323273242732527326273272732827329273302733127332273332733427335273362733727338273392734027341273422734327344273452734627347273482734927350273512735227353273542735527356273572735827359273602736127362273632736427365273662736727368273692737027371273722737327374273752737627377273782737927380273812738227383273842738527386273872738827389273902739127392273932739427395273962739727398273992740027401274022740327404274052740627407274082740927410274112741227413274142741527416274172741827419274202742127422274232742427425274262742727428274292743027431274322743327434274352743627437274382743927440274412744227443274442744527446274472744827449274502745127452274532745427455274562745727458274592746027461274622746327464274652746627467274682746927470274712747227473274742747527476274772747827479274802748127482274832748427485274862748727488274892749027491274922749327494274952749627497274982749927500275012750227503275042750527506275072750827509275102751127512275132751427515275162751727518275192752027521275222752327524275252752627527275282752927530275312753227533275342753527536275372753827539275402754127542275432754427545275462754727548275492755027551275522755327554275552755627557275582755927560275612756227563275642756527566275672756827569275702757127572275732757427575275762757727578275792758027581275822758327584275852758627587275882758927590275912759227593275942759527596275972759827599276002760127602276032760427605276062760727608276092761027611276122761327614276152761627617276182761927620276212762227623276242762527626276272762827629276302763127632276332763427635276362763727638276392764027641276422764327644276452764627647276482764927650276512765227653276542765527656276572765827659276602766127662276632766427665276662766727668276692767027671276722767327674276752767627677276782767927680276812768227683276842768527686276872768827689276902769127692276932769427695276962769727698276992770027701277022770327704277052770627707277082770927710277112771227713277142771527716277172771827719277202772127722277232772427725277262772727728277292773027731277322773327734277352773627737277382773927740277412774227743277442774527746277472774827749277502775127752277532775427755277562775727758277592776027761277622776327764277652776627767277682776927770277712777227773277742777527776277772777827779277802778127782277832778427785277862778727788277892779027791277922779327794277952779627797277982779927800278012780227803278042780527806278072780827809278102781127812278132781427815278162781727818278192782027821278222782327824278252782627827278282782927830278312783227833278342783527836278372783827839278402784127842278432784427845278462784727848278492785027851278522785327854278552785627857278582785927860278612786227863278642786527866278672786827869278702787127872278732787427875278762787727878278792788027881278822788327884278852788627887278882788927890278912789227893278942789527896278972789827899279002790127902279032790427905279062790727908279092791027911279122791327914279152791627917279182791927920279212792227923279242792527926279272792827929279302793127932279332793427935279362793727938279392794027941279422794327944279452794627947279482794927950279512795227953279542795527956279572795827959279602796127962279632796427965279662796727968279692797027971279722797327974279752797627977279782797927980279812798227983279842798527986279872798827989279902799127992279932799427995279962799727998279992800028001280022800328004280052800628007280082800928010280112801228013280142801528016280172801828019280202802128022280232802428025280262802728028280292803028031280322803328034280352803628037280382803928040280412804228043280442804528046280472804828049280502805128052280532805428055280562805728058280592806028061280622806328064280652806628067280682806928070280712807228073280742807528076280772807828079280802808128082280832808428085280862808728088280892809028091280922809328094280952809628097280982809928100281012810228103281042810528106281072810828109281102811128112281132811428115281162811728118281192812028121281222812328124281252812628127281282812928130281312813228133281342813528136281372813828139281402814128142281432814428145281462814728148281492815028151281522815328154281552815628157281582815928160281612816228163281642816528166281672816828169281702817128172281732817428175281762817728178281792818028181281822818328184281852818628187281882818928190281912819228193281942819528196281972819828199282002820128202282032820428205282062820728208282092821028211282122821328214282152821628217282182821928220282212822228223282242822528226282272822828229282302823128232282332823428235282362823728238282392824028241282422824328244282452824628247282482824928250282512825228253282542825528256282572825828259282602826128262282632826428265282662826728268282692827028271282722827328274282752827628277282782827928280282812828228283282842828528286282872828828289282902829128292282932829428295282962829728298282992830028301283022830328304283052830628307283082830928310283112831228313283142831528316283172831828319283202832128322283232832428325283262832728328283292833028331283322833328334283352833628337283382833928340283412834228343283442834528346283472834828349283502835128352283532835428355283562835728358283592836028361283622836328364283652836628367283682836928370283712837228373283742837528376283772837828379283802838128382283832838428385283862838728388283892839028391283922839328394283952839628397283982839928400284012840228403284042840528406284072840828409284102841128412284132841428415284162841728418284192842028421284222842328424284252842628427284282842928430284312843228433284342843528436284372843828439284402844128442284432844428445284462844728448284492845028451284522845328454284552845628457284582845928460284612846228463284642846528466284672846828469284702847128472284732847428475284762847728478284792848028481284822848328484284852848628487284882848928490284912849228493284942849528496284972849828499285002850128502285032850428505285062850728508285092851028511285122851328514285152851628517285182851928520285212852228523285242852528526285272852828529285302853128532285332853428535285362853728538285392854028541285422854328544285452854628547285482854928550285512855228553285542855528556285572855828559285602856128562285632856428565285662856728568285692857028571285722857328574285752857628577285782857928580285812858228583285842858528586285872858828589285902859128592285932859428595285962859728598285992860028601286022860328604286052860628607286082860928610286112861228613286142861528616286172861828619286202862128622286232862428625286262862728628286292863028631286322863328634286352863628637286382863928640286412864228643286442864528646286472864828649286502865128652286532865428655286562865728658286592866028661286622866328664286652866628667286682866928670286712867228673286742867528676286772867828679286802868128682286832868428685286862868728688286892869028691286922869328694286952869628697286982869928700287012870228703287042870528706287072870828709287102871128712287132871428715287162871728718287192872028721287222872328724287252872628727287282872928730287312873228733287342873528736287372873828739287402874128742287432874428745287462874728748287492875028751287522875328754287552875628757287582875928760287612876228763287642876528766287672876828769287702877128772287732877428775287762877728778287792878028781287822878328784287852878628787287882878928790287912879228793287942879528796287972879828799288002880128802288032880428805288062880728808288092881028811288122881328814288152881628817288182881928820288212882228823288242882528826288272882828829288302883128832288332883428835288362883728838288392884028841288422884328844288452884628847288482884928850288512885228853288542885528856288572885828859288602886128862288632886428865288662886728868288692887028871288722887328874288752887628877288782887928880288812888228883288842888528886288872888828889288902889128892288932889428895288962889728898288992890028901289022890328904289052890628907289082890928910289112891228913289142891528916289172891828919289202892128922289232892428925289262892728928289292893028931289322893328934289352893628937289382893928940289412894228943289442894528946289472894828949289502895128952289532895428955289562895728958289592896028961289622896328964289652896628967289682896928970289712897228973289742897528976289772897828979289802898128982289832898428985289862898728988289892899028991289922899328994289952899628997289982899929000290012900229003290042900529006290072900829009290102901129012290132901429015290162901729018290192902029021290222902329024290252902629027290282902929030290312903229033290342903529036290372903829039290402904129042290432904429045290462904729048290492905029051290522905329054290552905629057290582905929060290612906229063290642906529066290672906829069290702907129072290732907429075290762907729078290792908029081290822908329084290852908629087290882908929090290912909229093290942909529096290972909829099291002910129102291032910429105291062910729108291092911029111291122911329114291152911629117291182911929120291212912229123291242912529126291272912829129291302913129132291332913429135291362913729138291392914029141291422914329144291452914629147291482914929150291512915229153291542915529156291572915829159291602916129162291632916429165291662916729168291692917029171291722917329174291752917629177291782917929180291812918229183291842918529186291872918829189291902919129192291932919429195291962919729198291992920029201292022920329204292052920629207292082920929210292112921229213292142921529216292172921829219292202922129222292232922429225292262922729228292292923029231292322923329234292352923629237292382923929240292412924229243292442924529246292472924829249292502925129252292532925429255292562925729258292592926029261292622926329264292652926629267292682926929270292712927229273292742927529276292772927829279292802928129282292832928429285292862928729288292892929029291292922929329294292952929629297292982929929300293012930229303293042930529306293072930829309293102931129312293132931429315293162931729318293192932029321293222932329324293252932629327293282932929330293312933229333293342933529336293372933829339293402934129342293432934429345293462934729348293492935029351293522935329354293552935629357293582935929360293612936229363293642936529366293672936829369293702937129372293732937429375293762937729378293792938029381293822938329384293852938629387293882938929390293912939229393293942939529396293972939829399294002940129402294032940429405294062940729408294092941029411294122941329414294152941629417294182941929420294212942229423294242942529426294272942829429294302943129432294332943429435294362943729438294392944029441294422944329444294452944629447294482944929450294512945229453294542945529456294572945829459294602946129462294632946429465294662946729468294692947029471294722947329474294752947629477294782947929480294812948229483294842948529486294872948829489294902949129492294932949429495294962949729498294992950029501295022950329504295052950629507295082950929510295112951229513295142951529516295172951829519295202952129522295232952429525295262952729528295292953029531295322953329534295352953629537295382953929540295412954229543295442954529546295472954829549295502955129552295532955429555295562955729558295592956029561295622956329564295652956629567295682956929570295712957229573295742957529576295772957829579295802958129582295832958429585295862958729588295892959029591295922959329594295952959629597295982959929600296012960229603296042960529606296072960829609296102961129612296132961429615296162961729618296192962029621296222962329624296252962629627296282962929630296312963229633296342963529636296372963829639296402964129642296432964429645296462964729648296492965029651296522965329654296552965629657296582965929660296612966229663296642966529666296672966829669296702967129672296732967429675296762967729678296792968029681296822968329684296852968629687296882968929690296912969229693296942969529696296972969829699297002970129702297032970429705297062970729708297092971029711297122971329714297152971629717297182971929720297212972229723297242972529726297272972829729297302973129732297332973429735297362973729738297392974029741297422974329744297452974629747297482974929750297512975229753297542975529756297572975829759297602976129762297632976429765297662976729768297692977029771297722977329774297752977629777297782977929780297812978229783297842978529786297872978829789297902979129792297932979429795297962979729798297992980029801298022980329804298052980629807298082980929810298112981229813298142981529816298172981829819298202982129822298232982429825298262982729828298292983029831298322983329834298352983629837298382983929840298412984229843298442984529846298472984829849298502985129852298532985429855298562985729858298592986029861298622986329864298652986629867298682986929870298712987229873298742987529876298772987829879298802988129882298832988429885298862988729888298892989029891298922989329894298952989629897298982989929900299012990229903299042990529906299072990829909299102991129912299132991429915299162991729918299192992029921299222992329924299252992629927299282992929930299312993229933299342993529936299372993829939299402994129942299432994429945299462994729948299492995029951299522995329954299552995629957299582995929960299612996229963299642996529966299672996829969299702997129972299732997429975299762997729978299792998029981299822998329984299852998629987299882998929990299912999229993299942999529996299972999829999300003000130002300033000430005300063000730008300093001030011300123001330014300153001630017300183001930020300213002230023300243002530026300273002830029300303003130032300333003430035300363003730038300393004030041300423004330044300453004630047300483004930050300513005230053300543005530056300573005830059300603006130062300633006430065300663006730068300693007030071300723007330074300753007630077300783007930080300813008230083300843008530086300873008830089300903009130092300933009430095300963009730098300993010030101301023010330104301053010630107301083010930110301113011230113301143011530116301173011830119301203012130122301233012430125301263012730128301293013030131301323013330134301353013630137301383013930140301413014230143301443014530146301473014830149301503015130152301533015430155301563015730158301593016030161301623016330164301653016630167301683016930170301713017230173301743017530176301773017830179301803018130182301833018430185301863018730188301893019030191301923019330194301953019630197301983019930200302013020230203302043020530206302073020830209302103021130212302133021430215302163021730218302193022030221302223022330224302253022630227302283022930230302313023230233302343023530236302373023830239302403024130242302433024430245302463024730248302493025030251302523025330254302553025630257302583025930260302613026230263302643026530266302673026830269302703027130272302733027430275302763027730278302793028030281302823028330284302853028630287302883028930290302913029230293302943029530296302973029830299303003030130302303033030430305303063030730308303093031030311303123031330314303153031630317303183031930320303213032230323303243032530326303273032830329303303033130332303333033430335303363033730338303393034030341303423034330344303453034630347303483034930350303513035230353303543035530356303573035830359303603036130362303633036430365303663036730368303693037030371303723037330374303753037630377303783037930380303813038230383303843038530386303873038830389303903039130392303933039430395303963039730398303993040030401304023040330404304053040630407304083040930410304113041230413304143041530416304173041830419304203042130422304233042430425304263042730428304293043030431304323043330434304353043630437304383043930440304413044230443304443044530446304473044830449304503045130452304533045430455304563045730458304593046030461304623046330464304653046630467304683046930470304713047230473304743047530476304773047830479304803048130482304833048430485304863048730488304893049030491304923049330494304953049630497304983049930500305013050230503305043050530506305073050830509305103051130512305133051430515305163051730518305193052030521305223052330524305253052630527305283052930530305313053230533305343053530536305373053830539305403054130542305433054430545305463054730548305493055030551305523055330554305553055630557305583055930560305613056230563305643056530566305673056830569305703057130572305733057430575305763057730578305793058030581305823058330584305853058630587305883058930590305913059230593305943059530596305973059830599306003060130602306033060430605306063060730608306093061030611306123061330614306153061630617306183061930620306213062230623306243062530626306273062830629306303063130632306333063430635306363063730638306393064030641306423064330644306453064630647306483064930650306513065230653306543065530656306573065830659306603066130662306633066430665306663066730668306693067030671306723067330674306753067630677306783067930680306813068230683306843068530686306873068830689306903069130692306933069430695306963069730698306993070030701307023070330704307053070630707307083070930710307113071230713307143071530716307173071830719307203072130722307233072430725307263072730728307293073030731307323073330734307353073630737307383073930740307413074230743307443074530746307473074830749307503075130752307533075430755307563075730758307593076030761307623076330764307653076630767307683076930770307713077230773307743077530776307773077830779307803078130782307833078430785307863078730788307893079030791307923079330794307953079630797307983079930800308013080230803308043080530806308073080830809308103081130812308133081430815308163081730818308193082030821308223082330824308253082630827308283082930830308313083230833308343083530836308373083830839308403084130842308433084430845308463084730848308493085030851308523085330854308553085630857308583085930860308613086230863308643086530866308673086830869308703087130872308733087430875308763087730878308793088030881308823088330884308853088630887308883088930890308913089230893308943089530896308973089830899309003090130902309033090430905309063090730908309093091030911309123091330914309153091630917309183091930920309213092230923309243092530926309273092830929309303093130932309333093430935309363093730938309393094030941309423094330944309453094630947309483094930950309513095230953309543095530956309573095830959309603096130962309633096430965309663096730968309693097030971309723097330974309753097630977309783097930980309813098230983309843098530986309873098830989309903099130992309933099430995309963099730998309993100031001310023100331004310053100631007310083100931010310113101231013310143101531016310173101831019310203102131022310233102431025310263102731028310293103031031310323103331034310353103631037310383103931040310413104231043310443104531046310473104831049310503105131052310533105431055310563105731058310593106031061310623106331064310653106631067310683106931070310713107231073310743107531076310773107831079310803108131082310833108431085310863108731088310893109031091310923109331094310953109631097310983109931100311013110231103311043110531106311073110831109311103111131112311133111431115311163111731118311193112031121311223112331124311253112631127311283112931130311313113231133311343113531136311373113831139311403114131142311433114431145311463114731148311493115031151311523115331154311553115631157311583115931160311613116231163311643116531166311673116831169311703117131172311733117431175311763117731178311793118031181311823118331184311853118631187311883118931190311913119231193311943119531196311973119831199312003120131202312033120431205312063120731208312093121031211312123121331214312153121631217312183121931220312213122231223312243122531226312273122831229312303123131232312333123431235312363123731238312393124031241312423124331244312453124631247312483124931250312513125231253312543125531256312573125831259312603126131262312633126431265312663126731268312693127031271312723127331274312753127631277312783127931280312813128231283312843128531286312873128831289312903129131292312933129431295312963129731298312993130031301313023130331304313053130631307313083130931310313113131231313313143131531316313173131831319313203132131322313233132431325313263132731328313293133031331313323133331334313353133631337313383133931340313413134231343313443134531346313473134831349313503135131352313533135431355313563135731358313593136031361313623136331364313653136631367313683136931370313713137231373313743137531376313773137831379313803138131382313833138431385313863138731388313893139031391313923139331394313953139631397313983139931400314013140231403314043140531406314073140831409314103141131412314133141431415314163141731418314193142031421314223142331424314253142631427314283142931430314313143231433314343143531436314373143831439314403144131442314433144431445314463144731448314493145031451314523145331454314553145631457314583145931460314613146231463314643146531466314673146831469314703147131472314733147431475314763147731478314793148031481314823148331484314853148631487314883148931490314913149231493314943149531496314973149831499315003150131502315033150431505315063150731508315093151031511315123151331514315153151631517315183151931520315213152231523315243152531526315273152831529315303153131532315333153431535315363153731538315393154031541315423154331544315453154631547315483154931550315513155231553315543155531556315573155831559315603156131562315633156431565315663156731568315693157031571315723157331574315753157631577315783157931580315813158231583315843158531586315873158831589315903159131592315933159431595315963159731598315993160031601316023160331604316053160631607316083160931610316113161231613316143161531616316173161831619316203162131622316233162431625316263162731628316293163031631316323163331634316353163631637316383163931640316413164231643316443164531646316473164831649316503165131652316533165431655316563165731658316593166031661316623166331664316653166631667316683166931670316713167231673316743167531676316773167831679316803168131682316833168431685316863168731688316893169031691316923169331694316953169631697316983169931700317013170231703317043170531706317073170831709317103171131712317133171431715317163171731718317193172031721317223172331724317253172631727317283172931730317313173231733317343173531736317373173831739317403174131742317433174431745317463174731748317493175031751317523175331754317553175631757317583175931760317613176231763317643176531766317673176831769317703177131772317733177431775317763177731778317793178031781317823178331784317853178631787317883178931790317913179231793317943179531796
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var app = require('@firebase/app');
  4. var component = require('@firebase/component');
  5. var logger = require('@firebase/logger');
  6. var util$1 = require('util');
  7. var util = require('@firebase/util');
  8. var crypto = require('crypto');
  9. var webchannelWrapper = require('@firebase/webchannel-wrapper');
  10. var grpc = require('@grpc/grpc-js');
  11. var protoLoader = require('@grpc/proto-loader');
  12. function _interopNamespace(e) {
  13. if (e && e.__esModule) return e;
  14. var n = Object.create(null);
  15. if (e) {
  16. Object.keys(e).forEach(function (k) {
  17. if (k !== 'default') {
  18. var d = Object.getOwnPropertyDescriptor(e, k);
  19. Object.defineProperty(n, k, d.get ? d : {
  20. enumerable: true,
  21. get: function () { return e[k]; }
  22. });
  23. }
  24. });
  25. }
  26. n["default"] = e;
  27. return Object.freeze(n);
  28. }
  29. var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
  30. var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
  31. const name = "@firebase/firestore";
  32. const version$1 = "3.13.0";
  33. /**
  34. * @license
  35. * Copyright 2017 Google LLC
  36. *
  37. * Licensed under the Apache License, Version 2.0 (the "License");
  38. * you may not use this file except in compliance with the License.
  39. * You may obtain a copy of the License at
  40. *
  41. * http://www.apache.org/licenses/LICENSE-2.0
  42. *
  43. * Unless required by applicable law or agreed to in writing, software
  44. * distributed under the License is distributed on an "AS IS" BASIS,
  45. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  46. * See the License for the specific language governing permissions and
  47. * limitations under the License.
  48. */
  49. /**
  50. * Simple wrapper around a nullable UID. Mostly exists to make code more
  51. * readable.
  52. */
  53. class User {
  54. constructor(uid) {
  55. this.uid = uid;
  56. }
  57. isAuthenticated() {
  58. return this.uid != null;
  59. }
  60. /**
  61. * Returns a key representing this user, suitable for inclusion in a
  62. * dictionary.
  63. */
  64. toKey() {
  65. if (this.isAuthenticated()) {
  66. return 'uid:' + this.uid;
  67. }
  68. else {
  69. return 'anonymous-user';
  70. }
  71. }
  72. isEqual(otherUser) {
  73. return otherUser.uid === this.uid;
  74. }
  75. }
  76. /** A user with a null UID. */
  77. User.UNAUTHENTICATED = new User(null);
  78. // TODO(mikelehen): Look into getting a proper uid-equivalent for
  79. // non-FirebaseAuth providers.
  80. User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
  81. User.FIRST_PARTY = new User('first-party-uid');
  82. User.MOCK_USER = new User('mock-user');
  83. const version = "9.23.0";
  84. /**
  85. * @license
  86. * Copyright 2017 Google LLC
  87. *
  88. * Licensed under the Apache License, Version 2.0 (the "License");
  89. * you may not use this file except in compliance with the License.
  90. * You may obtain a copy of the License at
  91. *
  92. * http://www.apache.org/licenses/LICENSE-2.0
  93. *
  94. * Unless required by applicable law or agreed to in writing, software
  95. * distributed under the License is distributed on an "AS IS" BASIS,
  96. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  97. * See the License for the specific language governing permissions and
  98. * limitations under the License.
  99. */
  100. let SDK_VERSION = version;
  101. function setSDKVersion(version) {
  102. SDK_VERSION = version;
  103. }
  104. /**
  105. * @license
  106. * Copyright 2020 Google LLC
  107. *
  108. * Licensed under the Apache License, Version 2.0 (the "License");
  109. * you may not use this file except in compliance with the License.
  110. * You may obtain a copy of the License at
  111. *
  112. * http://www.apache.org/licenses/LICENSE-2.0
  113. *
  114. * Unless required by applicable law or agreed to in writing, software
  115. * distributed under the License is distributed on an "AS IS" BASIS,
  116. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  117. * See the License for the specific language governing permissions and
  118. * limitations under the License.
  119. */
  120. /** Formats an object as a JSON string, suitable for logging. */
  121. function formatJSON(value) {
  122. // util.inspect() results in much more readable output than JSON.stringify()
  123. return util$1.inspect(value, { depth: 100 });
  124. }
  125. /**
  126. * @license
  127. * Copyright 2017 Google LLC
  128. *
  129. * Licensed under the Apache License, Version 2.0 (the "License");
  130. * you may not use this file except in compliance with the License.
  131. * You may obtain a copy of the License at
  132. *
  133. * http://www.apache.org/licenses/LICENSE-2.0
  134. *
  135. * Unless required by applicable law or agreed to in writing, software
  136. * distributed under the License is distributed on an "AS IS" BASIS,
  137. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  138. * See the License for the specific language governing permissions and
  139. * limitations under the License.
  140. */
  141. const logClient = new logger.Logger('@firebase/firestore');
  142. // Helper methods are needed because variables can't be exported as read/write
  143. function getLogLevel() {
  144. return logClient.logLevel;
  145. }
  146. /**
  147. * Sets the verbosity of Cloud Firestore logs (debug, error, or silent).
  148. *
  149. * @param logLevel - The verbosity you set for activity and error logging. Can
  150. * be any of the following values:
  151. *
  152. * <ul>
  153. * <li>`debug` for the most verbose logging level, primarily for
  154. * debugging.</li>
  155. * <li>`error` to log errors only.</li>
  156. * <li><code>`silent` to turn off logging.</li>
  157. * </ul>
  158. */
  159. function setLogLevel(logLevel) {
  160. logClient.setLogLevel(logLevel);
  161. }
  162. function logDebug(msg, ...obj) {
  163. if (logClient.logLevel <= logger.LogLevel.DEBUG) {
  164. const args = obj.map(argToString);
  165. logClient.debug(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
  166. }
  167. }
  168. function logError(msg, ...obj) {
  169. if (logClient.logLevel <= logger.LogLevel.ERROR) {
  170. const args = obj.map(argToString);
  171. logClient.error(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
  172. }
  173. }
  174. /**
  175. * @internal
  176. */
  177. function logWarn(msg, ...obj) {
  178. if (logClient.logLevel <= logger.LogLevel.WARN) {
  179. const args = obj.map(argToString);
  180. logClient.warn(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
  181. }
  182. }
  183. /**
  184. * Converts an additional log parameter to a string representation.
  185. */
  186. function argToString(obj) {
  187. if (typeof obj === 'string') {
  188. return obj;
  189. }
  190. else {
  191. try {
  192. return formatJSON(obj);
  193. }
  194. catch (e) {
  195. // Converting to JSON failed, just log the object directly
  196. return obj;
  197. }
  198. }
  199. }
  200. /**
  201. * @license
  202. * Copyright 2017 Google LLC
  203. *
  204. * Licensed under the Apache License, Version 2.0 (the "License");
  205. * you may not use this file except in compliance with the License.
  206. * You may obtain a copy of the License at
  207. *
  208. * http://www.apache.org/licenses/LICENSE-2.0
  209. *
  210. * Unless required by applicable law or agreed to in writing, software
  211. * distributed under the License is distributed on an "AS IS" BASIS,
  212. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  213. * See the License for the specific language governing permissions and
  214. * limitations under the License.
  215. */
  216. /**
  217. * Unconditionally fails, throwing an Error with the given message.
  218. * Messages are stripped in production builds.
  219. *
  220. * Returns `never` and can be used in expressions:
  221. * @example
  222. * let futureVar = fail('not implemented yet');
  223. */
  224. function fail(failure = 'Unexpected state') {
  225. // Log the failure in addition to throw an exception, just in case the
  226. // exception is swallowed.
  227. const message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ` + failure;
  228. logError(message);
  229. // NOTE: We don't use FirestoreError here because these are internal failures
  230. // that cannot be handled by the user. (Also it would create a circular
  231. // dependency between the error and assert modules which doesn't work.)
  232. throw new Error(message);
  233. }
  234. /**
  235. * Fails if the given assertion condition is false, throwing an Error with the
  236. * given message if it did.
  237. *
  238. * Messages are stripped in production builds.
  239. */
  240. function hardAssert(assertion, message) {
  241. if (!assertion) {
  242. fail();
  243. }
  244. }
  245. /**
  246. * Fails if the given assertion condition is false, throwing an Error with the
  247. * given message if it did.
  248. *
  249. * The code of callsites invoking this function are stripped out in production
  250. * builds. Any side-effects of code within the debugAssert() invocation will not
  251. * happen in this case.
  252. *
  253. * @internal
  254. */
  255. function debugAssert(assertion, message) {
  256. if (!assertion) {
  257. fail();
  258. }
  259. }
  260. /**
  261. * Casts `obj` to `T`. In non-production builds, verifies that `obj` is an
  262. * instance of `T` before casting.
  263. */
  264. function debugCast(obj,
  265. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  266. constructor) {
  267. return obj;
  268. }
  269. /**
  270. * @license
  271. * Copyright 2017 Google LLC
  272. *
  273. * Licensed under the Apache License, Version 2.0 (the "License");
  274. * you may not use this file except in compliance with the License.
  275. * You may obtain a copy of the License at
  276. *
  277. * http://www.apache.org/licenses/LICENSE-2.0
  278. *
  279. * Unless required by applicable law or agreed to in writing, software
  280. * distributed under the License is distributed on an "AS IS" BASIS,
  281. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  282. * See the License for the specific language governing permissions and
  283. * limitations under the License.
  284. */
  285. const Code = {
  286. // Causes are copied from:
  287. // https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h
  288. /** Not an error; returned on success. */
  289. OK: 'ok',
  290. /** The operation was cancelled (typically by the caller). */
  291. CANCELLED: 'cancelled',
  292. /** Unknown error or an error from a different error domain. */
  293. UNKNOWN: 'unknown',
  294. /**
  295. * Client specified an invalid argument. Note that this differs from
  296. * FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
  297. * problematic regardless of the state of the system (e.g., a malformed file
  298. * name).
  299. */
  300. INVALID_ARGUMENT: 'invalid-argument',
  301. /**
  302. * Deadline expired before operation could complete. For operations that
  303. * change the state of the system, this error may be returned even if the
  304. * operation has completed successfully. For example, a successful response
  305. * from a server could have been delayed long enough for the deadline to
  306. * expire.
  307. */
  308. DEADLINE_EXCEEDED: 'deadline-exceeded',
  309. /** Some requested entity (e.g., file or directory) was not found. */
  310. NOT_FOUND: 'not-found',
  311. /**
  312. * Some entity that we attempted to create (e.g., file or directory) already
  313. * exists.
  314. */
  315. ALREADY_EXISTS: 'already-exists',
  316. /**
  317. * The caller does not have permission to execute the specified operation.
  318. * PERMISSION_DENIED must not be used for rejections caused by exhausting
  319. * some resource (use RESOURCE_EXHAUSTED instead for those errors).
  320. * PERMISSION_DENIED must not be used if the caller can not be identified
  321. * (use UNAUTHENTICATED instead for those errors).
  322. */
  323. PERMISSION_DENIED: 'permission-denied',
  324. /**
  325. * The request does not have valid authentication credentials for the
  326. * operation.
  327. */
  328. UNAUTHENTICATED: 'unauthenticated',
  329. /**
  330. * Some resource has been exhausted, perhaps a per-user quota, or perhaps the
  331. * entire file system is out of space.
  332. */
  333. RESOURCE_EXHAUSTED: 'resource-exhausted',
  334. /**
  335. * Operation was rejected because the system is not in a state required for
  336. * the operation's execution. For example, directory to be deleted may be
  337. * non-empty, an rmdir operation is applied to a non-directory, etc.
  338. *
  339. * A litmus test that may help a service implementor in deciding
  340. * between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
  341. * (a) Use UNAVAILABLE if the client can retry just the failing call.
  342. * (b) Use ABORTED if the client should retry at a higher-level
  343. * (e.g., restarting a read-modify-write sequence).
  344. * (c) Use FAILED_PRECONDITION if the client should not retry until
  345. * the system state has been explicitly fixed. E.g., if an "rmdir"
  346. * fails because the directory is non-empty, FAILED_PRECONDITION
  347. * should be returned since the client should not retry unless
  348. * they have first fixed up the directory by deleting files from it.
  349. * (d) Use FAILED_PRECONDITION if the client performs conditional
  350. * REST Get/Update/Delete on a resource and the resource on the
  351. * server does not match the condition. E.g., conflicting
  352. * read-modify-write on the same resource.
  353. */
  354. FAILED_PRECONDITION: 'failed-precondition',
  355. /**
  356. * The operation was aborted, typically due to a concurrency issue like
  357. * sequencer check failures, transaction aborts, etc.
  358. *
  359. * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
  360. * and UNAVAILABLE.
  361. */
  362. ABORTED: 'aborted',
  363. /**
  364. * Operation was attempted past the valid range. E.g., seeking or reading
  365. * past end of file.
  366. *
  367. * Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed
  368. * if the system state changes. For example, a 32-bit file system will
  369. * generate INVALID_ARGUMENT if asked to read at an offset that is not in the
  370. * range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from
  371. * an offset past the current file size.
  372. *
  373. * There is a fair bit of overlap between FAILED_PRECONDITION and
  374. * OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
  375. * when it applies so that callers who are iterating through a space can
  376. * easily look for an OUT_OF_RANGE error to detect when they are done.
  377. */
  378. OUT_OF_RANGE: 'out-of-range',
  379. /** Operation is not implemented or not supported/enabled in this service. */
  380. UNIMPLEMENTED: 'unimplemented',
  381. /**
  382. * Internal errors. Means some invariants expected by underlying System has
  383. * been broken. If you see one of these errors, Something is very broken.
  384. */
  385. INTERNAL: 'internal',
  386. /**
  387. * The service is currently unavailable. This is a most likely a transient
  388. * condition and may be corrected by retrying with a backoff.
  389. *
  390. * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
  391. * and UNAVAILABLE.
  392. */
  393. UNAVAILABLE: 'unavailable',
  394. /** Unrecoverable data loss or corruption. */
  395. DATA_LOSS: 'data-loss'
  396. };
  397. /** An error returned by a Firestore operation. */
  398. class FirestoreError extends util.FirebaseError {
  399. /** @hideconstructor */
  400. constructor(
  401. /**
  402. * The backend error code associated with this error.
  403. */
  404. code,
  405. /**
  406. * A custom error description.
  407. */
  408. message) {
  409. super(code, message);
  410. this.code = code;
  411. this.message = message;
  412. // HACK: We write a toString property directly because Error is not a real
  413. // class and so inheritance does not work correctly. We could alternatively
  414. // do the same "back-door inheritance" trick that FirebaseError does.
  415. this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`;
  416. }
  417. }
  418. /**
  419. * @license
  420. * Copyright 2017 Google LLC
  421. *
  422. * Licensed under the Apache License, Version 2.0 (the "License");
  423. * you may not use this file except in compliance with the License.
  424. * You may obtain a copy of the License at
  425. *
  426. * http://www.apache.org/licenses/LICENSE-2.0
  427. *
  428. * Unless required by applicable law or agreed to in writing, software
  429. * distributed under the License is distributed on an "AS IS" BASIS,
  430. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  431. * See the License for the specific language governing permissions and
  432. * limitations under the License.
  433. */
  434. class Deferred {
  435. constructor() {
  436. this.promise = new Promise((resolve, reject) => {
  437. this.resolve = resolve;
  438. this.reject = reject;
  439. });
  440. }
  441. }
  442. /**
  443. * @license
  444. * Copyright 2017 Google LLC
  445. *
  446. * Licensed under the Apache License, Version 2.0 (the "License");
  447. * you may not use this file except in compliance with the License.
  448. * You may obtain a copy of the License at
  449. *
  450. * http://www.apache.org/licenses/LICENSE-2.0
  451. *
  452. * Unless required by applicable law or agreed to in writing, software
  453. * distributed under the License is distributed on an "AS IS" BASIS,
  454. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  455. * See the License for the specific language governing permissions and
  456. * limitations under the License.
  457. */
  458. class OAuthToken {
  459. constructor(value, user) {
  460. this.user = user;
  461. this.type = 'OAuth';
  462. this.headers = new Map();
  463. this.headers.set('Authorization', `Bearer ${value}`);
  464. }
  465. }
  466. /**
  467. * A CredentialsProvider that always yields an empty token.
  468. * @internal
  469. */
  470. class EmptyAuthCredentialsProvider {
  471. getToken() {
  472. return Promise.resolve(null);
  473. }
  474. invalidateToken() { }
  475. start(asyncQueue, changeListener) {
  476. // Fire with initial user.
  477. asyncQueue.enqueueRetryable(() => changeListener(User.UNAUTHENTICATED));
  478. }
  479. shutdown() { }
  480. }
  481. /**
  482. * A CredentialsProvider that always returns a constant token. Used for
  483. * emulator token mocking.
  484. */
  485. class EmulatorAuthCredentialsProvider {
  486. constructor(token) {
  487. this.token = token;
  488. /**
  489. * Stores the listener registered with setChangeListener()
  490. * This isn't actually necessary since the UID never changes, but we use this
  491. * to verify the listen contract is adhered to in tests.
  492. */
  493. this.changeListener = null;
  494. }
  495. getToken() {
  496. return Promise.resolve(this.token);
  497. }
  498. invalidateToken() { }
  499. start(asyncQueue, changeListener) {
  500. this.changeListener = changeListener;
  501. // Fire with initial user.
  502. asyncQueue.enqueueRetryable(() => changeListener(this.token.user));
  503. }
  504. shutdown() {
  505. this.changeListener = null;
  506. }
  507. }
  508. class FirebaseAuthCredentialsProvider {
  509. constructor(authProvider) {
  510. this.authProvider = authProvider;
  511. /** Tracks the current User. */
  512. this.currentUser = User.UNAUTHENTICATED;
  513. /**
  514. * Counter used to detect if the token changed while a getToken request was
  515. * outstanding.
  516. */
  517. this.tokenCounter = 0;
  518. this.forceRefresh = false;
  519. this.auth = null;
  520. }
  521. start(asyncQueue, changeListener) {
  522. let lastTokenId = this.tokenCounter;
  523. // A change listener that prevents double-firing for the same token change.
  524. const guardedChangeListener = user => {
  525. if (this.tokenCounter !== lastTokenId) {
  526. lastTokenId = this.tokenCounter;
  527. return changeListener(user);
  528. }
  529. else {
  530. return Promise.resolve();
  531. }
  532. };
  533. // A promise that can be waited on to block on the next token change.
  534. // This promise is re-created after each change.
  535. let nextToken = new Deferred();
  536. this.tokenListener = () => {
  537. this.tokenCounter++;
  538. this.currentUser = this.getUser();
  539. nextToken.resolve();
  540. nextToken = new Deferred();
  541. asyncQueue.enqueueRetryable(() => guardedChangeListener(this.currentUser));
  542. };
  543. const awaitNextToken = () => {
  544. const currentTokenAttempt = nextToken;
  545. asyncQueue.enqueueRetryable(async () => {
  546. await currentTokenAttempt.promise;
  547. await guardedChangeListener(this.currentUser);
  548. });
  549. };
  550. const registerAuth = (auth) => {
  551. logDebug('FirebaseAuthCredentialsProvider', 'Auth detected');
  552. this.auth = auth;
  553. this.auth.addAuthTokenListener(this.tokenListener);
  554. awaitNextToken();
  555. };
  556. this.authProvider.onInit(auth => registerAuth(auth));
  557. // Our users can initialize Auth right after Firestore, so we give it
  558. // a chance to register itself with the component framework before we
  559. // determine whether to start up in unauthenticated mode.
  560. setTimeout(() => {
  561. if (!this.auth) {
  562. const auth = this.authProvider.getImmediate({ optional: true });
  563. if (auth) {
  564. registerAuth(auth);
  565. }
  566. else {
  567. // If auth is still not available, proceed with `null` user
  568. logDebug('FirebaseAuthCredentialsProvider', 'Auth not yet detected');
  569. nextToken.resolve();
  570. nextToken = new Deferred();
  571. }
  572. }
  573. }, 0);
  574. awaitNextToken();
  575. }
  576. getToken() {
  577. // Take note of the current value of the tokenCounter so that this method
  578. // can fail (with an ABORTED error) if there is a token change while the
  579. // request is outstanding.
  580. const initialTokenCounter = this.tokenCounter;
  581. const forceRefresh = this.forceRefresh;
  582. this.forceRefresh = false;
  583. if (!this.auth) {
  584. return Promise.resolve(null);
  585. }
  586. return this.auth.getToken(forceRefresh).then(tokenData => {
  587. // Cancel the request since the token changed while the request was
  588. // outstanding so the response is potentially for a previous user (which
  589. // user, we can't be sure).
  590. if (this.tokenCounter !== initialTokenCounter) {
  591. logDebug('FirebaseAuthCredentialsProvider', 'getToken aborted due to token change.');
  592. return this.getToken();
  593. }
  594. else {
  595. if (tokenData) {
  596. hardAssert(typeof tokenData.accessToken === 'string');
  597. return new OAuthToken(tokenData.accessToken, this.currentUser);
  598. }
  599. else {
  600. return null;
  601. }
  602. }
  603. });
  604. }
  605. invalidateToken() {
  606. this.forceRefresh = true;
  607. }
  608. shutdown() {
  609. if (this.auth) {
  610. this.auth.removeAuthTokenListener(this.tokenListener);
  611. }
  612. }
  613. // Auth.getUid() can return null even with a user logged in. It is because
  614. // getUid() is synchronous, but the auth code populating Uid is asynchronous.
  615. // This method should only be called in the AuthTokenListener callback
  616. // to guarantee to get the actual user.
  617. getUser() {
  618. const currentUid = this.auth && this.auth.getUid();
  619. hardAssert(currentUid === null || typeof currentUid === 'string');
  620. return new User(currentUid);
  621. }
  622. }
  623. /*
  624. * FirstPartyToken provides a fresh token each time its value
  625. * is requested, because if the token is too old, requests will be rejected.
  626. * Technically this may no longer be necessary since the SDK should gracefully
  627. * recover from unauthenticated errors (see b/33147818 for context), but it's
  628. * safer to keep the implementation as-is.
  629. */
  630. class FirstPartyToken {
  631. constructor(sessionIndex, iamToken, authTokenFactory) {
  632. this.sessionIndex = sessionIndex;
  633. this.iamToken = iamToken;
  634. this.authTokenFactory = authTokenFactory;
  635. this.type = 'FirstParty';
  636. this.user = User.FIRST_PARTY;
  637. this._headers = new Map();
  638. }
  639. /**
  640. * Gets an authorization token, using a provided factory function, or return
  641. * null.
  642. */
  643. getAuthToken() {
  644. if (this.authTokenFactory) {
  645. return this.authTokenFactory();
  646. }
  647. else {
  648. return null;
  649. }
  650. }
  651. get headers() {
  652. this._headers.set('X-Goog-AuthUser', this.sessionIndex);
  653. // Use array notation to prevent minification
  654. const authHeaderTokenValue = this.getAuthToken();
  655. if (authHeaderTokenValue) {
  656. this._headers.set('Authorization', authHeaderTokenValue);
  657. }
  658. if (this.iamToken) {
  659. this._headers.set('X-Goog-Iam-Authorization-Token', this.iamToken);
  660. }
  661. return this._headers;
  662. }
  663. }
  664. /*
  665. * Provides user credentials required for the Firestore JavaScript SDK
  666. * to authenticate the user, using technique that is only available
  667. * to applications hosted by Google.
  668. */
  669. class FirstPartyAuthCredentialsProvider {
  670. constructor(sessionIndex, iamToken, authTokenFactory) {
  671. this.sessionIndex = sessionIndex;
  672. this.iamToken = iamToken;
  673. this.authTokenFactory = authTokenFactory;
  674. }
  675. getToken() {
  676. return Promise.resolve(new FirstPartyToken(this.sessionIndex, this.iamToken, this.authTokenFactory));
  677. }
  678. start(asyncQueue, changeListener) {
  679. // Fire with initial uid.
  680. asyncQueue.enqueueRetryable(() => changeListener(User.FIRST_PARTY));
  681. }
  682. shutdown() { }
  683. invalidateToken() { }
  684. }
  685. class AppCheckToken {
  686. constructor(value) {
  687. this.value = value;
  688. this.type = 'AppCheck';
  689. this.headers = new Map();
  690. if (value && value.length > 0) {
  691. this.headers.set('x-firebase-appcheck', this.value);
  692. }
  693. }
  694. }
  695. class FirebaseAppCheckTokenProvider {
  696. constructor(appCheckProvider) {
  697. this.appCheckProvider = appCheckProvider;
  698. this.forceRefresh = false;
  699. this.appCheck = null;
  700. this.latestAppCheckToken = null;
  701. }
  702. start(asyncQueue, changeListener) {
  703. const onTokenChanged = tokenResult => {
  704. if (tokenResult.error != null) {
  705. logDebug('FirebaseAppCheckTokenProvider', `Error getting App Check token; using placeholder token instead. Error: ${tokenResult.error.message}`);
  706. }
  707. const tokenUpdated = tokenResult.token !== this.latestAppCheckToken;
  708. this.latestAppCheckToken = tokenResult.token;
  709. logDebug('FirebaseAppCheckTokenProvider', `Received ${tokenUpdated ? 'new' : 'existing'} token.`);
  710. return tokenUpdated
  711. ? changeListener(tokenResult.token)
  712. : Promise.resolve();
  713. };
  714. this.tokenListener = (tokenResult) => {
  715. asyncQueue.enqueueRetryable(() => onTokenChanged(tokenResult));
  716. };
  717. const registerAppCheck = (appCheck) => {
  718. logDebug('FirebaseAppCheckTokenProvider', 'AppCheck detected');
  719. this.appCheck = appCheck;
  720. this.appCheck.addTokenListener(this.tokenListener);
  721. };
  722. this.appCheckProvider.onInit(appCheck => registerAppCheck(appCheck));
  723. // Our users can initialize AppCheck after Firestore, so we give it
  724. // a chance to register itself with the component framework.
  725. setTimeout(() => {
  726. if (!this.appCheck) {
  727. const appCheck = this.appCheckProvider.getImmediate({ optional: true });
  728. if (appCheck) {
  729. registerAppCheck(appCheck);
  730. }
  731. else {
  732. // If AppCheck is still not available, proceed without it.
  733. logDebug('FirebaseAppCheckTokenProvider', 'AppCheck not yet detected');
  734. }
  735. }
  736. }, 0);
  737. }
  738. getToken() {
  739. const forceRefresh = this.forceRefresh;
  740. this.forceRefresh = false;
  741. if (!this.appCheck) {
  742. return Promise.resolve(null);
  743. }
  744. return this.appCheck.getToken(forceRefresh).then(tokenResult => {
  745. if (tokenResult) {
  746. hardAssert(typeof tokenResult.token === 'string');
  747. this.latestAppCheckToken = tokenResult.token;
  748. return new AppCheckToken(tokenResult.token);
  749. }
  750. else {
  751. return null;
  752. }
  753. });
  754. }
  755. invalidateToken() {
  756. this.forceRefresh = true;
  757. }
  758. shutdown() {
  759. if (this.appCheck) {
  760. this.appCheck.removeTokenListener(this.tokenListener);
  761. }
  762. }
  763. }
  764. /**
  765. * An AppCheck token provider that always yields an empty token.
  766. * @internal
  767. */
  768. class EmptyAppCheckTokenProvider {
  769. getToken() {
  770. return Promise.resolve(new AppCheckToken(''));
  771. }
  772. invalidateToken() { }
  773. start(asyncQueue, changeListener) { }
  774. shutdown() { }
  775. }
  776. /**
  777. * Builds a CredentialsProvider depending on the type of
  778. * the credentials passed in.
  779. */
  780. function makeAuthCredentialsProvider(credentials) {
  781. if (!credentials) {
  782. return new EmptyAuthCredentialsProvider();
  783. }
  784. switch (credentials['type']) {
  785. case 'firstParty':
  786. return new FirstPartyAuthCredentialsProvider(credentials['sessionIndex'] || '0', credentials['iamToken'] || null, credentials['authTokenFactory'] || null);
  787. case 'provider':
  788. return credentials['client'];
  789. default:
  790. throw new FirestoreError(Code.INVALID_ARGUMENT, 'makeAuthCredentialsProvider failed due to invalid credential type');
  791. }
  792. }
  793. /**
  794. * @license
  795. * Copyright 2020 Google LLC
  796. *
  797. * Licensed under the Apache License, Version 2.0 (the "License");
  798. * you may not use this file except in compliance with the License.
  799. * You may obtain a copy of the License at
  800. *
  801. * http://www.apache.org/licenses/LICENSE-2.0
  802. *
  803. * Unless required by applicable law or agreed to in writing, software
  804. * distributed under the License is distributed on an "AS IS" BASIS,
  805. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  806. * See the License for the specific language governing permissions and
  807. * limitations under the License.
  808. */
  809. /**
  810. * Generates `nBytes` of random bytes.
  811. *
  812. * If `nBytes < 0` , an error will be thrown.
  813. */
  814. function randomBytes(nBytes) {
  815. return crypto.randomBytes(nBytes);
  816. }
  817. /**
  818. * @license
  819. * Copyright 2017 Google LLC
  820. *
  821. * Licensed under the Apache License, Version 2.0 (the "License");
  822. * you may not use this file except in compliance with the License.
  823. * You may obtain a copy of the License at
  824. *
  825. * http://www.apache.org/licenses/LICENSE-2.0
  826. *
  827. * Unless required by applicable law or agreed to in writing, software
  828. * distributed under the License is distributed on an "AS IS" BASIS,
  829. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  830. * See the License for the specific language governing permissions and
  831. * limitations under the License.
  832. */
  833. class AutoId {
  834. static newId() {
  835. // Alphanumeric characters
  836. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  837. // The largest byte value that is a multiple of `char.length`.
  838. const maxMultiple = Math.floor(256 / chars.length) * chars.length;
  839. let autoId = '';
  840. const targetLength = 20;
  841. while (autoId.length < targetLength) {
  842. const bytes = randomBytes(40);
  843. for (let i = 0; i < bytes.length; ++i) {
  844. // Only accept values that are [0, maxMultiple), this ensures they can
  845. // be evenly mapped to indices of `chars` via a modulo operation.
  846. if (autoId.length < targetLength && bytes[i] < maxMultiple) {
  847. autoId += chars.charAt(bytes[i] % chars.length);
  848. }
  849. }
  850. }
  851. return autoId;
  852. }
  853. }
  854. function primitiveComparator(left, right) {
  855. if (left < right) {
  856. return -1;
  857. }
  858. if (left > right) {
  859. return 1;
  860. }
  861. return 0;
  862. }
  863. /** Helper to compare arrays using isEqual(). */
  864. function arrayEquals(left, right, comparator) {
  865. if (left.length !== right.length) {
  866. return false;
  867. }
  868. return left.every((value, index) => comparator(value, right[index]));
  869. }
  870. /**
  871. * Returns the immediate lexicographically-following string. This is useful to
  872. * construct an inclusive range for indexeddb iterators.
  873. */
  874. function immediateSuccessor(s) {
  875. // Return the input string, with an additional NUL byte appended.
  876. return s + '\0';
  877. }
  878. /**
  879. * @license
  880. * Copyright 2017 Google LLC
  881. *
  882. * Licensed under the Apache License, Version 2.0 (the "License");
  883. * you may not use this file except in compliance with the License.
  884. * You may obtain a copy of the License at
  885. *
  886. * http://www.apache.org/licenses/LICENSE-2.0
  887. *
  888. * Unless required by applicable law or agreed to in writing, software
  889. * distributed under the License is distributed on an "AS IS" BASIS,
  890. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  891. * See the License for the specific language governing permissions and
  892. * limitations under the License.
  893. */
  894. // The earliest date supported by Firestore timestamps (0001-01-01T00:00:00Z).
  895. const MIN_SECONDS = -62135596800;
  896. // Number of nanoseconds in a millisecond.
  897. const MS_TO_NANOS = 1e6;
  898. /**
  899. * A `Timestamp` represents a point in time independent of any time zone or
  900. * calendar, represented as seconds and fractions of seconds at nanosecond
  901. * resolution in UTC Epoch time.
  902. *
  903. * It is encoded using the Proleptic Gregorian Calendar which extends the
  904. * Gregorian calendar backwards to year one. It is encoded assuming all minutes
  905. * are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
  906. * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
  907. * 9999-12-31T23:59:59.999999999Z.
  908. *
  909. * For examples and further specifications, refer to the
  910. * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}.
  911. */
  912. class Timestamp {
  913. /**
  914. * Creates a new timestamp.
  915. *
  916. * @param seconds - The number of seconds of UTC time since Unix epoch
  917. * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
  918. * 9999-12-31T23:59:59Z inclusive.
  919. * @param nanoseconds - The non-negative fractions of a second at nanosecond
  920. * resolution. Negative second values with fractions must still have
  921. * non-negative nanoseconds values that count forward in time. Must be
  922. * from 0 to 999,999,999 inclusive.
  923. */
  924. constructor(
  925. /**
  926. * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
  927. */
  928. seconds,
  929. /**
  930. * The fractions of a second at nanosecond resolution.*
  931. */
  932. nanoseconds) {
  933. this.seconds = seconds;
  934. this.nanoseconds = nanoseconds;
  935. if (nanoseconds < 0) {
  936. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
  937. }
  938. if (nanoseconds >= 1e9) {
  939. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
  940. }
  941. if (seconds < MIN_SECONDS) {
  942. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
  943. }
  944. // This will break in the year 10,000.
  945. if (seconds >= 253402300800) {
  946. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
  947. }
  948. }
  949. /**
  950. * Creates a new timestamp with the current date, with millisecond precision.
  951. *
  952. * @returns a new timestamp representing the current date.
  953. */
  954. static now() {
  955. return Timestamp.fromMillis(Date.now());
  956. }
  957. /**
  958. * Creates a new timestamp from the given date.
  959. *
  960. * @param date - The date to initialize the `Timestamp` from.
  961. * @returns A new `Timestamp` representing the same point in time as the given
  962. * date.
  963. */
  964. static fromDate(date) {
  965. return Timestamp.fromMillis(date.getTime());
  966. }
  967. /**
  968. * Creates a new timestamp from the given number of milliseconds.
  969. *
  970. * @param milliseconds - Number of milliseconds since Unix epoch
  971. * 1970-01-01T00:00:00Z.
  972. * @returns A new `Timestamp` representing the same point in time as the given
  973. * number of milliseconds.
  974. */
  975. static fromMillis(milliseconds) {
  976. const seconds = Math.floor(milliseconds / 1000);
  977. const nanos = Math.floor((milliseconds - seconds * 1000) * MS_TO_NANOS);
  978. return new Timestamp(seconds, nanos);
  979. }
  980. /**
  981. * Converts a `Timestamp` to a JavaScript `Date` object. This conversion
  982. * causes a loss of precision since `Date` objects only support millisecond
  983. * precision.
  984. *
  985. * @returns JavaScript `Date` object representing the same point in time as
  986. * this `Timestamp`, with millisecond precision.
  987. */
  988. toDate() {
  989. return new Date(this.toMillis());
  990. }
  991. /**
  992. * Converts a `Timestamp` to a numeric timestamp (in milliseconds since
  993. * epoch). This operation causes a loss of precision.
  994. *
  995. * @returns The point in time corresponding to this timestamp, represented as
  996. * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
  997. */
  998. toMillis() {
  999. return this.seconds * 1000 + this.nanoseconds / MS_TO_NANOS;
  1000. }
  1001. _compareTo(other) {
  1002. if (this.seconds === other.seconds) {
  1003. return primitiveComparator(this.nanoseconds, other.nanoseconds);
  1004. }
  1005. return primitiveComparator(this.seconds, other.seconds);
  1006. }
  1007. /**
  1008. * Returns true if this `Timestamp` is equal to the provided one.
  1009. *
  1010. * @param other - The `Timestamp` to compare against.
  1011. * @returns true if this `Timestamp` is equal to the provided one.
  1012. */
  1013. isEqual(other) {
  1014. return (other.seconds === this.seconds && other.nanoseconds === this.nanoseconds);
  1015. }
  1016. /** Returns a textual representation of this `Timestamp`. */
  1017. toString() {
  1018. return ('Timestamp(seconds=' +
  1019. this.seconds +
  1020. ', nanoseconds=' +
  1021. this.nanoseconds +
  1022. ')');
  1023. }
  1024. /** Returns a JSON-serializable representation of this `Timestamp`. */
  1025. toJSON() {
  1026. return { seconds: this.seconds, nanoseconds: this.nanoseconds };
  1027. }
  1028. /**
  1029. * Converts this object to a primitive string, which allows `Timestamp` objects
  1030. * to be compared using the `>`, `<=`, `>=` and `>` operators.
  1031. */
  1032. valueOf() {
  1033. // This method returns a string of the form <seconds>.<nanoseconds> where
  1034. // <seconds> is translated to have a non-negative value and both <seconds>
  1035. // and <nanoseconds> are left-padded with zeroes to be a consistent length.
  1036. // Strings with this format then have a lexiographical ordering that matches
  1037. // the expected ordering. The <seconds> translation is done to avoid having
  1038. // a leading negative sign (i.e. a leading '-' character) in its string
  1039. // representation, which would affect its lexiographical ordering.
  1040. const adjustedSeconds = this.seconds - MIN_SECONDS;
  1041. // Note: Up to 12 decimal digits are required to represent all valid
  1042. // 'seconds' values.
  1043. const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
  1044. const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
  1045. return formattedSeconds + '.' + formattedNanoseconds;
  1046. }
  1047. }
  1048. /**
  1049. * @license
  1050. * Copyright 2017 Google LLC
  1051. *
  1052. * Licensed under the Apache License, Version 2.0 (the "License");
  1053. * you may not use this file except in compliance with the License.
  1054. * You may obtain a copy of the License at
  1055. *
  1056. * http://www.apache.org/licenses/LICENSE-2.0
  1057. *
  1058. * Unless required by applicable law or agreed to in writing, software
  1059. * distributed under the License is distributed on an "AS IS" BASIS,
  1060. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1061. * See the License for the specific language governing permissions and
  1062. * limitations under the License.
  1063. */
  1064. /**
  1065. * A version of a document in Firestore. This corresponds to the version
  1066. * timestamp, such as update_time or read_time.
  1067. */
  1068. class SnapshotVersion {
  1069. constructor(timestamp) {
  1070. this.timestamp = timestamp;
  1071. }
  1072. static fromTimestamp(value) {
  1073. return new SnapshotVersion(value);
  1074. }
  1075. static min() {
  1076. return new SnapshotVersion(new Timestamp(0, 0));
  1077. }
  1078. static max() {
  1079. return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
  1080. }
  1081. compareTo(other) {
  1082. return this.timestamp._compareTo(other.timestamp);
  1083. }
  1084. isEqual(other) {
  1085. return this.timestamp.isEqual(other.timestamp);
  1086. }
  1087. /** Returns a number representation of the version for use in spec tests. */
  1088. toMicroseconds() {
  1089. // Convert to microseconds.
  1090. return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;
  1091. }
  1092. toString() {
  1093. return 'SnapshotVersion(' + this.timestamp.toString() + ')';
  1094. }
  1095. toTimestamp() {
  1096. return this.timestamp;
  1097. }
  1098. }
  1099. /**
  1100. * @license
  1101. * Copyright 2017 Google LLC
  1102. *
  1103. * Licensed under the Apache License, Version 2.0 (the "License");
  1104. * you may not use this file except in compliance with the License.
  1105. * You may obtain a copy of the License at
  1106. *
  1107. * http://www.apache.org/licenses/LICENSE-2.0
  1108. *
  1109. * Unless required by applicable law or agreed to in writing, software
  1110. * distributed under the License is distributed on an "AS IS" BASIS,
  1111. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1112. * See the License for the specific language governing permissions and
  1113. * limitations under the License.
  1114. */
  1115. const DOCUMENT_KEY_NAME = '__name__';
  1116. /**
  1117. * Path represents an ordered sequence of string segments.
  1118. */
  1119. class BasePath {
  1120. constructor(segments, offset, length) {
  1121. if (offset === undefined) {
  1122. offset = 0;
  1123. }
  1124. else if (offset > segments.length) {
  1125. fail();
  1126. }
  1127. if (length === undefined) {
  1128. length = segments.length - offset;
  1129. }
  1130. else if (length > segments.length - offset) {
  1131. fail();
  1132. }
  1133. this.segments = segments;
  1134. this.offset = offset;
  1135. this.len = length;
  1136. }
  1137. get length() {
  1138. return this.len;
  1139. }
  1140. isEqual(other) {
  1141. return BasePath.comparator(this, other) === 0;
  1142. }
  1143. child(nameOrPath) {
  1144. const segments = this.segments.slice(this.offset, this.limit());
  1145. if (nameOrPath instanceof BasePath) {
  1146. nameOrPath.forEach(segment => {
  1147. segments.push(segment);
  1148. });
  1149. }
  1150. else {
  1151. segments.push(nameOrPath);
  1152. }
  1153. return this.construct(segments);
  1154. }
  1155. /** The index of one past the last segment of the path. */
  1156. limit() {
  1157. return this.offset + this.length;
  1158. }
  1159. popFirst(size) {
  1160. size = size === undefined ? 1 : size;
  1161. return this.construct(this.segments, this.offset + size, this.length - size);
  1162. }
  1163. popLast() {
  1164. return this.construct(this.segments, this.offset, this.length - 1);
  1165. }
  1166. firstSegment() {
  1167. return this.segments[this.offset];
  1168. }
  1169. lastSegment() {
  1170. return this.get(this.length - 1);
  1171. }
  1172. get(index) {
  1173. return this.segments[this.offset + index];
  1174. }
  1175. isEmpty() {
  1176. return this.length === 0;
  1177. }
  1178. isPrefixOf(other) {
  1179. if (other.length < this.length) {
  1180. return false;
  1181. }
  1182. for (let i = 0; i < this.length; i++) {
  1183. if (this.get(i) !== other.get(i)) {
  1184. return false;
  1185. }
  1186. }
  1187. return true;
  1188. }
  1189. isImmediateParentOf(potentialChild) {
  1190. if (this.length + 1 !== potentialChild.length) {
  1191. return false;
  1192. }
  1193. for (let i = 0; i < this.length; i++) {
  1194. if (this.get(i) !== potentialChild.get(i)) {
  1195. return false;
  1196. }
  1197. }
  1198. return true;
  1199. }
  1200. forEach(fn) {
  1201. for (let i = this.offset, end = this.limit(); i < end; i++) {
  1202. fn(this.segments[i]);
  1203. }
  1204. }
  1205. toArray() {
  1206. return this.segments.slice(this.offset, this.limit());
  1207. }
  1208. static comparator(p1, p2) {
  1209. const len = Math.min(p1.length, p2.length);
  1210. for (let i = 0; i < len; i++) {
  1211. const left = p1.get(i);
  1212. const right = p2.get(i);
  1213. if (left < right) {
  1214. return -1;
  1215. }
  1216. if (left > right) {
  1217. return 1;
  1218. }
  1219. }
  1220. if (p1.length < p2.length) {
  1221. return -1;
  1222. }
  1223. if (p1.length > p2.length) {
  1224. return 1;
  1225. }
  1226. return 0;
  1227. }
  1228. }
  1229. /**
  1230. * A slash-separated path for navigating resources (documents and collections)
  1231. * within Firestore.
  1232. *
  1233. * @internal
  1234. */
  1235. class ResourcePath extends BasePath {
  1236. construct(segments, offset, length) {
  1237. return new ResourcePath(segments, offset, length);
  1238. }
  1239. canonicalString() {
  1240. // NOTE: The client is ignorant of any path segments containing escape
  1241. // sequences (e.g. __id123__) and just passes them through raw (they exist
  1242. // for legacy reasons and should not be used frequently).
  1243. return this.toArray().join('/');
  1244. }
  1245. toString() {
  1246. return this.canonicalString();
  1247. }
  1248. /**
  1249. * Creates a resource path from the given slash-delimited string. If multiple
  1250. * arguments are provided, all components are combined. Leading and trailing
  1251. * slashes from all components are ignored.
  1252. */
  1253. static fromString(...pathComponents) {
  1254. // NOTE: The client is ignorant of any path segments containing escape
  1255. // sequences (e.g. __id123__) and just passes them through raw (they exist
  1256. // for legacy reasons and should not be used frequently).
  1257. const segments = [];
  1258. for (const path of pathComponents) {
  1259. if (path.indexOf('//') >= 0) {
  1260. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid segment (${path}). Paths must not contain // in them.`);
  1261. }
  1262. // Strip leading and traling slashed.
  1263. segments.push(...path.split('/').filter(segment => segment.length > 0));
  1264. }
  1265. return new ResourcePath(segments);
  1266. }
  1267. static emptyPath() {
  1268. return new ResourcePath([]);
  1269. }
  1270. }
  1271. const identifierRegExp = /^[_a-zA-Z][_a-zA-Z0-9]*$/;
  1272. /**
  1273. * A dot-separated path for navigating sub-objects within a document.
  1274. * @internal
  1275. */
  1276. class FieldPath$1 extends BasePath {
  1277. construct(segments, offset, length) {
  1278. return new FieldPath$1(segments, offset, length);
  1279. }
  1280. /**
  1281. * Returns true if the string could be used as a segment in a field path
  1282. * without escaping.
  1283. */
  1284. static isValidIdentifier(segment) {
  1285. return identifierRegExp.test(segment);
  1286. }
  1287. canonicalString() {
  1288. return this.toArray()
  1289. .map(str => {
  1290. str = str.replace(/\\/g, '\\\\').replace(/`/g, '\\`');
  1291. if (!FieldPath$1.isValidIdentifier(str)) {
  1292. str = '`' + str + '`';
  1293. }
  1294. return str;
  1295. })
  1296. .join('.');
  1297. }
  1298. toString() {
  1299. return this.canonicalString();
  1300. }
  1301. /**
  1302. * Returns true if this field references the key of a document.
  1303. */
  1304. isKeyField() {
  1305. return this.length === 1 && this.get(0) === DOCUMENT_KEY_NAME;
  1306. }
  1307. /**
  1308. * The field designating the key of a document.
  1309. */
  1310. static keyField() {
  1311. return new FieldPath$1([DOCUMENT_KEY_NAME]);
  1312. }
  1313. /**
  1314. * Parses a field string from the given server-formatted string.
  1315. *
  1316. * - Splitting the empty string is not allowed (for now at least).
  1317. * - Empty segments within the string (e.g. if there are two consecutive
  1318. * separators) are not allowed.
  1319. *
  1320. * TODO(b/37244157): we should make this more strict. Right now, it allows
  1321. * non-identifier path components, even if they aren't escaped.
  1322. */
  1323. static fromServerFormat(path) {
  1324. const segments = [];
  1325. let current = '';
  1326. let i = 0;
  1327. const addCurrentSegment = () => {
  1328. if (current.length === 0) {
  1329. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid field path (${path}). Paths must not be empty, begin ` +
  1330. `with '.', end with '.', or contain '..'`);
  1331. }
  1332. segments.push(current);
  1333. current = '';
  1334. };
  1335. let inBackticks = false;
  1336. while (i < path.length) {
  1337. const c = path[i];
  1338. if (c === '\\') {
  1339. if (i + 1 === path.length) {
  1340. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has trailing escape character: ' + path);
  1341. }
  1342. const next = path[i + 1];
  1343. if (!(next === '\\' || next === '.' || next === '`')) {
  1344. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has invalid escape sequence: ' + path);
  1345. }
  1346. current += next;
  1347. i += 2;
  1348. }
  1349. else if (c === '`') {
  1350. inBackticks = !inBackticks;
  1351. i++;
  1352. }
  1353. else if (c === '.' && !inBackticks) {
  1354. addCurrentSegment();
  1355. i++;
  1356. }
  1357. else {
  1358. current += c;
  1359. i++;
  1360. }
  1361. }
  1362. addCurrentSegment();
  1363. if (inBackticks) {
  1364. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Unterminated ` in path: ' + path);
  1365. }
  1366. return new FieldPath$1(segments);
  1367. }
  1368. static emptyPath() {
  1369. return new FieldPath$1([]);
  1370. }
  1371. }
  1372. /**
  1373. * @license
  1374. * Copyright 2017 Google LLC
  1375. *
  1376. * Licensed under the Apache License, Version 2.0 (the "License");
  1377. * you may not use this file except in compliance with the License.
  1378. * You may obtain a copy of the License at
  1379. *
  1380. * http://www.apache.org/licenses/LICENSE-2.0
  1381. *
  1382. * Unless required by applicable law or agreed to in writing, software
  1383. * distributed under the License is distributed on an "AS IS" BASIS,
  1384. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1385. * See the License for the specific language governing permissions and
  1386. * limitations under the License.
  1387. */
  1388. /**
  1389. * @internal
  1390. */
  1391. class DocumentKey {
  1392. constructor(path) {
  1393. this.path = path;
  1394. }
  1395. static fromPath(path) {
  1396. return new DocumentKey(ResourcePath.fromString(path));
  1397. }
  1398. static fromName(name) {
  1399. return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
  1400. }
  1401. static empty() {
  1402. return new DocumentKey(ResourcePath.emptyPath());
  1403. }
  1404. get collectionGroup() {
  1405. return this.path.popLast().lastSegment();
  1406. }
  1407. /** Returns true if the document is in the specified collectionId. */
  1408. hasCollectionId(collectionId) {
  1409. return (this.path.length >= 2 &&
  1410. this.path.get(this.path.length - 2) === collectionId);
  1411. }
  1412. /** Returns the collection group (i.e. the name of the parent collection) for this key. */
  1413. getCollectionGroup() {
  1414. return this.path.get(this.path.length - 2);
  1415. }
  1416. /** Returns the fully qualified path to the parent collection. */
  1417. getCollectionPath() {
  1418. return this.path.popLast();
  1419. }
  1420. isEqual(other) {
  1421. return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
  1422. }
  1423. toString() {
  1424. return this.path.toString();
  1425. }
  1426. static comparator(k1, k2) {
  1427. return ResourcePath.comparator(k1.path, k2.path);
  1428. }
  1429. static isDocumentKey(path) {
  1430. return path.length % 2 === 0;
  1431. }
  1432. /**
  1433. * Creates and returns a new document key with the given segments.
  1434. *
  1435. * @param segments - The segments of the path to the document
  1436. * @returns A new instance of DocumentKey
  1437. */
  1438. static fromSegments(segments) {
  1439. return new DocumentKey(new ResourcePath(segments.slice()));
  1440. }
  1441. }
  1442. /**
  1443. * @license
  1444. * Copyright 2021 Google LLC
  1445. *
  1446. * Licensed under the Apache License, Version 2.0 (the "License");
  1447. * you may not use this file except in compliance with the License.
  1448. * You may obtain a copy of the License at
  1449. *
  1450. * http://www.apache.org/licenses/LICENSE-2.0
  1451. *
  1452. * Unless required by applicable law or agreed to in writing, software
  1453. * distributed under the License is distributed on an "AS IS" BASIS,
  1454. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1455. * See the License for the specific language governing permissions and
  1456. * limitations under the License.
  1457. */
  1458. /**
  1459. * The initial mutation batch id for each index. Gets updated during index
  1460. * backfill.
  1461. */
  1462. const INITIAL_LARGEST_BATCH_ID = -1;
  1463. /**
  1464. * The initial sequence number for each index. Gets updated during index
  1465. * backfill.
  1466. */
  1467. const INITIAL_SEQUENCE_NUMBER = 0;
  1468. /**
  1469. * An index definition for field indexes in Firestore.
  1470. *
  1471. * Every index is associated with a collection. The definition contains a list
  1472. * of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
  1473. * `CONTAINS` for ArrayContains/ArrayContainsAny queries).
  1474. *
  1475. * Unlike the backend, the SDK does not differentiate between collection or
  1476. * collection group-scoped indices. Every index can be used for both single
  1477. * collection and collection group queries.
  1478. */
  1479. class FieldIndex {
  1480. constructor(
  1481. /**
  1482. * The index ID. Returns -1 if the index ID is not available (e.g. the index
  1483. * has not yet been persisted).
  1484. */
  1485. indexId,
  1486. /** The collection ID this index applies to. */
  1487. collectionGroup,
  1488. /** The field segments for this index. */
  1489. fields,
  1490. /** Shows how up-to-date the index is for the current user. */
  1491. indexState) {
  1492. this.indexId = indexId;
  1493. this.collectionGroup = collectionGroup;
  1494. this.fields = fields;
  1495. this.indexState = indexState;
  1496. }
  1497. }
  1498. /** An ID for an index that has not yet been added to persistence. */
  1499. FieldIndex.UNKNOWN_ID = -1;
  1500. /** Returns the ArrayContains/ArrayContainsAny segment for this index. */
  1501. function fieldIndexGetArraySegment(fieldIndex) {
  1502. return fieldIndex.fields.find(s => s.kind === 2 /* IndexKind.CONTAINS */);
  1503. }
  1504. /** Returns all directional (ascending/descending) segments for this index. */
  1505. function fieldIndexGetDirectionalSegments(fieldIndex) {
  1506. return fieldIndex.fields.filter(s => s.kind !== 2 /* IndexKind.CONTAINS */);
  1507. }
  1508. /**
  1509. * Returns the order of the document key component for the given index.
  1510. *
  1511. * PORTING NOTE: This is only used in the Web IndexedDb implementation.
  1512. */
  1513. function fieldIndexGetKeyOrder(fieldIndex) {
  1514. const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
  1515. return directionalSegments.length === 0
  1516. ? 0 /* IndexKind.ASCENDING */
  1517. : directionalSegments[directionalSegments.length - 1].kind;
  1518. }
  1519. /**
  1520. * Compares indexes by collection group and segments. Ignores update time and
  1521. * index ID.
  1522. */
  1523. function fieldIndexSemanticComparator(left, right) {
  1524. let cmp = primitiveComparator(left.collectionGroup, right.collectionGroup);
  1525. if (cmp !== 0) {
  1526. return cmp;
  1527. }
  1528. for (let i = 0; i < Math.min(left.fields.length, right.fields.length); ++i) {
  1529. cmp = indexSegmentComparator(left.fields[i], right.fields[i]);
  1530. if (cmp !== 0) {
  1531. return cmp;
  1532. }
  1533. }
  1534. return primitiveComparator(left.fields.length, right.fields.length);
  1535. }
  1536. /** Returns a debug representation of the field index */
  1537. function fieldIndexToString(fieldIndex) {
  1538. return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
  1539. }
  1540. /** An index component consisting of field path and index type. */
  1541. class IndexSegment {
  1542. constructor(
  1543. /** The field path of the component. */
  1544. fieldPath,
  1545. /** The fields sorting order. */
  1546. kind) {
  1547. this.fieldPath = fieldPath;
  1548. this.kind = kind;
  1549. }
  1550. }
  1551. function indexSegmentComparator(left, right) {
  1552. const cmp = FieldPath$1.comparator(left.fieldPath, right.fieldPath);
  1553. if (cmp !== 0) {
  1554. return cmp;
  1555. }
  1556. return primitiveComparator(left.kind, right.kind);
  1557. }
  1558. /**
  1559. * Stores the "high water mark" that indicates how updated the Index is for the
  1560. * current user.
  1561. */
  1562. class IndexState {
  1563. constructor(
  1564. /**
  1565. * Indicates when the index was last updated (relative to other indexes).
  1566. */
  1567. sequenceNumber,
  1568. /** The the latest indexed read time, document and batch id. */
  1569. offset) {
  1570. this.sequenceNumber = sequenceNumber;
  1571. this.offset = offset;
  1572. }
  1573. /** The state of an index that has not yet been backfilled. */
  1574. static empty() {
  1575. return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
  1576. }
  1577. }
  1578. /**
  1579. * Creates an offset that matches all documents with a read time higher than
  1580. * `readTime`.
  1581. */
  1582. function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
  1583. // We want to create an offset that matches all documents with a read time
  1584. // greater than the provided read time. To do so, we technically need to
  1585. // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
  1586. // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
  1587. // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
  1588. // all valid document IDs.
  1589. const successorSeconds = readTime.toTimestamp().seconds;
  1590. const successorNanos = readTime.toTimestamp().nanoseconds + 1;
  1591. const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
  1592. ? new Timestamp(successorSeconds + 1, 0)
  1593. : new Timestamp(successorSeconds, successorNanos));
  1594. return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
  1595. }
  1596. /** Creates a new offset based on the provided document. */
  1597. function newIndexOffsetFromDocument(document) {
  1598. return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
  1599. }
  1600. /**
  1601. * Stores the latest read time, document and batch ID that were processed for an
  1602. * index.
  1603. */
  1604. class IndexOffset {
  1605. constructor(
  1606. /**
  1607. * The latest read time version that has been indexed by Firestore for this
  1608. * field index.
  1609. */
  1610. readTime,
  1611. /**
  1612. * The key of the last document that was indexed for this query. Use
  1613. * `DocumentKey.empty()` if no document has been indexed.
  1614. */
  1615. documentKey,
  1616. /*
  1617. * The largest mutation batch id that's been processed by Firestore.
  1618. */
  1619. largestBatchId) {
  1620. this.readTime = readTime;
  1621. this.documentKey = documentKey;
  1622. this.largestBatchId = largestBatchId;
  1623. }
  1624. /** Returns an offset that sorts before all regular offsets. */
  1625. static min() {
  1626. return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
  1627. }
  1628. /** Returns an offset that sorts after all regular offsets. */
  1629. static max() {
  1630. return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
  1631. }
  1632. }
  1633. function indexOffsetComparator(left, right) {
  1634. let cmp = left.readTime.compareTo(right.readTime);
  1635. if (cmp !== 0) {
  1636. return cmp;
  1637. }
  1638. cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
  1639. if (cmp !== 0) {
  1640. return cmp;
  1641. }
  1642. return primitiveComparator(left.largestBatchId, right.largestBatchId);
  1643. }
  1644. /**
  1645. * @license
  1646. * Copyright 2020 Google LLC
  1647. *
  1648. * Licensed under the Apache License, Version 2.0 (the "License");
  1649. * you may not use this file except in compliance with the License.
  1650. * You may obtain a copy of the License at
  1651. *
  1652. * http://www.apache.org/licenses/LICENSE-2.0
  1653. *
  1654. * Unless required by applicable law or agreed to in writing, software
  1655. * distributed under the License is distributed on an "AS IS" BASIS,
  1656. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1657. * See the License for the specific language governing permissions and
  1658. * limitations under the License.
  1659. */
  1660. const PRIMARY_LEASE_LOST_ERROR_MSG = 'The current tab is not in the required state to perform this operation. ' +
  1661. 'It might be necessary to refresh the browser tab.';
  1662. /**
  1663. * A base class representing a persistence transaction, encapsulating both the
  1664. * transaction's sequence numbers as well as a list of onCommitted listeners.
  1665. *
  1666. * When you call Persistence.runTransaction(), it will create a transaction and
  1667. * pass it to your callback. You then pass it to any method that operates
  1668. * on persistence.
  1669. */
  1670. class PersistenceTransaction {
  1671. constructor() {
  1672. this.onCommittedListeners = [];
  1673. }
  1674. addOnCommittedListener(listener) {
  1675. this.onCommittedListeners.push(listener);
  1676. }
  1677. raiseOnCommittedEvent() {
  1678. this.onCommittedListeners.forEach(listener => listener());
  1679. }
  1680. }
  1681. /**
  1682. * @license
  1683. * Copyright 2017 Google LLC
  1684. *
  1685. * Licensed under the Apache License, Version 2.0 (the "License");
  1686. * you may not use this file except in compliance with the License.
  1687. * You may obtain a copy of the License at
  1688. *
  1689. * http://www.apache.org/licenses/LICENSE-2.0
  1690. *
  1691. * Unless required by applicable law or agreed to in writing, software
  1692. * distributed under the License is distributed on an "AS IS" BASIS,
  1693. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1694. * See the License for the specific language governing permissions and
  1695. * limitations under the License.
  1696. */
  1697. /**
  1698. * Verifies the error thrown by a LocalStore operation. If a LocalStore
  1699. * operation fails because the primary lease has been taken by another client,
  1700. * we ignore the error (the persistence layer will immediately call
  1701. * `applyPrimaryLease` to propagate the primary state change). All other errors
  1702. * are re-thrown.
  1703. *
  1704. * @param err - An error returned by a LocalStore operation.
  1705. * @returns A Promise that resolves after we recovered, or the original error.
  1706. */
  1707. async function ignoreIfPrimaryLeaseLoss(err) {
  1708. if (err.code === Code.FAILED_PRECONDITION &&
  1709. err.message === PRIMARY_LEASE_LOST_ERROR_MSG) {
  1710. logDebug('LocalStore', 'Unexpectedly lost primary lease');
  1711. }
  1712. else {
  1713. throw err;
  1714. }
  1715. }
  1716. /**
  1717. * @license
  1718. * Copyright 2017 Google LLC
  1719. *
  1720. * Licensed under the Apache License, Version 2.0 (the "License");
  1721. * you may not use this file except in compliance with the License.
  1722. * You may obtain a copy of the License at
  1723. *
  1724. * http://www.apache.org/licenses/LICENSE-2.0
  1725. *
  1726. * Unless required by applicable law or agreed to in writing, software
  1727. * distributed under the License is distributed on an "AS IS" BASIS,
  1728. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1729. * See the License for the specific language governing permissions and
  1730. * limitations under the License.
  1731. */
  1732. /**
  1733. * PersistencePromise is essentially a re-implementation of Promise except
  1734. * it has a .next() method instead of .then() and .next() and .catch() callbacks
  1735. * are executed synchronously when a PersistencePromise resolves rather than
  1736. * asynchronously (Promise implementations use setImmediate() or similar).
  1737. *
  1738. * This is necessary to interoperate with IndexedDB which will automatically
  1739. * commit transactions if control is returned to the event loop without
  1740. * synchronously initiating another operation on the transaction.
  1741. *
  1742. * NOTE: .then() and .catch() only allow a single consumer, unlike normal
  1743. * Promises.
  1744. */
  1745. class PersistencePromise {
  1746. constructor(callback) {
  1747. // NOTE: next/catchCallback will always point to our own wrapper functions,
  1748. // not the user's raw next() or catch() callbacks.
  1749. this.nextCallback = null;
  1750. this.catchCallback = null;
  1751. // When the operation resolves, we'll set result or error and mark isDone.
  1752. this.result = undefined;
  1753. this.error = undefined;
  1754. this.isDone = false;
  1755. // Set to true when .then() or .catch() are called and prevents additional
  1756. // chaining.
  1757. this.callbackAttached = false;
  1758. callback(value => {
  1759. this.isDone = true;
  1760. this.result = value;
  1761. if (this.nextCallback) {
  1762. // value should be defined unless T is Void, but we can't express
  1763. // that in the type system.
  1764. this.nextCallback(value);
  1765. }
  1766. }, error => {
  1767. this.isDone = true;
  1768. this.error = error;
  1769. if (this.catchCallback) {
  1770. this.catchCallback(error);
  1771. }
  1772. });
  1773. }
  1774. catch(fn) {
  1775. return this.next(undefined, fn);
  1776. }
  1777. next(nextFn, catchFn) {
  1778. if (this.callbackAttached) {
  1779. fail();
  1780. }
  1781. this.callbackAttached = true;
  1782. if (this.isDone) {
  1783. if (!this.error) {
  1784. return this.wrapSuccess(nextFn, this.result);
  1785. }
  1786. else {
  1787. return this.wrapFailure(catchFn, this.error);
  1788. }
  1789. }
  1790. else {
  1791. return new PersistencePromise((resolve, reject) => {
  1792. this.nextCallback = (value) => {
  1793. this.wrapSuccess(nextFn, value).next(resolve, reject);
  1794. };
  1795. this.catchCallback = (error) => {
  1796. this.wrapFailure(catchFn, error).next(resolve, reject);
  1797. };
  1798. });
  1799. }
  1800. }
  1801. toPromise() {
  1802. return new Promise((resolve, reject) => {
  1803. this.next(resolve, reject);
  1804. });
  1805. }
  1806. wrapUserFunction(fn) {
  1807. try {
  1808. const result = fn();
  1809. if (result instanceof PersistencePromise) {
  1810. return result;
  1811. }
  1812. else {
  1813. return PersistencePromise.resolve(result);
  1814. }
  1815. }
  1816. catch (e) {
  1817. return PersistencePromise.reject(e);
  1818. }
  1819. }
  1820. wrapSuccess(nextFn, value) {
  1821. if (nextFn) {
  1822. return this.wrapUserFunction(() => nextFn(value));
  1823. }
  1824. else {
  1825. // If there's no nextFn, then R must be the same as T
  1826. return PersistencePromise.resolve(value);
  1827. }
  1828. }
  1829. wrapFailure(catchFn, error) {
  1830. if (catchFn) {
  1831. return this.wrapUserFunction(() => catchFn(error));
  1832. }
  1833. else {
  1834. return PersistencePromise.reject(error);
  1835. }
  1836. }
  1837. static resolve(result) {
  1838. return new PersistencePromise((resolve, reject) => {
  1839. resolve(result);
  1840. });
  1841. }
  1842. static reject(error) {
  1843. return new PersistencePromise((resolve, reject) => {
  1844. reject(error);
  1845. });
  1846. }
  1847. static waitFor(
  1848. // Accept all Promise types in waitFor().
  1849. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  1850. all) {
  1851. return new PersistencePromise((resolve, reject) => {
  1852. let expectedCount = 0;
  1853. let resolvedCount = 0;
  1854. let done = false;
  1855. all.forEach(element => {
  1856. ++expectedCount;
  1857. element.next(() => {
  1858. ++resolvedCount;
  1859. if (done && resolvedCount === expectedCount) {
  1860. resolve();
  1861. }
  1862. }, err => reject(err));
  1863. });
  1864. done = true;
  1865. if (resolvedCount === expectedCount) {
  1866. resolve();
  1867. }
  1868. });
  1869. }
  1870. /**
  1871. * Given an array of predicate functions that asynchronously evaluate to a
  1872. * boolean, implements a short-circuiting `or` between the results. Predicates
  1873. * will be evaluated until one of them returns `true`, then stop. The final
  1874. * result will be whether any of them returned `true`.
  1875. */
  1876. static or(predicates) {
  1877. let p = PersistencePromise.resolve(false);
  1878. for (const predicate of predicates) {
  1879. p = p.next(isTrue => {
  1880. if (isTrue) {
  1881. return PersistencePromise.resolve(isTrue);
  1882. }
  1883. else {
  1884. return predicate();
  1885. }
  1886. });
  1887. }
  1888. return p;
  1889. }
  1890. static forEach(collection, f) {
  1891. const promises = [];
  1892. collection.forEach((r, s) => {
  1893. promises.push(f.call(this, r, s));
  1894. });
  1895. return this.waitFor(promises);
  1896. }
  1897. /**
  1898. * Concurrently map all array elements through asynchronous function.
  1899. */
  1900. static mapArray(array, f) {
  1901. return new PersistencePromise((resolve, reject) => {
  1902. const expectedCount = array.length;
  1903. const results = new Array(expectedCount);
  1904. let resolvedCount = 0;
  1905. for (let i = 0; i < expectedCount; i++) {
  1906. const current = i;
  1907. f(array[current]).next(result => {
  1908. results[current] = result;
  1909. ++resolvedCount;
  1910. if (resolvedCount === expectedCount) {
  1911. resolve(results);
  1912. }
  1913. }, err => reject(err));
  1914. }
  1915. });
  1916. }
  1917. /**
  1918. * An alternative to recursive PersistencePromise calls, that avoids
  1919. * potential memory problems from unbounded chains of promises.
  1920. *
  1921. * The `action` will be called repeatedly while `condition` is true.
  1922. */
  1923. static doWhile(condition, action) {
  1924. return new PersistencePromise((resolve, reject) => {
  1925. const process = () => {
  1926. if (condition() === true) {
  1927. action().next(() => {
  1928. process();
  1929. }, reject);
  1930. }
  1931. else {
  1932. resolve();
  1933. }
  1934. };
  1935. process();
  1936. });
  1937. }
  1938. }
  1939. /**
  1940. * @license
  1941. * Copyright 2017 Google LLC
  1942. *
  1943. * Licensed under the Apache License, Version 2.0 (the "License");
  1944. * you may not use this file except in compliance with the License.
  1945. * You may obtain a copy of the License at
  1946. *
  1947. * http://www.apache.org/licenses/LICENSE-2.0
  1948. *
  1949. * Unless required by applicable law or agreed to in writing, software
  1950. * distributed under the License is distributed on an "AS IS" BASIS,
  1951. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1952. * See the License for the specific language governing permissions and
  1953. * limitations under the License.
  1954. */
  1955. // References to `window` are guarded by SimpleDb.isAvailable()
  1956. /* eslint-disable no-restricted-globals */
  1957. const LOG_TAG$i = 'SimpleDb';
  1958. /**
  1959. * The maximum number of retry attempts for an IndexedDb transaction that fails
  1960. * with a DOMException.
  1961. */
  1962. const TRANSACTION_RETRY_COUNT = 3;
  1963. /**
  1964. * Wraps an IDBTransaction and exposes a store() method to get a handle to a
  1965. * specific object store.
  1966. */
  1967. class SimpleDbTransaction {
  1968. constructor(action, transaction) {
  1969. this.action = action;
  1970. this.transaction = transaction;
  1971. this.aborted = false;
  1972. /**
  1973. * A `Promise` that resolves with the result of the IndexedDb transaction.
  1974. */
  1975. this.completionDeferred = new Deferred();
  1976. this.transaction.oncomplete = () => {
  1977. this.completionDeferred.resolve();
  1978. };
  1979. this.transaction.onabort = () => {
  1980. if (transaction.error) {
  1981. this.completionDeferred.reject(new IndexedDbTransactionError(action, transaction.error));
  1982. }
  1983. else {
  1984. this.completionDeferred.resolve();
  1985. }
  1986. };
  1987. this.transaction.onerror = (event) => {
  1988. const error = checkForAndReportiOSError(event.target.error);
  1989. this.completionDeferred.reject(new IndexedDbTransactionError(action, error));
  1990. };
  1991. }
  1992. static open(db, action, mode, objectStoreNames) {
  1993. try {
  1994. return new SimpleDbTransaction(action, db.transaction(objectStoreNames, mode));
  1995. }
  1996. catch (e) {
  1997. throw new IndexedDbTransactionError(action, e);
  1998. }
  1999. }
  2000. get completionPromise() {
  2001. return this.completionDeferred.promise;
  2002. }
  2003. abort(error) {
  2004. if (error) {
  2005. this.completionDeferred.reject(error);
  2006. }
  2007. if (!this.aborted) {
  2008. logDebug(LOG_TAG$i, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
  2009. this.aborted = true;
  2010. this.transaction.abort();
  2011. }
  2012. }
  2013. maybeCommit() {
  2014. // If the browser supports V3 IndexedDB, we invoke commit() explicitly to
  2015. // speed up index DB processing if the event loop remains blocks.
  2016. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  2017. const maybeV3IndexedDb = this.transaction;
  2018. if (!this.aborted && typeof maybeV3IndexedDb.commit === 'function') {
  2019. maybeV3IndexedDb.commit();
  2020. }
  2021. }
  2022. /**
  2023. * Returns a SimpleDbStore<KeyType, ValueType> for the specified store. All
  2024. * operations performed on the SimpleDbStore happen within the context of this
  2025. * transaction and it cannot be used anymore once the transaction is
  2026. * completed.
  2027. *
  2028. * Note that we can't actually enforce that the KeyType and ValueType are
  2029. * correct, but they allow type safety through the rest of the consuming code.
  2030. */
  2031. store(storeName) {
  2032. const store = this.transaction.objectStore(storeName);
  2033. return new SimpleDbStore(store);
  2034. }
  2035. }
  2036. /**
  2037. * Provides a wrapper around IndexedDb with a simplified interface that uses
  2038. * Promise-like return values to chain operations. Real promises cannot be used
  2039. * since .then() continuations are executed asynchronously (e.g. via
  2040. * .setImmediate), which would cause IndexedDB to end the transaction.
  2041. * See PersistencePromise for more details.
  2042. */
  2043. class SimpleDb {
  2044. /*
  2045. * Creates a new SimpleDb wrapper for IndexedDb database `name`.
  2046. *
  2047. * Note that `version` must not be a downgrade. IndexedDB does not support
  2048. * downgrading the schema version. We currently do not support any way to do
  2049. * versioning outside of IndexedDB's versioning mechanism, as only
  2050. * version-upgrade transactions are allowed to do things like create
  2051. * objectstores.
  2052. */
  2053. constructor(name, version, schemaConverter) {
  2054. this.name = name;
  2055. this.version = version;
  2056. this.schemaConverter = schemaConverter;
  2057. const iOSVersion = SimpleDb.getIOSVersion(util.getUA());
  2058. // NOTE: According to https://bugs.webkit.org/show_bug.cgi?id=197050, the
  2059. // bug we're checking for should exist in iOS >= 12.2 and < 13, but for
  2060. // whatever reason it's much harder to hit after 12.2 so we only proactively
  2061. // log on 12.2.
  2062. if (iOSVersion === 12.2) {
  2063. logError('Firestore persistence suffers from a bug in iOS 12.2 ' +
  2064. 'Safari that may cause your app to stop working. See ' +
  2065. 'https://stackoverflow.com/q/56496296/110915 for details ' +
  2066. 'and a potential workaround.');
  2067. }
  2068. }
  2069. /** Deletes the specified database. */
  2070. static delete(name) {
  2071. logDebug(LOG_TAG$i, 'Removing database:', name);
  2072. return wrapRequest(window.indexedDB.deleteDatabase(name)).toPromise();
  2073. }
  2074. /** Returns true if IndexedDB is available in the current environment. */
  2075. static isAvailable() {
  2076. if (!util.isIndexedDBAvailable()) {
  2077. return false;
  2078. }
  2079. if (SimpleDb.isMockPersistence()) {
  2080. return true;
  2081. }
  2082. // We extensively use indexed array values and compound keys,
  2083. // which IE and Edge do not support. However, they still have indexedDB
  2084. // defined on the window, so we need to check for them here and make sure
  2085. // to return that persistence is not enabled for those browsers.
  2086. // For tracking support of this feature, see here:
  2087. // https://developer.microsoft.com/en-us/microsoft-edge/platform/status/indexeddbarraysandmultientrysupport/
  2088. // Check the UA string to find out the browser.
  2089. const ua = util.getUA();
  2090. // IE 10
  2091. // ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';
  2092. // IE 11
  2093. // ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
  2094. // Edge
  2095. // ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML,
  2096. // like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';
  2097. // iOS Safari: Disable for users running iOS version < 10.
  2098. const iOSVersion = SimpleDb.getIOSVersion(ua);
  2099. const isUnsupportedIOS = 0 < iOSVersion && iOSVersion < 10;
  2100. // Android browser: Disable for userse running version < 4.5.
  2101. const androidVersion = SimpleDb.getAndroidVersion(ua);
  2102. const isUnsupportedAndroid = 0 < androidVersion && androidVersion < 4.5;
  2103. if (ua.indexOf('MSIE ') > 0 ||
  2104. ua.indexOf('Trident/') > 0 ||
  2105. ua.indexOf('Edge/') > 0 ||
  2106. isUnsupportedIOS ||
  2107. isUnsupportedAndroid) {
  2108. return false;
  2109. }
  2110. else {
  2111. return true;
  2112. }
  2113. }
  2114. /**
  2115. * Returns true if the backing IndexedDB store is the Node IndexedDBShim
  2116. * (see https://github.com/axemclion/IndexedDBShim).
  2117. */
  2118. static isMockPersistence() {
  2119. var _a;
  2120. return (typeof process !== 'undefined' &&
  2121. ((_a = process.env) === null || _a === void 0 ? void 0 : _a.USE_MOCK_PERSISTENCE) === 'YES');
  2122. }
  2123. /** Helper to get a typed SimpleDbStore from a transaction. */
  2124. static getStore(txn, store) {
  2125. return txn.store(store);
  2126. }
  2127. // visible for testing
  2128. /** Parse User Agent to determine iOS version. Returns -1 if not found. */
  2129. static getIOSVersion(ua) {
  2130. const iOSVersionRegex = ua.match(/i(?:phone|pad|pod) os ([\d_]+)/i);
  2131. const version = iOSVersionRegex
  2132. ? iOSVersionRegex[1].split('_').slice(0, 2).join('.')
  2133. : '-1';
  2134. return Number(version);
  2135. }
  2136. // visible for testing
  2137. /** Parse User Agent to determine Android version. Returns -1 if not found. */
  2138. static getAndroidVersion(ua) {
  2139. const androidVersionRegex = ua.match(/Android ([\d.]+)/i);
  2140. const version = androidVersionRegex
  2141. ? androidVersionRegex[1].split('.').slice(0, 2).join('.')
  2142. : '-1';
  2143. return Number(version);
  2144. }
  2145. /**
  2146. * Opens the specified database, creating or upgrading it if necessary.
  2147. */
  2148. async ensureDb(action) {
  2149. if (!this.db) {
  2150. logDebug(LOG_TAG$i, 'Opening database:', this.name);
  2151. this.db = await new Promise((resolve, reject) => {
  2152. // TODO(mikelehen): Investigate browser compatibility.
  2153. // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
  2154. // suggests IE9 and older WebKit browsers handle upgrade
  2155. // differently. They expect setVersion, as described here:
  2156. // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion
  2157. const request = indexedDB.open(this.name, this.version);
  2158. request.onsuccess = (event) => {
  2159. const db = event.target.result;
  2160. resolve(db);
  2161. };
  2162. request.onblocked = () => {
  2163. reject(new IndexedDbTransactionError(action, 'Cannot upgrade IndexedDB schema while another tab is open. ' +
  2164. 'Close all tabs that access Firestore and reload this page to proceed.'));
  2165. };
  2166. request.onerror = (event) => {
  2167. const error = event.target.error;
  2168. if (error.name === 'VersionError') {
  2169. reject(new FirestoreError(Code.FAILED_PRECONDITION, 'A newer version of the Firestore SDK was previously used and so the persisted ' +
  2170. 'data is not compatible with the version of the SDK you are now using. The SDK ' +
  2171. 'will operate with persistence disabled. If you need persistence, please ' +
  2172. 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' +
  2173. 'data for your app to start fresh.'));
  2174. }
  2175. else if (error.name === 'InvalidStateError') {
  2176. reject(new FirestoreError(Code.FAILED_PRECONDITION, 'Unable to open an IndexedDB connection. This could be due to running in a ' +
  2177. 'private browsing session on a browser whose private browsing sessions do not ' +
  2178. 'support IndexedDB: ' +
  2179. error));
  2180. }
  2181. else {
  2182. reject(new IndexedDbTransactionError(action, error));
  2183. }
  2184. };
  2185. request.onupgradeneeded = (event) => {
  2186. logDebug(LOG_TAG$i, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
  2187. const db = event.target.result;
  2188. this.schemaConverter
  2189. .createOrUpgrade(db, request.transaction, event.oldVersion, this.version)
  2190. .next(() => {
  2191. logDebug(LOG_TAG$i, 'Database upgrade to version ' + this.version + ' complete');
  2192. });
  2193. };
  2194. });
  2195. }
  2196. if (this.versionchangelistener) {
  2197. this.db.onversionchange = event => this.versionchangelistener(event);
  2198. }
  2199. return this.db;
  2200. }
  2201. setVersionChangeListener(versionChangeListener) {
  2202. this.versionchangelistener = versionChangeListener;
  2203. if (this.db) {
  2204. this.db.onversionchange = (event) => {
  2205. return versionChangeListener(event);
  2206. };
  2207. }
  2208. }
  2209. async runTransaction(action, mode, objectStores, transactionFn) {
  2210. const readonly = mode === 'readonly';
  2211. let attemptNumber = 0;
  2212. while (true) {
  2213. ++attemptNumber;
  2214. try {
  2215. this.db = await this.ensureDb(action);
  2216. const transaction = SimpleDbTransaction.open(this.db, action, readonly ? 'readonly' : 'readwrite', objectStores);
  2217. const transactionFnResult = transactionFn(transaction)
  2218. .next(result => {
  2219. transaction.maybeCommit();
  2220. return result;
  2221. })
  2222. .catch(error => {
  2223. // Abort the transaction if there was an error.
  2224. transaction.abort(error);
  2225. // We cannot actually recover, and calling `abort()` will cause the transaction's
  2226. // completion promise to be rejected. This in turn means that we won't use
  2227. // `transactionFnResult` below. We return a rejection here so that we don't add the
  2228. // possibility of returning `void` to the type of `transactionFnResult`.
  2229. return PersistencePromise.reject(error);
  2230. })
  2231. .toPromise();
  2232. // As noted above, errors are propagated by aborting the transaction. So
  2233. // we swallow any error here to avoid the browser logging it as unhandled.
  2234. transactionFnResult.catch(() => { });
  2235. // Wait for the transaction to complete (i.e. IndexedDb's onsuccess event to
  2236. // fire), but still return the original transactionFnResult back to the
  2237. // caller.
  2238. await transaction.completionPromise;
  2239. return transactionFnResult;
  2240. }
  2241. catch (e) {
  2242. const error = e;
  2243. // TODO(schmidt-sebastian): We could probably be smarter about this and
  2244. // not retry exceptions that are likely unrecoverable (such as quota
  2245. // exceeded errors).
  2246. // Note: We cannot use an instanceof check for FirestoreException, since the
  2247. // exception is wrapped in a generic error by our async/await handling.
  2248. const retryable = error.name !== 'FirebaseError' &&
  2249. attemptNumber < TRANSACTION_RETRY_COUNT;
  2250. logDebug(LOG_TAG$i, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
  2251. this.close();
  2252. if (!retryable) {
  2253. return Promise.reject(error);
  2254. }
  2255. }
  2256. }
  2257. }
  2258. close() {
  2259. if (this.db) {
  2260. this.db.close();
  2261. }
  2262. this.db = undefined;
  2263. }
  2264. }
  2265. /**
  2266. * A controller for iterating over a key range or index. It allows an iterate
  2267. * callback to delete the currently-referenced object, or jump to a new key
  2268. * within the key range or index.
  2269. */
  2270. class IterationController {
  2271. constructor(dbCursor) {
  2272. this.dbCursor = dbCursor;
  2273. this.shouldStop = false;
  2274. this.nextKey = null;
  2275. }
  2276. get isDone() {
  2277. return this.shouldStop;
  2278. }
  2279. get skipToKey() {
  2280. return this.nextKey;
  2281. }
  2282. set cursor(value) {
  2283. this.dbCursor = value;
  2284. }
  2285. /**
  2286. * This function can be called to stop iteration at any point.
  2287. */
  2288. done() {
  2289. this.shouldStop = true;
  2290. }
  2291. /**
  2292. * This function can be called to skip to that next key, which could be
  2293. * an index or a primary key.
  2294. */
  2295. skip(key) {
  2296. this.nextKey = key;
  2297. }
  2298. /**
  2299. * Delete the current cursor value from the object store.
  2300. *
  2301. * NOTE: You CANNOT do this with a keysOnly query.
  2302. */
  2303. delete() {
  2304. return wrapRequest(this.dbCursor.delete());
  2305. }
  2306. }
  2307. /** An error that wraps exceptions that thrown during IndexedDB execution. */
  2308. class IndexedDbTransactionError extends FirestoreError {
  2309. constructor(actionName, cause) {
  2310. super(Code.UNAVAILABLE, `IndexedDB transaction '${actionName}' failed: ${cause}`);
  2311. this.name = 'IndexedDbTransactionError';
  2312. }
  2313. }
  2314. /** Verifies whether `e` is an IndexedDbTransactionError. */
  2315. function isIndexedDbTransactionError(e) {
  2316. // Use name equality, as instanceof checks on errors don't work with errors
  2317. // that wrap other errors.
  2318. return e.name === 'IndexedDbTransactionError';
  2319. }
  2320. /**
  2321. * A wrapper around an IDBObjectStore providing an API that:
  2322. *
  2323. * 1) Has generic KeyType / ValueType parameters to provide strongly-typed
  2324. * methods for acting against the object store.
  2325. * 2) Deals with IndexedDB's onsuccess / onerror event callbacks, making every
  2326. * method return a PersistencePromise instead.
  2327. * 3) Provides a higher-level API to avoid needing to do excessive wrapping of
  2328. * intermediate IndexedDB types (IDBCursorWithValue, etc.)
  2329. */
  2330. class SimpleDbStore {
  2331. constructor(store) {
  2332. this.store = store;
  2333. }
  2334. put(keyOrValue, value) {
  2335. let request;
  2336. if (value !== undefined) {
  2337. logDebug(LOG_TAG$i, 'PUT', this.store.name, keyOrValue, value);
  2338. request = this.store.put(value, keyOrValue);
  2339. }
  2340. else {
  2341. logDebug(LOG_TAG$i, 'PUT', this.store.name, '<auto-key>', keyOrValue);
  2342. request = this.store.put(keyOrValue);
  2343. }
  2344. return wrapRequest(request);
  2345. }
  2346. /**
  2347. * Adds a new value into an Object Store and returns the new key. Similar to
  2348. * IndexedDb's `add()`, this method will fail on primary key collisions.
  2349. *
  2350. * @param value - The object to write.
  2351. * @returns The key of the value to add.
  2352. */
  2353. add(value) {
  2354. logDebug(LOG_TAG$i, 'ADD', this.store.name, value, value);
  2355. const request = this.store.add(value);
  2356. return wrapRequest(request);
  2357. }
  2358. /**
  2359. * Gets the object with the specified key from the specified store, or null
  2360. * if no object exists with the specified key.
  2361. *
  2362. * @key The key of the object to get.
  2363. * @returns The object with the specified key or null if no object exists.
  2364. */
  2365. get(key) {
  2366. const request = this.store.get(key);
  2367. // We're doing an unsafe cast to ValueType.
  2368. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  2369. return wrapRequest(request).next(result => {
  2370. // Normalize nonexistence to null.
  2371. if (result === undefined) {
  2372. result = null;
  2373. }
  2374. logDebug(LOG_TAG$i, 'GET', this.store.name, key, result);
  2375. return result;
  2376. });
  2377. }
  2378. delete(key) {
  2379. logDebug(LOG_TAG$i, 'DELETE', this.store.name, key);
  2380. const request = this.store.delete(key);
  2381. return wrapRequest(request);
  2382. }
  2383. /**
  2384. * If we ever need more of the count variants, we can add overloads. For now,
  2385. * all we need is to count everything in a store.
  2386. *
  2387. * Returns the number of rows in the store.
  2388. */
  2389. count() {
  2390. logDebug(LOG_TAG$i, 'COUNT', this.store.name);
  2391. const request = this.store.count();
  2392. return wrapRequest(request);
  2393. }
  2394. loadAll(indexOrRange, range) {
  2395. const iterateOptions = this.options(indexOrRange, range);
  2396. // Use `getAll()` if the browser supports IndexedDB v3, as it is roughly
  2397. // 20% faster. Unfortunately, getAll() does not support custom indices.
  2398. if (!iterateOptions.index && typeof this.store.getAll === 'function') {
  2399. const request = this.store.getAll(iterateOptions.range);
  2400. return new PersistencePromise((resolve, reject) => {
  2401. request.onerror = (event) => {
  2402. reject(event.target.error);
  2403. };
  2404. request.onsuccess = (event) => {
  2405. resolve(event.target.result);
  2406. };
  2407. });
  2408. }
  2409. else {
  2410. const cursor = this.cursor(iterateOptions);
  2411. const results = [];
  2412. return this.iterateCursor(cursor, (key, value) => {
  2413. results.push(value);
  2414. }).next(() => {
  2415. return results;
  2416. });
  2417. }
  2418. }
  2419. /**
  2420. * Loads the first `count` elements from the provided index range. Loads all
  2421. * elements if no limit is provided.
  2422. */
  2423. loadFirst(range, count) {
  2424. const request = this.store.getAll(range, count === null ? undefined : count);
  2425. return new PersistencePromise((resolve, reject) => {
  2426. request.onerror = (event) => {
  2427. reject(event.target.error);
  2428. };
  2429. request.onsuccess = (event) => {
  2430. resolve(event.target.result);
  2431. };
  2432. });
  2433. }
  2434. deleteAll(indexOrRange, range) {
  2435. logDebug(LOG_TAG$i, 'DELETE ALL', this.store.name);
  2436. const options = this.options(indexOrRange, range);
  2437. options.keysOnly = false;
  2438. const cursor = this.cursor(options);
  2439. return this.iterateCursor(cursor, (key, value, control) => {
  2440. // NOTE: Calling delete() on a cursor is documented as more efficient than
  2441. // calling delete() on an object store with a single key
  2442. // (https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/delete),
  2443. // however, this requires us *not* to use a keysOnly cursor
  2444. // (https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/delete). We
  2445. // may want to compare the performance of each method.
  2446. return control.delete();
  2447. });
  2448. }
  2449. iterate(optionsOrCallback, callback) {
  2450. let options;
  2451. if (!callback) {
  2452. options = {};
  2453. callback = optionsOrCallback;
  2454. }
  2455. else {
  2456. options = optionsOrCallback;
  2457. }
  2458. const cursor = this.cursor(options);
  2459. return this.iterateCursor(cursor, callback);
  2460. }
  2461. /**
  2462. * Iterates over a store, but waits for the given callback to complete for
  2463. * each entry before iterating the next entry. This allows the callback to do
  2464. * asynchronous work to determine if this iteration should continue.
  2465. *
  2466. * The provided callback should return `true` to continue iteration, and
  2467. * `false` otherwise.
  2468. */
  2469. iterateSerial(callback) {
  2470. const cursorRequest = this.cursor({});
  2471. return new PersistencePromise((resolve, reject) => {
  2472. cursorRequest.onerror = (event) => {
  2473. const error = checkForAndReportiOSError(event.target.error);
  2474. reject(error);
  2475. };
  2476. cursorRequest.onsuccess = (event) => {
  2477. const cursor = event.target.result;
  2478. if (!cursor) {
  2479. resolve();
  2480. return;
  2481. }
  2482. callback(cursor.primaryKey, cursor.value).next(shouldContinue => {
  2483. if (shouldContinue) {
  2484. cursor.continue();
  2485. }
  2486. else {
  2487. resolve();
  2488. }
  2489. });
  2490. };
  2491. });
  2492. }
  2493. iterateCursor(cursorRequest, fn) {
  2494. const results = [];
  2495. return new PersistencePromise((resolve, reject) => {
  2496. cursorRequest.onerror = (event) => {
  2497. reject(event.target.error);
  2498. };
  2499. cursorRequest.onsuccess = (event) => {
  2500. const cursor = event.target.result;
  2501. if (!cursor) {
  2502. resolve();
  2503. return;
  2504. }
  2505. const controller = new IterationController(cursor);
  2506. const userResult = fn(cursor.primaryKey, cursor.value, controller);
  2507. if (userResult instanceof PersistencePromise) {
  2508. const userPromise = userResult.catch(err => {
  2509. controller.done();
  2510. return PersistencePromise.reject(err);
  2511. });
  2512. results.push(userPromise);
  2513. }
  2514. if (controller.isDone) {
  2515. resolve();
  2516. }
  2517. else if (controller.skipToKey === null) {
  2518. cursor.continue();
  2519. }
  2520. else {
  2521. cursor.continue(controller.skipToKey);
  2522. }
  2523. };
  2524. }).next(() => PersistencePromise.waitFor(results));
  2525. }
  2526. options(indexOrRange, range) {
  2527. let indexName = undefined;
  2528. if (indexOrRange !== undefined) {
  2529. if (typeof indexOrRange === 'string') {
  2530. indexName = indexOrRange;
  2531. }
  2532. else {
  2533. range = indexOrRange;
  2534. }
  2535. }
  2536. return { index: indexName, range };
  2537. }
  2538. cursor(options) {
  2539. let direction = 'next';
  2540. if (options.reverse) {
  2541. direction = 'prev';
  2542. }
  2543. if (options.index) {
  2544. const index = this.store.index(options.index);
  2545. if (options.keysOnly) {
  2546. return index.openKeyCursor(options.range, direction);
  2547. }
  2548. else {
  2549. return index.openCursor(options.range, direction);
  2550. }
  2551. }
  2552. else {
  2553. return this.store.openCursor(options.range, direction);
  2554. }
  2555. }
  2556. }
  2557. /**
  2558. * Wraps an IDBRequest in a PersistencePromise, using the onsuccess / onerror
  2559. * handlers to resolve / reject the PersistencePromise as appropriate.
  2560. */
  2561. function wrapRequest(request) {
  2562. return new PersistencePromise((resolve, reject) => {
  2563. request.onsuccess = (event) => {
  2564. const result = event.target.result;
  2565. resolve(result);
  2566. };
  2567. request.onerror = (event) => {
  2568. const error = checkForAndReportiOSError(event.target.error);
  2569. reject(error);
  2570. };
  2571. });
  2572. }
  2573. // Guard so we only report the error once.
  2574. let reportedIOSError = false;
  2575. function checkForAndReportiOSError(error) {
  2576. const iOSVersion = SimpleDb.getIOSVersion(util.getUA());
  2577. if (iOSVersion >= 12.2 && iOSVersion < 13) {
  2578. const IOS_ERROR = 'An internal error was encountered in the Indexed Database server';
  2579. if (error.message.indexOf(IOS_ERROR) >= 0) {
  2580. // Wrap error in a more descriptive one.
  2581. const newError = new FirestoreError('internal', `IOS_INDEXEDDB_BUG1: IndexedDb has thrown '${IOS_ERROR}'. This is likely ` +
  2582. `due to an unavoidable bug in iOS. See https://stackoverflow.com/q/56496296/110915 ` +
  2583. `for details and a potential workaround.`);
  2584. if (!reportedIOSError) {
  2585. reportedIOSError = true;
  2586. // Throw a global exception outside of this promise chain, for the user to
  2587. // potentially catch.
  2588. setTimeout(() => {
  2589. throw newError;
  2590. }, 0);
  2591. }
  2592. return newError;
  2593. }
  2594. }
  2595. return error;
  2596. }
  2597. const LOG_TAG$h = 'IndexBackiller';
  2598. /** How long we wait to try running index backfill after SDK initialization. */
  2599. const INITIAL_BACKFILL_DELAY_MS = 15 * 1000;
  2600. /** Minimum amount of time between backfill checks, after the first one. */
  2601. const REGULAR_BACKFILL_DELAY_MS = 60 * 1000;
  2602. /** The maximum number of documents to process each time backfill() is called. */
  2603. const MAX_DOCUMENTS_TO_PROCESS = 50;
  2604. /** This class is responsible for the scheduling of Index Backfiller. */
  2605. class IndexBackfillerScheduler {
  2606. constructor(asyncQueue, backfiller) {
  2607. this.asyncQueue = asyncQueue;
  2608. this.backfiller = backfiller;
  2609. this.task = null;
  2610. }
  2611. start() {
  2612. this.schedule(INITIAL_BACKFILL_DELAY_MS);
  2613. }
  2614. stop() {
  2615. if (this.task) {
  2616. this.task.cancel();
  2617. this.task = null;
  2618. }
  2619. }
  2620. get started() {
  2621. return this.task !== null;
  2622. }
  2623. schedule(delay) {
  2624. logDebug(LOG_TAG$h, `Scheduled in ${delay}ms`);
  2625. this.task = this.asyncQueue.enqueueAfterDelay("index_backfill" /* TimerId.IndexBackfill */, delay, async () => {
  2626. this.task = null;
  2627. try {
  2628. const documentsProcessed = await this.backfiller.backfill();
  2629. logDebug(LOG_TAG$h, `Documents written: ${documentsProcessed}`);
  2630. }
  2631. catch (e) {
  2632. if (isIndexedDbTransactionError(e)) {
  2633. logDebug(LOG_TAG$h, 'Ignoring IndexedDB error during index backfill: ', e);
  2634. }
  2635. else {
  2636. await ignoreIfPrimaryLeaseLoss(e);
  2637. }
  2638. }
  2639. await this.schedule(REGULAR_BACKFILL_DELAY_MS);
  2640. });
  2641. }
  2642. }
  2643. /** Implements the steps for backfilling indexes. */
  2644. class IndexBackfiller {
  2645. constructor(
  2646. /**
  2647. * LocalStore provides access to IndexManager and LocalDocumentView.
  2648. * These properties will update when the user changes. Consequently,
  2649. * making a local copy of IndexManager and LocalDocumentView will require
  2650. * updates over time. The simpler solution is to rely on LocalStore to have
  2651. * an up-to-date references to IndexManager and LocalDocumentStore.
  2652. */
  2653. localStore, persistence) {
  2654. this.localStore = localStore;
  2655. this.persistence = persistence;
  2656. }
  2657. async backfill(maxDocumentsToProcess = MAX_DOCUMENTS_TO_PROCESS) {
  2658. return this.persistence.runTransaction('Backfill Indexes', 'readwrite-primary', txn => this.writeIndexEntries(txn, maxDocumentsToProcess));
  2659. }
  2660. /** Writes index entries until the cap is reached. Returns the number of documents processed. */
  2661. writeIndexEntries(transation, maxDocumentsToProcess) {
  2662. const processedCollectionGroups = new Set();
  2663. let documentsRemaining = maxDocumentsToProcess;
  2664. let continueLoop = true;
  2665. return PersistencePromise.doWhile(() => continueLoop === true && documentsRemaining > 0, () => {
  2666. return this.localStore.indexManager
  2667. .getNextCollectionGroupToUpdate(transation)
  2668. .next((collectionGroup) => {
  2669. if (collectionGroup === null ||
  2670. processedCollectionGroups.has(collectionGroup)) {
  2671. continueLoop = false;
  2672. }
  2673. else {
  2674. logDebug(LOG_TAG$h, `Processing collection: ${collectionGroup}`);
  2675. return this.writeEntriesForCollectionGroup(transation, collectionGroup, documentsRemaining).next(documentsProcessed => {
  2676. documentsRemaining -= documentsProcessed;
  2677. processedCollectionGroups.add(collectionGroup);
  2678. });
  2679. }
  2680. });
  2681. }).next(() => maxDocumentsToProcess - documentsRemaining);
  2682. }
  2683. /**
  2684. * Writes entries for the provided collection group. Returns the number of documents processed.
  2685. */
  2686. writeEntriesForCollectionGroup(transaction, collectionGroup, documentsRemainingUnderCap) {
  2687. // Use the earliest offset of all field indexes to query the local cache.
  2688. return this.localStore.indexManager
  2689. .getMinOffsetFromCollectionGroup(transaction, collectionGroup)
  2690. .next(existingOffset => this.localStore.localDocuments
  2691. .getNextDocuments(transaction, collectionGroup, existingOffset, documentsRemainingUnderCap)
  2692. .next(nextBatch => {
  2693. const docs = nextBatch.changes;
  2694. return this.localStore.indexManager
  2695. .updateIndexEntries(transaction, docs)
  2696. .next(() => this.getNewOffset(existingOffset, nextBatch))
  2697. .next(newOffset => {
  2698. logDebug(LOG_TAG$h, `Updating offset: ${newOffset}`);
  2699. return this.localStore.indexManager.updateCollectionGroup(transaction, collectionGroup, newOffset);
  2700. })
  2701. .next(() => docs.size);
  2702. }));
  2703. }
  2704. /** Returns the next offset based on the provided documents. */
  2705. getNewOffset(existingOffset, lookupResult) {
  2706. let maxOffset = existingOffset;
  2707. lookupResult.changes.forEach((key, document) => {
  2708. const newOffset = newIndexOffsetFromDocument(document);
  2709. if (indexOffsetComparator(newOffset, maxOffset) > 0) {
  2710. maxOffset = newOffset;
  2711. }
  2712. });
  2713. return new IndexOffset(maxOffset.readTime, maxOffset.documentKey, Math.max(lookupResult.batchId, existingOffset.largestBatchId));
  2714. }
  2715. }
  2716. /**
  2717. * @license
  2718. * Copyright 2018 Google LLC
  2719. *
  2720. * Licensed under the Apache License, Version 2.0 (the "License");
  2721. * you may not use this file except in compliance with the License.
  2722. * You may obtain a copy of the License at
  2723. *
  2724. * http://www.apache.org/licenses/LICENSE-2.0
  2725. *
  2726. * Unless required by applicable law or agreed to in writing, software
  2727. * distributed under the License is distributed on an "AS IS" BASIS,
  2728. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2729. * See the License for the specific language governing permissions and
  2730. * limitations under the License.
  2731. */
  2732. /**
  2733. * `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to
  2734. * exceed. All subsequent calls to next will return increasing values. If provided with a
  2735. * `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as
  2736. * well as write out sequence numbers that it produces via `next()`.
  2737. */
  2738. class ListenSequence {
  2739. constructor(previousValue, sequenceNumberSyncer) {
  2740. this.previousValue = previousValue;
  2741. if (sequenceNumberSyncer) {
  2742. sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber => this.setPreviousValue(sequenceNumber);
  2743. this.writeNewSequenceNumber = sequenceNumber => sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);
  2744. }
  2745. }
  2746. setPreviousValue(externalPreviousValue) {
  2747. this.previousValue = Math.max(externalPreviousValue, this.previousValue);
  2748. return this.previousValue;
  2749. }
  2750. next() {
  2751. const nextValue = ++this.previousValue;
  2752. if (this.writeNewSequenceNumber) {
  2753. this.writeNewSequenceNumber(nextValue);
  2754. }
  2755. return nextValue;
  2756. }
  2757. }
  2758. ListenSequence.INVALID = -1;
  2759. /**
  2760. * @license
  2761. * Copyright 2017 Google LLC
  2762. *
  2763. * Licensed under the Apache License, Version 2.0 (the "License");
  2764. * you may not use this file except in compliance with the License.
  2765. * You may obtain a copy of the License at
  2766. *
  2767. * http://www.apache.org/licenses/LICENSE-2.0
  2768. *
  2769. * Unless required by applicable law or agreed to in writing, software
  2770. * distributed under the License is distributed on an "AS IS" BASIS,
  2771. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2772. * See the License for the specific language governing permissions and
  2773. * limitations under the License.
  2774. */
  2775. const escapeChar = '\u0001';
  2776. const encodedSeparatorChar = '\u0001';
  2777. const encodedNul = '\u0010';
  2778. const encodedEscape = '\u0011';
  2779. /**
  2780. * Encodes a resource path into a IndexedDb-compatible string form.
  2781. */
  2782. function encodeResourcePath(path) {
  2783. let result = '';
  2784. for (let i = 0; i < path.length; i++) {
  2785. if (result.length > 0) {
  2786. result = encodeSeparator(result);
  2787. }
  2788. result = encodeSegment(path.get(i), result);
  2789. }
  2790. return encodeSeparator(result);
  2791. }
  2792. /** Encodes a single segment of a resource path into the given result */
  2793. function encodeSegment(segment, resultBuf) {
  2794. let result = resultBuf;
  2795. const length = segment.length;
  2796. for (let i = 0; i < length; i++) {
  2797. const c = segment.charAt(i);
  2798. switch (c) {
  2799. case '\0':
  2800. result += escapeChar + encodedNul;
  2801. break;
  2802. case escapeChar:
  2803. result += escapeChar + encodedEscape;
  2804. break;
  2805. default:
  2806. result += c;
  2807. }
  2808. }
  2809. return result;
  2810. }
  2811. /** Encodes a path separator into the given result */
  2812. function encodeSeparator(result) {
  2813. return result + escapeChar + encodedSeparatorChar;
  2814. }
  2815. /**
  2816. * Decodes the given IndexedDb-compatible string form of a resource path into
  2817. * a ResourcePath instance. Note that this method is not suitable for use with
  2818. * decoding resource names from the server; those are One Platform format
  2819. * strings.
  2820. */
  2821. function decodeResourcePath(path) {
  2822. // Event the empty path must encode as a path of at least length 2. A path
  2823. // with exactly 2 must be the empty path.
  2824. const length = path.length;
  2825. hardAssert(length >= 2);
  2826. if (length === 2) {
  2827. hardAssert(path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar);
  2828. return ResourcePath.emptyPath();
  2829. }
  2830. // Escape characters cannot exist past the second-to-last position in the
  2831. // source value.
  2832. const lastReasonableEscapeIndex = length - 2;
  2833. const segments = [];
  2834. let segmentBuilder = '';
  2835. for (let start = 0; start < length;) {
  2836. // The last two characters of a valid encoded path must be a separator, so
  2837. // there must be an end to this segment.
  2838. const end = path.indexOf(escapeChar, start);
  2839. if (end < 0 || end > lastReasonableEscapeIndex) {
  2840. fail();
  2841. }
  2842. const next = path.charAt(end + 1);
  2843. switch (next) {
  2844. case encodedSeparatorChar:
  2845. const currentPiece = path.substring(start, end);
  2846. let segment;
  2847. if (segmentBuilder.length === 0) {
  2848. // Avoid copying for the common case of a segment that excludes \0
  2849. // and \001
  2850. segment = currentPiece;
  2851. }
  2852. else {
  2853. segmentBuilder += currentPiece;
  2854. segment = segmentBuilder;
  2855. segmentBuilder = '';
  2856. }
  2857. segments.push(segment);
  2858. break;
  2859. case encodedNul:
  2860. segmentBuilder += path.substring(start, end);
  2861. segmentBuilder += '\0';
  2862. break;
  2863. case encodedEscape:
  2864. // The escape character can be used in the output to encode itself.
  2865. segmentBuilder += path.substring(start, end + 1);
  2866. break;
  2867. default:
  2868. fail();
  2869. }
  2870. start = end + 2;
  2871. }
  2872. return new ResourcePath(segments);
  2873. }
  2874. /**
  2875. * @license
  2876. * Copyright 2022 Google LLC
  2877. *
  2878. * Licensed under the Apache License, Version 2.0 (the "License");
  2879. * you may not use this file except in compliance with the License.
  2880. * You may obtain a copy of the License at
  2881. *
  2882. * http://www.apache.org/licenses/LICENSE-2.0
  2883. *
  2884. * Unless required by applicable law or agreed to in writing, software
  2885. * distributed under the License is distributed on an "AS IS" BASIS,
  2886. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2887. * See the License for the specific language governing permissions and
  2888. * limitations under the License.
  2889. */
  2890. const DbRemoteDocumentStore$1 = 'remoteDocuments';
  2891. /**
  2892. * @license
  2893. * Copyright 2022 Google LLC
  2894. *
  2895. * Licensed under the Apache License, Version 2.0 (the "License");
  2896. * you may not use this file except in compliance with the License.
  2897. * You may obtain a copy of the License at
  2898. *
  2899. * http://www.apache.org/licenses/LICENSE-2.0
  2900. *
  2901. * Unless required by applicable law or agreed to in writing, software
  2902. * distributed under the License is distributed on an "AS IS" BASIS,
  2903. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2904. * See the License for the specific language governing permissions and
  2905. * limitations under the License.
  2906. */
  2907. /**
  2908. * Name of the IndexedDb object store.
  2909. *
  2910. * Note that the name 'owner' is chosen to ensure backwards compatibility with
  2911. * older clients that only supported single locked access to the persistence
  2912. * layer.
  2913. */
  2914. const DbPrimaryClientStore = 'owner';
  2915. /**
  2916. * The key string used for the single object that exists in the
  2917. * DbPrimaryClient store.
  2918. */
  2919. const DbPrimaryClientKey = 'owner';
  2920. /** Name of the IndexedDb object store. */
  2921. const DbMutationQueueStore = 'mutationQueues';
  2922. /** Keys are automatically assigned via the userId property. */
  2923. const DbMutationQueueKeyPath = 'userId';
  2924. /** Name of the IndexedDb object store. */
  2925. const DbMutationBatchStore = 'mutations';
  2926. /** Keys are automatically assigned via the userId, batchId properties. */
  2927. const DbMutationBatchKeyPath = 'batchId';
  2928. /** The index name for lookup of mutations by user. */
  2929. const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
  2930. /** The user mutations index is keyed by [userId, batchId] pairs. */
  2931. const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
  2932. /**
  2933. * Creates a [userId] key for use in the DbDocumentMutations index to iterate
  2934. * over all of a user's document mutations.
  2935. */
  2936. function newDbDocumentMutationPrefixForUser(userId) {
  2937. return [userId];
  2938. }
  2939. /**
  2940. * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
  2941. * index to iterate over all at document mutations for a given path or lower.
  2942. */
  2943. function newDbDocumentMutationPrefixForPath(userId, path) {
  2944. return [userId, encodeResourcePath(path)];
  2945. }
  2946. /**
  2947. * Creates a full index key of [userId, encodedPath, batchId] for inserting
  2948. * and deleting into the DbDocumentMutations index.
  2949. */
  2950. function newDbDocumentMutationKey(userId, path, batchId) {
  2951. return [userId, encodeResourcePath(path), batchId];
  2952. }
  2953. /**
  2954. * Because we store all the useful information for this store in the key,
  2955. * there is no useful information to store as the value. The raw (unencoded)
  2956. * path cannot be stored because IndexedDb doesn't store prototype
  2957. * information.
  2958. */
  2959. const DbDocumentMutationPlaceholder = {};
  2960. const DbDocumentMutationStore = 'documentMutations';
  2961. const DbRemoteDocumentStore = 'remoteDocumentsV14';
  2962. /**
  2963. * The primary key of the remote documents store, which allows for efficient
  2964. * access by collection path and read time.
  2965. */
  2966. const DbRemoteDocumentKeyPath = [
  2967. 'prefixPath',
  2968. 'collectionGroup',
  2969. 'readTime',
  2970. 'documentId'
  2971. ];
  2972. /** An index that provides access to documents by key. */
  2973. const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
  2974. const DbRemoteDocumentDocumentKeyIndexPath = [
  2975. 'prefixPath',
  2976. 'collectionGroup',
  2977. 'documentId'
  2978. ];
  2979. /**
  2980. * An index that provides access to documents by collection group and read
  2981. * time.
  2982. *
  2983. * This index is used by the index backfiller.
  2984. */
  2985. const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
  2986. const DbRemoteDocumentCollectionGroupIndexPath = [
  2987. 'collectionGroup',
  2988. 'readTime',
  2989. 'prefixPath',
  2990. 'documentId'
  2991. ];
  2992. const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
  2993. const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
  2994. const DbTargetStore = 'targets';
  2995. /** Keys are automatically assigned via the targetId property. */
  2996. const DbTargetKeyPath = 'targetId';
  2997. /** The name of the queryTargets index. */
  2998. const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
  2999. /**
  3000. * The index of all canonicalIds to the targets that they match. This is not
  3001. * a unique mapping because canonicalId does not promise a unique name for all
  3002. * possible queries, so we append the targetId to make the mapping unique.
  3003. */
  3004. const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
  3005. /** Name of the IndexedDb object store. */
  3006. const DbTargetDocumentStore = 'targetDocuments';
  3007. /** Keys are automatically assigned via the targetId, path properties. */
  3008. const DbTargetDocumentKeyPath = ['targetId', 'path'];
  3009. /** The index name for the reverse index. */
  3010. const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
  3011. /** We also need to create the reverse index for these properties. */
  3012. const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
  3013. /**
  3014. * The key string used for the single object that exists in the
  3015. * DbTargetGlobal store.
  3016. */
  3017. const DbTargetGlobalKey = 'targetGlobalKey';
  3018. const DbTargetGlobalStore = 'targetGlobal';
  3019. /** Name of the IndexedDb object store. */
  3020. const DbCollectionParentStore = 'collectionParents';
  3021. /** Keys are automatically assigned via the collectionId, parent properties. */
  3022. const DbCollectionParentKeyPath = ['collectionId', 'parent'];
  3023. /** Name of the IndexedDb object store. */
  3024. const DbClientMetadataStore = 'clientMetadata';
  3025. /** Keys are automatically assigned via the clientId properties. */
  3026. const DbClientMetadataKeyPath = 'clientId';
  3027. /** Name of the IndexedDb object store. */
  3028. const DbBundleStore = 'bundles';
  3029. const DbBundleKeyPath = 'bundleId';
  3030. /** Name of the IndexedDb object store. */
  3031. const DbNamedQueryStore = 'namedQueries';
  3032. const DbNamedQueryKeyPath = 'name';
  3033. /** Name of the IndexedDb object store. */
  3034. const DbIndexConfigurationStore = 'indexConfiguration';
  3035. const DbIndexConfigurationKeyPath = 'indexId';
  3036. /**
  3037. * An index that provides access to the index configurations by collection
  3038. * group.
  3039. *
  3040. * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
  3041. * not possible here as the Web client supports concurrent access to
  3042. * persistence via multi-tab.
  3043. */
  3044. const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
  3045. const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
  3046. /** Name of the IndexedDb object store. */
  3047. const DbIndexStateStore = 'indexState';
  3048. const DbIndexStateKeyPath = ['indexId', 'uid'];
  3049. /**
  3050. * An index that provides access to documents in a collection sorted by last
  3051. * update time. Used by the backfiller.
  3052. *
  3053. * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
  3054. * not possible here as the Web client supports concurrent access to
  3055. * persistence via multi-tab.
  3056. */
  3057. const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
  3058. const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
  3059. /** Name of the IndexedDb object store. */
  3060. const DbIndexEntryStore = 'indexEntries';
  3061. const DbIndexEntryKeyPath = [
  3062. 'indexId',
  3063. 'uid',
  3064. 'arrayValue',
  3065. 'directionalValue',
  3066. 'orderedDocumentKey',
  3067. 'documentKey'
  3068. ];
  3069. const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
  3070. const DbIndexEntryDocumentKeyIndexPath = [
  3071. 'indexId',
  3072. 'uid',
  3073. 'orderedDocumentKey'
  3074. ];
  3075. /** Name of the IndexedDb object store. */
  3076. const DbDocumentOverlayStore = 'documentOverlays';
  3077. const DbDocumentOverlayKeyPath = [
  3078. 'userId',
  3079. 'collectionPath',
  3080. 'documentId'
  3081. ];
  3082. const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
  3083. const DbDocumentOverlayCollectionPathOverlayIndexPath = [
  3084. 'userId',
  3085. 'collectionPath',
  3086. 'largestBatchId'
  3087. ];
  3088. const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
  3089. const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
  3090. 'userId',
  3091. 'collectionGroup',
  3092. 'largestBatchId'
  3093. ];
  3094. // Visible for testing
  3095. const V1_STORES = [
  3096. DbMutationQueueStore,
  3097. DbMutationBatchStore,
  3098. DbDocumentMutationStore,
  3099. DbRemoteDocumentStore$1,
  3100. DbTargetStore,
  3101. DbPrimaryClientStore,
  3102. DbTargetGlobalStore,
  3103. DbTargetDocumentStore
  3104. ];
  3105. // Visible for testing
  3106. const V3_STORES = V1_STORES;
  3107. // Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
  3108. const V4_STORES = [...V3_STORES, DbClientMetadataStore];
  3109. const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
  3110. const V8_STORES = [...V6_STORES, DbCollectionParentStore];
  3111. const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
  3112. const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
  3113. const V13_STORES = [
  3114. DbMutationQueueStore,
  3115. DbMutationBatchStore,
  3116. DbDocumentMutationStore,
  3117. DbRemoteDocumentStore,
  3118. DbTargetStore,
  3119. DbPrimaryClientStore,
  3120. DbTargetGlobalStore,
  3121. DbTargetDocumentStore,
  3122. DbClientMetadataStore,
  3123. DbRemoteDocumentGlobalStore,
  3124. DbCollectionParentStore,
  3125. DbBundleStore,
  3126. DbNamedQueryStore,
  3127. DbDocumentOverlayStore
  3128. ];
  3129. const V14_STORES = V13_STORES;
  3130. const V15_STORES = [
  3131. ...V14_STORES,
  3132. DbIndexConfigurationStore,
  3133. DbIndexStateStore,
  3134. DbIndexEntryStore
  3135. ];
  3136. /** Returns the object stores for the provided schema. */
  3137. function getObjectStores(schemaVersion) {
  3138. if (schemaVersion === 15) {
  3139. return V15_STORES;
  3140. }
  3141. else if (schemaVersion === 14) {
  3142. return V14_STORES;
  3143. }
  3144. else if (schemaVersion === 13) {
  3145. return V13_STORES;
  3146. }
  3147. else if (schemaVersion === 12) {
  3148. return V12_STORES;
  3149. }
  3150. else if (schemaVersion === 11) {
  3151. return V11_STORES;
  3152. }
  3153. else {
  3154. fail();
  3155. }
  3156. }
  3157. /**
  3158. * @license
  3159. * Copyright 2020 Google LLC
  3160. *
  3161. * Licensed under the Apache License, Version 2.0 (the "License");
  3162. * you may not use this file except in compliance with the License.
  3163. * You may obtain a copy of the License at
  3164. *
  3165. * http://www.apache.org/licenses/LICENSE-2.0
  3166. *
  3167. * Unless required by applicable law or agreed to in writing, software
  3168. * distributed under the License is distributed on an "AS IS" BASIS,
  3169. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3170. * See the License for the specific language governing permissions and
  3171. * limitations under the License.
  3172. */
  3173. class IndexedDbTransaction extends PersistenceTransaction {
  3174. constructor(simpleDbTransaction, currentSequenceNumber) {
  3175. super();
  3176. this.simpleDbTransaction = simpleDbTransaction;
  3177. this.currentSequenceNumber = currentSequenceNumber;
  3178. }
  3179. }
  3180. function getStore(txn, store) {
  3181. const indexedDbTransaction = debugCast(txn);
  3182. return SimpleDb.getStore(indexedDbTransaction.simpleDbTransaction, store);
  3183. }
  3184. /**
  3185. * @license
  3186. * Copyright 2017 Google LLC
  3187. *
  3188. * Licensed under the Apache License, Version 2.0 (the "License");
  3189. * you may not use this file except in compliance with the License.
  3190. * You may obtain a copy of the License at
  3191. *
  3192. * http://www.apache.org/licenses/LICENSE-2.0
  3193. *
  3194. * Unless required by applicable law or agreed to in writing, software
  3195. * distributed under the License is distributed on an "AS IS" BASIS,
  3196. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3197. * See the License for the specific language governing permissions and
  3198. * limitations under the License.
  3199. */
  3200. function objectSize(obj) {
  3201. let count = 0;
  3202. for (const key in obj) {
  3203. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3204. count++;
  3205. }
  3206. }
  3207. return count;
  3208. }
  3209. function forEach(obj, fn) {
  3210. for (const key in obj) {
  3211. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3212. fn(key, obj[key]);
  3213. }
  3214. }
  3215. }
  3216. function mapToArray(obj, fn) {
  3217. const result = [];
  3218. for (const key in obj) {
  3219. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3220. result.push(fn(obj[key], key, obj));
  3221. }
  3222. }
  3223. return result;
  3224. }
  3225. function isEmpty(obj) {
  3226. for (const key in obj) {
  3227. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3228. return false;
  3229. }
  3230. }
  3231. return true;
  3232. }
  3233. /**
  3234. * @license
  3235. * Copyright 2017 Google LLC
  3236. *
  3237. * Licensed under the Apache License, Version 2.0 (the "License");
  3238. * you may not use this file except in compliance with the License.
  3239. * You may obtain a copy of the License at
  3240. *
  3241. * http://www.apache.org/licenses/LICENSE-2.0
  3242. *
  3243. * Unless required by applicable law or agreed to in writing, software
  3244. * distributed under the License is distributed on an "AS IS" BASIS,
  3245. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3246. * See the License for the specific language governing permissions and
  3247. * limitations under the License.
  3248. */
  3249. // An immutable sorted map implementation, based on a Left-leaning Red-Black
  3250. // tree.
  3251. class SortedMap {
  3252. constructor(comparator, root) {
  3253. this.comparator = comparator;
  3254. this.root = root ? root : LLRBNode.EMPTY;
  3255. }
  3256. // Returns a copy of the map, with the specified key/value added or replaced.
  3257. insert(key, value) {
  3258. return new SortedMap(this.comparator, this.root
  3259. .insert(key, value, this.comparator)
  3260. .copy(null, null, LLRBNode.BLACK, null, null));
  3261. }
  3262. // Returns a copy of the map, with the specified key removed.
  3263. remove(key) {
  3264. return new SortedMap(this.comparator, this.root
  3265. .remove(key, this.comparator)
  3266. .copy(null, null, LLRBNode.BLACK, null, null));
  3267. }
  3268. // Returns the value of the node with the given key, or null.
  3269. get(key) {
  3270. let node = this.root;
  3271. while (!node.isEmpty()) {
  3272. const cmp = this.comparator(key, node.key);
  3273. if (cmp === 0) {
  3274. return node.value;
  3275. }
  3276. else if (cmp < 0) {
  3277. node = node.left;
  3278. }
  3279. else if (cmp > 0) {
  3280. node = node.right;
  3281. }
  3282. }
  3283. return null;
  3284. }
  3285. // Returns the index of the element in this sorted map, or -1 if it doesn't
  3286. // exist.
  3287. indexOf(key) {
  3288. // Number of nodes that were pruned when descending right
  3289. let prunedNodes = 0;
  3290. let node = this.root;
  3291. while (!node.isEmpty()) {
  3292. const cmp = this.comparator(key, node.key);
  3293. if (cmp === 0) {
  3294. return prunedNodes + node.left.size;
  3295. }
  3296. else if (cmp < 0) {
  3297. node = node.left;
  3298. }
  3299. else {
  3300. // Count all nodes left of the node plus the node itself
  3301. prunedNodes += node.left.size + 1;
  3302. node = node.right;
  3303. }
  3304. }
  3305. // Node not found
  3306. return -1;
  3307. }
  3308. isEmpty() {
  3309. return this.root.isEmpty();
  3310. }
  3311. // Returns the total number of nodes in the map.
  3312. get size() {
  3313. return this.root.size;
  3314. }
  3315. // Returns the minimum key in the map.
  3316. minKey() {
  3317. return this.root.minKey();
  3318. }
  3319. // Returns the maximum key in the map.
  3320. maxKey() {
  3321. return this.root.maxKey();
  3322. }
  3323. // Traverses the map in key order and calls the specified action function
  3324. // for each key/value pair. If action returns true, traversal is aborted.
  3325. // Returns the first truthy value returned by action, or the last falsey
  3326. // value returned by action.
  3327. inorderTraversal(action) {
  3328. return this.root.inorderTraversal(action);
  3329. }
  3330. forEach(fn) {
  3331. this.inorderTraversal((k, v) => {
  3332. fn(k, v);
  3333. return false;
  3334. });
  3335. }
  3336. toString() {
  3337. const descriptions = [];
  3338. this.inorderTraversal((k, v) => {
  3339. descriptions.push(`${k}:${v}`);
  3340. return false;
  3341. });
  3342. return `{${descriptions.join(', ')}}`;
  3343. }
  3344. // Traverses the map in reverse key order and calls the specified action
  3345. // function for each key/value pair. If action returns true, traversal is
  3346. // aborted.
  3347. // Returns the first truthy value returned by action, or the last falsey
  3348. // value returned by action.
  3349. reverseTraversal(action) {
  3350. return this.root.reverseTraversal(action);
  3351. }
  3352. // Returns an iterator over the SortedMap.
  3353. getIterator() {
  3354. return new SortedMapIterator(this.root, null, this.comparator, false);
  3355. }
  3356. getIteratorFrom(key) {
  3357. return new SortedMapIterator(this.root, key, this.comparator, false);
  3358. }
  3359. getReverseIterator() {
  3360. return new SortedMapIterator(this.root, null, this.comparator, true);
  3361. }
  3362. getReverseIteratorFrom(key) {
  3363. return new SortedMapIterator(this.root, key, this.comparator, true);
  3364. }
  3365. } // end SortedMap
  3366. // An iterator over an LLRBNode.
  3367. class SortedMapIterator {
  3368. constructor(node, startKey, comparator, isReverse) {
  3369. this.isReverse = isReverse;
  3370. this.nodeStack = [];
  3371. let cmp = 1;
  3372. while (!node.isEmpty()) {
  3373. cmp = startKey ? comparator(node.key, startKey) : 1;
  3374. // flip the comparison if we're going in reverse
  3375. if (startKey && isReverse) {
  3376. cmp *= -1;
  3377. }
  3378. if (cmp < 0) {
  3379. // This node is less than our start key. ignore it
  3380. if (this.isReverse) {
  3381. node = node.left;
  3382. }
  3383. else {
  3384. node = node.right;
  3385. }
  3386. }
  3387. else if (cmp === 0) {
  3388. // This node is exactly equal to our start key. Push it on the stack,
  3389. // but stop iterating;
  3390. this.nodeStack.push(node);
  3391. break;
  3392. }
  3393. else {
  3394. // This node is greater than our start key, add it to the stack and move
  3395. // to the next one
  3396. this.nodeStack.push(node);
  3397. if (this.isReverse) {
  3398. node = node.right;
  3399. }
  3400. else {
  3401. node = node.left;
  3402. }
  3403. }
  3404. }
  3405. }
  3406. getNext() {
  3407. let node = this.nodeStack.pop();
  3408. const result = { key: node.key, value: node.value };
  3409. if (this.isReverse) {
  3410. node = node.left;
  3411. while (!node.isEmpty()) {
  3412. this.nodeStack.push(node);
  3413. node = node.right;
  3414. }
  3415. }
  3416. else {
  3417. node = node.right;
  3418. while (!node.isEmpty()) {
  3419. this.nodeStack.push(node);
  3420. node = node.left;
  3421. }
  3422. }
  3423. return result;
  3424. }
  3425. hasNext() {
  3426. return this.nodeStack.length > 0;
  3427. }
  3428. peek() {
  3429. if (this.nodeStack.length === 0) {
  3430. return null;
  3431. }
  3432. const node = this.nodeStack[this.nodeStack.length - 1];
  3433. return { key: node.key, value: node.value };
  3434. }
  3435. } // end SortedMapIterator
  3436. // Represents a node in a Left-leaning Red-Black tree.
  3437. class LLRBNode {
  3438. constructor(key, value, color, left, right) {
  3439. this.key = key;
  3440. this.value = value;
  3441. this.color = color != null ? color : LLRBNode.RED;
  3442. this.left = left != null ? left : LLRBNode.EMPTY;
  3443. this.right = right != null ? right : LLRBNode.EMPTY;
  3444. this.size = this.left.size + 1 + this.right.size;
  3445. }
  3446. // Returns a copy of the current node, optionally replacing pieces of it.
  3447. copy(key, value, color, left, right) {
  3448. return new LLRBNode(key != null ? key : this.key, value != null ? value : this.value, color != null ? color : this.color, left != null ? left : this.left, right != null ? right : this.right);
  3449. }
  3450. isEmpty() {
  3451. return false;
  3452. }
  3453. // Traverses the tree in key order and calls the specified action function
  3454. // for each node. If action returns true, traversal is aborted.
  3455. // Returns the first truthy value returned by action, or the last falsey
  3456. // value returned by action.
  3457. inorderTraversal(action) {
  3458. return (this.left.inorderTraversal(action) ||
  3459. action(this.key, this.value) ||
  3460. this.right.inorderTraversal(action));
  3461. }
  3462. // Traverses the tree in reverse key order and calls the specified action
  3463. // function for each node. If action returns true, traversal is aborted.
  3464. // Returns the first truthy value returned by action, or the last falsey
  3465. // value returned by action.
  3466. reverseTraversal(action) {
  3467. return (this.right.reverseTraversal(action) ||
  3468. action(this.key, this.value) ||
  3469. this.left.reverseTraversal(action));
  3470. }
  3471. // Returns the minimum node in the tree.
  3472. min() {
  3473. if (this.left.isEmpty()) {
  3474. return this;
  3475. }
  3476. else {
  3477. return this.left.min();
  3478. }
  3479. }
  3480. // Returns the maximum key in the tree.
  3481. minKey() {
  3482. return this.min().key;
  3483. }
  3484. // Returns the maximum key in the tree.
  3485. maxKey() {
  3486. if (this.right.isEmpty()) {
  3487. return this.key;
  3488. }
  3489. else {
  3490. return this.right.maxKey();
  3491. }
  3492. }
  3493. // Returns new tree, with the key/value added.
  3494. insert(key, value, comparator) {
  3495. let n = this;
  3496. const cmp = comparator(key, n.key);
  3497. if (cmp < 0) {
  3498. n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
  3499. }
  3500. else if (cmp === 0) {
  3501. n = n.copy(null, value, null, null, null);
  3502. }
  3503. else {
  3504. n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
  3505. }
  3506. return n.fixUp();
  3507. }
  3508. removeMin() {
  3509. if (this.left.isEmpty()) {
  3510. return LLRBNode.EMPTY;
  3511. }
  3512. let n = this;
  3513. if (!n.left.isRed() && !n.left.left.isRed()) {
  3514. n = n.moveRedLeft();
  3515. }
  3516. n = n.copy(null, null, null, n.left.removeMin(), null);
  3517. return n.fixUp();
  3518. }
  3519. // Returns new tree, with the specified item removed.
  3520. remove(key, comparator) {
  3521. let smallest;
  3522. let n = this;
  3523. if (comparator(key, n.key) < 0) {
  3524. if (!n.left.isEmpty() && !n.left.isRed() && !n.left.left.isRed()) {
  3525. n = n.moveRedLeft();
  3526. }
  3527. n = n.copy(null, null, null, n.left.remove(key, comparator), null);
  3528. }
  3529. else {
  3530. if (n.left.isRed()) {
  3531. n = n.rotateRight();
  3532. }
  3533. if (!n.right.isEmpty() && !n.right.isRed() && !n.right.left.isRed()) {
  3534. n = n.moveRedRight();
  3535. }
  3536. if (comparator(key, n.key) === 0) {
  3537. if (n.right.isEmpty()) {
  3538. return LLRBNode.EMPTY;
  3539. }
  3540. else {
  3541. smallest = n.right.min();
  3542. n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin());
  3543. }
  3544. }
  3545. n = n.copy(null, null, null, null, n.right.remove(key, comparator));
  3546. }
  3547. return n.fixUp();
  3548. }
  3549. isRed() {
  3550. return this.color;
  3551. }
  3552. // Returns new tree after performing any needed rotations.
  3553. fixUp() {
  3554. let n = this;
  3555. if (n.right.isRed() && !n.left.isRed()) {
  3556. n = n.rotateLeft();
  3557. }
  3558. if (n.left.isRed() && n.left.left.isRed()) {
  3559. n = n.rotateRight();
  3560. }
  3561. if (n.left.isRed() && n.right.isRed()) {
  3562. n = n.colorFlip();
  3563. }
  3564. return n;
  3565. }
  3566. moveRedLeft() {
  3567. let n = this.colorFlip();
  3568. if (n.right.left.isRed()) {
  3569. n = n.copy(null, null, null, null, n.right.rotateRight());
  3570. n = n.rotateLeft();
  3571. n = n.colorFlip();
  3572. }
  3573. return n;
  3574. }
  3575. moveRedRight() {
  3576. let n = this.colorFlip();
  3577. if (n.left.left.isRed()) {
  3578. n = n.rotateRight();
  3579. n = n.colorFlip();
  3580. }
  3581. return n;
  3582. }
  3583. rotateLeft() {
  3584. const nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
  3585. return this.right.copy(null, null, this.color, nl, null);
  3586. }
  3587. rotateRight() {
  3588. const nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
  3589. return this.left.copy(null, null, this.color, null, nr);
  3590. }
  3591. colorFlip() {
  3592. const left = this.left.copy(null, null, !this.left.color, null, null);
  3593. const right = this.right.copy(null, null, !this.right.color, null, null);
  3594. return this.copy(null, null, !this.color, left, right);
  3595. }
  3596. // For testing.
  3597. checkMaxDepth() {
  3598. const blackDepth = this.check();
  3599. if (Math.pow(2.0, blackDepth) <= this.size + 1) {
  3600. return true;
  3601. }
  3602. else {
  3603. return false;
  3604. }
  3605. }
  3606. // In a balanced RB tree, the black-depth (number of black nodes) from root to
  3607. // leaves is equal on both sides. This function verifies that or asserts.
  3608. check() {
  3609. if (this.isRed() && this.left.isRed()) {
  3610. throw fail();
  3611. }
  3612. if (this.right.isRed()) {
  3613. throw fail();
  3614. }
  3615. const blackDepth = this.left.check();
  3616. if (blackDepth !== this.right.check()) {
  3617. throw fail();
  3618. }
  3619. else {
  3620. return blackDepth + (this.isRed() ? 0 : 1);
  3621. }
  3622. }
  3623. } // end LLRBNode
  3624. // Empty node is shared between all LLRB trees.
  3625. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  3626. LLRBNode.EMPTY = null;
  3627. LLRBNode.RED = true;
  3628. LLRBNode.BLACK = false;
  3629. // Represents an empty node (a leaf node in the Red-Black Tree).
  3630. class LLRBEmptyNode {
  3631. constructor() {
  3632. this.size = 0;
  3633. }
  3634. get key() {
  3635. throw fail();
  3636. }
  3637. get value() {
  3638. throw fail();
  3639. }
  3640. get color() {
  3641. throw fail();
  3642. }
  3643. get left() {
  3644. throw fail();
  3645. }
  3646. get right() {
  3647. throw fail();
  3648. }
  3649. // Returns a copy of the current node.
  3650. copy(key, value, color, left, right) {
  3651. return this;
  3652. }
  3653. // Returns a copy of the tree, with the specified key/value added.
  3654. insert(key, value, comparator) {
  3655. return new LLRBNode(key, value);
  3656. }
  3657. // Returns a copy of the tree, with the specified key removed.
  3658. remove(key, comparator) {
  3659. return this;
  3660. }
  3661. isEmpty() {
  3662. return true;
  3663. }
  3664. inorderTraversal(action) {
  3665. return false;
  3666. }
  3667. reverseTraversal(action) {
  3668. return false;
  3669. }
  3670. minKey() {
  3671. return null;
  3672. }
  3673. maxKey() {
  3674. return null;
  3675. }
  3676. isRed() {
  3677. return false;
  3678. }
  3679. // For testing.
  3680. checkMaxDepth() {
  3681. return true;
  3682. }
  3683. check() {
  3684. return 0;
  3685. }
  3686. } // end LLRBEmptyNode
  3687. LLRBNode.EMPTY = new LLRBEmptyNode();
  3688. /**
  3689. * @license
  3690. * Copyright 2017 Google LLC
  3691. *
  3692. * Licensed under the Apache License, Version 2.0 (the "License");
  3693. * you may not use this file except in compliance with the License.
  3694. * You may obtain a copy of the License at
  3695. *
  3696. * http://www.apache.org/licenses/LICENSE-2.0
  3697. *
  3698. * Unless required by applicable law or agreed to in writing, software
  3699. * distributed under the License is distributed on an "AS IS" BASIS,
  3700. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3701. * See the License for the specific language governing permissions and
  3702. * limitations under the License.
  3703. */
  3704. /**
  3705. * SortedSet is an immutable (copy-on-write) collection that holds elements
  3706. * in order specified by the provided comparator.
  3707. *
  3708. * NOTE: if provided comparator returns 0 for two elements, we consider them to
  3709. * be equal!
  3710. */
  3711. class SortedSet {
  3712. constructor(comparator) {
  3713. this.comparator = comparator;
  3714. this.data = new SortedMap(this.comparator);
  3715. }
  3716. has(elem) {
  3717. return this.data.get(elem) !== null;
  3718. }
  3719. first() {
  3720. return this.data.minKey();
  3721. }
  3722. last() {
  3723. return this.data.maxKey();
  3724. }
  3725. get size() {
  3726. return this.data.size;
  3727. }
  3728. indexOf(elem) {
  3729. return this.data.indexOf(elem);
  3730. }
  3731. /** Iterates elements in order defined by "comparator" */
  3732. forEach(cb) {
  3733. this.data.inorderTraversal((k, v) => {
  3734. cb(k);
  3735. return false;
  3736. });
  3737. }
  3738. /** Iterates over `elem`s such that: range[0] &lt;= elem &lt; range[1]. */
  3739. forEachInRange(range, cb) {
  3740. const iter = this.data.getIteratorFrom(range[0]);
  3741. while (iter.hasNext()) {
  3742. const elem = iter.getNext();
  3743. if (this.comparator(elem.key, range[1]) >= 0) {
  3744. return;
  3745. }
  3746. cb(elem.key);
  3747. }
  3748. }
  3749. /**
  3750. * Iterates over `elem`s such that: start &lt;= elem until false is returned.
  3751. */
  3752. forEachWhile(cb, start) {
  3753. let iter;
  3754. if (start !== undefined) {
  3755. iter = this.data.getIteratorFrom(start);
  3756. }
  3757. else {
  3758. iter = this.data.getIterator();
  3759. }
  3760. while (iter.hasNext()) {
  3761. const elem = iter.getNext();
  3762. const result = cb(elem.key);
  3763. if (!result) {
  3764. return;
  3765. }
  3766. }
  3767. }
  3768. /** Finds the least element greater than or equal to `elem`. */
  3769. firstAfterOrEqual(elem) {
  3770. const iter = this.data.getIteratorFrom(elem);
  3771. return iter.hasNext() ? iter.getNext().key : null;
  3772. }
  3773. getIterator() {
  3774. return new SortedSetIterator(this.data.getIterator());
  3775. }
  3776. getIteratorFrom(key) {
  3777. return new SortedSetIterator(this.data.getIteratorFrom(key));
  3778. }
  3779. /** Inserts or updates an element */
  3780. add(elem) {
  3781. return this.copy(this.data.remove(elem).insert(elem, true));
  3782. }
  3783. /** Deletes an element */
  3784. delete(elem) {
  3785. if (!this.has(elem)) {
  3786. return this;
  3787. }
  3788. return this.copy(this.data.remove(elem));
  3789. }
  3790. isEmpty() {
  3791. return this.data.isEmpty();
  3792. }
  3793. unionWith(other) {
  3794. let result = this;
  3795. // Make sure `result` always refers to the larger one of the two sets.
  3796. if (result.size < other.size) {
  3797. result = other;
  3798. other = this;
  3799. }
  3800. other.forEach(elem => {
  3801. result = result.add(elem);
  3802. });
  3803. return result;
  3804. }
  3805. isEqual(other) {
  3806. if (!(other instanceof SortedSet)) {
  3807. return false;
  3808. }
  3809. if (this.size !== other.size) {
  3810. return false;
  3811. }
  3812. const thisIt = this.data.getIterator();
  3813. const otherIt = other.data.getIterator();
  3814. while (thisIt.hasNext()) {
  3815. const thisElem = thisIt.getNext().key;
  3816. const otherElem = otherIt.getNext().key;
  3817. if (this.comparator(thisElem, otherElem) !== 0) {
  3818. return false;
  3819. }
  3820. }
  3821. return true;
  3822. }
  3823. toArray() {
  3824. const res = [];
  3825. this.forEach(targetId => {
  3826. res.push(targetId);
  3827. });
  3828. return res;
  3829. }
  3830. toString() {
  3831. const result = [];
  3832. this.forEach(elem => result.push(elem));
  3833. return 'SortedSet(' + result.toString() + ')';
  3834. }
  3835. copy(data) {
  3836. const result = new SortedSet(this.comparator);
  3837. result.data = data;
  3838. return result;
  3839. }
  3840. }
  3841. class SortedSetIterator {
  3842. constructor(iter) {
  3843. this.iter = iter;
  3844. }
  3845. getNext() {
  3846. return this.iter.getNext().key;
  3847. }
  3848. hasNext() {
  3849. return this.iter.hasNext();
  3850. }
  3851. }
  3852. /**
  3853. * Compares two sorted sets for equality using their natural ordering. The
  3854. * method computes the intersection and invokes `onAdd` for every element that
  3855. * is in `after` but not `before`. `onRemove` is invoked for every element in
  3856. * `before` but missing from `after`.
  3857. *
  3858. * The method creates a copy of both `before` and `after` and runs in O(n log
  3859. * n), where n is the size of the two lists.
  3860. *
  3861. * @param before - The elements that exist in the original set.
  3862. * @param after - The elements to diff against the original set.
  3863. * @param comparator - The comparator for the elements in before and after.
  3864. * @param onAdd - A function to invoke for every element that is part of `
  3865. * after` but not `before`.
  3866. * @param onRemove - A function to invoke for every element that is part of
  3867. * `before` but not `after`.
  3868. */
  3869. function diffSortedSets(before, after, comparator, onAdd, onRemove) {
  3870. const beforeIt = before.getIterator();
  3871. const afterIt = after.getIterator();
  3872. let beforeValue = advanceIterator(beforeIt);
  3873. let afterValue = advanceIterator(afterIt);
  3874. // Walk through the two sets at the same time, using the ordering defined by
  3875. // `comparator`.
  3876. while (beforeValue || afterValue) {
  3877. let added = false;
  3878. let removed = false;
  3879. if (beforeValue && afterValue) {
  3880. const cmp = comparator(beforeValue, afterValue);
  3881. if (cmp < 0) {
  3882. // The element was removed if the next element in our ordered
  3883. // walkthrough is only in `before`.
  3884. removed = true;
  3885. }
  3886. else if (cmp > 0) {
  3887. // The element was added if the next element in our ordered walkthrough
  3888. // is only in `after`.
  3889. added = true;
  3890. }
  3891. }
  3892. else if (beforeValue != null) {
  3893. removed = true;
  3894. }
  3895. else {
  3896. added = true;
  3897. }
  3898. if (added) {
  3899. onAdd(afterValue);
  3900. afterValue = advanceIterator(afterIt);
  3901. }
  3902. else if (removed) {
  3903. onRemove(beforeValue);
  3904. beforeValue = advanceIterator(beforeIt);
  3905. }
  3906. else {
  3907. beforeValue = advanceIterator(beforeIt);
  3908. afterValue = advanceIterator(afterIt);
  3909. }
  3910. }
  3911. }
  3912. /**
  3913. * Returns the next element from the iterator or `undefined` if none available.
  3914. */
  3915. function advanceIterator(it) {
  3916. return it.hasNext() ? it.getNext() : undefined;
  3917. }
  3918. /**
  3919. * @license
  3920. * Copyright 2020 Google LLC
  3921. *
  3922. * Licensed under the Apache License, Version 2.0 (the "License");
  3923. * you may not use this file except in compliance with the License.
  3924. * You may obtain a copy of the License at
  3925. *
  3926. * http://www.apache.org/licenses/LICENSE-2.0
  3927. *
  3928. * Unless required by applicable law or agreed to in writing, software
  3929. * distributed under the License is distributed on an "AS IS" BASIS,
  3930. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3931. * See the License for the specific language governing permissions and
  3932. * limitations under the License.
  3933. */
  3934. /**
  3935. * Provides a set of fields that can be used to partially patch a document.
  3936. * FieldMask is used in conjunction with ObjectValue.
  3937. * Examples:
  3938. * foo - Overwrites foo entirely with the provided value. If foo is not
  3939. * present in the companion ObjectValue, the field is deleted.
  3940. * foo.bar - Overwrites only the field bar of the object foo.
  3941. * If foo is not an object, foo is replaced with an object
  3942. * containing foo
  3943. */
  3944. class FieldMask {
  3945. constructor(fields) {
  3946. this.fields = fields;
  3947. // TODO(dimond): validation of FieldMask
  3948. // Sort the field mask to support `FieldMask.isEqual()` and assert below.
  3949. fields.sort(FieldPath$1.comparator);
  3950. }
  3951. static empty() {
  3952. return new FieldMask([]);
  3953. }
  3954. /**
  3955. * Returns a new FieldMask object that is the result of adding all the given
  3956. * fields paths to this field mask.
  3957. */
  3958. unionWith(extraFields) {
  3959. let mergedMaskSet = new SortedSet(FieldPath$1.comparator);
  3960. for (const fieldPath of this.fields) {
  3961. mergedMaskSet = mergedMaskSet.add(fieldPath);
  3962. }
  3963. for (const fieldPath of extraFields) {
  3964. mergedMaskSet = mergedMaskSet.add(fieldPath);
  3965. }
  3966. return new FieldMask(mergedMaskSet.toArray());
  3967. }
  3968. /**
  3969. * Verifies that `fieldPath` is included by at least one field in this field
  3970. * mask.
  3971. *
  3972. * This is an O(n) operation, where `n` is the size of the field mask.
  3973. */
  3974. covers(fieldPath) {
  3975. for (const fieldMaskPath of this.fields) {
  3976. if (fieldMaskPath.isPrefixOf(fieldPath)) {
  3977. return true;
  3978. }
  3979. }
  3980. return false;
  3981. }
  3982. isEqual(other) {
  3983. return arrayEquals(this.fields, other.fields, (l, r) => l.isEqual(r));
  3984. }
  3985. }
  3986. /**
  3987. * @license
  3988. * Copyright 2020 Google LLC
  3989. *
  3990. * Licensed under the Apache License, Version 2.0 (the "License");
  3991. * you may not use this file except in compliance with the License.
  3992. * You may obtain a copy of the License at
  3993. *
  3994. * http://www.apache.org/licenses/LICENSE-2.0
  3995. *
  3996. * Unless required by applicable law or agreed to in writing, software
  3997. * distributed under the License is distributed on an "AS IS" BASIS,
  3998. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3999. * See the License for the specific language governing permissions and
  4000. * limitations under the License.
  4001. */
  4002. /** Converts a Base64 encoded string to a binary string. */
  4003. function decodeBase64(encoded) {
  4004. // Note: We used to validate the base64 string here via a regular expression.
  4005. // This was removed to improve the performance of indexing.
  4006. return Buffer.from(encoded, 'base64').toString('binary');
  4007. }
  4008. /** Converts a binary string to a Base64 encoded string. */
  4009. function encodeBase64(raw) {
  4010. return Buffer.from(raw, 'binary').toString('base64');
  4011. }
  4012. /** True if and only if the Base64 conversion functions are available. */
  4013. function isBase64Available() {
  4014. return true;
  4015. }
  4016. /**
  4017. * @license
  4018. * Copyright 2020 Google LLC
  4019. *
  4020. * Licensed under the Apache License, Version 2.0 (the "License");
  4021. * you may not use this file except in compliance with the License.
  4022. * You may obtain a copy of the License at
  4023. *
  4024. * http://www.apache.org/licenses/LICENSE-2.0
  4025. *
  4026. * Unless required by applicable law or agreed to in writing, software
  4027. * distributed under the License is distributed on an "AS IS" BASIS,
  4028. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4029. * See the License for the specific language governing permissions and
  4030. * limitations under the License.
  4031. */
  4032. /**
  4033. * Immutable class that represents a "proto" byte string.
  4034. *
  4035. * Proto byte strings can either be Base64-encoded strings or Uint8Arrays when
  4036. * sent on the wire. This class abstracts away this differentiation by holding
  4037. * the proto byte string in a common class that must be converted into a string
  4038. * before being sent as a proto.
  4039. * @internal
  4040. */
  4041. class ByteString {
  4042. constructor(binaryString) {
  4043. this.binaryString = binaryString;
  4044. }
  4045. static fromBase64String(base64) {
  4046. const binaryString = decodeBase64(base64);
  4047. return new ByteString(binaryString);
  4048. }
  4049. static fromUint8Array(array) {
  4050. // TODO(indexing); Remove the copy of the byte string here as this method
  4051. // is frequently called during indexing.
  4052. const binaryString = binaryStringFromUint8Array(array);
  4053. return new ByteString(binaryString);
  4054. }
  4055. [Symbol.iterator]() {
  4056. let i = 0;
  4057. return {
  4058. next: () => {
  4059. if (i < this.binaryString.length) {
  4060. return { value: this.binaryString.charCodeAt(i++), done: false };
  4061. }
  4062. else {
  4063. return { value: undefined, done: true };
  4064. }
  4065. }
  4066. };
  4067. }
  4068. toBase64() {
  4069. return encodeBase64(this.binaryString);
  4070. }
  4071. toUint8Array() {
  4072. return uint8ArrayFromBinaryString(this.binaryString);
  4073. }
  4074. approximateByteSize() {
  4075. return this.binaryString.length * 2;
  4076. }
  4077. compareTo(other) {
  4078. return primitiveComparator(this.binaryString, other.binaryString);
  4079. }
  4080. isEqual(other) {
  4081. return this.binaryString === other.binaryString;
  4082. }
  4083. }
  4084. ByteString.EMPTY_BYTE_STRING = new ByteString('');
  4085. /**
  4086. * Helper function to convert an Uint8array to a binary string.
  4087. */
  4088. function binaryStringFromUint8Array(array) {
  4089. let binaryString = '';
  4090. for (let i = 0; i < array.length; ++i) {
  4091. binaryString += String.fromCharCode(array[i]);
  4092. }
  4093. return binaryString;
  4094. }
  4095. /**
  4096. * Helper function to convert a binary string to an Uint8Array.
  4097. */
  4098. function uint8ArrayFromBinaryString(binaryString) {
  4099. const buffer = new Uint8Array(binaryString.length);
  4100. for (let i = 0; i < binaryString.length; i++) {
  4101. buffer[i] = binaryString.charCodeAt(i);
  4102. }
  4103. return buffer;
  4104. }
  4105. /**
  4106. * @license
  4107. * Copyright 2020 Google LLC
  4108. *
  4109. * Licensed under the Apache License, Version 2.0 (the "License");
  4110. * you may not use this file except in compliance with the License.
  4111. * You may obtain a copy of the License at
  4112. *
  4113. * http://www.apache.org/licenses/LICENSE-2.0
  4114. *
  4115. * Unless required by applicable law or agreed to in writing, software
  4116. * distributed under the License is distributed on an "AS IS" BASIS,
  4117. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4118. * See the License for the specific language governing permissions and
  4119. * limitations under the License.
  4120. */
  4121. // A RegExp matching ISO 8601 UTC timestamps with optional fraction.
  4122. const ISO_TIMESTAMP_REG_EXP = new RegExp(/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.(\d+))?Z$/);
  4123. /**
  4124. * Converts the possible Proto values for a timestamp value into a "seconds and
  4125. * nanos" representation.
  4126. */
  4127. function normalizeTimestamp(date) {
  4128. hardAssert(!!date);
  4129. // The json interface (for the browser) will return an iso timestamp string,
  4130. // while the proto js library (for node) will return a
  4131. // google.protobuf.Timestamp instance.
  4132. if (typeof date === 'string') {
  4133. // The date string can have higher precision (nanos) than the Date class
  4134. // (millis), so we do some custom parsing here.
  4135. // Parse the nanos right out of the string.
  4136. let nanos = 0;
  4137. const fraction = ISO_TIMESTAMP_REG_EXP.exec(date);
  4138. hardAssert(!!fraction);
  4139. if (fraction[1]) {
  4140. // Pad the fraction out to 9 digits (nanos).
  4141. let nanoStr = fraction[1];
  4142. nanoStr = (nanoStr + '000000000').substr(0, 9);
  4143. nanos = Number(nanoStr);
  4144. }
  4145. // Parse the date to get the seconds.
  4146. const parsedDate = new Date(date);
  4147. const seconds = Math.floor(parsedDate.getTime() / 1000);
  4148. return { seconds, nanos };
  4149. }
  4150. else {
  4151. // TODO(b/37282237): Use strings for Proto3 timestamps
  4152. // assert(!this.options.useProto3Json,
  4153. // 'The timestamp instance format requires Proto JS.');
  4154. const seconds = normalizeNumber(date.seconds);
  4155. const nanos = normalizeNumber(date.nanos);
  4156. return { seconds, nanos };
  4157. }
  4158. }
  4159. /**
  4160. * Converts the possible Proto types for numbers into a JavaScript number.
  4161. * Returns 0 if the value is not numeric.
  4162. */
  4163. function normalizeNumber(value) {
  4164. // TODO(bjornick): Handle int64 greater than 53 bits.
  4165. if (typeof value === 'number') {
  4166. return value;
  4167. }
  4168. else if (typeof value === 'string') {
  4169. return Number(value);
  4170. }
  4171. else {
  4172. return 0;
  4173. }
  4174. }
  4175. /** Converts the possible Proto types for Blobs into a ByteString. */
  4176. function normalizeByteString(blob) {
  4177. if (typeof blob === 'string') {
  4178. return ByteString.fromBase64String(blob);
  4179. }
  4180. else {
  4181. return ByteString.fromUint8Array(blob);
  4182. }
  4183. }
  4184. /**
  4185. * @license
  4186. * Copyright 2020 Google LLC
  4187. *
  4188. * Licensed under the Apache License, Version 2.0 (the "License");
  4189. * you may not use this file except in compliance with the License.
  4190. * You may obtain a copy of the License at
  4191. *
  4192. * http://www.apache.org/licenses/LICENSE-2.0
  4193. *
  4194. * Unless required by applicable law or agreed to in writing, software
  4195. * distributed under the License is distributed on an "AS IS" BASIS,
  4196. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4197. * See the License for the specific language governing permissions and
  4198. * limitations under the License.
  4199. */
  4200. /**
  4201. * Represents a locally-applied ServerTimestamp.
  4202. *
  4203. * Server Timestamps are backed by MapValues that contain an internal field
  4204. * `__type__` with a value of `server_timestamp`. The previous value and local
  4205. * write time are stored in its `__previous_value__` and `__local_write_time__`
  4206. * fields respectively.
  4207. *
  4208. * Notes:
  4209. * - ServerTimestampValue instances are created as the result of applying a
  4210. * transform. They can only exist in the local view of a document. Therefore
  4211. * they do not need to be parsed or serialized.
  4212. * - When evaluated locally (e.g. for snapshot.data()), they by default
  4213. * evaluate to `null`. This behavior can be configured by passing custom
  4214. * FieldValueOptions to value().
  4215. * - With respect to other ServerTimestampValues, they sort by their
  4216. * localWriteTime.
  4217. */
  4218. const SERVER_TIMESTAMP_SENTINEL = 'server_timestamp';
  4219. const TYPE_KEY = '__type__';
  4220. const PREVIOUS_VALUE_KEY = '__previous_value__';
  4221. const LOCAL_WRITE_TIME_KEY = '__local_write_time__';
  4222. function isServerTimestamp(value) {
  4223. var _a, _b;
  4224. const type = (_b = (((_a = value === null || value === void 0 ? void 0 : value.mapValue) === null || _a === void 0 ? void 0 : _a.fields) || {})[TYPE_KEY]) === null || _b === void 0 ? void 0 : _b.stringValue;
  4225. return type === SERVER_TIMESTAMP_SENTINEL;
  4226. }
  4227. /**
  4228. * Creates a new ServerTimestamp proto value (using the internal format).
  4229. */
  4230. function serverTimestamp$1(localWriteTime, previousValue) {
  4231. const mapValue = {
  4232. fields: {
  4233. [TYPE_KEY]: {
  4234. stringValue: SERVER_TIMESTAMP_SENTINEL
  4235. },
  4236. [LOCAL_WRITE_TIME_KEY]: {
  4237. timestampValue: {
  4238. seconds: localWriteTime.seconds,
  4239. nanos: localWriteTime.nanoseconds
  4240. }
  4241. }
  4242. }
  4243. };
  4244. // We should avoid storing deeply nested server timestamp map values
  4245. // because we never use the intermediate "previous values".
  4246. // For example:
  4247. // previous: 42L, add: t1, result: t1 -> 42L
  4248. // previous: t1, add: t2, result: t2 -> 42L (NOT t2 -> t1 -> 42L)
  4249. // previous: t2, add: t3, result: t3 -> 42L (NOT t3 -> t2 -> t1 -> 42L)
  4250. // `getPreviousValue` recursively traverses server timestamps to find the
  4251. // least recent Value.
  4252. if (previousValue && isServerTimestamp(previousValue)) {
  4253. previousValue = getPreviousValue(previousValue);
  4254. }
  4255. if (previousValue) {
  4256. mapValue.fields[PREVIOUS_VALUE_KEY] = previousValue;
  4257. }
  4258. return { mapValue };
  4259. }
  4260. /**
  4261. * Returns the value of the field before this ServerTimestamp was set.
  4262. *
  4263. * Preserving the previous values allows the user to display the last resoled
  4264. * value until the backend responds with the timestamp.
  4265. */
  4266. function getPreviousValue(value) {
  4267. const previousValue = value.mapValue.fields[PREVIOUS_VALUE_KEY];
  4268. if (isServerTimestamp(previousValue)) {
  4269. return getPreviousValue(previousValue);
  4270. }
  4271. return previousValue;
  4272. }
  4273. /**
  4274. * Returns the local time at which this timestamp was first set.
  4275. */
  4276. function getLocalWriteTime(value) {
  4277. const localWriteTime = normalizeTimestamp(value.mapValue.fields[LOCAL_WRITE_TIME_KEY].timestampValue);
  4278. return new Timestamp(localWriteTime.seconds, localWriteTime.nanos);
  4279. }
  4280. /**
  4281. * @license
  4282. * Copyright 2017 Google LLC
  4283. *
  4284. * Licensed under the Apache License, Version 2.0 (the "License");
  4285. * you may not use this file except in compliance with the License.
  4286. * You may obtain a copy of the License at
  4287. *
  4288. * http://www.apache.org/licenses/LICENSE-2.0
  4289. *
  4290. * Unless required by applicable law or agreed to in writing, software
  4291. * distributed under the License is distributed on an "AS IS" BASIS,
  4292. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4293. * See the License for the specific language governing permissions and
  4294. * limitations under the License.
  4295. */
  4296. class DatabaseInfo {
  4297. /**
  4298. * Constructs a DatabaseInfo using the provided host, databaseId and
  4299. * persistenceKey.
  4300. *
  4301. * @param databaseId - The database to use.
  4302. * @param appId - The Firebase App Id.
  4303. * @param persistenceKey - A unique identifier for this Firestore's local
  4304. * storage (used in conjunction with the databaseId).
  4305. * @param host - The Firestore backend host to connect to.
  4306. * @param ssl - Whether to use SSL when connecting.
  4307. * @param forceLongPolling - Whether to use the forceLongPolling option
  4308. * when using WebChannel as the network transport.
  4309. * @param autoDetectLongPolling - Whether to use the detectBufferingProxy
  4310. * option when using WebChannel as the network transport.
  4311. * @param longPollingOptions Options that configure long-polling.
  4312. * @param useFetchStreams Whether to use the Fetch API instead of
  4313. * XMLHTTPRequest
  4314. */
  4315. constructor(databaseId, appId, persistenceKey, host, ssl, forceLongPolling, autoDetectLongPolling, longPollingOptions, useFetchStreams) {
  4316. this.databaseId = databaseId;
  4317. this.appId = appId;
  4318. this.persistenceKey = persistenceKey;
  4319. this.host = host;
  4320. this.ssl = ssl;
  4321. this.forceLongPolling = forceLongPolling;
  4322. this.autoDetectLongPolling = autoDetectLongPolling;
  4323. this.longPollingOptions = longPollingOptions;
  4324. this.useFetchStreams = useFetchStreams;
  4325. }
  4326. }
  4327. /** The default database name for a project. */
  4328. const DEFAULT_DATABASE_NAME = '(default)';
  4329. /**
  4330. * Represents the database ID a Firestore client is associated with.
  4331. * @internal
  4332. */
  4333. class DatabaseId {
  4334. constructor(projectId, database) {
  4335. this.projectId = projectId;
  4336. this.database = database ? database : DEFAULT_DATABASE_NAME;
  4337. }
  4338. static empty() {
  4339. return new DatabaseId('', '');
  4340. }
  4341. get isDefaultDatabase() {
  4342. return this.database === DEFAULT_DATABASE_NAME;
  4343. }
  4344. isEqual(other) {
  4345. return (other instanceof DatabaseId &&
  4346. other.projectId === this.projectId &&
  4347. other.database === this.database);
  4348. }
  4349. }
  4350. function databaseIdFromApp(app, database) {
  4351. if (!Object.prototype.hasOwnProperty.apply(app.options, ['projectId'])) {
  4352. throw new FirestoreError(Code.INVALID_ARGUMENT, '"projectId" not provided in firebase.initializeApp.');
  4353. }
  4354. return new DatabaseId(app.options.projectId, database);
  4355. }
  4356. /**
  4357. * @license
  4358. * Copyright 2017 Google LLC
  4359. *
  4360. * Licensed under the Apache License, Version 2.0 (the "License");
  4361. * you may not use this file except in compliance with the License.
  4362. * You may obtain a copy of the License at
  4363. *
  4364. * http://www.apache.org/licenses/LICENSE-2.0
  4365. *
  4366. * Unless required by applicable law or agreed to in writing, software
  4367. * distributed under the License is distributed on an "AS IS" BASIS,
  4368. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4369. * See the License for the specific language governing permissions and
  4370. * limitations under the License.
  4371. */
  4372. /** Sentinel value that sorts before any Mutation Batch ID. */
  4373. const BATCHID_UNKNOWN = -1;
  4374. /**
  4375. * Returns whether a variable is either undefined or null.
  4376. */
  4377. function isNullOrUndefined(value) {
  4378. return value === null || value === undefined;
  4379. }
  4380. /** Returns whether the value represents -0. */
  4381. function isNegativeZero(value) {
  4382. // Detect if the value is -0.0. Based on polyfill from
  4383. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
  4384. return value === 0 && 1 / value === 1 / -0;
  4385. }
  4386. /**
  4387. * Returns whether a value is an integer and in the safe integer range
  4388. * @param value - The value to test for being an integer and in the safe range
  4389. */
  4390. function isSafeInteger(value) {
  4391. return (typeof value === 'number' &&
  4392. Number.isInteger(value) &&
  4393. !isNegativeZero(value) &&
  4394. value <= Number.MAX_SAFE_INTEGER &&
  4395. value >= Number.MIN_SAFE_INTEGER);
  4396. }
  4397. /**
  4398. * @license
  4399. * Copyright 2020 Google LLC
  4400. *
  4401. * Licensed under the Apache License, Version 2.0 (the "License");
  4402. * you may not use this file except in compliance with the License.
  4403. * You may obtain a copy of the License at
  4404. *
  4405. * http://www.apache.org/licenses/LICENSE-2.0
  4406. *
  4407. * Unless required by applicable law or agreed to in writing, software
  4408. * distributed under the License is distributed on an "AS IS" BASIS,
  4409. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4410. * See the License for the specific language governing permissions and
  4411. * limitations under the License.
  4412. */
  4413. const MAX_VALUE_TYPE = '__max__';
  4414. const MAX_VALUE = {
  4415. mapValue: {
  4416. fields: {
  4417. '__type__': { stringValue: MAX_VALUE_TYPE }
  4418. }
  4419. }
  4420. };
  4421. const MIN_VALUE = {
  4422. nullValue: 'NULL_VALUE'
  4423. };
  4424. /** Extracts the backend's type order for the provided value. */
  4425. function typeOrder(value) {
  4426. if ('nullValue' in value) {
  4427. return 0 /* TypeOrder.NullValue */;
  4428. }
  4429. else if ('booleanValue' in value) {
  4430. return 1 /* TypeOrder.BooleanValue */;
  4431. }
  4432. else if ('integerValue' in value || 'doubleValue' in value) {
  4433. return 2 /* TypeOrder.NumberValue */;
  4434. }
  4435. else if ('timestampValue' in value) {
  4436. return 3 /* TypeOrder.TimestampValue */;
  4437. }
  4438. else if ('stringValue' in value) {
  4439. return 5 /* TypeOrder.StringValue */;
  4440. }
  4441. else if ('bytesValue' in value) {
  4442. return 6 /* TypeOrder.BlobValue */;
  4443. }
  4444. else if ('referenceValue' in value) {
  4445. return 7 /* TypeOrder.RefValue */;
  4446. }
  4447. else if ('geoPointValue' in value) {
  4448. return 8 /* TypeOrder.GeoPointValue */;
  4449. }
  4450. else if ('arrayValue' in value) {
  4451. return 9 /* TypeOrder.ArrayValue */;
  4452. }
  4453. else if ('mapValue' in value) {
  4454. if (isServerTimestamp(value)) {
  4455. return 4 /* TypeOrder.ServerTimestampValue */;
  4456. }
  4457. else if (isMaxValue(value)) {
  4458. return 9007199254740991 /* TypeOrder.MaxValue */;
  4459. }
  4460. return 10 /* TypeOrder.ObjectValue */;
  4461. }
  4462. else {
  4463. return fail();
  4464. }
  4465. }
  4466. /** Tests `left` and `right` for equality based on the backend semantics. */
  4467. function valueEquals(left, right) {
  4468. if (left === right) {
  4469. return true;
  4470. }
  4471. const leftType = typeOrder(left);
  4472. const rightType = typeOrder(right);
  4473. if (leftType !== rightType) {
  4474. return false;
  4475. }
  4476. switch (leftType) {
  4477. case 0 /* TypeOrder.NullValue */:
  4478. return true;
  4479. case 1 /* TypeOrder.BooleanValue */:
  4480. return left.booleanValue === right.booleanValue;
  4481. case 4 /* TypeOrder.ServerTimestampValue */:
  4482. return getLocalWriteTime(left).isEqual(getLocalWriteTime(right));
  4483. case 3 /* TypeOrder.TimestampValue */:
  4484. return timestampEquals(left, right);
  4485. case 5 /* TypeOrder.StringValue */:
  4486. return left.stringValue === right.stringValue;
  4487. case 6 /* TypeOrder.BlobValue */:
  4488. return blobEquals(left, right);
  4489. case 7 /* TypeOrder.RefValue */:
  4490. return left.referenceValue === right.referenceValue;
  4491. case 8 /* TypeOrder.GeoPointValue */:
  4492. return geoPointEquals(left, right);
  4493. case 2 /* TypeOrder.NumberValue */:
  4494. return numberEquals(left, right);
  4495. case 9 /* TypeOrder.ArrayValue */:
  4496. return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
  4497. case 10 /* TypeOrder.ObjectValue */:
  4498. return objectEquals(left, right);
  4499. case 9007199254740991 /* TypeOrder.MaxValue */:
  4500. return true;
  4501. default:
  4502. return fail();
  4503. }
  4504. }
  4505. function timestampEquals(left, right) {
  4506. if (typeof left.timestampValue === 'string' &&
  4507. typeof right.timestampValue === 'string' &&
  4508. left.timestampValue.length === right.timestampValue.length) {
  4509. // Use string equality for ISO 8601 timestamps
  4510. return left.timestampValue === right.timestampValue;
  4511. }
  4512. const leftTimestamp = normalizeTimestamp(left.timestampValue);
  4513. const rightTimestamp = normalizeTimestamp(right.timestampValue);
  4514. return (leftTimestamp.seconds === rightTimestamp.seconds &&
  4515. leftTimestamp.nanos === rightTimestamp.nanos);
  4516. }
  4517. function geoPointEquals(left, right) {
  4518. return (normalizeNumber(left.geoPointValue.latitude) ===
  4519. normalizeNumber(right.geoPointValue.latitude) &&
  4520. normalizeNumber(left.geoPointValue.longitude) ===
  4521. normalizeNumber(right.geoPointValue.longitude));
  4522. }
  4523. function blobEquals(left, right) {
  4524. return normalizeByteString(left.bytesValue).isEqual(normalizeByteString(right.bytesValue));
  4525. }
  4526. function numberEquals(left, right) {
  4527. if ('integerValue' in left && 'integerValue' in right) {
  4528. return (normalizeNumber(left.integerValue) === normalizeNumber(right.integerValue));
  4529. }
  4530. else if ('doubleValue' in left && 'doubleValue' in right) {
  4531. const n1 = normalizeNumber(left.doubleValue);
  4532. const n2 = normalizeNumber(right.doubleValue);
  4533. if (n1 === n2) {
  4534. return isNegativeZero(n1) === isNegativeZero(n2);
  4535. }
  4536. else {
  4537. return isNaN(n1) && isNaN(n2);
  4538. }
  4539. }
  4540. return false;
  4541. }
  4542. function objectEquals(left, right) {
  4543. const leftMap = left.mapValue.fields || {};
  4544. const rightMap = right.mapValue.fields || {};
  4545. if (objectSize(leftMap) !== objectSize(rightMap)) {
  4546. return false;
  4547. }
  4548. for (const key in leftMap) {
  4549. if (leftMap.hasOwnProperty(key)) {
  4550. if (rightMap[key] === undefined ||
  4551. !valueEquals(leftMap[key], rightMap[key])) {
  4552. return false;
  4553. }
  4554. }
  4555. }
  4556. return true;
  4557. }
  4558. /** Returns true if the ArrayValue contains the specified element. */
  4559. function arrayValueContains(haystack, needle) {
  4560. return ((haystack.values || []).find(v => valueEquals(v, needle)) !== undefined);
  4561. }
  4562. function valueCompare(left, right) {
  4563. if (left === right) {
  4564. return 0;
  4565. }
  4566. const leftType = typeOrder(left);
  4567. const rightType = typeOrder(right);
  4568. if (leftType !== rightType) {
  4569. return primitiveComparator(leftType, rightType);
  4570. }
  4571. switch (leftType) {
  4572. case 0 /* TypeOrder.NullValue */:
  4573. case 9007199254740991 /* TypeOrder.MaxValue */:
  4574. return 0;
  4575. case 1 /* TypeOrder.BooleanValue */:
  4576. return primitiveComparator(left.booleanValue, right.booleanValue);
  4577. case 2 /* TypeOrder.NumberValue */:
  4578. return compareNumbers(left, right);
  4579. case 3 /* TypeOrder.TimestampValue */:
  4580. return compareTimestamps(left.timestampValue, right.timestampValue);
  4581. case 4 /* TypeOrder.ServerTimestampValue */:
  4582. return compareTimestamps(getLocalWriteTime(left), getLocalWriteTime(right));
  4583. case 5 /* TypeOrder.StringValue */:
  4584. return primitiveComparator(left.stringValue, right.stringValue);
  4585. case 6 /* TypeOrder.BlobValue */:
  4586. return compareBlobs(left.bytesValue, right.bytesValue);
  4587. case 7 /* TypeOrder.RefValue */:
  4588. return compareReferences(left.referenceValue, right.referenceValue);
  4589. case 8 /* TypeOrder.GeoPointValue */:
  4590. return compareGeoPoints(left.geoPointValue, right.geoPointValue);
  4591. case 9 /* TypeOrder.ArrayValue */:
  4592. return compareArrays(left.arrayValue, right.arrayValue);
  4593. case 10 /* TypeOrder.ObjectValue */:
  4594. return compareMaps(left.mapValue, right.mapValue);
  4595. default:
  4596. throw fail();
  4597. }
  4598. }
  4599. function compareNumbers(left, right) {
  4600. const leftNumber = normalizeNumber(left.integerValue || left.doubleValue);
  4601. const rightNumber = normalizeNumber(right.integerValue || right.doubleValue);
  4602. if (leftNumber < rightNumber) {
  4603. return -1;
  4604. }
  4605. else if (leftNumber > rightNumber) {
  4606. return 1;
  4607. }
  4608. else if (leftNumber === rightNumber) {
  4609. return 0;
  4610. }
  4611. else {
  4612. // one or both are NaN.
  4613. if (isNaN(leftNumber)) {
  4614. return isNaN(rightNumber) ? 0 : -1;
  4615. }
  4616. else {
  4617. return 1;
  4618. }
  4619. }
  4620. }
  4621. function compareTimestamps(left, right) {
  4622. if (typeof left === 'string' &&
  4623. typeof right === 'string' &&
  4624. left.length === right.length) {
  4625. return primitiveComparator(left, right);
  4626. }
  4627. const leftTimestamp = normalizeTimestamp(left);
  4628. const rightTimestamp = normalizeTimestamp(right);
  4629. const comparison = primitiveComparator(leftTimestamp.seconds, rightTimestamp.seconds);
  4630. if (comparison !== 0) {
  4631. return comparison;
  4632. }
  4633. return primitiveComparator(leftTimestamp.nanos, rightTimestamp.nanos);
  4634. }
  4635. function compareReferences(leftPath, rightPath) {
  4636. const leftSegments = leftPath.split('/');
  4637. const rightSegments = rightPath.split('/');
  4638. for (let i = 0; i < leftSegments.length && i < rightSegments.length; i++) {
  4639. const comparison = primitiveComparator(leftSegments[i], rightSegments[i]);
  4640. if (comparison !== 0) {
  4641. return comparison;
  4642. }
  4643. }
  4644. return primitiveComparator(leftSegments.length, rightSegments.length);
  4645. }
  4646. function compareGeoPoints(left, right) {
  4647. const comparison = primitiveComparator(normalizeNumber(left.latitude), normalizeNumber(right.latitude));
  4648. if (comparison !== 0) {
  4649. return comparison;
  4650. }
  4651. return primitiveComparator(normalizeNumber(left.longitude), normalizeNumber(right.longitude));
  4652. }
  4653. function compareBlobs(left, right) {
  4654. const leftBytes = normalizeByteString(left);
  4655. const rightBytes = normalizeByteString(right);
  4656. return leftBytes.compareTo(rightBytes);
  4657. }
  4658. function compareArrays(left, right) {
  4659. const leftArray = left.values || [];
  4660. const rightArray = right.values || [];
  4661. for (let i = 0; i < leftArray.length && i < rightArray.length; ++i) {
  4662. const compare = valueCompare(leftArray[i], rightArray[i]);
  4663. if (compare) {
  4664. return compare;
  4665. }
  4666. }
  4667. return primitiveComparator(leftArray.length, rightArray.length);
  4668. }
  4669. function compareMaps(left, right) {
  4670. if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) {
  4671. return 0;
  4672. }
  4673. else if (left === MAX_VALUE.mapValue) {
  4674. return 1;
  4675. }
  4676. else if (right === MAX_VALUE.mapValue) {
  4677. return -1;
  4678. }
  4679. const leftMap = left.fields || {};
  4680. const leftKeys = Object.keys(leftMap);
  4681. const rightMap = right.fields || {};
  4682. const rightKeys = Object.keys(rightMap);
  4683. // Even though MapValues are likely sorted correctly based on their insertion
  4684. // order (e.g. when received from the backend), local modifications can bring
  4685. // elements out of order. We need to re-sort the elements to ensure that
  4686. // canonical IDs are independent of insertion order.
  4687. leftKeys.sort();
  4688. rightKeys.sort();
  4689. for (let i = 0; i < leftKeys.length && i < rightKeys.length; ++i) {
  4690. const keyCompare = primitiveComparator(leftKeys[i], rightKeys[i]);
  4691. if (keyCompare !== 0) {
  4692. return keyCompare;
  4693. }
  4694. const compare = valueCompare(leftMap[leftKeys[i]], rightMap[rightKeys[i]]);
  4695. if (compare !== 0) {
  4696. return compare;
  4697. }
  4698. }
  4699. return primitiveComparator(leftKeys.length, rightKeys.length);
  4700. }
  4701. /**
  4702. * Generates the canonical ID for the provided field value (as used in Target
  4703. * serialization).
  4704. */
  4705. function canonicalId(value) {
  4706. return canonifyValue(value);
  4707. }
  4708. function canonifyValue(value) {
  4709. if ('nullValue' in value) {
  4710. return 'null';
  4711. }
  4712. else if ('booleanValue' in value) {
  4713. return '' + value.booleanValue;
  4714. }
  4715. else if ('integerValue' in value) {
  4716. return '' + value.integerValue;
  4717. }
  4718. else if ('doubleValue' in value) {
  4719. return '' + value.doubleValue;
  4720. }
  4721. else if ('timestampValue' in value) {
  4722. return canonifyTimestamp(value.timestampValue);
  4723. }
  4724. else if ('stringValue' in value) {
  4725. return value.stringValue;
  4726. }
  4727. else if ('bytesValue' in value) {
  4728. return canonifyByteString(value.bytesValue);
  4729. }
  4730. else if ('referenceValue' in value) {
  4731. return canonifyReference(value.referenceValue);
  4732. }
  4733. else if ('geoPointValue' in value) {
  4734. return canonifyGeoPoint(value.geoPointValue);
  4735. }
  4736. else if ('arrayValue' in value) {
  4737. return canonifyArray(value.arrayValue);
  4738. }
  4739. else if ('mapValue' in value) {
  4740. return canonifyMap(value.mapValue);
  4741. }
  4742. else {
  4743. return fail();
  4744. }
  4745. }
  4746. function canonifyByteString(byteString) {
  4747. return normalizeByteString(byteString).toBase64();
  4748. }
  4749. function canonifyTimestamp(timestamp) {
  4750. const normalizedTimestamp = normalizeTimestamp(timestamp);
  4751. return `time(${normalizedTimestamp.seconds},${normalizedTimestamp.nanos})`;
  4752. }
  4753. function canonifyGeoPoint(geoPoint) {
  4754. return `geo(${geoPoint.latitude},${geoPoint.longitude})`;
  4755. }
  4756. function canonifyReference(referenceValue) {
  4757. return DocumentKey.fromName(referenceValue).toString();
  4758. }
  4759. function canonifyMap(mapValue) {
  4760. // Iteration order in JavaScript is not guaranteed. To ensure that we generate
  4761. // matching canonical IDs for identical maps, we need to sort the keys.
  4762. const sortedKeys = Object.keys(mapValue.fields || {}).sort();
  4763. let result = '{';
  4764. let first = true;
  4765. for (const key of sortedKeys) {
  4766. if (!first) {
  4767. result += ',';
  4768. }
  4769. else {
  4770. first = false;
  4771. }
  4772. result += `${key}:${canonifyValue(mapValue.fields[key])}`;
  4773. }
  4774. return result + '}';
  4775. }
  4776. function canonifyArray(arrayValue) {
  4777. let result = '[';
  4778. let first = true;
  4779. for (const value of arrayValue.values || []) {
  4780. if (!first) {
  4781. result += ',';
  4782. }
  4783. else {
  4784. first = false;
  4785. }
  4786. result += canonifyValue(value);
  4787. }
  4788. return result + ']';
  4789. }
  4790. /**
  4791. * Returns an approximate (and wildly inaccurate) in-memory size for the field
  4792. * value.
  4793. *
  4794. * The memory size takes into account only the actual user data as it resides
  4795. * in memory and ignores object overhead.
  4796. */
  4797. function estimateByteSize(value) {
  4798. switch (typeOrder(value)) {
  4799. case 0 /* TypeOrder.NullValue */:
  4800. return 4;
  4801. case 1 /* TypeOrder.BooleanValue */:
  4802. return 4;
  4803. case 2 /* TypeOrder.NumberValue */:
  4804. return 8;
  4805. case 3 /* TypeOrder.TimestampValue */:
  4806. // Timestamps are made up of two distinct numbers (seconds + nanoseconds)
  4807. return 16;
  4808. case 4 /* TypeOrder.ServerTimestampValue */:
  4809. const previousValue = getPreviousValue(value);
  4810. return previousValue ? 16 + estimateByteSize(previousValue) : 16;
  4811. case 5 /* TypeOrder.StringValue */:
  4812. // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures:
  4813. // "JavaScript's String type is [...] a set of elements of 16-bit unsigned
  4814. // integer values"
  4815. return value.stringValue.length * 2;
  4816. case 6 /* TypeOrder.BlobValue */:
  4817. return normalizeByteString(value.bytesValue).approximateByteSize();
  4818. case 7 /* TypeOrder.RefValue */:
  4819. return value.referenceValue.length;
  4820. case 8 /* TypeOrder.GeoPointValue */:
  4821. // GeoPoints are made up of two distinct numbers (latitude + longitude)
  4822. return 16;
  4823. case 9 /* TypeOrder.ArrayValue */:
  4824. return estimateArrayByteSize(value.arrayValue);
  4825. case 10 /* TypeOrder.ObjectValue */:
  4826. return estimateMapByteSize(value.mapValue);
  4827. default:
  4828. throw fail();
  4829. }
  4830. }
  4831. function estimateMapByteSize(mapValue) {
  4832. let size = 0;
  4833. forEach(mapValue.fields, (key, val) => {
  4834. size += key.length + estimateByteSize(val);
  4835. });
  4836. return size;
  4837. }
  4838. function estimateArrayByteSize(arrayValue) {
  4839. return (arrayValue.values || []).reduce((previousSize, value) => previousSize + estimateByteSize(value), 0);
  4840. }
  4841. /** Returns a reference value for the provided database and key. */
  4842. function refValue(databaseId, key) {
  4843. return {
  4844. referenceValue: `projects/${databaseId.projectId}/databases/${databaseId.database}/documents/${key.path.canonicalString()}`
  4845. };
  4846. }
  4847. /** Returns true if `value` is an IntegerValue . */
  4848. function isInteger(value) {
  4849. return !!value && 'integerValue' in value;
  4850. }
  4851. /** Returns true if `value` is a DoubleValue. */
  4852. function isDouble(value) {
  4853. return !!value && 'doubleValue' in value;
  4854. }
  4855. /** Returns true if `value` is either an IntegerValue or a DoubleValue. */
  4856. function isNumber(value) {
  4857. return isInteger(value) || isDouble(value);
  4858. }
  4859. /** Returns true if `value` is an ArrayValue. */
  4860. function isArray(value) {
  4861. return !!value && 'arrayValue' in value;
  4862. }
  4863. /** Returns true if `value` is a NullValue. */
  4864. function isNullValue(value) {
  4865. return !!value && 'nullValue' in value;
  4866. }
  4867. /** Returns true if `value` is NaN. */
  4868. function isNanValue(value) {
  4869. return !!value && 'doubleValue' in value && isNaN(Number(value.doubleValue));
  4870. }
  4871. /** Returns true if `value` is a MapValue. */
  4872. function isMapValue(value) {
  4873. return !!value && 'mapValue' in value;
  4874. }
  4875. /** Creates a deep copy of `source`. */
  4876. function deepClone(source) {
  4877. if (source.geoPointValue) {
  4878. return { geoPointValue: Object.assign({}, source.geoPointValue) };
  4879. }
  4880. else if (source.timestampValue &&
  4881. typeof source.timestampValue === 'object') {
  4882. return { timestampValue: Object.assign({}, source.timestampValue) };
  4883. }
  4884. else if (source.mapValue) {
  4885. const target = { mapValue: { fields: {} } };
  4886. forEach(source.mapValue.fields, (key, val) => (target.mapValue.fields[key] = deepClone(val)));
  4887. return target;
  4888. }
  4889. else if (source.arrayValue) {
  4890. const target = { arrayValue: { values: [] } };
  4891. for (let i = 0; i < (source.arrayValue.values || []).length; ++i) {
  4892. target.arrayValue.values[i] = deepClone(source.arrayValue.values[i]);
  4893. }
  4894. return target;
  4895. }
  4896. else {
  4897. return Object.assign({}, source);
  4898. }
  4899. }
  4900. /** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
  4901. function isMaxValue(value) {
  4902. return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
  4903. MAX_VALUE_TYPE);
  4904. }
  4905. /** Returns the lowest value for the given value type (inclusive). */
  4906. function valuesGetLowerBound(value) {
  4907. if ('nullValue' in value) {
  4908. return MIN_VALUE;
  4909. }
  4910. else if ('booleanValue' in value) {
  4911. return { booleanValue: false };
  4912. }
  4913. else if ('integerValue' in value || 'doubleValue' in value) {
  4914. return { doubleValue: NaN };
  4915. }
  4916. else if ('timestampValue' in value) {
  4917. return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
  4918. }
  4919. else if ('stringValue' in value) {
  4920. return { stringValue: '' };
  4921. }
  4922. else if ('bytesValue' in value) {
  4923. return { bytesValue: '' };
  4924. }
  4925. else if ('referenceValue' in value) {
  4926. return refValue(DatabaseId.empty(), DocumentKey.empty());
  4927. }
  4928. else if ('geoPointValue' in value) {
  4929. return { geoPointValue: { latitude: -90, longitude: -180 } };
  4930. }
  4931. else if ('arrayValue' in value) {
  4932. return { arrayValue: {} };
  4933. }
  4934. else if ('mapValue' in value) {
  4935. return { mapValue: {} };
  4936. }
  4937. else {
  4938. return fail();
  4939. }
  4940. }
  4941. /** Returns the largest value for the given value type (exclusive). */
  4942. function valuesGetUpperBound(value) {
  4943. if ('nullValue' in value) {
  4944. return { booleanValue: false };
  4945. }
  4946. else if ('booleanValue' in value) {
  4947. return { doubleValue: NaN };
  4948. }
  4949. else if ('integerValue' in value || 'doubleValue' in value) {
  4950. return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
  4951. }
  4952. else if ('timestampValue' in value) {
  4953. return { stringValue: '' };
  4954. }
  4955. else if ('stringValue' in value) {
  4956. return { bytesValue: '' };
  4957. }
  4958. else if ('bytesValue' in value) {
  4959. return refValue(DatabaseId.empty(), DocumentKey.empty());
  4960. }
  4961. else if ('referenceValue' in value) {
  4962. return { geoPointValue: { latitude: -90, longitude: -180 } };
  4963. }
  4964. else if ('geoPointValue' in value) {
  4965. return { arrayValue: {} };
  4966. }
  4967. else if ('arrayValue' in value) {
  4968. return { mapValue: {} };
  4969. }
  4970. else if ('mapValue' in value) {
  4971. return MAX_VALUE;
  4972. }
  4973. else {
  4974. return fail();
  4975. }
  4976. }
  4977. function lowerBoundCompare(left, right) {
  4978. const cmp = valueCompare(left.value, right.value);
  4979. if (cmp !== 0) {
  4980. return cmp;
  4981. }
  4982. if (left.inclusive && !right.inclusive) {
  4983. return -1;
  4984. }
  4985. else if (!left.inclusive && right.inclusive) {
  4986. return 1;
  4987. }
  4988. return 0;
  4989. }
  4990. function upperBoundCompare(left, right) {
  4991. const cmp = valueCompare(left.value, right.value);
  4992. if (cmp !== 0) {
  4993. return cmp;
  4994. }
  4995. if (left.inclusive && !right.inclusive) {
  4996. return 1;
  4997. }
  4998. else if (!left.inclusive && right.inclusive) {
  4999. return -1;
  5000. }
  5001. return 0;
  5002. }
  5003. /**
  5004. * @license
  5005. * Copyright 2017 Google LLC
  5006. *
  5007. * Licensed under the Apache License, Version 2.0 (the "License");
  5008. * you may not use this file except in compliance with the License.
  5009. * You may obtain a copy of the License at
  5010. *
  5011. * http://www.apache.org/licenses/LICENSE-2.0
  5012. *
  5013. * Unless required by applicable law or agreed to in writing, software
  5014. * distributed under the License is distributed on an "AS IS" BASIS,
  5015. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5016. * See the License for the specific language governing permissions and
  5017. * limitations under the License.
  5018. */
  5019. /**
  5020. * An ObjectValue represents a MapValue in the Firestore Proto and offers the
  5021. * ability to add and remove fields (via the ObjectValueBuilder).
  5022. */
  5023. class ObjectValue {
  5024. constructor(value) {
  5025. this.value = value;
  5026. }
  5027. static empty() {
  5028. return new ObjectValue({ mapValue: {} });
  5029. }
  5030. /**
  5031. * Returns the value at the given path or null.
  5032. *
  5033. * @param path - the path to search
  5034. * @returns The value at the path or null if the path is not set.
  5035. */
  5036. field(path) {
  5037. if (path.isEmpty()) {
  5038. return this.value;
  5039. }
  5040. else {
  5041. let currentLevel = this.value;
  5042. for (let i = 0; i < path.length - 1; ++i) {
  5043. currentLevel = (currentLevel.mapValue.fields || {})[path.get(i)];
  5044. if (!isMapValue(currentLevel)) {
  5045. return null;
  5046. }
  5047. }
  5048. currentLevel = (currentLevel.mapValue.fields || {})[path.lastSegment()];
  5049. return currentLevel || null;
  5050. }
  5051. }
  5052. /**
  5053. * Sets the field to the provided value.
  5054. *
  5055. * @param path - The field path to set.
  5056. * @param value - The value to set.
  5057. */
  5058. set(path, value) {
  5059. const fieldsMap = this.getFieldsMap(path.popLast());
  5060. fieldsMap[path.lastSegment()] = deepClone(value);
  5061. }
  5062. /**
  5063. * Sets the provided fields to the provided values.
  5064. *
  5065. * @param data - A map of fields to values (or null for deletes).
  5066. */
  5067. setAll(data) {
  5068. let parent = FieldPath$1.emptyPath();
  5069. let upserts = {};
  5070. let deletes = [];
  5071. data.forEach((value, path) => {
  5072. if (!parent.isImmediateParentOf(path)) {
  5073. // Insert the accumulated changes at this parent location
  5074. const fieldsMap = this.getFieldsMap(parent);
  5075. this.applyChanges(fieldsMap, upserts, deletes);
  5076. upserts = {};
  5077. deletes = [];
  5078. parent = path.popLast();
  5079. }
  5080. if (value) {
  5081. upserts[path.lastSegment()] = deepClone(value);
  5082. }
  5083. else {
  5084. deletes.push(path.lastSegment());
  5085. }
  5086. });
  5087. const fieldsMap = this.getFieldsMap(parent);
  5088. this.applyChanges(fieldsMap, upserts, deletes);
  5089. }
  5090. /**
  5091. * Removes the field at the specified path. If there is no field at the
  5092. * specified path, nothing is changed.
  5093. *
  5094. * @param path - The field path to remove.
  5095. */
  5096. delete(path) {
  5097. const nestedValue = this.field(path.popLast());
  5098. if (isMapValue(nestedValue) && nestedValue.mapValue.fields) {
  5099. delete nestedValue.mapValue.fields[path.lastSegment()];
  5100. }
  5101. }
  5102. isEqual(other) {
  5103. return valueEquals(this.value, other.value);
  5104. }
  5105. /**
  5106. * Returns the map that contains the leaf element of `path`. If the parent
  5107. * entry does not yet exist, or if it is not a map, a new map will be created.
  5108. */
  5109. getFieldsMap(path) {
  5110. let current = this.value;
  5111. if (!current.mapValue.fields) {
  5112. current.mapValue = { fields: {} };
  5113. }
  5114. for (let i = 0; i < path.length; ++i) {
  5115. let next = current.mapValue.fields[path.get(i)];
  5116. if (!isMapValue(next) || !next.mapValue.fields) {
  5117. next = { mapValue: { fields: {} } };
  5118. current.mapValue.fields[path.get(i)] = next;
  5119. }
  5120. current = next;
  5121. }
  5122. return current.mapValue.fields;
  5123. }
  5124. /**
  5125. * Modifies `fieldsMap` by adding, replacing or deleting the specified
  5126. * entries.
  5127. */
  5128. applyChanges(fieldsMap, inserts, deletes) {
  5129. forEach(inserts, (key, val) => (fieldsMap[key] = val));
  5130. for (const field of deletes) {
  5131. delete fieldsMap[field];
  5132. }
  5133. }
  5134. clone() {
  5135. return new ObjectValue(deepClone(this.value));
  5136. }
  5137. }
  5138. /**
  5139. * Returns a FieldMask built from all fields in a MapValue.
  5140. */
  5141. function extractFieldMask(value) {
  5142. const fields = [];
  5143. forEach(value.fields, (key, value) => {
  5144. const currentPath = new FieldPath$1([key]);
  5145. if (isMapValue(value)) {
  5146. const nestedMask = extractFieldMask(value.mapValue);
  5147. const nestedFields = nestedMask.fields;
  5148. if (nestedFields.length === 0) {
  5149. // Preserve the empty map by adding it to the FieldMask.
  5150. fields.push(currentPath);
  5151. }
  5152. else {
  5153. // For nested and non-empty ObjectValues, add the FieldPath of the
  5154. // leaf nodes.
  5155. for (const nestedPath of nestedFields) {
  5156. fields.push(currentPath.child(nestedPath));
  5157. }
  5158. }
  5159. }
  5160. else {
  5161. // For nested and non-empty ObjectValues, add the FieldPath of the leaf
  5162. // nodes.
  5163. fields.push(currentPath);
  5164. }
  5165. });
  5166. return new FieldMask(fields);
  5167. }
  5168. /**
  5169. * @license
  5170. * Copyright 2017 Google LLC
  5171. *
  5172. * Licensed under the Apache License, Version 2.0 (the "License");
  5173. * you may not use this file except in compliance with the License.
  5174. * You may obtain a copy of the License at
  5175. *
  5176. * http://www.apache.org/licenses/LICENSE-2.0
  5177. *
  5178. * Unless required by applicable law or agreed to in writing, software
  5179. * distributed under the License is distributed on an "AS IS" BASIS,
  5180. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5181. * See the License for the specific language governing permissions and
  5182. * limitations under the License.
  5183. */
  5184. /**
  5185. * Represents a document in Firestore with a key, version, data and whether it
  5186. * has local mutations applied to it.
  5187. *
  5188. * Documents can transition between states via `convertToFoundDocument()`,
  5189. * `convertToNoDocument()` and `convertToUnknownDocument()`. If a document does
  5190. * not transition to one of these states even after all mutations have been
  5191. * applied, `isValidDocument()` returns false and the document should be removed
  5192. * from all views.
  5193. */
  5194. class MutableDocument {
  5195. constructor(key, documentType, version, readTime, createTime, data, documentState) {
  5196. this.key = key;
  5197. this.documentType = documentType;
  5198. this.version = version;
  5199. this.readTime = readTime;
  5200. this.createTime = createTime;
  5201. this.data = data;
  5202. this.documentState = documentState;
  5203. }
  5204. /**
  5205. * Creates a document with no known version or data, but which can serve as
  5206. * base document for mutations.
  5207. */
  5208. static newInvalidDocument(documentKey) {
  5209. return new MutableDocument(documentKey, 0 /* DocumentType.INVALID */,
  5210. /* version */ SnapshotVersion.min(),
  5211. /* readTime */ SnapshotVersion.min(),
  5212. /* createTime */ SnapshotVersion.min(), ObjectValue.empty(), 0 /* DocumentState.SYNCED */);
  5213. }
  5214. /**
  5215. * Creates a new document that is known to exist with the given data at the
  5216. * given version.
  5217. */
  5218. static newFoundDocument(documentKey, version, createTime, value) {
  5219. return new MutableDocument(documentKey, 1 /* DocumentType.FOUND_DOCUMENT */,
  5220. /* version */ version,
  5221. /* readTime */ SnapshotVersion.min(),
  5222. /* createTime */ createTime, value, 0 /* DocumentState.SYNCED */);
  5223. }
  5224. /** Creates a new document that is known to not exist at the given version. */
  5225. static newNoDocument(documentKey, version) {
  5226. return new MutableDocument(documentKey, 2 /* DocumentType.NO_DOCUMENT */,
  5227. /* version */ version,
  5228. /* readTime */ SnapshotVersion.min(),
  5229. /* createTime */ SnapshotVersion.min(), ObjectValue.empty(), 0 /* DocumentState.SYNCED */);
  5230. }
  5231. /**
  5232. * Creates a new document that is known to exist at the given version but
  5233. * whose data is not known (e.g. a document that was updated without a known
  5234. * base document).
  5235. */
  5236. static newUnknownDocument(documentKey, version) {
  5237. return new MutableDocument(documentKey, 3 /* DocumentType.UNKNOWN_DOCUMENT */,
  5238. /* version */ version,
  5239. /* readTime */ SnapshotVersion.min(),
  5240. /* createTime */ SnapshotVersion.min(), ObjectValue.empty(), 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */);
  5241. }
  5242. /**
  5243. * Changes the document type to indicate that it exists and that its version
  5244. * and data are known.
  5245. */
  5246. convertToFoundDocument(version, value) {
  5247. // If a document is switching state from being an invalid or deleted
  5248. // document to a valid (FOUND_DOCUMENT) document, either due to receiving an
  5249. // update from Watch or due to applying a local set mutation on top
  5250. // of a deleted document, our best guess about its createTime would be the
  5251. // version at which the document transitioned to a FOUND_DOCUMENT.
  5252. if (this.createTime.isEqual(SnapshotVersion.min()) &&
  5253. (this.documentType === 2 /* DocumentType.NO_DOCUMENT */ ||
  5254. this.documentType === 0 /* DocumentType.INVALID */)) {
  5255. this.createTime = version;
  5256. }
  5257. this.version = version;
  5258. this.documentType = 1 /* DocumentType.FOUND_DOCUMENT */;
  5259. this.data = value;
  5260. this.documentState = 0 /* DocumentState.SYNCED */;
  5261. return this;
  5262. }
  5263. /**
  5264. * Changes the document type to indicate that it doesn't exist at the given
  5265. * version.
  5266. */
  5267. convertToNoDocument(version) {
  5268. this.version = version;
  5269. this.documentType = 2 /* DocumentType.NO_DOCUMENT */;
  5270. this.data = ObjectValue.empty();
  5271. this.documentState = 0 /* DocumentState.SYNCED */;
  5272. return this;
  5273. }
  5274. /**
  5275. * Changes the document type to indicate that it exists at a given version but
  5276. * that its data is not known (e.g. a document that was updated without a known
  5277. * base document).
  5278. */
  5279. convertToUnknownDocument(version) {
  5280. this.version = version;
  5281. this.documentType = 3 /* DocumentType.UNKNOWN_DOCUMENT */;
  5282. this.data = ObjectValue.empty();
  5283. this.documentState = 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */;
  5284. return this;
  5285. }
  5286. setHasCommittedMutations() {
  5287. this.documentState = 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */;
  5288. return this;
  5289. }
  5290. setHasLocalMutations() {
  5291. this.documentState = 1 /* DocumentState.HAS_LOCAL_MUTATIONS */;
  5292. this.version = SnapshotVersion.min();
  5293. return this;
  5294. }
  5295. setReadTime(readTime) {
  5296. this.readTime = readTime;
  5297. return this;
  5298. }
  5299. get hasLocalMutations() {
  5300. return this.documentState === 1 /* DocumentState.HAS_LOCAL_MUTATIONS */;
  5301. }
  5302. get hasCommittedMutations() {
  5303. return this.documentState === 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */;
  5304. }
  5305. get hasPendingWrites() {
  5306. return this.hasLocalMutations || this.hasCommittedMutations;
  5307. }
  5308. isValidDocument() {
  5309. return this.documentType !== 0 /* DocumentType.INVALID */;
  5310. }
  5311. isFoundDocument() {
  5312. return this.documentType === 1 /* DocumentType.FOUND_DOCUMENT */;
  5313. }
  5314. isNoDocument() {
  5315. return this.documentType === 2 /* DocumentType.NO_DOCUMENT */;
  5316. }
  5317. isUnknownDocument() {
  5318. return this.documentType === 3 /* DocumentType.UNKNOWN_DOCUMENT */;
  5319. }
  5320. isEqual(other) {
  5321. return (other instanceof MutableDocument &&
  5322. this.key.isEqual(other.key) &&
  5323. this.version.isEqual(other.version) &&
  5324. this.documentType === other.documentType &&
  5325. this.documentState === other.documentState &&
  5326. this.data.isEqual(other.data));
  5327. }
  5328. mutableCopy() {
  5329. return new MutableDocument(this.key, this.documentType, this.version, this.readTime, this.createTime, this.data.clone(), this.documentState);
  5330. }
  5331. toString() {
  5332. return (`Document(${this.key}, ${this.version}, ${JSON.stringify(this.data.value)}, ` +
  5333. `{createTime: ${this.createTime}}), ` +
  5334. `{documentType: ${this.documentType}}), ` +
  5335. `{documentState: ${this.documentState}})`);
  5336. }
  5337. }
  5338. /**
  5339. * Compares the value for field `field` in the provided documents. Throws if
  5340. * the field does not exist in both documents.
  5341. */
  5342. function compareDocumentsByField(field, d1, d2) {
  5343. const v1 = d1.data.field(field);
  5344. const v2 = d2.data.field(field);
  5345. if (v1 !== null && v2 !== null) {
  5346. return valueCompare(v1, v2);
  5347. }
  5348. else {
  5349. return fail();
  5350. }
  5351. }
  5352. /**
  5353. * @license
  5354. * Copyright 2022 Google LLC
  5355. *
  5356. * Licensed under the Apache License, Version 2.0 (the "License");
  5357. * you may not use this file except in compliance with the License.
  5358. * You may obtain a copy of the License at
  5359. *
  5360. * http://www.apache.org/licenses/LICENSE-2.0
  5361. *
  5362. * Unless required by applicable law or agreed to in writing, software
  5363. * distributed under the License is distributed on an "AS IS" BASIS,
  5364. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5365. * See the License for the specific language governing permissions and
  5366. * limitations under the License.
  5367. */
  5368. /**
  5369. * Represents a bound of a query.
  5370. *
  5371. * The bound is specified with the given components representing a position and
  5372. * whether it's just before or just after the position (relative to whatever the
  5373. * query order is).
  5374. *
  5375. * The position represents a logical index position for a query. It's a prefix
  5376. * of values for the (potentially implicit) order by clauses of a query.
  5377. *
  5378. * Bound provides a function to determine whether a document comes before or
  5379. * after a bound. This is influenced by whether the position is just before or
  5380. * just after the provided values.
  5381. */
  5382. class Bound {
  5383. constructor(position, inclusive) {
  5384. this.position = position;
  5385. this.inclusive = inclusive;
  5386. }
  5387. }
  5388. function boundCompareToDocument(bound, orderBy, doc) {
  5389. let comparison = 0;
  5390. for (let i = 0; i < bound.position.length; i++) {
  5391. const orderByComponent = orderBy[i];
  5392. const component = bound.position[i];
  5393. if (orderByComponent.field.isKeyField()) {
  5394. comparison = DocumentKey.comparator(DocumentKey.fromName(component.referenceValue), doc.key);
  5395. }
  5396. else {
  5397. const docValue = doc.data.field(orderByComponent.field);
  5398. comparison = valueCompare(component, docValue);
  5399. }
  5400. if (orderByComponent.dir === "desc" /* Direction.DESCENDING */) {
  5401. comparison = comparison * -1;
  5402. }
  5403. if (comparison !== 0) {
  5404. break;
  5405. }
  5406. }
  5407. return comparison;
  5408. }
  5409. /**
  5410. * Returns true if a document sorts after a bound using the provided sort
  5411. * order.
  5412. */
  5413. function boundSortsAfterDocument(bound, orderBy, doc) {
  5414. const comparison = boundCompareToDocument(bound, orderBy, doc);
  5415. return bound.inclusive ? comparison >= 0 : comparison > 0;
  5416. }
  5417. /**
  5418. * Returns true if a document sorts before a bound using the provided sort
  5419. * order.
  5420. */
  5421. function boundSortsBeforeDocument(bound, orderBy, doc) {
  5422. const comparison = boundCompareToDocument(bound, orderBy, doc);
  5423. return bound.inclusive ? comparison <= 0 : comparison < 0;
  5424. }
  5425. function boundEquals(left, right) {
  5426. if (left === null) {
  5427. return right === null;
  5428. }
  5429. else if (right === null) {
  5430. return false;
  5431. }
  5432. if (left.inclusive !== right.inclusive ||
  5433. left.position.length !== right.position.length) {
  5434. return false;
  5435. }
  5436. for (let i = 0; i < left.position.length; i++) {
  5437. const leftPosition = left.position[i];
  5438. const rightPosition = right.position[i];
  5439. if (!valueEquals(leftPosition, rightPosition)) {
  5440. return false;
  5441. }
  5442. }
  5443. return true;
  5444. }
  5445. /**
  5446. * @license
  5447. * Copyright 2022 Google LLC
  5448. *
  5449. * Licensed under the Apache License, Version 2.0 (the "License");
  5450. * you may not use this file except in compliance with the License.
  5451. * You may obtain a copy of the License at
  5452. *
  5453. * http://www.apache.org/licenses/LICENSE-2.0
  5454. *
  5455. * Unless required by applicable law or agreed to in writing, software
  5456. * distributed under the License is distributed on an "AS IS" BASIS,
  5457. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5458. * See the License for the specific language governing permissions and
  5459. * limitations under the License.
  5460. */
  5461. /**
  5462. * An ordering on a field, in some Direction. Direction defaults to ASCENDING.
  5463. */
  5464. class OrderBy {
  5465. constructor(field, dir = "asc" /* Direction.ASCENDING */) {
  5466. this.field = field;
  5467. this.dir = dir;
  5468. }
  5469. }
  5470. function canonifyOrderBy(orderBy) {
  5471. // TODO(b/29183165): Make this collision robust.
  5472. return orderBy.field.canonicalString() + orderBy.dir;
  5473. }
  5474. function stringifyOrderBy(orderBy) {
  5475. return `${orderBy.field.canonicalString()} (${orderBy.dir})`;
  5476. }
  5477. function orderByEquals(left, right) {
  5478. return left.dir === right.dir && left.field.isEqual(right.field);
  5479. }
  5480. /**
  5481. * @license
  5482. * Copyright 2022 Google LLC
  5483. *
  5484. * Licensed under the Apache License, Version 2.0 (the "License");
  5485. * you may not use this file except in compliance with the License.
  5486. * You may obtain a copy of the License at
  5487. *
  5488. * http://www.apache.org/licenses/LICENSE-2.0
  5489. *
  5490. * Unless required by applicable law or agreed to in writing, software
  5491. * distributed under the License is distributed on an "AS IS" BASIS,
  5492. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5493. * See the License for the specific language governing permissions and
  5494. * limitations under the License.
  5495. */
  5496. class Filter {
  5497. }
  5498. class FieldFilter extends Filter {
  5499. constructor(field, op, value) {
  5500. super();
  5501. this.field = field;
  5502. this.op = op;
  5503. this.value = value;
  5504. }
  5505. /**
  5506. * Creates a filter based on the provided arguments.
  5507. */
  5508. static create(field, op, value) {
  5509. if (field.isKeyField()) {
  5510. if (op === "in" /* Operator.IN */ || op === "not-in" /* Operator.NOT_IN */) {
  5511. return this.createKeyFieldInFilter(field, op, value);
  5512. }
  5513. else {
  5514. return new KeyFieldFilter(field, op, value);
  5515. }
  5516. }
  5517. else if (op === "array-contains" /* Operator.ARRAY_CONTAINS */) {
  5518. return new ArrayContainsFilter(field, value);
  5519. }
  5520. else if (op === "in" /* Operator.IN */) {
  5521. return new InFilter(field, value);
  5522. }
  5523. else if (op === "not-in" /* Operator.NOT_IN */) {
  5524. return new NotInFilter(field, value);
  5525. }
  5526. else if (op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  5527. return new ArrayContainsAnyFilter(field, value);
  5528. }
  5529. else {
  5530. return new FieldFilter(field, op, value);
  5531. }
  5532. }
  5533. static createKeyFieldInFilter(field, op, value) {
  5534. return op === "in" /* Operator.IN */
  5535. ? new KeyFieldInFilter(field, value)
  5536. : new KeyFieldNotInFilter(field, value);
  5537. }
  5538. matches(doc) {
  5539. const other = doc.data.field(this.field);
  5540. // Types do not have to match in NOT_EQUAL filters.
  5541. if (this.op === "!=" /* Operator.NOT_EQUAL */) {
  5542. return (other !== null &&
  5543. this.matchesComparison(valueCompare(other, this.value)));
  5544. }
  5545. // Only compare types with matching backend order (such as double and int).
  5546. return (other !== null &&
  5547. typeOrder(this.value) === typeOrder(other) &&
  5548. this.matchesComparison(valueCompare(other, this.value)));
  5549. }
  5550. matchesComparison(comparison) {
  5551. switch (this.op) {
  5552. case "<" /* Operator.LESS_THAN */:
  5553. return comparison < 0;
  5554. case "<=" /* Operator.LESS_THAN_OR_EQUAL */:
  5555. return comparison <= 0;
  5556. case "==" /* Operator.EQUAL */:
  5557. return comparison === 0;
  5558. case "!=" /* Operator.NOT_EQUAL */:
  5559. return comparison !== 0;
  5560. case ">" /* Operator.GREATER_THAN */:
  5561. return comparison > 0;
  5562. case ">=" /* Operator.GREATER_THAN_OR_EQUAL */:
  5563. return comparison >= 0;
  5564. default:
  5565. return fail();
  5566. }
  5567. }
  5568. isInequality() {
  5569. return ([
  5570. "<" /* Operator.LESS_THAN */,
  5571. "<=" /* Operator.LESS_THAN_OR_EQUAL */,
  5572. ">" /* Operator.GREATER_THAN */,
  5573. ">=" /* Operator.GREATER_THAN_OR_EQUAL */,
  5574. "!=" /* Operator.NOT_EQUAL */,
  5575. "not-in" /* Operator.NOT_IN */
  5576. ].indexOf(this.op) >= 0);
  5577. }
  5578. getFlattenedFilters() {
  5579. return [this];
  5580. }
  5581. getFilters() {
  5582. return [this];
  5583. }
  5584. getFirstInequalityField() {
  5585. if (this.isInequality()) {
  5586. return this.field;
  5587. }
  5588. return null;
  5589. }
  5590. }
  5591. class CompositeFilter extends Filter {
  5592. constructor(filters, op) {
  5593. super();
  5594. this.filters = filters;
  5595. this.op = op;
  5596. this.memoizedFlattenedFilters = null;
  5597. }
  5598. /**
  5599. * Creates a filter based on the provided arguments.
  5600. */
  5601. static create(filters, op) {
  5602. return new CompositeFilter(filters, op);
  5603. }
  5604. matches(doc) {
  5605. if (compositeFilterIsConjunction(this)) {
  5606. // For conjunctions, all filters must match, so return false if any filter doesn't match.
  5607. return this.filters.find(filter => !filter.matches(doc)) === undefined;
  5608. }
  5609. else {
  5610. // For disjunctions, at least one filter should match.
  5611. return this.filters.find(filter => filter.matches(doc)) !== undefined;
  5612. }
  5613. }
  5614. getFlattenedFilters() {
  5615. if (this.memoizedFlattenedFilters !== null) {
  5616. return this.memoizedFlattenedFilters;
  5617. }
  5618. this.memoizedFlattenedFilters = this.filters.reduce((result, subfilter) => {
  5619. return result.concat(subfilter.getFlattenedFilters());
  5620. }, []);
  5621. return this.memoizedFlattenedFilters;
  5622. }
  5623. // Returns a mutable copy of `this.filters`
  5624. getFilters() {
  5625. return Object.assign([], this.filters);
  5626. }
  5627. getFirstInequalityField() {
  5628. const found = this.findFirstMatchingFilter(filter => filter.isInequality());
  5629. if (found !== null) {
  5630. return found.field;
  5631. }
  5632. return null;
  5633. }
  5634. // Performs a depth-first search to find and return the first FieldFilter in the composite filter
  5635. // that satisfies the predicate. Returns `null` if none of the FieldFilters satisfy the
  5636. // predicate.
  5637. findFirstMatchingFilter(predicate) {
  5638. for (const fieldFilter of this.getFlattenedFilters()) {
  5639. if (predicate(fieldFilter)) {
  5640. return fieldFilter;
  5641. }
  5642. }
  5643. return null;
  5644. }
  5645. }
  5646. function compositeFilterIsConjunction(compositeFilter) {
  5647. return compositeFilter.op === "and" /* CompositeOperator.AND */;
  5648. }
  5649. function compositeFilterIsDisjunction(compositeFilter) {
  5650. return compositeFilter.op === "or" /* CompositeOperator.OR */;
  5651. }
  5652. /**
  5653. * Returns true if this filter is a conjunction of field filters only. Returns false otherwise.
  5654. */
  5655. function compositeFilterIsFlatConjunction(compositeFilter) {
  5656. return (compositeFilterIsFlat(compositeFilter) &&
  5657. compositeFilterIsConjunction(compositeFilter));
  5658. }
  5659. /**
  5660. * Returns true if this filter does not contain any composite filters. Returns false otherwise.
  5661. */
  5662. function compositeFilterIsFlat(compositeFilter) {
  5663. for (const filter of compositeFilter.filters) {
  5664. if (filter instanceof CompositeFilter) {
  5665. return false;
  5666. }
  5667. }
  5668. return true;
  5669. }
  5670. function canonifyFilter(filter) {
  5671. if (filter instanceof FieldFilter) {
  5672. // TODO(b/29183165): Technically, this won't be unique if two values have
  5673. // the same description, such as the int 3 and the string "3". So we should
  5674. // add the types in here somehow, too.
  5675. return (filter.field.canonicalString() +
  5676. filter.op.toString() +
  5677. canonicalId(filter.value));
  5678. }
  5679. else if (compositeFilterIsFlatConjunction(filter)) {
  5680. // Older SDK versions use an implicit AND operation between their filters.
  5681. // In the new SDK versions, the developer may use an explicit AND filter.
  5682. // To stay consistent with the old usages, we add a special case to ensure
  5683. // the canonical ID for these two are the same. For example:
  5684. // `col.whereEquals("a", 1).whereEquals("b", 2)` should have the same
  5685. // canonical ID as `col.where(and(equals("a",1), equals("b",2)))`.
  5686. return filter.filters.map(filter => canonifyFilter(filter)).join(',');
  5687. }
  5688. else {
  5689. // filter instanceof CompositeFilter
  5690. const canonicalIdsString = filter.filters
  5691. .map(filter => canonifyFilter(filter))
  5692. .join(',');
  5693. return `${filter.op}(${canonicalIdsString})`;
  5694. }
  5695. }
  5696. function filterEquals(f1, f2) {
  5697. if (f1 instanceof FieldFilter) {
  5698. return fieldFilterEquals(f1, f2);
  5699. }
  5700. else if (f1 instanceof CompositeFilter) {
  5701. return compositeFilterEquals(f1, f2);
  5702. }
  5703. else {
  5704. fail();
  5705. }
  5706. }
  5707. function fieldFilterEquals(f1, f2) {
  5708. return (f2 instanceof FieldFilter &&
  5709. f1.op === f2.op &&
  5710. f1.field.isEqual(f2.field) &&
  5711. valueEquals(f1.value, f2.value));
  5712. }
  5713. function compositeFilterEquals(f1, f2) {
  5714. if (f2 instanceof CompositeFilter &&
  5715. f1.op === f2.op &&
  5716. f1.filters.length === f2.filters.length) {
  5717. const subFiltersMatch = f1.filters.reduce((result, f1Filter, index) => result && filterEquals(f1Filter, f2.filters[index]), true);
  5718. return subFiltersMatch;
  5719. }
  5720. return false;
  5721. }
  5722. /**
  5723. * Returns a new composite filter that contains all filter from
  5724. * `compositeFilter` plus all the given filters in `otherFilters`.
  5725. */
  5726. function compositeFilterWithAddedFilters(compositeFilter, otherFilters) {
  5727. const mergedFilters = compositeFilter.filters.concat(otherFilters);
  5728. return CompositeFilter.create(mergedFilters, compositeFilter.op);
  5729. }
  5730. /** Returns a debug description for `filter`. */
  5731. function stringifyFilter(filter) {
  5732. if (filter instanceof FieldFilter) {
  5733. return stringifyFieldFilter(filter);
  5734. }
  5735. else if (filter instanceof CompositeFilter) {
  5736. return stringifyCompositeFilter(filter);
  5737. }
  5738. else {
  5739. return 'Filter';
  5740. }
  5741. }
  5742. function stringifyCompositeFilter(filter) {
  5743. return (filter.op.toString() +
  5744. ` {` +
  5745. filter.getFilters().map(stringifyFilter).join(' ,') +
  5746. '}');
  5747. }
  5748. function stringifyFieldFilter(filter) {
  5749. return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(filter.value)}`;
  5750. }
  5751. /** Filter that matches on key fields (i.e. '__name__'). */
  5752. class KeyFieldFilter extends FieldFilter {
  5753. constructor(field, op, value) {
  5754. super(field, op, value);
  5755. this.key = DocumentKey.fromName(value.referenceValue);
  5756. }
  5757. matches(doc) {
  5758. const comparison = DocumentKey.comparator(doc.key, this.key);
  5759. return this.matchesComparison(comparison);
  5760. }
  5761. }
  5762. /** Filter that matches on key fields within an array. */
  5763. class KeyFieldInFilter extends FieldFilter {
  5764. constructor(field, value) {
  5765. super(field, "in" /* Operator.IN */, value);
  5766. this.keys = extractDocumentKeysFromArrayValue("in" /* Operator.IN */, value);
  5767. }
  5768. matches(doc) {
  5769. return this.keys.some(key => key.isEqual(doc.key));
  5770. }
  5771. }
  5772. /** Filter that matches on key fields not present within an array. */
  5773. class KeyFieldNotInFilter extends FieldFilter {
  5774. constructor(field, value) {
  5775. super(field, "not-in" /* Operator.NOT_IN */, value);
  5776. this.keys = extractDocumentKeysFromArrayValue("not-in" /* Operator.NOT_IN */, value);
  5777. }
  5778. matches(doc) {
  5779. return !this.keys.some(key => key.isEqual(doc.key));
  5780. }
  5781. }
  5782. function extractDocumentKeysFromArrayValue(op, value) {
  5783. var _a;
  5784. return (((_a = value.arrayValue) === null || _a === void 0 ? void 0 : _a.values) || []).map(v => {
  5785. return DocumentKey.fromName(v.referenceValue);
  5786. });
  5787. }
  5788. /** A Filter that implements the array-contains operator. */
  5789. class ArrayContainsFilter extends FieldFilter {
  5790. constructor(field, value) {
  5791. super(field, "array-contains" /* Operator.ARRAY_CONTAINS */, value);
  5792. }
  5793. matches(doc) {
  5794. const other = doc.data.field(this.field);
  5795. return isArray(other) && arrayValueContains(other.arrayValue, this.value);
  5796. }
  5797. }
  5798. /** A Filter that implements the IN operator. */
  5799. class InFilter extends FieldFilter {
  5800. constructor(field, value) {
  5801. super(field, "in" /* Operator.IN */, value);
  5802. }
  5803. matches(doc) {
  5804. const other = doc.data.field(this.field);
  5805. return other !== null && arrayValueContains(this.value.arrayValue, other);
  5806. }
  5807. }
  5808. /** A Filter that implements the not-in operator. */
  5809. class NotInFilter extends FieldFilter {
  5810. constructor(field, value) {
  5811. super(field, "not-in" /* Operator.NOT_IN */, value);
  5812. }
  5813. matches(doc) {
  5814. if (arrayValueContains(this.value.arrayValue, { nullValue: 'NULL_VALUE' })) {
  5815. return false;
  5816. }
  5817. const other = doc.data.field(this.field);
  5818. return other !== null && !arrayValueContains(this.value.arrayValue, other);
  5819. }
  5820. }
  5821. /** A Filter that implements the array-contains-any operator. */
  5822. class ArrayContainsAnyFilter extends FieldFilter {
  5823. constructor(field, value) {
  5824. super(field, "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */, value);
  5825. }
  5826. matches(doc) {
  5827. const other = doc.data.field(this.field);
  5828. if (!isArray(other) || !other.arrayValue.values) {
  5829. return false;
  5830. }
  5831. return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
  5832. }
  5833. }
  5834. /**
  5835. * @license
  5836. * Copyright 2019 Google LLC
  5837. *
  5838. * Licensed under the Apache License, Version 2.0 (the "License");
  5839. * you may not use this file except in compliance with the License.
  5840. * You may obtain a copy of the License at
  5841. *
  5842. * http://www.apache.org/licenses/LICENSE-2.0
  5843. *
  5844. * Unless required by applicable law or agreed to in writing, software
  5845. * distributed under the License is distributed on an "AS IS" BASIS,
  5846. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5847. * See the License for the specific language governing permissions and
  5848. * limitations under the License.
  5849. */
  5850. // Visible for testing
  5851. class TargetImpl {
  5852. constructor(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
  5853. this.path = path;
  5854. this.collectionGroup = collectionGroup;
  5855. this.orderBy = orderBy;
  5856. this.filters = filters;
  5857. this.limit = limit;
  5858. this.startAt = startAt;
  5859. this.endAt = endAt;
  5860. this.memoizedCanonicalId = null;
  5861. }
  5862. }
  5863. /**
  5864. * Initializes a Target with a path and optional additional query constraints.
  5865. * Path must currently be empty if this is a collection group query.
  5866. *
  5867. * NOTE: you should always construct `Target` from `Query.toTarget` instead of
  5868. * using this factory method, because `Query` provides an implicit `orderBy`
  5869. * property.
  5870. */
  5871. function newTarget(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
  5872. return new TargetImpl(path, collectionGroup, orderBy, filters, limit, startAt, endAt);
  5873. }
  5874. function canonifyTarget(target) {
  5875. const targetImpl = debugCast(target);
  5876. if (targetImpl.memoizedCanonicalId === null) {
  5877. let str = targetImpl.path.canonicalString();
  5878. if (targetImpl.collectionGroup !== null) {
  5879. str += '|cg:' + targetImpl.collectionGroup;
  5880. }
  5881. str += '|f:';
  5882. str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
  5883. str += '|ob:';
  5884. str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
  5885. if (!isNullOrUndefined(targetImpl.limit)) {
  5886. str += '|l:';
  5887. str += targetImpl.limit;
  5888. }
  5889. if (targetImpl.startAt) {
  5890. str += '|lb:';
  5891. str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
  5892. str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
  5893. }
  5894. if (targetImpl.endAt) {
  5895. str += '|ub:';
  5896. str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
  5897. str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
  5898. }
  5899. targetImpl.memoizedCanonicalId = str;
  5900. }
  5901. return targetImpl.memoizedCanonicalId;
  5902. }
  5903. function stringifyTarget(target) {
  5904. let str = target.path.canonicalString();
  5905. if (target.collectionGroup !== null) {
  5906. str += ' collectionGroup=' + target.collectionGroup;
  5907. }
  5908. if (target.filters.length > 0) {
  5909. str += `, filters: [${target.filters
  5910. .map(f => stringifyFilter(f))
  5911. .join(', ')}]`;
  5912. }
  5913. if (!isNullOrUndefined(target.limit)) {
  5914. str += ', limit: ' + target.limit;
  5915. }
  5916. if (target.orderBy.length > 0) {
  5917. str += `, orderBy: [${target.orderBy
  5918. .map(o => stringifyOrderBy(o))
  5919. .join(', ')}]`;
  5920. }
  5921. if (target.startAt) {
  5922. str += ', startAt: ';
  5923. str += target.startAt.inclusive ? 'b:' : 'a:';
  5924. str += target.startAt.position.map(p => canonicalId(p)).join(',');
  5925. }
  5926. if (target.endAt) {
  5927. str += ', endAt: ';
  5928. str += target.endAt.inclusive ? 'a:' : 'b:';
  5929. str += target.endAt.position.map(p => canonicalId(p)).join(',');
  5930. }
  5931. return `Target(${str})`;
  5932. }
  5933. function targetEquals(left, right) {
  5934. if (left.limit !== right.limit) {
  5935. return false;
  5936. }
  5937. if (left.orderBy.length !== right.orderBy.length) {
  5938. return false;
  5939. }
  5940. for (let i = 0; i < left.orderBy.length; i++) {
  5941. if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {
  5942. return false;
  5943. }
  5944. }
  5945. if (left.filters.length !== right.filters.length) {
  5946. return false;
  5947. }
  5948. for (let i = 0; i < left.filters.length; i++) {
  5949. if (!filterEquals(left.filters[i], right.filters[i])) {
  5950. return false;
  5951. }
  5952. }
  5953. if (left.collectionGroup !== right.collectionGroup) {
  5954. return false;
  5955. }
  5956. if (!left.path.isEqual(right.path)) {
  5957. return false;
  5958. }
  5959. if (!boundEquals(left.startAt, right.startAt)) {
  5960. return false;
  5961. }
  5962. return boundEquals(left.endAt, right.endAt);
  5963. }
  5964. function targetIsDocumentTarget(target) {
  5965. return (DocumentKey.isDocumentKey(target.path) &&
  5966. target.collectionGroup === null &&
  5967. target.filters.length === 0);
  5968. }
  5969. /** Returns the field filters that target the given field path. */
  5970. function targetGetFieldFiltersForPath(target, path) {
  5971. return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
  5972. }
  5973. /**
  5974. * Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
  5975. * filters. Returns `null` if there are no such filters.
  5976. */
  5977. function targetGetArrayValues(target, fieldIndex) {
  5978. const segment = fieldIndexGetArraySegment(fieldIndex);
  5979. if (segment === undefined) {
  5980. return null;
  5981. }
  5982. for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
  5983. switch (fieldFilter.op) {
  5984. case "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */:
  5985. return fieldFilter.value.arrayValue.values || [];
  5986. case "array-contains" /* Operator.ARRAY_CONTAINS */:
  5987. return [fieldFilter.value];
  5988. // Remaining filters are not array filters.
  5989. }
  5990. }
  5991. return null;
  5992. }
  5993. /**
  5994. * Returns the list of values that are used in != or NOT_IN filters. Returns
  5995. * `null` if there are no such filters.
  5996. */
  5997. function targetGetNotInValues(target, fieldIndex) {
  5998. const values = new Map();
  5999. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  6000. for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
  6001. switch (fieldFilter.op) {
  6002. case "==" /* Operator.EQUAL */:
  6003. case "in" /* Operator.IN */:
  6004. // Encode equality prefix, which is encoded in the index value before
  6005. // the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
  6006. // `value != 'ab'`).
  6007. values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
  6008. break;
  6009. case "not-in" /* Operator.NOT_IN */:
  6010. case "!=" /* Operator.NOT_EQUAL */:
  6011. // NotIn/NotEqual is always a suffix. There cannot be any remaining
  6012. // segments and hence we can return early here.
  6013. values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
  6014. return Array.from(values.values());
  6015. // Remaining filters cannot be used as notIn bounds.
  6016. }
  6017. }
  6018. }
  6019. return null;
  6020. }
  6021. /**
  6022. * Returns a lower bound of field values that can be used as a starting point to
  6023. * scan the index defined by `fieldIndex`. Returns `MIN_VALUE` if no lower bound
  6024. * exists.
  6025. */
  6026. function targetGetLowerBound(target, fieldIndex) {
  6027. const values = [];
  6028. let inclusive = true;
  6029. // For each segment, retrieve a lower bound if there is a suitable filter or
  6030. // startAt.
  6031. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  6032. const segmentBound = segment.kind === 0 /* IndexKind.ASCENDING */
  6033. ? targetGetAscendingBound(target, segment.fieldPath, target.startAt)
  6034. : targetGetDescendingBound(target, segment.fieldPath, target.startAt);
  6035. values.push(segmentBound.value);
  6036. inclusive && (inclusive = segmentBound.inclusive);
  6037. }
  6038. return new Bound(values, inclusive);
  6039. }
  6040. /**
  6041. * Returns an upper bound of field values that can be used as an ending point
  6042. * when scanning the index defined by `fieldIndex`. Returns `MAX_VALUE` if no
  6043. * upper bound exists.
  6044. */
  6045. function targetGetUpperBound(target, fieldIndex) {
  6046. const values = [];
  6047. let inclusive = true;
  6048. // For each segment, retrieve an upper bound if there is a suitable filter or
  6049. // endAt.
  6050. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  6051. const segmentBound = segment.kind === 0 /* IndexKind.ASCENDING */
  6052. ? targetGetDescendingBound(target, segment.fieldPath, target.endAt)
  6053. : targetGetAscendingBound(target, segment.fieldPath, target.endAt);
  6054. values.push(segmentBound.value);
  6055. inclusive && (inclusive = segmentBound.inclusive);
  6056. }
  6057. return new Bound(values, inclusive);
  6058. }
  6059. /**
  6060. * Returns the value to use as the lower bound for ascending index segment at
  6061. * the provided `fieldPath` (or the upper bound for an descending segment).
  6062. */
  6063. function targetGetAscendingBound(target, fieldPath, bound) {
  6064. let value = MIN_VALUE;
  6065. let inclusive = true;
  6066. // Process all filters to find a value for the current field segment
  6067. for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
  6068. let filterValue = MIN_VALUE;
  6069. let filterInclusive = true;
  6070. switch (fieldFilter.op) {
  6071. case "<" /* Operator.LESS_THAN */:
  6072. case "<=" /* Operator.LESS_THAN_OR_EQUAL */:
  6073. filterValue = valuesGetLowerBound(fieldFilter.value);
  6074. break;
  6075. case "==" /* Operator.EQUAL */:
  6076. case "in" /* Operator.IN */:
  6077. case ">=" /* Operator.GREATER_THAN_OR_EQUAL */:
  6078. filterValue = fieldFilter.value;
  6079. break;
  6080. case ">" /* Operator.GREATER_THAN */:
  6081. filterValue = fieldFilter.value;
  6082. filterInclusive = false;
  6083. break;
  6084. case "!=" /* Operator.NOT_EQUAL */:
  6085. case "not-in" /* Operator.NOT_IN */:
  6086. filterValue = MIN_VALUE;
  6087. break;
  6088. // Remaining filters cannot be used as lower bounds.
  6089. }
  6090. if (lowerBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) < 0) {
  6091. value = filterValue;
  6092. inclusive = filterInclusive;
  6093. }
  6094. }
  6095. // If there is an additional bound, compare the values against the existing
  6096. // range to see if we can narrow the scope.
  6097. if (bound !== null) {
  6098. for (let i = 0; i < target.orderBy.length; ++i) {
  6099. const orderBy = target.orderBy[i];
  6100. if (orderBy.field.isEqual(fieldPath)) {
  6101. const cursorValue = bound.position[i];
  6102. if (lowerBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) < 0) {
  6103. value = cursorValue;
  6104. inclusive = bound.inclusive;
  6105. }
  6106. break;
  6107. }
  6108. }
  6109. }
  6110. return { value, inclusive };
  6111. }
  6112. /**
  6113. * Returns the value to use as the upper bound for ascending index segment at
  6114. * the provided `fieldPath` (or the lower bound for a descending segment).
  6115. */
  6116. function targetGetDescendingBound(target, fieldPath, bound) {
  6117. let value = MAX_VALUE;
  6118. let inclusive = true;
  6119. // Process all filters to find a value for the current field segment
  6120. for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
  6121. let filterValue = MAX_VALUE;
  6122. let filterInclusive = true;
  6123. switch (fieldFilter.op) {
  6124. case ">=" /* Operator.GREATER_THAN_OR_EQUAL */:
  6125. case ">" /* Operator.GREATER_THAN */:
  6126. filterValue = valuesGetUpperBound(fieldFilter.value);
  6127. filterInclusive = false;
  6128. break;
  6129. case "==" /* Operator.EQUAL */:
  6130. case "in" /* Operator.IN */:
  6131. case "<=" /* Operator.LESS_THAN_OR_EQUAL */:
  6132. filterValue = fieldFilter.value;
  6133. break;
  6134. case "<" /* Operator.LESS_THAN */:
  6135. filterValue = fieldFilter.value;
  6136. filterInclusive = false;
  6137. break;
  6138. case "!=" /* Operator.NOT_EQUAL */:
  6139. case "not-in" /* Operator.NOT_IN */:
  6140. filterValue = MAX_VALUE;
  6141. break;
  6142. // Remaining filters cannot be used as upper bounds.
  6143. }
  6144. if (upperBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) > 0) {
  6145. value = filterValue;
  6146. inclusive = filterInclusive;
  6147. }
  6148. }
  6149. // If there is an additional bound, compare the values against the existing
  6150. // range to see if we can narrow the scope.
  6151. if (bound !== null) {
  6152. for (let i = 0; i < target.orderBy.length; ++i) {
  6153. const orderBy = target.orderBy[i];
  6154. if (orderBy.field.isEqual(fieldPath)) {
  6155. const cursorValue = bound.position[i];
  6156. if (upperBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) > 0) {
  6157. value = cursorValue;
  6158. inclusive = bound.inclusive;
  6159. }
  6160. break;
  6161. }
  6162. }
  6163. }
  6164. return { value, inclusive };
  6165. }
  6166. /** Returns the number of segments of a perfect index for this target. */
  6167. function targetGetSegmentCount(target) {
  6168. let fields = new SortedSet(FieldPath$1.comparator);
  6169. let hasArraySegment = false;
  6170. for (const filter of target.filters) {
  6171. for (const subFilter of filter.getFlattenedFilters()) {
  6172. // __name__ is not an explicit segment of any index, so we don't need to
  6173. // count it.
  6174. if (subFilter.field.isKeyField()) {
  6175. continue;
  6176. }
  6177. // ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filters must be counted separately.
  6178. // For instance, it is possible to have an index for "a ARRAY a ASC". Even
  6179. // though these are on the same field, they should be counted as two
  6180. // separate segments in an index.
  6181. if (subFilter.op === "array-contains" /* Operator.ARRAY_CONTAINS */ ||
  6182. subFilter.op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  6183. hasArraySegment = true;
  6184. }
  6185. else {
  6186. fields = fields.add(subFilter.field);
  6187. }
  6188. }
  6189. }
  6190. for (const orderBy of target.orderBy) {
  6191. // __name__ is not an explicit segment of any index, so we don't need to
  6192. // count it.
  6193. if (!orderBy.field.isKeyField()) {
  6194. fields = fields.add(orderBy.field);
  6195. }
  6196. }
  6197. return fields.size + (hasArraySegment ? 1 : 0);
  6198. }
  6199. function targetHasLimit(target) {
  6200. return target.limit !== null;
  6201. }
  6202. /**
  6203. * @license
  6204. * Copyright 2017 Google LLC
  6205. *
  6206. * Licensed under the Apache License, Version 2.0 (the "License");
  6207. * you may not use this file except in compliance with the License.
  6208. * You may obtain a copy of the License at
  6209. *
  6210. * http://www.apache.org/licenses/LICENSE-2.0
  6211. *
  6212. * Unless required by applicable law or agreed to in writing, software
  6213. * distributed under the License is distributed on an "AS IS" BASIS,
  6214. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6215. * See the License for the specific language governing permissions and
  6216. * limitations under the License.
  6217. */
  6218. /**
  6219. * Query encapsulates all the query attributes we support in the SDK. It can
  6220. * be run against the LocalStore, as well as be converted to a `Target` to
  6221. * query the RemoteStore results.
  6222. *
  6223. * Visible for testing.
  6224. */
  6225. class QueryImpl {
  6226. /**
  6227. * Initializes a Query with a path and optional additional query constraints.
  6228. * Path must currently be empty if this is a collection group query.
  6229. */
  6230. constructor(path, collectionGroup = null, explicitOrderBy = [], filters = [], limit = null, limitType = "F" /* LimitType.First */, startAt = null, endAt = null) {
  6231. this.path = path;
  6232. this.collectionGroup = collectionGroup;
  6233. this.explicitOrderBy = explicitOrderBy;
  6234. this.filters = filters;
  6235. this.limit = limit;
  6236. this.limitType = limitType;
  6237. this.startAt = startAt;
  6238. this.endAt = endAt;
  6239. this.memoizedOrderBy = null;
  6240. // The corresponding `Target` of this `Query` instance.
  6241. this.memoizedTarget = null;
  6242. if (this.startAt) ;
  6243. if (this.endAt) ;
  6244. }
  6245. }
  6246. /** Creates a new Query instance with the options provided. */
  6247. function newQuery(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt) {
  6248. return new QueryImpl(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt);
  6249. }
  6250. /** Creates a new Query for a query that matches all documents at `path` */
  6251. function newQueryForPath(path) {
  6252. return new QueryImpl(path);
  6253. }
  6254. /**
  6255. * Helper to convert a collection group query into a collection query at a
  6256. * specific path. This is used when executing collection group queries, since
  6257. * we have to split the query into a set of collection queries at multiple
  6258. * paths.
  6259. */
  6260. function asCollectionQueryAtPath(query, path) {
  6261. return new QueryImpl(path,
  6262. /*collectionGroup=*/ null, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
  6263. }
  6264. /**
  6265. * Returns true if this query does not specify any query constraints that
  6266. * could remove results.
  6267. */
  6268. function queryMatchesAllDocuments(query) {
  6269. return (query.filters.length === 0 &&
  6270. query.limit === null &&
  6271. query.startAt == null &&
  6272. query.endAt == null &&
  6273. (query.explicitOrderBy.length === 0 ||
  6274. (query.explicitOrderBy.length === 1 &&
  6275. query.explicitOrderBy[0].field.isKeyField())));
  6276. }
  6277. function getFirstOrderByField(query) {
  6278. return query.explicitOrderBy.length > 0
  6279. ? query.explicitOrderBy[0].field
  6280. : null;
  6281. }
  6282. function getInequalityFilterField(query) {
  6283. for (const filter of query.filters) {
  6284. const result = filter.getFirstInequalityField();
  6285. if (result !== null) {
  6286. return result;
  6287. }
  6288. }
  6289. return null;
  6290. }
  6291. /**
  6292. * Creates a new Query for a collection group query that matches all documents
  6293. * within the provided collection group.
  6294. */
  6295. function newQueryForCollectionGroup(collectionId) {
  6296. return new QueryImpl(ResourcePath.emptyPath(), collectionId);
  6297. }
  6298. /**
  6299. * Returns whether the query matches a single document by path (rather than a
  6300. * collection).
  6301. */
  6302. function isDocumentQuery$1(query) {
  6303. return (DocumentKey.isDocumentKey(query.path) &&
  6304. query.collectionGroup === null &&
  6305. query.filters.length === 0);
  6306. }
  6307. /**
  6308. * Returns whether the query matches a collection group rather than a specific
  6309. * collection.
  6310. */
  6311. function isCollectionGroupQuery(query) {
  6312. return query.collectionGroup !== null;
  6313. }
  6314. /**
  6315. * Returns the implicit order by constraint that is used to execute the Query,
  6316. * which can be different from the order by constraints the user provided (e.g.
  6317. * the SDK and backend always orders by `__name__`).
  6318. */
  6319. function queryOrderBy(query) {
  6320. const queryImpl = debugCast(query);
  6321. if (queryImpl.memoizedOrderBy === null) {
  6322. queryImpl.memoizedOrderBy = [];
  6323. const inequalityField = getInequalityFilterField(queryImpl);
  6324. const firstOrderByField = getFirstOrderByField(queryImpl);
  6325. if (inequalityField !== null && firstOrderByField === null) {
  6326. // In order to implicitly add key ordering, we must also add the
  6327. // inequality filter field for it to be a valid query.
  6328. // Note that the default inequality field and key ordering is ascending.
  6329. if (!inequalityField.isKeyField()) {
  6330. queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
  6331. }
  6332. queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), "asc" /* Direction.ASCENDING */));
  6333. }
  6334. else {
  6335. let foundKeyOrdering = false;
  6336. for (const orderBy of queryImpl.explicitOrderBy) {
  6337. queryImpl.memoizedOrderBy.push(orderBy);
  6338. if (orderBy.field.isKeyField()) {
  6339. foundKeyOrdering = true;
  6340. }
  6341. }
  6342. if (!foundKeyOrdering) {
  6343. // The order of the implicit key ordering always matches the last
  6344. // explicit order by
  6345. const lastDirection = queryImpl.explicitOrderBy.length > 0
  6346. ? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
  6347. .dir
  6348. : "asc" /* Direction.ASCENDING */;
  6349. queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), lastDirection));
  6350. }
  6351. }
  6352. }
  6353. return queryImpl.memoizedOrderBy;
  6354. }
  6355. /**
  6356. * Converts this `Query` instance to it's corresponding `Target` representation.
  6357. */
  6358. function queryToTarget(query) {
  6359. const queryImpl = debugCast(query);
  6360. if (!queryImpl.memoizedTarget) {
  6361. if (queryImpl.limitType === "F" /* LimitType.First */) {
  6362. queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, queryOrderBy(queryImpl), queryImpl.filters, queryImpl.limit, queryImpl.startAt, queryImpl.endAt);
  6363. }
  6364. else {
  6365. // Flip the orderBy directions since we want the last results
  6366. const orderBys = [];
  6367. for (const orderBy of queryOrderBy(queryImpl)) {
  6368. const dir = orderBy.dir === "desc" /* Direction.DESCENDING */
  6369. ? "asc" /* Direction.ASCENDING */
  6370. : "desc" /* Direction.DESCENDING */;
  6371. orderBys.push(new OrderBy(orderBy.field, dir));
  6372. }
  6373. // We need to swap the cursors to match the now-flipped query ordering.
  6374. const startAt = queryImpl.endAt
  6375. ? new Bound(queryImpl.endAt.position, queryImpl.endAt.inclusive)
  6376. : null;
  6377. const endAt = queryImpl.startAt
  6378. ? new Bound(queryImpl.startAt.position, queryImpl.startAt.inclusive)
  6379. : null;
  6380. // Now return as a LimitType.First query.
  6381. queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
  6382. }
  6383. }
  6384. return queryImpl.memoizedTarget;
  6385. }
  6386. function queryWithAddedFilter(query, filter) {
  6387. filter.getFirstInequalityField();
  6388. getInequalityFilterField(query);
  6389. const newFilters = query.filters.concat([filter]);
  6390. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), newFilters, query.limit, query.limitType, query.startAt, query.endAt);
  6391. }
  6392. function queryWithAddedOrderBy(query, orderBy) {
  6393. // TODO(dimond): validate that orderBy does not list the same key twice.
  6394. const newOrderBy = query.explicitOrderBy.concat([orderBy]);
  6395. return new QueryImpl(query.path, query.collectionGroup, newOrderBy, query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
  6396. }
  6397. function queryWithLimit(query, limit, limitType) {
  6398. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), limit, limitType, query.startAt, query.endAt);
  6399. }
  6400. function queryWithStartAt(query, bound) {
  6401. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, bound, query.endAt);
  6402. }
  6403. function queryWithEndAt(query, bound) {
  6404. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, bound);
  6405. }
  6406. function queryEquals(left, right) {
  6407. return (targetEquals(queryToTarget(left), queryToTarget(right)) &&
  6408. left.limitType === right.limitType);
  6409. }
  6410. // TODO(b/29183165): This is used to get a unique string from a query to, for
  6411. // example, use as a dictionary key, but the implementation is subject to
  6412. // collisions. Make it collision-free.
  6413. function canonifyQuery(query) {
  6414. return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;
  6415. }
  6416. function stringifyQuery(query) {
  6417. return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${query.limitType})`;
  6418. }
  6419. /** Returns whether `doc` matches the constraints of `query`. */
  6420. function queryMatches(query, doc) {
  6421. return (doc.isFoundDocument() &&
  6422. queryMatchesPathAndCollectionGroup(query, doc) &&
  6423. queryMatchesOrderBy(query, doc) &&
  6424. queryMatchesFilters(query, doc) &&
  6425. queryMatchesBounds(query, doc));
  6426. }
  6427. function queryMatchesPathAndCollectionGroup(query, doc) {
  6428. const docPath = doc.key.path;
  6429. if (query.collectionGroup !== null) {
  6430. // NOTE: this.path is currently always empty since we don't expose Collection
  6431. // Group queries rooted at a document path yet.
  6432. return (doc.key.hasCollectionId(query.collectionGroup) &&
  6433. query.path.isPrefixOf(docPath));
  6434. }
  6435. else if (DocumentKey.isDocumentKey(query.path)) {
  6436. // exact match for document queries
  6437. return query.path.isEqual(docPath);
  6438. }
  6439. else {
  6440. // shallow ancestor queries by default
  6441. return query.path.isImmediateParentOf(docPath);
  6442. }
  6443. }
  6444. /**
  6445. * A document must have a value for every ordering clause in order to show up
  6446. * in the results.
  6447. */
  6448. function queryMatchesOrderBy(query, doc) {
  6449. // We must use `queryOrderBy()` to get the list of all orderBys (both implicit and explicit).
  6450. // Note that for OR queries, orderBy applies to all disjunction terms and implicit orderBys must
  6451. // be taken into account. For example, the query "a > 1 || b==1" has an implicit "orderBy a" due
  6452. // to the inequality, and is evaluated as "a > 1 orderBy a || b==1 orderBy a".
  6453. // A document with content of {b:1} matches the filters, but does not match the orderBy because
  6454. // it's missing the field 'a'.
  6455. for (const orderBy of queryOrderBy(query)) {
  6456. // order by key always matches
  6457. if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
  6458. return false;
  6459. }
  6460. }
  6461. return true;
  6462. }
  6463. function queryMatchesFilters(query, doc) {
  6464. for (const filter of query.filters) {
  6465. if (!filter.matches(doc)) {
  6466. return false;
  6467. }
  6468. }
  6469. return true;
  6470. }
  6471. /** Makes sure a document is within the bounds, if provided. */
  6472. function queryMatchesBounds(query, doc) {
  6473. if (query.startAt &&
  6474. !boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
  6475. return false;
  6476. }
  6477. if (query.endAt &&
  6478. !boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
  6479. return false;
  6480. }
  6481. return true;
  6482. }
  6483. /**
  6484. * Returns the collection group that this query targets.
  6485. *
  6486. * PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
  6487. * synchronization for query results.
  6488. */
  6489. function queryCollectionGroup(query) {
  6490. return (query.collectionGroup ||
  6491. (query.path.length % 2 === 1
  6492. ? query.path.lastSegment()
  6493. : query.path.get(query.path.length - 2)));
  6494. }
  6495. /**
  6496. * Returns a new comparator function that can be used to compare two documents
  6497. * based on the Query's ordering constraint.
  6498. */
  6499. function newQueryComparator(query) {
  6500. return (d1, d2) => {
  6501. let comparedOnKeyField = false;
  6502. for (const orderBy of queryOrderBy(query)) {
  6503. const comp = compareDocs(orderBy, d1, d2);
  6504. if (comp !== 0) {
  6505. return comp;
  6506. }
  6507. comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();
  6508. }
  6509. return 0;
  6510. };
  6511. }
  6512. function compareDocs(orderBy, d1, d2) {
  6513. const comparison = orderBy.field.isKeyField()
  6514. ? DocumentKey.comparator(d1.key, d2.key)
  6515. : compareDocumentsByField(orderBy.field, d1, d2);
  6516. switch (orderBy.dir) {
  6517. case "asc" /* Direction.ASCENDING */:
  6518. return comparison;
  6519. case "desc" /* Direction.DESCENDING */:
  6520. return -1 * comparison;
  6521. default:
  6522. return fail();
  6523. }
  6524. }
  6525. /**
  6526. * @license
  6527. * Copyright 2017 Google LLC
  6528. *
  6529. * Licensed under the Apache License, Version 2.0 (the "License");
  6530. * you may not use this file except in compliance with the License.
  6531. * You may obtain a copy of the License at
  6532. *
  6533. * http://www.apache.org/licenses/LICENSE-2.0
  6534. *
  6535. * Unless required by applicable law or agreed to in writing, software
  6536. * distributed under the License is distributed on an "AS IS" BASIS,
  6537. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6538. * See the License for the specific language governing permissions and
  6539. * limitations under the License.
  6540. */
  6541. /**
  6542. * A map implementation that uses objects as keys. Objects must have an
  6543. * associated equals function and must be immutable. Entries in the map are
  6544. * stored together with the key being produced from the mapKeyFn. This map
  6545. * automatically handles collisions of keys.
  6546. */
  6547. class ObjectMap {
  6548. constructor(mapKeyFn, equalsFn) {
  6549. this.mapKeyFn = mapKeyFn;
  6550. this.equalsFn = equalsFn;
  6551. /**
  6552. * The inner map for a key/value pair. Due to the possibility of collisions we
  6553. * keep a list of entries that we do a linear search through to find an actual
  6554. * match. Note that collisions should be rare, so we still expect near
  6555. * constant time lookups in practice.
  6556. */
  6557. this.inner = {};
  6558. /** The number of entries stored in the map */
  6559. this.innerSize = 0;
  6560. }
  6561. /** Get a value for this key, or undefined if it does not exist. */
  6562. get(key) {
  6563. const id = this.mapKeyFn(key);
  6564. const matches = this.inner[id];
  6565. if (matches === undefined) {
  6566. return undefined;
  6567. }
  6568. for (const [otherKey, value] of matches) {
  6569. if (this.equalsFn(otherKey, key)) {
  6570. return value;
  6571. }
  6572. }
  6573. return undefined;
  6574. }
  6575. has(key) {
  6576. return this.get(key) !== undefined;
  6577. }
  6578. /** Put this key and value in the map. */
  6579. set(key, value) {
  6580. const id = this.mapKeyFn(key);
  6581. const matches = this.inner[id];
  6582. if (matches === undefined) {
  6583. this.inner[id] = [[key, value]];
  6584. this.innerSize++;
  6585. return;
  6586. }
  6587. for (let i = 0; i < matches.length; i++) {
  6588. if (this.equalsFn(matches[i][0], key)) {
  6589. // This is updating an existing entry and does not increase `innerSize`.
  6590. matches[i] = [key, value];
  6591. return;
  6592. }
  6593. }
  6594. matches.push([key, value]);
  6595. this.innerSize++;
  6596. }
  6597. /**
  6598. * Remove this key from the map. Returns a boolean if anything was deleted.
  6599. */
  6600. delete(key) {
  6601. const id = this.mapKeyFn(key);
  6602. const matches = this.inner[id];
  6603. if (matches === undefined) {
  6604. return false;
  6605. }
  6606. for (let i = 0; i < matches.length; i++) {
  6607. if (this.equalsFn(matches[i][0], key)) {
  6608. if (matches.length === 1) {
  6609. delete this.inner[id];
  6610. }
  6611. else {
  6612. matches.splice(i, 1);
  6613. }
  6614. this.innerSize--;
  6615. return true;
  6616. }
  6617. }
  6618. return false;
  6619. }
  6620. forEach(fn) {
  6621. forEach(this.inner, (_, entries) => {
  6622. for (const [k, v] of entries) {
  6623. fn(k, v);
  6624. }
  6625. });
  6626. }
  6627. isEmpty() {
  6628. return isEmpty(this.inner);
  6629. }
  6630. size() {
  6631. return this.innerSize;
  6632. }
  6633. }
  6634. /**
  6635. * @license
  6636. * Copyright 2017 Google LLC
  6637. *
  6638. * Licensed under the Apache License, Version 2.0 (the "License");
  6639. * you may not use this file except in compliance with the License.
  6640. * You may obtain a copy of the License at
  6641. *
  6642. * http://www.apache.org/licenses/LICENSE-2.0
  6643. *
  6644. * Unless required by applicable law or agreed to in writing, software
  6645. * distributed under the License is distributed on an "AS IS" BASIS,
  6646. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6647. * See the License for the specific language governing permissions and
  6648. * limitations under the License.
  6649. */
  6650. const EMPTY_MUTABLE_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
  6651. function mutableDocumentMap() {
  6652. return EMPTY_MUTABLE_DOCUMENT_MAP;
  6653. }
  6654. const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
  6655. function documentMap(...docs) {
  6656. let map = EMPTY_DOCUMENT_MAP;
  6657. for (const doc of docs) {
  6658. map = map.insert(doc.key, doc);
  6659. }
  6660. return map;
  6661. }
  6662. function newOverlayedDocumentMap() {
  6663. return newDocumentKeyMap();
  6664. }
  6665. function convertOverlayedDocumentMapToDocumentMap(collection) {
  6666. let documents = EMPTY_DOCUMENT_MAP;
  6667. collection.forEach((k, v) => (documents = documents.insert(k, v.overlayedDocument)));
  6668. return documents;
  6669. }
  6670. function newOverlayMap() {
  6671. return newDocumentKeyMap();
  6672. }
  6673. function newMutationMap() {
  6674. return newDocumentKeyMap();
  6675. }
  6676. function newDocumentKeyMap() {
  6677. return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
  6678. }
  6679. const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
  6680. function documentVersionMap() {
  6681. return EMPTY_DOCUMENT_VERSION_MAP;
  6682. }
  6683. const EMPTY_DOCUMENT_KEY_SET = new SortedSet(DocumentKey.comparator);
  6684. function documentKeySet(...keys) {
  6685. let set = EMPTY_DOCUMENT_KEY_SET;
  6686. for (const key of keys) {
  6687. set = set.add(key);
  6688. }
  6689. return set;
  6690. }
  6691. const EMPTY_TARGET_ID_SET = new SortedSet(primitiveComparator);
  6692. function targetIdSet() {
  6693. return EMPTY_TARGET_ID_SET;
  6694. }
  6695. /**
  6696. * @license
  6697. * Copyright 2020 Google LLC
  6698. *
  6699. * Licensed under the Apache License, Version 2.0 (the "License");
  6700. * you may not use this file except in compliance with the License.
  6701. * You may obtain a copy of the License at
  6702. *
  6703. * http://www.apache.org/licenses/LICENSE-2.0
  6704. *
  6705. * Unless required by applicable law or agreed to in writing, software
  6706. * distributed under the License is distributed on an "AS IS" BASIS,
  6707. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6708. * See the License for the specific language governing permissions and
  6709. * limitations under the License.
  6710. */
  6711. /**
  6712. * Returns an DoubleValue for `value` that is encoded based the serializer's
  6713. * `useProto3Json` setting.
  6714. */
  6715. function toDouble(serializer, value) {
  6716. if (serializer.useProto3Json) {
  6717. if (isNaN(value)) {
  6718. return { doubleValue: 'NaN' };
  6719. }
  6720. else if (value === Infinity) {
  6721. return { doubleValue: 'Infinity' };
  6722. }
  6723. else if (value === -Infinity) {
  6724. return { doubleValue: '-Infinity' };
  6725. }
  6726. }
  6727. return { doubleValue: isNegativeZero(value) ? '-0' : value };
  6728. }
  6729. /**
  6730. * Returns an IntegerValue for `value`.
  6731. */
  6732. function toInteger(value) {
  6733. return { integerValue: '' + value };
  6734. }
  6735. /**
  6736. * Returns a value for a number that's appropriate to put into a proto.
  6737. * The return value is an IntegerValue if it can safely represent the value,
  6738. * otherwise a DoubleValue is returned.
  6739. */
  6740. function toNumber(serializer, value) {
  6741. return isSafeInteger(value) ? toInteger(value) : toDouble(serializer, value);
  6742. }
  6743. /**
  6744. * @license
  6745. * Copyright 2018 Google LLC
  6746. *
  6747. * Licensed under the Apache License, Version 2.0 (the "License");
  6748. * you may not use this file except in compliance with the License.
  6749. * You may obtain a copy of the License at
  6750. *
  6751. * http://www.apache.org/licenses/LICENSE-2.0
  6752. *
  6753. * Unless required by applicable law or agreed to in writing, software
  6754. * distributed under the License is distributed on an "AS IS" BASIS,
  6755. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6756. * See the License for the specific language governing permissions and
  6757. * limitations under the License.
  6758. */
  6759. /** Used to represent a field transform on a mutation. */
  6760. class TransformOperation {
  6761. constructor() {
  6762. // Make sure that the structural type of `TransformOperation` is unique.
  6763. // See https://github.com/microsoft/TypeScript/issues/5451
  6764. this._ = undefined;
  6765. }
  6766. }
  6767. /**
  6768. * Computes the local transform result against the provided `previousValue`,
  6769. * optionally using the provided localWriteTime.
  6770. */
  6771. function applyTransformOperationToLocalView(transform, previousValue, localWriteTime) {
  6772. if (transform instanceof ServerTimestampTransform) {
  6773. return serverTimestamp$1(localWriteTime, previousValue);
  6774. }
  6775. else if (transform instanceof ArrayUnionTransformOperation) {
  6776. return applyArrayUnionTransformOperation(transform, previousValue);
  6777. }
  6778. else if (transform instanceof ArrayRemoveTransformOperation) {
  6779. return applyArrayRemoveTransformOperation(transform, previousValue);
  6780. }
  6781. else {
  6782. return applyNumericIncrementTransformOperationToLocalView(transform, previousValue);
  6783. }
  6784. }
  6785. /**
  6786. * Computes a final transform result after the transform has been acknowledged
  6787. * by the server, potentially using the server-provided transformResult.
  6788. */
  6789. function applyTransformOperationToRemoteDocument(transform, previousValue, transformResult) {
  6790. // The server just sends null as the transform result for array operations,
  6791. // so we have to calculate a result the same as we do for local
  6792. // applications.
  6793. if (transform instanceof ArrayUnionTransformOperation) {
  6794. return applyArrayUnionTransformOperation(transform, previousValue);
  6795. }
  6796. else if (transform instanceof ArrayRemoveTransformOperation) {
  6797. return applyArrayRemoveTransformOperation(transform, previousValue);
  6798. }
  6799. return transformResult;
  6800. }
  6801. /**
  6802. * If this transform operation is not idempotent, returns the base value to
  6803. * persist for this transform. If a base value is returned, the transform
  6804. * operation is always applied to this base value, even if document has
  6805. * already been updated.
  6806. *
  6807. * Base values provide consistent behavior for non-idempotent transforms and
  6808. * allow us to return the same latency-compensated value even if the backend
  6809. * has already applied the transform operation. The base value is null for
  6810. * idempotent transforms, as they can be re-played even if the backend has
  6811. * already applied them.
  6812. *
  6813. * @returns a base value to store along with the mutation, or null for
  6814. * idempotent transforms.
  6815. */
  6816. function computeTransformOperationBaseValue(transform, previousValue) {
  6817. if (transform instanceof NumericIncrementTransformOperation) {
  6818. return isNumber(previousValue) ? previousValue : { integerValue: 0 };
  6819. }
  6820. return null;
  6821. }
  6822. function transformOperationEquals(left, right) {
  6823. if (left instanceof ArrayUnionTransformOperation &&
  6824. right instanceof ArrayUnionTransformOperation) {
  6825. return arrayEquals(left.elements, right.elements, valueEquals);
  6826. }
  6827. else if (left instanceof ArrayRemoveTransformOperation &&
  6828. right instanceof ArrayRemoveTransformOperation) {
  6829. return arrayEquals(left.elements, right.elements, valueEquals);
  6830. }
  6831. else if (left instanceof NumericIncrementTransformOperation &&
  6832. right instanceof NumericIncrementTransformOperation) {
  6833. return valueEquals(left.operand, right.operand);
  6834. }
  6835. return (left instanceof ServerTimestampTransform &&
  6836. right instanceof ServerTimestampTransform);
  6837. }
  6838. /** Transforms a value into a server-generated timestamp. */
  6839. class ServerTimestampTransform extends TransformOperation {
  6840. }
  6841. /** Transforms an array value via a union operation. */
  6842. class ArrayUnionTransformOperation extends TransformOperation {
  6843. constructor(elements) {
  6844. super();
  6845. this.elements = elements;
  6846. }
  6847. }
  6848. function applyArrayUnionTransformOperation(transform, previousValue) {
  6849. const values = coercedFieldValuesArray(previousValue);
  6850. for (const toUnion of transform.elements) {
  6851. if (!values.some(element => valueEquals(element, toUnion))) {
  6852. values.push(toUnion);
  6853. }
  6854. }
  6855. return { arrayValue: { values } };
  6856. }
  6857. /** Transforms an array value via a remove operation. */
  6858. class ArrayRemoveTransformOperation extends TransformOperation {
  6859. constructor(elements) {
  6860. super();
  6861. this.elements = elements;
  6862. }
  6863. }
  6864. function applyArrayRemoveTransformOperation(transform, previousValue) {
  6865. let values = coercedFieldValuesArray(previousValue);
  6866. for (const toRemove of transform.elements) {
  6867. values = values.filter(element => !valueEquals(element, toRemove));
  6868. }
  6869. return { arrayValue: { values } };
  6870. }
  6871. /**
  6872. * Implements the backend semantics for locally computed NUMERIC_ADD (increment)
  6873. * transforms. Converts all field values to integers or doubles, but unlike the
  6874. * backend does not cap integer values at 2^63. Instead, JavaScript number
  6875. * arithmetic is used and precision loss can occur for values greater than 2^53.
  6876. */
  6877. class NumericIncrementTransformOperation extends TransformOperation {
  6878. constructor(serializer, operand) {
  6879. super();
  6880. this.serializer = serializer;
  6881. this.operand = operand;
  6882. }
  6883. }
  6884. function applyNumericIncrementTransformOperationToLocalView(transform, previousValue) {
  6885. // PORTING NOTE: Since JavaScript's integer arithmetic is limited to 53 bit
  6886. // precision and resolves overflows by reducing precision, we do not
  6887. // manually cap overflows at 2^63.
  6888. const baseValue = computeTransformOperationBaseValue(transform, previousValue);
  6889. const sum = asNumber(baseValue) + asNumber(transform.operand);
  6890. if (isInteger(baseValue) && isInteger(transform.operand)) {
  6891. return toInteger(sum);
  6892. }
  6893. else {
  6894. return toDouble(transform.serializer, sum);
  6895. }
  6896. }
  6897. function asNumber(value) {
  6898. return normalizeNumber(value.integerValue || value.doubleValue);
  6899. }
  6900. function coercedFieldValuesArray(value) {
  6901. return isArray(value) && value.arrayValue.values
  6902. ? value.arrayValue.values.slice()
  6903. : [];
  6904. }
  6905. /**
  6906. * @license
  6907. * Copyright 2017 Google LLC
  6908. *
  6909. * Licensed under the Apache License, Version 2.0 (the "License");
  6910. * you may not use this file except in compliance with the License.
  6911. * You may obtain a copy of the License at
  6912. *
  6913. * http://www.apache.org/licenses/LICENSE-2.0
  6914. *
  6915. * Unless required by applicable law or agreed to in writing, software
  6916. * distributed under the License is distributed on an "AS IS" BASIS,
  6917. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6918. * See the License for the specific language governing permissions and
  6919. * limitations under the License.
  6920. */
  6921. /** A field path and the TransformOperation to perform upon it. */
  6922. class FieldTransform {
  6923. constructor(field, transform) {
  6924. this.field = field;
  6925. this.transform = transform;
  6926. }
  6927. }
  6928. function fieldTransformEquals(left, right) {
  6929. return (left.field.isEqual(right.field) &&
  6930. transformOperationEquals(left.transform, right.transform));
  6931. }
  6932. function fieldTransformsAreEqual(left, right) {
  6933. if (left === undefined && right === undefined) {
  6934. return true;
  6935. }
  6936. if (left && right) {
  6937. return arrayEquals(left, right, (l, r) => fieldTransformEquals(l, r));
  6938. }
  6939. return false;
  6940. }
  6941. /** The result of successfully applying a mutation to the backend. */
  6942. class MutationResult {
  6943. constructor(
  6944. /**
  6945. * The version at which the mutation was committed:
  6946. *
  6947. * - For most operations, this is the updateTime in the WriteResult.
  6948. * - For deletes, the commitTime of the WriteResponse (because deletes are
  6949. * not stored and have no updateTime).
  6950. *
  6951. * Note that these versions can be different: No-op writes will not change
  6952. * the updateTime even though the commitTime advances.
  6953. */
  6954. version,
  6955. /**
  6956. * The resulting fields returned from the backend after a mutation
  6957. * containing field transforms has been committed. Contains one FieldValue
  6958. * for each FieldTransform that was in the mutation.
  6959. *
  6960. * Will be empty if the mutation did not contain any field transforms.
  6961. */
  6962. transformResults) {
  6963. this.version = version;
  6964. this.transformResults = transformResults;
  6965. }
  6966. }
  6967. /**
  6968. * Encodes a precondition for a mutation. This follows the model that the
  6969. * backend accepts with the special case of an explicit "empty" precondition
  6970. * (meaning no precondition).
  6971. */
  6972. class Precondition {
  6973. constructor(updateTime, exists) {
  6974. this.updateTime = updateTime;
  6975. this.exists = exists;
  6976. }
  6977. /** Creates a new empty Precondition. */
  6978. static none() {
  6979. return new Precondition();
  6980. }
  6981. /** Creates a new Precondition with an exists flag. */
  6982. static exists(exists) {
  6983. return new Precondition(undefined, exists);
  6984. }
  6985. /** Creates a new Precondition based on a version a document exists at. */
  6986. static updateTime(version) {
  6987. return new Precondition(version);
  6988. }
  6989. /** Returns whether this Precondition is empty. */
  6990. get isNone() {
  6991. return this.updateTime === undefined && this.exists === undefined;
  6992. }
  6993. isEqual(other) {
  6994. return (this.exists === other.exists &&
  6995. (this.updateTime
  6996. ? !!other.updateTime && this.updateTime.isEqual(other.updateTime)
  6997. : !other.updateTime));
  6998. }
  6999. }
  7000. /** Returns true if the preconditions is valid for the given document. */
  7001. function preconditionIsValidForDocument(precondition, document) {
  7002. if (precondition.updateTime !== undefined) {
  7003. return (document.isFoundDocument() &&
  7004. document.version.isEqual(precondition.updateTime));
  7005. }
  7006. else if (precondition.exists !== undefined) {
  7007. return precondition.exists === document.isFoundDocument();
  7008. }
  7009. else {
  7010. return true;
  7011. }
  7012. }
  7013. /**
  7014. * A mutation describes a self-contained change to a document. Mutations can
  7015. * create, replace, delete, and update subsets of documents.
  7016. *
  7017. * Mutations not only act on the value of the document but also its version.
  7018. *
  7019. * For local mutations (mutations that haven't been committed yet), we preserve
  7020. * the existing version for Set and Patch mutations. For Delete mutations, we
  7021. * reset the version to 0.
  7022. *
  7023. * Here's the expected transition table.
  7024. *
  7025. * MUTATION APPLIED TO RESULTS IN
  7026. *
  7027. * SetMutation Document(v3) Document(v3)
  7028. * SetMutation NoDocument(v3) Document(v0)
  7029. * SetMutation InvalidDocument(v0) Document(v0)
  7030. * PatchMutation Document(v3) Document(v3)
  7031. * PatchMutation NoDocument(v3) NoDocument(v3)
  7032. * PatchMutation InvalidDocument(v0) UnknownDocument(v3)
  7033. * DeleteMutation Document(v3) NoDocument(v0)
  7034. * DeleteMutation NoDocument(v3) NoDocument(v0)
  7035. * DeleteMutation InvalidDocument(v0) NoDocument(v0)
  7036. *
  7037. * For acknowledged mutations, we use the updateTime of the WriteResponse as
  7038. * the resulting version for Set and Patch mutations. As deletes have no
  7039. * explicit update time, we use the commitTime of the WriteResponse for
  7040. * Delete mutations.
  7041. *
  7042. * If a mutation is acknowledged by the backend but fails the precondition check
  7043. * locally, we transition to an `UnknownDocument` and rely on Watch to send us
  7044. * the updated version.
  7045. *
  7046. * Field transforms are used only with Patch and Set Mutations. We use the
  7047. * `updateTransforms` message to store transforms, rather than the `transforms`s
  7048. * messages.
  7049. *
  7050. * ## Subclassing Notes
  7051. *
  7052. * Every type of mutation needs to implement its own applyToRemoteDocument() and
  7053. * applyToLocalView() to implement the actual behavior of applying the mutation
  7054. * to some source document (see `setMutationApplyToRemoteDocument()` for an
  7055. * example).
  7056. */
  7057. class Mutation {
  7058. }
  7059. /**
  7060. * A utility method to calculate a `Mutation` representing the overlay from the
  7061. * final state of the document, and a `FieldMask` representing the fields that
  7062. * are mutated by the local mutations.
  7063. */
  7064. function calculateOverlayMutation(doc, mask) {
  7065. if (!doc.hasLocalMutations || (mask && mask.fields.length === 0)) {
  7066. return null;
  7067. }
  7068. // mask is null when sets or deletes are applied to the current document.
  7069. if (mask === null) {
  7070. if (doc.isNoDocument()) {
  7071. return new DeleteMutation(doc.key, Precondition.none());
  7072. }
  7073. else {
  7074. return new SetMutation(doc.key, doc.data, Precondition.none());
  7075. }
  7076. }
  7077. else {
  7078. const docValue = doc.data;
  7079. const patchValue = ObjectValue.empty();
  7080. let maskSet = new SortedSet(FieldPath$1.comparator);
  7081. for (let path of mask.fields) {
  7082. if (!maskSet.has(path)) {
  7083. let value = docValue.field(path);
  7084. // If we are deleting a nested field, we take the immediate parent as
  7085. // the mask used to construct the resulting mutation.
  7086. // Justification: Nested fields can create parent fields implicitly. If
  7087. // only a leaf entry is deleted in later mutations, the parent field
  7088. // should still remain, but we may have lost this information.
  7089. // Consider mutation (foo.bar 1), then mutation (foo.bar delete()).
  7090. // This leaves the final result (foo, {}). Despite the fact that `doc`
  7091. // has the correct result, `foo` is not in `mask`, and the resulting
  7092. // mutation would miss `foo`.
  7093. if (value === null && path.length > 1) {
  7094. path = path.popLast();
  7095. value = docValue.field(path);
  7096. }
  7097. if (value === null) {
  7098. patchValue.delete(path);
  7099. }
  7100. else {
  7101. patchValue.set(path, value);
  7102. }
  7103. maskSet = maskSet.add(path);
  7104. }
  7105. }
  7106. return new PatchMutation(doc.key, patchValue, new FieldMask(maskSet.toArray()), Precondition.none());
  7107. }
  7108. }
  7109. /**
  7110. * Applies this mutation to the given document for the purposes of computing a
  7111. * new remote document. If the input document doesn't match the expected state
  7112. * (e.g. it is invalid or outdated), the document type may transition to
  7113. * unknown.
  7114. *
  7115. * @param mutation - The mutation to apply.
  7116. * @param document - The document to mutate. The input document can be an
  7117. * invalid document if the client has no knowledge of the pre-mutation state
  7118. * of the document.
  7119. * @param mutationResult - The result of applying the mutation from the backend.
  7120. */
  7121. function mutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7122. if (mutation instanceof SetMutation) {
  7123. setMutationApplyToRemoteDocument(mutation, document, mutationResult);
  7124. }
  7125. else if (mutation instanceof PatchMutation) {
  7126. patchMutationApplyToRemoteDocument(mutation, document, mutationResult);
  7127. }
  7128. else {
  7129. deleteMutationApplyToRemoteDocument(mutation, document, mutationResult);
  7130. }
  7131. }
  7132. /**
  7133. * Applies this mutation to the given document for the purposes of computing
  7134. * the new local view of a document. If the input document doesn't match the
  7135. * expected state, the document is not modified.
  7136. *
  7137. * @param mutation - The mutation to apply.
  7138. * @param document - The document to mutate. The input document can be an
  7139. * invalid document if the client has no knowledge of the pre-mutation state
  7140. * of the document.
  7141. * @param previousMask - The fields that have been updated before applying this mutation.
  7142. * @param localWriteTime - A timestamp indicating the local write time of the
  7143. * batch this mutation is a part of.
  7144. * @returns A `FieldMask` representing the fields that are changed by applying this mutation.
  7145. */
  7146. function mutationApplyToLocalView(mutation, document, previousMask, localWriteTime) {
  7147. if (mutation instanceof SetMutation) {
  7148. return setMutationApplyToLocalView(mutation, document, previousMask, localWriteTime);
  7149. }
  7150. else if (mutation instanceof PatchMutation) {
  7151. return patchMutationApplyToLocalView(mutation, document, previousMask, localWriteTime);
  7152. }
  7153. else {
  7154. return deleteMutationApplyToLocalView(mutation, document, previousMask);
  7155. }
  7156. }
  7157. /**
  7158. * If this mutation is not idempotent, returns the base value to persist with
  7159. * this mutation. If a base value is returned, the mutation is always applied
  7160. * to this base value, even if document has already been updated.
  7161. *
  7162. * The base value is a sparse object that consists of only the document
  7163. * fields for which this mutation contains a non-idempotent transformation
  7164. * (e.g. a numeric increment). The provided value guarantees consistent
  7165. * behavior for non-idempotent transforms and allow us to return the same
  7166. * latency-compensated value even if the backend has already applied the
  7167. * mutation. The base value is null for idempotent mutations, as they can be
  7168. * re-played even if the backend has already applied them.
  7169. *
  7170. * @returns a base value to store along with the mutation, or null for
  7171. * idempotent mutations.
  7172. */
  7173. function mutationExtractBaseValue(mutation, document) {
  7174. let baseObject = null;
  7175. for (const fieldTransform of mutation.fieldTransforms) {
  7176. const existingValue = document.data.field(fieldTransform.field);
  7177. const coercedValue = computeTransformOperationBaseValue(fieldTransform.transform, existingValue || null);
  7178. if (coercedValue != null) {
  7179. if (baseObject === null) {
  7180. baseObject = ObjectValue.empty();
  7181. }
  7182. baseObject.set(fieldTransform.field, coercedValue);
  7183. }
  7184. }
  7185. return baseObject ? baseObject : null;
  7186. }
  7187. function mutationEquals(left, right) {
  7188. if (left.type !== right.type) {
  7189. return false;
  7190. }
  7191. if (!left.key.isEqual(right.key)) {
  7192. return false;
  7193. }
  7194. if (!left.precondition.isEqual(right.precondition)) {
  7195. return false;
  7196. }
  7197. if (!fieldTransformsAreEqual(left.fieldTransforms, right.fieldTransforms)) {
  7198. return false;
  7199. }
  7200. if (left.type === 0 /* MutationType.Set */) {
  7201. return left.value.isEqual(right.value);
  7202. }
  7203. if (left.type === 1 /* MutationType.Patch */) {
  7204. return (left.data.isEqual(right.data) &&
  7205. left.fieldMask.isEqual(right.fieldMask));
  7206. }
  7207. return true;
  7208. }
  7209. /**
  7210. * A mutation that creates or replaces the document at the given key with the
  7211. * object value contents.
  7212. */
  7213. class SetMutation extends Mutation {
  7214. constructor(key, value, precondition, fieldTransforms = []) {
  7215. super();
  7216. this.key = key;
  7217. this.value = value;
  7218. this.precondition = precondition;
  7219. this.fieldTransforms = fieldTransforms;
  7220. this.type = 0 /* MutationType.Set */;
  7221. }
  7222. getFieldMask() {
  7223. return null;
  7224. }
  7225. }
  7226. function setMutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7227. // Unlike setMutationApplyToLocalView, if we're applying a mutation to a
  7228. // remote document the server has accepted the mutation so the precondition
  7229. // must have held.
  7230. const newData = mutation.value.clone();
  7231. const transformResults = serverTransformResults(mutation.fieldTransforms, document, mutationResult.transformResults);
  7232. newData.setAll(transformResults);
  7233. document
  7234. .convertToFoundDocument(mutationResult.version, newData)
  7235. .setHasCommittedMutations();
  7236. }
  7237. function setMutationApplyToLocalView(mutation, document, previousMask, localWriteTime) {
  7238. if (!preconditionIsValidForDocument(mutation.precondition, document)) {
  7239. // The mutation failed to apply (e.g. a document ID created with add()
  7240. // caused a name collision).
  7241. return previousMask;
  7242. }
  7243. const newData = mutation.value.clone();
  7244. const transformResults = localTransformResults(mutation.fieldTransforms, localWriteTime, document);
  7245. newData.setAll(transformResults);
  7246. document
  7247. .convertToFoundDocument(document.version, newData)
  7248. .setHasLocalMutations();
  7249. return null; // SetMutation overwrites all fields.
  7250. }
  7251. /**
  7252. * A mutation that modifies fields of the document at the given key with the
  7253. * given values. The values are applied through a field mask:
  7254. *
  7255. * * When a field is in both the mask and the values, the corresponding field
  7256. * is updated.
  7257. * * When a field is in neither the mask nor the values, the corresponding
  7258. * field is unmodified.
  7259. * * When a field is in the mask but not in the values, the corresponding field
  7260. * is deleted.
  7261. * * When a field is not in the mask but is in the values, the values map is
  7262. * ignored.
  7263. */
  7264. class PatchMutation extends Mutation {
  7265. constructor(key, data, fieldMask, precondition, fieldTransforms = []) {
  7266. super();
  7267. this.key = key;
  7268. this.data = data;
  7269. this.fieldMask = fieldMask;
  7270. this.precondition = precondition;
  7271. this.fieldTransforms = fieldTransforms;
  7272. this.type = 1 /* MutationType.Patch */;
  7273. }
  7274. getFieldMask() {
  7275. return this.fieldMask;
  7276. }
  7277. }
  7278. function patchMutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7279. if (!preconditionIsValidForDocument(mutation.precondition, document)) {
  7280. // Since the mutation was not rejected, we know that the precondition
  7281. // matched on the backend. We therefore must not have the expected version
  7282. // of the document in our cache and convert to an UnknownDocument with a
  7283. // known updateTime.
  7284. document.convertToUnknownDocument(mutationResult.version);
  7285. return;
  7286. }
  7287. const transformResults = serverTransformResults(mutation.fieldTransforms, document, mutationResult.transformResults);
  7288. const newData = document.data;
  7289. newData.setAll(getPatch(mutation));
  7290. newData.setAll(transformResults);
  7291. document
  7292. .convertToFoundDocument(mutationResult.version, newData)
  7293. .setHasCommittedMutations();
  7294. }
  7295. function patchMutationApplyToLocalView(mutation, document, previousMask, localWriteTime) {
  7296. if (!preconditionIsValidForDocument(mutation.precondition, document)) {
  7297. return previousMask;
  7298. }
  7299. const transformResults = localTransformResults(mutation.fieldTransforms, localWriteTime, document);
  7300. const newData = document.data;
  7301. newData.setAll(getPatch(mutation));
  7302. newData.setAll(transformResults);
  7303. document
  7304. .convertToFoundDocument(document.version, newData)
  7305. .setHasLocalMutations();
  7306. if (previousMask === null) {
  7307. return null;
  7308. }
  7309. return previousMask
  7310. .unionWith(mutation.fieldMask.fields)
  7311. .unionWith(mutation.fieldTransforms.map(transform => transform.field));
  7312. }
  7313. /**
  7314. * Returns a FieldPath/Value map with the content of the PatchMutation.
  7315. */
  7316. function getPatch(mutation) {
  7317. const result = new Map();
  7318. mutation.fieldMask.fields.forEach(fieldPath => {
  7319. if (!fieldPath.isEmpty()) {
  7320. const newValue = mutation.data.field(fieldPath);
  7321. result.set(fieldPath, newValue);
  7322. }
  7323. });
  7324. return result;
  7325. }
  7326. /**
  7327. * Creates a list of "transform results" (a transform result is a field value
  7328. * representing the result of applying a transform) for use after a mutation
  7329. * containing transforms has been acknowledged by the server.
  7330. *
  7331. * @param fieldTransforms - The field transforms to apply the result to.
  7332. * @param mutableDocument - The current state of the document after applying all
  7333. * previous mutations.
  7334. * @param serverTransformResults - The transform results received by the server.
  7335. * @returns The transform results list.
  7336. */
  7337. function serverTransformResults(fieldTransforms, mutableDocument, serverTransformResults) {
  7338. const transformResults = new Map();
  7339. hardAssert(fieldTransforms.length === serverTransformResults.length);
  7340. for (let i = 0; i < serverTransformResults.length; i++) {
  7341. const fieldTransform = fieldTransforms[i];
  7342. const transform = fieldTransform.transform;
  7343. const previousValue = mutableDocument.data.field(fieldTransform.field);
  7344. transformResults.set(fieldTransform.field, applyTransformOperationToRemoteDocument(transform, previousValue, serverTransformResults[i]));
  7345. }
  7346. return transformResults;
  7347. }
  7348. /**
  7349. * Creates a list of "transform results" (a transform result is a field value
  7350. * representing the result of applying a transform) for use when applying a
  7351. * transform locally.
  7352. *
  7353. * @param fieldTransforms - The field transforms to apply the result to.
  7354. * @param localWriteTime - The local time of the mutation (used to
  7355. * generate ServerTimestampValues).
  7356. * @param mutableDocument - The document to apply transforms on.
  7357. * @returns The transform results list.
  7358. */
  7359. function localTransformResults(fieldTransforms, localWriteTime, mutableDocument) {
  7360. const transformResults = new Map();
  7361. for (const fieldTransform of fieldTransforms) {
  7362. const transform = fieldTransform.transform;
  7363. const previousValue = mutableDocument.data.field(fieldTransform.field);
  7364. transformResults.set(fieldTransform.field, applyTransformOperationToLocalView(transform, previousValue, localWriteTime));
  7365. }
  7366. return transformResults;
  7367. }
  7368. /** A mutation that deletes the document at the given key. */
  7369. class DeleteMutation extends Mutation {
  7370. constructor(key, precondition) {
  7371. super();
  7372. this.key = key;
  7373. this.precondition = precondition;
  7374. this.type = 2 /* MutationType.Delete */;
  7375. this.fieldTransforms = [];
  7376. }
  7377. getFieldMask() {
  7378. return null;
  7379. }
  7380. }
  7381. function deleteMutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7382. // Unlike applyToLocalView, if we're applying a mutation to a remote
  7383. // document the server has accepted the mutation so the precondition must
  7384. // have held.
  7385. document
  7386. .convertToNoDocument(mutationResult.version)
  7387. .setHasCommittedMutations();
  7388. }
  7389. function deleteMutationApplyToLocalView(mutation, document, previousMask) {
  7390. if (preconditionIsValidForDocument(mutation.precondition, document)) {
  7391. document.convertToNoDocument(document.version).setHasLocalMutations();
  7392. return null;
  7393. }
  7394. return previousMask;
  7395. }
  7396. /**
  7397. * A mutation that verifies the existence of the document at the given key with
  7398. * the provided precondition.
  7399. *
  7400. * The `verify` operation is only used in Transactions, and this class serves
  7401. * primarily to facilitate serialization into protos.
  7402. */
  7403. class VerifyMutation extends Mutation {
  7404. constructor(key, precondition) {
  7405. super();
  7406. this.key = key;
  7407. this.precondition = precondition;
  7408. this.type = 3 /* MutationType.Verify */;
  7409. this.fieldTransforms = [];
  7410. }
  7411. getFieldMask() {
  7412. return null;
  7413. }
  7414. }
  7415. /**
  7416. * @license
  7417. * Copyright 2017 Google LLC
  7418. *
  7419. * Licensed under the Apache License, Version 2.0 (the "License");
  7420. * you may not use this file except in compliance with the License.
  7421. * You may obtain a copy of the License at
  7422. *
  7423. * http://www.apache.org/licenses/LICENSE-2.0
  7424. *
  7425. * Unless required by applicable law or agreed to in writing, software
  7426. * distributed under the License is distributed on an "AS IS" BASIS,
  7427. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7428. * See the License for the specific language governing permissions and
  7429. * limitations under the License.
  7430. */
  7431. /**
  7432. * A batch of mutations that will be sent as one unit to the backend.
  7433. */
  7434. class MutationBatch {
  7435. /**
  7436. * @param batchId - The unique ID of this mutation batch.
  7437. * @param localWriteTime - The original write time of this mutation.
  7438. * @param baseMutations - Mutations that are used to populate the base
  7439. * values when this mutation is applied locally. This can be used to locally
  7440. * overwrite values that are persisted in the remote document cache. Base
  7441. * mutations are never sent to the backend.
  7442. * @param mutations - The user-provided mutations in this mutation batch.
  7443. * User-provided mutations are applied both locally and remotely on the
  7444. * backend.
  7445. */
  7446. constructor(batchId, localWriteTime, baseMutations, mutations) {
  7447. this.batchId = batchId;
  7448. this.localWriteTime = localWriteTime;
  7449. this.baseMutations = baseMutations;
  7450. this.mutations = mutations;
  7451. }
  7452. /**
  7453. * Applies all the mutations in this MutationBatch to the specified document
  7454. * to compute the state of the remote document
  7455. *
  7456. * @param document - The document to apply mutations to.
  7457. * @param batchResult - The result of applying the MutationBatch to the
  7458. * backend.
  7459. */
  7460. applyToRemoteDocument(document, batchResult) {
  7461. const mutationResults = batchResult.mutationResults;
  7462. for (let i = 0; i < this.mutations.length; i++) {
  7463. const mutation = this.mutations[i];
  7464. if (mutation.key.isEqual(document.key)) {
  7465. const mutationResult = mutationResults[i];
  7466. mutationApplyToRemoteDocument(mutation, document, mutationResult);
  7467. }
  7468. }
  7469. }
  7470. /**
  7471. * Computes the local view of a document given all the mutations in this
  7472. * batch.
  7473. *
  7474. * @param document - The document to apply mutations to.
  7475. * @param mutatedFields - Fields that have been updated before applying this mutation batch.
  7476. * @returns A `FieldMask` representing all the fields that are mutated.
  7477. */
  7478. applyToLocalView(document, mutatedFields) {
  7479. // First, apply the base state. This allows us to apply non-idempotent
  7480. // transform against a consistent set of values.
  7481. for (const mutation of this.baseMutations) {
  7482. if (mutation.key.isEqual(document.key)) {
  7483. mutatedFields = mutationApplyToLocalView(mutation, document, mutatedFields, this.localWriteTime);
  7484. }
  7485. }
  7486. // Second, apply all user-provided mutations.
  7487. for (const mutation of this.mutations) {
  7488. if (mutation.key.isEqual(document.key)) {
  7489. mutatedFields = mutationApplyToLocalView(mutation, document, mutatedFields, this.localWriteTime);
  7490. }
  7491. }
  7492. return mutatedFields;
  7493. }
  7494. /**
  7495. * Computes the local view for all provided documents given the mutations in
  7496. * this batch. Returns a `DocumentKey` to `Mutation` map which can be used to
  7497. * replace all the mutation applications.
  7498. */
  7499. applyToLocalDocumentSet(documentMap, documentsWithoutRemoteVersion) {
  7500. // TODO(mrschmidt): This implementation is O(n^2). If we apply the mutations
  7501. // directly (as done in `applyToLocalView()`), we can reduce the complexity
  7502. // to O(n).
  7503. const overlays = newMutationMap();
  7504. this.mutations.forEach(m => {
  7505. const overlayedDocument = documentMap.get(m.key);
  7506. // TODO(mutabledocuments): This method should take a MutableDocumentMap
  7507. // and we should remove this cast.
  7508. const mutableDocument = overlayedDocument.overlayedDocument;
  7509. let mutatedFields = this.applyToLocalView(mutableDocument, overlayedDocument.mutatedFields);
  7510. // Set mutatedFields to null if the document is only from local mutations.
  7511. // This creates a Set or Delete mutation, instead of trying to create a
  7512. // patch mutation as the overlay.
  7513. mutatedFields = documentsWithoutRemoteVersion.has(m.key)
  7514. ? null
  7515. : mutatedFields;
  7516. const overlay = calculateOverlayMutation(mutableDocument, mutatedFields);
  7517. if (overlay !== null) {
  7518. overlays.set(m.key, overlay);
  7519. }
  7520. if (!mutableDocument.isValidDocument()) {
  7521. mutableDocument.convertToNoDocument(SnapshotVersion.min());
  7522. }
  7523. });
  7524. return overlays;
  7525. }
  7526. keys() {
  7527. return this.mutations.reduce((keys, m) => keys.add(m.key), documentKeySet());
  7528. }
  7529. isEqual(other) {
  7530. return (this.batchId === other.batchId &&
  7531. arrayEquals(this.mutations, other.mutations, (l, r) => mutationEquals(l, r)) &&
  7532. arrayEquals(this.baseMutations, other.baseMutations, (l, r) => mutationEquals(l, r)));
  7533. }
  7534. }
  7535. /** The result of applying a mutation batch to the backend. */
  7536. class MutationBatchResult {
  7537. constructor(batch, commitVersion, mutationResults,
  7538. /**
  7539. * A pre-computed mapping from each mutated document to the resulting
  7540. * version.
  7541. */
  7542. docVersions) {
  7543. this.batch = batch;
  7544. this.commitVersion = commitVersion;
  7545. this.mutationResults = mutationResults;
  7546. this.docVersions = docVersions;
  7547. }
  7548. /**
  7549. * Creates a new MutationBatchResult for the given batch and results. There
  7550. * must be one result for each mutation in the batch. This static factory
  7551. * caches a document=&gt;version mapping (docVersions).
  7552. */
  7553. static from(batch, commitVersion, results) {
  7554. hardAssert(batch.mutations.length === results.length);
  7555. let versionMap = documentVersionMap();
  7556. const mutations = batch.mutations;
  7557. for (let i = 0; i < mutations.length; i++) {
  7558. versionMap = versionMap.insert(mutations[i].key, results[i].version);
  7559. }
  7560. return new MutationBatchResult(batch, commitVersion, results, versionMap);
  7561. }
  7562. }
  7563. /**
  7564. * @license
  7565. * Copyright 2022 Google LLC
  7566. *
  7567. * Licensed under the Apache License, Version 2.0 (the "License");
  7568. * you may not use this file except in compliance with the License.
  7569. * You may obtain a copy of the License at
  7570. *
  7571. * http://www.apache.org/licenses/LICENSE-2.0
  7572. *
  7573. * Unless required by applicable law or agreed to in writing, software
  7574. * distributed under the License is distributed on an "AS IS" BASIS,
  7575. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7576. * See the License for the specific language governing permissions and
  7577. * limitations under the License.
  7578. */
  7579. /**
  7580. * Representation of an overlay computed by Firestore.
  7581. *
  7582. * Holds information about a mutation and the largest batch id in Firestore when
  7583. * the mutation was created.
  7584. */
  7585. class Overlay {
  7586. constructor(largestBatchId, mutation) {
  7587. this.largestBatchId = largestBatchId;
  7588. this.mutation = mutation;
  7589. }
  7590. getKey() {
  7591. return this.mutation.key;
  7592. }
  7593. isEqual(other) {
  7594. return other !== null && this.mutation === other.mutation;
  7595. }
  7596. toString() {
  7597. return `Overlay{
  7598. largestBatchId: ${this.largestBatchId},
  7599. mutation: ${this.mutation.toString()}
  7600. }`;
  7601. }
  7602. }
  7603. /**
  7604. * @license
  7605. * Copyright 2017 Google LLC
  7606. *
  7607. * Licensed under the Apache License, Version 2.0 (the "License");
  7608. * you may not use this file except in compliance with the License.
  7609. * You may obtain a copy of the License at
  7610. *
  7611. * http://www.apache.org/licenses/LICENSE-2.0
  7612. *
  7613. * Unless required by applicable law or agreed to in writing, software
  7614. * distributed under the License is distributed on an "AS IS" BASIS,
  7615. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7616. * See the License for the specific language governing permissions and
  7617. * limitations under the License.
  7618. */
  7619. class ExistenceFilter {
  7620. constructor(count, unchangedNames) {
  7621. this.count = count;
  7622. this.unchangedNames = unchangedNames;
  7623. }
  7624. }
  7625. /**
  7626. * @license
  7627. * Copyright 2017 Google LLC
  7628. *
  7629. * Licensed under the Apache License, Version 2.0 (the "License");
  7630. * you may not use this file except in compliance with the License.
  7631. * You may obtain a copy of the License at
  7632. *
  7633. * http://www.apache.org/licenses/LICENSE-2.0
  7634. *
  7635. * Unless required by applicable law or agreed to in writing, software
  7636. * distributed under the License is distributed on an "AS IS" BASIS,
  7637. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7638. * See the License for the specific language governing permissions and
  7639. * limitations under the License.
  7640. */
  7641. /**
  7642. * Error Codes describing the different ways GRPC can fail. These are copied
  7643. * directly from GRPC's sources here:
  7644. *
  7645. * https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h
  7646. *
  7647. * Important! The names of these identifiers matter because the string forms
  7648. * are used for reverse lookups from the webchannel stream. Do NOT change the
  7649. * names of these identifiers or change this into a const enum.
  7650. */
  7651. var RpcCode;
  7652. (function (RpcCode) {
  7653. RpcCode[RpcCode["OK"] = 0] = "OK";
  7654. RpcCode[RpcCode["CANCELLED"] = 1] = "CANCELLED";
  7655. RpcCode[RpcCode["UNKNOWN"] = 2] = "UNKNOWN";
  7656. RpcCode[RpcCode["INVALID_ARGUMENT"] = 3] = "INVALID_ARGUMENT";
  7657. RpcCode[RpcCode["DEADLINE_EXCEEDED"] = 4] = "DEADLINE_EXCEEDED";
  7658. RpcCode[RpcCode["NOT_FOUND"] = 5] = "NOT_FOUND";
  7659. RpcCode[RpcCode["ALREADY_EXISTS"] = 6] = "ALREADY_EXISTS";
  7660. RpcCode[RpcCode["PERMISSION_DENIED"] = 7] = "PERMISSION_DENIED";
  7661. RpcCode[RpcCode["UNAUTHENTICATED"] = 16] = "UNAUTHENTICATED";
  7662. RpcCode[RpcCode["RESOURCE_EXHAUSTED"] = 8] = "RESOURCE_EXHAUSTED";
  7663. RpcCode[RpcCode["FAILED_PRECONDITION"] = 9] = "FAILED_PRECONDITION";
  7664. RpcCode[RpcCode["ABORTED"] = 10] = "ABORTED";
  7665. RpcCode[RpcCode["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE";
  7666. RpcCode[RpcCode["UNIMPLEMENTED"] = 12] = "UNIMPLEMENTED";
  7667. RpcCode[RpcCode["INTERNAL"] = 13] = "INTERNAL";
  7668. RpcCode[RpcCode["UNAVAILABLE"] = 14] = "UNAVAILABLE";
  7669. RpcCode[RpcCode["DATA_LOSS"] = 15] = "DATA_LOSS";
  7670. })(RpcCode || (RpcCode = {}));
  7671. /**
  7672. * Determines whether an error code represents a permanent error when received
  7673. * in response to a non-write operation.
  7674. *
  7675. * See isPermanentWriteError for classifying write errors.
  7676. */
  7677. function isPermanentError(code) {
  7678. switch (code) {
  7679. case Code.OK:
  7680. return fail();
  7681. case Code.CANCELLED:
  7682. case Code.UNKNOWN:
  7683. case Code.DEADLINE_EXCEEDED:
  7684. case Code.RESOURCE_EXHAUSTED:
  7685. case Code.INTERNAL:
  7686. case Code.UNAVAILABLE:
  7687. // Unauthenticated means something went wrong with our token and we need
  7688. // to retry with new credentials which will happen automatically.
  7689. case Code.UNAUTHENTICATED:
  7690. return false;
  7691. case Code.INVALID_ARGUMENT:
  7692. case Code.NOT_FOUND:
  7693. case Code.ALREADY_EXISTS:
  7694. case Code.PERMISSION_DENIED:
  7695. case Code.FAILED_PRECONDITION:
  7696. // Aborted might be retried in some scenarios, but that is dependant on
  7697. // the context and should handled individually by the calling code.
  7698. // See https://cloud.google.com/apis/design/errors.
  7699. case Code.ABORTED:
  7700. case Code.OUT_OF_RANGE:
  7701. case Code.UNIMPLEMENTED:
  7702. case Code.DATA_LOSS:
  7703. return true;
  7704. default:
  7705. return fail();
  7706. }
  7707. }
  7708. /**
  7709. * Determines whether an error code represents a permanent error when received
  7710. * in response to a write operation.
  7711. *
  7712. * Write operations must be handled specially because as of b/119437764, ABORTED
  7713. * errors on the write stream should be retried too (even though ABORTED errors
  7714. * are not generally retryable).
  7715. *
  7716. * Note that during the initial handshake on the write stream an ABORTED error
  7717. * signals that we should discard our stream token (i.e. it is permanent). This
  7718. * means a handshake error should be classified with isPermanentError, above.
  7719. */
  7720. function isPermanentWriteError(code) {
  7721. return isPermanentError(code) && code !== Code.ABORTED;
  7722. }
  7723. /**
  7724. * Maps an error Code from GRPC status code number, like 0, 1, or 14. These
  7725. * are not the same as HTTP status codes.
  7726. *
  7727. * @returns The Code equivalent to the given GRPC status code. Fails if there
  7728. * is no match.
  7729. */
  7730. function mapCodeFromRpcCode(code) {
  7731. if (code === undefined) {
  7732. // This shouldn't normally happen, but in certain error cases (like trying
  7733. // to send invalid proto messages) we may get an error with no GRPC code.
  7734. logError('GRPC error has no .code');
  7735. return Code.UNKNOWN;
  7736. }
  7737. switch (code) {
  7738. case RpcCode.OK:
  7739. return Code.OK;
  7740. case RpcCode.CANCELLED:
  7741. return Code.CANCELLED;
  7742. case RpcCode.UNKNOWN:
  7743. return Code.UNKNOWN;
  7744. case RpcCode.DEADLINE_EXCEEDED:
  7745. return Code.DEADLINE_EXCEEDED;
  7746. case RpcCode.RESOURCE_EXHAUSTED:
  7747. return Code.RESOURCE_EXHAUSTED;
  7748. case RpcCode.INTERNAL:
  7749. return Code.INTERNAL;
  7750. case RpcCode.UNAVAILABLE:
  7751. return Code.UNAVAILABLE;
  7752. case RpcCode.UNAUTHENTICATED:
  7753. return Code.UNAUTHENTICATED;
  7754. case RpcCode.INVALID_ARGUMENT:
  7755. return Code.INVALID_ARGUMENT;
  7756. case RpcCode.NOT_FOUND:
  7757. return Code.NOT_FOUND;
  7758. case RpcCode.ALREADY_EXISTS:
  7759. return Code.ALREADY_EXISTS;
  7760. case RpcCode.PERMISSION_DENIED:
  7761. return Code.PERMISSION_DENIED;
  7762. case RpcCode.FAILED_PRECONDITION:
  7763. return Code.FAILED_PRECONDITION;
  7764. case RpcCode.ABORTED:
  7765. return Code.ABORTED;
  7766. case RpcCode.OUT_OF_RANGE:
  7767. return Code.OUT_OF_RANGE;
  7768. case RpcCode.UNIMPLEMENTED:
  7769. return Code.UNIMPLEMENTED;
  7770. case RpcCode.DATA_LOSS:
  7771. return Code.DATA_LOSS;
  7772. default:
  7773. return fail();
  7774. }
  7775. }
  7776. /**
  7777. * @license
  7778. * Copyright 2023 Google LLC
  7779. *
  7780. * Licensed under the Apache License, Version 2.0 (the "License");
  7781. * you may not use this file except in compliance with the License.
  7782. * You may obtain a copy of the License at
  7783. *
  7784. * http://www.apache.org/licenses/LICENSE-2.0
  7785. *
  7786. * Unless required by applicable law or agreed to in writing, software
  7787. * distributed under the License is distributed on an "AS IS" BASIS,
  7788. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7789. * See the License for the specific language governing permissions and
  7790. * limitations under the License.
  7791. */
  7792. /**
  7793. * An error encountered while decoding base64 string.
  7794. */
  7795. class Base64DecodeError extends Error {
  7796. constructor() {
  7797. super(...arguments);
  7798. this.name = 'Base64DecodeError';
  7799. }
  7800. }
  7801. /**
  7802. * @license
  7803. * Copyright 2023 Google LLC
  7804. *
  7805. * Licensed under the Apache License, Version 2.0 (the "License");
  7806. * you may not use this file except in compliance with the License.
  7807. * You may obtain a copy of the License at
  7808. *
  7809. * http://www.apache.org/licenses/LICENSE-2.0
  7810. *
  7811. * Unless required by applicable law or agreed to in writing, software
  7812. * distributed under the License is distributed on an "AS IS" BASIS,
  7813. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7814. * See the License for the specific language governing permissions and
  7815. * limitations under the License.
  7816. */
  7817. /**
  7818. * Manages "testing hooks", hooks into the internals of the SDK to verify
  7819. * internal state and events during integration tests. Do not use this class
  7820. * except for testing purposes.
  7821. *
  7822. * There are two ways to retrieve the global singleton instance of this class:
  7823. * 1. The `instance` property, which returns null if the global singleton
  7824. * instance has not been created. Use this property if the caller should
  7825. * "do nothing" if there are no testing hooks registered, such as when
  7826. * delivering an event to notify registered callbacks.
  7827. * 2. The `getOrCreateInstance()` method, which creates the global singleton
  7828. * instance if it has not been created. Use this method if the instance is
  7829. * needed to, for example, register a callback.
  7830. *
  7831. * @internal
  7832. */
  7833. class TestingHooks {
  7834. constructor() {
  7835. this.onExistenceFilterMismatchCallbacks = new Map();
  7836. }
  7837. /**
  7838. * Returns the singleton instance of this class, or null if it has not been
  7839. * initialized.
  7840. */
  7841. static get instance() {
  7842. return gTestingHooksSingletonInstance;
  7843. }
  7844. /**
  7845. * Returns the singleton instance of this class, creating it if is has never
  7846. * been created before.
  7847. */
  7848. static getOrCreateInstance() {
  7849. if (gTestingHooksSingletonInstance === null) {
  7850. gTestingHooksSingletonInstance = new TestingHooks();
  7851. }
  7852. return gTestingHooksSingletonInstance;
  7853. }
  7854. /**
  7855. * Registers a callback to be notified when an existence filter mismatch
  7856. * occurs in the Watch listen stream.
  7857. *
  7858. * The relative order in which callbacks are notified is unspecified; do not
  7859. * rely on any particular ordering. If a given callback is registered multiple
  7860. * times then it will be notified multiple times, once per registration.
  7861. *
  7862. * @param callback the callback to invoke upon existence filter mismatch.
  7863. *
  7864. * @return a function that, when called, unregisters the given callback; only
  7865. * the first invocation of the returned function does anything; all subsequent
  7866. * invocations do nothing.
  7867. */
  7868. onExistenceFilterMismatch(callback) {
  7869. const key = Symbol();
  7870. this.onExistenceFilterMismatchCallbacks.set(key, callback);
  7871. return () => this.onExistenceFilterMismatchCallbacks.delete(key);
  7872. }
  7873. /**
  7874. * Invokes all currently-registered `onExistenceFilterMismatch` callbacks.
  7875. * @param info Information about the existence filter mismatch.
  7876. */
  7877. notifyOnExistenceFilterMismatch(info) {
  7878. this.onExistenceFilterMismatchCallbacks.forEach(callback => callback(info));
  7879. }
  7880. }
  7881. /** The global singleton instance of `TestingHooks`. */
  7882. let gTestingHooksSingletonInstance = null;
  7883. /**
  7884. * @license
  7885. * Copyright 2023 Google LLC
  7886. *
  7887. * Licensed under the Apache License, Version 2.0 (the "License");
  7888. * you may not use this file except in compliance with the License.
  7889. * You may obtain a copy of the License at
  7890. *
  7891. * http://www.apache.org/licenses/LICENSE-2.0
  7892. *
  7893. * Unless required by applicable law or agreed to in writing, software
  7894. * distributed under the License is distributed on an "AS IS" BASIS,
  7895. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7896. * See the License for the specific language governing permissions and
  7897. * limitations under the License.
  7898. */
  7899. /**
  7900. * An instance of the Platform's 'TextEncoder' implementation.
  7901. */
  7902. function newTextEncoder() {
  7903. return new util$1.TextEncoder();
  7904. }
  7905. /**
  7906. * An instance of the Platform's 'TextDecoder' implementation.
  7907. */
  7908. function newTextDecoder() {
  7909. return new util$1.TextDecoder('utf-8');
  7910. }
  7911. /**
  7912. * @license
  7913. * Copyright 2022 Google LLC
  7914. *
  7915. * Licensed under the Apache License, Version 2.0 (the "License");
  7916. * you may not use this file except in compliance with the License.
  7917. * You may obtain a copy of the License at
  7918. *
  7919. * http://www.apache.org/licenses/LICENSE-2.0
  7920. *
  7921. * Unless required by applicable law or agreed to in writing, software
  7922. * distributed under the License is distributed on an "AS IS" BASIS,
  7923. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7924. * See the License for the specific language governing permissions and
  7925. * limitations under the License.
  7926. */
  7927. const MAX_64_BIT_UNSIGNED_INTEGER = new webchannelWrapper.Integer([0xffffffff, 0xffffffff], 0);
  7928. // Hash a string using md5 hashing algorithm.
  7929. function getMd5HashValue(value) {
  7930. const encodedValue = newTextEncoder().encode(value);
  7931. const md5 = new webchannelWrapper.Md5();
  7932. md5.update(encodedValue);
  7933. return new Uint8Array(md5.digest());
  7934. }
  7935. // Interpret the 16 bytes array as two 64-bit unsigned integers, encoded using
  7936. // 2’s complement using little endian.
  7937. function get64BitUints(Bytes) {
  7938. const dataView = new DataView(Bytes.buffer);
  7939. const chunk1 = dataView.getUint32(0, /* littleEndian= */ true);
  7940. const chunk2 = dataView.getUint32(4, /* littleEndian= */ true);
  7941. const chunk3 = dataView.getUint32(8, /* littleEndian= */ true);
  7942. const chunk4 = dataView.getUint32(12, /* littleEndian= */ true);
  7943. const integer1 = new webchannelWrapper.Integer([chunk1, chunk2], 0);
  7944. const integer2 = new webchannelWrapper.Integer([chunk3, chunk4], 0);
  7945. return [integer1, integer2];
  7946. }
  7947. class BloomFilter {
  7948. constructor(bitmap, padding, hashCount) {
  7949. this.bitmap = bitmap;
  7950. this.padding = padding;
  7951. this.hashCount = hashCount;
  7952. if (padding < 0 || padding >= 8) {
  7953. throw new BloomFilterError(`Invalid padding: ${padding}`);
  7954. }
  7955. if (hashCount < 0) {
  7956. throw new BloomFilterError(`Invalid hash count: ${hashCount}`);
  7957. }
  7958. if (bitmap.length > 0 && this.hashCount === 0) {
  7959. // Only empty bloom filter can have 0 hash count.
  7960. throw new BloomFilterError(`Invalid hash count: ${hashCount}`);
  7961. }
  7962. if (bitmap.length === 0 && padding !== 0) {
  7963. // Empty bloom filter should have 0 padding.
  7964. throw new BloomFilterError(`Invalid padding when bitmap length is 0: ${padding}`);
  7965. }
  7966. this.bitCount = bitmap.length * 8 - padding;
  7967. // Set the bit count in Integer to avoid repetition in mightContain().
  7968. this.bitCountInInteger = webchannelWrapper.Integer.fromNumber(this.bitCount);
  7969. }
  7970. // Calculate the ith hash value based on the hashed 64bit integers,
  7971. // and calculate its corresponding bit index in the bitmap to be checked.
  7972. getBitIndex(num1, num2, hashIndex) {
  7973. // Calculate hashed value h(i) = h1 + (i * h2).
  7974. let hashValue = num1.add(num2.multiply(webchannelWrapper.Integer.fromNumber(hashIndex)));
  7975. // Wrap if hash value overflow 64bit.
  7976. if (hashValue.compare(MAX_64_BIT_UNSIGNED_INTEGER) === 1) {
  7977. hashValue = new webchannelWrapper.Integer([hashValue.getBits(0), hashValue.getBits(1)], 0);
  7978. }
  7979. return hashValue.modulo(this.bitCountInInteger).toNumber();
  7980. }
  7981. // Return whether the bit on the given index in the bitmap is set to 1.
  7982. isBitSet(index) {
  7983. // To retrieve bit n, calculate: (bitmap[n / 8] & (0x01 << (n % 8))).
  7984. const byte = this.bitmap[Math.floor(index / 8)];
  7985. const offset = index % 8;
  7986. return (byte & (0x01 << offset)) !== 0;
  7987. }
  7988. mightContain(value) {
  7989. // Empty bitmap should always return false on membership check.
  7990. if (this.bitCount === 0) {
  7991. return false;
  7992. }
  7993. const md5HashedValue = getMd5HashValue(value);
  7994. const [hash1, hash2] = get64BitUints(md5HashedValue);
  7995. for (let i = 0; i < this.hashCount; i++) {
  7996. const index = this.getBitIndex(hash1, hash2, i);
  7997. if (!this.isBitSet(index)) {
  7998. return false;
  7999. }
  8000. }
  8001. return true;
  8002. }
  8003. /** Create bloom filter for testing purposes only. */
  8004. static create(bitCount, hashCount, contains) {
  8005. const padding = bitCount % 8 === 0 ? 0 : 8 - (bitCount % 8);
  8006. const bitmap = new Uint8Array(Math.ceil(bitCount / 8));
  8007. const bloomFilter = new BloomFilter(bitmap, padding, hashCount);
  8008. contains.forEach(item => bloomFilter.insert(item));
  8009. return bloomFilter;
  8010. }
  8011. insert(value) {
  8012. if (this.bitCount === 0) {
  8013. return;
  8014. }
  8015. const md5HashedValue = getMd5HashValue(value);
  8016. const [hash1, hash2] = get64BitUints(md5HashedValue);
  8017. for (let i = 0; i < this.hashCount; i++) {
  8018. const index = this.getBitIndex(hash1, hash2, i);
  8019. this.setBit(index);
  8020. }
  8021. }
  8022. setBit(index) {
  8023. const indexOfByte = Math.floor(index / 8);
  8024. const offset = index % 8;
  8025. this.bitmap[indexOfByte] |= 0x01 << offset;
  8026. }
  8027. }
  8028. class BloomFilterError extends Error {
  8029. constructor() {
  8030. super(...arguments);
  8031. this.name = 'BloomFilterError';
  8032. }
  8033. }
  8034. /**
  8035. * @license
  8036. * Copyright 2017 Google LLC
  8037. *
  8038. * Licensed under the Apache License, Version 2.0 (the "License");
  8039. * you may not use this file except in compliance with the License.
  8040. * You may obtain a copy of the License at
  8041. *
  8042. * http://www.apache.org/licenses/LICENSE-2.0
  8043. *
  8044. * Unless required by applicable law or agreed to in writing, software
  8045. * distributed under the License is distributed on an "AS IS" BASIS,
  8046. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8047. * See the License for the specific language governing permissions and
  8048. * limitations under the License.
  8049. */
  8050. /**
  8051. * An event from the RemoteStore. It is split into targetChanges (changes to the
  8052. * state or the set of documents in our watched targets) and documentUpdates
  8053. * (changes to the actual documents).
  8054. */
  8055. class RemoteEvent {
  8056. constructor(
  8057. /**
  8058. * The snapshot version this event brings us up to, or MIN if not set.
  8059. */
  8060. snapshotVersion,
  8061. /**
  8062. * A map from target to changes to the target. See TargetChange.
  8063. */
  8064. targetChanges,
  8065. /**
  8066. * A map of targets that is known to be inconsistent, and the purpose for
  8067. * re-listening. Listens for these targets should be re-established without
  8068. * resume tokens.
  8069. */
  8070. targetMismatches,
  8071. /**
  8072. * A set of which documents have changed or been deleted, along with the
  8073. * doc's new values (if not deleted).
  8074. */
  8075. documentUpdates,
  8076. /**
  8077. * A set of which document updates are due only to limbo resolution targets.
  8078. */
  8079. resolvedLimboDocuments) {
  8080. this.snapshotVersion = snapshotVersion;
  8081. this.targetChanges = targetChanges;
  8082. this.targetMismatches = targetMismatches;
  8083. this.documentUpdates = documentUpdates;
  8084. this.resolvedLimboDocuments = resolvedLimboDocuments;
  8085. }
  8086. /**
  8087. * HACK: Views require RemoteEvents in order to determine whether the view is
  8088. * CURRENT, but secondary tabs don't receive remote events. So this method is
  8089. * used to create a synthesized RemoteEvent that can be used to apply a
  8090. * CURRENT status change to a View, for queries executed in a different tab.
  8091. */
  8092. // PORTING NOTE: Multi-tab only
  8093. static createSynthesizedRemoteEventForCurrentChange(targetId, current, resumeToken) {
  8094. const targetChanges = new Map();
  8095. targetChanges.set(targetId, TargetChange.createSynthesizedTargetChangeForCurrentChange(targetId, current, resumeToken));
  8096. return new RemoteEvent(SnapshotVersion.min(), targetChanges, new SortedMap(primitiveComparator), mutableDocumentMap(), documentKeySet());
  8097. }
  8098. }
  8099. /**
  8100. * A TargetChange specifies the set of changes for a specific target as part of
  8101. * a RemoteEvent. These changes track which documents are added, modified or
  8102. * removed, as well as the target's resume token and whether the target is
  8103. * marked CURRENT.
  8104. * The actual changes *to* documents are not part of the TargetChange since
  8105. * documents may be part of multiple targets.
  8106. */
  8107. class TargetChange {
  8108. constructor(
  8109. /**
  8110. * An opaque, server-assigned token that allows watching a query to be resumed
  8111. * after disconnecting without retransmitting all the data that matches the
  8112. * query. The resume token essentially identifies a point in time from which
  8113. * the server should resume sending results.
  8114. */
  8115. resumeToken,
  8116. /**
  8117. * The "current" (synced) status of this target. Note that "current"
  8118. * has special meaning in the RPC protocol that implies that a target is
  8119. * both up-to-date and consistent with the rest of the watch stream.
  8120. */
  8121. current,
  8122. /**
  8123. * The set of documents that were newly assigned to this target as part of
  8124. * this remote event.
  8125. */
  8126. addedDocuments,
  8127. /**
  8128. * The set of documents that were already assigned to this target but received
  8129. * an update during this remote event.
  8130. */
  8131. modifiedDocuments,
  8132. /**
  8133. * The set of documents that were removed from this target as part of this
  8134. * remote event.
  8135. */
  8136. removedDocuments) {
  8137. this.resumeToken = resumeToken;
  8138. this.current = current;
  8139. this.addedDocuments = addedDocuments;
  8140. this.modifiedDocuments = modifiedDocuments;
  8141. this.removedDocuments = removedDocuments;
  8142. }
  8143. /**
  8144. * This method is used to create a synthesized TargetChanges that can be used to
  8145. * apply a CURRENT status change to a View (for queries executed in a different
  8146. * tab) or for new queries (to raise snapshots with correct CURRENT status).
  8147. */
  8148. static createSynthesizedTargetChangeForCurrentChange(targetId, current, resumeToken) {
  8149. return new TargetChange(resumeToken, current, documentKeySet(), documentKeySet(), documentKeySet());
  8150. }
  8151. }
  8152. /**
  8153. * @license
  8154. * Copyright 2017 Google LLC
  8155. *
  8156. * Licensed under the Apache License, Version 2.0 (the "License");
  8157. * you may not use this file except in compliance with the License.
  8158. * You may obtain a copy of the License at
  8159. *
  8160. * http://www.apache.org/licenses/LICENSE-2.0
  8161. *
  8162. * Unless required by applicable law or agreed to in writing, software
  8163. * distributed under the License is distributed on an "AS IS" BASIS,
  8164. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8165. * See the License for the specific language governing permissions and
  8166. * limitations under the License.
  8167. */
  8168. /**
  8169. * Represents a changed document and a list of target ids to which this change
  8170. * applies.
  8171. *
  8172. * If document has been deleted NoDocument will be provided.
  8173. */
  8174. class DocumentWatchChange {
  8175. constructor(
  8176. /** The new document applies to all of these targets. */
  8177. updatedTargetIds,
  8178. /** The new document is removed from all of these targets. */
  8179. removedTargetIds,
  8180. /** The key of the document for this change. */
  8181. key,
  8182. /**
  8183. * The new document or NoDocument if it was deleted. Is null if the
  8184. * document went out of view without the server sending a new document.
  8185. */
  8186. newDoc) {
  8187. this.updatedTargetIds = updatedTargetIds;
  8188. this.removedTargetIds = removedTargetIds;
  8189. this.key = key;
  8190. this.newDoc = newDoc;
  8191. }
  8192. }
  8193. class ExistenceFilterChange {
  8194. constructor(targetId, existenceFilter) {
  8195. this.targetId = targetId;
  8196. this.existenceFilter = existenceFilter;
  8197. }
  8198. }
  8199. class WatchTargetChange {
  8200. constructor(
  8201. /** What kind of change occurred to the watch target. */
  8202. state,
  8203. /** The target IDs that were added/removed/set. */
  8204. targetIds,
  8205. /**
  8206. * An opaque, server-assigned token that allows watching a target to be
  8207. * resumed after disconnecting without retransmitting all the data that
  8208. * matches the target. The resume token essentially identifies a point in
  8209. * time from which the server should resume sending results.
  8210. */
  8211. resumeToken = ByteString.EMPTY_BYTE_STRING,
  8212. /** An RPC error indicating why the watch failed. */
  8213. cause = null) {
  8214. this.state = state;
  8215. this.targetIds = targetIds;
  8216. this.resumeToken = resumeToken;
  8217. this.cause = cause;
  8218. }
  8219. }
  8220. /** Tracks the internal state of a Watch target. */
  8221. class TargetState {
  8222. constructor() {
  8223. /**
  8224. * The number of pending responses (adds or removes) that we are waiting on.
  8225. * We only consider targets active that have no pending responses.
  8226. */
  8227. this.pendingResponses = 0;
  8228. /**
  8229. * Keeps track of the document changes since the last raised snapshot.
  8230. *
  8231. * These changes are continuously updated as we receive document updates and
  8232. * always reflect the current set of changes against the last issued snapshot.
  8233. */
  8234. this.documentChanges = snapshotChangesMap();
  8235. /** See public getters for explanations of these fields. */
  8236. this._resumeToken = ByteString.EMPTY_BYTE_STRING;
  8237. this._current = false;
  8238. /**
  8239. * Whether this target state should be included in the next snapshot. We
  8240. * initialize to true so that newly-added targets are included in the next
  8241. * RemoteEvent.
  8242. */
  8243. this._hasPendingChanges = true;
  8244. }
  8245. /**
  8246. * Whether this target has been marked 'current'.
  8247. *
  8248. * 'Current' has special meaning in the RPC protocol: It implies that the
  8249. * Watch backend has sent us all changes up to the point at which the target
  8250. * was added and that the target is consistent with the rest of the watch
  8251. * stream.
  8252. */
  8253. get current() {
  8254. return this._current;
  8255. }
  8256. /** The last resume token sent to us for this target. */
  8257. get resumeToken() {
  8258. return this._resumeToken;
  8259. }
  8260. /** Whether this target has pending target adds or target removes. */
  8261. get isPending() {
  8262. return this.pendingResponses !== 0;
  8263. }
  8264. /** Whether we have modified any state that should trigger a snapshot. */
  8265. get hasPendingChanges() {
  8266. return this._hasPendingChanges;
  8267. }
  8268. /**
  8269. * Applies the resume token to the TargetChange, but only when it has a new
  8270. * value. Empty resumeTokens are discarded.
  8271. */
  8272. updateResumeToken(resumeToken) {
  8273. if (resumeToken.approximateByteSize() > 0) {
  8274. this._hasPendingChanges = true;
  8275. this._resumeToken = resumeToken;
  8276. }
  8277. }
  8278. /**
  8279. * Creates a target change from the current set of changes.
  8280. *
  8281. * To reset the document changes after raising this snapshot, call
  8282. * `clearPendingChanges()`.
  8283. */
  8284. toTargetChange() {
  8285. let addedDocuments = documentKeySet();
  8286. let modifiedDocuments = documentKeySet();
  8287. let removedDocuments = documentKeySet();
  8288. this.documentChanges.forEach((key, changeType) => {
  8289. switch (changeType) {
  8290. case 0 /* ChangeType.Added */:
  8291. addedDocuments = addedDocuments.add(key);
  8292. break;
  8293. case 2 /* ChangeType.Modified */:
  8294. modifiedDocuments = modifiedDocuments.add(key);
  8295. break;
  8296. case 1 /* ChangeType.Removed */:
  8297. removedDocuments = removedDocuments.add(key);
  8298. break;
  8299. default:
  8300. fail();
  8301. }
  8302. });
  8303. return new TargetChange(this._resumeToken, this._current, addedDocuments, modifiedDocuments, removedDocuments);
  8304. }
  8305. /**
  8306. * Resets the document changes and sets `hasPendingChanges` to false.
  8307. */
  8308. clearPendingChanges() {
  8309. this._hasPendingChanges = false;
  8310. this.documentChanges = snapshotChangesMap();
  8311. }
  8312. addDocumentChange(key, changeType) {
  8313. this._hasPendingChanges = true;
  8314. this.documentChanges = this.documentChanges.insert(key, changeType);
  8315. }
  8316. removeDocumentChange(key) {
  8317. this._hasPendingChanges = true;
  8318. this.documentChanges = this.documentChanges.remove(key);
  8319. }
  8320. recordPendingTargetRequest() {
  8321. this.pendingResponses += 1;
  8322. }
  8323. recordTargetResponse() {
  8324. this.pendingResponses -= 1;
  8325. }
  8326. markCurrent() {
  8327. this._hasPendingChanges = true;
  8328. this._current = true;
  8329. }
  8330. }
  8331. const LOG_TAG$g = 'WatchChangeAggregator';
  8332. /**
  8333. * A helper class to accumulate watch changes into a RemoteEvent.
  8334. */
  8335. class WatchChangeAggregator {
  8336. constructor(metadataProvider) {
  8337. this.metadataProvider = metadataProvider;
  8338. /** The internal state of all tracked targets. */
  8339. this.targetStates = new Map();
  8340. /** Keeps track of the documents to update since the last raised snapshot. */
  8341. this.pendingDocumentUpdates = mutableDocumentMap();
  8342. /** A mapping of document keys to their set of target IDs. */
  8343. this.pendingDocumentTargetMapping = documentTargetMap();
  8344. /**
  8345. * A map of targets with existence filter mismatches. These targets are
  8346. * known to be inconsistent and their listens needs to be re-established by
  8347. * RemoteStore.
  8348. */
  8349. this.pendingTargetResets = new SortedMap(primitiveComparator);
  8350. }
  8351. /**
  8352. * Processes and adds the DocumentWatchChange to the current set of changes.
  8353. */
  8354. handleDocumentChange(docChange) {
  8355. for (const targetId of docChange.updatedTargetIds) {
  8356. if (docChange.newDoc && docChange.newDoc.isFoundDocument()) {
  8357. this.addDocumentToTarget(targetId, docChange.newDoc);
  8358. }
  8359. else {
  8360. this.removeDocumentFromTarget(targetId, docChange.key, docChange.newDoc);
  8361. }
  8362. }
  8363. for (const targetId of docChange.removedTargetIds) {
  8364. this.removeDocumentFromTarget(targetId, docChange.key, docChange.newDoc);
  8365. }
  8366. }
  8367. /** Processes and adds the WatchTargetChange to the current set of changes. */
  8368. handleTargetChange(targetChange) {
  8369. this.forEachTarget(targetChange, targetId => {
  8370. const targetState = this.ensureTargetState(targetId);
  8371. switch (targetChange.state) {
  8372. case 0 /* WatchTargetChangeState.NoChange */:
  8373. if (this.isActiveTarget(targetId)) {
  8374. targetState.updateResumeToken(targetChange.resumeToken);
  8375. }
  8376. break;
  8377. case 1 /* WatchTargetChangeState.Added */:
  8378. // We need to decrement the number of pending acks needed from watch
  8379. // for this targetId.
  8380. targetState.recordTargetResponse();
  8381. if (!targetState.isPending) {
  8382. // We have a freshly added target, so we need to reset any state
  8383. // that we had previously. This can happen e.g. when remove and add
  8384. // back a target for existence filter mismatches.
  8385. targetState.clearPendingChanges();
  8386. }
  8387. targetState.updateResumeToken(targetChange.resumeToken);
  8388. break;
  8389. case 2 /* WatchTargetChangeState.Removed */:
  8390. // We need to keep track of removed targets to we can post-filter and
  8391. // remove any target changes.
  8392. // We need to decrement the number of pending acks needed from watch
  8393. // for this targetId.
  8394. targetState.recordTargetResponse();
  8395. if (!targetState.isPending) {
  8396. this.removeTarget(targetId);
  8397. }
  8398. break;
  8399. case 3 /* WatchTargetChangeState.Current */:
  8400. if (this.isActiveTarget(targetId)) {
  8401. targetState.markCurrent();
  8402. targetState.updateResumeToken(targetChange.resumeToken);
  8403. }
  8404. break;
  8405. case 4 /* WatchTargetChangeState.Reset */:
  8406. if (this.isActiveTarget(targetId)) {
  8407. // Reset the target and synthesizes removes for all existing
  8408. // documents. The backend will re-add any documents that still
  8409. // match the target before it sends the next global snapshot.
  8410. this.resetTarget(targetId);
  8411. targetState.updateResumeToken(targetChange.resumeToken);
  8412. }
  8413. break;
  8414. default:
  8415. fail();
  8416. }
  8417. });
  8418. }
  8419. /**
  8420. * Iterates over all targetIds that the watch change applies to: either the
  8421. * targetIds explicitly listed in the change or the targetIds of all currently
  8422. * active targets.
  8423. */
  8424. forEachTarget(targetChange, fn) {
  8425. if (targetChange.targetIds.length > 0) {
  8426. targetChange.targetIds.forEach(fn);
  8427. }
  8428. else {
  8429. this.targetStates.forEach((_, targetId) => {
  8430. if (this.isActiveTarget(targetId)) {
  8431. fn(targetId);
  8432. }
  8433. });
  8434. }
  8435. }
  8436. /**
  8437. * Handles existence filters and synthesizes deletes for filter mismatches.
  8438. * Targets that are invalidated by filter mismatches are added to
  8439. * `pendingTargetResets`.
  8440. */
  8441. handleExistenceFilter(watchChange) {
  8442. var _a;
  8443. const targetId = watchChange.targetId;
  8444. const expectedCount = watchChange.existenceFilter.count;
  8445. const targetData = this.targetDataForActiveTarget(targetId);
  8446. if (targetData) {
  8447. const target = targetData.target;
  8448. if (targetIsDocumentTarget(target)) {
  8449. if (expectedCount === 0) {
  8450. // The existence filter told us the document does not exist. We deduce
  8451. // that this document does not exist and apply a deleted document to
  8452. // our updates. Without applying this deleted document there might be
  8453. // another query that will raise this document as part of a snapshot
  8454. // until it is resolved, essentially exposing inconsistency between
  8455. // queries.
  8456. const key = new DocumentKey(target.path);
  8457. this.removeDocumentFromTarget(targetId, key, MutableDocument.newNoDocument(key, SnapshotVersion.min()));
  8458. }
  8459. else {
  8460. hardAssert(expectedCount === 1);
  8461. }
  8462. }
  8463. else {
  8464. const currentSize = this.getCurrentDocumentCountForTarget(targetId);
  8465. // Existence filter mismatch. Mark the documents as being in limbo, and
  8466. // raise a snapshot with `isFromCache:true`.
  8467. if (currentSize !== expectedCount) {
  8468. // Apply bloom filter to identify and mark removed documents.
  8469. const status = this.applyBloomFilter(watchChange, currentSize);
  8470. if (status !== 0 /* BloomFilterApplicationStatus.Success */) {
  8471. // If bloom filter application fails, we reset the mapping and
  8472. // trigger re-run of the query.
  8473. this.resetTarget(targetId);
  8474. const purpose = status === 2 /* BloomFilterApplicationStatus.FalsePositive */
  8475. ? "TargetPurposeExistenceFilterMismatchBloom" /* TargetPurpose.ExistenceFilterMismatchBloom */
  8476. : "TargetPurposeExistenceFilterMismatch" /* TargetPurpose.ExistenceFilterMismatch */;
  8477. this.pendingTargetResets = this.pendingTargetResets.insert(targetId, purpose);
  8478. }
  8479. (_a = TestingHooks.instance) === null || _a === void 0 ? void 0 : _a.notifyOnExistenceFilterMismatch(createExistenceFilterMismatchInfoForTestingHooks(status, currentSize, watchChange.existenceFilter));
  8480. }
  8481. }
  8482. }
  8483. }
  8484. /**
  8485. * Apply bloom filter to remove the deleted documents, and return the
  8486. * application status.
  8487. */
  8488. applyBloomFilter(watchChange, currentCount) {
  8489. const { unchangedNames, count: expectedCount } = watchChange.existenceFilter;
  8490. if (!unchangedNames || !unchangedNames.bits) {
  8491. return 1 /* BloomFilterApplicationStatus.Skipped */;
  8492. }
  8493. const { bits: { bitmap = '', padding = 0 }, hashCount = 0 } = unchangedNames;
  8494. let normalizedBitmap;
  8495. try {
  8496. normalizedBitmap = normalizeByteString(bitmap).toUint8Array();
  8497. }
  8498. catch (err) {
  8499. if (err instanceof Base64DecodeError) {
  8500. logWarn('Decoding the base64 bloom filter in existence filter failed (' +
  8501. err.message +
  8502. '); ignoring the bloom filter and falling back to full re-query.');
  8503. return 1 /* BloomFilterApplicationStatus.Skipped */;
  8504. }
  8505. else {
  8506. throw err;
  8507. }
  8508. }
  8509. let bloomFilter;
  8510. try {
  8511. // BloomFilter throws error if the inputs are invalid.
  8512. bloomFilter = new BloomFilter(normalizedBitmap, padding, hashCount);
  8513. }
  8514. catch (err) {
  8515. if (err instanceof BloomFilterError) {
  8516. logWarn('BloomFilter error: ', err);
  8517. }
  8518. else {
  8519. logWarn('Applying bloom filter failed: ', err);
  8520. }
  8521. return 1 /* BloomFilterApplicationStatus.Skipped */;
  8522. }
  8523. if (bloomFilter.bitCount === 0) {
  8524. return 1 /* BloomFilterApplicationStatus.Skipped */;
  8525. }
  8526. const removedDocumentCount = this.filterRemovedDocuments(watchChange.targetId, bloomFilter);
  8527. if (expectedCount !== currentCount - removedDocumentCount) {
  8528. return 2 /* BloomFilterApplicationStatus.FalsePositive */;
  8529. }
  8530. return 0 /* BloomFilterApplicationStatus.Success */;
  8531. }
  8532. /**
  8533. * Filter out removed documents based on bloom filter membership result and
  8534. * return number of documents removed.
  8535. */
  8536. filterRemovedDocuments(targetId, bloomFilter) {
  8537. const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);
  8538. let removalCount = 0;
  8539. existingKeys.forEach(key => {
  8540. const databaseId = this.metadataProvider.getDatabaseId();
  8541. const documentPath = `projects/${databaseId.projectId}/databases/${databaseId.database}/documents/${key.path.canonicalString()}`;
  8542. if (!bloomFilter.mightContain(documentPath)) {
  8543. this.removeDocumentFromTarget(targetId, key, /*updatedDocument=*/ null);
  8544. removalCount++;
  8545. }
  8546. });
  8547. return removalCount;
  8548. }
  8549. /**
  8550. * Converts the currently accumulated state into a remote event at the
  8551. * provided snapshot version. Resets the accumulated changes before returning.
  8552. */
  8553. createRemoteEvent(snapshotVersion) {
  8554. const targetChanges = new Map();
  8555. this.targetStates.forEach((targetState, targetId) => {
  8556. const targetData = this.targetDataForActiveTarget(targetId);
  8557. if (targetData) {
  8558. if (targetState.current && targetIsDocumentTarget(targetData.target)) {
  8559. // Document queries for document that don't exist can produce an empty
  8560. // result set. To update our local cache, we synthesize a document
  8561. // delete if we have not previously received the document. This
  8562. // resolves the limbo state of the document, removing it from
  8563. // limboDocumentRefs.
  8564. //
  8565. // TODO(dimond): Ideally we would have an explicit lookup target
  8566. // instead resulting in an explicit delete message and we could
  8567. // remove this special logic.
  8568. const key = new DocumentKey(targetData.target.path);
  8569. if (this.pendingDocumentUpdates.get(key) === null &&
  8570. !this.targetContainsDocument(targetId, key)) {
  8571. this.removeDocumentFromTarget(targetId, key, MutableDocument.newNoDocument(key, snapshotVersion));
  8572. }
  8573. }
  8574. if (targetState.hasPendingChanges) {
  8575. targetChanges.set(targetId, targetState.toTargetChange());
  8576. targetState.clearPendingChanges();
  8577. }
  8578. }
  8579. });
  8580. let resolvedLimboDocuments = documentKeySet();
  8581. // We extract the set of limbo-only document updates as the GC logic
  8582. // special-cases documents that do not appear in the target cache.
  8583. //
  8584. // TODO(gsoltis): Expand on this comment once GC is available in the JS
  8585. // client.
  8586. this.pendingDocumentTargetMapping.forEach((key, targets) => {
  8587. let isOnlyLimboTarget = true;
  8588. targets.forEachWhile(targetId => {
  8589. const targetData = this.targetDataForActiveTarget(targetId);
  8590. if (targetData &&
  8591. targetData.purpose !== "TargetPurposeLimboResolution" /* TargetPurpose.LimboResolution */) {
  8592. isOnlyLimboTarget = false;
  8593. return false;
  8594. }
  8595. return true;
  8596. });
  8597. if (isOnlyLimboTarget) {
  8598. resolvedLimboDocuments = resolvedLimboDocuments.add(key);
  8599. }
  8600. });
  8601. this.pendingDocumentUpdates.forEach((_, doc) => doc.setReadTime(snapshotVersion));
  8602. const remoteEvent = new RemoteEvent(snapshotVersion, targetChanges, this.pendingTargetResets, this.pendingDocumentUpdates, resolvedLimboDocuments);
  8603. this.pendingDocumentUpdates = mutableDocumentMap();
  8604. this.pendingDocumentTargetMapping = documentTargetMap();
  8605. this.pendingTargetResets = new SortedMap(primitiveComparator);
  8606. return remoteEvent;
  8607. }
  8608. /**
  8609. * Adds the provided document to the internal list of document updates and
  8610. * its document key to the given target's mapping.
  8611. */
  8612. // Visible for testing.
  8613. addDocumentToTarget(targetId, document) {
  8614. if (!this.isActiveTarget(targetId)) {
  8615. return;
  8616. }
  8617. const changeType = this.targetContainsDocument(targetId, document.key)
  8618. ? 2 /* ChangeType.Modified */
  8619. : 0 /* ChangeType.Added */;
  8620. const targetState = this.ensureTargetState(targetId);
  8621. targetState.addDocumentChange(document.key, changeType);
  8622. this.pendingDocumentUpdates = this.pendingDocumentUpdates.insert(document.key, document);
  8623. this.pendingDocumentTargetMapping =
  8624. this.pendingDocumentTargetMapping.insert(document.key, this.ensureDocumentTargetMapping(document.key).add(targetId));
  8625. }
  8626. /**
  8627. * Removes the provided document from the target mapping. If the
  8628. * document no longer matches the target, but the document's state is still
  8629. * known (e.g. we know that the document was deleted or we received the change
  8630. * that caused the filter mismatch), the new document can be provided
  8631. * to update the remote document cache.
  8632. */
  8633. // Visible for testing.
  8634. removeDocumentFromTarget(targetId, key, updatedDocument) {
  8635. if (!this.isActiveTarget(targetId)) {
  8636. return;
  8637. }
  8638. const targetState = this.ensureTargetState(targetId);
  8639. if (this.targetContainsDocument(targetId, key)) {
  8640. targetState.addDocumentChange(key, 1 /* ChangeType.Removed */);
  8641. }
  8642. else {
  8643. // The document may have entered and left the target before we raised a
  8644. // snapshot, so we can just ignore the change.
  8645. targetState.removeDocumentChange(key);
  8646. }
  8647. this.pendingDocumentTargetMapping =
  8648. this.pendingDocumentTargetMapping.insert(key, this.ensureDocumentTargetMapping(key).delete(targetId));
  8649. if (updatedDocument) {
  8650. this.pendingDocumentUpdates = this.pendingDocumentUpdates.insert(key, updatedDocument);
  8651. }
  8652. }
  8653. removeTarget(targetId) {
  8654. this.targetStates.delete(targetId);
  8655. }
  8656. /**
  8657. * Returns the current count of documents in the target. This includes both
  8658. * the number of documents that the LocalStore considers to be part of the
  8659. * target as well as any accumulated changes.
  8660. */
  8661. getCurrentDocumentCountForTarget(targetId) {
  8662. const targetState = this.ensureTargetState(targetId);
  8663. const targetChange = targetState.toTargetChange();
  8664. return (this.metadataProvider.getRemoteKeysForTarget(targetId).size +
  8665. targetChange.addedDocuments.size -
  8666. targetChange.removedDocuments.size);
  8667. }
  8668. /**
  8669. * Increment the number of acks needed from watch before we can consider the
  8670. * server to be 'in-sync' with the client's active targets.
  8671. */
  8672. recordPendingTargetRequest(targetId) {
  8673. // For each request we get we need to record we need a response for it.
  8674. const targetState = this.ensureTargetState(targetId);
  8675. targetState.recordPendingTargetRequest();
  8676. }
  8677. ensureTargetState(targetId) {
  8678. let result = this.targetStates.get(targetId);
  8679. if (!result) {
  8680. result = new TargetState();
  8681. this.targetStates.set(targetId, result);
  8682. }
  8683. return result;
  8684. }
  8685. ensureDocumentTargetMapping(key) {
  8686. let targetMapping = this.pendingDocumentTargetMapping.get(key);
  8687. if (!targetMapping) {
  8688. targetMapping = new SortedSet(primitiveComparator);
  8689. this.pendingDocumentTargetMapping =
  8690. this.pendingDocumentTargetMapping.insert(key, targetMapping);
  8691. }
  8692. return targetMapping;
  8693. }
  8694. /**
  8695. * Verifies that the user is still interested in this target (by calling
  8696. * `getTargetDataForTarget()`) and that we are not waiting for pending ADDs
  8697. * from watch.
  8698. */
  8699. isActiveTarget(targetId) {
  8700. const targetActive = this.targetDataForActiveTarget(targetId) !== null;
  8701. if (!targetActive) {
  8702. logDebug(LOG_TAG$g, 'Detected inactive target', targetId);
  8703. }
  8704. return targetActive;
  8705. }
  8706. /**
  8707. * Returns the TargetData for an active target (i.e. a target that the user
  8708. * is still interested in that has no outstanding target change requests).
  8709. */
  8710. targetDataForActiveTarget(targetId) {
  8711. const targetState = this.targetStates.get(targetId);
  8712. return targetState && targetState.isPending
  8713. ? null
  8714. : this.metadataProvider.getTargetDataForTarget(targetId);
  8715. }
  8716. /**
  8717. * Resets the state of a Watch target to its initial state (e.g. sets
  8718. * 'current' to false, clears the resume token and removes its target mapping
  8719. * from all documents).
  8720. */
  8721. resetTarget(targetId) {
  8722. this.targetStates.set(targetId, new TargetState());
  8723. // Trigger removal for any documents currently mapped to this target.
  8724. // These removals will be part of the initial snapshot if Watch does not
  8725. // resend these documents.
  8726. const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);
  8727. existingKeys.forEach(key => {
  8728. this.removeDocumentFromTarget(targetId, key, /*updatedDocument=*/ null);
  8729. });
  8730. }
  8731. /**
  8732. * Returns whether the LocalStore considers the document to be part of the
  8733. * specified target.
  8734. */
  8735. targetContainsDocument(targetId, key) {
  8736. const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);
  8737. return existingKeys.has(key);
  8738. }
  8739. }
  8740. function documentTargetMap() {
  8741. return new SortedMap(DocumentKey.comparator);
  8742. }
  8743. function snapshotChangesMap() {
  8744. return new SortedMap(DocumentKey.comparator);
  8745. }
  8746. function createExistenceFilterMismatchInfoForTestingHooks(status, localCacheCount, existenceFilter) {
  8747. var _a, _b, _c, _d, _e, _f;
  8748. const result = {
  8749. localCacheCount,
  8750. existenceFilterCount: existenceFilter.count
  8751. };
  8752. const unchangedNames = existenceFilter.unchangedNames;
  8753. if (unchangedNames) {
  8754. result.bloomFilter = {
  8755. applied: status === 0 /* BloomFilterApplicationStatus.Success */,
  8756. hashCount: (_a = unchangedNames === null || unchangedNames === void 0 ? void 0 : unchangedNames.hashCount) !== null && _a !== void 0 ? _a : 0,
  8757. bitmapLength: (_d = (_c = (_b = unchangedNames === null || unchangedNames === void 0 ? void 0 : unchangedNames.bits) === null || _b === void 0 ? void 0 : _b.bitmap) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0,
  8758. padding: (_f = (_e = unchangedNames === null || unchangedNames === void 0 ? void 0 : unchangedNames.bits) === null || _e === void 0 ? void 0 : _e.padding) !== null && _f !== void 0 ? _f : 0
  8759. };
  8760. }
  8761. return result;
  8762. }
  8763. /**
  8764. * @license
  8765. * Copyright 2017 Google LLC
  8766. *
  8767. * Licensed under the Apache License, Version 2.0 (the "License");
  8768. * you may not use this file except in compliance with the License.
  8769. * You may obtain a copy of the License at
  8770. *
  8771. * http://www.apache.org/licenses/LICENSE-2.0
  8772. *
  8773. * Unless required by applicable law or agreed to in writing, software
  8774. * distributed under the License is distributed on an "AS IS" BASIS,
  8775. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8776. * See the License for the specific language governing permissions and
  8777. * limitations under the License.
  8778. */
  8779. const DIRECTIONS = (() => {
  8780. const dirs = {};
  8781. dirs["asc" /* Direction.ASCENDING */] = 'ASCENDING';
  8782. dirs["desc" /* Direction.DESCENDING */] = 'DESCENDING';
  8783. return dirs;
  8784. })();
  8785. const OPERATORS = (() => {
  8786. const ops = {};
  8787. ops["<" /* Operator.LESS_THAN */] = 'LESS_THAN';
  8788. ops["<=" /* Operator.LESS_THAN_OR_EQUAL */] = 'LESS_THAN_OR_EQUAL';
  8789. ops[">" /* Operator.GREATER_THAN */] = 'GREATER_THAN';
  8790. ops[">=" /* Operator.GREATER_THAN_OR_EQUAL */] = 'GREATER_THAN_OR_EQUAL';
  8791. ops["==" /* Operator.EQUAL */] = 'EQUAL';
  8792. ops["!=" /* Operator.NOT_EQUAL */] = 'NOT_EQUAL';
  8793. ops["array-contains" /* Operator.ARRAY_CONTAINS */] = 'ARRAY_CONTAINS';
  8794. ops["in" /* Operator.IN */] = 'IN';
  8795. ops["not-in" /* Operator.NOT_IN */] = 'NOT_IN';
  8796. ops["array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */] = 'ARRAY_CONTAINS_ANY';
  8797. return ops;
  8798. })();
  8799. const COMPOSITE_OPERATORS = (() => {
  8800. const ops = {};
  8801. ops["and" /* CompositeOperator.AND */] = 'AND';
  8802. ops["or" /* CompositeOperator.OR */] = 'OR';
  8803. return ops;
  8804. })();
  8805. function assertPresent(value, description) {
  8806. }
  8807. /**
  8808. * This class generates JsonObject values for the Datastore API suitable for
  8809. * sending to either GRPC stub methods or via the JSON/HTTP REST API.
  8810. *
  8811. * The serializer supports both Protobuf.js and Proto3 JSON formats. By
  8812. * setting `useProto3Json` to true, the serializer will use the Proto3 JSON
  8813. * format.
  8814. *
  8815. * For a description of the Proto3 JSON format check
  8816. * https://developers.google.com/protocol-buffers/docs/proto3#json
  8817. *
  8818. * TODO(klimt): We can remove the databaseId argument if we keep the full
  8819. * resource name in documents.
  8820. */
  8821. class JsonProtoSerializer {
  8822. constructor(databaseId, useProto3Json) {
  8823. this.databaseId = databaseId;
  8824. this.useProto3Json = useProto3Json;
  8825. }
  8826. }
  8827. function fromRpcStatus(status) {
  8828. const code = status.code === undefined ? Code.UNKNOWN : mapCodeFromRpcCode(status.code);
  8829. return new FirestoreError(code, status.message || '');
  8830. }
  8831. /**
  8832. * Returns a value for a number (or null) that's appropriate to put into
  8833. * a google.protobuf.Int32Value proto.
  8834. * DO NOT USE THIS FOR ANYTHING ELSE.
  8835. * This method cheats. It's typed as returning "number" because that's what
  8836. * our generated proto interfaces say Int32Value must be. But GRPC actually
  8837. * expects a { value: <number> } struct.
  8838. */
  8839. function toInt32Proto(serializer, val) {
  8840. if (serializer.useProto3Json || isNullOrUndefined(val)) {
  8841. return val;
  8842. }
  8843. else {
  8844. return { value: val };
  8845. }
  8846. }
  8847. /**
  8848. * Returns a number (or null) from a google.protobuf.Int32Value proto.
  8849. */
  8850. function fromInt32Proto(val) {
  8851. let result;
  8852. if (typeof val === 'object') {
  8853. result = val.value;
  8854. }
  8855. else {
  8856. result = val;
  8857. }
  8858. return isNullOrUndefined(result) ? null : result;
  8859. }
  8860. /**
  8861. * Returns a value for a Date that's appropriate to put into a proto.
  8862. */
  8863. function toTimestamp(serializer, timestamp) {
  8864. if (serializer.useProto3Json) {
  8865. // Serialize to ISO-8601 date format, but with full nano resolution.
  8866. // Since JS Date has only millis, let's only use it for the seconds and
  8867. // then manually add the fractions to the end.
  8868. const jsDateStr = new Date(timestamp.seconds * 1000).toISOString();
  8869. // Remove .xxx frac part and Z in the end.
  8870. const strUntilSeconds = jsDateStr.replace(/\.\d*/, '').replace('Z', '');
  8871. // Pad the fraction out to 9 digits (nanos).
  8872. const nanoStr = ('000000000' + timestamp.nanoseconds).slice(-9);
  8873. return `${strUntilSeconds}.${nanoStr}Z`;
  8874. }
  8875. else {
  8876. return {
  8877. seconds: '' + timestamp.seconds,
  8878. nanos: timestamp.nanoseconds
  8879. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  8880. };
  8881. }
  8882. }
  8883. function fromTimestamp(date) {
  8884. const timestamp = normalizeTimestamp(date);
  8885. return new Timestamp(timestamp.seconds, timestamp.nanos);
  8886. }
  8887. /**
  8888. * Returns a value for bytes that's appropriate to put in a proto.
  8889. *
  8890. * Visible for testing.
  8891. */
  8892. function toBytes(serializer, bytes) {
  8893. if (serializer.useProto3Json) {
  8894. return bytes.toBase64();
  8895. }
  8896. else {
  8897. return bytes.toUint8Array();
  8898. }
  8899. }
  8900. /**
  8901. * Returns a ByteString based on the proto string value.
  8902. */
  8903. function fromBytes(serializer, value) {
  8904. if (serializer.useProto3Json) {
  8905. hardAssert(value === undefined || typeof value === 'string');
  8906. return ByteString.fromBase64String(value ? value : '');
  8907. }
  8908. else {
  8909. hardAssert(value === undefined || value instanceof Uint8Array);
  8910. return ByteString.fromUint8Array(value ? value : new Uint8Array());
  8911. }
  8912. }
  8913. function toVersion(serializer, version) {
  8914. return toTimestamp(serializer, version.toTimestamp());
  8915. }
  8916. function fromVersion(version) {
  8917. hardAssert(!!version);
  8918. return SnapshotVersion.fromTimestamp(fromTimestamp(version));
  8919. }
  8920. function toResourceName(databaseId, path) {
  8921. return fullyQualifiedPrefixPath(databaseId)
  8922. .child('documents')
  8923. .child(path)
  8924. .canonicalString();
  8925. }
  8926. function fromResourceName(name) {
  8927. const resource = ResourcePath.fromString(name);
  8928. hardAssert(isValidResourceName(resource));
  8929. return resource;
  8930. }
  8931. function toName(serializer, key) {
  8932. return toResourceName(serializer.databaseId, key.path);
  8933. }
  8934. function fromName(serializer, name) {
  8935. const resource = fromResourceName(name);
  8936. if (resource.get(1) !== serializer.databaseId.projectId) {
  8937. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Tried to deserialize key from different project: ' +
  8938. resource.get(1) +
  8939. ' vs ' +
  8940. serializer.databaseId.projectId);
  8941. }
  8942. if (resource.get(3) !== serializer.databaseId.database) {
  8943. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Tried to deserialize key from different database: ' +
  8944. resource.get(3) +
  8945. ' vs ' +
  8946. serializer.databaseId.database);
  8947. }
  8948. return new DocumentKey(extractLocalPathFromResourceName(resource));
  8949. }
  8950. function toQueryPath(serializer, path) {
  8951. return toResourceName(serializer.databaseId, path);
  8952. }
  8953. function fromQueryPath(name) {
  8954. const resourceName = fromResourceName(name);
  8955. // In v1beta1 queries for collections at the root did not have a trailing
  8956. // "/documents". In v1 all resource paths contain "/documents". Preserve the
  8957. // ability to read the v1beta1 form for compatibility with queries persisted
  8958. // in the local target cache.
  8959. if (resourceName.length === 4) {
  8960. return ResourcePath.emptyPath();
  8961. }
  8962. return extractLocalPathFromResourceName(resourceName);
  8963. }
  8964. function getEncodedDatabaseId(serializer) {
  8965. const path = new ResourcePath([
  8966. 'projects',
  8967. serializer.databaseId.projectId,
  8968. 'databases',
  8969. serializer.databaseId.database
  8970. ]);
  8971. return path.canonicalString();
  8972. }
  8973. function fullyQualifiedPrefixPath(databaseId) {
  8974. return new ResourcePath([
  8975. 'projects',
  8976. databaseId.projectId,
  8977. 'databases',
  8978. databaseId.database
  8979. ]);
  8980. }
  8981. function extractLocalPathFromResourceName(resourceName) {
  8982. hardAssert(resourceName.length > 4 && resourceName.get(4) === 'documents');
  8983. return resourceName.popFirst(5);
  8984. }
  8985. /** Creates a Document proto from key and fields (but no create/update time) */
  8986. function toMutationDocument(serializer, key, fields) {
  8987. return {
  8988. name: toName(serializer, key),
  8989. fields: fields.value.mapValue.fields
  8990. };
  8991. }
  8992. function toDocument(serializer, document) {
  8993. return {
  8994. name: toName(serializer, document.key),
  8995. fields: document.data.value.mapValue.fields,
  8996. updateTime: toTimestamp(serializer, document.version.toTimestamp()),
  8997. createTime: toTimestamp(serializer, document.createTime.toTimestamp())
  8998. };
  8999. }
  9000. function fromDocument(serializer, document, hasCommittedMutations) {
  9001. const key = fromName(serializer, document.name);
  9002. const version = fromVersion(document.updateTime);
  9003. // If we read a document from persistence that is missing createTime, it's due
  9004. // to older SDK versions not storing this information. In such cases, we'll
  9005. // set the createTime to zero. This can be removed in the long term.
  9006. const createTime = document.createTime
  9007. ? fromVersion(document.createTime)
  9008. : SnapshotVersion.min();
  9009. const data = new ObjectValue({ mapValue: { fields: document.fields } });
  9010. const result = MutableDocument.newFoundDocument(key, version, createTime, data);
  9011. if (hasCommittedMutations) {
  9012. result.setHasCommittedMutations();
  9013. }
  9014. return hasCommittedMutations ? result.setHasCommittedMutations() : result;
  9015. }
  9016. function fromFound(serializer, doc) {
  9017. hardAssert(!!doc.found);
  9018. assertPresent(doc.found.name);
  9019. assertPresent(doc.found.updateTime);
  9020. const key = fromName(serializer, doc.found.name);
  9021. const version = fromVersion(doc.found.updateTime);
  9022. const createTime = doc.found.createTime
  9023. ? fromVersion(doc.found.createTime)
  9024. : SnapshotVersion.min();
  9025. const data = new ObjectValue({ mapValue: { fields: doc.found.fields } });
  9026. return MutableDocument.newFoundDocument(key, version, createTime, data);
  9027. }
  9028. function fromMissing(serializer, result) {
  9029. hardAssert(!!result.missing);
  9030. hardAssert(!!result.readTime);
  9031. const key = fromName(serializer, result.missing);
  9032. const version = fromVersion(result.readTime);
  9033. return MutableDocument.newNoDocument(key, version);
  9034. }
  9035. function fromBatchGetDocumentsResponse(serializer, result) {
  9036. if ('found' in result) {
  9037. return fromFound(serializer, result);
  9038. }
  9039. else if ('missing' in result) {
  9040. return fromMissing(serializer, result);
  9041. }
  9042. return fail();
  9043. }
  9044. function fromWatchChange(serializer, change) {
  9045. let watchChange;
  9046. if ('targetChange' in change) {
  9047. assertPresent(change.targetChange);
  9048. // proto3 default value is unset in JSON (undefined), so use 'NO_CHANGE'
  9049. // if unset
  9050. const state = fromWatchTargetChangeState(change.targetChange.targetChangeType || 'NO_CHANGE');
  9051. const targetIds = change.targetChange.targetIds || [];
  9052. const resumeToken = fromBytes(serializer, change.targetChange.resumeToken);
  9053. const causeProto = change.targetChange.cause;
  9054. const cause = causeProto && fromRpcStatus(causeProto);
  9055. watchChange = new WatchTargetChange(state, targetIds, resumeToken, cause || null);
  9056. }
  9057. else if ('documentChange' in change) {
  9058. assertPresent(change.documentChange);
  9059. const entityChange = change.documentChange;
  9060. assertPresent(entityChange.document);
  9061. assertPresent(entityChange.document.name);
  9062. assertPresent(entityChange.document.updateTime);
  9063. const key = fromName(serializer, entityChange.document.name);
  9064. const version = fromVersion(entityChange.document.updateTime);
  9065. const createTime = entityChange.document.createTime
  9066. ? fromVersion(entityChange.document.createTime)
  9067. : SnapshotVersion.min();
  9068. const data = new ObjectValue({
  9069. mapValue: { fields: entityChange.document.fields }
  9070. });
  9071. const doc = MutableDocument.newFoundDocument(key, version, createTime, data);
  9072. const updatedTargetIds = entityChange.targetIds || [];
  9073. const removedTargetIds = entityChange.removedTargetIds || [];
  9074. watchChange = new DocumentWatchChange(updatedTargetIds, removedTargetIds, doc.key, doc);
  9075. }
  9076. else if ('documentDelete' in change) {
  9077. assertPresent(change.documentDelete);
  9078. const docDelete = change.documentDelete;
  9079. assertPresent(docDelete.document);
  9080. const key = fromName(serializer, docDelete.document);
  9081. const version = docDelete.readTime
  9082. ? fromVersion(docDelete.readTime)
  9083. : SnapshotVersion.min();
  9084. const doc = MutableDocument.newNoDocument(key, version);
  9085. const removedTargetIds = docDelete.removedTargetIds || [];
  9086. watchChange = new DocumentWatchChange([], removedTargetIds, doc.key, doc);
  9087. }
  9088. else if ('documentRemove' in change) {
  9089. assertPresent(change.documentRemove);
  9090. const docRemove = change.documentRemove;
  9091. assertPresent(docRemove.document);
  9092. const key = fromName(serializer, docRemove.document);
  9093. const removedTargetIds = docRemove.removedTargetIds || [];
  9094. watchChange = new DocumentWatchChange([], removedTargetIds, key, null);
  9095. }
  9096. else if ('filter' in change) {
  9097. // TODO(dimond): implement existence filter parsing with strategy.
  9098. assertPresent(change.filter);
  9099. const filter = change.filter;
  9100. assertPresent(filter.targetId);
  9101. const { count = 0, unchangedNames } = filter;
  9102. const existenceFilter = new ExistenceFilter(count, unchangedNames);
  9103. const targetId = filter.targetId;
  9104. watchChange = new ExistenceFilterChange(targetId, existenceFilter);
  9105. }
  9106. else {
  9107. return fail();
  9108. }
  9109. return watchChange;
  9110. }
  9111. function fromWatchTargetChangeState(state) {
  9112. if (state === 'NO_CHANGE') {
  9113. return 0 /* WatchTargetChangeState.NoChange */;
  9114. }
  9115. else if (state === 'ADD') {
  9116. return 1 /* WatchTargetChangeState.Added */;
  9117. }
  9118. else if (state === 'REMOVE') {
  9119. return 2 /* WatchTargetChangeState.Removed */;
  9120. }
  9121. else if (state === 'CURRENT') {
  9122. return 3 /* WatchTargetChangeState.Current */;
  9123. }
  9124. else if (state === 'RESET') {
  9125. return 4 /* WatchTargetChangeState.Reset */;
  9126. }
  9127. else {
  9128. return fail();
  9129. }
  9130. }
  9131. function versionFromListenResponse(change) {
  9132. // We have only reached a consistent snapshot for the entire stream if there
  9133. // is a read_time set and it applies to all targets (i.e. the list of
  9134. // targets is empty). The backend is guaranteed to send such responses.
  9135. if (!('targetChange' in change)) {
  9136. return SnapshotVersion.min();
  9137. }
  9138. const targetChange = change.targetChange;
  9139. if (targetChange.targetIds && targetChange.targetIds.length) {
  9140. return SnapshotVersion.min();
  9141. }
  9142. if (!targetChange.readTime) {
  9143. return SnapshotVersion.min();
  9144. }
  9145. return fromVersion(targetChange.readTime);
  9146. }
  9147. function toMutation(serializer, mutation) {
  9148. let result;
  9149. if (mutation instanceof SetMutation) {
  9150. result = {
  9151. update: toMutationDocument(serializer, mutation.key, mutation.value)
  9152. };
  9153. }
  9154. else if (mutation instanceof DeleteMutation) {
  9155. result = { delete: toName(serializer, mutation.key) };
  9156. }
  9157. else if (mutation instanceof PatchMutation) {
  9158. result = {
  9159. update: toMutationDocument(serializer, mutation.key, mutation.data),
  9160. updateMask: toDocumentMask(mutation.fieldMask)
  9161. };
  9162. }
  9163. else if (mutation instanceof VerifyMutation) {
  9164. result = {
  9165. verify: toName(serializer, mutation.key)
  9166. };
  9167. }
  9168. else {
  9169. return fail();
  9170. }
  9171. if (mutation.fieldTransforms.length > 0) {
  9172. result.updateTransforms = mutation.fieldTransforms.map(transform => toFieldTransform(serializer, transform));
  9173. }
  9174. if (!mutation.precondition.isNone) {
  9175. result.currentDocument = toPrecondition(serializer, mutation.precondition);
  9176. }
  9177. return result;
  9178. }
  9179. function fromMutation(serializer, proto) {
  9180. const precondition = proto.currentDocument
  9181. ? fromPrecondition(proto.currentDocument)
  9182. : Precondition.none();
  9183. const fieldTransforms = proto.updateTransforms
  9184. ? proto.updateTransforms.map(transform => fromFieldTransform(serializer, transform))
  9185. : [];
  9186. if (proto.update) {
  9187. assertPresent(proto.update.name);
  9188. const key = fromName(serializer, proto.update.name);
  9189. const value = new ObjectValue({
  9190. mapValue: { fields: proto.update.fields }
  9191. });
  9192. if (proto.updateMask) {
  9193. const fieldMask = fromDocumentMask(proto.updateMask);
  9194. return new PatchMutation(key, value, fieldMask, precondition, fieldTransforms);
  9195. }
  9196. else {
  9197. return new SetMutation(key, value, precondition, fieldTransforms);
  9198. }
  9199. }
  9200. else if (proto.delete) {
  9201. const key = fromName(serializer, proto.delete);
  9202. return new DeleteMutation(key, precondition);
  9203. }
  9204. else if (proto.verify) {
  9205. const key = fromName(serializer, proto.verify);
  9206. return new VerifyMutation(key, precondition);
  9207. }
  9208. else {
  9209. return fail();
  9210. }
  9211. }
  9212. function toPrecondition(serializer, precondition) {
  9213. if (precondition.updateTime !== undefined) {
  9214. return {
  9215. updateTime: toVersion(serializer, precondition.updateTime)
  9216. };
  9217. }
  9218. else if (precondition.exists !== undefined) {
  9219. return { exists: precondition.exists };
  9220. }
  9221. else {
  9222. return fail();
  9223. }
  9224. }
  9225. function fromPrecondition(precondition) {
  9226. if (precondition.updateTime !== undefined) {
  9227. return Precondition.updateTime(fromVersion(precondition.updateTime));
  9228. }
  9229. else if (precondition.exists !== undefined) {
  9230. return Precondition.exists(precondition.exists);
  9231. }
  9232. else {
  9233. return Precondition.none();
  9234. }
  9235. }
  9236. function fromWriteResult(proto, commitTime) {
  9237. // NOTE: Deletes don't have an updateTime.
  9238. let version = proto.updateTime
  9239. ? fromVersion(proto.updateTime)
  9240. : fromVersion(commitTime);
  9241. if (version.isEqual(SnapshotVersion.min())) {
  9242. // The Firestore Emulator currently returns an update time of 0 for
  9243. // deletes of non-existing documents (rather than null). This breaks the
  9244. // test "get deleted doc while offline with source=cache" as NoDocuments
  9245. // with version 0 are filtered by IndexedDb's RemoteDocumentCache.
  9246. // TODO(#2149): Remove this when Emulator is fixed
  9247. version = fromVersion(commitTime);
  9248. }
  9249. return new MutationResult(version, proto.transformResults || []);
  9250. }
  9251. function fromWriteResults(protos, commitTime) {
  9252. if (protos && protos.length > 0) {
  9253. hardAssert(commitTime !== undefined);
  9254. return protos.map(proto => fromWriteResult(proto, commitTime));
  9255. }
  9256. else {
  9257. return [];
  9258. }
  9259. }
  9260. function toFieldTransform(serializer, fieldTransform) {
  9261. const transform = fieldTransform.transform;
  9262. if (transform instanceof ServerTimestampTransform) {
  9263. return {
  9264. fieldPath: fieldTransform.field.canonicalString(),
  9265. setToServerValue: 'REQUEST_TIME'
  9266. };
  9267. }
  9268. else if (transform instanceof ArrayUnionTransformOperation) {
  9269. return {
  9270. fieldPath: fieldTransform.field.canonicalString(),
  9271. appendMissingElements: {
  9272. values: transform.elements
  9273. }
  9274. };
  9275. }
  9276. else if (transform instanceof ArrayRemoveTransformOperation) {
  9277. return {
  9278. fieldPath: fieldTransform.field.canonicalString(),
  9279. removeAllFromArray: {
  9280. values: transform.elements
  9281. }
  9282. };
  9283. }
  9284. else if (transform instanceof NumericIncrementTransformOperation) {
  9285. return {
  9286. fieldPath: fieldTransform.field.canonicalString(),
  9287. increment: transform.operand
  9288. };
  9289. }
  9290. else {
  9291. throw fail();
  9292. }
  9293. }
  9294. function fromFieldTransform(serializer, proto) {
  9295. let transform = null;
  9296. if ('setToServerValue' in proto) {
  9297. hardAssert(proto.setToServerValue === 'REQUEST_TIME');
  9298. transform = new ServerTimestampTransform();
  9299. }
  9300. else if ('appendMissingElements' in proto) {
  9301. const values = proto.appendMissingElements.values || [];
  9302. transform = new ArrayUnionTransformOperation(values);
  9303. }
  9304. else if ('removeAllFromArray' in proto) {
  9305. const values = proto.removeAllFromArray.values || [];
  9306. transform = new ArrayRemoveTransformOperation(values);
  9307. }
  9308. else if ('increment' in proto) {
  9309. transform = new NumericIncrementTransformOperation(serializer, proto.increment);
  9310. }
  9311. else {
  9312. fail();
  9313. }
  9314. const fieldPath = FieldPath$1.fromServerFormat(proto.fieldPath);
  9315. return new FieldTransform(fieldPath, transform);
  9316. }
  9317. function toDocumentsTarget(serializer, target) {
  9318. return { documents: [toQueryPath(serializer, target.path)] };
  9319. }
  9320. function fromDocumentsTarget(documentsTarget) {
  9321. const count = documentsTarget.documents.length;
  9322. hardAssert(count === 1);
  9323. const name = documentsTarget.documents[0];
  9324. return queryToTarget(newQueryForPath(fromQueryPath(name)));
  9325. }
  9326. function toQueryTarget(serializer, target) {
  9327. // Dissect the path into parent, collectionId, and optional key filter.
  9328. const result = { structuredQuery: {} };
  9329. const path = target.path;
  9330. if (target.collectionGroup !== null) {
  9331. result.parent = toQueryPath(serializer, path);
  9332. result.structuredQuery.from = [
  9333. {
  9334. collectionId: target.collectionGroup,
  9335. allDescendants: true
  9336. }
  9337. ];
  9338. }
  9339. else {
  9340. result.parent = toQueryPath(serializer, path.popLast());
  9341. result.structuredQuery.from = [{ collectionId: path.lastSegment() }];
  9342. }
  9343. const where = toFilters(target.filters);
  9344. if (where) {
  9345. result.structuredQuery.where = where;
  9346. }
  9347. const orderBy = toOrder(target.orderBy);
  9348. if (orderBy) {
  9349. result.structuredQuery.orderBy = orderBy;
  9350. }
  9351. const limit = toInt32Proto(serializer, target.limit);
  9352. if (limit !== null) {
  9353. result.structuredQuery.limit = limit;
  9354. }
  9355. if (target.startAt) {
  9356. result.structuredQuery.startAt = toStartAtCursor(target.startAt);
  9357. }
  9358. if (target.endAt) {
  9359. result.structuredQuery.endAt = toEndAtCursor(target.endAt);
  9360. }
  9361. return result;
  9362. }
  9363. function toRunAggregationQueryRequest(serializer, target, aggregates) {
  9364. const queryTarget = toQueryTarget(serializer, target);
  9365. const aliasMap = {};
  9366. const aggregations = [];
  9367. let aggregationNum = 0;
  9368. aggregates.forEach(aggregate => {
  9369. // Map all client-side aliases to a unique short-form
  9370. // alias. This avoids issues with client-side aliases that
  9371. // exceed the 1500-byte string size limit.
  9372. const serverAlias = `aggregate_${aggregationNum++}`;
  9373. aliasMap[serverAlias] = aggregate.alias;
  9374. if (aggregate.aggregateType === 'count') {
  9375. aggregations.push({
  9376. alias: serverAlias,
  9377. count: {}
  9378. });
  9379. }
  9380. else if (aggregate.aggregateType === 'avg') {
  9381. aggregations.push({
  9382. alias: serverAlias,
  9383. avg: {
  9384. field: toFieldPathReference(aggregate.fieldPath)
  9385. }
  9386. });
  9387. }
  9388. else if (aggregate.aggregateType === 'sum') {
  9389. aggregations.push({
  9390. alias: serverAlias,
  9391. sum: {
  9392. field: toFieldPathReference(aggregate.fieldPath)
  9393. }
  9394. });
  9395. }
  9396. });
  9397. return {
  9398. request: {
  9399. structuredAggregationQuery: {
  9400. aggregations,
  9401. structuredQuery: queryTarget.structuredQuery
  9402. },
  9403. parent: queryTarget.parent
  9404. },
  9405. aliasMap
  9406. };
  9407. }
  9408. function convertQueryTargetToQuery(target) {
  9409. let path = fromQueryPath(target.parent);
  9410. const query = target.structuredQuery;
  9411. const fromCount = query.from ? query.from.length : 0;
  9412. let collectionGroup = null;
  9413. if (fromCount > 0) {
  9414. hardAssert(fromCount === 1);
  9415. const from = query.from[0];
  9416. if (from.allDescendants) {
  9417. collectionGroup = from.collectionId;
  9418. }
  9419. else {
  9420. path = path.child(from.collectionId);
  9421. }
  9422. }
  9423. let filterBy = [];
  9424. if (query.where) {
  9425. filterBy = fromFilters(query.where);
  9426. }
  9427. let orderBy = [];
  9428. if (query.orderBy) {
  9429. orderBy = fromOrder(query.orderBy);
  9430. }
  9431. let limit = null;
  9432. if (query.limit) {
  9433. limit = fromInt32Proto(query.limit);
  9434. }
  9435. let startAt = null;
  9436. if (query.startAt) {
  9437. startAt = fromStartAtCursor(query.startAt);
  9438. }
  9439. let endAt = null;
  9440. if (query.endAt) {
  9441. endAt = fromEndAtCursor(query.endAt);
  9442. }
  9443. return newQuery(path, collectionGroup, orderBy, filterBy, limit, "F" /* LimitType.First */, startAt, endAt);
  9444. }
  9445. function fromQueryTarget(target) {
  9446. return queryToTarget(convertQueryTargetToQuery(target));
  9447. }
  9448. function toListenRequestLabels(serializer, targetData) {
  9449. const value = toLabel(targetData.purpose);
  9450. if (value == null) {
  9451. return null;
  9452. }
  9453. else {
  9454. return {
  9455. 'goog-listen-tags': value
  9456. };
  9457. }
  9458. }
  9459. function toLabel(purpose) {
  9460. switch (purpose) {
  9461. case "TargetPurposeListen" /* TargetPurpose.Listen */:
  9462. return null;
  9463. case "TargetPurposeExistenceFilterMismatch" /* TargetPurpose.ExistenceFilterMismatch */:
  9464. return 'existence-filter-mismatch';
  9465. case "TargetPurposeExistenceFilterMismatchBloom" /* TargetPurpose.ExistenceFilterMismatchBloom */:
  9466. return 'existence-filter-mismatch-bloom';
  9467. case "TargetPurposeLimboResolution" /* TargetPurpose.LimboResolution */:
  9468. return 'limbo-document';
  9469. default:
  9470. return fail();
  9471. }
  9472. }
  9473. function toTarget(serializer, targetData) {
  9474. let result;
  9475. const target = targetData.target;
  9476. if (targetIsDocumentTarget(target)) {
  9477. result = { documents: toDocumentsTarget(serializer, target) };
  9478. }
  9479. else {
  9480. result = { query: toQueryTarget(serializer, target) };
  9481. }
  9482. result.targetId = targetData.targetId;
  9483. if (targetData.resumeToken.approximateByteSize() > 0) {
  9484. result.resumeToken = toBytes(serializer, targetData.resumeToken);
  9485. const expectedCount = toInt32Proto(serializer, targetData.expectedCount);
  9486. if (expectedCount !== null) {
  9487. result.expectedCount = expectedCount;
  9488. }
  9489. }
  9490. else if (targetData.snapshotVersion.compareTo(SnapshotVersion.min()) > 0) {
  9491. // TODO(wuandy): Consider removing above check because it is most likely true.
  9492. // Right now, many tests depend on this behaviour though (leaving min() out
  9493. // of serialization).
  9494. result.readTime = toTimestamp(serializer, targetData.snapshotVersion.toTimestamp());
  9495. const expectedCount = toInt32Proto(serializer, targetData.expectedCount);
  9496. if (expectedCount !== null) {
  9497. result.expectedCount = expectedCount;
  9498. }
  9499. }
  9500. return result;
  9501. }
  9502. function toFilters(filters) {
  9503. if (filters.length === 0) {
  9504. return;
  9505. }
  9506. return toFilter(CompositeFilter.create(filters, "and" /* CompositeOperator.AND */));
  9507. }
  9508. function fromFilters(filter) {
  9509. const result = fromFilter(filter);
  9510. if (result instanceof CompositeFilter &&
  9511. compositeFilterIsFlatConjunction(result)) {
  9512. return result.getFilters();
  9513. }
  9514. return [result];
  9515. }
  9516. function fromFilter(filter) {
  9517. if (filter.unaryFilter !== undefined) {
  9518. return fromUnaryFilter(filter);
  9519. }
  9520. else if (filter.fieldFilter !== undefined) {
  9521. return fromFieldFilter(filter);
  9522. }
  9523. else if (filter.compositeFilter !== undefined) {
  9524. return fromCompositeFilter(filter);
  9525. }
  9526. else {
  9527. return fail();
  9528. }
  9529. }
  9530. function toOrder(orderBys) {
  9531. if (orderBys.length === 0) {
  9532. return;
  9533. }
  9534. return orderBys.map(order => toPropertyOrder(order));
  9535. }
  9536. function fromOrder(orderBys) {
  9537. return orderBys.map(order => fromPropertyOrder(order));
  9538. }
  9539. function toStartAtCursor(cursor) {
  9540. return {
  9541. before: cursor.inclusive,
  9542. values: cursor.position
  9543. };
  9544. }
  9545. function toEndAtCursor(cursor) {
  9546. return {
  9547. before: !cursor.inclusive,
  9548. values: cursor.position
  9549. };
  9550. }
  9551. function fromStartAtCursor(cursor) {
  9552. const inclusive = !!cursor.before;
  9553. const position = cursor.values || [];
  9554. return new Bound(position, inclusive);
  9555. }
  9556. function fromEndAtCursor(cursor) {
  9557. const inclusive = !cursor.before;
  9558. const position = cursor.values || [];
  9559. return new Bound(position, inclusive);
  9560. }
  9561. // visible for testing
  9562. function toDirection(dir) {
  9563. return DIRECTIONS[dir];
  9564. }
  9565. // visible for testing
  9566. function fromDirection(dir) {
  9567. switch (dir) {
  9568. case 'ASCENDING':
  9569. return "asc" /* Direction.ASCENDING */;
  9570. case 'DESCENDING':
  9571. return "desc" /* Direction.DESCENDING */;
  9572. default:
  9573. return undefined;
  9574. }
  9575. }
  9576. // visible for testing
  9577. function toOperatorName(op) {
  9578. return OPERATORS[op];
  9579. }
  9580. function toCompositeOperatorName(op) {
  9581. return COMPOSITE_OPERATORS[op];
  9582. }
  9583. function fromOperatorName(op) {
  9584. switch (op) {
  9585. case 'EQUAL':
  9586. return "==" /* Operator.EQUAL */;
  9587. case 'NOT_EQUAL':
  9588. return "!=" /* Operator.NOT_EQUAL */;
  9589. case 'GREATER_THAN':
  9590. return ">" /* Operator.GREATER_THAN */;
  9591. case 'GREATER_THAN_OR_EQUAL':
  9592. return ">=" /* Operator.GREATER_THAN_OR_EQUAL */;
  9593. case 'LESS_THAN':
  9594. return "<" /* Operator.LESS_THAN */;
  9595. case 'LESS_THAN_OR_EQUAL':
  9596. return "<=" /* Operator.LESS_THAN_OR_EQUAL */;
  9597. case 'ARRAY_CONTAINS':
  9598. return "array-contains" /* Operator.ARRAY_CONTAINS */;
  9599. case 'IN':
  9600. return "in" /* Operator.IN */;
  9601. case 'NOT_IN':
  9602. return "not-in" /* Operator.NOT_IN */;
  9603. case 'ARRAY_CONTAINS_ANY':
  9604. return "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */;
  9605. case 'OPERATOR_UNSPECIFIED':
  9606. return fail();
  9607. default:
  9608. return fail();
  9609. }
  9610. }
  9611. function fromCompositeOperatorName(op) {
  9612. switch (op) {
  9613. case 'AND':
  9614. return "and" /* CompositeOperator.AND */;
  9615. case 'OR':
  9616. return "or" /* CompositeOperator.OR */;
  9617. default:
  9618. return fail();
  9619. }
  9620. }
  9621. function toFieldPathReference(path) {
  9622. return { fieldPath: path.canonicalString() };
  9623. }
  9624. function fromFieldPathReference(fieldReference) {
  9625. return FieldPath$1.fromServerFormat(fieldReference.fieldPath);
  9626. }
  9627. // visible for testing
  9628. function toPropertyOrder(orderBy) {
  9629. return {
  9630. field: toFieldPathReference(orderBy.field),
  9631. direction: toDirection(orderBy.dir)
  9632. };
  9633. }
  9634. function fromPropertyOrder(orderBy) {
  9635. return new OrderBy(fromFieldPathReference(orderBy.field), fromDirection(orderBy.direction));
  9636. }
  9637. // visible for testing
  9638. function toFilter(filter) {
  9639. if (filter instanceof FieldFilter) {
  9640. return toUnaryOrFieldFilter(filter);
  9641. }
  9642. else if (filter instanceof CompositeFilter) {
  9643. return toCompositeFilter(filter);
  9644. }
  9645. else {
  9646. return fail();
  9647. }
  9648. }
  9649. function toCompositeFilter(filter) {
  9650. const protos = filter.getFilters().map(filter => toFilter(filter));
  9651. if (protos.length === 1) {
  9652. return protos[0];
  9653. }
  9654. return {
  9655. compositeFilter: {
  9656. op: toCompositeOperatorName(filter.op),
  9657. filters: protos
  9658. }
  9659. };
  9660. }
  9661. function toUnaryOrFieldFilter(filter) {
  9662. if (filter.op === "==" /* Operator.EQUAL */) {
  9663. if (isNanValue(filter.value)) {
  9664. return {
  9665. unaryFilter: {
  9666. field: toFieldPathReference(filter.field),
  9667. op: 'IS_NAN'
  9668. }
  9669. };
  9670. }
  9671. else if (isNullValue(filter.value)) {
  9672. return {
  9673. unaryFilter: {
  9674. field: toFieldPathReference(filter.field),
  9675. op: 'IS_NULL'
  9676. }
  9677. };
  9678. }
  9679. }
  9680. else if (filter.op === "!=" /* Operator.NOT_EQUAL */) {
  9681. if (isNanValue(filter.value)) {
  9682. return {
  9683. unaryFilter: {
  9684. field: toFieldPathReference(filter.field),
  9685. op: 'IS_NOT_NAN'
  9686. }
  9687. };
  9688. }
  9689. else if (isNullValue(filter.value)) {
  9690. return {
  9691. unaryFilter: {
  9692. field: toFieldPathReference(filter.field),
  9693. op: 'IS_NOT_NULL'
  9694. }
  9695. };
  9696. }
  9697. }
  9698. return {
  9699. fieldFilter: {
  9700. field: toFieldPathReference(filter.field),
  9701. op: toOperatorName(filter.op),
  9702. value: filter.value
  9703. }
  9704. };
  9705. }
  9706. function fromUnaryFilter(filter) {
  9707. switch (filter.unaryFilter.op) {
  9708. case 'IS_NAN':
  9709. const nanField = fromFieldPathReference(filter.unaryFilter.field);
  9710. return FieldFilter.create(nanField, "==" /* Operator.EQUAL */, {
  9711. doubleValue: NaN
  9712. });
  9713. case 'IS_NULL':
  9714. const nullField = fromFieldPathReference(filter.unaryFilter.field);
  9715. return FieldFilter.create(nullField, "==" /* Operator.EQUAL */, {
  9716. nullValue: 'NULL_VALUE'
  9717. });
  9718. case 'IS_NOT_NAN':
  9719. const notNanField = fromFieldPathReference(filter.unaryFilter.field);
  9720. return FieldFilter.create(notNanField, "!=" /* Operator.NOT_EQUAL */, {
  9721. doubleValue: NaN
  9722. });
  9723. case 'IS_NOT_NULL':
  9724. const notNullField = fromFieldPathReference(filter.unaryFilter.field);
  9725. return FieldFilter.create(notNullField, "!=" /* Operator.NOT_EQUAL */, {
  9726. nullValue: 'NULL_VALUE'
  9727. });
  9728. case 'OPERATOR_UNSPECIFIED':
  9729. return fail();
  9730. default:
  9731. return fail();
  9732. }
  9733. }
  9734. function fromFieldFilter(filter) {
  9735. return FieldFilter.create(fromFieldPathReference(filter.fieldFilter.field), fromOperatorName(filter.fieldFilter.op), filter.fieldFilter.value);
  9736. }
  9737. function fromCompositeFilter(filter) {
  9738. return CompositeFilter.create(filter.compositeFilter.filters.map(filter => fromFilter(filter)), fromCompositeOperatorName(filter.compositeFilter.op));
  9739. }
  9740. function toDocumentMask(fieldMask) {
  9741. const canonicalFields = [];
  9742. fieldMask.fields.forEach(field => canonicalFields.push(field.canonicalString()));
  9743. return {
  9744. fieldPaths: canonicalFields
  9745. };
  9746. }
  9747. function fromDocumentMask(proto) {
  9748. const paths = proto.fieldPaths || [];
  9749. return new FieldMask(paths.map(path => FieldPath$1.fromServerFormat(path)));
  9750. }
  9751. function isValidResourceName(path) {
  9752. // Resource names have at least 4 components (project ID, database ID)
  9753. return (path.length >= 4 &&
  9754. path.get(0) === 'projects' &&
  9755. path.get(2) === 'databases');
  9756. }
  9757. /**
  9758. * @license
  9759. * Copyright 2017 Google LLC
  9760. *
  9761. * Licensed under the Apache License, Version 2.0 (the "License");
  9762. * you may not use this file except in compliance with the License.
  9763. * You may obtain a copy of the License at
  9764. *
  9765. * http://www.apache.org/licenses/LICENSE-2.0
  9766. *
  9767. * Unless required by applicable law or agreed to in writing, software
  9768. * distributed under the License is distributed on an "AS IS" BASIS,
  9769. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9770. * See the License for the specific language governing permissions and
  9771. * limitations under the License.
  9772. */
  9773. /**
  9774. * An immutable set of metadata that the local store tracks for each target.
  9775. */
  9776. class TargetData {
  9777. constructor(
  9778. /** The target being listened to. */
  9779. target,
  9780. /**
  9781. * The target ID to which the target corresponds; Assigned by the
  9782. * LocalStore for user listens and by the SyncEngine for limbo watches.
  9783. */
  9784. targetId,
  9785. /** The purpose of the target. */
  9786. purpose,
  9787. /**
  9788. * The sequence number of the last transaction during which this target data
  9789. * was modified.
  9790. */
  9791. sequenceNumber,
  9792. /** The latest snapshot version seen for this target. */
  9793. snapshotVersion = SnapshotVersion.min(),
  9794. /**
  9795. * The maximum snapshot version at which the associated view
  9796. * contained no limbo documents.
  9797. */
  9798. lastLimboFreeSnapshotVersion = SnapshotVersion.min(),
  9799. /**
  9800. * An opaque, server-assigned token that allows watching a target to be
  9801. * resumed after disconnecting without retransmitting all the data that
  9802. * matches the target. The resume token essentially identifies a point in
  9803. * time from which the server should resume sending results.
  9804. */
  9805. resumeToken = ByteString.EMPTY_BYTE_STRING,
  9806. /**
  9807. * The number of documents that last matched the query at the resume token or
  9808. * read time. Documents are counted only when making a listen request with
  9809. * resume token or read time, otherwise, keep it null.
  9810. */
  9811. expectedCount = null) {
  9812. this.target = target;
  9813. this.targetId = targetId;
  9814. this.purpose = purpose;
  9815. this.sequenceNumber = sequenceNumber;
  9816. this.snapshotVersion = snapshotVersion;
  9817. this.lastLimboFreeSnapshotVersion = lastLimboFreeSnapshotVersion;
  9818. this.resumeToken = resumeToken;
  9819. this.expectedCount = expectedCount;
  9820. }
  9821. /** Creates a new target data instance with an updated sequence number. */
  9822. withSequenceNumber(sequenceNumber) {
  9823. return new TargetData(this.target, this.targetId, this.purpose, sequenceNumber, this.snapshotVersion, this.lastLimboFreeSnapshotVersion, this.resumeToken, this.expectedCount);
  9824. }
  9825. /**
  9826. * Creates a new target data instance with an updated resume token and
  9827. * snapshot version.
  9828. */
  9829. withResumeToken(resumeToken, snapshotVersion) {
  9830. return new TargetData(this.target, this.targetId, this.purpose, this.sequenceNumber, snapshotVersion, this.lastLimboFreeSnapshotVersion, resumeToken,
  9831. /* expectedCount= */ null);
  9832. }
  9833. /**
  9834. * Creates a new target data instance with an updated expected count.
  9835. */
  9836. withExpectedCount(expectedCount) {
  9837. return new TargetData(this.target, this.targetId, this.purpose, this.sequenceNumber, this.snapshotVersion, this.lastLimboFreeSnapshotVersion, this.resumeToken, expectedCount);
  9838. }
  9839. /**
  9840. * Creates a new target data instance with an updated last limbo free
  9841. * snapshot version number.
  9842. */
  9843. withLastLimboFreeSnapshotVersion(lastLimboFreeSnapshotVersion) {
  9844. return new TargetData(this.target, this.targetId, this.purpose, this.sequenceNumber, this.snapshotVersion, lastLimboFreeSnapshotVersion, this.resumeToken, this.expectedCount);
  9845. }
  9846. }
  9847. /**
  9848. * @license
  9849. * Copyright 2017 Google LLC
  9850. *
  9851. * Licensed under the Apache License, Version 2.0 (the "License");
  9852. * you may not use this file except in compliance with the License.
  9853. * You may obtain a copy of the License at
  9854. *
  9855. * http://www.apache.org/licenses/LICENSE-2.0
  9856. *
  9857. * Unless required by applicable law or agreed to in writing, software
  9858. * distributed under the License is distributed on an "AS IS" BASIS,
  9859. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9860. * See the License for the specific language governing permissions and
  9861. * limitations under the License.
  9862. */
  9863. /** Serializer for values stored in the LocalStore. */
  9864. class LocalSerializer {
  9865. constructor(remoteSerializer) {
  9866. this.remoteSerializer = remoteSerializer;
  9867. }
  9868. }
  9869. /** Decodes a remote document from storage locally to a Document. */
  9870. function fromDbRemoteDocument(localSerializer, remoteDoc) {
  9871. let doc;
  9872. if (remoteDoc.document) {
  9873. doc = fromDocument(localSerializer.remoteSerializer, remoteDoc.document, !!remoteDoc.hasCommittedMutations);
  9874. }
  9875. else if (remoteDoc.noDocument) {
  9876. const key = DocumentKey.fromSegments(remoteDoc.noDocument.path);
  9877. const version = fromDbTimestamp(remoteDoc.noDocument.readTime);
  9878. doc = MutableDocument.newNoDocument(key, version);
  9879. if (remoteDoc.hasCommittedMutations) {
  9880. doc.setHasCommittedMutations();
  9881. }
  9882. }
  9883. else if (remoteDoc.unknownDocument) {
  9884. const key = DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
  9885. const version = fromDbTimestamp(remoteDoc.unknownDocument.version);
  9886. doc = MutableDocument.newUnknownDocument(key, version);
  9887. }
  9888. else {
  9889. return fail();
  9890. }
  9891. if (remoteDoc.readTime) {
  9892. doc.setReadTime(fromDbTimestampKey(remoteDoc.readTime));
  9893. }
  9894. return doc;
  9895. }
  9896. /** Encodes a document for storage locally. */
  9897. function toDbRemoteDocument(localSerializer, document) {
  9898. const key = document.key;
  9899. const remoteDoc = {
  9900. prefixPath: key.getCollectionPath().popLast().toArray(),
  9901. collectionGroup: key.collectionGroup,
  9902. documentId: key.path.lastSegment(),
  9903. readTime: toDbTimestampKey(document.readTime),
  9904. hasCommittedMutations: document.hasCommittedMutations
  9905. };
  9906. if (document.isFoundDocument()) {
  9907. remoteDoc.document = toDocument(localSerializer.remoteSerializer, document);
  9908. }
  9909. else if (document.isNoDocument()) {
  9910. remoteDoc.noDocument = {
  9911. path: key.path.toArray(),
  9912. readTime: toDbTimestamp(document.version)
  9913. };
  9914. }
  9915. else if (document.isUnknownDocument()) {
  9916. remoteDoc.unknownDocument = {
  9917. path: key.path.toArray(),
  9918. version: toDbTimestamp(document.version)
  9919. };
  9920. }
  9921. else {
  9922. return fail();
  9923. }
  9924. return remoteDoc;
  9925. }
  9926. function toDbTimestampKey(snapshotVersion) {
  9927. const timestamp = snapshotVersion.toTimestamp();
  9928. return [timestamp.seconds, timestamp.nanoseconds];
  9929. }
  9930. function fromDbTimestampKey(dbTimestampKey) {
  9931. const timestamp = new Timestamp(dbTimestampKey[0], dbTimestampKey[1]);
  9932. return SnapshotVersion.fromTimestamp(timestamp);
  9933. }
  9934. function toDbTimestamp(snapshotVersion) {
  9935. const timestamp = snapshotVersion.toTimestamp();
  9936. return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
  9937. }
  9938. function fromDbTimestamp(dbTimestamp) {
  9939. const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
  9940. return SnapshotVersion.fromTimestamp(timestamp);
  9941. }
  9942. /** Encodes a batch of mutations into a DbMutationBatch for local storage. */
  9943. function toDbMutationBatch(localSerializer, userId, batch) {
  9944. const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
  9945. const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
  9946. return {
  9947. userId,
  9948. batchId: batch.batchId,
  9949. localWriteTimeMs: batch.localWriteTime.toMillis(),
  9950. baseMutations: serializedBaseMutations,
  9951. mutations: serializedMutations
  9952. };
  9953. }
  9954. /** Decodes a DbMutationBatch into a MutationBatch */
  9955. function fromDbMutationBatch(localSerializer, dbBatch) {
  9956. const baseMutations = (dbBatch.baseMutations || []).map(m => fromMutation(localSerializer.remoteSerializer, m));
  9957. // Squash old transform mutations into existing patch or set mutations.
  9958. // The replacement of representing `transforms` with `update_transforms`
  9959. // on the SDK means that old `transform` mutations stored in IndexedDB need
  9960. // to be updated to `update_transforms`.
  9961. // TODO(b/174608374): Remove this code once we perform a schema migration.
  9962. for (let i = 0; i < dbBatch.mutations.length - 1; ++i) {
  9963. const currentMutation = dbBatch.mutations[i];
  9964. const hasTransform = i + 1 < dbBatch.mutations.length &&
  9965. dbBatch.mutations[i + 1].transform !== undefined;
  9966. if (hasTransform) {
  9967. const transformMutation = dbBatch.mutations[i + 1];
  9968. currentMutation.updateTransforms =
  9969. transformMutation.transform.fieldTransforms;
  9970. dbBatch.mutations.splice(i + 1, 1);
  9971. ++i;
  9972. }
  9973. }
  9974. const mutations = dbBatch.mutations.map(m => fromMutation(localSerializer.remoteSerializer, m));
  9975. const timestamp = Timestamp.fromMillis(dbBatch.localWriteTimeMs);
  9976. return new MutationBatch(dbBatch.batchId, timestamp, baseMutations, mutations);
  9977. }
  9978. /** Decodes a DbTarget into TargetData */
  9979. function fromDbTarget(dbTarget) {
  9980. const version = fromDbTimestamp(dbTarget.readTime);
  9981. const lastLimboFreeSnapshotVersion = dbTarget.lastLimboFreeSnapshotVersion !== undefined
  9982. ? fromDbTimestamp(dbTarget.lastLimboFreeSnapshotVersion)
  9983. : SnapshotVersion.min();
  9984. let target;
  9985. if (isDocumentQuery(dbTarget.query)) {
  9986. target = fromDocumentsTarget(dbTarget.query);
  9987. }
  9988. else {
  9989. target = fromQueryTarget(dbTarget.query);
  9990. }
  9991. return new TargetData(target, dbTarget.targetId, "TargetPurposeListen" /* TargetPurpose.Listen */, dbTarget.lastListenSequenceNumber, version, lastLimboFreeSnapshotVersion, ByteString.fromBase64String(dbTarget.resumeToken));
  9992. }
  9993. /** Encodes TargetData into a DbTarget for storage locally. */
  9994. function toDbTarget(localSerializer, targetData) {
  9995. const dbTimestamp = toDbTimestamp(targetData.snapshotVersion);
  9996. const dbLastLimboFreeTimestamp = toDbTimestamp(targetData.lastLimboFreeSnapshotVersion);
  9997. let queryProto;
  9998. if (targetIsDocumentTarget(targetData.target)) {
  9999. queryProto = toDocumentsTarget(localSerializer.remoteSerializer, targetData.target);
  10000. }
  10001. else {
  10002. queryProto = toQueryTarget(localSerializer.remoteSerializer, targetData.target);
  10003. }
  10004. // We can't store the resumeToken as a ByteString in IndexedDb, so we
  10005. // convert it to a base64 string for storage.
  10006. const resumeToken = targetData.resumeToken.toBase64();
  10007. // lastListenSequenceNumber is always 0 until we do real GC.
  10008. return {
  10009. targetId: targetData.targetId,
  10010. canonicalId: canonifyTarget(targetData.target),
  10011. readTime: dbTimestamp,
  10012. resumeToken,
  10013. lastListenSequenceNumber: targetData.sequenceNumber,
  10014. lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
  10015. query: queryProto
  10016. };
  10017. }
  10018. /**
  10019. * A helper function for figuring out what kind of query has been stored.
  10020. */
  10021. function isDocumentQuery(dbQuery) {
  10022. return dbQuery.documents !== undefined;
  10023. }
  10024. /** Encodes a DbBundle to a BundleMetadata object. */
  10025. function fromDbBundle(dbBundle) {
  10026. return {
  10027. id: dbBundle.bundleId,
  10028. createTime: fromDbTimestamp(dbBundle.createTime),
  10029. version: dbBundle.version
  10030. };
  10031. }
  10032. /** Encodes a BundleMetadata to a DbBundle. */
  10033. function toDbBundle(metadata) {
  10034. return {
  10035. bundleId: metadata.id,
  10036. createTime: toDbTimestamp(fromVersion(metadata.createTime)),
  10037. version: metadata.version
  10038. };
  10039. }
  10040. /** Encodes a DbNamedQuery to a NamedQuery. */
  10041. function fromDbNamedQuery(dbNamedQuery) {
  10042. return {
  10043. name: dbNamedQuery.name,
  10044. query: fromBundledQuery(dbNamedQuery.bundledQuery),
  10045. readTime: fromDbTimestamp(dbNamedQuery.readTime)
  10046. };
  10047. }
  10048. /** Encodes a NamedQuery from a bundle proto to a DbNamedQuery. */
  10049. function toDbNamedQuery(query) {
  10050. return {
  10051. name: query.name,
  10052. readTime: toDbTimestamp(fromVersion(query.readTime)),
  10053. bundledQuery: query.bundledQuery
  10054. };
  10055. }
  10056. /**
  10057. * Encodes a `BundledQuery` from bundle proto to a Query object.
  10058. *
  10059. * This reconstructs the original query used to build the bundle being loaded,
  10060. * including features exists only in SDKs (for example: limit-to-last).
  10061. */
  10062. function fromBundledQuery(bundledQuery) {
  10063. const query = convertQueryTargetToQuery({
  10064. parent: bundledQuery.parent,
  10065. structuredQuery: bundledQuery.structuredQuery
  10066. });
  10067. if (bundledQuery.limitType === 'LAST') {
  10068. return queryWithLimit(query, query.limit, "L" /* LimitType.Last */);
  10069. }
  10070. return query;
  10071. }
  10072. /** Encodes a NamedQuery proto object to a NamedQuery model object. */
  10073. function fromProtoNamedQuery(namedQuery) {
  10074. return {
  10075. name: namedQuery.name,
  10076. query: fromBundledQuery(namedQuery.bundledQuery),
  10077. readTime: fromVersion(namedQuery.readTime)
  10078. };
  10079. }
  10080. /** Decodes a BundleMetadata proto into a BundleMetadata object. */
  10081. function fromBundleMetadata(metadata) {
  10082. return {
  10083. id: metadata.id,
  10084. version: metadata.version,
  10085. createTime: fromVersion(metadata.createTime)
  10086. };
  10087. }
  10088. /** Encodes a DbDocumentOverlay object to an Overlay model object. */
  10089. function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
  10090. return new Overlay(dbDocumentOverlay.largestBatchId, fromMutation(localSerializer.remoteSerializer, dbDocumentOverlay.overlayMutation));
  10091. }
  10092. /** Decodes an Overlay model object into a DbDocumentOverlay object. */
  10093. function toDbDocumentOverlay(localSerializer, userId, overlay) {
  10094. const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
  10095. return {
  10096. userId,
  10097. collectionPath,
  10098. documentId,
  10099. collectionGroup: overlay.mutation.key.getCollectionGroup(),
  10100. largestBatchId: overlay.largestBatchId,
  10101. overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
  10102. };
  10103. }
  10104. /**
  10105. * Returns the DbDocumentOverlayKey corresponding to the given user and
  10106. * document key.
  10107. */
  10108. function toDbDocumentOverlayKey(userId, docKey) {
  10109. const docId = docKey.path.lastSegment();
  10110. const collectionPath = encodeResourcePath(docKey.path.popLast());
  10111. return [userId, collectionPath, docId];
  10112. }
  10113. function toDbIndexConfiguration(index) {
  10114. return {
  10115. indexId: index.indexId,
  10116. collectionGroup: index.collectionGroup,
  10117. fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
  10118. };
  10119. }
  10120. function fromDbIndexConfiguration(index, state) {
  10121. const decodedState = state
  10122. ? new IndexState(state.sequenceNumber, new IndexOffset(fromDbTimestamp(state.readTime), new DocumentKey(decodeResourcePath(state.documentKey)), state.largestBatchId))
  10123. : IndexState.empty();
  10124. const decodedSegments = index.fields.map(([fieldPath, kind]) => new IndexSegment(FieldPath$1.fromServerFormat(fieldPath), kind));
  10125. return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
  10126. }
  10127. function toDbIndexState(indexId, user, sequenceNumber, offset) {
  10128. return {
  10129. indexId,
  10130. uid: user.uid || '',
  10131. sequenceNumber,
  10132. readTime: toDbTimestamp(offset.readTime),
  10133. documentKey: encodeResourcePath(offset.documentKey.path),
  10134. largestBatchId: offset.largestBatchId
  10135. };
  10136. }
  10137. /**
  10138. * @license
  10139. * Copyright 2020 Google LLC
  10140. *
  10141. * Licensed under the Apache License, Version 2.0 (the "License");
  10142. * you may not use this file except in compliance with the License.
  10143. * You may obtain a copy of the License at
  10144. *
  10145. * http://www.apache.org/licenses/LICENSE-2.0
  10146. *
  10147. * Unless required by applicable law or agreed to in writing, software
  10148. * distributed under the License is distributed on an "AS IS" BASIS,
  10149. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10150. * See the License for the specific language governing permissions and
  10151. * limitations under the License.
  10152. */
  10153. class IndexedDbBundleCache {
  10154. getBundleMetadata(transaction, bundleId) {
  10155. return bundlesStore(transaction)
  10156. .get(bundleId)
  10157. .next(bundle => {
  10158. if (bundle) {
  10159. return fromDbBundle(bundle);
  10160. }
  10161. return undefined;
  10162. });
  10163. }
  10164. saveBundleMetadata(transaction, bundleMetadata) {
  10165. return bundlesStore(transaction).put(toDbBundle(bundleMetadata));
  10166. }
  10167. getNamedQuery(transaction, queryName) {
  10168. return namedQueriesStore(transaction)
  10169. .get(queryName)
  10170. .next(query => {
  10171. if (query) {
  10172. return fromDbNamedQuery(query);
  10173. }
  10174. return undefined;
  10175. });
  10176. }
  10177. saveNamedQuery(transaction, query) {
  10178. return namedQueriesStore(transaction).put(toDbNamedQuery(query));
  10179. }
  10180. }
  10181. /**
  10182. * Helper to get a typed SimpleDbStore for the bundles object store.
  10183. */
  10184. function bundlesStore(txn) {
  10185. return getStore(txn, DbBundleStore);
  10186. }
  10187. /**
  10188. * Helper to get a typed SimpleDbStore for the namedQueries object store.
  10189. */
  10190. function namedQueriesStore(txn) {
  10191. return getStore(txn, DbNamedQueryStore);
  10192. }
  10193. /**
  10194. * @license
  10195. * Copyright 2022 Google LLC
  10196. *
  10197. * Licensed under the Apache License, Version 2.0 (the "License");
  10198. * you may not use this file except in compliance with the License.
  10199. * You may obtain a copy of the License at
  10200. *
  10201. * http://www.apache.org/licenses/LICENSE-2.0
  10202. *
  10203. * Unless required by applicable law or agreed to in writing, software
  10204. * distributed under the License is distributed on an "AS IS" BASIS,
  10205. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10206. * See the License for the specific language governing permissions and
  10207. * limitations under the License.
  10208. */
  10209. /**
  10210. * Implementation of DocumentOverlayCache using IndexedDb.
  10211. */
  10212. class IndexedDbDocumentOverlayCache {
  10213. /**
  10214. * @param serializer - The document serializer.
  10215. * @param userId - The userId for which we are accessing overlays.
  10216. */
  10217. constructor(serializer, userId) {
  10218. this.serializer = serializer;
  10219. this.userId = userId;
  10220. }
  10221. static forUser(serializer, user) {
  10222. const userId = user.uid || '';
  10223. return new IndexedDbDocumentOverlayCache(serializer, userId);
  10224. }
  10225. getOverlay(transaction, key) {
  10226. return documentOverlayStore(transaction)
  10227. .get(toDbDocumentOverlayKey(this.userId, key))
  10228. .next(dbOverlay => {
  10229. if (dbOverlay) {
  10230. return fromDbDocumentOverlay(this.serializer, dbOverlay);
  10231. }
  10232. return null;
  10233. });
  10234. }
  10235. getOverlays(transaction, keys) {
  10236. const result = newOverlayMap();
  10237. return PersistencePromise.forEach(keys, (key) => {
  10238. return this.getOverlay(transaction, key).next(overlay => {
  10239. if (overlay !== null) {
  10240. result.set(key, overlay);
  10241. }
  10242. });
  10243. }).next(() => result);
  10244. }
  10245. saveOverlays(transaction, largestBatchId, overlays) {
  10246. const promises = [];
  10247. overlays.forEach((_, mutation) => {
  10248. const overlay = new Overlay(largestBatchId, mutation);
  10249. promises.push(this.saveOverlay(transaction, overlay));
  10250. });
  10251. return PersistencePromise.waitFor(promises);
  10252. }
  10253. removeOverlaysForBatchId(transaction, documentKeys, batchId) {
  10254. const collectionPaths = new Set();
  10255. // Get the set of unique collection paths.
  10256. documentKeys.forEach(key => collectionPaths.add(encodeResourcePath(key.getCollectionPath())));
  10257. const promises = [];
  10258. collectionPaths.forEach(collectionPath => {
  10259. const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
  10260. /*lowerOpen=*/ false,
  10261. /*upperOpen=*/ true);
  10262. promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
  10263. });
  10264. return PersistencePromise.waitFor(promises);
  10265. }
  10266. getOverlaysForCollection(transaction, collection, sinceBatchId) {
  10267. const result = newOverlayMap();
  10268. const collectionPath = encodeResourcePath(collection);
  10269. // We want batch IDs larger than `sinceBatchId`, and so the lower bound
  10270. // is not inclusive.
  10271. const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
  10272. /*lowerOpen=*/ true);
  10273. return documentOverlayStore(transaction)
  10274. .loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
  10275. .next(dbOverlays => {
  10276. for (const dbOverlay of dbOverlays) {
  10277. const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
  10278. result.set(overlay.getKey(), overlay);
  10279. }
  10280. return result;
  10281. });
  10282. }
  10283. getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
  10284. const result = newOverlayMap();
  10285. let currentBatchId = undefined;
  10286. // We want batch IDs larger than `sinceBatchId`, and so the lower bound
  10287. // is not inclusive.
  10288. const range = IDBKeyRange.bound([this.userId, collectionGroup, sinceBatchId], [this.userId, collectionGroup, Number.POSITIVE_INFINITY],
  10289. /*lowerOpen=*/ true);
  10290. return documentOverlayStore(transaction)
  10291. .iterate({
  10292. index: DbDocumentOverlayCollectionGroupOverlayIndex,
  10293. range
  10294. }, (_, dbOverlay, control) => {
  10295. // We do not want to return partial batch overlays, even if the size
  10296. // of the result set exceeds the given `count` argument. Therefore, we
  10297. // continue to aggregate results even after the result size exceeds
  10298. // `count` if there are more overlays from the `currentBatchId`.
  10299. const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
  10300. if (result.size() < count ||
  10301. overlay.largestBatchId === currentBatchId) {
  10302. result.set(overlay.getKey(), overlay);
  10303. currentBatchId = overlay.largestBatchId;
  10304. }
  10305. else {
  10306. control.done();
  10307. }
  10308. })
  10309. .next(() => result);
  10310. }
  10311. saveOverlay(transaction, overlay) {
  10312. return documentOverlayStore(transaction).put(toDbDocumentOverlay(this.serializer, this.userId, overlay));
  10313. }
  10314. }
  10315. /**
  10316. * Helper to get a typed SimpleDbStore for the document overlay object store.
  10317. */
  10318. function documentOverlayStore(txn) {
  10319. return getStore(txn, DbDocumentOverlayStore);
  10320. }
  10321. /**
  10322. * @license
  10323. * Copyright 2021 Google LLC
  10324. *
  10325. * Licensed under the Apache License, Version 2.0 (the "License");
  10326. * you may not use this file except in compliance with the License.
  10327. * You may obtain a copy of the License at
  10328. *
  10329. * http://www.apache.org/licenses/LICENSE-2.0
  10330. *
  10331. * Unless required by applicable law or agreed to in writing, software
  10332. * distributed under the License is distributed on an "AS IS" BASIS,
  10333. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10334. * See the License for the specific language governing permissions and
  10335. * limitations under the License.
  10336. */
  10337. // Note: This code is copied from the backend. Code that is not used by
  10338. // Firestore was removed.
  10339. const INDEX_TYPE_NULL = 5;
  10340. const INDEX_TYPE_BOOLEAN = 10;
  10341. const INDEX_TYPE_NAN = 13;
  10342. const INDEX_TYPE_NUMBER = 15;
  10343. const INDEX_TYPE_TIMESTAMP = 20;
  10344. const INDEX_TYPE_STRING = 25;
  10345. const INDEX_TYPE_BLOB = 30;
  10346. const INDEX_TYPE_REFERENCE = 37;
  10347. const INDEX_TYPE_GEOPOINT = 45;
  10348. const INDEX_TYPE_ARRAY = 50;
  10349. const INDEX_TYPE_MAP = 55;
  10350. const INDEX_TYPE_REFERENCE_SEGMENT = 60;
  10351. // A terminator that indicates that a truncatable value was not truncated.
  10352. // This must be smaller than all other type labels.
  10353. const NOT_TRUNCATED = 2;
  10354. /** Firestore index value writer. */
  10355. class FirestoreIndexValueWriter {
  10356. constructor() { }
  10357. // The write methods below short-circuit writing terminators for values
  10358. // containing a (terminating) truncated value.
  10359. //
  10360. // As an example, consider the resulting encoding for:
  10361. //
  10362. // ["bar", [2, "foo"]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TERM, TERM, TERM)
  10363. // ["bar", [2, truncated("foo")]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TRUNC)
  10364. // ["bar", truncated(["foo"])] -> (STRING, "bar", TERM, ARRAY. STRING, "foo", TERM, TRUNC)
  10365. /** Writes an index value. */
  10366. writeIndexValue(value, encoder) {
  10367. this.writeIndexValueAux(value, encoder);
  10368. // Write separator to split index values
  10369. // (see go/firestore-storage-format#encodings).
  10370. encoder.writeInfinity();
  10371. }
  10372. writeIndexValueAux(indexValue, encoder) {
  10373. if ('nullValue' in indexValue) {
  10374. this.writeValueTypeLabel(encoder, INDEX_TYPE_NULL);
  10375. }
  10376. else if ('booleanValue' in indexValue) {
  10377. this.writeValueTypeLabel(encoder, INDEX_TYPE_BOOLEAN);
  10378. encoder.writeNumber(indexValue.booleanValue ? 1 : 0);
  10379. }
  10380. else if ('integerValue' in indexValue) {
  10381. this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
  10382. encoder.writeNumber(normalizeNumber(indexValue.integerValue));
  10383. }
  10384. else if ('doubleValue' in indexValue) {
  10385. const n = normalizeNumber(indexValue.doubleValue);
  10386. if (isNaN(n)) {
  10387. this.writeValueTypeLabel(encoder, INDEX_TYPE_NAN);
  10388. }
  10389. else {
  10390. this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
  10391. if (isNegativeZero(n)) {
  10392. // -0.0, 0 and 0.0 are all considered the same
  10393. encoder.writeNumber(0.0);
  10394. }
  10395. else {
  10396. encoder.writeNumber(n);
  10397. }
  10398. }
  10399. }
  10400. else if ('timestampValue' in indexValue) {
  10401. const timestamp = indexValue.timestampValue;
  10402. this.writeValueTypeLabel(encoder, INDEX_TYPE_TIMESTAMP);
  10403. if (typeof timestamp === 'string') {
  10404. encoder.writeString(timestamp);
  10405. }
  10406. else {
  10407. encoder.writeString(`${timestamp.seconds || ''}`);
  10408. encoder.writeNumber(timestamp.nanos || 0);
  10409. }
  10410. }
  10411. else if ('stringValue' in indexValue) {
  10412. this.writeIndexString(indexValue.stringValue, encoder);
  10413. this.writeTruncationMarker(encoder);
  10414. }
  10415. else if ('bytesValue' in indexValue) {
  10416. this.writeValueTypeLabel(encoder, INDEX_TYPE_BLOB);
  10417. encoder.writeBytes(normalizeByteString(indexValue.bytesValue));
  10418. this.writeTruncationMarker(encoder);
  10419. }
  10420. else if ('referenceValue' in indexValue) {
  10421. this.writeIndexEntityRef(indexValue.referenceValue, encoder);
  10422. }
  10423. else if ('geoPointValue' in indexValue) {
  10424. const geoPoint = indexValue.geoPointValue;
  10425. this.writeValueTypeLabel(encoder, INDEX_TYPE_GEOPOINT);
  10426. encoder.writeNumber(geoPoint.latitude || 0);
  10427. encoder.writeNumber(geoPoint.longitude || 0);
  10428. }
  10429. else if ('mapValue' in indexValue) {
  10430. if (isMaxValue(indexValue)) {
  10431. this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER);
  10432. }
  10433. else {
  10434. this.writeIndexMap(indexValue.mapValue, encoder);
  10435. this.writeTruncationMarker(encoder);
  10436. }
  10437. }
  10438. else if ('arrayValue' in indexValue) {
  10439. this.writeIndexArray(indexValue.arrayValue, encoder);
  10440. this.writeTruncationMarker(encoder);
  10441. }
  10442. else {
  10443. fail();
  10444. }
  10445. }
  10446. writeIndexString(stringIndexValue, encoder) {
  10447. this.writeValueTypeLabel(encoder, INDEX_TYPE_STRING);
  10448. this.writeUnlabeledIndexString(stringIndexValue, encoder);
  10449. }
  10450. writeUnlabeledIndexString(stringIndexValue, encoder) {
  10451. encoder.writeString(stringIndexValue);
  10452. }
  10453. writeIndexMap(mapIndexValue, encoder) {
  10454. const map = mapIndexValue.fields || {};
  10455. this.writeValueTypeLabel(encoder, INDEX_TYPE_MAP);
  10456. for (const key of Object.keys(map)) {
  10457. this.writeIndexString(key, encoder);
  10458. this.writeIndexValueAux(map[key], encoder);
  10459. }
  10460. }
  10461. writeIndexArray(arrayIndexValue, encoder) {
  10462. const values = arrayIndexValue.values || [];
  10463. this.writeValueTypeLabel(encoder, INDEX_TYPE_ARRAY);
  10464. for (const element of values) {
  10465. this.writeIndexValueAux(element, encoder);
  10466. }
  10467. }
  10468. writeIndexEntityRef(referenceValue, encoder) {
  10469. this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE);
  10470. const path = DocumentKey.fromName(referenceValue).path;
  10471. path.forEach(segment => {
  10472. this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE_SEGMENT);
  10473. this.writeUnlabeledIndexString(segment, encoder);
  10474. });
  10475. }
  10476. writeValueTypeLabel(encoder, typeOrder) {
  10477. encoder.writeNumber(typeOrder);
  10478. }
  10479. writeTruncationMarker(encoder) {
  10480. // While the SDK does not implement truncation, the truncation marker is
  10481. // used to terminate all variable length values (which are strings, bytes,
  10482. // references, arrays and maps).
  10483. encoder.writeNumber(NOT_TRUNCATED);
  10484. }
  10485. }
  10486. FirestoreIndexValueWriter.INSTANCE = new FirestoreIndexValueWriter();
  10487. /**
  10488. * @license
  10489. * Copyright 2021 Google LLC
  10490. *
  10491. * Licensed under the Apache License, Version 2.0 (the "License");
  10492. * you may not use this file except in compliance with the License.
  10493. * You may obtain a copy of the License at
  10494. *
  10495. * http://www.apache.org/licenses/LICENSE-2.0
  10496. *
  10497. * Unless required by applicable law | agreed to in writing, software
  10498. * distributed under the License is distributed on an "AS IS" BASIS,
  10499. * WITHOUT WARRANTIES | CONDITIONS OF ANY KIND, either express | implied.
  10500. * See the License for the specific language governing permissions and
  10501. * limitations under the License.
  10502. */
  10503. /** These constants are taken from the backend. */
  10504. const MIN_SURROGATE = '\uD800';
  10505. const MAX_SURROGATE = '\uDBFF';
  10506. const ESCAPE1 = 0x00;
  10507. const NULL_BYTE = 0xff; // Combined with ESCAPE1
  10508. const SEPARATOR = 0x01; // Combined with ESCAPE1
  10509. const ESCAPE2 = 0xff;
  10510. const INFINITY = 0xff; // Combined with ESCAPE2
  10511. const FF_BYTE = 0x00; // Combined with ESCAPE2
  10512. const LONG_SIZE = 64;
  10513. const BYTE_SIZE = 8;
  10514. /**
  10515. * The default size of the buffer. This is arbitrary, but likely larger than
  10516. * most index values so that less copies of the underlying buffer will be made.
  10517. * For large values, a single copy will made to double the buffer length.
  10518. */
  10519. const DEFAULT_BUFFER_SIZE = 1024;
  10520. /** Converts a JavaScript number to a byte array (using big endian encoding). */
  10521. function doubleToLongBits(value) {
  10522. const dv = new DataView(new ArrayBuffer(8));
  10523. dv.setFloat64(0, value, /* littleEndian= */ false);
  10524. return new Uint8Array(dv.buffer);
  10525. }
  10526. /**
  10527. * Counts the number of zeros in a byte.
  10528. *
  10529. * Visible for testing.
  10530. */
  10531. function numberOfLeadingZerosInByte(x) {
  10532. if (x === 0) {
  10533. return 8;
  10534. }
  10535. let zeros = 0;
  10536. if (x >> 4 === 0) {
  10537. // Test if the first four bits are zero.
  10538. zeros += 4;
  10539. x = x << 4;
  10540. }
  10541. if (x >> 6 === 0) {
  10542. // Test if the first two (or next two) bits are zero.
  10543. zeros += 2;
  10544. x = x << 2;
  10545. }
  10546. if (x >> 7 === 0) {
  10547. // Test if the remaining bit is zero.
  10548. zeros += 1;
  10549. }
  10550. return zeros;
  10551. }
  10552. /** Counts the number of leading zeros in the given byte array. */
  10553. function numberOfLeadingZeros(bytes) {
  10554. let leadingZeros = 0;
  10555. for (let i = 0; i < 8; ++i) {
  10556. const zeros = numberOfLeadingZerosInByte(bytes[i] & 0xff);
  10557. leadingZeros += zeros;
  10558. if (zeros !== 8) {
  10559. break;
  10560. }
  10561. }
  10562. return leadingZeros;
  10563. }
  10564. /**
  10565. * Returns the number of bytes required to store "value". Leading zero bytes
  10566. * are skipped.
  10567. */
  10568. function unsignedNumLength(value) {
  10569. // This is just the number of bytes for the unsigned representation of the number.
  10570. const numBits = LONG_SIZE - numberOfLeadingZeros(value);
  10571. return Math.ceil(numBits / BYTE_SIZE);
  10572. }
  10573. /**
  10574. * OrderedCodeWriter is a minimal-allocation implementation of the writing
  10575. * behavior defined by the backend.
  10576. *
  10577. * The code is ported from its Java counterpart.
  10578. */
  10579. class OrderedCodeWriter {
  10580. constructor() {
  10581. this.buffer = new Uint8Array(DEFAULT_BUFFER_SIZE);
  10582. this.position = 0;
  10583. }
  10584. writeBytesAscending(value) {
  10585. const it = value[Symbol.iterator]();
  10586. let byte = it.next();
  10587. while (!byte.done) {
  10588. this.writeByteAscending(byte.value);
  10589. byte = it.next();
  10590. }
  10591. this.writeSeparatorAscending();
  10592. }
  10593. writeBytesDescending(value) {
  10594. const it = value[Symbol.iterator]();
  10595. let byte = it.next();
  10596. while (!byte.done) {
  10597. this.writeByteDescending(byte.value);
  10598. byte = it.next();
  10599. }
  10600. this.writeSeparatorDescending();
  10601. }
  10602. /** Writes utf8 bytes into this byte sequence, ascending. */
  10603. writeUtf8Ascending(sequence) {
  10604. for (const c of sequence) {
  10605. const charCode = c.charCodeAt(0);
  10606. if (charCode < 0x80) {
  10607. this.writeByteAscending(charCode);
  10608. }
  10609. else if (charCode < 0x800) {
  10610. this.writeByteAscending((0x0f << 6) | (charCode >>> 6));
  10611. this.writeByteAscending(0x80 | (0x3f & charCode));
  10612. }
  10613. else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
  10614. this.writeByteAscending((0x0f << 5) | (charCode >>> 12));
  10615. this.writeByteAscending(0x80 | (0x3f & (charCode >>> 6)));
  10616. this.writeByteAscending(0x80 | (0x3f & charCode));
  10617. }
  10618. else {
  10619. const codePoint = c.codePointAt(0);
  10620. this.writeByteAscending((0x0f << 4) | (codePoint >>> 18));
  10621. this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 12)));
  10622. this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 6)));
  10623. this.writeByteAscending(0x80 | (0x3f & codePoint));
  10624. }
  10625. }
  10626. this.writeSeparatorAscending();
  10627. }
  10628. /** Writes utf8 bytes into this byte sequence, descending */
  10629. writeUtf8Descending(sequence) {
  10630. for (const c of sequence) {
  10631. const charCode = c.charCodeAt(0);
  10632. if (charCode < 0x80) {
  10633. this.writeByteDescending(charCode);
  10634. }
  10635. else if (charCode < 0x800) {
  10636. this.writeByteDescending((0x0f << 6) | (charCode >>> 6));
  10637. this.writeByteDescending(0x80 | (0x3f & charCode));
  10638. }
  10639. else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
  10640. this.writeByteDescending((0x0f << 5) | (charCode >>> 12));
  10641. this.writeByteDescending(0x80 | (0x3f & (charCode >>> 6)));
  10642. this.writeByteDescending(0x80 | (0x3f & charCode));
  10643. }
  10644. else {
  10645. const codePoint = c.codePointAt(0);
  10646. this.writeByteDescending((0x0f << 4) | (codePoint >>> 18));
  10647. this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 12)));
  10648. this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 6)));
  10649. this.writeByteDescending(0x80 | (0x3f & codePoint));
  10650. }
  10651. }
  10652. this.writeSeparatorDescending();
  10653. }
  10654. writeNumberAscending(val) {
  10655. // Values are encoded with a single byte length prefix, followed by the
  10656. // actual value in big-endian format with leading 0 bytes dropped.
  10657. const value = this.toOrderedBits(val);
  10658. const len = unsignedNumLength(value);
  10659. this.ensureAvailable(1 + len);
  10660. this.buffer[this.position++] = len & 0xff; // Write the length
  10661. for (let i = value.length - len; i < value.length; ++i) {
  10662. this.buffer[this.position++] = value[i] & 0xff;
  10663. }
  10664. }
  10665. writeNumberDescending(val) {
  10666. // Values are encoded with a single byte length prefix, followed by the
  10667. // inverted value in big-endian format with leading 0 bytes dropped.
  10668. const value = this.toOrderedBits(val);
  10669. const len = unsignedNumLength(value);
  10670. this.ensureAvailable(1 + len);
  10671. this.buffer[this.position++] = ~(len & 0xff); // Write the length
  10672. for (let i = value.length - len; i < value.length; ++i) {
  10673. this.buffer[this.position++] = ~(value[i] & 0xff);
  10674. }
  10675. }
  10676. /**
  10677. * Writes the "infinity" byte sequence that sorts after all other byte
  10678. * sequences written in ascending order.
  10679. */
  10680. writeInfinityAscending() {
  10681. this.writeEscapedByteAscending(ESCAPE2);
  10682. this.writeEscapedByteAscending(INFINITY);
  10683. }
  10684. /**
  10685. * Writes the "infinity" byte sequence that sorts before all other byte
  10686. * sequences written in descending order.
  10687. */
  10688. writeInfinityDescending() {
  10689. this.writeEscapedByteDescending(ESCAPE2);
  10690. this.writeEscapedByteDescending(INFINITY);
  10691. }
  10692. /**
  10693. * Resets the buffer such that it is the same as when it was newly
  10694. * constructed.
  10695. */
  10696. reset() {
  10697. this.position = 0;
  10698. }
  10699. seed(encodedBytes) {
  10700. this.ensureAvailable(encodedBytes.length);
  10701. this.buffer.set(encodedBytes, this.position);
  10702. this.position += encodedBytes.length;
  10703. }
  10704. /** Makes a copy of the encoded bytes in this buffer. */
  10705. encodedBytes() {
  10706. return this.buffer.slice(0, this.position);
  10707. }
  10708. /**
  10709. * Encodes `val` into an encoding so that the order matches the IEEE 754
  10710. * floating-point comparison results with the following exceptions:
  10711. * -0.0 < 0.0
  10712. * all non-NaN < NaN
  10713. * NaN = NaN
  10714. */
  10715. toOrderedBits(val) {
  10716. const value = doubleToLongBits(val);
  10717. // Check if the first bit is set. We use a bit mask since value[0] is
  10718. // encoded as a number from 0 to 255.
  10719. const isNegative = (value[0] & 0x80) !== 0;
  10720. // Revert the two complement to get natural ordering
  10721. value[0] ^= isNegative ? 0xff : 0x80;
  10722. for (let i = 1; i < value.length; ++i) {
  10723. value[i] ^= isNegative ? 0xff : 0x00;
  10724. }
  10725. return value;
  10726. }
  10727. /** Writes a single byte ascending to the buffer. */
  10728. writeByteAscending(b) {
  10729. const masked = b & 0xff;
  10730. if (masked === ESCAPE1) {
  10731. this.writeEscapedByteAscending(ESCAPE1);
  10732. this.writeEscapedByteAscending(NULL_BYTE);
  10733. }
  10734. else if (masked === ESCAPE2) {
  10735. this.writeEscapedByteAscending(ESCAPE2);
  10736. this.writeEscapedByteAscending(FF_BYTE);
  10737. }
  10738. else {
  10739. this.writeEscapedByteAscending(masked);
  10740. }
  10741. }
  10742. /** Writes a single byte descending to the buffer. */
  10743. writeByteDescending(b) {
  10744. const masked = b & 0xff;
  10745. if (masked === ESCAPE1) {
  10746. this.writeEscapedByteDescending(ESCAPE1);
  10747. this.writeEscapedByteDescending(NULL_BYTE);
  10748. }
  10749. else if (masked === ESCAPE2) {
  10750. this.writeEscapedByteDescending(ESCAPE2);
  10751. this.writeEscapedByteDescending(FF_BYTE);
  10752. }
  10753. else {
  10754. this.writeEscapedByteDescending(b);
  10755. }
  10756. }
  10757. writeSeparatorAscending() {
  10758. this.writeEscapedByteAscending(ESCAPE1);
  10759. this.writeEscapedByteAscending(SEPARATOR);
  10760. }
  10761. writeSeparatorDescending() {
  10762. this.writeEscapedByteDescending(ESCAPE1);
  10763. this.writeEscapedByteDescending(SEPARATOR);
  10764. }
  10765. writeEscapedByteAscending(b) {
  10766. this.ensureAvailable(1);
  10767. this.buffer[this.position++] = b;
  10768. }
  10769. writeEscapedByteDescending(b) {
  10770. this.ensureAvailable(1);
  10771. this.buffer[this.position++] = ~b;
  10772. }
  10773. ensureAvailable(bytes) {
  10774. const minCapacity = bytes + this.position;
  10775. if (minCapacity <= this.buffer.length) {
  10776. return;
  10777. }
  10778. // Try doubling.
  10779. let newLength = this.buffer.length * 2;
  10780. // Still not big enough? Just allocate the right size.
  10781. if (newLength < minCapacity) {
  10782. newLength = minCapacity;
  10783. }
  10784. // Create the new buffer.
  10785. const newBuffer = new Uint8Array(newLength);
  10786. newBuffer.set(this.buffer); // copy old data
  10787. this.buffer = newBuffer;
  10788. }
  10789. }
  10790. class AscendingIndexByteEncoder {
  10791. constructor(orderedCode) {
  10792. this.orderedCode = orderedCode;
  10793. }
  10794. writeBytes(value) {
  10795. this.orderedCode.writeBytesAscending(value);
  10796. }
  10797. writeString(value) {
  10798. this.orderedCode.writeUtf8Ascending(value);
  10799. }
  10800. writeNumber(value) {
  10801. this.orderedCode.writeNumberAscending(value);
  10802. }
  10803. writeInfinity() {
  10804. this.orderedCode.writeInfinityAscending();
  10805. }
  10806. }
  10807. class DescendingIndexByteEncoder {
  10808. constructor(orderedCode) {
  10809. this.orderedCode = orderedCode;
  10810. }
  10811. writeBytes(value) {
  10812. this.orderedCode.writeBytesDescending(value);
  10813. }
  10814. writeString(value) {
  10815. this.orderedCode.writeUtf8Descending(value);
  10816. }
  10817. writeNumber(value) {
  10818. this.orderedCode.writeNumberDescending(value);
  10819. }
  10820. writeInfinity() {
  10821. this.orderedCode.writeInfinityDescending();
  10822. }
  10823. }
  10824. /**
  10825. * Implements `DirectionalIndexByteEncoder` using `OrderedCodeWriter` for the
  10826. * actual encoding.
  10827. */
  10828. class IndexByteEncoder {
  10829. constructor() {
  10830. this.orderedCode = new OrderedCodeWriter();
  10831. this.ascending = new AscendingIndexByteEncoder(this.orderedCode);
  10832. this.descending = new DescendingIndexByteEncoder(this.orderedCode);
  10833. }
  10834. seed(encodedBytes) {
  10835. this.orderedCode.seed(encodedBytes);
  10836. }
  10837. forKind(kind) {
  10838. return kind === 0 /* IndexKind.ASCENDING */ ? this.ascending : this.descending;
  10839. }
  10840. encodedBytes() {
  10841. return this.orderedCode.encodedBytes();
  10842. }
  10843. reset() {
  10844. this.orderedCode.reset();
  10845. }
  10846. }
  10847. /**
  10848. * @license
  10849. * Copyright 2022 Google LLC
  10850. *
  10851. * Licensed under the Apache License, Version 2.0 (the "License");
  10852. * you may not use this file except in compliance with the License.
  10853. * You may obtain a copy of the License at
  10854. *
  10855. * http://www.apache.org/licenses/LICENSE-2.0
  10856. *
  10857. * Unless required by applicable law or agreed to in writing, software
  10858. * distributed under the License is distributed on an "AS IS" BASIS,
  10859. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10860. * See the License for the specific language governing permissions and
  10861. * limitations under the License.
  10862. */
  10863. /** Represents an index entry saved by the SDK in persisted storage. */
  10864. class IndexEntry {
  10865. constructor(indexId, documentKey, arrayValue, directionalValue) {
  10866. this.indexId = indexId;
  10867. this.documentKey = documentKey;
  10868. this.arrayValue = arrayValue;
  10869. this.directionalValue = directionalValue;
  10870. }
  10871. /**
  10872. * Returns an IndexEntry entry that sorts immediately after the current
  10873. * directional value.
  10874. */
  10875. successor() {
  10876. const currentLength = this.directionalValue.length;
  10877. const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
  10878. ? currentLength + 1
  10879. : currentLength;
  10880. const successor = new Uint8Array(newLength);
  10881. successor.set(this.directionalValue, 0);
  10882. if (newLength !== currentLength) {
  10883. successor.set([0], this.directionalValue.length);
  10884. }
  10885. else {
  10886. ++successor[successor.length - 1];
  10887. }
  10888. return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
  10889. }
  10890. }
  10891. function indexEntryComparator(left, right) {
  10892. let cmp = left.indexId - right.indexId;
  10893. if (cmp !== 0) {
  10894. return cmp;
  10895. }
  10896. cmp = compareByteArrays(left.arrayValue, right.arrayValue);
  10897. if (cmp !== 0) {
  10898. return cmp;
  10899. }
  10900. cmp = compareByteArrays(left.directionalValue, right.directionalValue);
  10901. if (cmp !== 0) {
  10902. return cmp;
  10903. }
  10904. return DocumentKey.comparator(left.documentKey, right.documentKey);
  10905. }
  10906. function compareByteArrays(left, right) {
  10907. for (let i = 0; i < left.length && i < right.length; ++i) {
  10908. const compare = left[i] - right[i];
  10909. if (compare !== 0) {
  10910. return compare;
  10911. }
  10912. }
  10913. return left.length - right.length;
  10914. }
  10915. /**
  10916. * @license
  10917. * Copyright 2022 Google LLC
  10918. *
  10919. * Licensed under the Apache License, Version 2.0 (the "License");
  10920. * you may not use this file except in compliance with the License.
  10921. * You may obtain a copy of the License at
  10922. *
  10923. * http://www.apache.org/licenses/LICENSE-2.0
  10924. *
  10925. * Unless required by applicable law or agreed to in writing, software
  10926. * distributed under the License is distributed on an "AS IS" BASIS,
  10927. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10928. * See the License for the specific language governing permissions and
  10929. * limitations under the License.
  10930. */
  10931. /**
  10932. * A light query planner for Firestore.
  10933. *
  10934. * This class matches a `FieldIndex` against a Firestore Query `Target`. It
  10935. * determines whether a given index can be used to serve the specified target.
  10936. *
  10937. * The following table showcases some possible index configurations:
  10938. *
  10939. * Query | Index
  10940. * -----------------------------------------------------------------------------
  10941. * where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
  10942. * where('a', '==', 'a').where('b', '==', 'b') | a ASC
  10943. * where('a', '==', 'a').where('b', '==', 'b') | b DESC
  10944. * where('a', '>=', 'a').orderBy('a') | a ASC
  10945. * where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
  10946. * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
  10947. * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
  10948. * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
  10949. * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
  10950. */
  10951. class TargetIndexMatcher {
  10952. constructor(target) {
  10953. this.collectionId =
  10954. target.collectionGroup != null
  10955. ? target.collectionGroup
  10956. : target.path.lastSegment();
  10957. this.orderBys = target.orderBy;
  10958. this.equalityFilters = [];
  10959. for (const filter of target.filters) {
  10960. const fieldFilter = filter;
  10961. if (fieldFilter.isInequality()) {
  10962. this.inequalityFilter = fieldFilter;
  10963. }
  10964. else {
  10965. this.equalityFilters.push(fieldFilter);
  10966. }
  10967. }
  10968. }
  10969. /**
  10970. * Returns whether the index can be used to serve the TargetIndexMatcher's
  10971. * target.
  10972. *
  10973. * An index is considered capable of serving the target when:
  10974. * - The target uses all index segments for its filters and orderBy clauses.
  10975. * The target can have additional filter and orderBy clauses, but not
  10976. * fewer.
  10977. * - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
  10978. * have a corresponding `CONTAINS` segment.
  10979. * - All directional index segments can be mapped to the target as a series of
  10980. * equality filters, a single inequality filter and a series of orderBy
  10981. * clauses.
  10982. * - The segments that represent the equality filters may appear out of order.
  10983. * - The optional segment for the inequality filter must appear after all
  10984. * equality segments.
  10985. * - The segments that represent that orderBy clause of the target must appear
  10986. * in order after all equality and inequality segments. Single orderBy
  10987. * clauses cannot be skipped, but a continuous orderBy suffix may be
  10988. * omitted.
  10989. */
  10990. servedByIndex(index) {
  10991. hardAssert(index.collectionGroup === this.collectionId);
  10992. // If there is an array element, find a matching filter.
  10993. const arraySegment = fieldIndexGetArraySegment(index);
  10994. if (arraySegment !== undefined &&
  10995. !this.hasMatchingEqualityFilter(arraySegment)) {
  10996. return false;
  10997. }
  10998. const segments = fieldIndexGetDirectionalSegments(index);
  10999. let equalitySegments = new Set();
  11000. let segmentIndex = 0;
  11001. let orderBysIndex = 0;
  11002. // Process all equalities first. Equalities can appear out of order.
  11003. for (; segmentIndex < segments.length; ++segmentIndex) {
  11004. // We attempt to greedily match all segments to equality filters. If a
  11005. // filter matches an index segment, we can mark the segment as used.
  11006. if (this.hasMatchingEqualityFilter(segments[segmentIndex])) {
  11007. equalitySegments = equalitySegments.add(segments[segmentIndex].fieldPath.canonicalString());
  11008. }
  11009. else {
  11010. // If we cannot find a matching filter, we need to verify whether the
  11011. // remaining segments map to the target's inequality and its orderBy
  11012. // clauses.
  11013. break;
  11014. }
  11015. }
  11016. // If we already have processed all segments, all segments are used to serve
  11017. // the equality filters and we do not need to map any segments to the
  11018. // target's inequality and orderBy clauses.
  11019. if (segmentIndex === segments.length) {
  11020. return true;
  11021. }
  11022. if (this.inequalityFilter !== undefined) {
  11023. // If there is an inequality filter and the field was not in one of the
  11024. // equality filters above, the next segment must match both the filter
  11025. // and the first orderBy clause.
  11026. if (!equalitySegments.has(this.inequalityFilter.field.canonicalString())) {
  11027. const segment = segments[segmentIndex];
  11028. if (!this.matchesFilter(this.inequalityFilter, segment) ||
  11029. !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
  11030. return false;
  11031. }
  11032. }
  11033. ++segmentIndex;
  11034. }
  11035. // All remaining segments need to represent the prefix of the target's
  11036. // orderBy.
  11037. for (; segmentIndex < segments.length; ++segmentIndex) {
  11038. const segment = segments[segmentIndex];
  11039. if (orderBysIndex >= this.orderBys.length ||
  11040. !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
  11041. return false;
  11042. }
  11043. }
  11044. return true;
  11045. }
  11046. hasMatchingEqualityFilter(segment) {
  11047. for (const filter of this.equalityFilters) {
  11048. if (this.matchesFilter(filter, segment)) {
  11049. return true;
  11050. }
  11051. }
  11052. return false;
  11053. }
  11054. matchesFilter(filter, segment) {
  11055. if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
  11056. return false;
  11057. }
  11058. const isArrayOperator = filter.op === "array-contains" /* Operator.ARRAY_CONTAINS */ ||
  11059. filter.op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */;
  11060. return (segment.kind === 2 /* IndexKind.CONTAINS */) === isArrayOperator;
  11061. }
  11062. matchesOrderBy(orderBy, segment) {
  11063. if (!orderBy.field.isEqual(segment.fieldPath)) {
  11064. return false;
  11065. }
  11066. return ((segment.kind === 0 /* IndexKind.ASCENDING */ &&
  11067. orderBy.dir === "asc" /* Direction.ASCENDING */) ||
  11068. (segment.kind === 1 /* IndexKind.DESCENDING */ &&
  11069. orderBy.dir === "desc" /* Direction.DESCENDING */));
  11070. }
  11071. }
  11072. /**
  11073. * @license
  11074. * Copyright 2022 Google LLC
  11075. *
  11076. * Licensed under the Apache License, Version 2.0 (the "License");
  11077. * you may not use this file except in compliance with the License.
  11078. * You may obtain a copy of the License at
  11079. *
  11080. * http://www.apache.org/licenses/LICENSE-2.0
  11081. *
  11082. * Unless required by applicable law or agreed to in writing, software
  11083. * distributed under the License is distributed on an "AS IS" BASIS,
  11084. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11085. * See the License for the specific language governing permissions and
  11086. * limitations under the License.
  11087. */
  11088. /**
  11089. * Provides utility functions that help with boolean logic transformations needed for handling
  11090. * complex filters used in queries.
  11091. */
  11092. /**
  11093. * The `in` filter is only a syntactic sugar over a disjunction of equalities. For instance: `a in
  11094. * [1,2,3]` is in fact `a==1 || a==2 || a==3`. This method expands any `in` filter in the given
  11095. * input into a disjunction of equality filters and returns the expanded filter.
  11096. */
  11097. function computeInExpansion(filter) {
  11098. var _a, _b;
  11099. hardAssert(filter instanceof FieldFilter || filter instanceof CompositeFilter);
  11100. if (filter instanceof FieldFilter) {
  11101. if (filter instanceof InFilter) {
  11102. const expandedFilters = ((_b = (_a = filter.value.arrayValue) === null || _a === void 0 ? void 0 : _a.values) === null || _b === void 0 ? void 0 : _b.map(value => FieldFilter.create(filter.field, "==" /* Operator.EQUAL */, value))) || [];
  11103. return CompositeFilter.create(expandedFilters, "or" /* CompositeOperator.OR */);
  11104. }
  11105. else {
  11106. // We have reached other kinds of field filters.
  11107. return filter;
  11108. }
  11109. }
  11110. // We have a composite filter.
  11111. const expandedFilters = filter.filters.map(subfilter => computeInExpansion(subfilter));
  11112. return CompositeFilter.create(expandedFilters, filter.op);
  11113. }
  11114. /**
  11115. * Given a composite filter, returns the list of terms in its disjunctive normal form.
  11116. *
  11117. * <p>Each element in the return value is one term of the resulting DNF. For instance: For the
  11118. * input: (A || B) && C, the DNF form is: (A && C) || (B && C), and the return value is a list
  11119. * with two elements: a composite filter that performs (A && C), and a composite filter that
  11120. * performs (B && C).
  11121. *
  11122. * @param filter the composite filter to calculate DNF transform for.
  11123. * @return the terms in the DNF transform.
  11124. */
  11125. function getDnfTerms(filter) {
  11126. if (filter.getFilters().length === 0) {
  11127. return [];
  11128. }
  11129. const result = computeDistributedNormalForm(computeInExpansion(filter));
  11130. hardAssert(isDisjunctiveNormalForm(result));
  11131. if (isSingleFieldFilter(result) || isFlatConjunction(result)) {
  11132. return [result];
  11133. }
  11134. return result.getFilters();
  11135. }
  11136. /** Returns true if the given filter is a single field filter. e.g. (a == 10). */
  11137. function isSingleFieldFilter(filter) {
  11138. return filter instanceof FieldFilter;
  11139. }
  11140. /**
  11141. * Returns true if the given filter is the conjunction of one or more field filters. e.g. (a == 10
  11142. * && b == 20)
  11143. */
  11144. function isFlatConjunction(filter) {
  11145. return (filter instanceof CompositeFilter &&
  11146. compositeFilterIsFlatConjunction(filter));
  11147. }
  11148. /**
  11149. * Returns whether or not the given filter is in disjunctive normal form (DNF).
  11150. *
  11151. * <p>In boolean logic, a disjunctive normal form (DNF) is a canonical normal form of a logical
  11152. * formula consisting of a disjunction of conjunctions; it can also be described as an OR of ANDs.
  11153. *
  11154. * <p>For more info, visit: https://en.wikipedia.org/wiki/Disjunctive_normal_form
  11155. */
  11156. function isDisjunctiveNormalForm(filter) {
  11157. return (isSingleFieldFilter(filter) ||
  11158. isFlatConjunction(filter) ||
  11159. isDisjunctionOfFieldFiltersAndFlatConjunctions(filter));
  11160. }
  11161. /**
  11162. * Returns true if the given filter is the disjunction of one or more "flat conjunctions" and
  11163. * field filters. e.g. (a == 10) || (b==20 && c==30)
  11164. */
  11165. function isDisjunctionOfFieldFiltersAndFlatConjunctions(filter) {
  11166. if (filter instanceof CompositeFilter) {
  11167. if (compositeFilterIsDisjunction(filter)) {
  11168. for (const subFilter of filter.getFilters()) {
  11169. if (!isSingleFieldFilter(subFilter) && !isFlatConjunction(subFilter)) {
  11170. return false;
  11171. }
  11172. }
  11173. return true;
  11174. }
  11175. }
  11176. return false;
  11177. }
  11178. function computeDistributedNormalForm(filter) {
  11179. hardAssert(filter instanceof FieldFilter || filter instanceof CompositeFilter);
  11180. if (filter instanceof FieldFilter) {
  11181. return filter;
  11182. }
  11183. if (filter.filters.length === 1) {
  11184. return computeDistributedNormalForm(filter.filters[0]);
  11185. }
  11186. // Compute DNF for each of the subfilters first
  11187. const result = filter.filters.map(subfilter => computeDistributedNormalForm(subfilter));
  11188. let newFilter = CompositeFilter.create(result, filter.op);
  11189. newFilter = applyAssociation(newFilter);
  11190. if (isDisjunctiveNormalForm(newFilter)) {
  11191. return newFilter;
  11192. }
  11193. hardAssert(newFilter instanceof CompositeFilter);
  11194. hardAssert(compositeFilterIsConjunction(newFilter));
  11195. hardAssert(newFilter.filters.length > 1);
  11196. return newFilter.filters.reduce((runningResult, filter) => applyDistribution(runningResult, filter));
  11197. }
  11198. function applyDistribution(lhs, rhs) {
  11199. hardAssert(lhs instanceof FieldFilter || lhs instanceof CompositeFilter);
  11200. hardAssert(rhs instanceof FieldFilter || rhs instanceof CompositeFilter);
  11201. let result;
  11202. if (lhs instanceof FieldFilter) {
  11203. if (rhs instanceof FieldFilter) {
  11204. // FieldFilter FieldFilter
  11205. result = applyDistributionFieldFilters(lhs, rhs);
  11206. }
  11207. else {
  11208. // FieldFilter CompositeFilter
  11209. result = applyDistributionFieldAndCompositeFilters(lhs, rhs);
  11210. }
  11211. }
  11212. else {
  11213. if (rhs instanceof FieldFilter) {
  11214. // CompositeFilter FieldFilter
  11215. result = applyDistributionFieldAndCompositeFilters(rhs, lhs);
  11216. }
  11217. else {
  11218. // CompositeFilter CompositeFilter
  11219. result = applyDistributionCompositeFilters(lhs, rhs);
  11220. }
  11221. }
  11222. return applyAssociation(result);
  11223. }
  11224. function applyDistributionFieldFilters(lhs, rhs) {
  11225. // Conjunction distribution for two field filters is the conjunction of them.
  11226. return CompositeFilter.create([lhs, rhs], "and" /* CompositeOperator.AND */);
  11227. }
  11228. function applyDistributionCompositeFilters(lhs, rhs) {
  11229. hardAssert(lhs.filters.length > 0 && rhs.filters.length > 0);
  11230. // There are four cases:
  11231. // (A & B) & (C & D) --> (A & B & C & D)
  11232. // (A & B) & (C | D) --> (A & B & C) | (A & B & D)
  11233. // (A | B) & (C & D) --> (C & D & A) | (C & D & B)
  11234. // (A | B) & (C | D) --> (A & C) | (A & D) | (B & C) | (B & D)
  11235. // Case 1 is a merge.
  11236. if (compositeFilterIsConjunction(lhs) && compositeFilterIsConjunction(rhs)) {
  11237. return compositeFilterWithAddedFilters(lhs, rhs.getFilters());
  11238. }
  11239. // Case 2,3,4 all have at least one side (lhs or rhs) that is a disjunction. In all three cases
  11240. // we should take each element of the disjunction and distribute it over the other side, and
  11241. // return the disjunction of the distribution results.
  11242. const disjunctionSide = compositeFilterIsDisjunction(lhs) ? lhs : rhs;
  11243. const otherSide = compositeFilterIsDisjunction(lhs) ? rhs : lhs;
  11244. const results = disjunctionSide.filters.map(subfilter => applyDistribution(subfilter, otherSide));
  11245. return CompositeFilter.create(results, "or" /* CompositeOperator.OR */);
  11246. }
  11247. function applyDistributionFieldAndCompositeFilters(fieldFilter, compositeFilter) {
  11248. // There are two cases:
  11249. // A & (B & C) --> (A & B & C)
  11250. // A & (B | C) --> (A & B) | (A & C)
  11251. if (compositeFilterIsConjunction(compositeFilter)) {
  11252. // Case 1
  11253. return compositeFilterWithAddedFilters(compositeFilter, fieldFilter.getFilters());
  11254. }
  11255. else {
  11256. // Case 2
  11257. const newFilters = compositeFilter.filters.map(subfilter => applyDistribution(fieldFilter, subfilter));
  11258. return CompositeFilter.create(newFilters, "or" /* CompositeOperator.OR */);
  11259. }
  11260. }
  11261. /**
  11262. * Applies the associativity property to the given filter and returns the resulting filter.
  11263. *
  11264. * <ul>
  11265. * <li>A | (B | C) == (A | B) | C == (A | B | C)
  11266. * <li>A & (B & C) == (A & B) & C == (A & B & C)
  11267. * </ul>
  11268. *
  11269. * <p>For more info, visit: https://en.wikipedia.org/wiki/Associative_property#Propositional_logic
  11270. */
  11271. function applyAssociation(filter) {
  11272. hardAssert(filter instanceof FieldFilter || filter instanceof CompositeFilter);
  11273. if (filter instanceof FieldFilter) {
  11274. return filter;
  11275. }
  11276. const filters = filter.getFilters();
  11277. // If the composite filter only contains 1 filter, apply associativity to it.
  11278. if (filters.length === 1) {
  11279. return applyAssociation(filters[0]);
  11280. }
  11281. // Associativity applied to a flat composite filter results is itself.
  11282. if (compositeFilterIsFlat(filter)) {
  11283. return filter;
  11284. }
  11285. // First apply associativity to all subfilters. This will in turn recursively apply
  11286. // associativity to all nested composite filters and field filters.
  11287. const updatedFilters = filters.map(subfilter => applyAssociation(subfilter));
  11288. // For composite subfilters that perform the same kind of logical operation as `compositeFilter`
  11289. // take out their filters and add them to `compositeFilter`. For example:
  11290. // compositeFilter = (A | (B | C | D))
  11291. // compositeSubfilter = (B | C | D)
  11292. // Result: (A | B | C | D)
  11293. // Note that the `compositeSubfilter` has been eliminated, and its filters (B, C, D) have been
  11294. // added to the top-level "compositeFilter".
  11295. const newSubfilters = [];
  11296. updatedFilters.forEach(subfilter => {
  11297. if (subfilter instanceof FieldFilter) {
  11298. newSubfilters.push(subfilter);
  11299. }
  11300. else if (subfilter instanceof CompositeFilter) {
  11301. if (subfilter.op === filter.op) {
  11302. // compositeFilter: (A | (B | C))
  11303. // compositeSubfilter: (B | C)
  11304. // Result: (A | B | C)
  11305. newSubfilters.push(...subfilter.filters);
  11306. }
  11307. else {
  11308. // compositeFilter: (A | (B & C))
  11309. // compositeSubfilter: (B & C)
  11310. // Result: (A | (B & C))
  11311. newSubfilters.push(subfilter);
  11312. }
  11313. }
  11314. });
  11315. if (newSubfilters.length === 1) {
  11316. return newSubfilters[0];
  11317. }
  11318. return CompositeFilter.create(newSubfilters, filter.op);
  11319. }
  11320. /**
  11321. * @license
  11322. * Copyright 2019 Google LLC
  11323. *
  11324. * Licensed under the Apache License, Version 2.0 (the "License");
  11325. * you may not use this file except in compliance with the License.
  11326. * You may obtain a copy of the License at
  11327. *
  11328. * http://www.apache.org/licenses/LICENSE-2.0
  11329. *
  11330. * Unless required by applicable law or agreed to in writing, software
  11331. * distributed under the License is distributed on an "AS IS" BASIS,
  11332. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11333. * See the License for the specific language governing permissions and
  11334. * limitations under the License.
  11335. */
  11336. /**
  11337. * An in-memory implementation of IndexManager.
  11338. */
  11339. class MemoryIndexManager {
  11340. constructor() {
  11341. this.collectionParentIndex = new MemoryCollectionParentIndex();
  11342. }
  11343. addToCollectionParentIndex(transaction, collectionPath) {
  11344. this.collectionParentIndex.add(collectionPath);
  11345. return PersistencePromise.resolve();
  11346. }
  11347. getCollectionParents(transaction, collectionId) {
  11348. return PersistencePromise.resolve(this.collectionParentIndex.getEntries(collectionId));
  11349. }
  11350. addFieldIndex(transaction, index) {
  11351. // Field indices are not supported with memory persistence.
  11352. return PersistencePromise.resolve();
  11353. }
  11354. deleteFieldIndex(transaction, index) {
  11355. // Field indices are not supported with memory persistence.
  11356. return PersistencePromise.resolve();
  11357. }
  11358. getDocumentsMatchingTarget(transaction, target) {
  11359. // Field indices are not supported with memory persistence.
  11360. return PersistencePromise.resolve(null);
  11361. }
  11362. getIndexType(transaction, target) {
  11363. // Field indices are not supported with memory persistence.
  11364. return PersistencePromise.resolve(0 /* IndexType.NONE */);
  11365. }
  11366. getFieldIndexes(transaction, collectionGroup) {
  11367. // Field indices are not supported with memory persistence.
  11368. return PersistencePromise.resolve([]);
  11369. }
  11370. getNextCollectionGroupToUpdate(transaction) {
  11371. // Field indices are not supported with memory persistence.
  11372. return PersistencePromise.resolve(null);
  11373. }
  11374. getMinOffset(transaction, target) {
  11375. return PersistencePromise.resolve(IndexOffset.min());
  11376. }
  11377. getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
  11378. return PersistencePromise.resolve(IndexOffset.min());
  11379. }
  11380. updateCollectionGroup(transaction, collectionGroup, offset) {
  11381. // Field indices are not supported with memory persistence.
  11382. return PersistencePromise.resolve();
  11383. }
  11384. updateIndexEntries(transaction, documents) {
  11385. // Field indices are not supported with memory persistence.
  11386. return PersistencePromise.resolve();
  11387. }
  11388. }
  11389. /**
  11390. * Internal implementation of the collection-parent index exposed by MemoryIndexManager.
  11391. * Also used for in-memory caching by IndexedDbIndexManager and initial index population
  11392. * in indexeddb_schema.ts
  11393. */
  11394. class MemoryCollectionParentIndex {
  11395. constructor() {
  11396. this.index = {};
  11397. }
  11398. // Returns false if the entry already existed.
  11399. add(collectionPath) {
  11400. const collectionId = collectionPath.lastSegment();
  11401. const parentPath = collectionPath.popLast();
  11402. const existingParents = this.index[collectionId] ||
  11403. new SortedSet(ResourcePath.comparator);
  11404. const added = !existingParents.has(parentPath);
  11405. this.index[collectionId] = existingParents.add(parentPath);
  11406. return added;
  11407. }
  11408. has(collectionPath) {
  11409. const collectionId = collectionPath.lastSegment();
  11410. const parentPath = collectionPath.popLast();
  11411. const existingParents = this.index[collectionId];
  11412. return existingParents && existingParents.has(parentPath);
  11413. }
  11414. getEntries(collectionId) {
  11415. const parentPaths = this.index[collectionId] ||
  11416. new SortedSet(ResourcePath.comparator);
  11417. return parentPaths.toArray();
  11418. }
  11419. }
  11420. /**
  11421. * @license
  11422. * Copyright 2019 Google LLC
  11423. *
  11424. * Licensed under the Apache License, Version 2.0 (the "License");
  11425. * you may not use this file except in compliance with the License.
  11426. * You may obtain a copy of the License at
  11427. *
  11428. * http://www.apache.org/licenses/LICENSE-2.0
  11429. *
  11430. * Unless required by applicable law or agreed to in writing, software
  11431. * distributed under the License is distributed on an "AS IS" BASIS,
  11432. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11433. * See the License for the specific language governing permissions and
  11434. * limitations under the License.
  11435. */
  11436. const LOG_TAG$f = 'IndexedDbIndexManager';
  11437. const EMPTY_VALUE = new Uint8Array(0);
  11438. /**
  11439. * A persisted implementation of IndexManager.
  11440. *
  11441. * PORTING NOTE: Unlike iOS and Android, the Web SDK does not memoize index
  11442. * data as it supports multi-tab access.
  11443. */
  11444. class IndexedDbIndexManager {
  11445. constructor(user, databaseId) {
  11446. this.user = user;
  11447. this.databaseId = databaseId;
  11448. /**
  11449. * An in-memory copy of the index entries we've already written since the SDK
  11450. * launched. Used to avoid re-writing the same entry repeatedly.
  11451. *
  11452. * This is *NOT* a complete cache of what's in persistence and so can never be
  11453. * used to satisfy reads.
  11454. */
  11455. this.collectionParentsCache = new MemoryCollectionParentIndex();
  11456. /**
  11457. * Maps from a target to its equivalent list of sub-targets. Each sub-target
  11458. * contains only one term from the target's disjunctive normal form (DNF).
  11459. */
  11460. this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
  11461. this.uid = user.uid || '';
  11462. }
  11463. /**
  11464. * Adds a new entry to the collection parent index.
  11465. *
  11466. * Repeated calls for the same collectionPath should be avoided within a
  11467. * transaction as IndexedDbIndexManager only caches writes once a transaction
  11468. * has been committed.
  11469. */
  11470. addToCollectionParentIndex(transaction, collectionPath) {
  11471. if (!this.collectionParentsCache.has(collectionPath)) {
  11472. const collectionId = collectionPath.lastSegment();
  11473. const parentPath = collectionPath.popLast();
  11474. transaction.addOnCommittedListener(() => {
  11475. // Add the collection to the in memory cache only if the transaction was
  11476. // successfully committed.
  11477. this.collectionParentsCache.add(collectionPath);
  11478. });
  11479. const collectionParent = {
  11480. collectionId,
  11481. parent: encodeResourcePath(parentPath)
  11482. };
  11483. return collectionParentsStore(transaction).put(collectionParent);
  11484. }
  11485. return PersistencePromise.resolve();
  11486. }
  11487. getCollectionParents(transaction, collectionId) {
  11488. const parentPaths = [];
  11489. const range = IDBKeyRange.bound([collectionId, ''], [immediateSuccessor(collectionId), ''],
  11490. /*lowerOpen=*/ false,
  11491. /*upperOpen=*/ true);
  11492. return collectionParentsStore(transaction)
  11493. .loadAll(range)
  11494. .next(entries => {
  11495. for (const entry of entries) {
  11496. // This collectionId guard shouldn't be necessary (and isn't as long
  11497. // as we're running in a real browser), but there's a bug in
  11498. // indexeddbshim that breaks our range in our tests running in node:
  11499. // https://github.com/axemclion/IndexedDBShim/issues/334
  11500. if (entry.collectionId !== collectionId) {
  11501. break;
  11502. }
  11503. parentPaths.push(decodeResourcePath(entry.parent));
  11504. }
  11505. return parentPaths;
  11506. });
  11507. }
  11508. addFieldIndex(transaction, index) {
  11509. // TODO(indexing): Verify that the auto-incrementing index ID works in
  11510. // Safari & Firefox.
  11511. const indexes = indexConfigurationStore(transaction);
  11512. const dbIndex = toDbIndexConfiguration(index);
  11513. delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
  11514. const result = indexes.add(dbIndex);
  11515. if (index.indexState) {
  11516. const states = indexStateStore(transaction);
  11517. return result.next(indexId => {
  11518. states.put(toDbIndexState(indexId, this.user, index.indexState.sequenceNumber, index.indexState.offset));
  11519. });
  11520. }
  11521. else {
  11522. return result.next();
  11523. }
  11524. }
  11525. deleteFieldIndex(transaction, index) {
  11526. const indexes = indexConfigurationStore(transaction);
  11527. const states = indexStateStore(transaction);
  11528. const entries = indexEntriesStore(transaction);
  11529. return indexes
  11530. .delete(index.indexId)
  11531. .next(() => states.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
  11532. /*lowerOpen=*/ false,
  11533. /*upperOpen=*/ true)))
  11534. .next(() => entries.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
  11535. /*lowerOpen=*/ false,
  11536. /*upperOpen=*/ true)));
  11537. }
  11538. getDocumentsMatchingTarget(transaction, target) {
  11539. const indexEntries = indexEntriesStore(transaction);
  11540. let canServeTarget = true;
  11541. const indexes = new Map();
  11542. return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
  11543. return this.getFieldIndex(transaction, subTarget).next(index => {
  11544. canServeTarget && (canServeTarget = !!index);
  11545. indexes.set(subTarget, index);
  11546. });
  11547. }).next(() => {
  11548. if (!canServeTarget) {
  11549. return PersistencePromise.resolve(null);
  11550. }
  11551. else {
  11552. let existingKeys = documentKeySet();
  11553. const result = [];
  11554. return PersistencePromise.forEach(indexes, (index, subTarget) => {
  11555. logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
  11556. const arrayValues = targetGetArrayValues(subTarget, index);
  11557. const notInValues = targetGetNotInValues(subTarget, index);
  11558. const lowerBound = targetGetLowerBound(subTarget, index);
  11559. const upperBound = targetGetUpperBound(subTarget, index);
  11560. const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
  11561. const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
  11562. const notInEncoded = this.encodeValues(index, subTarget, notInValues);
  11563. const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, lowerBound.inclusive, upperBoundEncoded, upperBound.inclusive, notInEncoded);
  11564. return PersistencePromise.forEach(indexRanges, (indexRange) => {
  11565. return indexEntries
  11566. .loadFirst(indexRange, target.limit)
  11567. .next(entries => {
  11568. entries.forEach(entry => {
  11569. const documentKey = DocumentKey.fromSegments(entry.documentKey);
  11570. if (!existingKeys.has(documentKey)) {
  11571. existingKeys = existingKeys.add(documentKey);
  11572. result.push(documentKey);
  11573. }
  11574. });
  11575. });
  11576. });
  11577. }).next(() => result);
  11578. }
  11579. });
  11580. }
  11581. getSubTargets(target) {
  11582. let subTargets = this.targetToDnfSubTargets.get(target);
  11583. if (subTargets) {
  11584. return subTargets;
  11585. }
  11586. if (target.filters.length === 0) {
  11587. subTargets = [target];
  11588. }
  11589. else {
  11590. // There is an implicit AND operation between all the filters stored in the target
  11591. const dnf = getDnfTerms(CompositeFilter.create(target.filters, "and" /* CompositeOperator.AND */));
  11592. subTargets = dnf.map(term => newTarget(target.path, target.collectionGroup, target.orderBy, term.getFilters(), target.limit, target.startAt, target.endAt));
  11593. }
  11594. this.targetToDnfSubTargets.set(target, subTargets);
  11595. return subTargets;
  11596. }
  11597. /**
  11598. * Constructs a key range query on `DbIndexEntryStore` that unions all
  11599. * bounds.
  11600. */
  11601. generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
  11602. // The number of total index scans we union together. This is similar to a
  11603. // distributed normal form, but adapted for array values. We create a single
  11604. // index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
  11605. // combined with the values from the query bounds.
  11606. const totalScans = (arrayValues != null ? arrayValues.length : 1) *
  11607. Math.max(lowerBounds.length, upperBounds.length);
  11608. const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
  11609. const indexRanges = [];
  11610. for (let i = 0; i < totalScans; ++i) {
  11611. const arrayValue = arrayValues
  11612. ? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
  11613. : EMPTY_VALUE;
  11614. const lowerBound = this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive);
  11615. const upperBound = this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive);
  11616. const notInBound = notInValues.map(notIn => this.generateLowerBound(indexId, arrayValue, notIn,
  11617. /* inclusive= */ true));
  11618. indexRanges.push(...this.createRange(lowerBound, upperBound, notInBound));
  11619. }
  11620. return indexRanges;
  11621. }
  11622. /** Generates the lower bound for `arrayValue` and `directionalValue`. */
  11623. generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
  11624. const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
  11625. return inclusive ? entry : entry.successor();
  11626. }
  11627. /** Generates the upper bound for `arrayValue` and `directionalValue`. */
  11628. generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
  11629. const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
  11630. return inclusive ? entry.successor() : entry;
  11631. }
  11632. getFieldIndex(transaction, target) {
  11633. const targetIndexMatcher = new TargetIndexMatcher(target);
  11634. const collectionGroup = target.collectionGroup != null
  11635. ? target.collectionGroup
  11636. : target.path.lastSegment();
  11637. return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
  11638. // Return the index with the most number of segments.
  11639. let index = null;
  11640. for (const candidate of indexes) {
  11641. const matches = targetIndexMatcher.servedByIndex(candidate);
  11642. if (matches &&
  11643. (!index || candidate.fields.length > index.fields.length)) {
  11644. index = candidate;
  11645. }
  11646. }
  11647. return index;
  11648. });
  11649. }
  11650. getIndexType(transaction, target) {
  11651. let indexType = 2 /* IndexType.FULL */;
  11652. const subTargets = this.getSubTargets(target);
  11653. return PersistencePromise.forEach(subTargets, (target) => {
  11654. return this.getFieldIndex(transaction, target).next(index => {
  11655. if (!index) {
  11656. indexType = 0 /* IndexType.NONE */;
  11657. }
  11658. else if (indexType !== 0 /* IndexType.NONE */ &&
  11659. index.fields.length < targetGetSegmentCount(target)) {
  11660. indexType = 1 /* IndexType.PARTIAL */;
  11661. }
  11662. });
  11663. }).next(() => {
  11664. // OR queries have more than one sub-target (one sub-target per DNF term). We currently consider
  11665. // OR queries that have a `limit` to have a partial index. For such queries we perform sorting
  11666. // and apply the limit in memory as a post-processing step.
  11667. if (targetHasLimit(target) &&
  11668. subTargets.length > 1 &&
  11669. indexType === 2 /* IndexType.FULL */) {
  11670. return 1 /* IndexType.PARTIAL */;
  11671. }
  11672. return indexType;
  11673. });
  11674. }
  11675. /**
  11676. * Returns the byte encoded form of the directional values in the field index.
  11677. * Returns `null` if the document does not have all fields specified in the
  11678. * index.
  11679. */
  11680. encodeDirectionalElements(fieldIndex, document) {
  11681. const encoder = new IndexByteEncoder();
  11682. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  11683. const field = document.data.field(segment.fieldPath);
  11684. if (field == null) {
  11685. return null;
  11686. }
  11687. const directionalEncoder = encoder.forKind(segment.kind);
  11688. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
  11689. }
  11690. return encoder.encodedBytes();
  11691. }
  11692. /** Encodes a single value to the ascending index format. */
  11693. encodeSingleElement(value) {
  11694. const encoder = new IndexByteEncoder();
  11695. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* IndexKind.ASCENDING */));
  11696. return encoder.encodedBytes();
  11697. }
  11698. /**
  11699. * Returns an encoded form of the document key that sorts based on the key
  11700. * ordering of the field index.
  11701. */
  11702. encodeDirectionalKey(fieldIndex, documentKey) {
  11703. const encoder = new IndexByteEncoder();
  11704. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(refValue(this.databaseId, documentKey), encoder.forKind(fieldIndexGetKeyOrder(fieldIndex)));
  11705. return encoder.encodedBytes();
  11706. }
  11707. /**
  11708. * Encodes the given field values according to the specification in `target`.
  11709. * For IN queries, a list of possible values is returned.
  11710. */
  11711. encodeValues(fieldIndex, target, values) {
  11712. if (values === null) {
  11713. return [];
  11714. }
  11715. let encoders = [];
  11716. encoders.push(new IndexByteEncoder());
  11717. let valueIdx = 0;
  11718. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  11719. const value = values[valueIdx++];
  11720. for (const encoder of encoders) {
  11721. if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
  11722. encoders = this.expandIndexValues(encoders, segment, value);
  11723. }
  11724. else {
  11725. const directionalEncoder = encoder.forKind(segment.kind);
  11726. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
  11727. }
  11728. }
  11729. }
  11730. return this.getEncodedBytes(encoders);
  11731. }
  11732. /**
  11733. * Encodes the given bounds according to the specification in `target`. For IN
  11734. * queries, a list of possible values is returned.
  11735. */
  11736. encodeBound(fieldIndex, target, bound) {
  11737. return this.encodeValues(fieldIndex, target, bound.position);
  11738. }
  11739. /** Returns the byte representation for the provided encoders. */
  11740. getEncodedBytes(encoders) {
  11741. const result = [];
  11742. for (let i = 0; i < encoders.length; ++i) {
  11743. result[i] = encoders[i].encodedBytes();
  11744. }
  11745. return result;
  11746. }
  11747. /**
  11748. * Creates a separate encoder for each element of an array.
  11749. *
  11750. * The method appends each value to all existing encoders (e.g. filter("a",
  11751. * "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
  11752. * list of new encoders is returned.
  11753. */
  11754. expandIndexValues(encoders, segment, value) {
  11755. const prefixes = [...encoders];
  11756. const results = [];
  11757. for (const arrayElement of value.arrayValue.values || []) {
  11758. for (const prefix of prefixes) {
  11759. const clonedEncoder = new IndexByteEncoder();
  11760. clonedEncoder.seed(prefix.encodedBytes());
  11761. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
  11762. results.push(clonedEncoder);
  11763. }
  11764. }
  11765. return results;
  11766. }
  11767. isInFilter(target, fieldPath) {
  11768. return !!target.filters.find(f => f instanceof FieldFilter &&
  11769. f.field.isEqual(fieldPath) &&
  11770. (f.op === "in" /* Operator.IN */ || f.op === "not-in" /* Operator.NOT_IN */));
  11771. }
  11772. getFieldIndexes(transaction, collectionGroup) {
  11773. const indexes = indexConfigurationStore(transaction);
  11774. const states = indexStateStore(transaction);
  11775. return (collectionGroup
  11776. ? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
  11777. : indexes.loadAll()).next(indexConfigs => {
  11778. const result = [];
  11779. return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
  11780. return states
  11781. .get([indexConfig.indexId, this.uid])
  11782. .next(indexState => {
  11783. result.push(fromDbIndexConfiguration(indexConfig, indexState));
  11784. });
  11785. }).next(() => result);
  11786. });
  11787. }
  11788. getNextCollectionGroupToUpdate(transaction) {
  11789. return this.getFieldIndexes(transaction).next(indexes => {
  11790. if (indexes.length === 0) {
  11791. return null;
  11792. }
  11793. indexes.sort((l, r) => {
  11794. const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
  11795. return cmp !== 0
  11796. ? cmp
  11797. : primitiveComparator(l.collectionGroup, r.collectionGroup);
  11798. });
  11799. return indexes[0].collectionGroup;
  11800. });
  11801. }
  11802. updateCollectionGroup(transaction, collectionGroup, offset) {
  11803. const indexes = indexConfigurationStore(transaction);
  11804. const states = indexStateStore(transaction);
  11805. return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
  11806. .loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
  11807. .next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
  11808. }
  11809. updateIndexEntries(transaction, documents) {
  11810. // Porting Note: `getFieldIndexes()` on Web does not cache index lookups as
  11811. // it could be used across different IndexedDB transactions. As any cached
  11812. // data might be invalidated by other multi-tab clients, we can only trust
  11813. // data within a single IndexedDB transaction. We therefore add a cache
  11814. // here.
  11815. const memoizedIndexes = new Map();
  11816. return PersistencePromise.forEach(documents, (key, doc) => {
  11817. const memoizedCollectionIndexes = memoizedIndexes.get(key.collectionGroup);
  11818. const fieldIndexes = memoizedCollectionIndexes
  11819. ? PersistencePromise.resolve(memoizedCollectionIndexes)
  11820. : this.getFieldIndexes(transaction, key.collectionGroup);
  11821. return fieldIndexes.next(fieldIndexes => {
  11822. memoizedIndexes.set(key.collectionGroup, fieldIndexes);
  11823. return PersistencePromise.forEach(fieldIndexes, (fieldIndex) => {
  11824. return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
  11825. const newEntries = this.computeIndexEntries(doc, fieldIndex);
  11826. if (!existingEntries.isEqual(newEntries)) {
  11827. return this.updateEntries(transaction, doc, fieldIndex, existingEntries, newEntries);
  11828. }
  11829. return PersistencePromise.resolve();
  11830. });
  11831. });
  11832. });
  11833. });
  11834. }
  11835. addIndexEntry(transaction, document, fieldIndex, indexEntry) {
  11836. const indexEntries = indexEntriesStore(transaction);
  11837. return indexEntries.put({
  11838. indexId: indexEntry.indexId,
  11839. uid: this.uid,
  11840. arrayValue: indexEntry.arrayValue,
  11841. directionalValue: indexEntry.directionalValue,
  11842. orderedDocumentKey: this.encodeDirectionalKey(fieldIndex, document.key),
  11843. documentKey: document.key.path.toArray()
  11844. });
  11845. }
  11846. deleteIndexEntry(transaction, document, fieldIndex, indexEntry) {
  11847. const indexEntries = indexEntriesStore(transaction);
  11848. return indexEntries.delete([
  11849. indexEntry.indexId,
  11850. this.uid,
  11851. indexEntry.arrayValue,
  11852. indexEntry.directionalValue,
  11853. this.encodeDirectionalKey(fieldIndex, document.key),
  11854. document.key.path.toArray()
  11855. ]);
  11856. }
  11857. getExistingIndexEntries(transaction, documentKey, fieldIndex) {
  11858. const indexEntries = indexEntriesStore(transaction);
  11859. let results = new SortedSet(indexEntryComparator);
  11860. return indexEntries
  11861. .iterate({
  11862. index: DbIndexEntryDocumentKeyIndex,
  11863. range: IDBKeyRange.only([
  11864. fieldIndex.indexId,
  11865. this.uid,
  11866. this.encodeDirectionalKey(fieldIndex, documentKey)
  11867. ])
  11868. }, (_, entry) => {
  11869. results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
  11870. })
  11871. .next(() => results);
  11872. }
  11873. /** Creates the index entries for the given document. */
  11874. computeIndexEntries(document, fieldIndex) {
  11875. let results = new SortedSet(indexEntryComparator);
  11876. const directionalValue = this.encodeDirectionalElements(fieldIndex, document);
  11877. if (directionalValue == null) {
  11878. return results;
  11879. }
  11880. const arraySegment = fieldIndexGetArraySegment(fieldIndex);
  11881. if (arraySegment != null) {
  11882. const value = document.data.field(arraySegment.fieldPath);
  11883. if (isArray(value)) {
  11884. for (const arrayValue of value.arrayValue.values || []) {
  11885. results = results.add(new IndexEntry(fieldIndex.indexId, document.key, this.encodeSingleElement(arrayValue), directionalValue));
  11886. }
  11887. }
  11888. }
  11889. else {
  11890. results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
  11891. }
  11892. return results;
  11893. }
  11894. /**
  11895. * Updates the index entries for the provided document by deleting entries
  11896. * that are no longer referenced in `newEntries` and adding all newly added
  11897. * entries.
  11898. */
  11899. updateEntries(transaction, document, fieldIndex, existingEntries, newEntries) {
  11900. logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
  11901. const promises = [];
  11902. diffSortedSets(existingEntries, newEntries, indexEntryComparator,
  11903. /* onAdd= */ entry => {
  11904. promises.push(this.addIndexEntry(transaction, document, fieldIndex, entry));
  11905. },
  11906. /* onRemove= */ entry => {
  11907. promises.push(this.deleteIndexEntry(transaction, document, fieldIndex, entry));
  11908. });
  11909. return PersistencePromise.waitFor(promises);
  11910. }
  11911. getNextSequenceNumber(transaction) {
  11912. let nextSequenceNumber = 1;
  11913. const states = indexStateStore(transaction);
  11914. return states
  11915. .iterate({
  11916. index: DbIndexStateSequenceNumberIndex,
  11917. reverse: true,
  11918. range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
  11919. }, (_, state, controller) => {
  11920. controller.done();
  11921. nextSequenceNumber = state.sequenceNumber + 1;
  11922. })
  11923. .next(() => nextSequenceNumber);
  11924. }
  11925. /**
  11926. * Returns a new set of IDB ranges that splits the existing range and excludes
  11927. * any values that match the `notInValue` from these ranges. As an example,
  11928. * '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
  11929. */
  11930. createRange(lower, upper, notInValues) {
  11931. // The notIn values need to be sorted and unique so that we can return a
  11932. // sorted set of non-overlapping ranges.
  11933. notInValues = notInValues
  11934. .sort((l, r) => indexEntryComparator(l, r))
  11935. .filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
  11936. const bounds = [];
  11937. bounds.push(lower);
  11938. for (const notInValue of notInValues) {
  11939. const cmpToLower = indexEntryComparator(notInValue, lower);
  11940. const cmpToUpper = indexEntryComparator(notInValue, upper);
  11941. if (cmpToLower === 0) {
  11942. // `notInValue` is the lower bound. We therefore need to raise the bound
  11943. // to the next value.
  11944. bounds[0] = lower.successor();
  11945. }
  11946. else if (cmpToLower > 0 && cmpToUpper < 0) {
  11947. // `notInValue` is in the middle of the range
  11948. bounds.push(notInValue);
  11949. bounds.push(notInValue.successor());
  11950. }
  11951. else if (cmpToUpper > 0) {
  11952. // `notInValue` (and all following values) are out of the range
  11953. break;
  11954. }
  11955. }
  11956. bounds.push(upper);
  11957. const ranges = [];
  11958. for (let i = 0; i < bounds.length; i += 2) {
  11959. // If we encounter two bounds that will create an unmatchable key range,
  11960. // then we return an empty set of key ranges.
  11961. if (this.isRangeMatchable(bounds[i], bounds[i + 1])) {
  11962. return [];
  11963. }
  11964. const lowerBound = [
  11965. bounds[i].indexId,
  11966. this.uid,
  11967. bounds[i].arrayValue,
  11968. bounds[i].directionalValue,
  11969. EMPTY_VALUE,
  11970. []
  11971. ];
  11972. const upperBound = [
  11973. bounds[i + 1].indexId,
  11974. this.uid,
  11975. bounds[i + 1].arrayValue,
  11976. bounds[i + 1].directionalValue,
  11977. EMPTY_VALUE,
  11978. []
  11979. ];
  11980. ranges.push(IDBKeyRange.bound(lowerBound, upperBound));
  11981. }
  11982. return ranges;
  11983. }
  11984. isRangeMatchable(lowerBound, upperBound) {
  11985. // If lower bound is greater than the upper bound, then the key
  11986. // range can never be matched.
  11987. return indexEntryComparator(lowerBound, upperBound) > 0;
  11988. }
  11989. getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
  11990. return this.getFieldIndexes(transaction, collectionGroup).next(getMinOffsetFromFieldIndexes);
  11991. }
  11992. getMinOffset(transaction, target) {
  11993. return PersistencePromise.mapArray(this.getSubTargets(target), (subTarget) => this.getFieldIndex(transaction, subTarget).next(index => index ? index : fail())).next(getMinOffsetFromFieldIndexes);
  11994. }
  11995. }
  11996. /**
  11997. * Helper to get a typed SimpleDbStore for the collectionParents
  11998. * document store.
  11999. */
  12000. function collectionParentsStore(txn) {
  12001. return getStore(txn, DbCollectionParentStore);
  12002. }
  12003. /**
  12004. * Helper to get a typed SimpleDbStore for the index entry object store.
  12005. */
  12006. function indexEntriesStore(txn) {
  12007. return getStore(txn, DbIndexEntryStore);
  12008. }
  12009. /**
  12010. * Helper to get a typed SimpleDbStore for the index configuration object store.
  12011. */
  12012. function indexConfigurationStore(txn) {
  12013. return getStore(txn, DbIndexConfigurationStore);
  12014. }
  12015. /**
  12016. * Helper to get a typed SimpleDbStore for the index state object store.
  12017. */
  12018. function indexStateStore(txn) {
  12019. return getStore(txn, DbIndexStateStore);
  12020. }
  12021. function getMinOffsetFromFieldIndexes(fieldIndexes) {
  12022. hardAssert(fieldIndexes.length !== 0);
  12023. let minOffset = fieldIndexes[0].indexState.offset;
  12024. let maxBatchId = minOffset.largestBatchId;
  12025. for (let i = 1; i < fieldIndexes.length; i++) {
  12026. const newOffset = fieldIndexes[i].indexState.offset;
  12027. if (indexOffsetComparator(newOffset, minOffset) < 0) {
  12028. minOffset = newOffset;
  12029. }
  12030. if (maxBatchId < newOffset.largestBatchId) {
  12031. maxBatchId = newOffset.largestBatchId;
  12032. }
  12033. }
  12034. return new IndexOffset(minOffset.readTime, minOffset.documentKey, maxBatchId);
  12035. }
  12036. /**
  12037. * @license
  12038. * Copyright 2020 Google LLC
  12039. *
  12040. * Licensed under the Apache License, Version 2.0 (the "License");
  12041. * you may not use this file except in compliance with the License.
  12042. * You may obtain a copy of the License at
  12043. *
  12044. * http://www.apache.org/licenses/LICENSE-2.0
  12045. *
  12046. * Unless required by applicable law or agreed to in writing, software
  12047. * distributed under the License is distributed on an "AS IS" BASIS,
  12048. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12049. * See the License for the specific language governing permissions and
  12050. * limitations under the License.
  12051. */
  12052. /**
  12053. * Delete a mutation batch and the associated document mutations.
  12054. * @returns A PersistencePromise of the document mutations that were removed.
  12055. */
  12056. function removeMutationBatch(txn, userId, batch) {
  12057. const mutationStore = txn.store(DbMutationBatchStore);
  12058. const indexTxn = txn.store(DbDocumentMutationStore);
  12059. const promises = [];
  12060. const range = IDBKeyRange.only(batch.batchId);
  12061. let numDeleted = 0;
  12062. const removePromise = mutationStore.iterate({ range }, (key, value, control) => {
  12063. numDeleted++;
  12064. return control.delete();
  12065. });
  12066. promises.push(removePromise.next(() => {
  12067. hardAssert(numDeleted === 1);
  12068. }));
  12069. const removedDocuments = [];
  12070. for (const mutation of batch.mutations) {
  12071. const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
  12072. promises.push(indexTxn.delete(indexKey));
  12073. removedDocuments.push(mutation.key);
  12074. }
  12075. return PersistencePromise.waitFor(promises).next(() => removedDocuments);
  12076. }
  12077. /**
  12078. * Returns an approximate size for the given document.
  12079. */
  12080. function dbDocumentSize(doc) {
  12081. if (!doc) {
  12082. return 0;
  12083. }
  12084. let value;
  12085. if (doc.document) {
  12086. value = doc.document;
  12087. }
  12088. else if (doc.unknownDocument) {
  12089. value = doc.unknownDocument;
  12090. }
  12091. else if (doc.noDocument) {
  12092. value = doc.noDocument;
  12093. }
  12094. else {
  12095. throw fail();
  12096. }
  12097. return JSON.stringify(value).length;
  12098. }
  12099. /**
  12100. * @license
  12101. * Copyright 2017 Google LLC
  12102. *
  12103. * Licensed under the Apache License, Version 2.0 (the "License");
  12104. * you may not use this file except in compliance with the License.
  12105. * You may obtain a copy of the License at
  12106. *
  12107. * http://www.apache.org/licenses/LICENSE-2.0
  12108. *
  12109. * Unless required by applicable law or agreed to in writing, software
  12110. * distributed under the License is distributed on an "AS IS" BASIS,
  12111. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12112. * See the License for the specific language governing permissions and
  12113. * limitations under the License.
  12114. */
  12115. /** A mutation queue for a specific user, backed by IndexedDB. */
  12116. class IndexedDbMutationQueue {
  12117. constructor(
  12118. /**
  12119. * The normalized userId (e.g. null UID => "" userId) used to store /
  12120. * retrieve mutations.
  12121. */
  12122. userId, serializer, indexManager, referenceDelegate) {
  12123. this.userId = userId;
  12124. this.serializer = serializer;
  12125. this.indexManager = indexManager;
  12126. this.referenceDelegate = referenceDelegate;
  12127. /**
  12128. * Caches the document keys for pending mutation batches. If the mutation
  12129. * has been removed from IndexedDb, the cached value may continue to
  12130. * be used to retrieve the batch's document keys. To remove a cached value
  12131. * locally, `removeCachedMutationKeys()` should be invoked either directly
  12132. * or through `removeMutationBatches()`.
  12133. *
  12134. * With multi-tab, when the primary client acknowledges or rejects a mutation,
  12135. * this cache is used by secondary clients to invalidate the local
  12136. * view of the documents that were previously affected by the mutation.
  12137. */
  12138. // PORTING NOTE: Multi-tab only.
  12139. this.documentKeysByBatchId = {};
  12140. }
  12141. /**
  12142. * Creates a new mutation queue for the given user.
  12143. * @param user - The user for which to create a mutation queue.
  12144. * @param serializer - The serializer to use when persisting to IndexedDb.
  12145. */
  12146. static forUser(user, serializer, indexManager, referenceDelegate) {
  12147. // TODO(mcg): Figure out what constraints there are on userIDs
  12148. // In particular, are there any reserved characters? are empty ids allowed?
  12149. // For the moment store these together in the same mutations table assuming
  12150. // that empty userIDs aren't allowed.
  12151. hardAssert(user.uid !== '');
  12152. const userId = user.isAuthenticated() ? user.uid : '';
  12153. return new IndexedDbMutationQueue(userId, serializer, indexManager, referenceDelegate);
  12154. }
  12155. checkEmpty(transaction) {
  12156. let empty = true;
  12157. const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
  12158. return mutationsStore(transaction)
  12159. .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
  12160. empty = false;
  12161. control.done();
  12162. })
  12163. .next(() => empty);
  12164. }
  12165. addMutationBatch(transaction, localWriteTime, baseMutations, mutations) {
  12166. const documentStore = documentMutationsStore(transaction);
  12167. const mutationStore = mutationsStore(transaction);
  12168. // The IndexedDb implementation in Chrome (and Firefox) does not handle
  12169. // compound indices that include auto-generated keys correctly. To ensure
  12170. // that the index entry is added correctly in all browsers, we perform two
  12171. // writes: The first write is used to retrieve the next auto-generated Batch
  12172. // ID, and the second write populates the index and stores the actual
  12173. // mutation batch.
  12174. // See: https://bugs.chromium.org/p/chromium/issues/detail?id=701972
  12175. // We write an empty object to obtain key
  12176. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  12177. return mutationStore.add({}).next(batchId => {
  12178. hardAssert(typeof batchId === 'number');
  12179. const batch = new MutationBatch(batchId, localWriteTime, baseMutations, mutations);
  12180. const dbBatch = toDbMutationBatch(this.serializer, this.userId, batch);
  12181. const promises = [];
  12182. let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
  12183. for (const mutation of mutations) {
  12184. const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
  12185. collectionParents = collectionParents.add(mutation.key.path.popLast());
  12186. promises.push(mutationStore.put(dbBatch));
  12187. promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
  12188. }
  12189. collectionParents.forEach(parent => {
  12190. promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
  12191. });
  12192. transaction.addOnCommittedListener(() => {
  12193. this.documentKeysByBatchId[batchId] = batch.keys();
  12194. });
  12195. return PersistencePromise.waitFor(promises).next(() => batch);
  12196. });
  12197. }
  12198. lookupMutationBatch(transaction, batchId) {
  12199. return mutationsStore(transaction)
  12200. .get(batchId)
  12201. .next(dbBatch => {
  12202. if (dbBatch) {
  12203. hardAssert(dbBatch.userId === this.userId);
  12204. return fromDbMutationBatch(this.serializer, dbBatch);
  12205. }
  12206. return null;
  12207. });
  12208. }
  12209. /**
  12210. * Returns the document keys for the mutation batch with the given batchId.
  12211. * For primary clients, this method returns `null` after
  12212. * `removeMutationBatches()` has been called. Secondary clients return a
  12213. * cached result until `removeCachedMutationKeys()` is invoked.
  12214. */
  12215. // PORTING NOTE: Multi-tab only.
  12216. lookupMutationKeys(transaction, batchId) {
  12217. if (this.documentKeysByBatchId[batchId]) {
  12218. return PersistencePromise.resolve(this.documentKeysByBatchId[batchId]);
  12219. }
  12220. else {
  12221. return this.lookupMutationBatch(transaction, batchId).next(batch => {
  12222. if (batch) {
  12223. const keys = batch.keys();
  12224. this.documentKeysByBatchId[batchId] = keys;
  12225. return keys;
  12226. }
  12227. else {
  12228. return null;
  12229. }
  12230. });
  12231. }
  12232. }
  12233. getNextMutationBatchAfterBatchId(transaction, batchId) {
  12234. const nextBatchId = batchId + 1;
  12235. const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
  12236. let foundBatch = null;
  12237. return mutationsStore(transaction)
  12238. .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
  12239. if (dbBatch.userId === this.userId) {
  12240. hardAssert(dbBatch.batchId >= nextBatchId);
  12241. foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
  12242. }
  12243. control.done();
  12244. })
  12245. .next(() => foundBatch);
  12246. }
  12247. getHighestUnacknowledgedBatchId(transaction) {
  12248. const range = IDBKeyRange.upperBound([
  12249. this.userId,
  12250. Number.POSITIVE_INFINITY
  12251. ]);
  12252. let batchId = BATCHID_UNKNOWN;
  12253. return mutationsStore(transaction)
  12254. .iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
  12255. batchId = dbBatch.batchId;
  12256. control.done();
  12257. })
  12258. .next(() => batchId);
  12259. }
  12260. getAllMutationBatches(transaction) {
  12261. const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
  12262. return mutationsStore(transaction)
  12263. .loadAll(DbMutationBatchUserMutationsIndex, range)
  12264. .next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
  12265. }
  12266. getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
  12267. // Scan the document-mutation index starting with a prefix starting with
  12268. // the given documentKey.
  12269. const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
  12270. const indexStart = IDBKeyRange.lowerBound(indexPrefix);
  12271. const results = [];
  12272. return documentMutationsStore(transaction)
  12273. .iterate({ range: indexStart }, (indexKey, _, control) => {
  12274. const [userID, encodedPath, batchId] = indexKey;
  12275. // Only consider rows matching exactly the specific key of
  12276. // interest. Note that because we order by path first, and we
  12277. // order terminators before path separators, we'll encounter all
  12278. // the index rows for documentKey contiguously. In particular, all
  12279. // the rows for documentKey will occur before any rows for
  12280. // documents nested in a subcollection beneath documentKey so we
  12281. // can stop as soon as we hit any such row.
  12282. const path = decodeResourcePath(encodedPath);
  12283. if (userID !== this.userId || !documentKey.path.isEqual(path)) {
  12284. control.done();
  12285. return;
  12286. }
  12287. // Look up the mutation batch in the store.
  12288. return mutationsStore(transaction)
  12289. .get(batchId)
  12290. .next(mutation => {
  12291. if (!mutation) {
  12292. throw fail();
  12293. }
  12294. hardAssert(mutation.userId === this.userId);
  12295. results.push(fromDbMutationBatch(this.serializer, mutation));
  12296. });
  12297. })
  12298. .next(() => results);
  12299. }
  12300. getAllMutationBatchesAffectingDocumentKeys(transaction, documentKeys) {
  12301. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  12302. const promises = [];
  12303. documentKeys.forEach(documentKey => {
  12304. const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
  12305. const range = IDBKeyRange.lowerBound(indexStart);
  12306. const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
  12307. const [userID, encodedPath, batchID] = indexKey;
  12308. // Only consider rows matching exactly the specific key of
  12309. // interest. Note that because we order by path first, and we
  12310. // order terminators before path separators, we'll encounter all
  12311. // the index rows for documentKey contiguously. In particular, all
  12312. // the rows for documentKey will occur before any rows for
  12313. // documents nested in a subcollection beneath documentKey so we
  12314. // can stop as soon as we hit any such row.
  12315. const path = decodeResourcePath(encodedPath);
  12316. if (userID !== this.userId || !documentKey.path.isEqual(path)) {
  12317. control.done();
  12318. return;
  12319. }
  12320. uniqueBatchIDs = uniqueBatchIDs.add(batchID);
  12321. });
  12322. promises.push(promise);
  12323. });
  12324. return PersistencePromise.waitFor(promises).next(() => this.lookupMutationBatches(transaction, uniqueBatchIDs));
  12325. }
  12326. getAllMutationBatchesAffectingQuery(transaction, query) {
  12327. const queryPath = query.path;
  12328. const immediateChildrenLength = queryPath.length + 1;
  12329. // TODO(mcg): Actually implement a single-collection query
  12330. //
  12331. // This is actually executing an ancestor query, traversing the whole
  12332. // subtree below the collection which can be horrifically inefficient for
  12333. // some structures. The right way to solve this is to implement the full
  12334. // value index, but that's not in the cards in the near future so this is
  12335. // the best we can do for the moment.
  12336. //
  12337. // Since we don't yet index the actual properties in the mutations, our
  12338. // current approach is to just return all mutation batches that affect
  12339. // documents in the collection being queried.
  12340. const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
  12341. const indexStart = IDBKeyRange.lowerBound(indexPrefix);
  12342. // Collect up unique batchIDs encountered during a scan of the index. Use a
  12343. // SortedSet to accumulate batch IDs so they can be traversed in order in a
  12344. // scan of the main table.
  12345. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  12346. return documentMutationsStore(transaction)
  12347. .iterate({ range: indexStart }, (indexKey, _, control) => {
  12348. const [userID, encodedPath, batchID] = indexKey;
  12349. const path = decodeResourcePath(encodedPath);
  12350. if (userID !== this.userId || !queryPath.isPrefixOf(path)) {
  12351. control.done();
  12352. return;
  12353. }
  12354. // Rows with document keys more than one segment longer than the
  12355. // query path can't be matches. For example, a query on 'rooms'
  12356. // can't match the document /rooms/abc/messages/xyx.
  12357. // TODO(mcg): we'll need a different scanner when we implement
  12358. // ancestor queries.
  12359. if (path.length !== immediateChildrenLength) {
  12360. return;
  12361. }
  12362. uniqueBatchIDs = uniqueBatchIDs.add(batchID);
  12363. })
  12364. .next(() => this.lookupMutationBatches(transaction, uniqueBatchIDs));
  12365. }
  12366. lookupMutationBatches(transaction, batchIDs) {
  12367. const results = [];
  12368. const promises = [];
  12369. // TODO(rockwood): Implement this using iterate.
  12370. batchIDs.forEach(batchId => {
  12371. promises.push(mutationsStore(transaction)
  12372. .get(batchId)
  12373. .next(mutation => {
  12374. if (mutation === null) {
  12375. throw fail();
  12376. }
  12377. hardAssert(mutation.userId === this.userId);
  12378. results.push(fromDbMutationBatch(this.serializer, mutation));
  12379. }));
  12380. });
  12381. return PersistencePromise.waitFor(promises).next(() => results);
  12382. }
  12383. removeMutationBatch(transaction, batch) {
  12384. return removeMutationBatch(transaction.simpleDbTransaction, this.userId, batch).next(removedDocuments => {
  12385. transaction.addOnCommittedListener(() => {
  12386. this.removeCachedMutationKeys(batch.batchId);
  12387. });
  12388. return PersistencePromise.forEach(removedDocuments, (key) => {
  12389. return this.referenceDelegate.markPotentiallyOrphaned(transaction, key);
  12390. });
  12391. });
  12392. }
  12393. /**
  12394. * Clears the cached keys for a mutation batch. This method should be
  12395. * called by secondary clients after they process mutation updates.
  12396. *
  12397. * Note that this method does not have to be called from primary clients as
  12398. * the corresponding cache entries are cleared when an acknowledged or
  12399. * rejected batch is removed from the mutation queue.
  12400. */
  12401. // PORTING NOTE: Multi-tab only
  12402. removeCachedMutationKeys(batchId) {
  12403. delete this.documentKeysByBatchId[batchId];
  12404. }
  12405. performConsistencyCheck(txn) {
  12406. return this.checkEmpty(txn).next(empty => {
  12407. if (!empty) {
  12408. return PersistencePromise.resolve();
  12409. }
  12410. // Verify that there are no entries in the documentMutations index if
  12411. // the queue is empty.
  12412. const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
  12413. const danglingMutationReferences = [];
  12414. return documentMutationsStore(txn)
  12415. .iterate({ range: startRange }, (key, _, control) => {
  12416. const userID = key[0];
  12417. if (userID !== this.userId) {
  12418. control.done();
  12419. return;
  12420. }
  12421. else {
  12422. const path = decodeResourcePath(key[1]);
  12423. danglingMutationReferences.push(path);
  12424. }
  12425. })
  12426. .next(() => {
  12427. hardAssert(danglingMutationReferences.length === 0);
  12428. });
  12429. });
  12430. }
  12431. containsKey(txn, key) {
  12432. return mutationQueueContainsKey(txn, this.userId, key);
  12433. }
  12434. // PORTING NOTE: Multi-tab only (state is held in memory in other clients).
  12435. /** Returns the mutation queue's metadata from IndexedDb. */
  12436. getMutationQueueMetadata(transaction) {
  12437. return mutationQueuesStore(transaction)
  12438. .get(this.userId)
  12439. .next((metadata) => {
  12440. return (metadata || {
  12441. userId: this.userId,
  12442. lastAcknowledgedBatchId: BATCHID_UNKNOWN,
  12443. lastStreamToken: ''
  12444. });
  12445. });
  12446. }
  12447. }
  12448. /**
  12449. * @returns true if the mutation queue for the given user contains a pending
  12450. * mutation for the given key.
  12451. */
  12452. function mutationQueueContainsKey(txn, userId, key) {
  12453. const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
  12454. const encodedPath = indexKey[1];
  12455. const startRange = IDBKeyRange.lowerBound(indexKey);
  12456. let containsKey = false;
  12457. return documentMutationsStore(txn)
  12458. .iterate({ range: startRange, keysOnly: true }, (key, value, control) => {
  12459. const [userID, keyPath, /*batchID*/ _] = key;
  12460. if (userID === userId && keyPath === encodedPath) {
  12461. containsKey = true;
  12462. }
  12463. control.done();
  12464. })
  12465. .next(() => containsKey);
  12466. }
  12467. /** Returns true if any mutation queue contains the given document. */
  12468. function mutationQueuesContainKey(txn, docKey) {
  12469. let found = false;
  12470. return mutationQueuesStore(txn)
  12471. .iterateSerial(userId => {
  12472. return mutationQueueContainsKey(txn, userId, docKey).next(containsKey => {
  12473. if (containsKey) {
  12474. found = true;
  12475. }
  12476. return PersistencePromise.resolve(!containsKey);
  12477. });
  12478. })
  12479. .next(() => found);
  12480. }
  12481. /**
  12482. * Helper to get a typed SimpleDbStore for the mutations object store.
  12483. */
  12484. function mutationsStore(txn) {
  12485. return getStore(txn, DbMutationBatchStore);
  12486. }
  12487. /**
  12488. * Helper to get a typed SimpleDbStore for the mutationQueues object store.
  12489. */
  12490. function documentMutationsStore(txn) {
  12491. return getStore(txn, DbDocumentMutationStore);
  12492. }
  12493. /**
  12494. * Helper to get a typed SimpleDbStore for the mutationQueues object store.
  12495. */
  12496. function mutationQueuesStore(txn) {
  12497. return getStore(txn, DbMutationQueueStore);
  12498. }
  12499. /**
  12500. * @license
  12501. * Copyright 2017 Google LLC
  12502. *
  12503. * Licensed under the Apache License, Version 2.0 (the "License");
  12504. * you may not use this file except in compliance with the License.
  12505. * You may obtain a copy of the License at
  12506. *
  12507. * http://www.apache.org/licenses/LICENSE-2.0
  12508. *
  12509. * Unless required by applicable law or agreed to in writing, software
  12510. * distributed under the License is distributed on an "AS IS" BASIS,
  12511. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12512. * See the License for the specific language governing permissions and
  12513. * limitations under the License.
  12514. */
  12515. /** Offset to ensure non-overlapping target ids. */
  12516. const OFFSET = 2;
  12517. /**
  12518. * Generates monotonically increasing target IDs for sending targets to the
  12519. * watch stream.
  12520. *
  12521. * The client constructs two generators, one for the target cache, and one for
  12522. * for the sync engine (to generate limbo documents targets). These
  12523. * generators produce non-overlapping IDs (by using even and odd IDs
  12524. * respectively).
  12525. *
  12526. * By separating the target ID space, the query cache can generate target IDs
  12527. * that persist across client restarts, while sync engine can independently
  12528. * generate in-memory target IDs that are transient and can be reused after a
  12529. * restart.
  12530. */
  12531. class TargetIdGenerator {
  12532. constructor(lastId) {
  12533. this.lastId = lastId;
  12534. }
  12535. next() {
  12536. this.lastId += OFFSET;
  12537. return this.lastId;
  12538. }
  12539. static forTargetCache() {
  12540. // The target cache generator must return '2' in its first call to `next()`
  12541. // as there is no differentiation in the protocol layer between an unset
  12542. // number and the number '0'. If we were to sent a target with target ID
  12543. // '0', the backend would consider it unset and replace it with its own ID.
  12544. return new TargetIdGenerator(2 - OFFSET);
  12545. }
  12546. static forSyncEngine() {
  12547. // Sync engine assigns target IDs for limbo document detection.
  12548. return new TargetIdGenerator(1 - OFFSET);
  12549. }
  12550. }
  12551. /**
  12552. * @license
  12553. * Copyright 2017 Google LLC
  12554. *
  12555. * Licensed under the Apache License, Version 2.0 (the "License");
  12556. * you may not use this file except in compliance with the License.
  12557. * You may obtain a copy of the License at
  12558. *
  12559. * http://www.apache.org/licenses/LICENSE-2.0
  12560. *
  12561. * Unless required by applicable law or agreed to in writing, software
  12562. * distributed under the License is distributed on an "AS IS" BASIS,
  12563. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12564. * See the License for the specific language governing permissions and
  12565. * limitations under the License.
  12566. */
  12567. class IndexedDbTargetCache {
  12568. constructor(referenceDelegate, serializer) {
  12569. this.referenceDelegate = referenceDelegate;
  12570. this.serializer = serializer;
  12571. }
  12572. // PORTING NOTE: We don't cache global metadata for the target cache, since
  12573. // some of it (in particular `highestTargetId`) can be modified by secondary
  12574. // tabs. We could perhaps be more granular (and e.g. still cache
  12575. // `lastRemoteSnapshotVersion` in memory) but for simplicity we currently go
  12576. // to IndexedDb whenever we need to read metadata. We can revisit if it turns
  12577. // out to have a meaningful performance impact.
  12578. allocateTargetId(transaction) {
  12579. return this.retrieveMetadata(transaction).next(metadata => {
  12580. const targetIdGenerator = new TargetIdGenerator(metadata.highestTargetId);
  12581. metadata.highestTargetId = targetIdGenerator.next();
  12582. return this.saveMetadata(transaction, metadata).next(() => metadata.highestTargetId);
  12583. });
  12584. }
  12585. getLastRemoteSnapshotVersion(transaction) {
  12586. return this.retrieveMetadata(transaction).next(metadata => {
  12587. return SnapshotVersion.fromTimestamp(new Timestamp(metadata.lastRemoteSnapshotVersion.seconds, metadata.lastRemoteSnapshotVersion.nanoseconds));
  12588. });
  12589. }
  12590. getHighestSequenceNumber(transaction) {
  12591. return this.retrieveMetadata(transaction).next(targetGlobal => targetGlobal.highestListenSequenceNumber);
  12592. }
  12593. setTargetsMetadata(transaction, highestListenSequenceNumber, lastRemoteSnapshotVersion) {
  12594. return this.retrieveMetadata(transaction).next(metadata => {
  12595. metadata.highestListenSequenceNumber = highestListenSequenceNumber;
  12596. if (lastRemoteSnapshotVersion) {
  12597. metadata.lastRemoteSnapshotVersion =
  12598. lastRemoteSnapshotVersion.toTimestamp();
  12599. }
  12600. if (highestListenSequenceNumber > metadata.highestListenSequenceNumber) {
  12601. metadata.highestListenSequenceNumber = highestListenSequenceNumber;
  12602. }
  12603. return this.saveMetadata(transaction, metadata);
  12604. });
  12605. }
  12606. addTargetData(transaction, targetData) {
  12607. return this.saveTargetData(transaction, targetData).next(() => {
  12608. return this.retrieveMetadata(transaction).next(metadata => {
  12609. metadata.targetCount += 1;
  12610. this.updateMetadataFromTargetData(targetData, metadata);
  12611. return this.saveMetadata(transaction, metadata);
  12612. });
  12613. });
  12614. }
  12615. updateTargetData(transaction, targetData) {
  12616. return this.saveTargetData(transaction, targetData);
  12617. }
  12618. removeTargetData(transaction, targetData) {
  12619. return this.removeMatchingKeysForTargetId(transaction, targetData.targetId)
  12620. .next(() => targetsStore(transaction).delete(targetData.targetId))
  12621. .next(() => this.retrieveMetadata(transaction))
  12622. .next(metadata => {
  12623. hardAssert(metadata.targetCount > 0);
  12624. metadata.targetCount -= 1;
  12625. return this.saveMetadata(transaction, metadata);
  12626. });
  12627. }
  12628. /**
  12629. * Drops any targets with sequence number less than or equal to the upper bound, excepting those
  12630. * present in `activeTargetIds`. Document associations for the removed targets are also removed.
  12631. * Returns the number of targets removed.
  12632. */
  12633. removeTargets(txn, upperBound, activeTargetIds) {
  12634. let count = 0;
  12635. const promises = [];
  12636. return targetsStore(txn)
  12637. .iterate((key, value) => {
  12638. const targetData = fromDbTarget(value);
  12639. if (targetData.sequenceNumber <= upperBound &&
  12640. activeTargetIds.get(targetData.targetId) === null) {
  12641. count++;
  12642. promises.push(this.removeTargetData(txn, targetData));
  12643. }
  12644. })
  12645. .next(() => PersistencePromise.waitFor(promises))
  12646. .next(() => count);
  12647. }
  12648. /**
  12649. * Call provided function with each `TargetData` that we have cached.
  12650. */
  12651. forEachTarget(txn, f) {
  12652. return targetsStore(txn).iterate((key, value) => {
  12653. const targetData = fromDbTarget(value);
  12654. f(targetData);
  12655. });
  12656. }
  12657. retrieveMetadata(transaction) {
  12658. return globalTargetStore(transaction)
  12659. .get(DbTargetGlobalKey)
  12660. .next(metadata => {
  12661. hardAssert(metadata !== null);
  12662. return metadata;
  12663. });
  12664. }
  12665. saveMetadata(transaction, metadata) {
  12666. return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
  12667. }
  12668. saveTargetData(transaction, targetData) {
  12669. return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
  12670. }
  12671. /**
  12672. * In-place updates the provided metadata to account for values in the given
  12673. * TargetData. Saving is done separately. Returns true if there were any
  12674. * changes to the metadata.
  12675. */
  12676. updateMetadataFromTargetData(targetData, metadata) {
  12677. let updated = false;
  12678. if (targetData.targetId > metadata.highestTargetId) {
  12679. metadata.highestTargetId = targetData.targetId;
  12680. updated = true;
  12681. }
  12682. if (targetData.sequenceNumber > metadata.highestListenSequenceNumber) {
  12683. metadata.highestListenSequenceNumber = targetData.sequenceNumber;
  12684. updated = true;
  12685. }
  12686. return updated;
  12687. }
  12688. getTargetCount(transaction) {
  12689. return this.retrieveMetadata(transaction).next(metadata => metadata.targetCount);
  12690. }
  12691. getTargetData(transaction, target) {
  12692. // Iterating by the canonicalId may yield more than one result because
  12693. // canonicalId values are not required to be unique per target. This query
  12694. // depends on the queryTargets index to be efficient.
  12695. const canonicalId = canonifyTarget(target);
  12696. const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
  12697. let result = null;
  12698. return targetsStore(transaction)
  12699. .iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
  12700. const found = fromDbTarget(value);
  12701. // After finding a potential match, check that the target is
  12702. // actually equal to the requested target.
  12703. if (targetEquals(target, found.target)) {
  12704. result = found;
  12705. control.done();
  12706. }
  12707. })
  12708. .next(() => result);
  12709. }
  12710. addMatchingKeys(txn, keys, targetId) {
  12711. // PORTING NOTE: The reverse index (documentsTargets) is maintained by
  12712. // IndexedDb.
  12713. const promises = [];
  12714. const store = documentTargetStore(txn);
  12715. keys.forEach(key => {
  12716. const path = encodeResourcePath(key.path);
  12717. promises.push(store.put({ targetId, path }));
  12718. promises.push(this.referenceDelegate.addReference(txn, targetId, key));
  12719. });
  12720. return PersistencePromise.waitFor(promises);
  12721. }
  12722. removeMatchingKeys(txn, keys, targetId) {
  12723. // PORTING NOTE: The reverse index (documentsTargets) is maintained by
  12724. // IndexedDb.
  12725. const store = documentTargetStore(txn);
  12726. return PersistencePromise.forEach(keys, (key) => {
  12727. const path = encodeResourcePath(key.path);
  12728. return PersistencePromise.waitFor([
  12729. store.delete([targetId, path]),
  12730. this.referenceDelegate.removeReference(txn, targetId, key)
  12731. ]);
  12732. });
  12733. }
  12734. removeMatchingKeysForTargetId(txn, targetId) {
  12735. const store = documentTargetStore(txn);
  12736. const range = IDBKeyRange.bound([targetId], [targetId + 1],
  12737. /*lowerOpen=*/ false,
  12738. /*upperOpen=*/ true);
  12739. return store.delete(range);
  12740. }
  12741. getMatchingKeysForTargetId(txn, targetId) {
  12742. const range = IDBKeyRange.bound([targetId], [targetId + 1],
  12743. /*lowerOpen=*/ false,
  12744. /*upperOpen=*/ true);
  12745. const store = documentTargetStore(txn);
  12746. let result = documentKeySet();
  12747. return store
  12748. .iterate({ range, keysOnly: true }, (key, _, control) => {
  12749. const path = decodeResourcePath(key[1]);
  12750. const docKey = new DocumentKey(path);
  12751. result = result.add(docKey);
  12752. })
  12753. .next(() => result);
  12754. }
  12755. containsKey(txn, key) {
  12756. const path = encodeResourcePath(key.path);
  12757. const range = IDBKeyRange.bound([path], [immediateSuccessor(path)],
  12758. /*lowerOpen=*/ false,
  12759. /*upperOpen=*/ true);
  12760. let count = 0;
  12761. return documentTargetStore(txn)
  12762. .iterate({
  12763. index: DbTargetDocumentDocumentTargetsIndex,
  12764. keysOnly: true,
  12765. range
  12766. }, ([targetId, path], _, control) => {
  12767. // Having a sentinel row for a document does not count as containing that document;
  12768. // For the target cache, containing the document means the document is part of some
  12769. // target.
  12770. if (targetId !== 0) {
  12771. count++;
  12772. control.done();
  12773. }
  12774. })
  12775. .next(() => count > 0);
  12776. }
  12777. /**
  12778. * Looks up a TargetData entry by target ID.
  12779. *
  12780. * @param targetId - The target ID of the TargetData entry to look up.
  12781. * @returns The cached TargetData entry, or null if the cache has no entry for
  12782. * the target.
  12783. */
  12784. // PORTING NOTE: Multi-tab only.
  12785. getTargetDataForTarget(transaction, targetId) {
  12786. return targetsStore(transaction)
  12787. .get(targetId)
  12788. .next(found => {
  12789. if (found) {
  12790. return fromDbTarget(found);
  12791. }
  12792. else {
  12793. return null;
  12794. }
  12795. });
  12796. }
  12797. }
  12798. /**
  12799. * Helper to get a typed SimpleDbStore for the queries object store.
  12800. */
  12801. function targetsStore(txn) {
  12802. return getStore(txn, DbTargetStore);
  12803. }
  12804. /**
  12805. * Helper to get a typed SimpleDbStore for the target globals object store.
  12806. */
  12807. function globalTargetStore(txn) {
  12808. return getStore(txn, DbTargetGlobalStore);
  12809. }
  12810. /**
  12811. * Helper to get a typed SimpleDbStore for the document target object store.
  12812. */
  12813. function documentTargetStore(txn) {
  12814. return getStore(txn, DbTargetDocumentStore);
  12815. }
  12816. /**
  12817. * @license
  12818. * Copyright 2018 Google LLC
  12819. *
  12820. * Licensed under the Apache License, Version 2.0 (the "License");
  12821. * you may not use this file except in compliance with the License.
  12822. * You may obtain a copy of the License at
  12823. *
  12824. * http://www.apache.org/licenses/LICENSE-2.0
  12825. *
  12826. * Unless required by applicable law or agreed to in writing, software
  12827. * distributed under the License is distributed on an "AS IS" BASIS,
  12828. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12829. * See the License for the specific language governing permissions and
  12830. * limitations under the License.
  12831. */
  12832. const GC_DID_NOT_RUN = {
  12833. didRun: false,
  12834. sequenceNumbersCollected: 0,
  12835. targetsRemoved: 0,
  12836. documentsRemoved: 0
  12837. };
  12838. const LRU_COLLECTION_DISABLED = -1;
  12839. const LRU_DEFAULT_CACHE_SIZE_BYTES = 40 * 1024 * 1024;
  12840. class LruParams {
  12841. constructor(
  12842. // When we attempt to collect, we will only do so if the cache size is greater than this
  12843. // threshold. Passing `COLLECTION_DISABLED` here will cause collection to always be skipped.
  12844. cacheSizeCollectionThreshold,
  12845. // The percentage of sequence numbers that we will attempt to collect
  12846. percentileToCollect,
  12847. // A cap on the total number of sequence numbers that will be collected. This prevents
  12848. // us from collecting a huge number of sequence numbers if the cache has grown very large.
  12849. maximumSequenceNumbersToCollect) {
  12850. this.cacheSizeCollectionThreshold = cacheSizeCollectionThreshold;
  12851. this.percentileToCollect = percentileToCollect;
  12852. this.maximumSequenceNumbersToCollect = maximumSequenceNumbersToCollect;
  12853. }
  12854. static withCacheSize(cacheSize) {
  12855. return new LruParams(cacheSize, LruParams.DEFAULT_COLLECTION_PERCENTILE, LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT);
  12856. }
  12857. }
  12858. LruParams.DEFAULT_COLLECTION_PERCENTILE = 10;
  12859. LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT = 1000;
  12860. LruParams.DEFAULT = new LruParams(LRU_DEFAULT_CACHE_SIZE_BYTES, LruParams.DEFAULT_COLLECTION_PERCENTILE, LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT);
  12861. LruParams.DISABLED = new LruParams(LRU_COLLECTION_DISABLED, 0, 0);
  12862. /**
  12863. * @license
  12864. * Copyright 2020 Google LLC
  12865. *
  12866. * Licensed under the Apache License, Version 2.0 (the "License");
  12867. * you may not use this file except in compliance with the License.
  12868. * You may obtain a copy of the License at
  12869. *
  12870. * http://www.apache.org/licenses/LICENSE-2.0
  12871. *
  12872. * Unless required by applicable law or agreed to in writing, software
  12873. * distributed under the License is distributed on an "AS IS" BASIS,
  12874. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12875. * See the License for the specific language governing permissions and
  12876. * limitations under the License.
  12877. */
  12878. const LOG_TAG$e = 'LruGarbageCollector';
  12879. const LRU_MINIMUM_CACHE_SIZE_BYTES = 1 * 1024 * 1024;
  12880. /** How long we wait to try running LRU GC after SDK initialization. */
  12881. const INITIAL_GC_DELAY_MS = 1 * 60 * 1000;
  12882. /** Minimum amount of time between GC checks, after the first one. */
  12883. const REGULAR_GC_DELAY_MS = 5 * 60 * 1000;
  12884. function bufferEntryComparator([aSequence, aIndex], [bSequence, bIndex]) {
  12885. const seqCmp = primitiveComparator(aSequence, bSequence);
  12886. if (seqCmp === 0) {
  12887. // This order doesn't matter, but we can bias against churn by sorting
  12888. // entries created earlier as less than newer entries.
  12889. return primitiveComparator(aIndex, bIndex);
  12890. }
  12891. else {
  12892. return seqCmp;
  12893. }
  12894. }
  12895. /**
  12896. * Used to calculate the nth sequence number. Keeps a rolling buffer of the
  12897. * lowest n values passed to `addElement`, and finally reports the largest of
  12898. * them in `maxValue`.
  12899. */
  12900. class RollingSequenceNumberBuffer {
  12901. constructor(maxElements) {
  12902. this.maxElements = maxElements;
  12903. this.buffer = new SortedSet(bufferEntryComparator);
  12904. this.previousIndex = 0;
  12905. }
  12906. nextIndex() {
  12907. return ++this.previousIndex;
  12908. }
  12909. addElement(sequenceNumber) {
  12910. const entry = [sequenceNumber, this.nextIndex()];
  12911. if (this.buffer.size < this.maxElements) {
  12912. this.buffer = this.buffer.add(entry);
  12913. }
  12914. else {
  12915. const highestValue = this.buffer.last();
  12916. if (bufferEntryComparator(entry, highestValue) < 0) {
  12917. this.buffer = this.buffer.delete(highestValue).add(entry);
  12918. }
  12919. }
  12920. }
  12921. get maxValue() {
  12922. // Guaranteed to be non-empty. If we decide we are not collecting any
  12923. // sequence numbers, nthSequenceNumber below short-circuits. If we have
  12924. // decided that we are collecting n sequence numbers, it's because n is some
  12925. // percentage of the existing sequence numbers. That means we should never
  12926. // be in a situation where we are collecting sequence numbers but don't
  12927. // actually have any.
  12928. return this.buffer.last()[0];
  12929. }
  12930. }
  12931. /**
  12932. * This class is responsible for the scheduling of LRU garbage collection. It handles checking
  12933. * whether or not GC is enabled, as well as which delay to use before the next run.
  12934. */
  12935. class LruScheduler {
  12936. constructor(garbageCollector, asyncQueue, localStore) {
  12937. this.garbageCollector = garbageCollector;
  12938. this.asyncQueue = asyncQueue;
  12939. this.localStore = localStore;
  12940. this.gcTask = null;
  12941. }
  12942. start() {
  12943. if (this.garbageCollector.params.cacheSizeCollectionThreshold !==
  12944. LRU_COLLECTION_DISABLED) {
  12945. this.scheduleGC(INITIAL_GC_DELAY_MS);
  12946. }
  12947. }
  12948. stop() {
  12949. if (this.gcTask) {
  12950. this.gcTask.cancel();
  12951. this.gcTask = null;
  12952. }
  12953. }
  12954. get started() {
  12955. return this.gcTask !== null;
  12956. }
  12957. scheduleGC(delay) {
  12958. logDebug(LOG_TAG$e, `Garbage collection scheduled in ${delay}ms`);
  12959. this.gcTask = this.asyncQueue.enqueueAfterDelay("lru_garbage_collection" /* TimerId.LruGarbageCollection */, delay, async () => {
  12960. this.gcTask = null;
  12961. try {
  12962. await this.localStore.collectGarbage(this.garbageCollector);
  12963. }
  12964. catch (e) {
  12965. if (isIndexedDbTransactionError(e)) {
  12966. logDebug(LOG_TAG$e, 'Ignoring IndexedDB error during garbage collection: ', e);
  12967. }
  12968. else {
  12969. await ignoreIfPrimaryLeaseLoss(e);
  12970. }
  12971. }
  12972. await this.scheduleGC(REGULAR_GC_DELAY_MS);
  12973. });
  12974. }
  12975. }
  12976. /**
  12977. * Implements the steps for LRU garbage collection.
  12978. */
  12979. class LruGarbageCollectorImpl {
  12980. constructor(delegate, params) {
  12981. this.delegate = delegate;
  12982. this.params = params;
  12983. }
  12984. calculateTargetCount(txn, percentile) {
  12985. return this.delegate.getSequenceNumberCount(txn).next(targetCount => {
  12986. return Math.floor((percentile / 100.0) * targetCount);
  12987. });
  12988. }
  12989. nthSequenceNumber(txn, n) {
  12990. if (n === 0) {
  12991. return PersistencePromise.resolve(ListenSequence.INVALID);
  12992. }
  12993. const buffer = new RollingSequenceNumberBuffer(n);
  12994. return this.delegate
  12995. .forEachTarget(txn, target => buffer.addElement(target.sequenceNumber))
  12996. .next(() => {
  12997. return this.delegate.forEachOrphanedDocumentSequenceNumber(txn, sequenceNumber => buffer.addElement(sequenceNumber));
  12998. })
  12999. .next(() => buffer.maxValue);
  13000. }
  13001. removeTargets(txn, upperBound, activeTargetIds) {
  13002. return this.delegate.removeTargets(txn, upperBound, activeTargetIds);
  13003. }
  13004. removeOrphanedDocuments(txn, upperBound) {
  13005. return this.delegate.removeOrphanedDocuments(txn, upperBound);
  13006. }
  13007. collect(txn, activeTargetIds) {
  13008. if (this.params.cacheSizeCollectionThreshold === LRU_COLLECTION_DISABLED) {
  13009. logDebug('LruGarbageCollector', 'Garbage collection skipped; disabled');
  13010. return PersistencePromise.resolve(GC_DID_NOT_RUN);
  13011. }
  13012. return this.getCacheSize(txn).next(cacheSize => {
  13013. if (cacheSize < this.params.cacheSizeCollectionThreshold) {
  13014. logDebug('LruGarbageCollector', `Garbage collection skipped; Cache size ${cacheSize} ` +
  13015. `is lower than threshold ${this.params.cacheSizeCollectionThreshold}`);
  13016. return GC_DID_NOT_RUN;
  13017. }
  13018. else {
  13019. return this.runGarbageCollection(txn, activeTargetIds);
  13020. }
  13021. });
  13022. }
  13023. getCacheSize(txn) {
  13024. return this.delegate.getCacheSize(txn);
  13025. }
  13026. runGarbageCollection(txn, activeTargetIds) {
  13027. let upperBoundSequenceNumber;
  13028. let sequenceNumbersToCollect, targetsRemoved;
  13029. // Timestamps for various pieces of the process
  13030. let countedTargetsTs, foundUpperBoundTs, removedTargetsTs, removedDocumentsTs;
  13031. const startTs = Date.now();
  13032. return this.calculateTargetCount(txn, this.params.percentileToCollect)
  13033. .next(sequenceNumbers => {
  13034. // Cap at the configured max
  13035. if (sequenceNumbers > this.params.maximumSequenceNumbersToCollect) {
  13036. logDebug('LruGarbageCollector', 'Capping sequence numbers to collect down ' +
  13037. `to the maximum of ${this.params.maximumSequenceNumbersToCollect} ` +
  13038. `from ${sequenceNumbers}`);
  13039. sequenceNumbersToCollect =
  13040. this.params.maximumSequenceNumbersToCollect;
  13041. }
  13042. else {
  13043. sequenceNumbersToCollect = sequenceNumbers;
  13044. }
  13045. countedTargetsTs = Date.now();
  13046. return this.nthSequenceNumber(txn, sequenceNumbersToCollect);
  13047. })
  13048. .next(upperBound => {
  13049. upperBoundSequenceNumber = upperBound;
  13050. foundUpperBoundTs = Date.now();
  13051. return this.removeTargets(txn, upperBoundSequenceNumber, activeTargetIds);
  13052. })
  13053. .next(numTargetsRemoved => {
  13054. targetsRemoved = numTargetsRemoved;
  13055. removedTargetsTs = Date.now();
  13056. return this.removeOrphanedDocuments(txn, upperBoundSequenceNumber);
  13057. })
  13058. .next(documentsRemoved => {
  13059. removedDocumentsTs = Date.now();
  13060. if (getLogLevel() <= logger.LogLevel.DEBUG) {
  13061. const desc = 'LRU Garbage Collection\n' +
  13062. `\tCounted targets in ${countedTargetsTs - startTs}ms\n` +
  13063. `\tDetermined least recently used ${sequenceNumbersToCollect} in ` +
  13064. `${foundUpperBoundTs - countedTargetsTs}ms\n` +
  13065. `\tRemoved ${targetsRemoved} targets in ` +
  13066. `${removedTargetsTs - foundUpperBoundTs}ms\n` +
  13067. `\tRemoved ${documentsRemoved} documents in ` +
  13068. `${removedDocumentsTs - removedTargetsTs}ms\n` +
  13069. `Total Duration: ${removedDocumentsTs - startTs}ms`;
  13070. logDebug('LruGarbageCollector', desc);
  13071. }
  13072. return PersistencePromise.resolve({
  13073. didRun: true,
  13074. sequenceNumbersCollected: sequenceNumbersToCollect,
  13075. targetsRemoved,
  13076. documentsRemoved
  13077. });
  13078. });
  13079. }
  13080. }
  13081. function newLruGarbageCollector(delegate, params) {
  13082. return new LruGarbageCollectorImpl(delegate, params);
  13083. }
  13084. /**
  13085. * @license
  13086. * Copyright 2020 Google LLC
  13087. *
  13088. * Licensed under the Apache License, Version 2.0 (the "License");
  13089. * you may not use this file except in compliance with the License.
  13090. * You may obtain a copy of the License at
  13091. *
  13092. * http://www.apache.org/licenses/LICENSE-2.0
  13093. *
  13094. * Unless required by applicable law or agreed to in writing, software
  13095. * distributed under the License is distributed on an "AS IS" BASIS,
  13096. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13097. * See the License for the specific language governing permissions and
  13098. * limitations under the License.
  13099. */
  13100. /** Provides LRU functionality for IndexedDB persistence. */
  13101. class IndexedDbLruDelegateImpl {
  13102. constructor(db, params) {
  13103. this.db = db;
  13104. this.garbageCollector = newLruGarbageCollector(this, params);
  13105. }
  13106. getSequenceNumberCount(txn) {
  13107. const docCountPromise = this.orphanedDocumentCount(txn);
  13108. const targetCountPromise = this.db.getTargetCache().getTargetCount(txn);
  13109. return targetCountPromise.next(targetCount => docCountPromise.next(docCount => targetCount + docCount));
  13110. }
  13111. orphanedDocumentCount(txn) {
  13112. let orphanedCount = 0;
  13113. return this.forEachOrphanedDocumentSequenceNumber(txn, _ => {
  13114. orphanedCount++;
  13115. }).next(() => orphanedCount);
  13116. }
  13117. forEachTarget(txn, f) {
  13118. return this.db.getTargetCache().forEachTarget(txn, f);
  13119. }
  13120. forEachOrphanedDocumentSequenceNumber(txn, f) {
  13121. return this.forEachOrphanedDocument(txn, (docKey, sequenceNumber) => f(sequenceNumber));
  13122. }
  13123. addReference(txn, targetId, key) {
  13124. return writeSentinelKey(txn, key);
  13125. }
  13126. removeReference(txn, targetId, key) {
  13127. return writeSentinelKey(txn, key);
  13128. }
  13129. removeTargets(txn, upperBound, activeTargetIds) {
  13130. return this.db.getTargetCache().removeTargets(txn, upperBound, activeTargetIds);
  13131. }
  13132. markPotentiallyOrphaned(txn, key) {
  13133. return writeSentinelKey(txn, key);
  13134. }
  13135. /**
  13136. * Returns true if anything would prevent this document from being garbage
  13137. * collected, given that the document in question is not present in any
  13138. * targets and has a sequence number less than or equal to the upper bound for
  13139. * the collection run.
  13140. */
  13141. isPinned(txn, docKey) {
  13142. return mutationQueuesContainKey(txn, docKey);
  13143. }
  13144. removeOrphanedDocuments(txn, upperBound) {
  13145. const documentCache = this.db.getRemoteDocumentCache();
  13146. const changeBuffer = documentCache.newChangeBuffer();
  13147. const promises = [];
  13148. let documentCount = 0;
  13149. const iteration = this.forEachOrphanedDocument(txn, (docKey, sequenceNumber) => {
  13150. if (sequenceNumber <= upperBound) {
  13151. const p = this.isPinned(txn, docKey).next(isPinned => {
  13152. if (!isPinned) {
  13153. documentCount++;
  13154. // Our size accounting requires us to read all documents before
  13155. // removing them.
  13156. return changeBuffer.getEntry(txn, docKey).next(() => {
  13157. changeBuffer.removeEntry(docKey, SnapshotVersion.min());
  13158. return documentTargetStore(txn).delete(sentinelKey$1(docKey));
  13159. });
  13160. }
  13161. });
  13162. promises.push(p);
  13163. }
  13164. });
  13165. return iteration
  13166. .next(() => PersistencePromise.waitFor(promises))
  13167. .next(() => changeBuffer.apply(txn))
  13168. .next(() => documentCount);
  13169. }
  13170. removeTarget(txn, targetData) {
  13171. const updated = targetData.withSequenceNumber(txn.currentSequenceNumber);
  13172. return this.db.getTargetCache().updateTargetData(txn, updated);
  13173. }
  13174. updateLimboDocument(txn, key) {
  13175. return writeSentinelKey(txn, key);
  13176. }
  13177. /**
  13178. * Call provided function for each document in the cache that is 'orphaned'. Orphaned
  13179. * means not a part of any target, so the only entry in the target-document index for
  13180. * that document will be the sentinel row (targetId 0), which will also have the sequence
  13181. * number for the last time the document was accessed.
  13182. */
  13183. forEachOrphanedDocument(txn, f) {
  13184. const store = documentTargetStore(txn);
  13185. let nextToReport = ListenSequence.INVALID;
  13186. let nextPath;
  13187. return store
  13188. .iterate({
  13189. index: DbTargetDocumentDocumentTargetsIndex
  13190. }, ([targetId, docKey], { path, sequenceNumber }) => {
  13191. if (targetId === 0) {
  13192. // if nextToReport is valid, report it, this is a new key so the
  13193. // last one must not be a member of any targets.
  13194. if (nextToReport !== ListenSequence.INVALID) {
  13195. f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
  13196. }
  13197. // set nextToReport to be this sequence number. It's the next one we
  13198. // might report, if we don't find any targets for this document.
  13199. // Note that the sequence number must be defined when the targetId
  13200. // is 0.
  13201. nextToReport = sequenceNumber;
  13202. nextPath = path;
  13203. }
  13204. else {
  13205. // set nextToReport to be invalid, we know we don't need to report
  13206. // this one since we found a target for it.
  13207. nextToReport = ListenSequence.INVALID;
  13208. }
  13209. })
  13210. .next(() => {
  13211. // Since we report sequence numbers after getting to the next key, we
  13212. // need to check if the last key we iterated over was an orphaned
  13213. // document and report it.
  13214. if (nextToReport !== ListenSequence.INVALID) {
  13215. f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
  13216. }
  13217. });
  13218. }
  13219. getCacheSize(txn) {
  13220. return this.db.getRemoteDocumentCache().getSize(txn);
  13221. }
  13222. }
  13223. function sentinelKey$1(key) {
  13224. return [0, encodeResourcePath(key.path)];
  13225. }
  13226. /**
  13227. * @returns A value suitable for writing a sentinel row in the target-document
  13228. * store.
  13229. */
  13230. function sentinelRow(key, sequenceNumber) {
  13231. return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
  13232. }
  13233. function writeSentinelKey(txn, key) {
  13234. return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
  13235. }
  13236. /**
  13237. * @license
  13238. * Copyright 2017 Google LLC
  13239. *
  13240. * Licensed under the Apache License, Version 2.0 (the "License");
  13241. * you may not use this file except in compliance with the License.
  13242. * You may obtain a copy of the License at
  13243. *
  13244. * http://www.apache.org/licenses/LICENSE-2.0
  13245. *
  13246. * Unless required by applicable law or agreed to in writing, software
  13247. * distributed under the License is distributed on an "AS IS" BASIS,
  13248. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13249. * See the License for the specific language governing permissions and
  13250. * limitations under the License.
  13251. */
  13252. /**
  13253. * An in-memory buffer of entries to be written to a RemoteDocumentCache.
  13254. * It can be used to batch up a set of changes to be written to the cache, but
  13255. * additionally supports reading entries back with the `getEntry()` method,
  13256. * falling back to the underlying RemoteDocumentCache if no entry is
  13257. * buffered.
  13258. *
  13259. * Entries added to the cache *must* be read first. This is to facilitate
  13260. * calculating the size delta of the pending changes.
  13261. *
  13262. * PORTING NOTE: This class was implemented then removed from other platforms.
  13263. * If byte-counting ends up being needed on the other platforms, consider
  13264. * porting this class as part of that implementation work.
  13265. */
  13266. class RemoteDocumentChangeBuffer {
  13267. constructor() {
  13268. // A mapping of document key to the new cache entry that should be written.
  13269. this.changes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
  13270. this.changesApplied = false;
  13271. }
  13272. /**
  13273. * Buffers a `RemoteDocumentCache.addEntry()` call.
  13274. *
  13275. * You can only modify documents that have already been retrieved via
  13276. * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
  13277. */
  13278. addEntry(document) {
  13279. this.assertNotApplied();
  13280. this.changes.set(document.key, document);
  13281. }
  13282. /**
  13283. * Buffers a `RemoteDocumentCache.removeEntry()` call.
  13284. *
  13285. * You can only remove documents that have already been retrieved via
  13286. * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
  13287. */
  13288. removeEntry(key, readTime) {
  13289. this.assertNotApplied();
  13290. this.changes.set(key, MutableDocument.newInvalidDocument(key).setReadTime(readTime));
  13291. }
  13292. /**
  13293. * Looks up an entry in the cache. The buffered changes will first be checked,
  13294. * and if no buffered change applies, this will forward to
  13295. * `RemoteDocumentCache.getEntry()`.
  13296. *
  13297. * @param transaction - The transaction in which to perform any persistence
  13298. * operations.
  13299. * @param documentKey - The key of the entry to look up.
  13300. * @returns The cached document or an invalid document if we have nothing
  13301. * cached.
  13302. */
  13303. getEntry(transaction, documentKey) {
  13304. this.assertNotApplied();
  13305. const bufferedEntry = this.changes.get(documentKey);
  13306. if (bufferedEntry !== undefined) {
  13307. return PersistencePromise.resolve(bufferedEntry);
  13308. }
  13309. else {
  13310. return this.getFromCache(transaction, documentKey);
  13311. }
  13312. }
  13313. /**
  13314. * Looks up several entries in the cache, forwarding to
  13315. * `RemoteDocumentCache.getEntry()`.
  13316. *
  13317. * @param transaction - The transaction in which to perform any persistence
  13318. * operations.
  13319. * @param documentKeys - The keys of the entries to look up.
  13320. * @returns A map of cached documents, indexed by key. If an entry cannot be
  13321. * found, the corresponding key will be mapped to an invalid document.
  13322. */
  13323. getEntries(transaction, documentKeys) {
  13324. return this.getAllFromCache(transaction, documentKeys);
  13325. }
  13326. /**
  13327. * Applies buffered changes to the underlying RemoteDocumentCache, using
  13328. * the provided transaction.
  13329. */
  13330. apply(transaction) {
  13331. this.assertNotApplied();
  13332. this.changesApplied = true;
  13333. return this.applyChanges(transaction);
  13334. }
  13335. /** Helper to assert this.changes is not null */
  13336. assertNotApplied() {
  13337. }
  13338. }
  13339. /**
  13340. * @license
  13341. * Copyright 2017 Google LLC
  13342. *
  13343. * Licensed under the Apache License, Version 2.0 (the "License");
  13344. * you may not use this file except in compliance with the License.
  13345. * You may obtain a copy of the License at
  13346. *
  13347. * http://www.apache.org/licenses/LICENSE-2.0
  13348. *
  13349. * Unless required by applicable law or agreed to in writing, software
  13350. * distributed under the License is distributed on an "AS IS" BASIS,
  13351. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13352. * See the License for the specific language governing permissions and
  13353. * limitations under the License.
  13354. */
  13355. /**
  13356. * The RemoteDocumentCache for IndexedDb. To construct, invoke
  13357. * `newIndexedDbRemoteDocumentCache()`.
  13358. */
  13359. class IndexedDbRemoteDocumentCacheImpl {
  13360. constructor(serializer) {
  13361. this.serializer = serializer;
  13362. }
  13363. setIndexManager(indexManager) {
  13364. this.indexManager = indexManager;
  13365. }
  13366. /**
  13367. * Adds the supplied entries to the cache.
  13368. *
  13369. * All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer
  13370. * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
  13371. */
  13372. addEntry(transaction, key, doc) {
  13373. const documentStore = remoteDocumentsStore(transaction);
  13374. return documentStore.put(doc);
  13375. }
  13376. /**
  13377. * Removes a document from the cache.
  13378. *
  13379. * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
  13380. * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
  13381. */
  13382. removeEntry(transaction, documentKey, readTime) {
  13383. const store = remoteDocumentsStore(transaction);
  13384. return store.delete(dbReadTimeKey(documentKey, readTime));
  13385. }
  13386. /**
  13387. * Updates the current cache size.
  13388. *
  13389. * Callers to `addEntry()` and `removeEntry()` *must* call this afterwards to update the
  13390. * cache's metadata.
  13391. */
  13392. updateMetadata(transaction, sizeDelta) {
  13393. return this.getMetadata(transaction).next(metadata => {
  13394. metadata.byteSize += sizeDelta;
  13395. return this.setMetadata(transaction, metadata);
  13396. });
  13397. }
  13398. getEntry(transaction, documentKey) {
  13399. let doc = MutableDocument.newInvalidDocument(documentKey);
  13400. return remoteDocumentsStore(transaction)
  13401. .iterate({
  13402. index: DbRemoteDocumentDocumentKeyIndex,
  13403. range: IDBKeyRange.only(dbKey(documentKey))
  13404. }, (_, dbRemoteDoc) => {
  13405. doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
  13406. })
  13407. .next(() => doc);
  13408. }
  13409. /**
  13410. * Looks up an entry in the cache.
  13411. *
  13412. * @param documentKey - The key of the entry to look up.
  13413. * @returns The cached document entry and its size.
  13414. */
  13415. getSizedEntry(transaction, documentKey) {
  13416. let result = {
  13417. size: 0,
  13418. document: MutableDocument.newInvalidDocument(documentKey)
  13419. };
  13420. return remoteDocumentsStore(transaction)
  13421. .iterate({
  13422. index: DbRemoteDocumentDocumentKeyIndex,
  13423. range: IDBKeyRange.only(dbKey(documentKey))
  13424. }, (_, dbRemoteDoc) => {
  13425. result = {
  13426. document: this.maybeDecodeDocument(documentKey, dbRemoteDoc),
  13427. size: dbDocumentSize(dbRemoteDoc)
  13428. };
  13429. })
  13430. .next(() => result);
  13431. }
  13432. getEntries(transaction, documentKeys) {
  13433. let results = mutableDocumentMap();
  13434. return this.forEachDbEntry(transaction, documentKeys, (key, dbRemoteDoc) => {
  13435. const doc = this.maybeDecodeDocument(key, dbRemoteDoc);
  13436. results = results.insert(key, doc);
  13437. }).next(() => results);
  13438. }
  13439. /**
  13440. * Looks up several entries in the cache.
  13441. *
  13442. * @param documentKeys - The set of keys entries to look up.
  13443. * @returns A map of documents indexed by key and a map of sizes indexed by
  13444. * key (zero if the document does not exist).
  13445. */
  13446. getSizedEntries(transaction, documentKeys) {
  13447. let results = mutableDocumentMap();
  13448. let sizeMap = new SortedMap(DocumentKey.comparator);
  13449. return this.forEachDbEntry(transaction, documentKeys, (key, dbRemoteDoc) => {
  13450. const doc = this.maybeDecodeDocument(key, dbRemoteDoc);
  13451. results = results.insert(key, doc);
  13452. sizeMap = sizeMap.insert(key, dbDocumentSize(dbRemoteDoc));
  13453. }).next(() => {
  13454. return { documents: results, sizeMap };
  13455. });
  13456. }
  13457. forEachDbEntry(transaction, documentKeys, callback) {
  13458. if (documentKeys.isEmpty()) {
  13459. return PersistencePromise.resolve();
  13460. }
  13461. let sortedKeys = new SortedSet(dbKeyComparator);
  13462. documentKeys.forEach(e => (sortedKeys = sortedKeys.add(e)));
  13463. const range = IDBKeyRange.bound(dbKey(sortedKeys.first()), dbKey(sortedKeys.last()));
  13464. const keyIter = sortedKeys.getIterator();
  13465. let nextKey = keyIter.getNext();
  13466. return remoteDocumentsStore(transaction)
  13467. .iterate({ index: DbRemoteDocumentDocumentKeyIndex, range }, (_, dbRemoteDoc, control) => {
  13468. const potentialKey = DocumentKey.fromSegments([
  13469. ...dbRemoteDoc.prefixPath,
  13470. dbRemoteDoc.collectionGroup,
  13471. dbRemoteDoc.documentId
  13472. ]);
  13473. // Go through keys not found in cache.
  13474. while (nextKey && dbKeyComparator(nextKey, potentialKey) < 0) {
  13475. callback(nextKey, null);
  13476. nextKey = keyIter.getNext();
  13477. }
  13478. if (nextKey && nextKey.isEqual(potentialKey)) {
  13479. // Key found in cache.
  13480. callback(nextKey, dbRemoteDoc);
  13481. nextKey = keyIter.hasNext() ? keyIter.getNext() : null;
  13482. }
  13483. // Skip to the next key (if there is one).
  13484. if (nextKey) {
  13485. control.skip(dbKey(nextKey));
  13486. }
  13487. else {
  13488. control.done();
  13489. }
  13490. })
  13491. .next(() => {
  13492. // The rest of the keys are not in the cache. One case where `iterate`
  13493. // above won't go through them is when the cache is empty.
  13494. while (nextKey) {
  13495. callback(nextKey, null);
  13496. nextKey = keyIter.hasNext() ? keyIter.getNext() : null;
  13497. }
  13498. });
  13499. }
  13500. getDocumentsMatchingQuery(transaction, query, offset, mutatedDocs) {
  13501. const collection = query.path;
  13502. const startKey = [
  13503. collection.popLast().toArray(),
  13504. collection.lastSegment(),
  13505. toDbTimestampKey(offset.readTime),
  13506. offset.documentKey.path.isEmpty()
  13507. ? ''
  13508. : offset.documentKey.path.lastSegment()
  13509. ];
  13510. const endKey = [
  13511. collection.popLast().toArray(),
  13512. collection.lastSegment(),
  13513. [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
  13514. ''
  13515. ];
  13516. return remoteDocumentsStore(transaction)
  13517. .loadAll(IDBKeyRange.bound(startKey, endKey, true))
  13518. .next(dbRemoteDocs => {
  13519. let results = mutableDocumentMap();
  13520. for (const dbRemoteDoc of dbRemoteDocs) {
  13521. const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
  13522. if (document.isFoundDocument() &&
  13523. (queryMatches(query, document) || mutatedDocs.has(document.key))) {
  13524. // Either the document matches the given query, or it is mutated.
  13525. results = results.insert(document.key, document);
  13526. }
  13527. }
  13528. return results;
  13529. });
  13530. }
  13531. getAllFromCollectionGroup(transaction, collectionGroup, offset, limit) {
  13532. let results = mutableDocumentMap();
  13533. const startKey = dbCollectionGroupKey(collectionGroup, offset);
  13534. const endKey = dbCollectionGroupKey(collectionGroup, IndexOffset.max());
  13535. return remoteDocumentsStore(transaction)
  13536. .iterate({
  13537. index: DbRemoteDocumentCollectionGroupIndex,
  13538. range: IDBKeyRange.bound(startKey, endKey, true)
  13539. }, (_, dbRemoteDoc, control) => {
  13540. const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
  13541. results = results.insert(document.key, document);
  13542. if (results.size === limit) {
  13543. control.done();
  13544. }
  13545. })
  13546. .next(() => results);
  13547. }
  13548. newChangeBuffer(options) {
  13549. return new IndexedDbRemoteDocumentChangeBuffer(this, !!options && options.trackRemovals);
  13550. }
  13551. getSize(txn) {
  13552. return this.getMetadata(txn).next(metadata => metadata.byteSize);
  13553. }
  13554. getMetadata(txn) {
  13555. return documentGlobalStore(txn)
  13556. .get(DbRemoteDocumentGlobalKey)
  13557. .next(metadata => {
  13558. hardAssert(!!metadata);
  13559. return metadata;
  13560. });
  13561. }
  13562. setMetadata(txn, metadata) {
  13563. return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
  13564. }
  13565. /**
  13566. * Decodes `dbRemoteDoc` and returns the document (or an invalid document if
  13567. * the document corresponds to the format used for sentinel deletes).
  13568. */
  13569. maybeDecodeDocument(documentKey, dbRemoteDoc) {
  13570. if (dbRemoteDoc) {
  13571. const doc = fromDbRemoteDocument(this.serializer, dbRemoteDoc);
  13572. // Whether the document is a sentinel removal and should only be used in the
  13573. // `getNewDocumentChanges()`
  13574. const isSentinelRemoval = doc.isNoDocument() && doc.version.isEqual(SnapshotVersion.min());
  13575. if (!isSentinelRemoval) {
  13576. return doc;
  13577. }
  13578. }
  13579. return MutableDocument.newInvalidDocument(documentKey);
  13580. }
  13581. }
  13582. /** Creates a new IndexedDbRemoteDocumentCache. */
  13583. function newIndexedDbRemoteDocumentCache(serializer) {
  13584. return new IndexedDbRemoteDocumentCacheImpl(serializer);
  13585. }
  13586. /**
  13587. * Handles the details of adding and updating documents in the IndexedDbRemoteDocumentCache.
  13588. *
  13589. * Unlike the MemoryRemoteDocumentChangeBuffer, the IndexedDb implementation computes the size
  13590. * delta for all submitted changes. This avoids having to re-read all documents from IndexedDb
  13591. * when we apply the changes.
  13592. */
  13593. class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
  13594. /**
  13595. * @param documentCache - The IndexedDbRemoteDocumentCache to apply the changes to.
  13596. * @param trackRemovals - Whether to create sentinel deletes that can be tracked by
  13597. * `getNewDocumentChanges()`.
  13598. */
  13599. constructor(documentCache, trackRemovals) {
  13600. super();
  13601. this.documentCache = documentCache;
  13602. this.trackRemovals = trackRemovals;
  13603. // A map of document sizes and read times prior to applying the changes in
  13604. // this buffer.
  13605. this.documentStates = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
  13606. }
  13607. applyChanges(transaction) {
  13608. const promises = [];
  13609. let sizeDelta = 0;
  13610. let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
  13611. this.changes.forEach((key, documentChange) => {
  13612. const previousDoc = this.documentStates.get(key);
  13613. promises.push(this.documentCache.removeEntry(transaction, key, previousDoc.readTime));
  13614. if (documentChange.isValidDocument()) {
  13615. const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
  13616. collectionParents = collectionParents.add(key.path.popLast());
  13617. const size = dbDocumentSize(doc);
  13618. sizeDelta += size - previousDoc.size;
  13619. promises.push(this.documentCache.addEntry(transaction, key, doc));
  13620. }
  13621. else {
  13622. sizeDelta -= previousDoc.size;
  13623. if (this.trackRemovals) {
  13624. // In order to track removals, we store a "sentinel delete" in the
  13625. // RemoteDocumentCache. This entry is represented by a NoDocument
  13626. // with a version of 0 and ignored by `maybeDecodeDocument()` but
  13627. // preserved in `getNewDocumentChanges()`.
  13628. const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
  13629. promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
  13630. }
  13631. }
  13632. });
  13633. collectionParents.forEach(parent => {
  13634. promises.push(this.documentCache.indexManager.addToCollectionParentIndex(transaction, parent));
  13635. });
  13636. promises.push(this.documentCache.updateMetadata(transaction, sizeDelta));
  13637. return PersistencePromise.waitFor(promises);
  13638. }
  13639. getFromCache(transaction, documentKey) {
  13640. // Record the size of everything we load from the cache so we can compute a delta later.
  13641. return this.documentCache
  13642. .getSizedEntry(transaction, documentKey)
  13643. .next(getResult => {
  13644. this.documentStates.set(documentKey, {
  13645. size: getResult.size,
  13646. readTime: getResult.document.readTime
  13647. });
  13648. return getResult.document;
  13649. });
  13650. }
  13651. getAllFromCache(transaction, documentKeys) {
  13652. // Record the size of everything we load from the cache so we can compute
  13653. // a delta later.
  13654. return this.documentCache
  13655. .getSizedEntries(transaction, documentKeys)
  13656. .next(({ documents, sizeMap }) => {
  13657. // Note: `getAllFromCache` returns two maps instead of a single map from
  13658. // keys to `DocumentSizeEntry`s. This is to allow returning the
  13659. // `MutableDocumentMap` directly, without a conversion.
  13660. sizeMap.forEach((documentKey, size) => {
  13661. this.documentStates.set(documentKey, {
  13662. size,
  13663. readTime: documents.get(documentKey).readTime
  13664. });
  13665. });
  13666. return documents;
  13667. });
  13668. }
  13669. }
  13670. function documentGlobalStore(txn) {
  13671. return getStore(txn, DbRemoteDocumentGlobalStore);
  13672. }
  13673. /**
  13674. * Helper to get a typed SimpleDbStore for the remoteDocuments object store.
  13675. */
  13676. function remoteDocumentsStore(txn) {
  13677. return getStore(txn, DbRemoteDocumentStore);
  13678. }
  13679. /**
  13680. * Returns a key that can be used for document lookups on the
  13681. * `DbRemoteDocumentDocumentKeyIndex` index.
  13682. */
  13683. function dbKey(documentKey) {
  13684. const path = documentKey.path.toArray();
  13685. return [
  13686. /* prefix path */ path.slice(0, path.length - 2),
  13687. /* collection id */ path[path.length - 2],
  13688. /* document id */ path[path.length - 1]
  13689. ];
  13690. }
  13691. /**
  13692. * Returns a key that can be used for document lookups via the primary key of
  13693. * the DbRemoteDocument object store.
  13694. */
  13695. function dbReadTimeKey(documentKey, readTime) {
  13696. const path = documentKey.path.toArray();
  13697. return [
  13698. /* prefix path */ path.slice(0, path.length - 2),
  13699. /* collection id */ path[path.length - 2],
  13700. toDbTimestampKey(readTime),
  13701. /* document id */ path[path.length - 1]
  13702. ];
  13703. }
  13704. /**
  13705. * Returns a key that can be used for document lookups on the
  13706. * `DbRemoteDocumentDocumentCollectionGroupIndex` index.
  13707. */
  13708. function dbCollectionGroupKey(collectionGroup, offset) {
  13709. const path = offset.documentKey.path.toArray();
  13710. return [
  13711. /* collection id */ collectionGroup,
  13712. toDbTimestampKey(offset.readTime),
  13713. /* prefix path */ path.slice(0, path.length - 2),
  13714. /* document id */ path.length > 0 ? path[path.length - 1] : ''
  13715. ];
  13716. }
  13717. /**
  13718. * Comparator that compares document keys according to the primary key sorting
  13719. * used by the `DbRemoteDocumentDocument` store (by prefix path, collection id
  13720. * and then document ID).
  13721. *
  13722. * Visible for testing.
  13723. */
  13724. function dbKeyComparator(l, r) {
  13725. const left = l.path.toArray();
  13726. const right = r.path.toArray();
  13727. // The ordering is based on https://chromium.googlesource.com/chromium/blink/+/fe5c21fef94dae71c1c3344775b8d8a7f7e6d9ec/Source/modules/indexeddb/IDBKey.cpp#74
  13728. let cmp = 0;
  13729. for (let i = 0; i < left.length - 2 && i < right.length - 2; ++i) {
  13730. cmp = primitiveComparator(left[i], right[i]);
  13731. if (cmp) {
  13732. return cmp;
  13733. }
  13734. }
  13735. cmp = primitiveComparator(left.length, right.length);
  13736. if (cmp) {
  13737. return cmp;
  13738. }
  13739. cmp = primitiveComparator(left[left.length - 2], right[right.length - 2]);
  13740. if (cmp) {
  13741. return cmp;
  13742. }
  13743. return primitiveComparator(left[left.length - 1], right[right.length - 1]);
  13744. }
  13745. /**
  13746. * @license
  13747. * Copyright 2017 Google LLC
  13748. *
  13749. * Licensed under the Apache License, Version 2.0 (the "License");
  13750. * you may not use this file except in compliance with the License.
  13751. * You may obtain a copy of the License at
  13752. *
  13753. * http://www.apache.org/licenses/LICENSE-2.0
  13754. *
  13755. * Unless required by applicable law or agreed to in writing, software
  13756. * distributed under the License is distributed on an "AS IS" BASIS,
  13757. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13758. * See the License for the specific language governing permissions and
  13759. * limitations under the License.
  13760. */
  13761. /**
  13762. * Schema Version for the Web client:
  13763. * 1. Initial version including Mutation Queue, Query Cache, and Remote
  13764. * Document Cache
  13765. * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
  13766. * longer required because migration 3 unconditionally clears it.
  13767. * 3. Dropped and re-created Query Cache to deal with cache corruption related
  13768. * to limbo resolution. Addresses
  13769. * https://github.com/firebase/firebase-ios-sdk/issues/1548
  13770. * 4. Multi-Tab Support.
  13771. * 5. Removal of held write acks.
  13772. * 6. Create document global for tracking document cache size.
  13773. * 7. Ensure every cached document has a sentinel row with a sequence number.
  13774. * 8. Add collection-parent index for Collection Group queries.
  13775. * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
  13776. * an auto-incrementing ID. This is required for Index-Free queries.
  13777. * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
  13778. * 11. Add bundles and named_queries for bundle support.
  13779. * 12. Add document overlays.
  13780. * 13. Rewrite the keys of the remote document cache to allow for efficient
  13781. * document lookup via `getAll()`.
  13782. * 14. Add overlays.
  13783. * 15. Add indexing support.
  13784. */
  13785. const SCHEMA_VERSION = 15;
  13786. /**
  13787. * @license
  13788. * Copyright 2022 Google LLC
  13789. *
  13790. * Licensed under the Apache License, Version 2.0 (the "License");
  13791. * you may not use this file except in compliance with the License.
  13792. * You may obtain a copy of the License at
  13793. *
  13794. * http://www.apache.org/licenses/LICENSE-2.0
  13795. *
  13796. * Unless required by applicable law or agreed to in writing, software
  13797. * distributed under the License is distributed on an "AS IS" BASIS,
  13798. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13799. * See the License for the specific language governing permissions and
  13800. * limitations under the License.
  13801. */
  13802. /**
  13803. * Represents a local view (overlay) of a document, and the fields that are
  13804. * locally mutated.
  13805. */
  13806. class OverlayedDocument {
  13807. constructor(overlayedDocument,
  13808. /**
  13809. * The fields that are locally mutated by patch mutations.
  13810. *
  13811. * If the overlayed document is from set or delete mutations, this is `null`.
  13812. * If there is no overlay (mutation) for the document, this is an empty `FieldMask`.
  13813. */
  13814. mutatedFields) {
  13815. this.overlayedDocument = overlayedDocument;
  13816. this.mutatedFields = mutatedFields;
  13817. }
  13818. }
  13819. /**
  13820. * @license
  13821. * Copyright 2017 Google LLC
  13822. *
  13823. * Licensed under the Apache License, Version 2.0 (the "License");
  13824. * you may not use this file except in compliance with the License.
  13825. * You may obtain a copy of the License at
  13826. *
  13827. * http://www.apache.org/licenses/LICENSE-2.0
  13828. *
  13829. * Unless required by applicable law or agreed to in writing, software
  13830. * distributed under the License is distributed on an "AS IS" BASIS,
  13831. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13832. * See the License for the specific language governing permissions and
  13833. * limitations under the License.
  13834. */
  13835. /**
  13836. * A readonly view of the local state of all documents we're tracking (i.e. we
  13837. * have a cached version in remoteDocumentCache or local mutations for the
  13838. * document). The view is computed by applying the mutations in the
  13839. * MutationQueue to the RemoteDocumentCache.
  13840. */
  13841. class LocalDocumentsView {
  13842. constructor(remoteDocumentCache, mutationQueue, documentOverlayCache, indexManager) {
  13843. this.remoteDocumentCache = remoteDocumentCache;
  13844. this.mutationQueue = mutationQueue;
  13845. this.documentOverlayCache = documentOverlayCache;
  13846. this.indexManager = indexManager;
  13847. }
  13848. /**
  13849. * Get the local view of the document identified by `key`.
  13850. *
  13851. * @returns Local view of the document or null if we don't have any cached
  13852. * state for it.
  13853. */
  13854. getDocument(transaction, key) {
  13855. let overlay = null;
  13856. return this.documentOverlayCache
  13857. .getOverlay(transaction, key)
  13858. .next(value => {
  13859. overlay = value;
  13860. return this.remoteDocumentCache.getEntry(transaction, key);
  13861. })
  13862. .next(document => {
  13863. if (overlay !== null) {
  13864. mutationApplyToLocalView(overlay.mutation, document, FieldMask.empty(), Timestamp.now());
  13865. }
  13866. return document;
  13867. });
  13868. }
  13869. /**
  13870. * Gets the local view of the documents identified by `keys`.
  13871. *
  13872. * If we don't have cached state for a document in `keys`, a NoDocument will
  13873. * be stored for that key in the resulting set.
  13874. */
  13875. getDocuments(transaction, keys) {
  13876. return this.remoteDocumentCache
  13877. .getEntries(transaction, keys)
  13878. .next(docs => this.getLocalViewOfDocuments(transaction, docs, documentKeySet()).next(() => docs));
  13879. }
  13880. /**
  13881. * Similar to `getDocuments`, but creates the local view from the given
  13882. * `baseDocs` without retrieving documents from the local store.
  13883. *
  13884. * @param transaction - The transaction this operation is scoped to.
  13885. * @param docs - The documents to apply local mutations to get the local views.
  13886. * @param existenceStateChanged - The set of document keys whose existence state
  13887. * is changed. This is useful to determine if some documents overlay needs
  13888. * to be recalculated.
  13889. */
  13890. getLocalViewOfDocuments(transaction, docs, existenceStateChanged = documentKeySet()) {
  13891. const overlays = newOverlayMap();
  13892. return this.populateOverlays(transaction, overlays, docs).next(() => {
  13893. return this.computeViews(transaction, docs, overlays, existenceStateChanged).next(computeViewsResult => {
  13894. let result = documentMap();
  13895. computeViewsResult.forEach((documentKey, overlayedDocument) => {
  13896. result = result.insert(documentKey, overlayedDocument.overlayedDocument);
  13897. });
  13898. return result;
  13899. });
  13900. });
  13901. }
  13902. /**
  13903. * Gets the overlayed documents for the given document map, which will include
  13904. * the local view of those documents and a `FieldMask` indicating which fields
  13905. * are mutated locally, `null` if overlay is a Set or Delete mutation.
  13906. */
  13907. getOverlayedDocuments(transaction, docs) {
  13908. const overlays = newOverlayMap();
  13909. return this.populateOverlays(transaction, overlays, docs).next(() => this.computeViews(transaction, docs, overlays, documentKeySet()));
  13910. }
  13911. /**
  13912. * Fetches the overlays for {@code docs} and adds them to provided overlay map
  13913. * if the map does not already contain an entry for the given document key.
  13914. */
  13915. populateOverlays(transaction, overlays, docs) {
  13916. const missingOverlays = [];
  13917. docs.forEach(key => {
  13918. if (!overlays.has(key)) {
  13919. missingOverlays.push(key);
  13920. }
  13921. });
  13922. return this.documentOverlayCache
  13923. .getOverlays(transaction, missingOverlays)
  13924. .next(result => {
  13925. result.forEach((key, val) => {
  13926. overlays.set(key, val);
  13927. });
  13928. });
  13929. }
  13930. /**
  13931. * Computes the local view for the given documents.
  13932. *
  13933. * @param docs - The documents to compute views for. It also has the base
  13934. * version of the documents.
  13935. * @param overlays - The overlays that need to be applied to the given base
  13936. * version of the documents.
  13937. * @param existenceStateChanged - A set of documents whose existence states
  13938. * might have changed. This is used to determine if we need to re-calculate
  13939. * overlays from mutation queues.
  13940. * @return A map represents the local documents view.
  13941. */
  13942. computeViews(transaction, docs, overlays, existenceStateChanged) {
  13943. let recalculateDocuments = mutableDocumentMap();
  13944. const mutatedFields = newDocumentKeyMap();
  13945. const results = newOverlayedDocumentMap();
  13946. docs.forEach((_, doc) => {
  13947. const overlay = overlays.get(doc.key);
  13948. // Recalculate an overlay if the document's existence state changed due to
  13949. // a remote event *and* the overlay is a PatchMutation. This is because
  13950. // document existence state can change if some patch mutation's
  13951. // preconditions are met.
  13952. // NOTE: we recalculate when `overlay` is undefined as well, because there
  13953. // might be a patch mutation whose precondition does not match before the
  13954. // change (hence overlay is undefined), but would now match.
  13955. if (existenceStateChanged.has(doc.key) &&
  13956. (overlay === undefined || overlay.mutation instanceof PatchMutation)) {
  13957. recalculateDocuments = recalculateDocuments.insert(doc.key, doc);
  13958. }
  13959. else if (overlay !== undefined) {
  13960. mutatedFields.set(doc.key, overlay.mutation.getFieldMask());
  13961. mutationApplyToLocalView(overlay.mutation, doc, overlay.mutation.getFieldMask(), Timestamp.now());
  13962. }
  13963. else {
  13964. // no overlay exists
  13965. // Using EMPTY to indicate there is no overlay for the document.
  13966. mutatedFields.set(doc.key, FieldMask.empty());
  13967. }
  13968. });
  13969. return this.recalculateAndSaveOverlays(transaction, recalculateDocuments).next(recalculatedFields => {
  13970. recalculatedFields.forEach((documentKey, mask) => mutatedFields.set(documentKey, mask));
  13971. docs.forEach((documentKey, document) => {
  13972. var _a;
  13973. return results.set(documentKey, new OverlayedDocument(document, (_a = mutatedFields.get(documentKey)) !== null && _a !== void 0 ? _a : null));
  13974. });
  13975. return results;
  13976. });
  13977. }
  13978. recalculateAndSaveOverlays(transaction, docs) {
  13979. const masks = newDocumentKeyMap();
  13980. // A reverse lookup map from batch id to the documents within that batch.
  13981. let documentsByBatchId = new SortedMap((key1, key2) => key1 - key2);
  13982. let processed = documentKeySet();
  13983. return this.mutationQueue
  13984. .getAllMutationBatchesAffectingDocumentKeys(transaction, docs)
  13985. .next(batches => {
  13986. for (const batch of batches) {
  13987. batch.keys().forEach(key => {
  13988. const baseDoc = docs.get(key);
  13989. if (baseDoc === null) {
  13990. return;
  13991. }
  13992. let mask = masks.get(key) || FieldMask.empty();
  13993. mask = batch.applyToLocalView(baseDoc, mask);
  13994. masks.set(key, mask);
  13995. const newSet = (documentsByBatchId.get(batch.batchId) || documentKeySet()).add(key);
  13996. documentsByBatchId = documentsByBatchId.insert(batch.batchId, newSet);
  13997. });
  13998. }
  13999. })
  14000. .next(() => {
  14001. const promises = [];
  14002. // Iterate in descending order of batch IDs, and skip documents that are
  14003. // already saved.
  14004. const iter = documentsByBatchId.getReverseIterator();
  14005. while (iter.hasNext()) {
  14006. const entry = iter.getNext();
  14007. const batchId = entry.key;
  14008. const keys = entry.value;
  14009. const overlays = newMutationMap();
  14010. keys.forEach(key => {
  14011. if (!processed.has(key)) {
  14012. const overlayMutation = calculateOverlayMutation(docs.get(key), masks.get(key));
  14013. if (overlayMutation !== null) {
  14014. overlays.set(key, overlayMutation);
  14015. }
  14016. processed = processed.add(key);
  14017. }
  14018. });
  14019. promises.push(this.documentOverlayCache.saveOverlays(transaction, batchId, overlays));
  14020. }
  14021. return PersistencePromise.waitFor(promises);
  14022. })
  14023. .next(() => masks);
  14024. }
  14025. /**
  14026. * Recalculates overlays by reading the documents from remote document cache
  14027. * first, and saves them after they are calculated.
  14028. */
  14029. recalculateAndSaveOverlaysForDocumentKeys(transaction, documentKeys) {
  14030. return this.remoteDocumentCache
  14031. .getEntries(transaction, documentKeys)
  14032. .next(docs => this.recalculateAndSaveOverlays(transaction, docs));
  14033. }
  14034. /**
  14035. * Performs a query against the local view of all documents.
  14036. *
  14037. * @param transaction - The persistence transaction.
  14038. * @param query - The query to match documents against.
  14039. * @param offset - Read time and key to start scanning by (exclusive).
  14040. */
  14041. getDocumentsMatchingQuery(transaction, query, offset) {
  14042. if (isDocumentQuery$1(query)) {
  14043. return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
  14044. }
  14045. else if (isCollectionGroupQuery(query)) {
  14046. return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, offset);
  14047. }
  14048. else {
  14049. return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
  14050. }
  14051. }
  14052. /**
  14053. * Given a collection group, returns the next documents that follow the provided offset, along
  14054. * with an updated batch ID.
  14055. *
  14056. * <p>The documents returned by this method are ordered by remote version from the provided
  14057. * offset. If there are no more remote documents after the provided offset, documents with
  14058. * mutations in order of batch id from the offset are returned. Since all documents in a batch are
  14059. * returned together, the total number of documents returned can exceed {@code count}.
  14060. *
  14061. * @param transaction
  14062. * @param collectionGroup The collection group for the documents.
  14063. * @param offset The offset to index into.
  14064. * @param count The number of documents to return
  14065. * @return A LocalWriteResult with the documents that follow the provided offset and the last processed batch id.
  14066. */
  14067. getNextDocuments(transaction, collectionGroup, offset, count) {
  14068. return this.remoteDocumentCache
  14069. .getAllFromCollectionGroup(transaction, collectionGroup, offset, count)
  14070. .next((originalDocs) => {
  14071. const overlaysPromise = count - originalDocs.size > 0
  14072. ? this.documentOverlayCache.getOverlaysForCollectionGroup(transaction, collectionGroup, offset.largestBatchId, count - originalDocs.size)
  14073. : PersistencePromise.resolve(newOverlayMap());
  14074. // The callsite will use the largest batch ID together with the latest read time to create
  14075. // a new index offset. Since we only process batch IDs if all remote documents have been read,
  14076. // no overlay will increase the overall read time. This is why we only need to special case
  14077. // the batch id.
  14078. let largestBatchId = INITIAL_LARGEST_BATCH_ID;
  14079. let modifiedDocs = originalDocs;
  14080. return overlaysPromise.next(overlays => {
  14081. return PersistencePromise.forEach(overlays, (key, overlay) => {
  14082. if (largestBatchId < overlay.largestBatchId) {
  14083. largestBatchId = overlay.largestBatchId;
  14084. }
  14085. if (originalDocs.get(key)) {
  14086. return PersistencePromise.resolve();
  14087. }
  14088. return this.remoteDocumentCache
  14089. .getEntry(transaction, key)
  14090. .next(doc => {
  14091. modifiedDocs = modifiedDocs.insert(key, doc);
  14092. });
  14093. })
  14094. .next(() => this.populateOverlays(transaction, overlays, originalDocs))
  14095. .next(() => this.computeViews(transaction, modifiedDocs, overlays, documentKeySet()))
  14096. .next(localDocs => ({
  14097. batchId: largestBatchId,
  14098. changes: convertOverlayedDocumentMapToDocumentMap(localDocs)
  14099. }));
  14100. });
  14101. });
  14102. }
  14103. getDocumentsMatchingDocumentQuery(transaction, docPath) {
  14104. // Just do a simple document lookup.
  14105. return this.getDocument(transaction, new DocumentKey(docPath)).next(document => {
  14106. let result = documentMap();
  14107. if (document.isFoundDocument()) {
  14108. result = result.insert(document.key, document);
  14109. }
  14110. return result;
  14111. });
  14112. }
  14113. getDocumentsMatchingCollectionGroupQuery(transaction, query, offset) {
  14114. const collectionId = query.collectionGroup;
  14115. let results = documentMap();
  14116. return this.indexManager
  14117. .getCollectionParents(transaction, collectionId)
  14118. .next(parents => {
  14119. // Perform a collection query against each parent that contains the
  14120. // collectionId and aggregate the results.
  14121. return PersistencePromise.forEach(parents, (parent) => {
  14122. const collectionQuery = asCollectionQueryAtPath(query, parent.child(collectionId));
  14123. return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, offset).next(r => {
  14124. r.forEach((key, doc) => {
  14125. results = results.insert(key, doc);
  14126. });
  14127. });
  14128. }).next(() => results);
  14129. });
  14130. }
  14131. getDocumentsMatchingCollectionQuery(transaction, query, offset) {
  14132. // Query the remote documents and overlay mutations.
  14133. let overlays;
  14134. return this.documentOverlayCache
  14135. .getOverlaysForCollection(transaction, query.path, offset.largestBatchId)
  14136. .next(result => {
  14137. overlays = result;
  14138. return this.remoteDocumentCache.getDocumentsMatchingQuery(transaction, query, offset, overlays);
  14139. })
  14140. .next(remoteDocuments => {
  14141. // As documents might match the query because of their overlay we need to
  14142. // include documents for all overlays in the initial document set.
  14143. overlays.forEach((_, overlay) => {
  14144. const key = overlay.getKey();
  14145. if (remoteDocuments.get(key) === null) {
  14146. remoteDocuments = remoteDocuments.insert(key, MutableDocument.newInvalidDocument(key));
  14147. }
  14148. });
  14149. // Apply the overlays and match against the query.
  14150. let results = documentMap();
  14151. remoteDocuments.forEach((key, document) => {
  14152. const overlay = overlays.get(key);
  14153. if (overlay !== undefined) {
  14154. mutationApplyToLocalView(overlay.mutation, document, FieldMask.empty(), Timestamp.now());
  14155. }
  14156. // Finally, insert the documents that still match the query
  14157. if (queryMatches(query, document)) {
  14158. results = results.insert(key, document);
  14159. }
  14160. });
  14161. return results;
  14162. });
  14163. }
  14164. }
  14165. /**
  14166. * @license
  14167. * Copyright 2020 Google LLC
  14168. *
  14169. * Licensed under the Apache License, Version 2.0 (the "License");
  14170. * you may not use this file except in compliance with the License.
  14171. * You may obtain a copy of the License at
  14172. *
  14173. * http://www.apache.org/licenses/LICENSE-2.0
  14174. *
  14175. * Unless required by applicable law or agreed to in writing, software
  14176. * distributed under the License is distributed on an "AS IS" BASIS,
  14177. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14178. * See the License for the specific language governing permissions and
  14179. * limitations under the License.
  14180. */
  14181. class MemoryBundleCache {
  14182. constructor(serializer) {
  14183. this.serializer = serializer;
  14184. this.bundles = new Map();
  14185. this.namedQueries = new Map();
  14186. }
  14187. getBundleMetadata(transaction, bundleId) {
  14188. return PersistencePromise.resolve(this.bundles.get(bundleId));
  14189. }
  14190. saveBundleMetadata(transaction, bundleMetadata) {
  14191. this.bundles.set(bundleMetadata.id, fromBundleMetadata(bundleMetadata));
  14192. return PersistencePromise.resolve();
  14193. }
  14194. getNamedQuery(transaction, queryName) {
  14195. return PersistencePromise.resolve(this.namedQueries.get(queryName));
  14196. }
  14197. saveNamedQuery(transaction, query) {
  14198. this.namedQueries.set(query.name, fromProtoNamedQuery(query));
  14199. return PersistencePromise.resolve();
  14200. }
  14201. }
  14202. /**
  14203. * @license
  14204. * Copyright 2022 Google LLC
  14205. *
  14206. * Licensed under the Apache License, Version 2.0 (the "License");
  14207. * you may not use this file except in compliance with the License.
  14208. * You may obtain a copy of the License at
  14209. *
  14210. * http://www.apache.org/licenses/LICENSE-2.0
  14211. *
  14212. * Unless required by applicable law or agreed to in writing, software
  14213. * distributed under the License is distributed on an "AS IS" BASIS,
  14214. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14215. * See the License for the specific language governing permissions and
  14216. * limitations under the License.
  14217. */
  14218. /**
  14219. * An in-memory implementation of DocumentOverlayCache.
  14220. */
  14221. class MemoryDocumentOverlayCache {
  14222. constructor() {
  14223. // A map sorted by DocumentKey, whose value is a pair of the largest batch id
  14224. // for the overlay and the overlay itself.
  14225. this.overlays = new SortedMap(DocumentKey.comparator);
  14226. this.overlayByBatchId = new Map();
  14227. }
  14228. getOverlay(transaction, key) {
  14229. return PersistencePromise.resolve(this.overlays.get(key));
  14230. }
  14231. getOverlays(transaction, keys) {
  14232. const result = newOverlayMap();
  14233. return PersistencePromise.forEach(keys, (key) => {
  14234. return this.getOverlay(transaction, key).next(overlay => {
  14235. if (overlay !== null) {
  14236. result.set(key, overlay);
  14237. }
  14238. });
  14239. }).next(() => result);
  14240. }
  14241. saveOverlays(transaction, largestBatchId, overlays) {
  14242. overlays.forEach((_, mutation) => {
  14243. this.saveOverlay(transaction, largestBatchId, mutation);
  14244. });
  14245. return PersistencePromise.resolve();
  14246. }
  14247. removeOverlaysForBatchId(transaction, documentKeys, batchId) {
  14248. const keys = this.overlayByBatchId.get(batchId);
  14249. if (keys !== undefined) {
  14250. keys.forEach(key => (this.overlays = this.overlays.remove(key)));
  14251. this.overlayByBatchId.delete(batchId);
  14252. }
  14253. return PersistencePromise.resolve();
  14254. }
  14255. getOverlaysForCollection(transaction, collection, sinceBatchId) {
  14256. const result = newOverlayMap();
  14257. const immediateChildrenPathLength = collection.length + 1;
  14258. const prefix = new DocumentKey(collection.child(''));
  14259. const iter = this.overlays.getIteratorFrom(prefix);
  14260. while (iter.hasNext()) {
  14261. const entry = iter.getNext();
  14262. const overlay = entry.value;
  14263. const key = overlay.getKey();
  14264. if (!collection.isPrefixOf(key.path)) {
  14265. break;
  14266. }
  14267. // Documents from sub-collections
  14268. if (key.path.length !== immediateChildrenPathLength) {
  14269. continue;
  14270. }
  14271. if (overlay.largestBatchId > sinceBatchId) {
  14272. result.set(overlay.getKey(), overlay);
  14273. }
  14274. }
  14275. return PersistencePromise.resolve(result);
  14276. }
  14277. getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
  14278. let batchIdToOverlays = new SortedMap((key1, key2) => key1 - key2);
  14279. const iter = this.overlays.getIterator();
  14280. while (iter.hasNext()) {
  14281. const entry = iter.getNext();
  14282. const overlay = entry.value;
  14283. const key = overlay.getKey();
  14284. if (key.getCollectionGroup() !== collectionGroup) {
  14285. continue;
  14286. }
  14287. if (overlay.largestBatchId > sinceBatchId) {
  14288. let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
  14289. if (overlaysForBatchId === null) {
  14290. overlaysForBatchId = newOverlayMap();
  14291. batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
  14292. }
  14293. overlaysForBatchId.set(overlay.getKey(), overlay);
  14294. }
  14295. }
  14296. const result = newOverlayMap();
  14297. const batchIter = batchIdToOverlays.getIterator();
  14298. while (batchIter.hasNext()) {
  14299. const entry = batchIter.getNext();
  14300. const overlays = entry.value;
  14301. overlays.forEach((key, overlay) => result.set(key, overlay));
  14302. if (result.size() >= count) {
  14303. break;
  14304. }
  14305. }
  14306. return PersistencePromise.resolve(result);
  14307. }
  14308. saveOverlay(transaction, largestBatchId, mutation) {
  14309. // Remove the association of the overlay to its batch id.
  14310. const existing = this.overlays.get(mutation.key);
  14311. if (existing !== null) {
  14312. const newSet = this.overlayByBatchId
  14313. .get(existing.largestBatchId)
  14314. .delete(mutation.key);
  14315. this.overlayByBatchId.set(existing.largestBatchId, newSet);
  14316. }
  14317. this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
  14318. // Create the association of this overlay to the given largestBatchId.
  14319. let batch = this.overlayByBatchId.get(largestBatchId);
  14320. if (batch === undefined) {
  14321. batch = documentKeySet();
  14322. this.overlayByBatchId.set(largestBatchId, batch);
  14323. }
  14324. this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
  14325. }
  14326. }
  14327. /**
  14328. * @license
  14329. * Copyright 2017 Google LLC
  14330. *
  14331. * Licensed under the Apache License, Version 2.0 (the "License");
  14332. * you may not use this file except in compliance with the License.
  14333. * You may obtain a copy of the License at
  14334. *
  14335. * http://www.apache.org/licenses/LICENSE-2.0
  14336. *
  14337. * Unless required by applicable law or agreed to in writing, software
  14338. * distributed under the License is distributed on an "AS IS" BASIS,
  14339. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14340. * See the License for the specific language governing permissions and
  14341. * limitations under the License.
  14342. */
  14343. /**
  14344. * A collection of references to a document from some kind of numbered entity
  14345. * (either a target ID or batch ID). As references are added to or removed from
  14346. * the set corresponding events are emitted to a registered garbage collector.
  14347. *
  14348. * Each reference is represented by a DocumentReference object. Each of them
  14349. * contains enough information to uniquely identify the reference. They are all
  14350. * stored primarily in a set sorted by key. A document is considered garbage if
  14351. * there's no references in that set (this can be efficiently checked thanks to
  14352. * sorting by key).
  14353. *
  14354. * ReferenceSet also keeps a secondary set that contains references sorted by
  14355. * IDs. This one is used to efficiently implement removal of all references by
  14356. * some target ID.
  14357. */
  14358. class ReferenceSet {
  14359. constructor() {
  14360. // A set of outstanding references to a document sorted by key.
  14361. this.refsByKey = new SortedSet(DocReference.compareByKey);
  14362. // A set of outstanding references to a document sorted by target id.
  14363. this.refsByTarget = new SortedSet(DocReference.compareByTargetId);
  14364. }
  14365. /** Returns true if the reference set contains no references. */
  14366. isEmpty() {
  14367. return this.refsByKey.isEmpty();
  14368. }
  14369. /** Adds a reference to the given document key for the given ID. */
  14370. addReference(key, id) {
  14371. const ref = new DocReference(key, id);
  14372. this.refsByKey = this.refsByKey.add(ref);
  14373. this.refsByTarget = this.refsByTarget.add(ref);
  14374. }
  14375. /** Add references to the given document keys for the given ID. */
  14376. addReferences(keys, id) {
  14377. keys.forEach(key => this.addReference(key, id));
  14378. }
  14379. /**
  14380. * Removes a reference to the given document key for the given
  14381. * ID.
  14382. */
  14383. removeReference(key, id) {
  14384. this.removeRef(new DocReference(key, id));
  14385. }
  14386. removeReferences(keys, id) {
  14387. keys.forEach(key => this.removeReference(key, id));
  14388. }
  14389. /**
  14390. * Clears all references with a given ID. Calls removeRef() for each key
  14391. * removed.
  14392. */
  14393. removeReferencesForId(id) {
  14394. const emptyKey = new DocumentKey(new ResourcePath([]));
  14395. const startRef = new DocReference(emptyKey, id);
  14396. const endRef = new DocReference(emptyKey, id + 1);
  14397. const keys = [];
  14398. this.refsByTarget.forEachInRange([startRef, endRef], ref => {
  14399. this.removeRef(ref);
  14400. keys.push(ref.key);
  14401. });
  14402. return keys;
  14403. }
  14404. removeAllReferences() {
  14405. this.refsByKey.forEach(ref => this.removeRef(ref));
  14406. }
  14407. removeRef(ref) {
  14408. this.refsByKey = this.refsByKey.delete(ref);
  14409. this.refsByTarget = this.refsByTarget.delete(ref);
  14410. }
  14411. referencesForId(id) {
  14412. const emptyKey = new DocumentKey(new ResourcePath([]));
  14413. const startRef = new DocReference(emptyKey, id);
  14414. const endRef = new DocReference(emptyKey, id + 1);
  14415. let keys = documentKeySet();
  14416. this.refsByTarget.forEachInRange([startRef, endRef], ref => {
  14417. keys = keys.add(ref.key);
  14418. });
  14419. return keys;
  14420. }
  14421. containsKey(key) {
  14422. const ref = new DocReference(key, 0);
  14423. const firstRef = this.refsByKey.firstAfterOrEqual(ref);
  14424. return firstRef !== null && key.isEqual(firstRef.key);
  14425. }
  14426. }
  14427. class DocReference {
  14428. constructor(key, targetOrBatchId) {
  14429. this.key = key;
  14430. this.targetOrBatchId = targetOrBatchId;
  14431. }
  14432. /** Compare by key then by ID */
  14433. static compareByKey(left, right) {
  14434. return (DocumentKey.comparator(left.key, right.key) ||
  14435. primitiveComparator(left.targetOrBatchId, right.targetOrBatchId));
  14436. }
  14437. /** Compare by ID then by key */
  14438. static compareByTargetId(left, right) {
  14439. return (primitiveComparator(left.targetOrBatchId, right.targetOrBatchId) ||
  14440. DocumentKey.comparator(left.key, right.key));
  14441. }
  14442. }
  14443. /**
  14444. * @license
  14445. * Copyright 2017 Google LLC
  14446. *
  14447. * Licensed under the Apache License, Version 2.0 (the "License");
  14448. * you may not use this file except in compliance with the License.
  14449. * You may obtain a copy of the License at
  14450. *
  14451. * http://www.apache.org/licenses/LICENSE-2.0
  14452. *
  14453. * Unless required by applicable law or agreed to in writing, software
  14454. * distributed under the License is distributed on an "AS IS" BASIS,
  14455. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14456. * See the License for the specific language governing permissions and
  14457. * limitations under the License.
  14458. */
  14459. class MemoryMutationQueue {
  14460. constructor(indexManager, referenceDelegate) {
  14461. this.indexManager = indexManager;
  14462. this.referenceDelegate = referenceDelegate;
  14463. /**
  14464. * The set of all mutations that have been sent but not yet been applied to
  14465. * the backend.
  14466. */
  14467. this.mutationQueue = [];
  14468. /** Next value to use when assigning sequential IDs to each mutation batch. */
  14469. this.nextBatchId = 1;
  14470. /** An ordered mapping between documents and the mutations batch IDs. */
  14471. this.batchesByDocumentKey = new SortedSet(DocReference.compareByKey);
  14472. }
  14473. checkEmpty(transaction) {
  14474. return PersistencePromise.resolve(this.mutationQueue.length === 0);
  14475. }
  14476. addMutationBatch(transaction, localWriteTime, baseMutations, mutations) {
  14477. const batchId = this.nextBatchId;
  14478. this.nextBatchId++;
  14479. if (this.mutationQueue.length > 0) {
  14480. this.mutationQueue[this.mutationQueue.length - 1];
  14481. }
  14482. const batch = new MutationBatch(batchId, localWriteTime, baseMutations, mutations);
  14483. this.mutationQueue.push(batch);
  14484. // Track references by document key and index collection parents.
  14485. for (const mutation of mutations) {
  14486. this.batchesByDocumentKey = this.batchesByDocumentKey.add(new DocReference(mutation.key, batchId));
  14487. this.indexManager.addToCollectionParentIndex(transaction, mutation.key.path.popLast());
  14488. }
  14489. return PersistencePromise.resolve(batch);
  14490. }
  14491. lookupMutationBatch(transaction, batchId) {
  14492. return PersistencePromise.resolve(this.findMutationBatch(batchId));
  14493. }
  14494. getNextMutationBatchAfterBatchId(transaction, batchId) {
  14495. const nextBatchId = batchId + 1;
  14496. // The requested batchId may still be out of range so normalize it to the
  14497. // start of the queue.
  14498. const rawIndex = this.indexOfBatchId(nextBatchId);
  14499. const index = rawIndex < 0 ? 0 : rawIndex;
  14500. return PersistencePromise.resolve(this.mutationQueue.length > index ? this.mutationQueue[index] : null);
  14501. }
  14502. getHighestUnacknowledgedBatchId() {
  14503. return PersistencePromise.resolve(this.mutationQueue.length === 0 ? BATCHID_UNKNOWN : this.nextBatchId - 1);
  14504. }
  14505. getAllMutationBatches(transaction) {
  14506. return PersistencePromise.resolve(this.mutationQueue.slice());
  14507. }
  14508. getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
  14509. const start = new DocReference(documentKey, 0);
  14510. const end = new DocReference(documentKey, Number.POSITIVE_INFINITY);
  14511. const result = [];
  14512. this.batchesByDocumentKey.forEachInRange([start, end], ref => {
  14513. const batch = this.findMutationBatch(ref.targetOrBatchId);
  14514. result.push(batch);
  14515. });
  14516. return PersistencePromise.resolve(result);
  14517. }
  14518. getAllMutationBatchesAffectingDocumentKeys(transaction, documentKeys) {
  14519. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  14520. documentKeys.forEach(documentKey => {
  14521. const start = new DocReference(documentKey, 0);
  14522. const end = new DocReference(documentKey, Number.POSITIVE_INFINITY);
  14523. this.batchesByDocumentKey.forEachInRange([start, end], ref => {
  14524. uniqueBatchIDs = uniqueBatchIDs.add(ref.targetOrBatchId);
  14525. });
  14526. });
  14527. return PersistencePromise.resolve(this.findMutationBatches(uniqueBatchIDs));
  14528. }
  14529. getAllMutationBatchesAffectingQuery(transaction, query) {
  14530. // Use the query path as a prefix for testing if a document matches the
  14531. // query.
  14532. const prefix = query.path;
  14533. const immediateChildrenPathLength = prefix.length + 1;
  14534. // Construct a document reference for actually scanning the index. Unlike
  14535. // the prefix the document key in this reference must have an even number of
  14536. // segments. The empty segment can be used a suffix of the query path
  14537. // because it precedes all other segments in an ordered traversal.
  14538. let startPath = prefix;
  14539. if (!DocumentKey.isDocumentKey(startPath)) {
  14540. startPath = startPath.child('');
  14541. }
  14542. const start = new DocReference(new DocumentKey(startPath), 0);
  14543. // Find unique batchIDs referenced by all documents potentially matching the
  14544. // query.
  14545. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  14546. this.batchesByDocumentKey.forEachWhile(ref => {
  14547. const rowKeyPath = ref.key.path;
  14548. if (!prefix.isPrefixOf(rowKeyPath)) {
  14549. return false;
  14550. }
  14551. else {
  14552. // Rows with document keys more than one segment longer than the query
  14553. // path can't be matches. For example, a query on 'rooms' can't match
  14554. // the document /rooms/abc/messages/xyx.
  14555. // TODO(mcg): we'll need a different scanner when we implement
  14556. // ancestor queries.
  14557. if (rowKeyPath.length === immediateChildrenPathLength) {
  14558. uniqueBatchIDs = uniqueBatchIDs.add(ref.targetOrBatchId);
  14559. }
  14560. return true;
  14561. }
  14562. }, start);
  14563. return PersistencePromise.resolve(this.findMutationBatches(uniqueBatchIDs));
  14564. }
  14565. findMutationBatches(batchIDs) {
  14566. // Construct an array of matching batches, sorted by batchID to ensure that
  14567. // multiple mutations affecting the same document key are applied in order.
  14568. const result = [];
  14569. batchIDs.forEach(batchId => {
  14570. const batch = this.findMutationBatch(batchId);
  14571. if (batch !== null) {
  14572. result.push(batch);
  14573. }
  14574. });
  14575. return result;
  14576. }
  14577. removeMutationBatch(transaction, batch) {
  14578. // Find the position of the first batch for removal.
  14579. const batchIndex = this.indexOfExistingBatchId(batch.batchId, 'removed');
  14580. hardAssert(batchIndex === 0);
  14581. this.mutationQueue.shift();
  14582. let references = this.batchesByDocumentKey;
  14583. return PersistencePromise.forEach(batch.mutations, (mutation) => {
  14584. const ref = new DocReference(mutation.key, batch.batchId);
  14585. references = references.delete(ref);
  14586. return this.referenceDelegate.markPotentiallyOrphaned(transaction, mutation.key);
  14587. }).next(() => {
  14588. this.batchesByDocumentKey = references;
  14589. });
  14590. }
  14591. removeCachedMutationKeys(batchId) {
  14592. // No-op since the memory mutation queue does not maintain a separate cache.
  14593. }
  14594. containsKey(txn, key) {
  14595. const ref = new DocReference(key, 0);
  14596. const firstRef = this.batchesByDocumentKey.firstAfterOrEqual(ref);
  14597. return PersistencePromise.resolve(key.isEqual(firstRef && firstRef.key));
  14598. }
  14599. performConsistencyCheck(txn) {
  14600. if (this.mutationQueue.length === 0) ;
  14601. return PersistencePromise.resolve();
  14602. }
  14603. /**
  14604. * Finds the index of the given batchId in the mutation queue and asserts that
  14605. * the resulting index is within the bounds of the queue.
  14606. *
  14607. * @param batchId - The batchId to search for
  14608. * @param action - A description of what the caller is doing, phrased in passive
  14609. * form (e.g. "acknowledged" in a routine that acknowledges batches).
  14610. */
  14611. indexOfExistingBatchId(batchId, action) {
  14612. const index = this.indexOfBatchId(batchId);
  14613. return index;
  14614. }
  14615. /**
  14616. * Finds the index of the given batchId in the mutation queue. This operation
  14617. * is O(1).
  14618. *
  14619. * @returns The computed index of the batch with the given batchId, based on
  14620. * the state of the queue. Note this index can be negative if the requested
  14621. * batchId has already been remvoed from the queue or past the end of the
  14622. * queue if the batchId is larger than the last added batch.
  14623. */
  14624. indexOfBatchId(batchId) {
  14625. if (this.mutationQueue.length === 0) {
  14626. // As an index this is past the end of the queue
  14627. return 0;
  14628. }
  14629. // Examine the front of the queue to figure out the difference between the
  14630. // batchId and indexes in the array. Note that since the queue is ordered
  14631. // by batchId, if the first batch has a larger batchId then the requested
  14632. // batchId doesn't exist in the queue.
  14633. const firstBatchId = this.mutationQueue[0].batchId;
  14634. return batchId - firstBatchId;
  14635. }
  14636. /**
  14637. * A version of lookupMutationBatch that doesn't return a promise, this makes
  14638. * other functions that uses this code easier to read and more efficent.
  14639. */
  14640. findMutationBatch(batchId) {
  14641. const index = this.indexOfBatchId(batchId);
  14642. if (index < 0 || index >= this.mutationQueue.length) {
  14643. return null;
  14644. }
  14645. const batch = this.mutationQueue[index];
  14646. return batch;
  14647. }
  14648. }
  14649. /**
  14650. * @license
  14651. * Copyright 2017 Google LLC
  14652. *
  14653. * Licensed under the Apache License, Version 2.0 (the "License");
  14654. * you may not use this file except in compliance with the License.
  14655. * You may obtain a copy of the License at
  14656. *
  14657. * http://www.apache.org/licenses/LICENSE-2.0
  14658. *
  14659. * Unless required by applicable law or agreed to in writing, software
  14660. * distributed under the License is distributed on an "AS IS" BASIS,
  14661. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14662. * See the License for the specific language governing permissions and
  14663. * limitations under the License.
  14664. */
  14665. function documentEntryMap() {
  14666. return new SortedMap(DocumentKey.comparator);
  14667. }
  14668. /**
  14669. * The memory-only RemoteDocumentCache for IndexedDb. To construct, invoke
  14670. * `newMemoryRemoteDocumentCache()`.
  14671. */
  14672. class MemoryRemoteDocumentCacheImpl {
  14673. /**
  14674. * @param sizer - Used to assess the size of a document. For eager GC, this is
  14675. * expected to just return 0 to avoid unnecessarily doing the work of
  14676. * calculating the size.
  14677. */
  14678. constructor(sizer) {
  14679. this.sizer = sizer;
  14680. /** Underlying cache of documents and their read times. */
  14681. this.docs = documentEntryMap();
  14682. /** Size of all cached documents. */
  14683. this.size = 0;
  14684. }
  14685. setIndexManager(indexManager) {
  14686. this.indexManager = indexManager;
  14687. }
  14688. /**
  14689. * Adds the supplied entry to the cache and updates the cache size as appropriate.
  14690. *
  14691. * All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer
  14692. * returned by `newChangeBuffer()`.
  14693. */
  14694. addEntry(transaction, doc) {
  14695. const key = doc.key;
  14696. const entry = this.docs.get(key);
  14697. const previousSize = entry ? entry.size : 0;
  14698. const currentSize = this.sizer(doc);
  14699. this.docs = this.docs.insert(key, {
  14700. document: doc.mutableCopy(),
  14701. size: currentSize
  14702. });
  14703. this.size += currentSize - previousSize;
  14704. return this.indexManager.addToCollectionParentIndex(transaction, key.path.popLast());
  14705. }
  14706. /**
  14707. * Removes the specified entry from the cache and updates the cache size as appropriate.
  14708. *
  14709. * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
  14710. * returned by `newChangeBuffer()`.
  14711. */
  14712. removeEntry(documentKey) {
  14713. const entry = this.docs.get(documentKey);
  14714. if (entry) {
  14715. this.docs = this.docs.remove(documentKey);
  14716. this.size -= entry.size;
  14717. }
  14718. }
  14719. getEntry(transaction, documentKey) {
  14720. const entry = this.docs.get(documentKey);
  14721. return PersistencePromise.resolve(entry
  14722. ? entry.document.mutableCopy()
  14723. : MutableDocument.newInvalidDocument(documentKey));
  14724. }
  14725. getEntries(transaction, documentKeys) {
  14726. let results = mutableDocumentMap();
  14727. documentKeys.forEach(documentKey => {
  14728. const entry = this.docs.get(documentKey);
  14729. results = results.insert(documentKey, entry
  14730. ? entry.document.mutableCopy()
  14731. : MutableDocument.newInvalidDocument(documentKey));
  14732. });
  14733. return PersistencePromise.resolve(results);
  14734. }
  14735. getDocumentsMatchingQuery(transaction, query, offset, mutatedDocs) {
  14736. let results = mutableDocumentMap();
  14737. // Documents are ordered by key, so we can use a prefix scan to narrow down
  14738. // the documents we need to match the query against.
  14739. const collectionPath = query.path;
  14740. const prefix = new DocumentKey(collectionPath.child(''));
  14741. const iterator = this.docs.getIteratorFrom(prefix);
  14742. while (iterator.hasNext()) {
  14743. const { key, value: { document } } = iterator.getNext();
  14744. if (!collectionPath.isPrefixOf(key.path)) {
  14745. break;
  14746. }
  14747. if (key.path.length > collectionPath.length + 1) {
  14748. // Exclude entries from subcollections.
  14749. continue;
  14750. }
  14751. if (indexOffsetComparator(newIndexOffsetFromDocument(document), offset) <= 0) {
  14752. // The document sorts before the offset.
  14753. continue;
  14754. }
  14755. if (!mutatedDocs.has(document.key) && !queryMatches(query, document)) {
  14756. // The document cannot possibly match the query.
  14757. continue;
  14758. }
  14759. results = results.insert(document.key, document.mutableCopy());
  14760. }
  14761. return PersistencePromise.resolve(results);
  14762. }
  14763. getAllFromCollectionGroup(transaction, collectionGroup, offset, limti) {
  14764. // This method should only be called from the IndexBackfiller if persistence
  14765. // is enabled.
  14766. fail();
  14767. }
  14768. forEachDocumentKey(transaction, f) {
  14769. return PersistencePromise.forEach(this.docs, (key) => f(key));
  14770. }
  14771. newChangeBuffer(options) {
  14772. // `trackRemovals` is ignores since the MemoryRemoteDocumentCache keeps
  14773. // a separate changelog and does not need special handling for removals.
  14774. return new MemoryRemoteDocumentChangeBuffer(this);
  14775. }
  14776. getSize(txn) {
  14777. return PersistencePromise.resolve(this.size);
  14778. }
  14779. }
  14780. /**
  14781. * Creates a new memory-only RemoteDocumentCache.
  14782. *
  14783. * @param sizer - Used to assess the size of a document. For eager GC, this is
  14784. * expected to just return 0 to avoid unnecessarily doing the work of
  14785. * calculating the size.
  14786. */
  14787. function newMemoryRemoteDocumentCache(sizer) {
  14788. return new MemoryRemoteDocumentCacheImpl(sizer);
  14789. }
  14790. /**
  14791. * Handles the details of adding and updating documents in the MemoryRemoteDocumentCache.
  14792. */
  14793. class MemoryRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
  14794. constructor(documentCache) {
  14795. super();
  14796. this.documentCache = documentCache;
  14797. }
  14798. applyChanges(transaction) {
  14799. const promises = [];
  14800. this.changes.forEach((key, doc) => {
  14801. if (doc.isValidDocument()) {
  14802. promises.push(this.documentCache.addEntry(transaction, doc));
  14803. }
  14804. else {
  14805. this.documentCache.removeEntry(key);
  14806. }
  14807. });
  14808. return PersistencePromise.waitFor(promises);
  14809. }
  14810. getFromCache(transaction, documentKey) {
  14811. return this.documentCache.getEntry(transaction, documentKey);
  14812. }
  14813. getAllFromCache(transaction, documentKeys) {
  14814. return this.documentCache.getEntries(transaction, documentKeys);
  14815. }
  14816. }
  14817. /**
  14818. * @license
  14819. * Copyright 2017 Google LLC
  14820. *
  14821. * Licensed under the Apache License, Version 2.0 (the "License");
  14822. * you may not use this file except in compliance with the License.
  14823. * You may obtain a copy of the License at
  14824. *
  14825. * http://www.apache.org/licenses/LICENSE-2.0
  14826. *
  14827. * Unless required by applicable law or agreed to in writing, software
  14828. * distributed under the License is distributed on an "AS IS" BASIS,
  14829. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14830. * See the License for the specific language governing permissions and
  14831. * limitations under the License.
  14832. */
  14833. class MemoryTargetCache {
  14834. constructor(persistence) {
  14835. this.persistence = persistence;
  14836. /**
  14837. * Maps a target to the data about that target
  14838. */
  14839. this.targets = new ObjectMap(t => canonifyTarget(t), targetEquals);
  14840. /** The last received snapshot version. */
  14841. this.lastRemoteSnapshotVersion = SnapshotVersion.min();
  14842. /** The highest numbered target ID encountered. */
  14843. this.highestTargetId = 0;
  14844. /** The highest sequence number encountered. */
  14845. this.highestSequenceNumber = 0;
  14846. /**
  14847. * A ordered bidirectional mapping between documents and the remote target
  14848. * IDs.
  14849. */
  14850. this.references = new ReferenceSet();
  14851. this.targetCount = 0;
  14852. this.targetIdGenerator = TargetIdGenerator.forTargetCache();
  14853. }
  14854. forEachTarget(txn, f) {
  14855. this.targets.forEach((_, targetData) => f(targetData));
  14856. return PersistencePromise.resolve();
  14857. }
  14858. getLastRemoteSnapshotVersion(transaction) {
  14859. return PersistencePromise.resolve(this.lastRemoteSnapshotVersion);
  14860. }
  14861. getHighestSequenceNumber(transaction) {
  14862. return PersistencePromise.resolve(this.highestSequenceNumber);
  14863. }
  14864. allocateTargetId(transaction) {
  14865. this.highestTargetId = this.targetIdGenerator.next();
  14866. return PersistencePromise.resolve(this.highestTargetId);
  14867. }
  14868. setTargetsMetadata(transaction, highestListenSequenceNumber, lastRemoteSnapshotVersion) {
  14869. if (lastRemoteSnapshotVersion) {
  14870. this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;
  14871. }
  14872. if (highestListenSequenceNumber > this.highestSequenceNumber) {
  14873. this.highestSequenceNumber = highestListenSequenceNumber;
  14874. }
  14875. return PersistencePromise.resolve();
  14876. }
  14877. saveTargetData(targetData) {
  14878. this.targets.set(targetData.target, targetData);
  14879. const targetId = targetData.targetId;
  14880. if (targetId > this.highestTargetId) {
  14881. this.targetIdGenerator = new TargetIdGenerator(targetId);
  14882. this.highestTargetId = targetId;
  14883. }
  14884. if (targetData.sequenceNumber > this.highestSequenceNumber) {
  14885. this.highestSequenceNumber = targetData.sequenceNumber;
  14886. }
  14887. }
  14888. addTargetData(transaction, targetData) {
  14889. this.saveTargetData(targetData);
  14890. this.targetCount += 1;
  14891. return PersistencePromise.resolve();
  14892. }
  14893. updateTargetData(transaction, targetData) {
  14894. this.saveTargetData(targetData);
  14895. return PersistencePromise.resolve();
  14896. }
  14897. removeTargetData(transaction, targetData) {
  14898. this.targets.delete(targetData.target);
  14899. this.references.removeReferencesForId(targetData.targetId);
  14900. this.targetCount -= 1;
  14901. return PersistencePromise.resolve();
  14902. }
  14903. removeTargets(transaction, upperBound, activeTargetIds) {
  14904. let count = 0;
  14905. const removals = [];
  14906. this.targets.forEach((key, targetData) => {
  14907. if (targetData.sequenceNumber <= upperBound &&
  14908. activeTargetIds.get(targetData.targetId) === null) {
  14909. this.targets.delete(key);
  14910. removals.push(this.removeMatchingKeysForTargetId(transaction, targetData.targetId));
  14911. count++;
  14912. }
  14913. });
  14914. return PersistencePromise.waitFor(removals).next(() => count);
  14915. }
  14916. getTargetCount(transaction) {
  14917. return PersistencePromise.resolve(this.targetCount);
  14918. }
  14919. getTargetData(transaction, target) {
  14920. const targetData = this.targets.get(target) || null;
  14921. return PersistencePromise.resolve(targetData);
  14922. }
  14923. addMatchingKeys(txn, keys, targetId) {
  14924. this.references.addReferences(keys, targetId);
  14925. return PersistencePromise.resolve();
  14926. }
  14927. removeMatchingKeys(txn, keys, targetId) {
  14928. this.references.removeReferences(keys, targetId);
  14929. const referenceDelegate = this.persistence.referenceDelegate;
  14930. const promises = [];
  14931. if (referenceDelegate) {
  14932. keys.forEach(key => {
  14933. promises.push(referenceDelegate.markPotentiallyOrphaned(txn, key));
  14934. });
  14935. }
  14936. return PersistencePromise.waitFor(promises);
  14937. }
  14938. removeMatchingKeysForTargetId(txn, targetId) {
  14939. this.references.removeReferencesForId(targetId);
  14940. return PersistencePromise.resolve();
  14941. }
  14942. getMatchingKeysForTargetId(txn, targetId) {
  14943. const matchingKeys = this.references.referencesForId(targetId);
  14944. return PersistencePromise.resolve(matchingKeys);
  14945. }
  14946. containsKey(txn, key) {
  14947. return PersistencePromise.resolve(this.references.containsKey(key));
  14948. }
  14949. }
  14950. /**
  14951. * @license
  14952. * Copyright 2017 Google LLC
  14953. *
  14954. * Licensed under the Apache License, Version 2.0 (the "License");
  14955. * you may not use this file except in compliance with the License.
  14956. * You may obtain a copy of the License at
  14957. *
  14958. * http://www.apache.org/licenses/LICENSE-2.0
  14959. *
  14960. * Unless required by applicable law or agreed to in writing, software
  14961. * distributed under the License is distributed on an "AS IS" BASIS,
  14962. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14963. * See the License for the specific language governing permissions and
  14964. * limitations under the License.
  14965. */
  14966. const LOG_TAG$d = 'MemoryPersistence';
  14967. /**
  14968. * A memory-backed instance of Persistence. Data is stored only in RAM and
  14969. * not persisted across sessions.
  14970. */
  14971. class MemoryPersistence {
  14972. /**
  14973. * The constructor accepts a factory for creating a reference delegate. This
  14974. * allows both the delegate and this instance to have strong references to
  14975. * each other without having nullable fields that would then need to be
  14976. * checked or asserted on every access.
  14977. */
  14978. constructor(referenceDelegateFactory, serializer) {
  14979. this.mutationQueues = {};
  14980. this.overlays = {};
  14981. this.listenSequence = new ListenSequence(0);
  14982. this._started = false;
  14983. this._started = true;
  14984. this.referenceDelegate = referenceDelegateFactory(this);
  14985. this.targetCache = new MemoryTargetCache(this);
  14986. const sizer = (doc) => this.referenceDelegate.documentSize(doc);
  14987. this.indexManager = new MemoryIndexManager();
  14988. this.remoteDocumentCache = newMemoryRemoteDocumentCache(sizer);
  14989. this.serializer = new LocalSerializer(serializer);
  14990. this.bundleCache = new MemoryBundleCache(this.serializer);
  14991. }
  14992. start() {
  14993. return Promise.resolve();
  14994. }
  14995. shutdown() {
  14996. // No durable state to ensure is closed on shutdown.
  14997. this._started = false;
  14998. return Promise.resolve();
  14999. }
  15000. get started() {
  15001. return this._started;
  15002. }
  15003. setDatabaseDeletedListener() {
  15004. // No op.
  15005. }
  15006. setNetworkEnabled() {
  15007. // No op.
  15008. }
  15009. getIndexManager(user) {
  15010. // We do not currently support indices for memory persistence, so we can
  15011. // return the same shared instance of the memory index manager.
  15012. return this.indexManager;
  15013. }
  15014. getDocumentOverlayCache(user) {
  15015. let overlay = this.overlays[user.toKey()];
  15016. if (!overlay) {
  15017. overlay = new MemoryDocumentOverlayCache();
  15018. this.overlays[user.toKey()] = overlay;
  15019. }
  15020. return overlay;
  15021. }
  15022. getMutationQueue(user, indexManager) {
  15023. let queue = this.mutationQueues[user.toKey()];
  15024. if (!queue) {
  15025. queue = new MemoryMutationQueue(indexManager, this.referenceDelegate);
  15026. this.mutationQueues[user.toKey()] = queue;
  15027. }
  15028. return queue;
  15029. }
  15030. getTargetCache() {
  15031. return this.targetCache;
  15032. }
  15033. getRemoteDocumentCache() {
  15034. return this.remoteDocumentCache;
  15035. }
  15036. getBundleCache() {
  15037. return this.bundleCache;
  15038. }
  15039. runTransaction(action, mode, transactionOperation) {
  15040. logDebug(LOG_TAG$d, 'Starting transaction:', action);
  15041. const txn = new MemoryTransaction(this.listenSequence.next());
  15042. this.referenceDelegate.onTransactionStarted();
  15043. return transactionOperation(txn)
  15044. .next(result => {
  15045. return this.referenceDelegate
  15046. .onTransactionCommitted(txn)
  15047. .next(() => result);
  15048. })
  15049. .toPromise()
  15050. .then(result => {
  15051. txn.raiseOnCommittedEvent();
  15052. return result;
  15053. });
  15054. }
  15055. mutationQueuesContainKey(transaction, key) {
  15056. return PersistencePromise.or(Object.values(this.mutationQueues).map(queue => () => queue.containsKey(transaction, key)));
  15057. }
  15058. }
  15059. /**
  15060. * Memory persistence is not actually transactional, but future implementations
  15061. * may have transaction-scoped state.
  15062. */
  15063. class MemoryTransaction extends PersistenceTransaction {
  15064. constructor(currentSequenceNumber) {
  15065. super();
  15066. this.currentSequenceNumber = currentSequenceNumber;
  15067. }
  15068. }
  15069. class MemoryEagerDelegate {
  15070. constructor(persistence) {
  15071. this.persistence = persistence;
  15072. /** Tracks all documents that are active in Query views. */
  15073. this.localViewReferences = new ReferenceSet();
  15074. /** The list of documents that are potentially GCed after each transaction. */
  15075. this._orphanedDocuments = null;
  15076. }
  15077. static factory(persistence) {
  15078. return new MemoryEagerDelegate(persistence);
  15079. }
  15080. get orphanedDocuments() {
  15081. if (!this._orphanedDocuments) {
  15082. throw fail();
  15083. }
  15084. else {
  15085. return this._orphanedDocuments;
  15086. }
  15087. }
  15088. addReference(txn, targetId, key) {
  15089. this.localViewReferences.addReference(key, targetId);
  15090. this.orphanedDocuments.delete(key.toString());
  15091. return PersistencePromise.resolve();
  15092. }
  15093. removeReference(txn, targetId, key) {
  15094. this.localViewReferences.removeReference(key, targetId);
  15095. this.orphanedDocuments.add(key.toString());
  15096. return PersistencePromise.resolve();
  15097. }
  15098. markPotentiallyOrphaned(txn, key) {
  15099. this.orphanedDocuments.add(key.toString());
  15100. return PersistencePromise.resolve();
  15101. }
  15102. removeTarget(txn, targetData) {
  15103. const orphaned = this.localViewReferences.removeReferencesForId(targetData.targetId);
  15104. orphaned.forEach(key => this.orphanedDocuments.add(key.toString()));
  15105. const cache = this.persistence.getTargetCache();
  15106. return cache
  15107. .getMatchingKeysForTargetId(txn, targetData.targetId)
  15108. .next(keys => {
  15109. keys.forEach(key => this.orphanedDocuments.add(key.toString()));
  15110. })
  15111. .next(() => cache.removeTargetData(txn, targetData));
  15112. }
  15113. onTransactionStarted() {
  15114. this._orphanedDocuments = new Set();
  15115. }
  15116. onTransactionCommitted(txn) {
  15117. // Remove newly orphaned documents.
  15118. const cache = this.persistence.getRemoteDocumentCache();
  15119. const changeBuffer = cache.newChangeBuffer();
  15120. return PersistencePromise.forEach(this.orphanedDocuments, (path) => {
  15121. const key = DocumentKey.fromPath(path);
  15122. return this.isReferenced(txn, key).next(isReferenced => {
  15123. if (!isReferenced) {
  15124. changeBuffer.removeEntry(key, SnapshotVersion.min());
  15125. }
  15126. });
  15127. }).next(() => {
  15128. this._orphanedDocuments = null;
  15129. return changeBuffer.apply(txn);
  15130. });
  15131. }
  15132. updateLimboDocument(txn, key) {
  15133. return this.isReferenced(txn, key).next(isReferenced => {
  15134. if (isReferenced) {
  15135. this.orphanedDocuments.delete(key.toString());
  15136. }
  15137. else {
  15138. this.orphanedDocuments.add(key.toString());
  15139. }
  15140. });
  15141. }
  15142. documentSize(doc) {
  15143. // For eager GC, we don't care about the document size, there are no size thresholds.
  15144. return 0;
  15145. }
  15146. isReferenced(txn, key) {
  15147. return PersistencePromise.or([
  15148. () => PersistencePromise.resolve(this.localViewReferences.containsKey(key)),
  15149. () => this.persistence.getTargetCache().containsKey(txn, key),
  15150. () => this.persistence.mutationQueuesContainKey(txn, key)
  15151. ]);
  15152. }
  15153. }
  15154. class MemoryLruDelegate {
  15155. constructor(persistence, lruParams) {
  15156. this.persistence = persistence;
  15157. this.orphanedSequenceNumbers = new ObjectMap(k => encodeResourcePath(k.path), (l, r) => l.isEqual(r));
  15158. this.garbageCollector = newLruGarbageCollector(this, lruParams);
  15159. }
  15160. static factory(persistence, lruParams) {
  15161. return new MemoryLruDelegate(persistence, lruParams);
  15162. }
  15163. // No-ops, present so memory persistence doesn't have to care which delegate
  15164. // it has.
  15165. onTransactionStarted() { }
  15166. onTransactionCommitted(txn) {
  15167. return PersistencePromise.resolve();
  15168. }
  15169. forEachTarget(txn, f) {
  15170. return this.persistence.getTargetCache().forEachTarget(txn, f);
  15171. }
  15172. getSequenceNumberCount(txn) {
  15173. const docCountPromise = this.orphanedDocumentCount(txn);
  15174. const targetCountPromise = this.persistence
  15175. .getTargetCache()
  15176. .getTargetCount(txn);
  15177. return targetCountPromise.next(targetCount => docCountPromise.next(docCount => targetCount + docCount));
  15178. }
  15179. orphanedDocumentCount(txn) {
  15180. let orphanedCount = 0;
  15181. return this.forEachOrphanedDocumentSequenceNumber(txn, _ => {
  15182. orphanedCount++;
  15183. }).next(() => orphanedCount);
  15184. }
  15185. forEachOrphanedDocumentSequenceNumber(txn, f) {
  15186. return PersistencePromise.forEach(this.orphanedSequenceNumbers, (key, sequenceNumber) => {
  15187. // Pass in the exact sequence number as the upper bound so we know it won't be pinned by
  15188. // being too recent.
  15189. return this.isPinned(txn, key, sequenceNumber).next(isPinned => {
  15190. if (!isPinned) {
  15191. return f(sequenceNumber);
  15192. }
  15193. else {
  15194. return PersistencePromise.resolve();
  15195. }
  15196. });
  15197. });
  15198. }
  15199. removeTargets(txn, upperBound, activeTargetIds) {
  15200. return this.persistence
  15201. .getTargetCache()
  15202. .removeTargets(txn, upperBound, activeTargetIds);
  15203. }
  15204. removeOrphanedDocuments(txn, upperBound) {
  15205. let count = 0;
  15206. const cache = this.persistence.getRemoteDocumentCache();
  15207. const changeBuffer = cache.newChangeBuffer();
  15208. const p = cache.forEachDocumentKey(txn, key => {
  15209. return this.isPinned(txn, key, upperBound).next(isPinned => {
  15210. if (!isPinned) {
  15211. count++;
  15212. changeBuffer.removeEntry(key, SnapshotVersion.min());
  15213. }
  15214. });
  15215. });
  15216. return p.next(() => changeBuffer.apply(txn)).next(() => count);
  15217. }
  15218. markPotentiallyOrphaned(txn, key) {
  15219. this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);
  15220. return PersistencePromise.resolve();
  15221. }
  15222. removeTarget(txn, targetData) {
  15223. const updated = targetData.withSequenceNumber(txn.currentSequenceNumber);
  15224. return this.persistence.getTargetCache().updateTargetData(txn, updated);
  15225. }
  15226. addReference(txn, targetId, key) {
  15227. this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);
  15228. return PersistencePromise.resolve();
  15229. }
  15230. removeReference(txn, targetId, key) {
  15231. this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);
  15232. return PersistencePromise.resolve();
  15233. }
  15234. updateLimboDocument(txn, key) {
  15235. this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);
  15236. return PersistencePromise.resolve();
  15237. }
  15238. documentSize(document) {
  15239. let documentSize = document.key.toString().length;
  15240. if (document.isFoundDocument()) {
  15241. documentSize += estimateByteSize(document.data.value);
  15242. }
  15243. return documentSize;
  15244. }
  15245. isPinned(txn, key, upperBound) {
  15246. return PersistencePromise.or([
  15247. () => this.persistence.mutationQueuesContainKey(txn, key),
  15248. () => this.persistence.getTargetCache().containsKey(txn, key),
  15249. () => {
  15250. const orphanedAt = this.orphanedSequenceNumbers.get(key);
  15251. return PersistencePromise.resolve(orphanedAt !== undefined && orphanedAt > upperBound);
  15252. }
  15253. ]);
  15254. }
  15255. getCacheSize(txn) {
  15256. return this.persistence.getRemoteDocumentCache().getSize(txn);
  15257. }
  15258. }
  15259. /**
  15260. * @license
  15261. * Copyright 2020 Google LLC
  15262. *
  15263. * Licensed under the Apache License, Version 2.0 (the "License");
  15264. * you may not use this file except in compliance with the License.
  15265. * You may obtain a copy of the License at
  15266. *
  15267. * http://www.apache.org/licenses/LICENSE-2.0
  15268. *
  15269. * Unless required by applicable law or agreed to in writing, software
  15270. * distributed under the License is distributed on an "AS IS" BASIS,
  15271. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15272. * See the License for the specific language governing permissions and
  15273. * limitations under the License.
  15274. */
  15275. /** Performs database creation and schema upgrades. */
  15276. class SchemaConverter {
  15277. constructor(serializer) {
  15278. this.serializer = serializer;
  15279. }
  15280. /**
  15281. * Performs database creation and schema upgrades.
  15282. *
  15283. * Note that in production, this method is only ever used to upgrade the schema
  15284. * to SCHEMA_VERSION. Different values of toVersion are only used for testing
  15285. * and local feature development.
  15286. */
  15287. createOrUpgrade(db, txn, fromVersion, toVersion) {
  15288. const simpleDbTransaction = new SimpleDbTransaction('createOrUpgrade', txn);
  15289. if (fromVersion < 1 && toVersion >= 1) {
  15290. createPrimaryClientStore(db);
  15291. createMutationQueue(db);
  15292. createQueryCache(db);
  15293. createLegacyRemoteDocumentCache(db);
  15294. }
  15295. // Migration 2 to populate the targetGlobal object no longer needed since
  15296. // migration 3 unconditionally clears it.
  15297. let p = PersistencePromise.resolve();
  15298. if (fromVersion < 3 && toVersion >= 3) {
  15299. // Brand new clients don't need to drop and recreate--only clients that
  15300. // potentially have corrupt data.
  15301. if (fromVersion !== 0) {
  15302. dropQueryCache(db);
  15303. createQueryCache(db);
  15304. }
  15305. p = p.next(() => writeEmptyTargetGlobalEntry(simpleDbTransaction));
  15306. }
  15307. if (fromVersion < 4 && toVersion >= 4) {
  15308. if (fromVersion !== 0) {
  15309. // Schema version 3 uses auto-generated keys to generate globally unique
  15310. // mutation batch IDs (this was previously ensured internally by the
  15311. // client). To migrate to the new schema, we have to read all mutations
  15312. // and write them back out. We preserve the existing batch IDs to guarantee
  15313. // consistency with other object stores. Any further mutation batch IDs will
  15314. // be auto-generated.
  15315. p = p.next(() => upgradeMutationBatchSchemaAndMigrateData(db, simpleDbTransaction));
  15316. }
  15317. p = p.next(() => {
  15318. createClientMetadataStore(db);
  15319. });
  15320. }
  15321. if (fromVersion < 5 && toVersion >= 5) {
  15322. p = p.next(() => this.removeAcknowledgedMutations(simpleDbTransaction));
  15323. }
  15324. if (fromVersion < 6 && toVersion >= 6) {
  15325. p = p.next(() => {
  15326. createDocumentGlobalStore(db);
  15327. return this.addDocumentGlobal(simpleDbTransaction);
  15328. });
  15329. }
  15330. if (fromVersion < 7 && toVersion >= 7) {
  15331. p = p.next(() => this.ensureSequenceNumbers(simpleDbTransaction));
  15332. }
  15333. if (fromVersion < 8 && toVersion >= 8) {
  15334. p = p.next(() => this.createCollectionParentIndex(db, simpleDbTransaction));
  15335. }
  15336. if (fromVersion < 9 && toVersion >= 9) {
  15337. p = p.next(() => {
  15338. // Multi-Tab used to manage its own changelog, but this has been moved
  15339. // to the DbRemoteDocument object store itself. Since the previous change
  15340. // log only contained transient data, we can drop its object store.
  15341. dropRemoteDocumentChangesStore(db);
  15342. // Note: Schema version 9 used to create a read time index for the
  15343. // RemoteDocumentCache. This is now done with schema version 13.
  15344. });
  15345. }
  15346. if (fromVersion < 10 && toVersion >= 10) {
  15347. p = p.next(() => this.rewriteCanonicalIds(simpleDbTransaction));
  15348. }
  15349. if (fromVersion < 11 && toVersion >= 11) {
  15350. p = p.next(() => {
  15351. createBundlesStore(db);
  15352. createNamedQueriesStore(db);
  15353. });
  15354. }
  15355. if (fromVersion < 12 && toVersion >= 12) {
  15356. p = p.next(() => {
  15357. createDocumentOverlayStore(db);
  15358. });
  15359. }
  15360. if (fromVersion < 13 && toVersion >= 13) {
  15361. p = p
  15362. .next(() => createRemoteDocumentCache(db))
  15363. .next(() => this.rewriteRemoteDocumentCache(db, simpleDbTransaction))
  15364. .next(() => db.deleteObjectStore(DbRemoteDocumentStore$1));
  15365. }
  15366. if (fromVersion < 14 && toVersion >= 14) {
  15367. p = p.next(() => this.runOverlayMigration(db, simpleDbTransaction));
  15368. }
  15369. if (fromVersion < 15 && toVersion >= 15) {
  15370. p = p.next(() => createFieldIndex(db));
  15371. }
  15372. return p;
  15373. }
  15374. addDocumentGlobal(txn) {
  15375. let byteSize = 0;
  15376. return txn
  15377. .store(DbRemoteDocumentStore$1)
  15378. .iterate((_, doc) => {
  15379. byteSize += dbDocumentSize(doc);
  15380. })
  15381. .next(() => {
  15382. const metadata = { byteSize };
  15383. return txn
  15384. .store(DbRemoteDocumentGlobalStore)
  15385. .put(DbRemoteDocumentGlobalKey, metadata);
  15386. });
  15387. }
  15388. removeAcknowledgedMutations(txn) {
  15389. const queuesStore = txn.store(DbMutationQueueStore);
  15390. const mutationsStore = txn.store(DbMutationBatchStore);
  15391. return queuesStore.loadAll().next(queues => {
  15392. return PersistencePromise.forEach(queues, (queue) => {
  15393. const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
  15394. return mutationsStore
  15395. .loadAll(DbMutationBatchUserMutationsIndex, range)
  15396. .next(dbBatches => {
  15397. return PersistencePromise.forEach(dbBatches, (dbBatch) => {
  15398. hardAssert(dbBatch.userId === queue.userId);
  15399. const batch = fromDbMutationBatch(this.serializer, dbBatch);
  15400. return removeMutationBatch(txn, queue.userId, batch).next(() => { });
  15401. });
  15402. });
  15403. });
  15404. });
  15405. }
  15406. /**
  15407. * Ensures that every document in the remote document cache has a corresponding sentinel row
  15408. * with a sequence number. Missing rows are given the most recently used sequence number.
  15409. */
  15410. ensureSequenceNumbers(txn) {
  15411. const documentTargetStore = txn.store(DbTargetDocumentStore);
  15412. const documentsStore = txn.store(DbRemoteDocumentStore$1);
  15413. const globalTargetStore = txn.store(DbTargetGlobalStore);
  15414. return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
  15415. const writeSentinelKey = (path) => {
  15416. return documentTargetStore.put({
  15417. targetId: 0,
  15418. path: encodeResourcePath(path),
  15419. sequenceNumber: metadata.highestListenSequenceNumber
  15420. });
  15421. };
  15422. const promises = [];
  15423. return documentsStore
  15424. .iterate((key, doc) => {
  15425. const path = new ResourcePath(key);
  15426. const docSentinelKey = sentinelKey(path);
  15427. promises.push(documentTargetStore.get(docSentinelKey).next(maybeSentinel => {
  15428. if (!maybeSentinel) {
  15429. return writeSentinelKey(path);
  15430. }
  15431. else {
  15432. return PersistencePromise.resolve();
  15433. }
  15434. }));
  15435. })
  15436. .next(() => PersistencePromise.waitFor(promises));
  15437. });
  15438. }
  15439. createCollectionParentIndex(db, txn) {
  15440. // Create the index.
  15441. db.createObjectStore(DbCollectionParentStore, {
  15442. keyPath: DbCollectionParentKeyPath
  15443. });
  15444. const collectionParentsStore = txn.store(DbCollectionParentStore);
  15445. // Helper to add an index entry iff we haven't already written it.
  15446. const cache = new MemoryCollectionParentIndex();
  15447. const addEntry = (collectionPath) => {
  15448. if (cache.add(collectionPath)) {
  15449. const collectionId = collectionPath.lastSegment();
  15450. const parentPath = collectionPath.popLast();
  15451. return collectionParentsStore.put({
  15452. collectionId,
  15453. parent: encodeResourcePath(parentPath)
  15454. });
  15455. }
  15456. };
  15457. // Index existing remote documents.
  15458. return txn
  15459. .store(DbRemoteDocumentStore$1)
  15460. .iterate({ keysOnly: true }, (pathSegments, _) => {
  15461. const path = new ResourcePath(pathSegments);
  15462. return addEntry(path.popLast());
  15463. })
  15464. .next(() => {
  15465. // Index existing mutations.
  15466. return txn
  15467. .store(DbDocumentMutationStore)
  15468. .iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
  15469. const path = decodeResourcePath(encodedPath);
  15470. return addEntry(path.popLast());
  15471. });
  15472. });
  15473. }
  15474. rewriteCanonicalIds(txn) {
  15475. const targetStore = txn.store(DbTargetStore);
  15476. return targetStore.iterate((key, originalDbTarget) => {
  15477. const originalTargetData = fromDbTarget(originalDbTarget);
  15478. const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
  15479. return targetStore.put(updatedDbTarget);
  15480. });
  15481. }
  15482. rewriteRemoteDocumentCache(db, transaction) {
  15483. const legacyRemoteDocumentStore = transaction.store(DbRemoteDocumentStore$1);
  15484. const writes = [];
  15485. return legacyRemoteDocumentStore
  15486. .iterate((_, legacyDocument) => {
  15487. const remoteDocumentStore = transaction.store(DbRemoteDocumentStore);
  15488. const path = extractKey(legacyDocument).path.toArray();
  15489. const dbRemoteDocument = {
  15490. prefixPath: path.slice(0, path.length - 2),
  15491. collectionGroup: path[path.length - 2],
  15492. documentId: path[path.length - 1],
  15493. readTime: legacyDocument.readTime || [0, 0],
  15494. unknownDocument: legacyDocument.unknownDocument,
  15495. noDocument: legacyDocument.noDocument,
  15496. document: legacyDocument.document,
  15497. hasCommittedMutations: !!legacyDocument.hasCommittedMutations
  15498. };
  15499. writes.push(remoteDocumentStore.put(dbRemoteDocument));
  15500. })
  15501. .next(() => PersistencePromise.waitFor(writes));
  15502. }
  15503. runOverlayMigration(db, transaction) {
  15504. const mutationsStore = transaction.store(DbMutationBatchStore);
  15505. const remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
  15506. const memoryPersistence = new MemoryPersistence(MemoryEagerDelegate.factory, this.serializer.remoteSerializer);
  15507. return mutationsStore.loadAll().next(dbBatches => {
  15508. const userToDocumentSet = new Map();
  15509. dbBatches.forEach(dbBatch => {
  15510. var _a;
  15511. let documentSet = (_a = userToDocumentSet.get(dbBatch.userId)) !== null && _a !== void 0 ? _a : documentKeySet();
  15512. const batch = fromDbMutationBatch(this.serializer, dbBatch);
  15513. batch.keys().forEach(key => (documentSet = documentSet.add(key)));
  15514. userToDocumentSet.set(dbBatch.userId, documentSet);
  15515. });
  15516. return PersistencePromise.forEach(userToDocumentSet, (allDocumentKeysForUser, userId) => {
  15517. const user = new User(userId);
  15518. const documentOverlayCache = IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
  15519. // NOTE: The index manager and the reference delegate are
  15520. // irrelevant for the purpose of recalculating and saving
  15521. // overlays. We can therefore simply use the memory
  15522. // implementation.
  15523. const indexManager = memoryPersistence.getIndexManager(user);
  15524. const mutationQueue = IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, memoryPersistence.referenceDelegate);
  15525. const localDocumentsView = new LocalDocumentsView(remoteDocumentCache, mutationQueue, documentOverlayCache, indexManager);
  15526. return localDocumentsView
  15527. .recalculateAndSaveOverlaysForDocumentKeys(new IndexedDbTransaction(transaction, ListenSequence.INVALID), allDocumentKeysForUser)
  15528. .next();
  15529. });
  15530. });
  15531. }
  15532. }
  15533. function sentinelKey(path) {
  15534. return [0, encodeResourcePath(path)];
  15535. }
  15536. function createPrimaryClientStore(db) {
  15537. db.createObjectStore(DbPrimaryClientStore);
  15538. }
  15539. function createMutationQueue(db) {
  15540. db.createObjectStore(DbMutationQueueStore, {
  15541. keyPath: DbMutationQueueKeyPath
  15542. });
  15543. const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
  15544. keyPath: DbMutationBatchKeyPath,
  15545. autoIncrement: true
  15546. });
  15547. mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
  15548. db.createObjectStore(DbDocumentMutationStore);
  15549. }
  15550. /**
  15551. * Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
  15552. * and rewrites all data.
  15553. */
  15554. function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
  15555. const v1MutationsStore = txn.store(DbMutationBatchStore);
  15556. return v1MutationsStore.loadAll().next(existingMutations => {
  15557. db.deleteObjectStore(DbMutationBatchStore);
  15558. const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
  15559. keyPath: DbMutationBatchKeyPath,
  15560. autoIncrement: true
  15561. });
  15562. mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
  15563. const v3MutationsStore = txn.store(DbMutationBatchStore);
  15564. const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
  15565. return PersistencePromise.waitFor(writeAll);
  15566. });
  15567. }
  15568. function createLegacyRemoteDocumentCache(db) {
  15569. db.createObjectStore(DbRemoteDocumentStore$1);
  15570. }
  15571. function createRemoteDocumentCache(db) {
  15572. const remoteDocumentStore = db.createObjectStore(DbRemoteDocumentStore, {
  15573. keyPath: DbRemoteDocumentKeyPath
  15574. });
  15575. remoteDocumentStore.createIndex(DbRemoteDocumentDocumentKeyIndex, DbRemoteDocumentDocumentKeyIndexPath);
  15576. remoteDocumentStore.createIndex(DbRemoteDocumentCollectionGroupIndex, DbRemoteDocumentCollectionGroupIndexPath);
  15577. }
  15578. function createDocumentGlobalStore(db) {
  15579. db.createObjectStore(DbRemoteDocumentGlobalStore);
  15580. }
  15581. function createQueryCache(db) {
  15582. const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
  15583. keyPath: DbTargetDocumentKeyPath
  15584. });
  15585. targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
  15586. const targetStore = db.createObjectStore(DbTargetStore, {
  15587. keyPath: DbTargetKeyPath
  15588. });
  15589. // NOTE: This is unique only because the TargetId is the suffix.
  15590. targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
  15591. db.createObjectStore(DbTargetGlobalStore);
  15592. }
  15593. function dropQueryCache(db) {
  15594. db.deleteObjectStore(DbTargetDocumentStore);
  15595. db.deleteObjectStore(DbTargetStore);
  15596. db.deleteObjectStore(DbTargetGlobalStore);
  15597. }
  15598. function dropRemoteDocumentChangesStore(db) {
  15599. if (db.objectStoreNames.contains('remoteDocumentChanges')) {
  15600. db.deleteObjectStore('remoteDocumentChanges');
  15601. }
  15602. }
  15603. /**
  15604. * Creates the target global singleton row.
  15605. *
  15606. * @param txn - The version upgrade transaction for indexeddb
  15607. */
  15608. function writeEmptyTargetGlobalEntry(txn) {
  15609. const globalStore = txn.store(DbTargetGlobalStore);
  15610. const metadata = {
  15611. highestTargetId: 0,
  15612. highestListenSequenceNumber: 0,
  15613. lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
  15614. targetCount: 0
  15615. };
  15616. return globalStore.put(DbTargetGlobalKey, metadata);
  15617. }
  15618. function createClientMetadataStore(db) {
  15619. db.createObjectStore(DbClientMetadataStore, {
  15620. keyPath: DbClientMetadataKeyPath
  15621. });
  15622. }
  15623. function createBundlesStore(db) {
  15624. db.createObjectStore(DbBundleStore, {
  15625. keyPath: DbBundleKeyPath
  15626. });
  15627. }
  15628. function createNamedQueriesStore(db) {
  15629. db.createObjectStore(DbNamedQueryStore, {
  15630. keyPath: DbNamedQueryKeyPath
  15631. });
  15632. }
  15633. function createFieldIndex(db) {
  15634. const indexConfigurationStore = db.createObjectStore(DbIndexConfigurationStore, {
  15635. keyPath: DbIndexConfigurationKeyPath,
  15636. autoIncrement: true
  15637. });
  15638. indexConfigurationStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
  15639. const indexStateStore = db.createObjectStore(DbIndexStateStore, {
  15640. keyPath: DbIndexStateKeyPath
  15641. });
  15642. indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
  15643. const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
  15644. keyPath: DbIndexEntryKeyPath
  15645. });
  15646. indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
  15647. }
  15648. function createDocumentOverlayStore(db) {
  15649. const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
  15650. keyPath: DbDocumentOverlayKeyPath
  15651. });
  15652. documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
  15653. documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
  15654. }
  15655. function extractKey(remoteDoc) {
  15656. if (remoteDoc.document) {
  15657. return new DocumentKey(ResourcePath.fromString(remoteDoc.document.name).popFirst(5));
  15658. }
  15659. else if (remoteDoc.noDocument) {
  15660. return DocumentKey.fromSegments(remoteDoc.noDocument.path);
  15661. }
  15662. else if (remoteDoc.unknownDocument) {
  15663. return DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
  15664. }
  15665. else {
  15666. return fail();
  15667. }
  15668. }
  15669. /**
  15670. * @license
  15671. * Copyright 2017 Google LLC
  15672. *
  15673. * Licensed under the Apache License, Version 2.0 (the "License");
  15674. * you may not use this file except in compliance with the License.
  15675. * You may obtain a copy of the License at
  15676. *
  15677. * http://www.apache.org/licenses/LICENSE-2.0
  15678. *
  15679. * Unless required by applicable law or agreed to in writing, software
  15680. * distributed under the License is distributed on an "AS IS" BASIS,
  15681. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15682. * See the License for the specific language governing permissions and
  15683. * limitations under the License.
  15684. */
  15685. const LOG_TAG$c = 'IndexedDbPersistence';
  15686. /**
  15687. * Oldest acceptable age in milliseconds for client metadata before the client
  15688. * is considered inactive and its associated data is garbage collected.
  15689. */
  15690. const MAX_CLIENT_AGE_MS = 30 * 60 * 1000; // 30 minutes
  15691. /**
  15692. * Oldest acceptable metadata age for clients that may participate in the
  15693. * primary lease election. Clients that have not updated their client metadata
  15694. * within 5 seconds are not eligible to receive a primary lease.
  15695. */
  15696. const MAX_PRIMARY_ELIGIBLE_AGE_MS = 5000;
  15697. /**
  15698. * The interval at which clients will update their metadata, including
  15699. * refreshing their primary lease if held or potentially trying to acquire it if
  15700. * not held.
  15701. *
  15702. * Primary clients may opportunistically refresh their metadata earlier
  15703. * if they're already performing an IndexedDB operation.
  15704. */
  15705. const CLIENT_METADATA_REFRESH_INTERVAL_MS = 4000;
  15706. /** User-facing error when the primary lease is required but not available. */
  15707. const PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG = 'Failed to obtain exclusive access to the persistence layer. To allow ' +
  15708. 'shared access, multi-tab synchronization has to be enabled in all tabs. ' +
  15709. 'If you are using `experimentalForceOwningTab:true`, make sure that only ' +
  15710. 'one tab has persistence enabled at any given time.';
  15711. const UNSUPPORTED_PLATFORM_ERROR_MSG = 'This platform is either missing IndexedDB or is known to have ' +
  15712. 'an incomplete implementation. Offline persistence has been disabled.';
  15713. // The format of the LocalStorage key that stores zombied client is:
  15714. // firestore_zombie_<persistence_prefix>_<instance_key>
  15715. const ZOMBIED_CLIENTS_KEY_PREFIX = 'firestore_zombie';
  15716. /**
  15717. * The name of the main (and currently only) IndexedDB database. This name is
  15718. * appended to the prefix provided to the IndexedDbPersistence constructor.
  15719. */
  15720. const MAIN_DATABASE = 'main';
  15721. /**
  15722. * An IndexedDB-backed instance of Persistence. Data is stored persistently
  15723. * across sessions.
  15724. *
  15725. * On Web only, the Firestore SDKs support shared access to its persistence
  15726. * layer. This allows multiple browser tabs to read and write to IndexedDb and
  15727. * to synchronize state even without network connectivity. Shared access is
  15728. * currently optional and not enabled unless all clients invoke
  15729. * `enablePersistence()` with `{synchronizeTabs:true}`.
  15730. *
  15731. * In multi-tab mode, if multiple clients are active at the same time, the SDK
  15732. * will designate one client as the “primary client”. An effort is made to pick
  15733. * a visible, network-connected and active client, and this client is
  15734. * responsible for letting other clients know about its presence. The primary
  15735. * client writes a unique client-generated identifier (the client ID) to
  15736. * IndexedDb’s “owner” store every 4 seconds. If the primary client fails to
  15737. * update this entry, another client can acquire the lease and take over as
  15738. * primary.
  15739. *
  15740. * Some persistence operations in the SDK are designated as primary-client only
  15741. * operations. This includes the acknowledgment of mutations and all updates of
  15742. * remote documents. The effects of these operations are written to persistence
  15743. * and then broadcast to other tabs via LocalStorage (see
  15744. * `WebStorageSharedClientState`), which then refresh their state from
  15745. * persistence.
  15746. *
  15747. * Similarly, the primary client listens to notifications sent by secondary
  15748. * clients to discover persistence changes written by secondary clients, such as
  15749. * the addition of new mutations and query targets.
  15750. *
  15751. * If multi-tab is not enabled and another tab already obtained the primary
  15752. * lease, IndexedDbPersistence enters a failed state and all subsequent
  15753. * operations will automatically fail.
  15754. *
  15755. * Additionally, there is an optimization so that when a tab is closed, the
  15756. * primary lease is released immediately (this is especially important to make
  15757. * sure that a refreshed tab is able to immediately re-acquire the primary
  15758. * lease). Unfortunately, IndexedDB cannot be reliably used in window.unload
  15759. * since it is an asynchronous API. So in addition to attempting to give up the
  15760. * lease, the leaseholder writes its client ID to a "zombiedClient" entry in
  15761. * LocalStorage which acts as an indicator that another tab should go ahead and
  15762. * take the primary lease immediately regardless of the current lease timestamp.
  15763. *
  15764. * TODO(b/114226234): Remove `synchronizeTabs` section when multi-tab is no
  15765. * longer optional.
  15766. */
  15767. class IndexedDbPersistence {
  15768. constructor(
  15769. /**
  15770. * Whether to synchronize the in-memory state of multiple tabs and share
  15771. * access to local persistence.
  15772. */
  15773. allowTabSynchronization, persistenceKey, clientId, lruParams, queue, window, document, serializer, sequenceNumberSyncer,
  15774. /**
  15775. * If set to true, forcefully obtains database access. Existing tabs will
  15776. * no longer be able to access IndexedDB.
  15777. */
  15778. forceOwningTab, schemaVersion = SCHEMA_VERSION) {
  15779. this.allowTabSynchronization = allowTabSynchronization;
  15780. this.persistenceKey = persistenceKey;
  15781. this.clientId = clientId;
  15782. this.queue = queue;
  15783. this.window = window;
  15784. this.document = document;
  15785. this.sequenceNumberSyncer = sequenceNumberSyncer;
  15786. this.forceOwningTab = forceOwningTab;
  15787. this.schemaVersion = schemaVersion;
  15788. this.listenSequence = null;
  15789. this._started = false;
  15790. this.isPrimary = false;
  15791. this.networkEnabled = true;
  15792. /** Our window.unload handler, if registered. */
  15793. this.windowUnloadHandler = null;
  15794. this.inForeground = false;
  15795. /** Our 'visibilitychange' listener if registered. */
  15796. this.documentVisibilityHandler = null;
  15797. /** The client metadata refresh task. */
  15798. this.clientMetadataRefresher = null;
  15799. /** The last time we garbage collected the client metadata object store. */
  15800. this.lastGarbageCollectionTime = Number.NEGATIVE_INFINITY;
  15801. /** A listener to notify on primary state changes. */
  15802. this.primaryStateListener = _ => Promise.resolve();
  15803. if (!IndexedDbPersistence.isAvailable()) {
  15804. throw new FirestoreError(Code.UNIMPLEMENTED, UNSUPPORTED_PLATFORM_ERROR_MSG);
  15805. }
  15806. this.referenceDelegate = new IndexedDbLruDelegateImpl(this, lruParams);
  15807. this.dbName = persistenceKey + MAIN_DATABASE;
  15808. this.serializer = new LocalSerializer(serializer);
  15809. this.simpleDb = new SimpleDb(this.dbName, this.schemaVersion, new SchemaConverter(this.serializer));
  15810. this.targetCache = new IndexedDbTargetCache(this.referenceDelegate, this.serializer);
  15811. this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
  15812. this.bundleCache = new IndexedDbBundleCache();
  15813. if (this.window && this.window.localStorage) {
  15814. this.webStorage = this.window.localStorage;
  15815. }
  15816. else {
  15817. this.webStorage = null;
  15818. if (forceOwningTab === false) {
  15819. logError(LOG_TAG$c, 'LocalStorage is unavailable. As a result, persistence may not work ' +
  15820. 'reliably. In particular enablePersistence() could fail immediately ' +
  15821. 'after refreshing the page.');
  15822. }
  15823. }
  15824. }
  15825. /**
  15826. * Attempt to start IndexedDb persistence.
  15827. *
  15828. * @returns Whether persistence was enabled.
  15829. */
  15830. start() {
  15831. // NOTE: This is expected to fail sometimes (in the case of another tab
  15832. // already having the persistence lock), so it's the first thing we should
  15833. // do.
  15834. return this.updateClientMetadataAndTryBecomePrimary()
  15835. .then(() => {
  15836. if (!this.isPrimary && !this.allowTabSynchronization) {
  15837. // Fail `start()` if `synchronizeTabs` is disabled and we cannot
  15838. // obtain the primary lease.
  15839. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG);
  15840. }
  15841. this.attachVisibilityHandler();
  15842. this.attachWindowUnloadHook();
  15843. this.scheduleClientMetadataAndPrimaryLeaseRefreshes();
  15844. return this.runTransaction('getHighestListenSequenceNumber', 'readonly', txn => this.targetCache.getHighestSequenceNumber(txn));
  15845. })
  15846. .then(highestListenSequenceNumber => {
  15847. this.listenSequence = new ListenSequence(highestListenSequenceNumber, this.sequenceNumberSyncer);
  15848. })
  15849. .then(() => {
  15850. this._started = true;
  15851. })
  15852. .catch(reason => {
  15853. this.simpleDb && this.simpleDb.close();
  15854. return Promise.reject(reason);
  15855. });
  15856. }
  15857. /**
  15858. * Registers a listener that gets called when the primary state of the
  15859. * instance changes. Upon registering, this listener is invoked immediately
  15860. * with the current primary state.
  15861. *
  15862. * PORTING NOTE: This is only used for Web multi-tab.
  15863. */
  15864. setPrimaryStateListener(primaryStateListener) {
  15865. this.primaryStateListener = async (primaryState) => {
  15866. if (this.started) {
  15867. return primaryStateListener(primaryState);
  15868. }
  15869. };
  15870. return primaryStateListener(this.isPrimary);
  15871. }
  15872. /**
  15873. * Registers a listener that gets called when the database receives a
  15874. * version change event indicating that it has deleted.
  15875. *
  15876. * PORTING NOTE: This is only used for Web multi-tab.
  15877. */
  15878. setDatabaseDeletedListener(databaseDeletedListener) {
  15879. this.simpleDb.setVersionChangeListener(async (event) => {
  15880. // Check if an attempt is made to delete IndexedDB.
  15881. if (event.newVersion === null) {
  15882. await databaseDeletedListener();
  15883. }
  15884. });
  15885. }
  15886. /**
  15887. * Adjusts the current network state in the client's metadata, potentially
  15888. * affecting the primary lease.
  15889. *
  15890. * PORTING NOTE: This is only used for Web multi-tab.
  15891. */
  15892. setNetworkEnabled(networkEnabled) {
  15893. if (this.networkEnabled !== networkEnabled) {
  15894. this.networkEnabled = networkEnabled;
  15895. // Schedule a primary lease refresh for immediate execution. The eventual
  15896. // lease update will be propagated via `primaryStateListener`.
  15897. this.queue.enqueueAndForget(async () => {
  15898. if (this.started) {
  15899. await this.updateClientMetadataAndTryBecomePrimary();
  15900. }
  15901. });
  15902. }
  15903. }
  15904. /**
  15905. * Updates the client metadata in IndexedDb and attempts to either obtain or
  15906. * extend the primary lease for the local client. Asynchronously notifies the
  15907. * primary state listener if the client either newly obtained or released its
  15908. * primary lease.
  15909. */
  15910. updateClientMetadataAndTryBecomePrimary() {
  15911. return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
  15912. const metadataStore = clientMetadataStore(txn);
  15913. return metadataStore
  15914. .put({
  15915. clientId: this.clientId,
  15916. updateTimeMs: Date.now(),
  15917. networkEnabled: this.networkEnabled,
  15918. inForeground: this.inForeground
  15919. })
  15920. .next(() => {
  15921. if (this.isPrimary) {
  15922. return this.verifyPrimaryLease(txn).next(success => {
  15923. if (!success) {
  15924. this.isPrimary = false;
  15925. this.queue.enqueueRetryable(() => this.primaryStateListener(false));
  15926. }
  15927. });
  15928. }
  15929. })
  15930. .next(() => this.canActAsPrimary(txn))
  15931. .next(canActAsPrimary => {
  15932. if (this.isPrimary && !canActAsPrimary) {
  15933. return this.releasePrimaryLeaseIfHeld(txn).next(() => false);
  15934. }
  15935. else if (canActAsPrimary) {
  15936. return this.acquireOrExtendPrimaryLease(txn).next(() => true);
  15937. }
  15938. else {
  15939. return /* canActAsPrimary= */ false;
  15940. }
  15941. });
  15942. })
  15943. .catch(e => {
  15944. if (isIndexedDbTransactionError(e)) {
  15945. logDebug(LOG_TAG$c, 'Failed to extend owner lease: ', e);
  15946. // Proceed with the existing state. Any subsequent access to
  15947. // IndexedDB will verify the lease.
  15948. return this.isPrimary;
  15949. }
  15950. if (!this.allowTabSynchronization) {
  15951. throw e;
  15952. }
  15953. logDebug(LOG_TAG$c, 'Releasing owner lease after error during lease refresh', e);
  15954. return /* isPrimary= */ false;
  15955. })
  15956. .then(isPrimary => {
  15957. if (this.isPrimary !== isPrimary) {
  15958. this.queue.enqueueRetryable(() => this.primaryStateListener(isPrimary));
  15959. }
  15960. this.isPrimary = isPrimary;
  15961. });
  15962. }
  15963. verifyPrimaryLease(txn) {
  15964. const store = primaryClientStore(txn);
  15965. return store.get(DbPrimaryClientKey).next(primaryClient => {
  15966. return PersistencePromise.resolve(this.isLocalClient(primaryClient));
  15967. });
  15968. }
  15969. removeClientMetadata(txn) {
  15970. const metadataStore = clientMetadataStore(txn);
  15971. return metadataStore.delete(this.clientId);
  15972. }
  15973. /**
  15974. * If the garbage collection threshold has passed, prunes the
  15975. * RemoteDocumentChanges and the ClientMetadata store based on the last update
  15976. * time of all clients.
  15977. */
  15978. async maybeGarbageCollectMultiClientState() {
  15979. if (this.isPrimary &&
  15980. !this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
  15981. this.lastGarbageCollectionTime = Date.now();
  15982. const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
  15983. const metadataStore = getStore(txn, DbClientMetadataStore);
  15984. return metadataStore.loadAll().next(existingClients => {
  15985. const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
  15986. const inactive = existingClients.filter(client => active.indexOf(client) === -1);
  15987. // Delete metadata for clients that are no longer considered active.
  15988. return PersistencePromise.forEach(inactive, (inactiveClient) => metadataStore.delete(inactiveClient.clientId)).next(() => inactive);
  15989. });
  15990. }).catch(() => {
  15991. // Ignore primary lease violations or any other type of error. The next
  15992. // primary will run `maybeGarbageCollectMultiClientState()` again.
  15993. // We don't use `ignoreIfPrimaryLeaseLoss()` since we don't want to depend
  15994. // on LocalStore.
  15995. return [];
  15996. });
  15997. // Delete potential leftover entries that may continue to mark the
  15998. // inactive clients as zombied in LocalStorage.
  15999. // Ideally we'd delete the IndexedDb and LocalStorage zombie entries for
  16000. // the client atomically, but we can't. So we opt to delete the IndexedDb
  16001. // entries first to avoid potentially reviving a zombied client.
  16002. if (this.webStorage) {
  16003. for (const inactiveClient of inactiveClients) {
  16004. this.webStorage.removeItem(this.zombiedClientLocalStorageKey(inactiveClient.clientId));
  16005. }
  16006. }
  16007. }
  16008. }
  16009. /**
  16010. * Schedules a recurring timer to update the client metadata and to either
  16011. * extend or acquire the primary lease if the client is eligible.
  16012. */
  16013. scheduleClientMetadataAndPrimaryLeaseRefreshes() {
  16014. this.clientMetadataRefresher = this.queue.enqueueAfterDelay("client_metadata_refresh" /* TimerId.ClientMetadataRefresh */, CLIENT_METADATA_REFRESH_INTERVAL_MS, () => {
  16015. return this.updateClientMetadataAndTryBecomePrimary()
  16016. .then(() => this.maybeGarbageCollectMultiClientState())
  16017. .then(() => this.scheduleClientMetadataAndPrimaryLeaseRefreshes());
  16018. });
  16019. }
  16020. /** Checks whether `client` is the local client. */
  16021. isLocalClient(client) {
  16022. return client ? client.ownerId === this.clientId : false;
  16023. }
  16024. /**
  16025. * Evaluate the state of all active clients and determine whether the local
  16026. * client is or can act as the holder of the primary lease. Returns whether
  16027. * the client is eligible for the lease, but does not actually acquire it.
  16028. * May return 'false' even if there is no active leaseholder and another
  16029. * (foreground) client should become leaseholder instead.
  16030. */
  16031. canActAsPrimary(txn) {
  16032. if (this.forceOwningTab) {
  16033. return PersistencePromise.resolve(true);
  16034. }
  16035. const store = primaryClientStore(txn);
  16036. return store
  16037. .get(DbPrimaryClientKey)
  16038. .next(currentPrimary => {
  16039. const currentLeaseIsValid = currentPrimary !== null &&
  16040. this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
  16041. !this.isClientZombied(currentPrimary.ownerId);
  16042. // A client is eligible for the primary lease if:
  16043. // - its network is enabled and the client's tab is in the foreground.
  16044. // - its network is enabled and no other client's tab is in the
  16045. // foreground.
  16046. // - every clients network is disabled and the client's tab is in the
  16047. // foreground.
  16048. // - every clients network is disabled and no other client's tab is in
  16049. // the foreground.
  16050. // - the `forceOwningTab` setting was passed in.
  16051. if (currentLeaseIsValid) {
  16052. if (this.isLocalClient(currentPrimary) && this.networkEnabled) {
  16053. return true;
  16054. }
  16055. if (!this.isLocalClient(currentPrimary)) {
  16056. if (!currentPrimary.allowTabSynchronization) {
  16057. // Fail the `canActAsPrimary` check if the current leaseholder has
  16058. // not opted into multi-tab synchronization. If this happens at
  16059. // client startup, we reject the Promise returned by
  16060. // `enablePersistence()` and the user can continue to use Firestore
  16061. // with in-memory persistence.
  16062. // If this fails during a lease refresh, we will instead block the
  16063. // AsyncQueue from executing further operations. Note that this is
  16064. // acceptable since mixing & matching different `synchronizeTabs`
  16065. // settings is not supported.
  16066. //
  16067. // TODO(b/114226234): Remove this check when `synchronizeTabs` can
  16068. // no longer be turned off.
  16069. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG);
  16070. }
  16071. return false;
  16072. }
  16073. }
  16074. if (this.networkEnabled && this.inForeground) {
  16075. return true;
  16076. }
  16077. return clientMetadataStore(txn)
  16078. .loadAll()
  16079. .next(existingClients => {
  16080. // Process all existing clients and determine whether at least one of
  16081. // them is better suited to obtain the primary lease.
  16082. const preferredCandidate = this.filterActiveClients(existingClients, MAX_PRIMARY_ELIGIBLE_AGE_MS).find(otherClient => {
  16083. if (this.clientId !== otherClient.clientId) {
  16084. const otherClientHasBetterNetworkState = !this.networkEnabled && otherClient.networkEnabled;
  16085. const otherClientHasBetterVisibility = !this.inForeground && otherClient.inForeground;
  16086. const otherClientHasSameNetworkState = this.networkEnabled === otherClient.networkEnabled;
  16087. if (otherClientHasBetterNetworkState ||
  16088. (otherClientHasBetterVisibility &&
  16089. otherClientHasSameNetworkState)) {
  16090. return true;
  16091. }
  16092. }
  16093. return false;
  16094. });
  16095. return preferredCandidate === undefined;
  16096. });
  16097. })
  16098. .next(canActAsPrimary => {
  16099. if (this.isPrimary !== canActAsPrimary) {
  16100. logDebug(LOG_TAG$c, `Client ${canActAsPrimary ? 'is' : 'is not'} eligible for a primary lease.`);
  16101. }
  16102. return canActAsPrimary;
  16103. });
  16104. }
  16105. async shutdown() {
  16106. // The shutdown() operations are idempotent and can be called even when
  16107. // start() aborted (e.g. because it couldn't acquire the persistence lease).
  16108. this._started = false;
  16109. this.markClientZombied();
  16110. if (this.clientMetadataRefresher) {
  16111. this.clientMetadataRefresher.cancel();
  16112. this.clientMetadataRefresher = null;
  16113. }
  16114. this.detachVisibilityHandler();
  16115. this.detachWindowUnloadHook();
  16116. // Use `SimpleDb.runTransaction` directly to avoid failing if another tab
  16117. // has obtained the primary lease.
  16118. await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
  16119. const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
  16120. return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
  16121. });
  16122. this.simpleDb.close();
  16123. // Remove the entry marking the client as zombied from LocalStorage since
  16124. // we successfully deleted its metadata from IndexedDb.
  16125. this.removeClientZombiedEntry();
  16126. }
  16127. /**
  16128. * Returns clients that are not zombied and have an updateTime within the
  16129. * provided threshold.
  16130. */
  16131. filterActiveClients(clients, activityThresholdMs) {
  16132. return clients.filter(client => this.isWithinAge(client.updateTimeMs, activityThresholdMs) &&
  16133. !this.isClientZombied(client.clientId));
  16134. }
  16135. /**
  16136. * Returns the IDs of the clients that are currently active. If multi-tab
  16137. * is not supported, returns an array that only contains the local client's
  16138. * ID.
  16139. *
  16140. * PORTING NOTE: This is only used for Web multi-tab.
  16141. */
  16142. getActiveClients() {
  16143. return this.runTransaction('getActiveClients', 'readonly', txn => {
  16144. return clientMetadataStore(txn)
  16145. .loadAll()
  16146. .next(clients => this.filterActiveClients(clients, MAX_CLIENT_AGE_MS).map(clientMetadata => clientMetadata.clientId));
  16147. });
  16148. }
  16149. get started() {
  16150. return this._started;
  16151. }
  16152. getMutationQueue(user, indexManager) {
  16153. return IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, this.referenceDelegate);
  16154. }
  16155. getTargetCache() {
  16156. return this.targetCache;
  16157. }
  16158. getRemoteDocumentCache() {
  16159. return this.remoteDocumentCache;
  16160. }
  16161. getIndexManager(user) {
  16162. return new IndexedDbIndexManager(user, this.serializer.remoteSerializer.databaseId);
  16163. }
  16164. getDocumentOverlayCache(user) {
  16165. return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
  16166. }
  16167. getBundleCache() {
  16168. return this.bundleCache;
  16169. }
  16170. runTransaction(action, mode, transactionOperation) {
  16171. logDebug(LOG_TAG$c, 'Starting transaction:', action);
  16172. const simpleDbMode = mode === 'readonly' ? 'readonly' : 'readwrite';
  16173. const objectStores = getObjectStores(this.schemaVersion);
  16174. let persistenceTransaction;
  16175. // Do all transactions as readwrite against all object stores, since we
  16176. // are the only reader/writer.
  16177. return this.simpleDb
  16178. .runTransaction(action, simpleDbMode, objectStores, simpleDbTxn => {
  16179. persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, this.listenSequence
  16180. ? this.listenSequence.next()
  16181. : ListenSequence.INVALID);
  16182. if (mode === 'readwrite-primary') {
  16183. // While we merely verify that we have (or can acquire) the lease
  16184. // immediately, we wait to extend the primary lease until after
  16185. // executing transactionOperation(). This ensures that even if the
  16186. // transactionOperation takes a long time, we'll use a recent
  16187. // leaseTimestampMs in the extended (or newly acquired) lease.
  16188. return this.verifyPrimaryLease(persistenceTransaction)
  16189. .next(holdsPrimaryLease => {
  16190. if (holdsPrimaryLease) {
  16191. return /* holdsPrimaryLease= */ true;
  16192. }
  16193. return this.canActAsPrimary(persistenceTransaction);
  16194. })
  16195. .next(holdsPrimaryLease => {
  16196. if (!holdsPrimaryLease) {
  16197. logError(`Failed to obtain primary lease for action '${action}'.`);
  16198. this.isPrimary = false;
  16199. this.queue.enqueueRetryable(() => this.primaryStateListener(false));
  16200. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_LOST_ERROR_MSG);
  16201. }
  16202. return transactionOperation(persistenceTransaction);
  16203. })
  16204. .next(result => {
  16205. return this.acquireOrExtendPrimaryLease(persistenceTransaction).next(() => result);
  16206. });
  16207. }
  16208. else {
  16209. return this.verifyAllowTabSynchronization(persistenceTransaction).next(() => transactionOperation(persistenceTransaction));
  16210. }
  16211. })
  16212. .then(result => {
  16213. persistenceTransaction.raiseOnCommittedEvent();
  16214. return result;
  16215. });
  16216. }
  16217. /**
  16218. * Verifies that the current tab is the primary leaseholder or alternatively
  16219. * that the leaseholder has opted into multi-tab synchronization.
  16220. */
  16221. // TODO(b/114226234): Remove this check when `synchronizeTabs` can no longer
  16222. // be turned off.
  16223. verifyAllowTabSynchronization(txn) {
  16224. const store = primaryClientStore(txn);
  16225. return store.get(DbPrimaryClientKey).next(currentPrimary => {
  16226. const currentLeaseIsValid = currentPrimary !== null &&
  16227. this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
  16228. !this.isClientZombied(currentPrimary.ownerId);
  16229. if (currentLeaseIsValid && !this.isLocalClient(currentPrimary)) {
  16230. if (!this.forceOwningTab &&
  16231. (!this.allowTabSynchronization ||
  16232. !currentPrimary.allowTabSynchronization)) {
  16233. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG);
  16234. }
  16235. }
  16236. });
  16237. }
  16238. /**
  16239. * Obtains or extends the new primary lease for the local client. This
  16240. * method does not verify that the client is eligible for this lease.
  16241. */
  16242. acquireOrExtendPrimaryLease(txn) {
  16243. const newPrimary = {
  16244. ownerId: this.clientId,
  16245. allowTabSynchronization: this.allowTabSynchronization,
  16246. leaseTimestampMs: Date.now()
  16247. };
  16248. return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
  16249. }
  16250. static isAvailable() {
  16251. return SimpleDb.isAvailable();
  16252. }
  16253. /** Checks the primary lease and removes it if we are the current primary. */
  16254. releasePrimaryLeaseIfHeld(txn) {
  16255. const store = primaryClientStore(txn);
  16256. return store.get(DbPrimaryClientKey).next(primaryClient => {
  16257. if (this.isLocalClient(primaryClient)) {
  16258. logDebug(LOG_TAG$c, 'Releasing primary lease.');
  16259. return store.delete(DbPrimaryClientKey);
  16260. }
  16261. else {
  16262. return PersistencePromise.resolve();
  16263. }
  16264. });
  16265. }
  16266. /** Verifies that `updateTimeMs` is within `maxAgeMs`. */
  16267. isWithinAge(updateTimeMs, maxAgeMs) {
  16268. const now = Date.now();
  16269. const minAcceptable = now - maxAgeMs;
  16270. const maxAcceptable = now;
  16271. if (updateTimeMs < minAcceptable) {
  16272. return false;
  16273. }
  16274. else if (updateTimeMs > maxAcceptable) {
  16275. logError(`Detected an update time that is in the future: ${updateTimeMs} > ${maxAcceptable}`);
  16276. return false;
  16277. }
  16278. return true;
  16279. }
  16280. attachVisibilityHandler() {
  16281. if (this.document !== null &&
  16282. typeof this.document.addEventListener === 'function') {
  16283. this.documentVisibilityHandler = () => {
  16284. this.queue.enqueueAndForget(() => {
  16285. this.inForeground = this.document.visibilityState === 'visible';
  16286. return this.updateClientMetadataAndTryBecomePrimary();
  16287. });
  16288. };
  16289. this.document.addEventListener('visibilitychange', this.documentVisibilityHandler);
  16290. this.inForeground = this.document.visibilityState === 'visible';
  16291. }
  16292. }
  16293. detachVisibilityHandler() {
  16294. if (this.documentVisibilityHandler) {
  16295. this.document.removeEventListener('visibilitychange', this.documentVisibilityHandler);
  16296. this.documentVisibilityHandler = null;
  16297. }
  16298. }
  16299. /**
  16300. * Attaches a window.unload handler that will synchronously write our
  16301. * clientId to a "zombie client id" location in LocalStorage. This can be used
  16302. * by tabs trying to acquire the primary lease to determine that the lease
  16303. * is no longer valid even if the timestamp is recent. This is particularly
  16304. * important for the refresh case (so the tab correctly re-acquires the
  16305. * primary lease). LocalStorage is used for this rather than IndexedDb because
  16306. * it is a synchronous API and so can be used reliably from an unload
  16307. * handler.
  16308. */
  16309. attachWindowUnloadHook() {
  16310. var _a;
  16311. if (typeof ((_a = this.window) === null || _a === void 0 ? void 0 : _a.addEventListener) === 'function') {
  16312. this.windowUnloadHandler = () => {
  16313. // Note: In theory, this should be scheduled on the AsyncQueue since it
  16314. // accesses internal state. We execute this code directly during shutdown
  16315. // to make sure it gets a chance to run.
  16316. this.markClientZombied();
  16317. const safariIndexdbBugVersionRegex = /(?:Version|Mobile)\/1[456]/;
  16318. if (util.isSafari() &&
  16319. (navigator.appVersion.match(safariIndexdbBugVersionRegex) ||
  16320. navigator.userAgent.match(safariIndexdbBugVersionRegex))) {
  16321. // On Safari 14, 15, and 16, we do not run any cleanup actions as it might
  16322. // trigger a bug that prevents Safari from re-opening IndexedDB during
  16323. // the next page load.
  16324. // See https://bugs.webkit.org/show_bug.cgi?id=226547
  16325. this.queue.enterRestrictedMode(/* purgeExistingTasks= */ true);
  16326. }
  16327. this.queue.enqueueAndForget(() => {
  16328. // Attempt graceful shutdown (including releasing our primary lease),
  16329. // but there's no guarantee it will complete.
  16330. return this.shutdown();
  16331. });
  16332. };
  16333. this.window.addEventListener('pagehide', this.windowUnloadHandler);
  16334. }
  16335. }
  16336. detachWindowUnloadHook() {
  16337. if (this.windowUnloadHandler) {
  16338. this.window.removeEventListener('pagehide', this.windowUnloadHandler);
  16339. this.windowUnloadHandler = null;
  16340. }
  16341. }
  16342. /**
  16343. * Returns whether a client is "zombied" based on its LocalStorage entry.
  16344. * Clients become zombied when their tab closes without running all of the
  16345. * cleanup logic in `shutdown()`.
  16346. */
  16347. isClientZombied(clientId) {
  16348. var _a;
  16349. try {
  16350. const isZombied = ((_a = this.webStorage) === null || _a === void 0 ? void 0 : _a.getItem(this.zombiedClientLocalStorageKey(clientId))) !== null;
  16351. logDebug(LOG_TAG$c, `Client '${clientId}' ${isZombied ? 'is' : 'is not'} zombied in LocalStorage`);
  16352. return isZombied;
  16353. }
  16354. catch (e) {
  16355. // Gracefully handle if LocalStorage isn't working.
  16356. logError(LOG_TAG$c, 'Failed to get zombied client id.', e);
  16357. return false;
  16358. }
  16359. }
  16360. /**
  16361. * Record client as zombied (a client that had its tab closed). Zombied
  16362. * clients are ignored during primary tab selection.
  16363. */
  16364. markClientZombied() {
  16365. if (!this.webStorage) {
  16366. return;
  16367. }
  16368. try {
  16369. this.webStorage.setItem(this.zombiedClientLocalStorageKey(this.clientId), String(Date.now()));
  16370. }
  16371. catch (e) {
  16372. // Gracefully handle if LocalStorage isn't available / working.
  16373. logError('Failed to set zombie client id.', e);
  16374. }
  16375. }
  16376. /** Removes the zombied client entry if it exists. */
  16377. removeClientZombiedEntry() {
  16378. if (!this.webStorage) {
  16379. return;
  16380. }
  16381. try {
  16382. this.webStorage.removeItem(this.zombiedClientLocalStorageKey(this.clientId));
  16383. }
  16384. catch (e) {
  16385. // Ignore
  16386. }
  16387. }
  16388. zombiedClientLocalStorageKey(clientId) {
  16389. return `${ZOMBIED_CLIENTS_KEY_PREFIX}_${this.persistenceKey}_${clientId}`;
  16390. }
  16391. }
  16392. /**
  16393. * Helper to get a typed SimpleDbStore for the primary client object store.
  16394. */
  16395. function primaryClientStore(txn) {
  16396. return getStore(txn, DbPrimaryClientStore);
  16397. }
  16398. /**
  16399. * Helper to get a typed SimpleDbStore for the client metadata object store.
  16400. */
  16401. function clientMetadataStore(txn) {
  16402. return getStore(txn, DbClientMetadataStore);
  16403. }
  16404. /**
  16405. * Generates a string used as a prefix when storing data in IndexedDB and
  16406. * LocalStorage.
  16407. */
  16408. function indexedDbStoragePrefix(databaseId, persistenceKey) {
  16409. // Use two different prefix formats:
  16410. //
  16411. // * firestore / persistenceKey / projectID . databaseID / ...
  16412. // * firestore / persistenceKey / projectID / ...
  16413. //
  16414. // projectIDs are DNS-compatible names and cannot contain dots
  16415. // so there's no danger of collisions.
  16416. let database = databaseId.projectId;
  16417. if (!databaseId.isDefaultDatabase) {
  16418. database += '.' + databaseId.database;
  16419. }
  16420. return 'firestore/' + persistenceKey + '/' + database + '/';
  16421. }
  16422. async function indexedDbClearPersistence(persistenceKey) {
  16423. if (!SimpleDb.isAvailable()) {
  16424. return Promise.resolve();
  16425. }
  16426. const dbName = persistenceKey + MAIN_DATABASE;
  16427. await SimpleDb.delete(dbName);
  16428. }
  16429. /**
  16430. * @license
  16431. * Copyright 2017 Google LLC
  16432. *
  16433. * Licensed under the Apache License, Version 2.0 (the "License");
  16434. * you may not use this file except in compliance with the License.
  16435. * You may obtain a copy of the License at
  16436. *
  16437. * http://www.apache.org/licenses/LICENSE-2.0
  16438. *
  16439. * Unless required by applicable law or agreed to in writing, software
  16440. * distributed under the License is distributed on an "AS IS" BASIS,
  16441. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16442. * See the License for the specific language governing permissions and
  16443. * limitations under the License.
  16444. */
  16445. /**
  16446. * Compares two array for equality using comparator. The method computes the
  16447. * intersection and invokes `onAdd` for every element that is in `after` but not
  16448. * `before`. `onRemove` is invoked for every element in `before` but missing
  16449. * from `after`.
  16450. *
  16451. * The method creates a copy of both `before` and `after` and runs in O(n log
  16452. * n), where n is the size of the two lists.
  16453. *
  16454. * @param before - The elements that exist in the original array.
  16455. * @param after - The elements to diff against the original array.
  16456. * @param comparator - The comparator for the elements in before and after.
  16457. * @param onAdd - A function to invoke for every element that is part of `
  16458. * after` but not `before`.
  16459. * @param onRemove - A function to invoke for every element that is part of
  16460. * `before` but not `after`.
  16461. */
  16462. function diffArrays(before, after, comparator, onAdd, onRemove) {
  16463. before = [...before];
  16464. after = [...after];
  16465. before.sort(comparator);
  16466. after.sort(comparator);
  16467. const bLen = before.length;
  16468. const aLen = after.length;
  16469. let a = 0;
  16470. let b = 0;
  16471. while (a < aLen && b < bLen) {
  16472. const cmp = comparator(before[b], after[a]);
  16473. if (cmp < 0) {
  16474. // The element was removed if the next element in our ordered
  16475. // walkthrough is only in `before`.
  16476. onRemove(before[b++]);
  16477. }
  16478. else if (cmp > 0) {
  16479. // The element was added if the next element in our ordered walkthrough
  16480. // is only in `after`.
  16481. onAdd(after[a++]);
  16482. }
  16483. else {
  16484. a++;
  16485. b++;
  16486. }
  16487. }
  16488. while (a < aLen) {
  16489. onAdd(after[a++]);
  16490. }
  16491. while (b < bLen) {
  16492. onRemove(before[b++]);
  16493. }
  16494. }
  16495. /**
  16496. * @license
  16497. * Copyright 2020 Google LLC
  16498. *
  16499. * Licensed under the Apache License, Version 2.0 (the "License");
  16500. * you may not use this file except in compliance with the License.
  16501. * You may obtain a copy of the License at
  16502. *
  16503. * http://www.apache.org/licenses/LICENSE-2.0
  16504. *
  16505. * Unless required by applicable law or agreed to in writing, software
  16506. * distributed under the License is distributed on an "AS IS" BASIS,
  16507. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16508. * See the License for the specific language governing permissions and
  16509. * limitations under the License.
  16510. */
  16511. const LOG_TAG$b = 'LocalStore';
  16512. /**
  16513. * The maximum time to leave a resume token buffered without writing it out.
  16514. * This value is arbitrary: it's long enough to avoid several writes
  16515. * (possibly indefinitely if updates come more frequently than this) but
  16516. * short enough that restarting after crashing will still have a pretty
  16517. * recent resume token.
  16518. */
  16519. const RESUME_TOKEN_MAX_AGE_MICROS = 5 * 60 * 1e6;
  16520. /**
  16521. * Implements `LocalStore` interface.
  16522. *
  16523. * Note: some field defined in this class might have public access level, but
  16524. * the class is not exported so they are only accessible from this module.
  16525. * This is useful to implement optional features (like bundles) in free
  16526. * functions, such that they are tree-shakeable.
  16527. */
  16528. class LocalStoreImpl {
  16529. constructor(
  16530. /** Manages our in-memory or durable persistence. */
  16531. persistence, queryEngine, initialUser, serializer) {
  16532. this.persistence = persistence;
  16533. this.queryEngine = queryEngine;
  16534. this.serializer = serializer;
  16535. /**
  16536. * Maps a targetID to data about its target.
  16537. *
  16538. * PORTING NOTE: We are using an immutable data structure on Web to make re-runs
  16539. * of `applyRemoteEvent()` idempotent.
  16540. */
  16541. this.targetDataByTarget = new SortedMap(primitiveComparator);
  16542. /** Maps a target to its targetID. */
  16543. // TODO(wuandy): Evaluate if TargetId can be part of Target.
  16544. this.targetIdByTarget = new ObjectMap(t => canonifyTarget(t), targetEquals);
  16545. /**
  16546. * A per collection group index of the last read time processed by
  16547. * `getNewDocumentChanges()`.
  16548. *
  16549. * PORTING NOTE: This is only used for multi-tab synchronization.
  16550. */
  16551. this.collectionGroupReadTime = new Map();
  16552. this.remoteDocuments = persistence.getRemoteDocumentCache();
  16553. this.targetCache = persistence.getTargetCache();
  16554. this.bundleCache = persistence.getBundleCache();
  16555. this.initializeUserComponents(initialUser);
  16556. }
  16557. initializeUserComponents(user) {
  16558. // TODO(indexing): Add spec tests that test these components change after a
  16559. // user change
  16560. this.documentOverlayCache = this.persistence.getDocumentOverlayCache(user);
  16561. this.indexManager = this.persistence.getIndexManager(user);
  16562. this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
  16563. this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.documentOverlayCache, this.indexManager);
  16564. this.remoteDocuments.setIndexManager(this.indexManager);
  16565. this.queryEngine.initialize(this.localDocuments, this.indexManager);
  16566. }
  16567. collectGarbage(garbageCollector) {
  16568. return this.persistence.runTransaction('Collect garbage', 'readwrite-primary', txn => garbageCollector.collect(txn, this.targetDataByTarget));
  16569. }
  16570. }
  16571. function newLocalStore(
  16572. /** Manages our in-memory or durable persistence. */
  16573. persistence, queryEngine, initialUser, serializer) {
  16574. return new LocalStoreImpl(persistence, queryEngine, initialUser, serializer);
  16575. }
  16576. /**
  16577. * Tells the LocalStore that the currently authenticated user has changed.
  16578. *
  16579. * In response the local store switches the mutation queue to the new user and
  16580. * returns any resulting document changes.
  16581. */
  16582. // PORTING NOTE: Android and iOS only return the documents affected by the
  16583. // change.
  16584. async function localStoreHandleUserChange(localStore, user) {
  16585. const localStoreImpl = debugCast(localStore);
  16586. const result = await localStoreImpl.persistence.runTransaction('Handle user change', 'readonly', txn => {
  16587. // Swap out the mutation queue, grabbing the pending mutation batches
  16588. // before and after.
  16589. let oldBatches;
  16590. return localStoreImpl.mutationQueue
  16591. .getAllMutationBatches(txn)
  16592. .next(promisedOldBatches => {
  16593. oldBatches = promisedOldBatches;
  16594. localStoreImpl.initializeUserComponents(user);
  16595. return localStoreImpl.mutationQueue.getAllMutationBatches(txn);
  16596. })
  16597. .next(newBatches => {
  16598. const removedBatchIds = [];
  16599. const addedBatchIds = [];
  16600. // Union the old/new changed keys.
  16601. let changedKeys = documentKeySet();
  16602. for (const batch of oldBatches) {
  16603. removedBatchIds.push(batch.batchId);
  16604. for (const mutation of batch.mutations) {
  16605. changedKeys = changedKeys.add(mutation.key);
  16606. }
  16607. }
  16608. for (const batch of newBatches) {
  16609. addedBatchIds.push(batch.batchId);
  16610. for (const mutation of batch.mutations) {
  16611. changedKeys = changedKeys.add(mutation.key);
  16612. }
  16613. }
  16614. // Return the set of all (potentially) changed documents and the list
  16615. // of mutation batch IDs that were affected by change.
  16616. return localStoreImpl.localDocuments
  16617. .getDocuments(txn, changedKeys)
  16618. .next(affectedDocuments => {
  16619. return {
  16620. affectedDocuments,
  16621. removedBatchIds,
  16622. addedBatchIds
  16623. };
  16624. });
  16625. });
  16626. });
  16627. return result;
  16628. }
  16629. /* Accepts locally generated Mutations and commit them to storage. */
  16630. function localStoreWriteLocally(localStore, mutations) {
  16631. const localStoreImpl = debugCast(localStore);
  16632. const localWriteTime = Timestamp.now();
  16633. const keys = mutations.reduce((keys, m) => keys.add(m.key), documentKeySet());
  16634. let overlayedDocuments;
  16635. let mutationBatch;
  16636. return localStoreImpl.persistence
  16637. .runTransaction('Locally write mutations', 'readwrite', txn => {
  16638. // Figure out which keys do not have a remote version in the cache, this
  16639. // is needed to create the right overlay mutation: if no remote version
  16640. // presents, we do not need to create overlays as patch mutations.
  16641. // TODO(Overlay): Is there a better way to determine this? Using the
  16642. // document version does not work because local mutations set them back
  16643. // to 0.
  16644. let remoteDocs = mutableDocumentMap();
  16645. let docsWithoutRemoteVersion = documentKeySet();
  16646. return localStoreImpl.remoteDocuments
  16647. .getEntries(txn, keys)
  16648. .next(docs => {
  16649. remoteDocs = docs;
  16650. remoteDocs.forEach((key, doc) => {
  16651. if (!doc.isValidDocument()) {
  16652. docsWithoutRemoteVersion = docsWithoutRemoteVersion.add(key);
  16653. }
  16654. });
  16655. })
  16656. .next(() => {
  16657. // Load and apply all existing mutations. This lets us compute the
  16658. // current base state for all non-idempotent transforms before applying
  16659. // any additional user-provided writes.
  16660. return localStoreImpl.localDocuments.getOverlayedDocuments(txn, remoteDocs);
  16661. })
  16662. .next((docs) => {
  16663. overlayedDocuments = docs;
  16664. // For non-idempotent mutations (such as `FieldValue.increment()`),
  16665. // we record the base state in a separate patch mutation. This is
  16666. // later used to guarantee consistent values and prevents flicker
  16667. // even if the backend sends us an update that already includes our
  16668. // transform.
  16669. const baseMutations = [];
  16670. for (const mutation of mutations) {
  16671. const baseValue = mutationExtractBaseValue(mutation, overlayedDocuments.get(mutation.key).overlayedDocument);
  16672. if (baseValue != null) {
  16673. // NOTE: The base state should only be applied if there's some
  16674. // existing document to override, so use a Precondition of
  16675. // exists=true
  16676. baseMutations.push(new PatchMutation(mutation.key, baseValue, extractFieldMask(baseValue.value.mapValue), Precondition.exists(true)));
  16677. }
  16678. }
  16679. return localStoreImpl.mutationQueue.addMutationBatch(txn, localWriteTime, baseMutations, mutations);
  16680. })
  16681. .next(batch => {
  16682. mutationBatch = batch;
  16683. const overlays = batch.applyToLocalDocumentSet(overlayedDocuments, docsWithoutRemoteVersion);
  16684. return localStoreImpl.documentOverlayCache.saveOverlays(txn, batch.batchId, overlays);
  16685. });
  16686. })
  16687. .then(() => ({
  16688. batchId: mutationBatch.batchId,
  16689. changes: convertOverlayedDocumentMapToDocumentMap(overlayedDocuments)
  16690. }));
  16691. }
  16692. /**
  16693. * Acknowledges the given batch.
  16694. *
  16695. * On the happy path when a batch is acknowledged, the local store will
  16696. *
  16697. * + remove the batch from the mutation queue;
  16698. * + apply the changes to the remote document cache;
  16699. * + recalculate the latency compensated view implied by those changes (there
  16700. * may be mutations in the queue that affect the documents but haven't been
  16701. * acknowledged yet); and
  16702. * + give the changed documents back the sync engine
  16703. *
  16704. * @returns The resulting (modified) documents.
  16705. */
  16706. function localStoreAcknowledgeBatch(localStore, batchResult) {
  16707. const localStoreImpl = debugCast(localStore);
  16708. return localStoreImpl.persistence.runTransaction('Acknowledge batch', 'readwrite-primary', txn => {
  16709. const affected = batchResult.batch.keys();
  16710. const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
  16711. trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
  16712. });
  16713. return applyWriteToRemoteDocuments(localStoreImpl, txn, batchResult, documentBuffer)
  16714. .next(() => documentBuffer.apply(txn))
  16715. .next(() => localStoreImpl.mutationQueue.performConsistencyCheck(txn))
  16716. .next(() => localStoreImpl.documentOverlayCache.removeOverlaysForBatchId(txn, affected, batchResult.batch.batchId))
  16717. .next(() => localStoreImpl.localDocuments.recalculateAndSaveOverlaysForDocumentKeys(txn, getKeysWithTransformResults(batchResult)))
  16718. .next(() => localStoreImpl.localDocuments.getDocuments(txn, affected));
  16719. });
  16720. }
  16721. function getKeysWithTransformResults(batchResult) {
  16722. let result = documentKeySet();
  16723. for (let i = 0; i < batchResult.mutationResults.length; ++i) {
  16724. const mutationResult = batchResult.mutationResults[i];
  16725. if (mutationResult.transformResults.length > 0) {
  16726. result = result.add(batchResult.batch.mutations[i].key);
  16727. }
  16728. }
  16729. return result;
  16730. }
  16731. /**
  16732. * Removes mutations from the MutationQueue for the specified batch;
  16733. * LocalDocuments will be recalculated.
  16734. *
  16735. * @returns The resulting modified documents.
  16736. */
  16737. function localStoreRejectBatch(localStore, batchId) {
  16738. const localStoreImpl = debugCast(localStore);
  16739. return localStoreImpl.persistence.runTransaction('Reject batch', 'readwrite-primary', txn => {
  16740. let affectedKeys;
  16741. return localStoreImpl.mutationQueue
  16742. .lookupMutationBatch(txn, batchId)
  16743. .next((batch) => {
  16744. hardAssert(batch !== null);
  16745. affectedKeys = batch.keys();
  16746. return localStoreImpl.mutationQueue.removeMutationBatch(txn, batch);
  16747. })
  16748. .next(() => localStoreImpl.mutationQueue.performConsistencyCheck(txn))
  16749. .next(() => localStoreImpl.documentOverlayCache.removeOverlaysForBatchId(txn, affectedKeys, batchId))
  16750. .next(() => localStoreImpl.localDocuments.recalculateAndSaveOverlaysForDocumentKeys(txn, affectedKeys))
  16751. .next(() => localStoreImpl.localDocuments.getDocuments(txn, affectedKeys));
  16752. });
  16753. }
  16754. /**
  16755. * Returns the largest (latest) batch id in mutation queue that is pending
  16756. * server response.
  16757. *
  16758. * Returns `BATCHID_UNKNOWN` if the queue is empty.
  16759. */
  16760. function localStoreGetHighestUnacknowledgedBatchId(localStore) {
  16761. const localStoreImpl = debugCast(localStore);
  16762. return localStoreImpl.persistence.runTransaction('Get highest unacknowledged batch id', 'readonly', txn => localStoreImpl.mutationQueue.getHighestUnacknowledgedBatchId(txn));
  16763. }
  16764. /**
  16765. * Returns the last consistent snapshot processed (used by the RemoteStore to
  16766. * determine whether to buffer incoming snapshots from the backend).
  16767. */
  16768. function localStoreGetLastRemoteSnapshotVersion(localStore) {
  16769. const localStoreImpl = debugCast(localStore);
  16770. return localStoreImpl.persistence.runTransaction('Get last remote snapshot version', 'readonly', txn => localStoreImpl.targetCache.getLastRemoteSnapshotVersion(txn));
  16771. }
  16772. /**
  16773. * Updates the "ground-state" (remote) documents. We assume that the remote
  16774. * event reflects any write batches that have been acknowledged or rejected
  16775. * (i.e. we do not re-apply local mutations to updates from this event).
  16776. *
  16777. * LocalDocuments are re-calculated if there are remaining mutations in the
  16778. * queue.
  16779. */
  16780. function localStoreApplyRemoteEventToLocalCache(localStore, remoteEvent) {
  16781. const localStoreImpl = debugCast(localStore);
  16782. const remoteVersion = remoteEvent.snapshotVersion;
  16783. let newTargetDataByTargetMap = localStoreImpl.targetDataByTarget;
  16784. return localStoreImpl.persistence
  16785. .runTransaction('Apply remote event', 'readwrite-primary', txn => {
  16786. const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
  16787. trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
  16788. });
  16789. // Reset newTargetDataByTargetMap in case this transaction gets re-run.
  16790. newTargetDataByTargetMap = localStoreImpl.targetDataByTarget;
  16791. const promises = [];
  16792. remoteEvent.targetChanges.forEach((change, targetId) => {
  16793. const oldTargetData = newTargetDataByTargetMap.get(targetId);
  16794. if (!oldTargetData) {
  16795. return;
  16796. }
  16797. // Only update the remote keys if the target is still active. This
  16798. // ensures that we can persist the updated target data along with
  16799. // the updated assignment.
  16800. promises.push(localStoreImpl.targetCache
  16801. .removeMatchingKeys(txn, change.removedDocuments, targetId)
  16802. .next(() => {
  16803. return localStoreImpl.targetCache.addMatchingKeys(txn, change.addedDocuments, targetId);
  16804. }));
  16805. let newTargetData = oldTargetData.withSequenceNumber(txn.currentSequenceNumber);
  16806. if (remoteEvent.targetMismatches.get(targetId) !== null) {
  16807. newTargetData = newTargetData
  16808. .withResumeToken(ByteString.EMPTY_BYTE_STRING, SnapshotVersion.min())
  16809. .withLastLimboFreeSnapshotVersion(SnapshotVersion.min());
  16810. }
  16811. else if (change.resumeToken.approximateByteSize() > 0) {
  16812. newTargetData = newTargetData.withResumeToken(change.resumeToken, remoteVersion);
  16813. }
  16814. newTargetDataByTargetMap = newTargetDataByTargetMap.insert(targetId, newTargetData);
  16815. // Update the target data if there are target changes (or if
  16816. // sufficient time has passed since the last update).
  16817. if (shouldPersistTargetData(oldTargetData, newTargetData, change)) {
  16818. promises.push(localStoreImpl.targetCache.updateTargetData(txn, newTargetData));
  16819. }
  16820. });
  16821. let changedDocs = mutableDocumentMap();
  16822. let existenceChangedKeys = documentKeySet();
  16823. remoteEvent.documentUpdates.forEach(key => {
  16824. if (remoteEvent.resolvedLimboDocuments.has(key)) {
  16825. promises.push(localStoreImpl.persistence.referenceDelegate.updateLimboDocument(txn, key));
  16826. }
  16827. });
  16828. // Each loop iteration only affects its "own" doc, so it's safe to get all
  16829. // the remote documents in advance in a single call.
  16830. promises.push(populateDocumentChangeBuffer(txn, documentBuffer, remoteEvent.documentUpdates).next(result => {
  16831. changedDocs = result.changedDocuments;
  16832. existenceChangedKeys = result.existenceChangedKeys;
  16833. }));
  16834. // HACK: The only reason we allow a null snapshot version is so that we
  16835. // can synthesize remote events when we get permission denied errors while
  16836. // trying to resolve the state of a locally cached document that is in
  16837. // limbo.
  16838. if (!remoteVersion.isEqual(SnapshotVersion.min())) {
  16839. const updateRemoteVersion = localStoreImpl.targetCache
  16840. .getLastRemoteSnapshotVersion(txn)
  16841. .next(lastRemoteSnapshotVersion => {
  16842. return localStoreImpl.targetCache.setTargetsMetadata(txn, txn.currentSequenceNumber, remoteVersion);
  16843. });
  16844. promises.push(updateRemoteVersion);
  16845. }
  16846. return PersistencePromise.waitFor(promises)
  16847. .next(() => documentBuffer.apply(txn))
  16848. .next(() => localStoreImpl.localDocuments.getLocalViewOfDocuments(txn, changedDocs, existenceChangedKeys))
  16849. .next(() => changedDocs);
  16850. })
  16851. .then(changedDocs => {
  16852. localStoreImpl.targetDataByTarget = newTargetDataByTargetMap;
  16853. return changedDocs;
  16854. });
  16855. }
  16856. /**
  16857. * Populates document change buffer with documents from backend or a bundle.
  16858. * Returns the document changes resulting from applying those documents, and
  16859. * also a set of documents whose existence state are changed as a result.
  16860. *
  16861. * @param txn - Transaction to use to read existing documents from storage.
  16862. * @param documentBuffer - Document buffer to collect the resulted changes to be
  16863. * applied to storage.
  16864. * @param documents - Documents to be applied.
  16865. */
  16866. function populateDocumentChangeBuffer(txn, documentBuffer, documents) {
  16867. let updatedKeys = documentKeySet();
  16868. let existenceChangedKeys = documentKeySet();
  16869. documents.forEach(k => (updatedKeys = updatedKeys.add(k)));
  16870. return documentBuffer.getEntries(txn, updatedKeys).next(existingDocs => {
  16871. let changedDocuments = mutableDocumentMap();
  16872. documents.forEach((key, doc) => {
  16873. const existingDoc = existingDocs.get(key);
  16874. // Check if see if there is a existence state change for this document.
  16875. if (doc.isFoundDocument() !== existingDoc.isFoundDocument()) {
  16876. existenceChangedKeys = existenceChangedKeys.add(key);
  16877. }
  16878. // Note: The order of the steps below is important, since we want
  16879. // to ensure that rejected limbo resolutions (which fabricate
  16880. // NoDocuments with SnapshotVersion.min()) never add documents to
  16881. // cache.
  16882. if (doc.isNoDocument() && doc.version.isEqual(SnapshotVersion.min())) {
  16883. // NoDocuments with SnapshotVersion.min() are used in manufactured
  16884. // events. We remove these documents from cache since we lost
  16885. // access.
  16886. documentBuffer.removeEntry(key, doc.readTime);
  16887. changedDocuments = changedDocuments.insert(key, doc);
  16888. }
  16889. else if (!existingDoc.isValidDocument() ||
  16890. doc.version.compareTo(existingDoc.version) > 0 ||
  16891. (doc.version.compareTo(existingDoc.version) === 0 &&
  16892. existingDoc.hasPendingWrites)) {
  16893. documentBuffer.addEntry(doc);
  16894. changedDocuments = changedDocuments.insert(key, doc);
  16895. }
  16896. else {
  16897. logDebug(LOG_TAG$b, 'Ignoring outdated watch update for ', key, '. Current version:', existingDoc.version, ' Watch version:', doc.version);
  16898. }
  16899. });
  16900. return { changedDocuments, existenceChangedKeys };
  16901. });
  16902. }
  16903. /**
  16904. * Returns true if the newTargetData should be persisted during an update of
  16905. * an active target. TargetData should always be persisted when a target is
  16906. * being released and should not call this function.
  16907. *
  16908. * While the target is active, TargetData updates can be omitted when nothing
  16909. * about the target has changed except metadata like the resume token or
  16910. * snapshot version. Occasionally it's worth the extra write to prevent these
  16911. * values from getting too stale after a crash, but this doesn't have to be
  16912. * too frequent.
  16913. */
  16914. function shouldPersistTargetData(oldTargetData, newTargetData, change) {
  16915. // Always persist target data if we don't already have a resume token.
  16916. if (oldTargetData.resumeToken.approximateByteSize() === 0) {
  16917. return true;
  16918. }
  16919. // Don't allow resume token changes to be buffered indefinitely. This
  16920. // allows us to be reasonably up-to-date after a crash and avoids needing
  16921. // to loop over all active queries on shutdown. Especially in the browser
  16922. // we may not get time to do anything interesting while the current tab is
  16923. // closing.
  16924. const timeDelta = newTargetData.snapshotVersion.toMicroseconds() -
  16925. oldTargetData.snapshotVersion.toMicroseconds();
  16926. if (timeDelta >= RESUME_TOKEN_MAX_AGE_MICROS) {
  16927. return true;
  16928. }
  16929. // Otherwise if the only thing that has changed about a target is its resume
  16930. // token it's not worth persisting. Note that the RemoteStore keeps an
  16931. // in-memory view of the currently active targets which includes the current
  16932. // resume token, so stream failure or user changes will still use an
  16933. // up-to-date resume token regardless of what we do here.
  16934. const changes = change.addedDocuments.size +
  16935. change.modifiedDocuments.size +
  16936. change.removedDocuments.size;
  16937. return changes > 0;
  16938. }
  16939. /**
  16940. * Notifies local store of the changed views to locally pin documents.
  16941. */
  16942. async function localStoreNotifyLocalViewChanges(localStore, viewChanges) {
  16943. const localStoreImpl = debugCast(localStore);
  16944. try {
  16945. await localStoreImpl.persistence.runTransaction('notifyLocalViewChanges', 'readwrite', txn => {
  16946. return PersistencePromise.forEach(viewChanges, (viewChange) => {
  16947. return PersistencePromise.forEach(viewChange.addedKeys, (key) => localStoreImpl.persistence.referenceDelegate.addReference(txn, viewChange.targetId, key)).next(() => PersistencePromise.forEach(viewChange.removedKeys, (key) => localStoreImpl.persistence.referenceDelegate.removeReference(txn, viewChange.targetId, key)));
  16948. });
  16949. });
  16950. }
  16951. catch (e) {
  16952. if (isIndexedDbTransactionError(e)) {
  16953. // If `notifyLocalViewChanges` fails, we did not advance the sequence
  16954. // number for the documents that were included in this transaction.
  16955. // This might trigger them to be deleted earlier than they otherwise
  16956. // would have, but it should not invalidate the integrity of the data.
  16957. logDebug(LOG_TAG$b, 'Failed to update sequence numbers: ' + e);
  16958. }
  16959. else {
  16960. throw e;
  16961. }
  16962. }
  16963. for (const viewChange of viewChanges) {
  16964. const targetId = viewChange.targetId;
  16965. if (!viewChange.fromCache) {
  16966. const targetData = localStoreImpl.targetDataByTarget.get(targetId);
  16967. // Advance the last limbo free snapshot version
  16968. const lastLimboFreeSnapshotVersion = targetData.snapshotVersion;
  16969. const updatedTargetData = targetData.withLastLimboFreeSnapshotVersion(lastLimboFreeSnapshotVersion);
  16970. localStoreImpl.targetDataByTarget =
  16971. localStoreImpl.targetDataByTarget.insert(targetId, updatedTargetData);
  16972. // TODO(b/272564316): Apply the optimization done on other platforms.
  16973. // This is a problem for web because saving the updated targetData from
  16974. // non-primary client conflicts with what primary client saved.
  16975. }
  16976. }
  16977. }
  16978. /**
  16979. * Gets the mutation batch after the passed in batchId in the mutation queue
  16980. * or null if empty.
  16981. * @param afterBatchId - If provided, the batch to search after.
  16982. * @returns The next mutation or null if there wasn't one.
  16983. */
  16984. function localStoreGetNextMutationBatch(localStore, afterBatchId) {
  16985. const localStoreImpl = debugCast(localStore);
  16986. return localStoreImpl.persistence.runTransaction('Get next mutation batch', 'readonly', txn => {
  16987. if (afterBatchId === undefined) {
  16988. afterBatchId = BATCHID_UNKNOWN;
  16989. }
  16990. return localStoreImpl.mutationQueue.getNextMutationBatchAfterBatchId(txn, afterBatchId);
  16991. });
  16992. }
  16993. /**
  16994. * Reads the current value of a Document with a given key or null if not
  16995. * found - used for testing.
  16996. */
  16997. function localStoreReadDocument(localStore, key) {
  16998. const localStoreImpl = debugCast(localStore);
  16999. return localStoreImpl.persistence.runTransaction('read document', 'readonly', txn => localStoreImpl.localDocuments.getDocument(txn, key));
  17000. }
  17001. /**
  17002. * Assigns the given target an internal ID so that its results can be pinned so
  17003. * they don't get GC'd. A target must be allocated in the local store before
  17004. * the store can be used to manage its view.
  17005. *
  17006. * Allocating an already allocated `Target` will return the existing `TargetData`
  17007. * for that `Target`.
  17008. */
  17009. function localStoreAllocateTarget(localStore, target) {
  17010. const localStoreImpl = debugCast(localStore);
  17011. return localStoreImpl.persistence
  17012. .runTransaction('Allocate target', 'readwrite', txn => {
  17013. let targetData;
  17014. return localStoreImpl.targetCache
  17015. .getTargetData(txn, target)
  17016. .next((cached) => {
  17017. if (cached) {
  17018. // This target has been listened to previously, so reuse the
  17019. // previous targetID.
  17020. // TODO(mcg): freshen last accessed date?
  17021. targetData = cached;
  17022. return PersistencePromise.resolve(targetData);
  17023. }
  17024. else {
  17025. return localStoreImpl.targetCache
  17026. .allocateTargetId(txn)
  17027. .next(targetId => {
  17028. targetData = new TargetData(target, targetId, "TargetPurposeListen" /* TargetPurpose.Listen */, txn.currentSequenceNumber);
  17029. return localStoreImpl.targetCache
  17030. .addTargetData(txn, targetData)
  17031. .next(() => targetData);
  17032. });
  17033. }
  17034. });
  17035. })
  17036. .then(targetData => {
  17037. // If Multi-Tab is enabled, the existing target data may be newer than
  17038. // the in-memory data
  17039. const cachedTargetData = localStoreImpl.targetDataByTarget.get(targetData.targetId);
  17040. if (cachedTargetData === null ||
  17041. targetData.snapshotVersion.compareTo(cachedTargetData.snapshotVersion) >
  17042. 0) {
  17043. localStoreImpl.targetDataByTarget =
  17044. localStoreImpl.targetDataByTarget.insert(targetData.targetId, targetData);
  17045. localStoreImpl.targetIdByTarget.set(target, targetData.targetId);
  17046. }
  17047. return targetData;
  17048. });
  17049. }
  17050. /**
  17051. * Returns the TargetData as seen by the LocalStore, including updates that may
  17052. * have not yet been persisted to the TargetCache.
  17053. */
  17054. // Visible for testing.
  17055. function localStoreGetTargetData(localStore, transaction, target) {
  17056. const localStoreImpl = debugCast(localStore);
  17057. const targetId = localStoreImpl.targetIdByTarget.get(target);
  17058. if (targetId !== undefined) {
  17059. return PersistencePromise.resolve(localStoreImpl.targetDataByTarget.get(targetId));
  17060. }
  17061. else {
  17062. return localStoreImpl.targetCache.getTargetData(transaction, target);
  17063. }
  17064. }
  17065. /**
  17066. * Unpins all the documents associated with the given target. If
  17067. * `keepPersistedTargetData` is set to false and Eager GC enabled, the method
  17068. * directly removes the associated target data from the target cache.
  17069. *
  17070. * Releasing a non-existing `Target` is a no-op.
  17071. */
  17072. // PORTING NOTE: `keepPersistedTargetData` is multi-tab only.
  17073. async function localStoreReleaseTarget(localStore, targetId, keepPersistedTargetData) {
  17074. const localStoreImpl = debugCast(localStore);
  17075. const targetData = localStoreImpl.targetDataByTarget.get(targetId);
  17076. const mode = keepPersistedTargetData ? 'readwrite' : 'readwrite-primary';
  17077. try {
  17078. if (!keepPersistedTargetData) {
  17079. await localStoreImpl.persistence.runTransaction('Release target', mode, txn => {
  17080. return localStoreImpl.persistence.referenceDelegate.removeTarget(txn, targetData);
  17081. });
  17082. }
  17083. }
  17084. catch (e) {
  17085. if (isIndexedDbTransactionError(e)) {
  17086. // All `releaseTarget` does is record the final metadata state for the
  17087. // target, but we've been recording this periodically during target
  17088. // activity. If we lose this write this could cause a very slight
  17089. // difference in the order of target deletion during GC, but we
  17090. // don't define exact LRU semantics so this is acceptable.
  17091. logDebug(LOG_TAG$b, `Failed to update sequence numbers for target ${targetId}: ${e}`);
  17092. }
  17093. else {
  17094. throw e;
  17095. }
  17096. }
  17097. localStoreImpl.targetDataByTarget =
  17098. localStoreImpl.targetDataByTarget.remove(targetId);
  17099. localStoreImpl.targetIdByTarget.delete(targetData.target);
  17100. }
  17101. /**
  17102. * Runs the specified query against the local store and returns the results,
  17103. * potentially taking advantage of query data from previous executions (such
  17104. * as the set of remote keys).
  17105. *
  17106. * @param usePreviousResults - Whether results from previous executions can
  17107. * be used to optimize this query execution.
  17108. */
  17109. function localStoreExecuteQuery(localStore, query, usePreviousResults) {
  17110. const localStoreImpl = debugCast(localStore);
  17111. let lastLimboFreeSnapshotVersion = SnapshotVersion.min();
  17112. let remoteKeys = documentKeySet();
  17113. return localStoreImpl.persistence.runTransaction('Execute query', 'readonly', txn => {
  17114. return localStoreGetTargetData(localStoreImpl, txn, queryToTarget(query))
  17115. .next(targetData => {
  17116. if (targetData) {
  17117. lastLimboFreeSnapshotVersion =
  17118. targetData.lastLimboFreeSnapshotVersion;
  17119. return localStoreImpl.targetCache
  17120. .getMatchingKeysForTargetId(txn, targetData.targetId)
  17121. .next(result => {
  17122. remoteKeys = result;
  17123. });
  17124. }
  17125. })
  17126. .next(() => localStoreImpl.queryEngine.getDocumentsMatchingQuery(txn, query, usePreviousResults
  17127. ? lastLimboFreeSnapshotVersion
  17128. : SnapshotVersion.min(), usePreviousResults ? remoteKeys : documentKeySet()))
  17129. .next(documents => {
  17130. setMaxReadTime(localStoreImpl, queryCollectionGroup(query), documents);
  17131. return { documents, remoteKeys };
  17132. });
  17133. });
  17134. }
  17135. function applyWriteToRemoteDocuments(localStoreImpl, txn, batchResult, documentBuffer) {
  17136. const batch = batchResult.batch;
  17137. const docKeys = batch.keys();
  17138. let promiseChain = PersistencePromise.resolve();
  17139. docKeys.forEach(docKey => {
  17140. promiseChain = promiseChain
  17141. .next(() => documentBuffer.getEntry(txn, docKey))
  17142. .next(doc => {
  17143. const ackVersion = batchResult.docVersions.get(docKey);
  17144. hardAssert(ackVersion !== null);
  17145. if (doc.version.compareTo(ackVersion) < 0) {
  17146. batch.applyToRemoteDocument(doc, batchResult);
  17147. if (doc.isValidDocument()) {
  17148. // We use the commitVersion as the readTime rather than the
  17149. // document's updateTime since the updateTime is not advanced
  17150. // for updates that do not modify the underlying document.
  17151. doc.setReadTime(batchResult.commitVersion);
  17152. documentBuffer.addEntry(doc);
  17153. }
  17154. }
  17155. });
  17156. });
  17157. return promiseChain.next(() => localStoreImpl.mutationQueue.removeMutationBatch(txn, batch));
  17158. }
  17159. /** Returns the local view of the documents affected by a mutation batch. */
  17160. // PORTING NOTE: Multi-Tab only.
  17161. function localStoreLookupMutationDocuments(localStore, batchId) {
  17162. const localStoreImpl = debugCast(localStore);
  17163. const mutationQueueImpl = debugCast(localStoreImpl.mutationQueue);
  17164. return localStoreImpl.persistence.runTransaction('Lookup mutation documents', 'readonly', txn => {
  17165. return mutationQueueImpl.lookupMutationKeys(txn, batchId).next(keys => {
  17166. if (keys) {
  17167. return localStoreImpl.localDocuments.getDocuments(txn, keys);
  17168. }
  17169. else {
  17170. return PersistencePromise.resolve(null);
  17171. }
  17172. });
  17173. });
  17174. }
  17175. // PORTING NOTE: Multi-Tab only.
  17176. function localStoreRemoveCachedMutationBatchMetadata(localStore, batchId) {
  17177. const mutationQueueImpl = debugCast(debugCast(localStore, LocalStoreImpl).mutationQueue);
  17178. mutationQueueImpl.removeCachedMutationKeys(batchId);
  17179. }
  17180. // PORTING NOTE: Multi-Tab only.
  17181. function localStoreGetActiveClients(localStore) {
  17182. const persistenceImpl = debugCast(debugCast(localStore, LocalStoreImpl).persistence);
  17183. return persistenceImpl.getActiveClients();
  17184. }
  17185. // PORTING NOTE: Multi-Tab only.
  17186. function localStoreGetCachedTarget(localStore, targetId) {
  17187. const localStoreImpl = debugCast(localStore);
  17188. const targetCacheImpl = debugCast(localStoreImpl.targetCache);
  17189. const cachedTargetData = localStoreImpl.targetDataByTarget.get(targetId);
  17190. if (cachedTargetData) {
  17191. return Promise.resolve(cachedTargetData.target);
  17192. }
  17193. else {
  17194. return localStoreImpl.persistence.runTransaction('Get target data', 'readonly', txn => {
  17195. return targetCacheImpl
  17196. .getTargetDataForTarget(txn, targetId)
  17197. .next(targetData => (targetData ? targetData.target : null));
  17198. });
  17199. }
  17200. }
  17201. /**
  17202. * Returns the set of documents that have been updated since the last call.
  17203. * If this is the first call, returns the set of changes since client
  17204. * initialization. Further invocations will return document that have changed
  17205. * since the prior call.
  17206. */
  17207. // PORTING NOTE: Multi-Tab only.
  17208. function localStoreGetNewDocumentChanges(localStore, collectionGroup) {
  17209. const localStoreImpl = debugCast(localStore);
  17210. // Get the current maximum read time for the collection. This should always
  17211. // exist, but to reduce the chance for regressions we default to
  17212. // SnapshotVersion.Min()
  17213. // TODO(indexing): Consider removing the default value.
  17214. const readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
  17215. SnapshotVersion.min();
  17216. return localStoreImpl.persistence
  17217. .runTransaction('Get new document changes', 'readonly', txn => localStoreImpl.remoteDocuments.getAllFromCollectionGroup(txn, collectionGroup, newIndexOffsetSuccessorFromReadTime(readTime, INITIAL_LARGEST_BATCH_ID),
  17218. /* limit= */ Number.MAX_SAFE_INTEGER))
  17219. .then(changedDocs => {
  17220. setMaxReadTime(localStoreImpl, collectionGroup, changedDocs);
  17221. return changedDocs;
  17222. });
  17223. }
  17224. /** Sets the collection group's maximum read time from the given documents. */
  17225. // PORTING NOTE: Multi-Tab only.
  17226. function setMaxReadTime(localStoreImpl, collectionGroup, changedDocs) {
  17227. let readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
  17228. SnapshotVersion.min();
  17229. changedDocs.forEach((_, doc) => {
  17230. if (doc.readTime.compareTo(readTime) > 0) {
  17231. readTime = doc.readTime;
  17232. }
  17233. });
  17234. localStoreImpl.collectionGroupReadTime.set(collectionGroup, readTime);
  17235. }
  17236. /**
  17237. * Creates a new target using the given bundle name, which will be used to
  17238. * hold the keys of all documents from the bundle in query-document mappings.
  17239. * This ensures that the loaded documents do not get garbage collected
  17240. * right away.
  17241. */
  17242. function umbrellaTarget(bundleName) {
  17243. // It is OK that the path used for the query is not valid, because this will
  17244. // not be read and queried.
  17245. return queryToTarget(newQueryForPath(ResourcePath.fromString(`__bundle__/docs/${bundleName}`)));
  17246. }
  17247. /**
  17248. * Applies the documents from a bundle to the "ground-state" (remote)
  17249. * documents.
  17250. *
  17251. * LocalDocuments are re-calculated if there are remaining mutations in the
  17252. * queue.
  17253. */
  17254. async function localStoreApplyBundledDocuments(localStore, bundleConverter, documents, bundleName) {
  17255. const localStoreImpl = debugCast(localStore);
  17256. let documentKeys = documentKeySet();
  17257. let documentMap = mutableDocumentMap();
  17258. for (const bundleDoc of documents) {
  17259. const documentKey = bundleConverter.toDocumentKey(bundleDoc.metadata.name);
  17260. if (bundleDoc.document) {
  17261. documentKeys = documentKeys.add(documentKey);
  17262. }
  17263. const doc = bundleConverter.toMutableDocument(bundleDoc);
  17264. doc.setReadTime(bundleConverter.toSnapshotVersion(bundleDoc.metadata.readTime));
  17265. documentMap = documentMap.insert(documentKey, doc);
  17266. }
  17267. const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
  17268. trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
  17269. });
  17270. // Allocates a target to hold all document keys from the bundle, such that
  17271. // they will not get garbage collected right away.
  17272. const umbrellaTargetData = await localStoreAllocateTarget(localStoreImpl, umbrellaTarget(bundleName));
  17273. return localStoreImpl.persistence.runTransaction('Apply bundle documents', 'readwrite', txn => {
  17274. return populateDocumentChangeBuffer(txn, documentBuffer, documentMap)
  17275. .next(documentChangeResult => {
  17276. documentBuffer.apply(txn);
  17277. return documentChangeResult;
  17278. })
  17279. .next(documentChangeResult => {
  17280. return localStoreImpl.targetCache
  17281. .removeMatchingKeysForTargetId(txn, umbrellaTargetData.targetId)
  17282. .next(() => localStoreImpl.targetCache.addMatchingKeys(txn, documentKeys, umbrellaTargetData.targetId))
  17283. .next(() => localStoreImpl.localDocuments.getLocalViewOfDocuments(txn, documentChangeResult.changedDocuments, documentChangeResult.existenceChangedKeys))
  17284. .next(() => documentChangeResult.changedDocuments);
  17285. });
  17286. });
  17287. }
  17288. /**
  17289. * Returns a promise of a boolean to indicate if the given bundle has already
  17290. * been loaded and the create time is newer than the current loading bundle.
  17291. */
  17292. function localStoreHasNewerBundle(localStore, bundleMetadata) {
  17293. const localStoreImpl = debugCast(localStore);
  17294. const currentReadTime = fromVersion(bundleMetadata.createTime);
  17295. return localStoreImpl.persistence
  17296. .runTransaction('hasNewerBundle', 'readonly', transaction => {
  17297. return localStoreImpl.bundleCache.getBundleMetadata(transaction, bundleMetadata.id);
  17298. })
  17299. .then(cached => {
  17300. return !!cached && cached.createTime.compareTo(currentReadTime) >= 0;
  17301. });
  17302. }
  17303. /**
  17304. * Saves the given `BundleMetadata` to local persistence.
  17305. */
  17306. function localStoreSaveBundle(localStore, bundleMetadata) {
  17307. const localStoreImpl = debugCast(localStore);
  17308. return localStoreImpl.persistence.runTransaction('Save bundle', 'readwrite', transaction => {
  17309. return localStoreImpl.bundleCache.saveBundleMetadata(transaction, bundleMetadata);
  17310. });
  17311. }
  17312. /**
  17313. * Returns a promise of a `NamedQuery` associated with given query name. Promise
  17314. * resolves to undefined if no persisted data can be found.
  17315. */
  17316. function localStoreGetNamedQuery(localStore, queryName) {
  17317. const localStoreImpl = debugCast(localStore);
  17318. return localStoreImpl.persistence.runTransaction('Get named query', 'readonly', transaction => localStoreImpl.bundleCache.getNamedQuery(transaction, queryName));
  17319. }
  17320. /**
  17321. * Saves the given `NamedQuery` to local persistence.
  17322. */
  17323. async function localStoreSaveNamedQuery(localStore, query, documents = documentKeySet()) {
  17324. // Allocate a target for the named query such that it can be resumed
  17325. // from associated read time if users use it to listen.
  17326. // NOTE: this also means if no corresponding target exists, the new target
  17327. // will remain active and will not get collected, unless users happen to
  17328. // unlisten the query somehow.
  17329. const allocated = await localStoreAllocateTarget(localStore, queryToTarget(fromBundledQuery(query.bundledQuery)));
  17330. const localStoreImpl = debugCast(localStore);
  17331. return localStoreImpl.persistence.runTransaction('Save named query', 'readwrite', transaction => {
  17332. const readTime = fromVersion(query.readTime);
  17333. // Simply save the query itself if it is older than what the SDK already
  17334. // has.
  17335. if (allocated.snapshotVersion.compareTo(readTime) >= 0) {
  17336. return localStoreImpl.bundleCache.saveNamedQuery(transaction, query);
  17337. }
  17338. // Update existing target data because the query from the bundle is newer.
  17339. const newTargetData = allocated.withResumeToken(ByteString.EMPTY_BYTE_STRING, readTime);
  17340. localStoreImpl.targetDataByTarget =
  17341. localStoreImpl.targetDataByTarget.insert(newTargetData.targetId, newTargetData);
  17342. return localStoreImpl.targetCache
  17343. .updateTargetData(transaction, newTargetData)
  17344. .next(() => localStoreImpl.targetCache.removeMatchingKeysForTargetId(transaction, allocated.targetId))
  17345. .next(() => localStoreImpl.targetCache.addMatchingKeys(transaction, documents, allocated.targetId))
  17346. .next(() => localStoreImpl.bundleCache.saveNamedQuery(transaction, query));
  17347. });
  17348. }
  17349. async function localStoreConfigureFieldIndexes(localStore, newFieldIndexes) {
  17350. const localStoreImpl = debugCast(localStore);
  17351. const indexManager = localStoreImpl.indexManager;
  17352. const promises = [];
  17353. return localStoreImpl.persistence.runTransaction('Configure indexes', 'readwrite', transaction => indexManager
  17354. .getFieldIndexes(transaction)
  17355. .next(oldFieldIndexes => diffArrays(oldFieldIndexes, newFieldIndexes, fieldIndexSemanticComparator, fieldIndex => {
  17356. promises.push(indexManager.addFieldIndex(transaction, fieldIndex));
  17357. }, fieldIndex => {
  17358. promises.push(indexManager.deleteFieldIndex(transaction, fieldIndex));
  17359. }))
  17360. .next(() => PersistencePromise.waitFor(promises)));
  17361. }
  17362. /**
  17363. * @license
  17364. * Copyright 2019 Google LLC
  17365. *
  17366. * Licensed under the Apache License, Version 2.0 (the "License");
  17367. * you may not use this file except in compliance with the License.
  17368. * You may obtain a copy of the License at
  17369. *
  17370. * http://www.apache.org/licenses/LICENSE-2.0
  17371. *
  17372. * Unless required by applicable law or agreed to in writing, software
  17373. * distributed under the License is distributed on an "AS IS" BASIS,
  17374. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17375. * See the License for the specific language governing permissions and
  17376. * limitations under the License.
  17377. */
  17378. /**
  17379. * The Firestore query engine.
  17380. *
  17381. * Firestore queries can be executed in three modes. The Query Engine determines
  17382. * what mode to use based on what data is persisted. The mode only determines
  17383. * the runtime complexity of the query - the result set is equivalent across all
  17384. * implementations.
  17385. *
  17386. * The Query engine will use indexed-based execution if a user has configured
  17387. * any index that can be used to execute query (via `setIndexConfiguration()`).
  17388. * Otherwise, the engine will try to optimize the query by re-using a previously
  17389. * persisted query result. If that is not possible, the query will be executed
  17390. * via a full collection scan.
  17391. *
  17392. * Index-based execution is the default when available. The query engine
  17393. * supports partial indexed execution and merges the result from the index
  17394. * lookup with documents that have not yet been indexed. The index evaluation
  17395. * matches the backend's format and as such, the SDK can use indexing for all
  17396. * queries that the backend supports.
  17397. *
  17398. * If no index exists, the query engine tries to take advantage of the target
  17399. * document mapping in the TargetCache. These mappings exists for all queries
  17400. * that have been synced with the backend at least once and allow the query
  17401. * engine to only read documents that previously matched a query plus any
  17402. * documents that were edited after the query was last listened to.
  17403. *
  17404. * There are some cases when this optimization is not guaranteed to produce
  17405. * the same results as full collection scans. In these cases, query
  17406. * processing falls back to full scans. These cases are:
  17407. *
  17408. * - Limit queries where a document that matched the query previously no longer
  17409. * matches the query.
  17410. *
  17411. * - Limit queries where a document edit may cause the document to sort below
  17412. * another document that is in the local cache.
  17413. *
  17414. * - Queries that have never been CURRENT or free of limbo documents.
  17415. */
  17416. class QueryEngine {
  17417. constructor() {
  17418. this.initialized = false;
  17419. }
  17420. /** Sets the document view to query against. */
  17421. initialize(localDocuments, indexManager) {
  17422. this.localDocumentsView = localDocuments;
  17423. this.indexManager = indexManager;
  17424. this.initialized = true;
  17425. }
  17426. /** Returns all local documents matching the specified query. */
  17427. getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion, remoteKeys) {
  17428. return this.performQueryUsingIndex(transaction, query)
  17429. .next(result => result
  17430. ? result
  17431. : this.performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion))
  17432. .next(result => result ? result : this.executeFullCollectionScan(transaction, query));
  17433. }
  17434. /**
  17435. * Performs an indexed query that evaluates the query based on a collection's
  17436. * persisted index values. Returns `null` if an index is not available.
  17437. */
  17438. performQueryUsingIndex(transaction, query) {
  17439. if (queryMatchesAllDocuments(query)) {
  17440. // Queries that match all documents don't benefit from using
  17441. // key-based lookups. It is more efficient to scan all documents in a
  17442. // collection, rather than to perform individual lookups.
  17443. return PersistencePromise.resolve(null);
  17444. }
  17445. let target = queryToTarget(query);
  17446. return this.indexManager
  17447. .getIndexType(transaction, target)
  17448. .next(indexType => {
  17449. if (indexType === 0 /* IndexType.NONE */) {
  17450. // The target cannot be served from any index.
  17451. return null;
  17452. }
  17453. if (query.limit !== null && indexType === 1 /* IndexType.PARTIAL */) {
  17454. // We cannot apply a limit for targets that are served using a partial
  17455. // index. If a partial index will be used to serve the target, the
  17456. // query may return a superset of documents that match the target
  17457. // (e.g. if the index doesn't include all the target's filters), or
  17458. // may return the correct set of documents in the wrong order (e.g. if
  17459. // the index doesn't include a segment for one of the orderBys).
  17460. // Therefore, a limit should not be applied in such cases.
  17461. query = queryWithLimit(query, null, "F" /* LimitType.First */);
  17462. target = queryToTarget(query);
  17463. }
  17464. return this.indexManager
  17465. .getDocumentsMatchingTarget(transaction, target)
  17466. .next(keys => {
  17467. const sortedKeys = documentKeySet(...keys);
  17468. return this.localDocumentsView
  17469. .getDocuments(transaction, sortedKeys)
  17470. .next(indexedDocuments => {
  17471. return this.indexManager
  17472. .getMinOffset(transaction, target)
  17473. .next(offset => {
  17474. const previousResults = this.applyQuery(query, indexedDocuments);
  17475. if (this.needsRefill(query, previousResults, sortedKeys, offset.readTime)) {
  17476. // A limit query whose boundaries change due to local
  17477. // edits can be re-run against the cache by excluding the
  17478. // limit. This ensures that all documents that match the
  17479. // query's filters are included in the result set. The SDK
  17480. // can then apply the limit once all local edits are
  17481. // incorporated.
  17482. return this.performQueryUsingIndex(transaction, queryWithLimit(query, null, "F" /* LimitType.First */));
  17483. }
  17484. return this.appendRemainingResults(transaction, previousResults, query, offset);
  17485. });
  17486. });
  17487. });
  17488. });
  17489. }
  17490. /**
  17491. * Performs a query based on the target's persisted query mapping. Returns
  17492. * `null` if the mapping is not available or cannot be used.
  17493. */
  17494. performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion) {
  17495. if (queryMatchesAllDocuments(query)) {
  17496. // Queries that match all documents don't benefit from using
  17497. // key-based lookups. It is more efficient to scan all documents in a
  17498. // collection, rather than to perform individual lookups.
  17499. return this.executeFullCollectionScan(transaction, query);
  17500. }
  17501. // Queries that have never seen a snapshot without limbo free documents
  17502. // should also be run as a full collection scan.
  17503. if (lastLimboFreeSnapshotVersion.isEqual(SnapshotVersion.min())) {
  17504. return this.executeFullCollectionScan(transaction, query);
  17505. }
  17506. return this.localDocumentsView.getDocuments(transaction, remoteKeys).next(documents => {
  17507. const previousResults = this.applyQuery(query, documents);
  17508. if (this.needsRefill(query, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
  17509. return this.executeFullCollectionScan(transaction, query);
  17510. }
  17511. if (getLogLevel() <= logger.LogLevel.DEBUG) {
  17512. logDebug('QueryEngine', 'Re-using previous result from %s to execute query: %s', lastLimboFreeSnapshotVersion.toString(), stringifyQuery(query));
  17513. }
  17514. // Retrieve all results for documents that were updated since the last
  17515. // limbo-document free remote snapshot.
  17516. return this.appendRemainingResults(transaction, previousResults, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID));
  17517. });
  17518. }
  17519. /** Applies the query filter and sorting to the provided documents. */
  17520. applyQuery(query, documents) {
  17521. // Sort the documents and re-apply the query filter since previously
  17522. // matching documents do not necessarily still match the query.
  17523. let queryResults = new SortedSet(newQueryComparator(query));
  17524. documents.forEach((_, maybeDoc) => {
  17525. if (queryMatches(query, maybeDoc)) {
  17526. queryResults = queryResults.add(maybeDoc);
  17527. }
  17528. });
  17529. return queryResults;
  17530. }
  17531. /**
  17532. * Determines if a limit query needs to be refilled from cache, making it
  17533. * ineligible for index-free execution.
  17534. *
  17535. * @param query - The query.
  17536. * @param sortedPreviousResults - The documents that matched the query when it
  17537. * was last synchronized, sorted by the query's comparator.
  17538. * @param remoteKeys - The document keys that matched the query at the last
  17539. * snapshot.
  17540. * @param limboFreeSnapshotVersion - The version of the snapshot when the
  17541. * query was last synchronized.
  17542. */
  17543. needsRefill(query, sortedPreviousResults, remoteKeys, limboFreeSnapshotVersion) {
  17544. if (query.limit === null) {
  17545. // Queries without limits do not need to be refilled.
  17546. return false;
  17547. }
  17548. if (remoteKeys.size !== sortedPreviousResults.size) {
  17549. // The query needs to be refilled if a previously matching document no
  17550. // longer matches.
  17551. return true;
  17552. }
  17553. // Limit queries are not eligible for index-free query execution if there is
  17554. // a potential that an older document from cache now sorts before a document
  17555. // that was previously part of the limit. This, however, can only happen if
  17556. // the document at the edge of the limit goes out of limit.
  17557. // If a document that is not the limit boundary sorts differently,
  17558. // the boundary of the limit itself did not change and documents from cache
  17559. // will continue to be "rejected" by this boundary. Therefore, we can ignore
  17560. // any modifications that don't affect the last document.
  17561. const docAtLimitEdge = query.limitType === "F" /* LimitType.First */
  17562. ? sortedPreviousResults.last()
  17563. : sortedPreviousResults.first();
  17564. if (!docAtLimitEdge) {
  17565. // We don't need to refill the query if there were already no documents.
  17566. return false;
  17567. }
  17568. return (docAtLimitEdge.hasPendingWrites ||
  17569. docAtLimitEdge.version.compareTo(limboFreeSnapshotVersion) > 0);
  17570. }
  17571. executeFullCollectionScan(transaction, query) {
  17572. if (getLogLevel() <= logger.LogLevel.DEBUG) {
  17573. logDebug('QueryEngine', 'Using full collection scan to execute query:', stringifyQuery(query));
  17574. }
  17575. return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
  17576. }
  17577. /**
  17578. * Combines the results from an indexed execution with the remaining documents
  17579. * that have not yet been indexed.
  17580. */
  17581. appendRemainingResults(transaction, indexedResults, query, offset) {
  17582. // Retrieve all results for documents that were updated since the offset.
  17583. return this.localDocumentsView
  17584. .getDocumentsMatchingQuery(transaction, query, offset)
  17585. .next(remainingResults => {
  17586. // Merge with existing results
  17587. indexedResults.forEach(d => {
  17588. remainingResults = remainingResults.insert(d.key, d);
  17589. });
  17590. return remainingResults;
  17591. });
  17592. }
  17593. }
  17594. /**
  17595. * @license
  17596. * Copyright 2019 Google LLC
  17597. *
  17598. * Licensed under the Apache License, Version 2.0 (the "License");
  17599. * you may not use this file except in compliance with the License.
  17600. * You may obtain a copy of the License at
  17601. *
  17602. * http://www.apache.org/licenses/LICENSE-2.0
  17603. *
  17604. * Unless required by applicable law or agreed to in writing, software
  17605. * distributed under the License is distributed on an "AS IS" BASIS,
  17606. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17607. * See the License for the specific language governing permissions and
  17608. * limitations under the License.
  17609. */
  17610. // The format of the LocalStorage key that stores the client state is:
  17611. // firestore_clients_<persistence_prefix>_<instance_key>
  17612. const CLIENT_STATE_KEY_PREFIX = 'firestore_clients';
  17613. /** Assembles the key for a client state in WebStorage */
  17614. function createWebStorageClientStateKey(persistenceKey, clientId) {
  17615. return `${CLIENT_STATE_KEY_PREFIX}_${persistenceKey}_${clientId}`;
  17616. }
  17617. // The format of the WebStorage key that stores the mutation state is:
  17618. // firestore_mutations_<persistence_prefix>_<batch_id>
  17619. // (for unauthenticated users)
  17620. // or: firestore_mutations_<persistence_prefix>_<batch_id>_<user_uid>
  17621. //
  17622. // 'user_uid' is last to avoid needing to escape '_' characters that it might
  17623. // contain.
  17624. const MUTATION_BATCH_KEY_PREFIX = 'firestore_mutations';
  17625. /** Assembles the key for a mutation batch in WebStorage */
  17626. function createWebStorageMutationBatchKey(persistenceKey, user, batchId) {
  17627. let mutationKey = `${MUTATION_BATCH_KEY_PREFIX}_${persistenceKey}_${batchId}`;
  17628. if (user.isAuthenticated()) {
  17629. mutationKey += `_${user.uid}`;
  17630. }
  17631. return mutationKey;
  17632. }
  17633. // The format of the WebStorage key that stores a query target's metadata is:
  17634. // firestore_targets_<persistence_prefix>_<target_id>
  17635. const QUERY_TARGET_KEY_PREFIX = 'firestore_targets';
  17636. /** Assembles the key for a query state in WebStorage */
  17637. function createWebStorageQueryTargetMetadataKey(persistenceKey, targetId) {
  17638. return `${QUERY_TARGET_KEY_PREFIX}_${persistenceKey}_${targetId}`;
  17639. }
  17640. // The WebStorage prefix that stores the primary tab's online state. The
  17641. // format of the key is:
  17642. // firestore_online_state_<persistence_prefix>
  17643. const ONLINE_STATE_KEY_PREFIX = 'firestore_online_state';
  17644. /** Assembles the key for the online state of the primary tab. */
  17645. function createWebStorageOnlineStateKey(persistenceKey) {
  17646. return `${ONLINE_STATE_KEY_PREFIX}_${persistenceKey}`;
  17647. }
  17648. // The WebStorage prefix that plays as a event to indicate the remote documents
  17649. // might have changed due to some secondary tabs loading a bundle.
  17650. // format of the key is:
  17651. // firestore_bundle_loaded_v2_<persistenceKey>
  17652. // The version ending with "v2" stores the list of modified collection groups.
  17653. const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded_v2';
  17654. function createBundleLoadedKey(persistenceKey) {
  17655. return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
  17656. }
  17657. // The WebStorage key prefix for the key that stores the last sequence number allocated. The key
  17658. // looks like 'firestore_sequence_number_<persistence_prefix>'.
  17659. const SEQUENCE_NUMBER_KEY_PREFIX = 'firestore_sequence_number';
  17660. /** Assembles the key for the current sequence number. */
  17661. function createWebStorageSequenceNumberKey(persistenceKey) {
  17662. return `${SEQUENCE_NUMBER_KEY_PREFIX}_${persistenceKey}`;
  17663. }
  17664. /**
  17665. * @license
  17666. * Copyright 2018 Google LLC
  17667. *
  17668. * Licensed under the Apache License, Version 2.0 (the "License");
  17669. * you may not use this file except in compliance with the License.
  17670. * You may obtain a copy of the License at
  17671. *
  17672. * http://www.apache.org/licenses/LICENSE-2.0
  17673. *
  17674. * Unless required by applicable law or agreed to in writing, software
  17675. * distributed under the License is distributed on an "AS IS" BASIS,
  17676. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17677. * See the License for the specific language governing permissions and
  17678. * limitations under the License.
  17679. */
  17680. const LOG_TAG$a = 'SharedClientState';
  17681. /**
  17682. * Holds the state of a mutation batch, including its user ID, batch ID and
  17683. * whether the batch is 'pending', 'acknowledged' or 'rejected'.
  17684. */
  17685. // Visible for testing
  17686. class MutationMetadata {
  17687. constructor(user, batchId, state, error) {
  17688. this.user = user;
  17689. this.batchId = batchId;
  17690. this.state = state;
  17691. this.error = error;
  17692. }
  17693. /**
  17694. * Parses a MutationMetadata from its JSON representation in WebStorage.
  17695. * Logs a warning and returns null if the format of the data is not valid.
  17696. */
  17697. static fromWebStorageEntry(user, batchId, value) {
  17698. const mutationBatch = JSON.parse(value);
  17699. let validData = typeof mutationBatch === 'object' &&
  17700. ['pending', 'acknowledged', 'rejected'].indexOf(mutationBatch.state) !==
  17701. -1 &&
  17702. (mutationBatch.error === undefined ||
  17703. typeof mutationBatch.error === 'object');
  17704. let firestoreError = undefined;
  17705. if (validData && mutationBatch.error) {
  17706. validData =
  17707. typeof mutationBatch.error.message === 'string' &&
  17708. typeof mutationBatch.error.code === 'string';
  17709. if (validData) {
  17710. firestoreError = new FirestoreError(mutationBatch.error.code, mutationBatch.error.message);
  17711. }
  17712. }
  17713. if (validData) {
  17714. return new MutationMetadata(user, batchId, mutationBatch.state, firestoreError);
  17715. }
  17716. else {
  17717. logError(LOG_TAG$a, `Failed to parse mutation state for ID '${batchId}': ${value}`);
  17718. return null;
  17719. }
  17720. }
  17721. toWebStorageJSON() {
  17722. const batchMetadata = {
  17723. state: this.state,
  17724. updateTimeMs: Date.now() // Modify the existing value to trigger update.
  17725. };
  17726. if (this.error) {
  17727. batchMetadata.error = {
  17728. code: this.error.code,
  17729. message: this.error.message
  17730. };
  17731. }
  17732. return JSON.stringify(batchMetadata);
  17733. }
  17734. }
  17735. /**
  17736. * Holds the state of a query target, including its target ID and whether the
  17737. * target is 'not-current', 'current' or 'rejected'.
  17738. */
  17739. // Visible for testing
  17740. class QueryTargetMetadata {
  17741. constructor(targetId, state, error) {
  17742. this.targetId = targetId;
  17743. this.state = state;
  17744. this.error = error;
  17745. }
  17746. /**
  17747. * Parses a QueryTargetMetadata from its JSON representation in WebStorage.
  17748. * Logs a warning and returns null if the format of the data is not valid.
  17749. */
  17750. static fromWebStorageEntry(targetId, value) {
  17751. const targetState = JSON.parse(value);
  17752. let validData = typeof targetState === 'object' &&
  17753. ['not-current', 'current', 'rejected'].indexOf(targetState.state) !==
  17754. -1 &&
  17755. (targetState.error === undefined ||
  17756. typeof targetState.error === 'object');
  17757. let firestoreError = undefined;
  17758. if (validData && targetState.error) {
  17759. validData =
  17760. typeof targetState.error.message === 'string' &&
  17761. typeof targetState.error.code === 'string';
  17762. if (validData) {
  17763. firestoreError = new FirestoreError(targetState.error.code, targetState.error.message);
  17764. }
  17765. }
  17766. if (validData) {
  17767. return new QueryTargetMetadata(targetId, targetState.state, firestoreError);
  17768. }
  17769. else {
  17770. logError(LOG_TAG$a, `Failed to parse target state for ID '${targetId}': ${value}`);
  17771. return null;
  17772. }
  17773. }
  17774. toWebStorageJSON() {
  17775. const targetState = {
  17776. state: this.state,
  17777. updateTimeMs: Date.now() // Modify the existing value to trigger update.
  17778. };
  17779. if (this.error) {
  17780. targetState.error = {
  17781. code: this.error.code,
  17782. message: this.error.message
  17783. };
  17784. }
  17785. return JSON.stringify(targetState);
  17786. }
  17787. }
  17788. /**
  17789. * This class represents the immutable ClientState for a client read from
  17790. * WebStorage, containing the list of active query targets.
  17791. */
  17792. class RemoteClientState {
  17793. constructor(clientId, activeTargetIds) {
  17794. this.clientId = clientId;
  17795. this.activeTargetIds = activeTargetIds;
  17796. }
  17797. /**
  17798. * Parses a RemoteClientState from the JSON representation in WebStorage.
  17799. * Logs a warning and returns null if the format of the data is not valid.
  17800. */
  17801. static fromWebStorageEntry(clientId, value) {
  17802. const clientState = JSON.parse(value);
  17803. let validData = typeof clientState === 'object' &&
  17804. clientState.activeTargetIds instanceof Array;
  17805. let activeTargetIdsSet = targetIdSet();
  17806. for (let i = 0; validData && i < clientState.activeTargetIds.length; ++i) {
  17807. validData = isSafeInteger(clientState.activeTargetIds[i]);
  17808. activeTargetIdsSet = activeTargetIdsSet.add(clientState.activeTargetIds[i]);
  17809. }
  17810. if (validData) {
  17811. return new RemoteClientState(clientId, activeTargetIdsSet);
  17812. }
  17813. else {
  17814. logError(LOG_TAG$a, `Failed to parse client data for instance '${clientId}': ${value}`);
  17815. return null;
  17816. }
  17817. }
  17818. }
  17819. /**
  17820. * This class represents the online state for all clients participating in
  17821. * multi-tab. The online state is only written to by the primary client, and
  17822. * used in secondary clients to update their query views.
  17823. */
  17824. class SharedOnlineState {
  17825. constructor(clientId, onlineState) {
  17826. this.clientId = clientId;
  17827. this.onlineState = onlineState;
  17828. }
  17829. /**
  17830. * Parses a SharedOnlineState from its JSON representation in WebStorage.
  17831. * Logs a warning and returns null if the format of the data is not valid.
  17832. */
  17833. static fromWebStorageEntry(value) {
  17834. const onlineState = JSON.parse(value);
  17835. const validData = typeof onlineState === 'object' &&
  17836. ['Unknown', 'Online', 'Offline'].indexOf(onlineState.onlineState) !==
  17837. -1 &&
  17838. typeof onlineState.clientId === 'string';
  17839. if (validData) {
  17840. return new SharedOnlineState(onlineState.clientId, onlineState.onlineState);
  17841. }
  17842. else {
  17843. logError(LOG_TAG$a, `Failed to parse online state: ${value}`);
  17844. return null;
  17845. }
  17846. }
  17847. }
  17848. /**
  17849. * Metadata state of the local client. Unlike `RemoteClientState`, this class is
  17850. * mutable and keeps track of all pending mutations, which allows us to
  17851. * update the range of pending mutation batch IDs as new mutations are added or
  17852. * removed.
  17853. *
  17854. * The data in `LocalClientState` is not read from WebStorage and instead
  17855. * updated via its instance methods. The updated state can be serialized via
  17856. * `toWebStorageJSON()`.
  17857. */
  17858. // Visible for testing.
  17859. class LocalClientState {
  17860. constructor() {
  17861. this.activeTargetIds = targetIdSet();
  17862. }
  17863. addQueryTarget(targetId) {
  17864. this.activeTargetIds = this.activeTargetIds.add(targetId);
  17865. }
  17866. removeQueryTarget(targetId) {
  17867. this.activeTargetIds = this.activeTargetIds.delete(targetId);
  17868. }
  17869. /**
  17870. * Converts this entry into a JSON-encoded format we can use for WebStorage.
  17871. * Does not encode `clientId` as it is part of the key in WebStorage.
  17872. */
  17873. toWebStorageJSON() {
  17874. const data = {
  17875. activeTargetIds: this.activeTargetIds.toArray(),
  17876. updateTimeMs: Date.now() // Modify the existing value to trigger update.
  17877. };
  17878. return JSON.stringify(data);
  17879. }
  17880. }
  17881. /**
  17882. * `WebStorageSharedClientState` uses WebStorage (window.localStorage) as the
  17883. * backing store for the SharedClientState. It keeps track of all active
  17884. * clients and supports modifications of the local client's data.
  17885. */
  17886. class WebStorageSharedClientState {
  17887. constructor(window, queue, persistenceKey, localClientId, initialUser) {
  17888. this.window = window;
  17889. this.queue = queue;
  17890. this.persistenceKey = persistenceKey;
  17891. this.localClientId = localClientId;
  17892. this.syncEngine = null;
  17893. this.onlineStateHandler = null;
  17894. this.sequenceNumberHandler = null;
  17895. this.storageListener = this.handleWebStorageEvent.bind(this);
  17896. this.activeClients = new SortedMap(primitiveComparator);
  17897. this.started = false;
  17898. /**
  17899. * Captures WebStorage events that occur before `start()` is called. These
  17900. * events are replayed once `WebStorageSharedClientState` is started.
  17901. */
  17902. this.earlyEvents = [];
  17903. // Escape the special characters mentioned here:
  17904. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
  17905. const escapedPersistenceKey = persistenceKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  17906. this.storage = this.window.localStorage;
  17907. this.currentUser = initialUser;
  17908. this.localClientStorageKey = createWebStorageClientStateKey(this.persistenceKey, this.localClientId);
  17909. this.sequenceNumberKey = createWebStorageSequenceNumberKey(this.persistenceKey);
  17910. this.activeClients = this.activeClients.insert(this.localClientId, new LocalClientState());
  17911. this.clientStateKeyRe = new RegExp(`^${CLIENT_STATE_KEY_PREFIX}_${escapedPersistenceKey}_([^_]*)$`);
  17912. this.mutationBatchKeyRe = new RegExp(`^${MUTATION_BATCH_KEY_PREFIX}_${escapedPersistenceKey}_(\\d+)(?:_(.*))?$`);
  17913. this.queryTargetKeyRe = new RegExp(`^${QUERY_TARGET_KEY_PREFIX}_${escapedPersistenceKey}_(\\d+)$`);
  17914. this.onlineStateKey = createWebStorageOnlineStateKey(this.persistenceKey);
  17915. this.bundleLoadedKey = createBundleLoadedKey(this.persistenceKey);
  17916. // Rather than adding the storage observer during start(), we add the
  17917. // storage observer during initialization. This ensures that we collect
  17918. // events before other components populate their initial state (during their
  17919. // respective start() calls). Otherwise, we might for example miss a
  17920. // mutation that is added after LocalStore's start() processed the existing
  17921. // mutations but before we observe WebStorage events.
  17922. this.window.addEventListener('storage', this.storageListener);
  17923. }
  17924. /** Returns 'true' if WebStorage is available in the current environment. */
  17925. static isAvailable(window) {
  17926. return !!(window && window.localStorage);
  17927. }
  17928. async start() {
  17929. // Retrieve the list of existing clients to backfill the data in
  17930. // SharedClientState.
  17931. const existingClients = await this.syncEngine.getActiveClients();
  17932. for (const clientId of existingClients) {
  17933. if (clientId === this.localClientId) {
  17934. continue;
  17935. }
  17936. const storageItem = this.getItem(createWebStorageClientStateKey(this.persistenceKey, clientId));
  17937. if (storageItem) {
  17938. const clientState = RemoteClientState.fromWebStorageEntry(clientId, storageItem);
  17939. if (clientState) {
  17940. this.activeClients = this.activeClients.insert(clientState.clientId, clientState);
  17941. }
  17942. }
  17943. }
  17944. this.persistClientState();
  17945. // Check if there is an existing online state and call the callback handler
  17946. // if applicable.
  17947. const onlineStateJSON = this.storage.getItem(this.onlineStateKey);
  17948. if (onlineStateJSON) {
  17949. const onlineState = this.fromWebStorageOnlineState(onlineStateJSON);
  17950. if (onlineState) {
  17951. this.handleOnlineStateEvent(onlineState);
  17952. }
  17953. }
  17954. for (const event of this.earlyEvents) {
  17955. this.handleWebStorageEvent(event);
  17956. }
  17957. this.earlyEvents = [];
  17958. // Register a window unload hook to remove the client metadata entry from
  17959. // WebStorage even if `shutdown()` was not called.
  17960. this.window.addEventListener('pagehide', () => this.shutdown());
  17961. this.started = true;
  17962. }
  17963. writeSequenceNumber(sequenceNumber) {
  17964. this.setItem(this.sequenceNumberKey, JSON.stringify(sequenceNumber));
  17965. }
  17966. getAllActiveQueryTargets() {
  17967. return this.extractActiveQueryTargets(this.activeClients);
  17968. }
  17969. isActiveQueryTarget(targetId) {
  17970. let found = false;
  17971. this.activeClients.forEach((key, value) => {
  17972. if (value.activeTargetIds.has(targetId)) {
  17973. found = true;
  17974. }
  17975. });
  17976. return found;
  17977. }
  17978. addPendingMutation(batchId) {
  17979. this.persistMutationState(batchId, 'pending');
  17980. }
  17981. updateMutationState(batchId, state, error) {
  17982. this.persistMutationState(batchId, state, error);
  17983. // Once a final mutation result is observed by other clients, they no longer
  17984. // access the mutation's metadata entry. Since WebStorage replays events
  17985. // in order, it is safe to delete the entry right after updating it.
  17986. this.removeMutationState(batchId);
  17987. }
  17988. addLocalQueryTarget(targetId) {
  17989. let queryState = 'not-current';
  17990. // Lookup an existing query state if the target ID was already registered
  17991. // by another tab
  17992. if (this.isActiveQueryTarget(targetId)) {
  17993. const storageItem = this.storage.getItem(createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId));
  17994. if (storageItem) {
  17995. const metadata = QueryTargetMetadata.fromWebStorageEntry(targetId, storageItem);
  17996. if (metadata) {
  17997. queryState = metadata.state;
  17998. }
  17999. }
  18000. }
  18001. this.localClientState.addQueryTarget(targetId);
  18002. this.persistClientState();
  18003. return queryState;
  18004. }
  18005. removeLocalQueryTarget(targetId) {
  18006. this.localClientState.removeQueryTarget(targetId);
  18007. this.persistClientState();
  18008. }
  18009. isLocalQueryTarget(targetId) {
  18010. return this.localClientState.activeTargetIds.has(targetId);
  18011. }
  18012. clearQueryState(targetId) {
  18013. this.removeItem(createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId));
  18014. }
  18015. updateQueryState(targetId, state, error) {
  18016. this.persistQueryTargetState(targetId, state, error);
  18017. }
  18018. handleUserChange(user, removedBatchIds, addedBatchIds) {
  18019. removedBatchIds.forEach(batchId => {
  18020. this.removeMutationState(batchId);
  18021. });
  18022. this.currentUser = user;
  18023. addedBatchIds.forEach(batchId => {
  18024. this.addPendingMutation(batchId);
  18025. });
  18026. }
  18027. setOnlineState(onlineState) {
  18028. this.persistOnlineState(onlineState);
  18029. }
  18030. notifyBundleLoaded(collectionGroups) {
  18031. this.persistBundleLoadedState(collectionGroups);
  18032. }
  18033. shutdown() {
  18034. if (this.started) {
  18035. this.window.removeEventListener('storage', this.storageListener);
  18036. this.removeItem(this.localClientStorageKey);
  18037. this.started = false;
  18038. }
  18039. }
  18040. getItem(key) {
  18041. const value = this.storage.getItem(key);
  18042. logDebug(LOG_TAG$a, 'READ', key, value);
  18043. return value;
  18044. }
  18045. setItem(key, value) {
  18046. logDebug(LOG_TAG$a, 'SET', key, value);
  18047. this.storage.setItem(key, value);
  18048. }
  18049. removeItem(key) {
  18050. logDebug(LOG_TAG$a, 'REMOVE', key);
  18051. this.storage.removeItem(key);
  18052. }
  18053. handleWebStorageEvent(event) {
  18054. // Note: The function is typed to take Event to be interface-compatible with
  18055. // `Window.addEventListener`.
  18056. const storageEvent = event;
  18057. if (storageEvent.storageArea === this.storage) {
  18058. logDebug(LOG_TAG$a, 'EVENT', storageEvent.key, storageEvent.newValue);
  18059. if (storageEvent.key === this.localClientStorageKey) {
  18060. logError('Received WebStorage notification for local change. Another client might have ' +
  18061. 'garbage-collected our state');
  18062. return;
  18063. }
  18064. this.queue.enqueueRetryable(async () => {
  18065. if (!this.started) {
  18066. this.earlyEvents.push(storageEvent);
  18067. return;
  18068. }
  18069. if (storageEvent.key === null) {
  18070. return;
  18071. }
  18072. if (this.clientStateKeyRe.test(storageEvent.key)) {
  18073. if (storageEvent.newValue != null) {
  18074. const clientState = this.fromWebStorageClientState(storageEvent.key, storageEvent.newValue);
  18075. if (clientState) {
  18076. return this.handleClientStateEvent(clientState.clientId, clientState);
  18077. }
  18078. }
  18079. else {
  18080. const clientId = this.fromWebStorageClientStateKey(storageEvent.key);
  18081. return this.handleClientStateEvent(clientId, null);
  18082. }
  18083. }
  18084. else if (this.mutationBatchKeyRe.test(storageEvent.key)) {
  18085. if (storageEvent.newValue !== null) {
  18086. const mutationMetadata = this.fromWebStorageMutationMetadata(storageEvent.key, storageEvent.newValue);
  18087. if (mutationMetadata) {
  18088. return this.handleMutationBatchEvent(mutationMetadata);
  18089. }
  18090. }
  18091. }
  18092. else if (this.queryTargetKeyRe.test(storageEvent.key)) {
  18093. if (storageEvent.newValue !== null) {
  18094. const queryTargetMetadata = this.fromWebStorageQueryTargetMetadata(storageEvent.key, storageEvent.newValue);
  18095. if (queryTargetMetadata) {
  18096. return this.handleQueryTargetEvent(queryTargetMetadata);
  18097. }
  18098. }
  18099. }
  18100. else if (storageEvent.key === this.onlineStateKey) {
  18101. if (storageEvent.newValue !== null) {
  18102. const onlineState = this.fromWebStorageOnlineState(storageEvent.newValue);
  18103. if (onlineState) {
  18104. return this.handleOnlineStateEvent(onlineState);
  18105. }
  18106. }
  18107. }
  18108. else if (storageEvent.key === this.sequenceNumberKey) {
  18109. const sequenceNumber = fromWebStorageSequenceNumber(storageEvent.newValue);
  18110. if (sequenceNumber !== ListenSequence.INVALID) {
  18111. this.sequenceNumberHandler(sequenceNumber);
  18112. }
  18113. }
  18114. else if (storageEvent.key === this.bundleLoadedKey) {
  18115. const collectionGroups = this.fromWebStoreBundleLoadedState(storageEvent.newValue);
  18116. await Promise.all(collectionGroups.map(cg => this.syncEngine.synchronizeWithChangedDocuments(cg)));
  18117. }
  18118. });
  18119. }
  18120. }
  18121. get localClientState() {
  18122. return this.activeClients.get(this.localClientId);
  18123. }
  18124. persistClientState() {
  18125. this.setItem(this.localClientStorageKey, this.localClientState.toWebStorageJSON());
  18126. }
  18127. persistMutationState(batchId, state, error) {
  18128. const mutationState = new MutationMetadata(this.currentUser, batchId, state, error);
  18129. const mutationKey = createWebStorageMutationBatchKey(this.persistenceKey, this.currentUser, batchId);
  18130. this.setItem(mutationKey, mutationState.toWebStorageJSON());
  18131. }
  18132. removeMutationState(batchId) {
  18133. const mutationKey = createWebStorageMutationBatchKey(this.persistenceKey, this.currentUser, batchId);
  18134. this.removeItem(mutationKey);
  18135. }
  18136. persistOnlineState(onlineState) {
  18137. const entry = {
  18138. clientId: this.localClientId,
  18139. onlineState
  18140. };
  18141. this.storage.setItem(this.onlineStateKey, JSON.stringify(entry));
  18142. }
  18143. persistQueryTargetState(targetId, state, error) {
  18144. const targetKey = createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId);
  18145. const targetMetadata = new QueryTargetMetadata(targetId, state, error);
  18146. this.setItem(targetKey, targetMetadata.toWebStorageJSON());
  18147. }
  18148. persistBundleLoadedState(collectionGroups) {
  18149. const json = JSON.stringify(Array.from(collectionGroups));
  18150. this.setItem(this.bundleLoadedKey, json);
  18151. }
  18152. /**
  18153. * Parses a client state key in WebStorage. Returns null if the key does not
  18154. * match the expected key format.
  18155. */
  18156. fromWebStorageClientStateKey(key) {
  18157. const match = this.clientStateKeyRe.exec(key);
  18158. return match ? match[1] : null;
  18159. }
  18160. /**
  18161. * Parses a client state in WebStorage. Returns 'null' if the value could not
  18162. * be parsed.
  18163. */
  18164. fromWebStorageClientState(key, value) {
  18165. const clientId = this.fromWebStorageClientStateKey(key);
  18166. return RemoteClientState.fromWebStorageEntry(clientId, value);
  18167. }
  18168. /**
  18169. * Parses a mutation batch state in WebStorage. Returns 'null' if the value
  18170. * could not be parsed.
  18171. */
  18172. fromWebStorageMutationMetadata(key, value) {
  18173. const match = this.mutationBatchKeyRe.exec(key);
  18174. const batchId = Number(match[1]);
  18175. const userId = match[2] !== undefined ? match[2] : null;
  18176. return MutationMetadata.fromWebStorageEntry(new User(userId), batchId, value);
  18177. }
  18178. /**
  18179. * Parses a query target state from WebStorage. Returns 'null' if the value
  18180. * could not be parsed.
  18181. */
  18182. fromWebStorageQueryTargetMetadata(key, value) {
  18183. const match = this.queryTargetKeyRe.exec(key);
  18184. const targetId = Number(match[1]);
  18185. return QueryTargetMetadata.fromWebStorageEntry(targetId, value);
  18186. }
  18187. /**
  18188. * Parses an online state from WebStorage. Returns 'null' if the value
  18189. * could not be parsed.
  18190. */
  18191. fromWebStorageOnlineState(value) {
  18192. return SharedOnlineState.fromWebStorageEntry(value);
  18193. }
  18194. fromWebStoreBundleLoadedState(value) {
  18195. return JSON.parse(value);
  18196. }
  18197. async handleMutationBatchEvent(mutationBatch) {
  18198. if (mutationBatch.user.uid !== this.currentUser.uid) {
  18199. logDebug(LOG_TAG$a, `Ignoring mutation for non-active user ${mutationBatch.user.uid}`);
  18200. return;
  18201. }
  18202. return this.syncEngine.applyBatchState(mutationBatch.batchId, mutationBatch.state, mutationBatch.error);
  18203. }
  18204. handleQueryTargetEvent(targetMetadata) {
  18205. return this.syncEngine.applyTargetState(targetMetadata.targetId, targetMetadata.state, targetMetadata.error);
  18206. }
  18207. handleClientStateEvent(clientId, clientState) {
  18208. const updatedClients = clientState
  18209. ? this.activeClients.insert(clientId, clientState)
  18210. : this.activeClients.remove(clientId);
  18211. const existingTargets = this.extractActiveQueryTargets(this.activeClients);
  18212. const newTargets = this.extractActiveQueryTargets(updatedClients);
  18213. const addedTargets = [];
  18214. const removedTargets = [];
  18215. newTargets.forEach(targetId => {
  18216. if (!existingTargets.has(targetId)) {
  18217. addedTargets.push(targetId);
  18218. }
  18219. });
  18220. existingTargets.forEach(targetId => {
  18221. if (!newTargets.has(targetId)) {
  18222. removedTargets.push(targetId);
  18223. }
  18224. });
  18225. return this.syncEngine.applyActiveTargetsChange(addedTargets, removedTargets).then(() => {
  18226. this.activeClients = updatedClients;
  18227. });
  18228. }
  18229. handleOnlineStateEvent(onlineState) {
  18230. // We check whether the client that wrote this online state is still active
  18231. // by comparing its client ID to the list of clients kept active in
  18232. // IndexedDb. If a client does not update their IndexedDb client state
  18233. // within 5 seconds, it is considered inactive and we don't emit an online
  18234. // state event.
  18235. if (this.activeClients.get(onlineState.clientId)) {
  18236. this.onlineStateHandler(onlineState.onlineState);
  18237. }
  18238. }
  18239. extractActiveQueryTargets(clients) {
  18240. let activeTargets = targetIdSet();
  18241. clients.forEach((kev, value) => {
  18242. activeTargets = activeTargets.unionWith(value.activeTargetIds);
  18243. });
  18244. return activeTargets;
  18245. }
  18246. }
  18247. function fromWebStorageSequenceNumber(seqString) {
  18248. let sequenceNumber = ListenSequence.INVALID;
  18249. if (seqString != null) {
  18250. try {
  18251. const parsed = JSON.parse(seqString);
  18252. hardAssert(typeof parsed === 'number');
  18253. sequenceNumber = parsed;
  18254. }
  18255. catch (e) {
  18256. logError(LOG_TAG$a, 'Failed to read sequence number from WebStorage', e);
  18257. }
  18258. }
  18259. return sequenceNumber;
  18260. }
  18261. /**
  18262. * `MemorySharedClientState` is a simple implementation of SharedClientState for
  18263. * clients using memory persistence. The state in this class remains fully
  18264. * isolated and no synchronization is performed.
  18265. */
  18266. class MemorySharedClientState {
  18267. constructor() {
  18268. this.localState = new LocalClientState();
  18269. this.queryState = {};
  18270. this.onlineStateHandler = null;
  18271. this.sequenceNumberHandler = null;
  18272. }
  18273. addPendingMutation(batchId) {
  18274. // No op.
  18275. }
  18276. updateMutationState(batchId, state, error) {
  18277. // No op.
  18278. }
  18279. addLocalQueryTarget(targetId) {
  18280. this.localState.addQueryTarget(targetId);
  18281. return this.queryState[targetId] || 'not-current';
  18282. }
  18283. updateQueryState(targetId, state, error) {
  18284. this.queryState[targetId] = state;
  18285. }
  18286. removeLocalQueryTarget(targetId) {
  18287. this.localState.removeQueryTarget(targetId);
  18288. }
  18289. isLocalQueryTarget(targetId) {
  18290. return this.localState.activeTargetIds.has(targetId);
  18291. }
  18292. clearQueryState(targetId) {
  18293. delete this.queryState[targetId];
  18294. }
  18295. getAllActiveQueryTargets() {
  18296. return this.localState.activeTargetIds;
  18297. }
  18298. isActiveQueryTarget(targetId) {
  18299. return this.localState.activeTargetIds.has(targetId);
  18300. }
  18301. start() {
  18302. this.localState = new LocalClientState();
  18303. return Promise.resolve();
  18304. }
  18305. handleUserChange(user, removedBatchIds, addedBatchIds) {
  18306. // No op.
  18307. }
  18308. setOnlineState(onlineState) {
  18309. // No op.
  18310. }
  18311. shutdown() { }
  18312. writeSequenceNumber(sequenceNumber) { }
  18313. notifyBundleLoaded(collectionGroups) {
  18314. // No op.
  18315. }
  18316. }
  18317. /**
  18318. * @license
  18319. * Copyright 2019 Google LLC
  18320. *
  18321. * Licensed under the Apache License, Version 2.0 (the "License");
  18322. * you may not use this file except in compliance with the License.
  18323. * You may obtain a copy of the License at
  18324. *
  18325. * http://www.apache.org/licenses/LICENSE-2.0
  18326. *
  18327. * Unless required by applicable law or agreed to in writing, software
  18328. * distributed under the License is distributed on an "AS IS" BASIS,
  18329. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18330. * See the License for the specific language governing permissions and
  18331. * limitations under the License.
  18332. */
  18333. class NoopConnectivityMonitor {
  18334. addCallback(callback) {
  18335. // No-op.
  18336. }
  18337. shutdown() {
  18338. // No-op.
  18339. }
  18340. }
  18341. /**
  18342. * @license
  18343. * Copyright 2017 Google LLC
  18344. *
  18345. * Licensed under the Apache License, Version 2.0 (the "License");
  18346. * you may not use this file except in compliance with the License.
  18347. * You may obtain a copy of the License at
  18348. *
  18349. * http://www.apache.org/licenses/LICENSE-2.0
  18350. *
  18351. * Unless required by applicable law or agreed to in writing, software
  18352. * distributed under the License is distributed on an "AS IS" BASIS,
  18353. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18354. * See the License for the specific language governing permissions and
  18355. * limitations under the License.
  18356. */
  18357. /**
  18358. * Provides a simple helper class that implements the Stream interface to
  18359. * bridge to other implementations that are streams but do not implement the
  18360. * interface. The stream callbacks are invoked with the callOn... methods.
  18361. */
  18362. class StreamBridge {
  18363. constructor(args) {
  18364. this.sendFn = args.sendFn;
  18365. this.closeFn = args.closeFn;
  18366. }
  18367. onOpen(callback) {
  18368. this.wrappedOnOpen = callback;
  18369. }
  18370. onClose(callback) {
  18371. this.wrappedOnClose = callback;
  18372. }
  18373. onMessage(callback) {
  18374. this.wrappedOnMessage = callback;
  18375. }
  18376. close() {
  18377. this.closeFn();
  18378. }
  18379. send(msg) {
  18380. this.sendFn(msg);
  18381. }
  18382. callOnOpen() {
  18383. this.wrappedOnOpen();
  18384. }
  18385. callOnClose(err) {
  18386. this.wrappedOnClose(err);
  18387. }
  18388. callOnMessage(msg) {
  18389. this.wrappedOnMessage(msg);
  18390. }
  18391. }
  18392. /**
  18393. * @license
  18394. * Copyright 2023 Google LLC
  18395. *
  18396. * Licensed under the Apache License, Version 2.0 (the "License");
  18397. * you may not use this file except in compliance with the License.
  18398. * You may obtain a copy of the License at
  18399. *
  18400. * http://www.apache.org/licenses/LICENSE-2.0
  18401. *
  18402. * Unless required by applicable law or agreed to in writing, software
  18403. * distributed under the License is distributed on an "AS IS" BASIS,
  18404. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18405. * See the License for the specific language governing permissions and
  18406. * limitations under the License.
  18407. */
  18408. /**
  18409. * The value returned from the most recent invocation of
  18410. * `generateUniqueDebugId()`, or null if it has never been invoked.
  18411. */
  18412. let lastUniqueDebugId = null;
  18413. /**
  18414. * Generates and returns an initial value for `lastUniqueDebugId`.
  18415. *
  18416. * The returned value is randomly selected from a range of integers that are
  18417. * represented as 8 hexadecimal digits. This means that (within reason) any
  18418. * numbers generated by incrementing the returned number by 1 will also be
  18419. * represented by 8 hexadecimal digits. This leads to all "IDs" having the same
  18420. * length when converted to a hexadecimal string, making reading logs containing
  18421. * these IDs easier to follow. And since the return value is randomly selected
  18422. * it will help to differentiate between logs from different executions.
  18423. */
  18424. function generateInitialUniqueDebugId() {
  18425. const minResult = 0x10000000;
  18426. const maxResult = 0x90000000;
  18427. const resultRange = maxResult - minResult;
  18428. const resultOffset = Math.round(resultRange * Math.random());
  18429. return minResult + resultOffset;
  18430. }
  18431. /**
  18432. * Generates and returns a unique ID as a hexadecimal string.
  18433. *
  18434. * The returned ID is intended to be used in debug logging messages to help
  18435. * correlate log messages that may be spatially separated in the logs, but
  18436. * logically related. For example, a network connection could include the same
  18437. * "debug ID" string in all of its log messages to help trace a specific
  18438. * connection over time.
  18439. *
  18440. * @return the 10-character generated ID (e.g. "0xa1b2c3d4").
  18441. */
  18442. function generateUniqueDebugId() {
  18443. if (lastUniqueDebugId === null) {
  18444. lastUniqueDebugId = generateInitialUniqueDebugId();
  18445. }
  18446. else {
  18447. lastUniqueDebugId++;
  18448. }
  18449. return '0x' + lastUniqueDebugId.toString(16);
  18450. }
  18451. /**
  18452. * @license
  18453. * Copyright 2017 Google LLC
  18454. *
  18455. * Licensed under the Apache License, Version 2.0 (the "License");
  18456. * you may not use this file except in compliance with the License.
  18457. * You may obtain a copy of the License at
  18458. *
  18459. * http://www.apache.org/licenses/LICENSE-2.0
  18460. *
  18461. * Unless required by applicable law or agreed to in writing, software
  18462. * distributed under the License is distributed on an "AS IS" BASIS,
  18463. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18464. * See the License for the specific language governing permissions and
  18465. * limitations under the License.
  18466. */
  18467. /*
  18468. * Utilities for dealing with node.js-style APIs. See nodePromise for more
  18469. * details.
  18470. */
  18471. /**
  18472. * Creates a node-style callback that resolves or rejects a new Promise. The
  18473. * callback is passed to the given action which can then use the callback as
  18474. * a parameter to a node-style function.
  18475. *
  18476. * The intent is to directly bridge a node-style function (which takes a
  18477. * callback) into a Promise without manually converting between the node-style
  18478. * callback and the promise at each call.
  18479. *
  18480. * In effect it allows you to convert:
  18481. *
  18482. * @example
  18483. * new Promise((resolve: (value?: fs.Stats) => void,
  18484. * reject: (error?: any) => void) => {
  18485. * fs.stat(path, (error?: any, stat?: fs.Stats) => {
  18486. * if (error) {
  18487. * reject(error);
  18488. * } else {
  18489. * resolve(stat);
  18490. * }
  18491. * });
  18492. * });
  18493. *
  18494. * Into
  18495. * @example
  18496. * nodePromise((callback: NodeCallback<fs.Stats>) => {
  18497. * fs.stat(path, callback);
  18498. * });
  18499. *
  18500. * @param action - a function that takes a node-style callback as an argument
  18501. * and then uses that callback to invoke some node-style API.
  18502. * @returns a new Promise which will be rejected if the callback is given the
  18503. * first Error parameter or will resolve to the value given otherwise.
  18504. */
  18505. function nodePromise(action) {
  18506. return new Promise((resolve, reject) => {
  18507. action((error, value) => {
  18508. if (error) {
  18509. reject(error);
  18510. }
  18511. else {
  18512. resolve(value);
  18513. }
  18514. });
  18515. });
  18516. }
  18517. /**
  18518. * @license
  18519. * Copyright 2017 Google LLC
  18520. *
  18521. * Licensed under the Apache License, Version 2.0 (the "License");
  18522. * you may not use this file except in compliance with the License.
  18523. * You may obtain a copy of the License at
  18524. *
  18525. * http://www.apache.org/licenses/LICENSE-2.0
  18526. *
  18527. * Unless required by applicable law or agreed to in writing, software
  18528. * distributed under the License is distributed on an "AS IS" BASIS,
  18529. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18530. * See the License for the specific language governing permissions and
  18531. * limitations under the License.
  18532. */
  18533. // TODO: Fetch runtime version from grpc-js/package.json instead
  18534. // when there's a cleaner way to dynamic require JSON in both Node ESM and CJS
  18535. const grpcVersion = '1.7.3';
  18536. const LOG_TAG$9 = 'GrpcConnection';
  18537. const X_GOOG_API_CLIENT_VALUE = `gl-node/${process.versions.node} fire/${SDK_VERSION} grpc/${grpcVersion}`;
  18538. function createMetadata(databasePath, authToken, appCheckToken, appId) {
  18539. hardAssert(authToken === null || authToken.type === 'OAuth');
  18540. const metadata = new grpc__namespace.Metadata();
  18541. if (authToken) {
  18542. authToken.headers.forEach((value, key) => metadata.set(key, value));
  18543. }
  18544. if (appCheckToken) {
  18545. appCheckToken.headers.forEach((value, key) => metadata.set(key, value));
  18546. }
  18547. if (appId) {
  18548. metadata.set('X-Firebase-GMPID', appId);
  18549. }
  18550. metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
  18551. // These headers are used to improve routing and project isolation by the
  18552. // backend.
  18553. // TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
  18554. // released with cl/428820046. Currently blocked because Emulators are now built with Java
  18555. // 11 from Google3.
  18556. metadata.set('Google-Cloud-Resource-Prefix', databasePath);
  18557. metadata.set('x-goog-request-params', databasePath);
  18558. return metadata;
  18559. }
  18560. /**
  18561. * A Connection implemented by GRPC-Node.
  18562. */
  18563. class GrpcConnection {
  18564. constructor(protos, databaseInfo) {
  18565. this.databaseInfo = databaseInfo;
  18566. // We cache stubs for the most-recently-used token.
  18567. this.cachedStub = null;
  18568. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  18569. this.firestore = protos['google']['firestore']['v1'];
  18570. this.databasePath = `projects/${databaseInfo.databaseId.projectId}/databases/${databaseInfo.databaseId.database}`;
  18571. }
  18572. get shouldResourcePathBeIncludedInRequest() {
  18573. // Both `invokeRPC()` and `invokeStreamingRPC()` ignore their `path` arguments, and expect
  18574. // the "path" to be part of the given `request`.
  18575. return true;
  18576. }
  18577. ensureActiveStub() {
  18578. if (!this.cachedStub) {
  18579. logDebug(LOG_TAG$9, 'Creating Firestore stub.');
  18580. const credentials = this.databaseInfo.ssl
  18581. ? grpc__namespace.credentials.createSsl()
  18582. : grpc__namespace.credentials.createInsecure();
  18583. this.cachedStub = new this.firestore.Firestore(this.databaseInfo.host, credentials);
  18584. }
  18585. return this.cachedStub;
  18586. }
  18587. invokeRPC(rpcName, path, request, authToken, appCheckToken) {
  18588. const streamId = generateUniqueDebugId();
  18589. const stub = this.ensureActiveStub();
  18590. const metadata = createMetadata(this.databasePath, authToken, appCheckToken, this.databaseInfo.appId);
  18591. const jsonRequest = Object.assign({ database: this.databasePath }, request);
  18592. return nodePromise((callback) => {
  18593. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} invoked with request:`, request);
  18594. return stub[rpcName](jsonRequest, metadata, (grpcError, value) => {
  18595. if (grpcError) {
  18596. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} failed with error:`, grpcError);
  18597. callback(new FirestoreError(mapCodeFromRpcCode(grpcError.code), grpcError.message));
  18598. }
  18599. else {
  18600. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} completed with response:`, value);
  18601. callback(undefined, value);
  18602. }
  18603. });
  18604. });
  18605. }
  18606. invokeStreamingRPC(rpcName, path, request, authToken, appCheckToken, expectedResponseCount) {
  18607. const streamId = generateUniqueDebugId();
  18608. const results = [];
  18609. const responseDeferred = new Deferred();
  18610. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} invoked (streaming) with request:`, request);
  18611. const stub = this.ensureActiveStub();
  18612. const metadata = createMetadata(this.databasePath, authToken, appCheckToken, this.databaseInfo.appId);
  18613. const jsonRequest = Object.assign(Object.assign({}, request), { database: this.databasePath });
  18614. const stream = stub[rpcName](jsonRequest, metadata);
  18615. let callbackFired = false;
  18616. stream.on('data', (response) => {
  18617. logDebug(LOG_TAG$9, `RPC ${rpcName} ${streamId} received result:`, response);
  18618. results.push(response);
  18619. if (expectedResponseCount !== undefined &&
  18620. results.length === expectedResponseCount) {
  18621. callbackFired = true;
  18622. responseDeferred.resolve(results);
  18623. }
  18624. });
  18625. stream.on('end', () => {
  18626. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} completed.`);
  18627. if (!callbackFired) {
  18628. callbackFired = true;
  18629. responseDeferred.resolve(results);
  18630. }
  18631. });
  18632. stream.on('error', (grpcError) => {
  18633. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} failed with error:`, grpcError);
  18634. const code = mapCodeFromRpcCode(grpcError.code);
  18635. responseDeferred.reject(new FirestoreError(code, grpcError.message));
  18636. });
  18637. return responseDeferred.promise;
  18638. }
  18639. // TODO(mikelehen): This "method" is a monster. Should be refactored.
  18640. openStream(rpcName, authToken, appCheckToken) {
  18641. const streamId = generateUniqueDebugId();
  18642. const stub = this.ensureActiveStub();
  18643. const metadata = createMetadata(this.databasePath, authToken, appCheckToken, this.databaseInfo.appId);
  18644. const grpcStream = stub[rpcName](metadata);
  18645. let closed = false;
  18646. const close = (err) => {
  18647. if (!closed) {
  18648. closed = true;
  18649. stream.callOnClose(err);
  18650. grpcStream.end();
  18651. }
  18652. };
  18653. const stream = new StreamBridge({
  18654. sendFn: (msg) => {
  18655. if (!closed) {
  18656. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} sending:`, msg);
  18657. try {
  18658. grpcStream.write(msg);
  18659. }
  18660. catch (e) {
  18661. // This probably means we didn't conform to the proto. Make sure to
  18662. // log the message we sent.
  18663. logError('Failure sending:', msg);
  18664. logError('Error:', e);
  18665. throw e;
  18666. }
  18667. }
  18668. else {
  18669. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} ` +
  18670. 'not sending because gRPC stream is closed:', msg);
  18671. }
  18672. },
  18673. closeFn: () => {
  18674. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} closed locally via close().`);
  18675. close();
  18676. }
  18677. });
  18678. grpcStream.on('data', (msg) => {
  18679. if (!closed) {
  18680. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} received:`, msg);
  18681. stream.callOnMessage(msg);
  18682. }
  18683. });
  18684. grpcStream.on('end', () => {
  18685. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} ended.`);
  18686. close();
  18687. });
  18688. grpcStream.on('error', (grpcError) => {
  18689. if (!closed) {
  18690. logWarn(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} error. Code:`, grpcError.code, 'Message:', grpcError.message);
  18691. const code = mapCodeFromRpcCode(grpcError.code);
  18692. close(new FirestoreError(code, grpcError.message));
  18693. }
  18694. });
  18695. logDebug(LOG_TAG$9, `Opening RPC '${rpcName}' stream ${streamId} ` +
  18696. `to ${this.databaseInfo.host}`);
  18697. // TODO(dimond): Since grpc has no explicit open status (or does it?) we
  18698. // simulate an onOpen in the next loop after the stream had it's listeners
  18699. // registered
  18700. setTimeout(() => {
  18701. stream.callOnOpen();
  18702. }, 0);
  18703. return stream;
  18704. }
  18705. }
  18706. const nested = {
  18707. google: {
  18708. nested: {
  18709. protobuf: {
  18710. options: {
  18711. csharp_namespace: "Google.Protobuf.WellKnownTypes",
  18712. go_package: "github.com/golang/protobuf/ptypes/wrappers",
  18713. java_package: "com.google.protobuf",
  18714. java_outer_classname: "WrappersProto",
  18715. java_multiple_files: true,
  18716. objc_class_prefix: "GPB",
  18717. cc_enable_arenas: true,
  18718. optimize_for: "SPEED"
  18719. },
  18720. nested: {
  18721. Timestamp: {
  18722. fields: {
  18723. seconds: {
  18724. type: "int64",
  18725. id: 1
  18726. },
  18727. nanos: {
  18728. type: "int32",
  18729. id: 2
  18730. }
  18731. }
  18732. },
  18733. FileDescriptorSet: {
  18734. fields: {
  18735. file: {
  18736. rule: "repeated",
  18737. type: "FileDescriptorProto",
  18738. id: 1
  18739. }
  18740. }
  18741. },
  18742. FileDescriptorProto: {
  18743. fields: {
  18744. name: {
  18745. type: "string",
  18746. id: 1
  18747. },
  18748. "package": {
  18749. type: "string",
  18750. id: 2
  18751. },
  18752. dependency: {
  18753. rule: "repeated",
  18754. type: "string",
  18755. id: 3
  18756. },
  18757. publicDependency: {
  18758. rule: "repeated",
  18759. type: "int32",
  18760. id: 10,
  18761. options: {
  18762. packed: false
  18763. }
  18764. },
  18765. weakDependency: {
  18766. rule: "repeated",
  18767. type: "int32",
  18768. id: 11,
  18769. options: {
  18770. packed: false
  18771. }
  18772. },
  18773. messageType: {
  18774. rule: "repeated",
  18775. type: "DescriptorProto",
  18776. id: 4
  18777. },
  18778. enumType: {
  18779. rule: "repeated",
  18780. type: "EnumDescriptorProto",
  18781. id: 5
  18782. },
  18783. service: {
  18784. rule: "repeated",
  18785. type: "ServiceDescriptorProto",
  18786. id: 6
  18787. },
  18788. extension: {
  18789. rule: "repeated",
  18790. type: "FieldDescriptorProto",
  18791. id: 7
  18792. },
  18793. options: {
  18794. type: "FileOptions",
  18795. id: 8
  18796. },
  18797. sourceCodeInfo: {
  18798. type: "SourceCodeInfo",
  18799. id: 9
  18800. },
  18801. syntax: {
  18802. type: "string",
  18803. id: 12
  18804. }
  18805. }
  18806. },
  18807. DescriptorProto: {
  18808. fields: {
  18809. name: {
  18810. type: "string",
  18811. id: 1
  18812. },
  18813. field: {
  18814. rule: "repeated",
  18815. type: "FieldDescriptorProto",
  18816. id: 2
  18817. },
  18818. extension: {
  18819. rule: "repeated",
  18820. type: "FieldDescriptorProto",
  18821. id: 6
  18822. },
  18823. nestedType: {
  18824. rule: "repeated",
  18825. type: "DescriptorProto",
  18826. id: 3
  18827. },
  18828. enumType: {
  18829. rule: "repeated",
  18830. type: "EnumDescriptorProto",
  18831. id: 4
  18832. },
  18833. extensionRange: {
  18834. rule: "repeated",
  18835. type: "ExtensionRange",
  18836. id: 5
  18837. },
  18838. oneofDecl: {
  18839. rule: "repeated",
  18840. type: "OneofDescriptorProto",
  18841. id: 8
  18842. },
  18843. options: {
  18844. type: "MessageOptions",
  18845. id: 7
  18846. },
  18847. reservedRange: {
  18848. rule: "repeated",
  18849. type: "ReservedRange",
  18850. id: 9
  18851. },
  18852. reservedName: {
  18853. rule: "repeated",
  18854. type: "string",
  18855. id: 10
  18856. }
  18857. },
  18858. nested: {
  18859. ExtensionRange: {
  18860. fields: {
  18861. start: {
  18862. type: "int32",
  18863. id: 1
  18864. },
  18865. end: {
  18866. type: "int32",
  18867. id: 2
  18868. }
  18869. }
  18870. },
  18871. ReservedRange: {
  18872. fields: {
  18873. start: {
  18874. type: "int32",
  18875. id: 1
  18876. },
  18877. end: {
  18878. type: "int32",
  18879. id: 2
  18880. }
  18881. }
  18882. }
  18883. }
  18884. },
  18885. FieldDescriptorProto: {
  18886. fields: {
  18887. name: {
  18888. type: "string",
  18889. id: 1
  18890. },
  18891. number: {
  18892. type: "int32",
  18893. id: 3
  18894. },
  18895. label: {
  18896. type: "Label",
  18897. id: 4
  18898. },
  18899. type: {
  18900. type: "Type",
  18901. id: 5
  18902. },
  18903. typeName: {
  18904. type: "string",
  18905. id: 6
  18906. },
  18907. extendee: {
  18908. type: "string",
  18909. id: 2
  18910. },
  18911. defaultValue: {
  18912. type: "string",
  18913. id: 7
  18914. },
  18915. oneofIndex: {
  18916. type: "int32",
  18917. id: 9
  18918. },
  18919. jsonName: {
  18920. type: "string",
  18921. id: 10
  18922. },
  18923. options: {
  18924. type: "FieldOptions",
  18925. id: 8
  18926. }
  18927. },
  18928. nested: {
  18929. Type: {
  18930. values: {
  18931. TYPE_DOUBLE: 1,
  18932. TYPE_FLOAT: 2,
  18933. TYPE_INT64: 3,
  18934. TYPE_UINT64: 4,
  18935. TYPE_INT32: 5,
  18936. TYPE_FIXED64: 6,
  18937. TYPE_FIXED32: 7,
  18938. TYPE_BOOL: 8,
  18939. TYPE_STRING: 9,
  18940. TYPE_GROUP: 10,
  18941. TYPE_MESSAGE: 11,
  18942. TYPE_BYTES: 12,
  18943. TYPE_UINT32: 13,
  18944. TYPE_ENUM: 14,
  18945. TYPE_SFIXED32: 15,
  18946. TYPE_SFIXED64: 16,
  18947. TYPE_SINT32: 17,
  18948. TYPE_SINT64: 18
  18949. }
  18950. },
  18951. Label: {
  18952. values: {
  18953. LABEL_OPTIONAL: 1,
  18954. LABEL_REQUIRED: 2,
  18955. LABEL_REPEATED: 3
  18956. }
  18957. }
  18958. }
  18959. },
  18960. OneofDescriptorProto: {
  18961. fields: {
  18962. name: {
  18963. type: "string",
  18964. id: 1
  18965. },
  18966. options: {
  18967. type: "OneofOptions",
  18968. id: 2
  18969. }
  18970. }
  18971. },
  18972. EnumDescriptorProto: {
  18973. fields: {
  18974. name: {
  18975. type: "string",
  18976. id: 1
  18977. },
  18978. value: {
  18979. rule: "repeated",
  18980. type: "EnumValueDescriptorProto",
  18981. id: 2
  18982. },
  18983. options: {
  18984. type: "EnumOptions",
  18985. id: 3
  18986. }
  18987. }
  18988. },
  18989. EnumValueDescriptorProto: {
  18990. fields: {
  18991. name: {
  18992. type: "string",
  18993. id: 1
  18994. },
  18995. number: {
  18996. type: "int32",
  18997. id: 2
  18998. },
  18999. options: {
  19000. type: "EnumValueOptions",
  19001. id: 3
  19002. }
  19003. }
  19004. },
  19005. ServiceDescriptorProto: {
  19006. fields: {
  19007. name: {
  19008. type: "string",
  19009. id: 1
  19010. },
  19011. method: {
  19012. rule: "repeated",
  19013. type: "MethodDescriptorProto",
  19014. id: 2
  19015. },
  19016. options: {
  19017. type: "ServiceOptions",
  19018. id: 3
  19019. }
  19020. }
  19021. },
  19022. MethodDescriptorProto: {
  19023. fields: {
  19024. name: {
  19025. type: "string",
  19026. id: 1
  19027. },
  19028. inputType: {
  19029. type: "string",
  19030. id: 2
  19031. },
  19032. outputType: {
  19033. type: "string",
  19034. id: 3
  19035. },
  19036. options: {
  19037. type: "MethodOptions",
  19038. id: 4
  19039. },
  19040. clientStreaming: {
  19041. type: "bool",
  19042. id: 5
  19043. },
  19044. serverStreaming: {
  19045. type: "bool",
  19046. id: 6
  19047. }
  19048. }
  19049. },
  19050. FileOptions: {
  19051. fields: {
  19052. javaPackage: {
  19053. type: "string",
  19054. id: 1
  19055. },
  19056. javaOuterClassname: {
  19057. type: "string",
  19058. id: 8
  19059. },
  19060. javaMultipleFiles: {
  19061. type: "bool",
  19062. id: 10
  19063. },
  19064. javaGenerateEqualsAndHash: {
  19065. type: "bool",
  19066. id: 20,
  19067. options: {
  19068. deprecated: true
  19069. }
  19070. },
  19071. javaStringCheckUtf8: {
  19072. type: "bool",
  19073. id: 27
  19074. },
  19075. optimizeFor: {
  19076. type: "OptimizeMode",
  19077. id: 9,
  19078. options: {
  19079. "default": "SPEED"
  19080. }
  19081. },
  19082. goPackage: {
  19083. type: "string",
  19084. id: 11
  19085. },
  19086. ccGenericServices: {
  19087. type: "bool",
  19088. id: 16
  19089. },
  19090. javaGenericServices: {
  19091. type: "bool",
  19092. id: 17
  19093. },
  19094. pyGenericServices: {
  19095. type: "bool",
  19096. id: 18
  19097. },
  19098. deprecated: {
  19099. type: "bool",
  19100. id: 23
  19101. },
  19102. ccEnableArenas: {
  19103. type: "bool",
  19104. id: 31
  19105. },
  19106. objcClassPrefix: {
  19107. type: "string",
  19108. id: 36
  19109. },
  19110. csharpNamespace: {
  19111. type: "string",
  19112. id: 37
  19113. },
  19114. uninterpretedOption: {
  19115. rule: "repeated",
  19116. type: "UninterpretedOption",
  19117. id: 999
  19118. }
  19119. },
  19120. extensions: [
  19121. [
  19122. 1000,
  19123. 536870911
  19124. ]
  19125. ],
  19126. reserved: [
  19127. [
  19128. 38,
  19129. 38
  19130. ]
  19131. ],
  19132. nested: {
  19133. OptimizeMode: {
  19134. values: {
  19135. SPEED: 1,
  19136. CODE_SIZE: 2,
  19137. LITE_RUNTIME: 3
  19138. }
  19139. }
  19140. }
  19141. },
  19142. MessageOptions: {
  19143. fields: {
  19144. messageSetWireFormat: {
  19145. type: "bool",
  19146. id: 1
  19147. },
  19148. noStandardDescriptorAccessor: {
  19149. type: "bool",
  19150. id: 2
  19151. },
  19152. deprecated: {
  19153. type: "bool",
  19154. id: 3
  19155. },
  19156. mapEntry: {
  19157. type: "bool",
  19158. id: 7
  19159. },
  19160. uninterpretedOption: {
  19161. rule: "repeated",
  19162. type: "UninterpretedOption",
  19163. id: 999
  19164. }
  19165. },
  19166. extensions: [
  19167. [
  19168. 1000,
  19169. 536870911
  19170. ]
  19171. ],
  19172. reserved: [
  19173. [
  19174. 8,
  19175. 8
  19176. ]
  19177. ]
  19178. },
  19179. FieldOptions: {
  19180. fields: {
  19181. ctype: {
  19182. type: "CType",
  19183. id: 1,
  19184. options: {
  19185. "default": "STRING"
  19186. }
  19187. },
  19188. packed: {
  19189. type: "bool",
  19190. id: 2
  19191. },
  19192. jstype: {
  19193. type: "JSType",
  19194. id: 6,
  19195. options: {
  19196. "default": "JS_NORMAL"
  19197. }
  19198. },
  19199. lazy: {
  19200. type: "bool",
  19201. id: 5
  19202. },
  19203. deprecated: {
  19204. type: "bool",
  19205. id: 3
  19206. },
  19207. weak: {
  19208. type: "bool",
  19209. id: 10
  19210. },
  19211. uninterpretedOption: {
  19212. rule: "repeated",
  19213. type: "UninterpretedOption",
  19214. id: 999
  19215. }
  19216. },
  19217. extensions: [
  19218. [
  19219. 1000,
  19220. 536870911
  19221. ]
  19222. ],
  19223. reserved: [
  19224. [
  19225. 4,
  19226. 4
  19227. ]
  19228. ],
  19229. nested: {
  19230. CType: {
  19231. values: {
  19232. STRING: 0,
  19233. CORD: 1,
  19234. STRING_PIECE: 2
  19235. }
  19236. },
  19237. JSType: {
  19238. values: {
  19239. JS_NORMAL: 0,
  19240. JS_STRING: 1,
  19241. JS_NUMBER: 2
  19242. }
  19243. }
  19244. }
  19245. },
  19246. OneofOptions: {
  19247. fields: {
  19248. uninterpretedOption: {
  19249. rule: "repeated",
  19250. type: "UninterpretedOption",
  19251. id: 999
  19252. }
  19253. },
  19254. extensions: [
  19255. [
  19256. 1000,
  19257. 536870911
  19258. ]
  19259. ]
  19260. },
  19261. EnumOptions: {
  19262. fields: {
  19263. allowAlias: {
  19264. type: "bool",
  19265. id: 2
  19266. },
  19267. deprecated: {
  19268. type: "bool",
  19269. id: 3
  19270. },
  19271. uninterpretedOption: {
  19272. rule: "repeated",
  19273. type: "UninterpretedOption",
  19274. id: 999
  19275. }
  19276. },
  19277. extensions: [
  19278. [
  19279. 1000,
  19280. 536870911
  19281. ]
  19282. ]
  19283. },
  19284. EnumValueOptions: {
  19285. fields: {
  19286. deprecated: {
  19287. type: "bool",
  19288. id: 1
  19289. },
  19290. uninterpretedOption: {
  19291. rule: "repeated",
  19292. type: "UninterpretedOption",
  19293. id: 999
  19294. }
  19295. },
  19296. extensions: [
  19297. [
  19298. 1000,
  19299. 536870911
  19300. ]
  19301. ]
  19302. },
  19303. ServiceOptions: {
  19304. fields: {
  19305. deprecated: {
  19306. type: "bool",
  19307. id: 33
  19308. },
  19309. uninterpretedOption: {
  19310. rule: "repeated",
  19311. type: "UninterpretedOption",
  19312. id: 999
  19313. }
  19314. },
  19315. extensions: [
  19316. [
  19317. 1000,
  19318. 536870911
  19319. ]
  19320. ]
  19321. },
  19322. MethodOptions: {
  19323. fields: {
  19324. deprecated: {
  19325. type: "bool",
  19326. id: 33
  19327. },
  19328. uninterpretedOption: {
  19329. rule: "repeated",
  19330. type: "UninterpretedOption",
  19331. id: 999
  19332. }
  19333. },
  19334. extensions: [
  19335. [
  19336. 1000,
  19337. 536870911
  19338. ]
  19339. ]
  19340. },
  19341. UninterpretedOption: {
  19342. fields: {
  19343. name: {
  19344. rule: "repeated",
  19345. type: "NamePart",
  19346. id: 2
  19347. },
  19348. identifierValue: {
  19349. type: "string",
  19350. id: 3
  19351. },
  19352. positiveIntValue: {
  19353. type: "uint64",
  19354. id: 4
  19355. },
  19356. negativeIntValue: {
  19357. type: "int64",
  19358. id: 5
  19359. },
  19360. doubleValue: {
  19361. type: "double",
  19362. id: 6
  19363. },
  19364. stringValue: {
  19365. type: "bytes",
  19366. id: 7
  19367. },
  19368. aggregateValue: {
  19369. type: "string",
  19370. id: 8
  19371. }
  19372. },
  19373. nested: {
  19374. NamePart: {
  19375. fields: {
  19376. namePart: {
  19377. rule: "required",
  19378. type: "string",
  19379. id: 1
  19380. },
  19381. isExtension: {
  19382. rule: "required",
  19383. type: "bool",
  19384. id: 2
  19385. }
  19386. }
  19387. }
  19388. }
  19389. },
  19390. SourceCodeInfo: {
  19391. fields: {
  19392. location: {
  19393. rule: "repeated",
  19394. type: "Location",
  19395. id: 1
  19396. }
  19397. },
  19398. nested: {
  19399. Location: {
  19400. fields: {
  19401. path: {
  19402. rule: "repeated",
  19403. type: "int32",
  19404. id: 1
  19405. },
  19406. span: {
  19407. rule: "repeated",
  19408. type: "int32",
  19409. id: 2
  19410. },
  19411. leadingComments: {
  19412. type: "string",
  19413. id: 3
  19414. },
  19415. trailingComments: {
  19416. type: "string",
  19417. id: 4
  19418. },
  19419. leadingDetachedComments: {
  19420. rule: "repeated",
  19421. type: "string",
  19422. id: 6
  19423. }
  19424. }
  19425. }
  19426. }
  19427. },
  19428. GeneratedCodeInfo: {
  19429. fields: {
  19430. annotation: {
  19431. rule: "repeated",
  19432. type: "Annotation",
  19433. id: 1
  19434. }
  19435. },
  19436. nested: {
  19437. Annotation: {
  19438. fields: {
  19439. path: {
  19440. rule: "repeated",
  19441. type: "int32",
  19442. id: 1
  19443. },
  19444. sourceFile: {
  19445. type: "string",
  19446. id: 2
  19447. },
  19448. begin: {
  19449. type: "int32",
  19450. id: 3
  19451. },
  19452. end: {
  19453. type: "int32",
  19454. id: 4
  19455. }
  19456. }
  19457. }
  19458. }
  19459. },
  19460. Struct: {
  19461. fields: {
  19462. fields: {
  19463. keyType: "string",
  19464. type: "Value",
  19465. id: 1
  19466. }
  19467. }
  19468. },
  19469. Value: {
  19470. oneofs: {
  19471. kind: {
  19472. oneof: [
  19473. "nullValue",
  19474. "numberValue",
  19475. "stringValue",
  19476. "boolValue",
  19477. "structValue",
  19478. "listValue"
  19479. ]
  19480. }
  19481. },
  19482. fields: {
  19483. nullValue: {
  19484. type: "NullValue",
  19485. id: 1
  19486. },
  19487. numberValue: {
  19488. type: "double",
  19489. id: 2
  19490. },
  19491. stringValue: {
  19492. type: "string",
  19493. id: 3
  19494. },
  19495. boolValue: {
  19496. type: "bool",
  19497. id: 4
  19498. },
  19499. structValue: {
  19500. type: "Struct",
  19501. id: 5
  19502. },
  19503. listValue: {
  19504. type: "ListValue",
  19505. id: 6
  19506. }
  19507. }
  19508. },
  19509. NullValue: {
  19510. values: {
  19511. NULL_VALUE: 0
  19512. }
  19513. },
  19514. ListValue: {
  19515. fields: {
  19516. values: {
  19517. rule: "repeated",
  19518. type: "Value",
  19519. id: 1
  19520. }
  19521. }
  19522. },
  19523. Empty: {
  19524. fields: {
  19525. }
  19526. },
  19527. DoubleValue: {
  19528. fields: {
  19529. value: {
  19530. type: "double",
  19531. id: 1
  19532. }
  19533. }
  19534. },
  19535. FloatValue: {
  19536. fields: {
  19537. value: {
  19538. type: "float",
  19539. id: 1
  19540. }
  19541. }
  19542. },
  19543. Int64Value: {
  19544. fields: {
  19545. value: {
  19546. type: "int64",
  19547. id: 1
  19548. }
  19549. }
  19550. },
  19551. UInt64Value: {
  19552. fields: {
  19553. value: {
  19554. type: "uint64",
  19555. id: 1
  19556. }
  19557. }
  19558. },
  19559. Int32Value: {
  19560. fields: {
  19561. value: {
  19562. type: "int32",
  19563. id: 1
  19564. }
  19565. }
  19566. },
  19567. UInt32Value: {
  19568. fields: {
  19569. value: {
  19570. type: "uint32",
  19571. id: 1
  19572. }
  19573. }
  19574. },
  19575. BoolValue: {
  19576. fields: {
  19577. value: {
  19578. type: "bool",
  19579. id: 1
  19580. }
  19581. }
  19582. },
  19583. StringValue: {
  19584. fields: {
  19585. value: {
  19586. type: "string",
  19587. id: 1
  19588. }
  19589. }
  19590. },
  19591. BytesValue: {
  19592. fields: {
  19593. value: {
  19594. type: "bytes",
  19595. id: 1
  19596. }
  19597. }
  19598. },
  19599. Any: {
  19600. fields: {
  19601. typeUrl: {
  19602. type: "string",
  19603. id: 1
  19604. },
  19605. value: {
  19606. type: "bytes",
  19607. id: 2
  19608. }
  19609. }
  19610. }
  19611. }
  19612. },
  19613. firestore: {
  19614. nested: {
  19615. v1: {
  19616. options: {
  19617. csharp_namespace: "Google.Cloud.Firestore.V1",
  19618. go_package: "google.golang.org/genproto/googleapis/firestore/v1;firestore",
  19619. java_multiple_files: true,
  19620. java_outer_classname: "WriteProto",
  19621. java_package: "com.google.firestore.v1",
  19622. objc_class_prefix: "GCFS",
  19623. php_namespace: "Google\\Cloud\\Firestore\\V1",
  19624. ruby_package: "Google::Cloud::Firestore::V1"
  19625. },
  19626. nested: {
  19627. AggregationResult: {
  19628. fields: {
  19629. aggregateFields: {
  19630. keyType: "string",
  19631. type: "Value",
  19632. id: 2
  19633. }
  19634. }
  19635. },
  19636. BitSequence: {
  19637. fields: {
  19638. bitmap: {
  19639. type: "bytes",
  19640. id: 1
  19641. },
  19642. padding: {
  19643. type: "int32",
  19644. id: 2
  19645. }
  19646. }
  19647. },
  19648. BloomFilter: {
  19649. fields: {
  19650. bits: {
  19651. type: "BitSequence",
  19652. id: 1
  19653. },
  19654. hashCount: {
  19655. type: "int32",
  19656. id: 2
  19657. }
  19658. }
  19659. },
  19660. DocumentMask: {
  19661. fields: {
  19662. fieldPaths: {
  19663. rule: "repeated",
  19664. type: "string",
  19665. id: 1
  19666. }
  19667. }
  19668. },
  19669. Precondition: {
  19670. oneofs: {
  19671. conditionType: {
  19672. oneof: [
  19673. "exists",
  19674. "updateTime"
  19675. ]
  19676. }
  19677. },
  19678. fields: {
  19679. exists: {
  19680. type: "bool",
  19681. id: 1
  19682. },
  19683. updateTime: {
  19684. type: "google.protobuf.Timestamp",
  19685. id: 2
  19686. }
  19687. }
  19688. },
  19689. TransactionOptions: {
  19690. oneofs: {
  19691. mode: {
  19692. oneof: [
  19693. "readOnly",
  19694. "readWrite"
  19695. ]
  19696. }
  19697. },
  19698. fields: {
  19699. readOnly: {
  19700. type: "ReadOnly",
  19701. id: 2
  19702. },
  19703. readWrite: {
  19704. type: "ReadWrite",
  19705. id: 3
  19706. }
  19707. },
  19708. nested: {
  19709. ReadWrite: {
  19710. fields: {
  19711. retryTransaction: {
  19712. type: "bytes",
  19713. id: 1
  19714. }
  19715. }
  19716. },
  19717. ReadOnly: {
  19718. oneofs: {
  19719. consistencySelector: {
  19720. oneof: [
  19721. "readTime"
  19722. ]
  19723. }
  19724. },
  19725. fields: {
  19726. readTime: {
  19727. type: "google.protobuf.Timestamp",
  19728. id: 2
  19729. }
  19730. }
  19731. }
  19732. }
  19733. },
  19734. Document: {
  19735. fields: {
  19736. name: {
  19737. type: "string",
  19738. id: 1
  19739. },
  19740. fields: {
  19741. keyType: "string",
  19742. type: "Value",
  19743. id: 2
  19744. },
  19745. createTime: {
  19746. type: "google.protobuf.Timestamp",
  19747. id: 3
  19748. },
  19749. updateTime: {
  19750. type: "google.protobuf.Timestamp",
  19751. id: 4
  19752. }
  19753. }
  19754. },
  19755. Value: {
  19756. oneofs: {
  19757. valueType: {
  19758. oneof: [
  19759. "nullValue",
  19760. "booleanValue",
  19761. "integerValue",
  19762. "doubleValue",
  19763. "timestampValue",
  19764. "stringValue",
  19765. "bytesValue",
  19766. "referenceValue",
  19767. "geoPointValue",
  19768. "arrayValue",
  19769. "mapValue"
  19770. ]
  19771. }
  19772. },
  19773. fields: {
  19774. nullValue: {
  19775. type: "google.protobuf.NullValue",
  19776. id: 11
  19777. },
  19778. booleanValue: {
  19779. type: "bool",
  19780. id: 1
  19781. },
  19782. integerValue: {
  19783. type: "int64",
  19784. id: 2
  19785. },
  19786. doubleValue: {
  19787. type: "double",
  19788. id: 3
  19789. },
  19790. timestampValue: {
  19791. type: "google.protobuf.Timestamp",
  19792. id: 10
  19793. },
  19794. stringValue: {
  19795. type: "string",
  19796. id: 17
  19797. },
  19798. bytesValue: {
  19799. type: "bytes",
  19800. id: 18
  19801. },
  19802. referenceValue: {
  19803. type: "string",
  19804. id: 5
  19805. },
  19806. geoPointValue: {
  19807. type: "google.type.LatLng",
  19808. id: 8
  19809. },
  19810. arrayValue: {
  19811. type: "ArrayValue",
  19812. id: 9
  19813. },
  19814. mapValue: {
  19815. type: "MapValue",
  19816. id: 6
  19817. }
  19818. }
  19819. },
  19820. ArrayValue: {
  19821. fields: {
  19822. values: {
  19823. rule: "repeated",
  19824. type: "Value",
  19825. id: 1
  19826. }
  19827. }
  19828. },
  19829. MapValue: {
  19830. fields: {
  19831. fields: {
  19832. keyType: "string",
  19833. type: "Value",
  19834. id: 1
  19835. }
  19836. }
  19837. },
  19838. Firestore: {
  19839. options: {
  19840. "(google.api.default_host)": "firestore.googleapis.com",
  19841. "(google.api.oauth_scopes)": "https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/datastore"
  19842. },
  19843. methods: {
  19844. GetDocument: {
  19845. requestType: "GetDocumentRequest",
  19846. responseType: "Document",
  19847. options: {
  19848. "(google.api.http).get": "/v1/{name=projects/*/databases/*/documents/*/**}"
  19849. },
  19850. parsedOptions: [
  19851. {
  19852. "(google.api.http)": {
  19853. get: "/v1/{name=projects/*/databases/*/documents/*/**}"
  19854. }
  19855. }
  19856. ]
  19857. },
  19858. ListDocuments: {
  19859. requestType: "ListDocumentsRequest",
  19860. responseType: "ListDocumentsResponse",
  19861. options: {
  19862. "(google.api.http).get": "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}"
  19863. },
  19864. parsedOptions: [
  19865. {
  19866. "(google.api.http)": {
  19867. get: "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}"
  19868. }
  19869. }
  19870. ]
  19871. },
  19872. UpdateDocument: {
  19873. requestType: "UpdateDocumentRequest",
  19874. responseType: "Document",
  19875. options: {
  19876. "(google.api.http).patch": "/v1/{document.name=projects/*/databases/*/documents/*/**}",
  19877. "(google.api.http).body": "document",
  19878. "(google.api.method_signature)": "document,update_mask"
  19879. },
  19880. parsedOptions: [
  19881. {
  19882. "(google.api.http)": {
  19883. patch: "/v1/{document.name=projects/*/databases/*/documents/*/**}",
  19884. body: "document"
  19885. }
  19886. },
  19887. {
  19888. "(google.api.method_signature)": "document,update_mask"
  19889. }
  19890. ]
  19891. },
  19892. DeleteDocument: {
  19893. requestType: "DeleteDocumentRequest",
  19894. responseType: "google.protobuf.Empty",
  19895. options: {
  19896. "(google.api.http).delete": "/v1/{name=projects/*/databases/*/documents/*/**}",
  19897. "(google.api.method_signature)": "name"
  19898. },
  19899. parsedOptions: [
  19900. {
  19901. "(google.api.http)": {
  19902. "delete": "/v1/{name=projects/*/databases/*/documents/*/**}"
  19903. }
  19904. },
  19905. {
  19906. "(google.api.method_signature)": "name"
  19907. }
  19908. ]
  19909. },
  19910. BatchGetDocuments: {
  19911. requestType: "BatchGetDocumentsRequest",
  19912. responseType: "BatchGetDocumentsResponse",
  19913. responseStream: true,
  19914. options: {
  19915. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:batchGet",
  19916. "(google.api.http).body": "*"
  19917. },
  19918. parsedOptions: [
  19919. {
  19920. "(google.api.http)": {
  19921. post: "/v1/{database=projects/*/databases/*}/documents:batchGet",
  19922. body: "*"
  19923. }
  19924. }
  19925. ]
  19926. },
  19927. BeginTransaction: {
  19928. requestType: "BeginTransactionRequest",
  19929. responseType: "BeginTransactionResponse",
  19930. options: {
  19931. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:beginTransaction",
  19932. "(google.api.http).body": "*",
  19933. "(google.api.method_signature)": "database"
  19934. },
  19935. parsedOptions: [
  19936. {
  19937. "(google.api.http)": {
  19938. post: "/v1/{database=projects/*/databases/*}/documents:beginTransaction",
  19939. body: "*"
  19940. }
  19941. },
  19942. {
  19943. "(google.api.method_signature)": "database"
  19944. }
  19945. ]
  19946. },
  19947. Commit: {
  19948. requestType: "CommitRequest",
  19949. responseType: "CommitResponse",
  19950. options: {
  19951. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:commit",
  19952. "(google.api.http).body": "*",
  19953. "(google.api.method_signature)": "database,writes"
  19954. },
  19955. parsedOptions: [
  19956. {
  19957. "(google.api.http)": {
  19958. post: "/v1/{database=projects/*/databases/*}/documents:commit",
  19959. body: "*"
  19960. }
  19961. },
  19962. {
  19963. "(google.api.method_signature)": "database,writes"
  19964. }
  19965. ]
  19966. },
  19967. Rollback: {
  19968. requestType: "RollbackRequest",
  19969. responseType: "google.protobuf.Empty",
  19970. options: {
  19971. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:rollback",
  19972. "(google.api.http).body": "*",
  19973. "(google.api.method_signature)": "database,transaction"
  19974. },
  19975. parsedOptions: [
  19976. {
  19977. "(google.api.http)": {
  19978. post: "/v1/{database=projects/*/databases/*}/documents:rollback",
  19979. body: "*"
  19980. }
  19981. },
  19982. {
  19983. "(google.api.method_signature)": "database,transaction"
  19984. }
  19985. ]
  19986. },
  19987. RunQuery: {
  19988. requestType: "RunQueryRequest",
  19989. responseType: "RunQueryResponse",
  19990. responseStream: true,
  19991. options: {
  19992. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:runQuery",
  19993. "(google.api.http).body": "*",
  19994. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:runQuery",
  19995. "(google.api.http).additional_bindings.body": "*"
  19996. },
  19997. parsedOptions: [
  19998. {
  19999. "(google.api.http)": {
  20000. post: "/v1/{parent=projects/*/databases/*/documents}:runQuery",
  20001. body: "*",
  20002. additional_bindings: {
  20003. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:runQuery",
  20004. body: "*"
  20005. }
  20006. }
  20007. }
  20008. ]
  20009. },
  20010. RunAggregationQuery: {
  20011. requestType: "RunAggregationQueryRequest",
  20012. responseType: "RunAggregationQueryResponse",
  20013. responseStream: true,
  20014. options: {
  20015. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:runAggregationQuery",
  20016. "(google.api.http).body": "*",
  20017. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:runAggregationQuery",
  20018. "(google.api.http).additional_bindings.body": "*"
  20019. },
  20020. parsedOptions: [
  20021. {
  20022. "(google.api.http)": {
  20023. post: "/v1/{parent=projects/*/databases/*/documents}:runAggregationQuery",
  20024. body: "*",
  20025. additional_bindings: {
  20026. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:runAggregationQuery",
  20027. body: "*"
  20028. }
  20029. }
  20030. }
  20031. ]
  20032. },
  20033. PartitionQuery: {
  20034. requestType: "PartitionQueryRequest",
  20035. responseType: "PartitionQueryResponse",
  20036. options: {
  20037. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:partitionQuery",
  20038. "(google.api.http).body": "*",
  20039. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery",
  20040. "(google.api.http).additional_bindings.body": "*"
  20041. },
  20042. parsedOptions: [
  20043. {
  20044. "(google.api.http)": {
  20045. post: "/v1/{parent=projects/*/databases/*/documents}:partitionQuery",
  20046. body: "*",
  20047. additional_bindings: {
  20048. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery",
  20049. body: "*"
  20050. }
  20051. }
  20052. }
  20053. ]
  20054. },
  20055. Write: {
  20056. requestType: "WriteRequest",
  20057. requestStream: true,
  20058. responseType: "WriteResponse",
  20059. responseStream: true,
  20060. options: {
  20061. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:write",
  20062. "(google.api.http).body": "*"
  20063. },
  20064. parsedOptions: [
  20065. {
  20066. "(google.api.http)": {
  20067. post: "/v1/{database=projects/*/databases/*}/documents:write",
  20068. body: "*"
  20069. }
  20070. }
  20071. ]
  20072. },
  20073. Listen: {
  20074. requestType: "ListenRequest",
  20075. requestStream: true,
  20076. responseType: "ListenResponse",
  20077. responseStream: true,
  20078. options: {
  20079. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:listen",
  20080. "(google.api.http).body": "*"
  20081. },
  20082. parsedOptions: [
  20083. {
  20084. "(google.api.http)": {
  20085. post: "/v1/{database=projects/*/databases/*}/documents:listen",
  20086. body: "*"
  20087. }
  20088. }
  20089. ]
  20090. },
  20091. ListCollectionIds: {
  20092. requestType: "ListCollectionIdsRequest",
  20093. responseType: "ListCollectionIdsResponse",
  20094. options: {
  20095. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:listCollectionIds",
  20096. "(google.api.http).body": "*",
  20097. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:listCollectionIds",
  20098. "(google.api.http).additional_bindings.body": "*",
  20099. "(google.api.method_signature)": "parent"
  20100. },
  20101. parsedOptions: [
  20102. {
  20103. "(google.api.http)": {
  20104. post: "/v1/{parent=projects/*/databases/*/documents}:listCollectionIds",
  20105. body: "*",
  20106. additional_bindings: {
  20107. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:listCollectionIds",
  20108. body: "*"
  20109. }
  20110. }
  20111. },
  20112. {
  20113. "(google.api.method_signature)": "parent"
  20114. }
  20115. ]
  20116. },
  20117. BatchWrite: {
  20118. requestType: "BatchWriteRequest",
  20119. responseType: "BatchWriteResponse",
  20120. options: {
  20121. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:batchWrite",
  20122. "(google.api.http).body": "*"
  20123. },
  20124. parsedOptions: [
  20125. {
  20126. "(google.api.http)": {
  20127. post: "/v1/{database=projects/*/databases/*}/documents:batchWrite",
  20128. body: "*"
  20129. }
  20130. }
  20131. ]
  20132. },
  20133. CreateDocument: {
  20134. requestType: "CreateDocumentRequest",
  20135. responseType: "Document",
  20136. options: {
  20137. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}",
  20138. "(google.api.http).body": "document"
  20139. },
  20140. parsedOptions: [
  20141. {
  20142. "(google.api.http)": {
  20143. post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}",
  20144. body: "document"
  20145. }
  20146. }
  20147. ]
  20148. }
  20149. }
  20150. },
  20151. GetDocumentRequest: {
  20152. oneofs: {
  20153. consistencySelector: {
  20154. oneof: [
  20155. "transaction",
  20156. "readTime"
  20157. ]
  20158. }
  20159. },
  20160. fields: {
  20161. name: {
  20162. type: "string",
  20163. id: 1,
  20164. options: {
  20165. "(google.api.field_behavior)": "REQUIRED"
  20166. }
  20167. },
  20168. mask: {
  20169. type: "DocumentMask",
  20170. id: 2
  20171. },
  20172. transaction: {
  20173. type: "bytes",
  20174. id: 3
  20175. },
  20176. readTime: {
  20177. type: "google.protobuf.Timestamp",
  20178. id: 5
  20179. }
  20180. }
  20181. },
  20182. ListDocumentsRequest: {
  20183. oneofs: {
  20184. consistencySelector: {
  20185. oneof: [
  20186. "transaction",
  20187. "readTime"
  20188. ]
  20189. }
  20190. },
  20191. fields: {
  20192. parent: {
  20193. type: "string",
  20194. id: 1,
  20195. options: {
  20196. "(google.api.field_behavior)": "REQUIRED"
  20197. }
  20198. },
  20199. collectionId: {
  20200. type: "string",
  20201. id: 2,
  20202. options: {
  20203. "(google.api.field_behavior)": "REQUIRED"
  20204. }
  20205. },
  20206. pageSize: {
  20207. type: "int32",
  20208. id: 3
  20209. },
  20210. pageToken: {
  20211. type: "string",
  20212. id: 4
  20213. },
  20214. orderBy: {
  20215. type: "string",
  20216. id: 6
  20217. },
  20218. mask: {
  20219. type: "DocumentMask",
  20220. id: 7
  20221. },
  20222. transaction: {
  20223. type: "bytes",
  20224. id: 8
  20225. },
  20226. readTime: {
  20227. type: "google.protobuf.Timestamp",
  20228. id: 10
  20229. },
  20230. showMissing: {
  20231. type: "bool",
  20232. id: 12
  20233. }
  20234. }
  20235. },
  20236. ListDocumentsResponse: {
  20237. fields: {
  20238. documents: {
  20239. rule: "repeated",
  20240. type: "Document",
  20241. id: 1
  20242. },
  20243. nextPageToken: {
  20244. type: "string",
  20245. id: 2
  20246. }
  20247. }
  20248. },
  20249. CreateDocumentRequest: {
  20250. fields: {
  20251. parent: {
  20252. type: "string",
  20253. id: 1,
  20254. options: {
  20255. "(google.api.field_behavior)": "REQUIRED"
  20256. }
  20257. },
  20258. collectionId: {
  20259. type: "string",
  20260. id: 2,
  20261. options: {
  20262. "(google.api.field_behavior)": "REQUIRED"
  20263. }
  20264. },
  20265. documentId: {
  20266. type: "string",
  20267. id: 3
  20268. },
  20269. document: {
  20270. type: "Document",
  20271. id: 4,
  20272. options: {
  20273. "(google.api.field_behavior)": "REQUIRED"
  20274. }
  20275. },
  20276. mask: {
  20277. type: "DocumentMask",
  20278. id: 5
  20279. }
  20280. }
  20281. },
  20282. UpdateDocumentRequest: {
  20283. fields: {
  20284. document: {
  20285. type: "Document",
  20286. id: 1,
  20287. options: {
  20288. "(google.api.field_behavior)": "REQUIRED"
  20289. }
  20290. },
  20291. updateMask: {
  20292. type: "DocumentMask",
  20293. id: 2
  20294. },
  20295. mask: {
  20296. type: "DocumentMask",
  20297. id: 3
  20298. },
  20299. currentDocument: {
  20300. type: "Precondition",
  20301. id: 4
  20302. }
  20303. }
  20304. },
  20305. DeleteDocumentRequest: {
  20306. fields: {
  20307. name: {
  20308. type: "string",
  20309. id: 1,
  20310. options: {
  20311. "(google.api.field_behavior)": "REQUIRED"
  20312. }
  20313. },
  20314. currentDocument: {
  20315. type: "Precondition",
  20316. id: 2
  20317. }
  20318. }
  20319. },
  20320. BatchGetDocumentsRequest: {
  20321. oneofs: {
  20322. consistencySelector: {
  20323. oneof: [
  20324. "transaction",
  20325. "newTransaction",
  20326. "readTime"
  20327. ]
  20328. }
  20329. },
  20330. fields: {
  20331. database: {
  20332. type: "string",
  20333. id: 1,
  20334. options: {
  20335. "(google.api.field_behavior)": "REQUIRED"
  20336. }
  20337. },
  20338. documents: {
  20339. rule: "repeated",
  20340. type: "string",
  20341. id: 2
  20342. },
  20343. mask: {
  20344. type: "DocumentMask",
  20345. id: 3
  20346. },
  20347. transaction: {
  20348. type: "bytes",
  20349. id: 4
  20350. },
  20351. newTransaction: {
  20352. type: "TransactionOptions",
  20353. id: 5
  20354. },
  20355. readTime: {
  20356. type: "google.protobuf.Timestamp",
  20357. id: 7
  20358. }
  20359. }
  20360. },
  20361. BatchGetDocumentsResponse: {
  20362. oneofs: {
  20363. result: {
  20364. oneof: [
  20365. "found",
  20366. "missing"
  20367. ]
  20368. }
  20369. },
  20370. fields: {
  20371. found: {
  20372. type: "Document",
  20373. id: 1
  20374. },
  20375. missing: {
  20376. type: "string",
  20377. id: 2
  20378. },
  20379. transaction: {
  20380. type: "bytes",
  20381. id: 3
  20382. },
  20383. readTime: {
  20384. type: "google.protobuf.Timestamp",
  20385. id: 4
  20386. }
  20387. }
  20388. },
  20389. BeginTransactionRequest: {
  20390. fields: {
  20391. database: {
  20392. type: "string",
  20393. id: 1,
  20394. options: {
  20395. "(google.api.field_behavior)": "REQUIRED"
  20396. }
  20397. },
  20398. options: {
  20399. type: "TransactionOptions",
  20400. id: 2
  20401. }
  20402. }
  20403. },
  20404. BeginTransactionResponse: {
  20405. fields: {
  20406. transaction: {
  20407. type: "bytes",
  20408. id: 1
  20409. }
  20410. }
  20411. },
  20412. CommitRequest: {
  20413. fields: {
  20414. database: {
  20415. type: "string",
  20416. id: 1,
  20417. options: {
  20418. "(google.api.field_behavior)": "REQUIRED"
  20419. }
  20420. },
  20421. writes: {
  20422. rule: "repeated",
  20423. type: "Write",
  20424. id: 2
  20425. },
  20426. transaction: {
  20427. type: "bytes",
  20428. id: 3
  20429. }
  20430. }
  20431. },
  20432. CommitResponse: {
  20433. fields: {
  20434. writeResults: {
  20435. rule: "repeated",
  20436. type: "WriteResult",
  20437. id: 1
  20438. },
  20439. commitTime: {
  20440. type: "google.protobuf.Timestamp",
  20441. id: 2
  20442. }
  20443. }
  20444. },
  20445. RollbackRequest: {
  20446. fields: {
  20447. database: {
  20448. type: "string",
  20449. id: 1,
  20450. options: {
  20451. "(google.api.field_behavior)": "REQUIRED"
  20452. }
  20453. },
  20454. transaction: {
  20455. type: "bytes",
  20456. id: 2,
  20457. options: {
  20458. "(google.api.field_behavior)": "REQUIRED"
  20459. }
  20460. }
  20461. }
  20462. },
  20463. RunQueryRequest: {
  20464. oneofs: {
  20465. queryType: {
  20466. oneof: [
  20467. "structuredQuery"
  20468. ]
  20469. },
  20470. consistencySelector: {
  20471. oneof: [
  20472. "transaction",
  20473. "newTransaction",
  20474. "readTime"
  20475. ]
  20476. }
  20477. },
  20478. fields: {
  20479. parent: {
  20480. type: "string",
  20481. id: 1,
  20482. options: {
  20483. "(google.api.field_behavior)": "REQUIRED"
  20484. }
  20485. },
  20486. structuredQuery: {
  20487. type: "StructuredQuery",
  20488. id: 2
  20489. },
  20490. transaction: {
  20491. type: "bytes",
  20492. id: 5
  20493. },
  20494. newTransaction: {
  20495. type: "TransactionOptions",
  20496. id: 6
  20497. },
  20498. readTime: {
  20499. type: "google.protobuf.Timestamp",
  20500. id: 7
  20501. }
  20502. }
  20503. },
  20504. RunQueryResponse: {
  20505. fields: {
  20506. transaction: {
  20507. type: "bytes",
  20508. id: 2
  20509. },
  20510. document: {
  20511. type: "Document",
  20512. id: 1
  20513. },
  20514. readTime: {
  20515. type: "google.protobuf.Timestamp",
  20516. id: 3
  20517. },
  20518. skippedResults: {
  20519. type: "int32",
  20520. id: 4
  20521. }
  20522. }
  20523. },
  20524. RunAggregationQueryRequest: {
  20525. oneofs: {
  20526. queryType: {
  20527. oneof: [
  20528. "structuredAggregationQuery"
  20529. ]
  20530. },
  20531. consistencySelector: {
  20532. oneof: [
  20533. "transaction",
  20534. "newTransaction",
  20535. "readTime"
  20536. ]
  20537. }
  20538. },
  20539. fields: {
  20540. parent: {
  20541. type: "string",
  20542. id: 1,
  20543. options: {
  20544. "(google.api.field_behavior)": "REQUIRED"
  20545. }
  20546. },
  20547. structuredAggregationQuery: {
  20548. type: "StructuredAggregationQuery",
  20549. id: 2
  20550. },
  20551. transaction: {
  20552. type: "bytes",
  20553. id: 4
  20554. },
  20555. newTransaction: {
  20556. type: "TransactionOptions",
  20557. id: 5
  20558. },
  20559. readTime: {
  20560. type: "google.protobuf.Timestamp",
  20561. id: 6
  20562. }
  20563. }
  20564. },
  20565. RunAggregationQueryResponse: {
  20566. fields: {
  20567. result: {
  20568. type: "AggregationResult",
  20569. id: 1
  20570. },
  20571. transaction: {
  20572. type: "bytes",
  20573. id: 2
  20574. },
  20575. readTime: {
  20576. type: "google.protobuf.Timestamp",
  20577. id: 3
  20578. }
  20579. }
  20580. },
  20581. PartitionQueryRequest: {
  20582. oneofs: {
  20583. queryType: {
  20584. oneof: [
  20585. "structuredQuery"
  20586. ]
  20587. }
  20588. },
  20589. fields: {
  20590. parent: {
  20591. type: "string",
  20592. id: 1,
  20593. options: {
  20594. "(google.api.field_behavior)": "REQUIRED"
  20595. }
  20596. },
  20597. structuredQuery: {
  20598. type: "StructuredQuery",
  20599. id: 2
  20600. },
  20601. partitionCount: {
  20602. type: "int64",
  20603. id: 3
  20604. },
  20605. pageToken: {
  20606. type: "string",
  20607. id: 4
  20608. },
  20609. pageSize: {
  20610. type: "int32",
  20611. id: 5
  20612. }
  20613. }
  20614. },
  20615. PartitionQueryResponse: {
  20616. fields: {
  20617. partitions: {
  20618. rule: "repeated",
  20619. type: "Cursor",
  20620. id: 1
  20621. },
  20622. nextPageToken: {
  20623. type: "string",
  20624. id: 2
  20625. }
  20626. }
  20627. },
  20628. WriteRequest: {
  20629. fields: {
  20630. database: {
  20631. type: "string",
  20632. id: 1,
  20633. options: {
  20634. "(google.api.field_behavior)": "REQUIRED"
  20635. }
  20636. },
  20637. streamId: {
  20638. type: "string",
  20639. id: 2
  20640. },
  20641. writes: {
  20642. rule: "repeated",
  20643. type: "Write",
  20644. id: 3
  20645. },
  20646. streamToken: {
  20647. type: "bytes",
  20648. id: 4
  20649. },
  20650. labels: {
  20651. keyType: "string",
  20652. type: "string",
  20653. id: 5
  20654. }
  20655. }
  20656. },
  20657. WriteResponse: {
  20658. fields: {
  20659. streamId: {
  20660. type: "string",
  20661. id: 1
  20662. },
  20663. streamToken: {
  20664. type: "bytes",
  20665. id: 2
  20666. },
  20667. writeResults: {
  20668. rule: "repeated",
  20669. type: "WriteResult",
  20670. id: 3
  20671. },
  20672. commitTime: {
  20673. type: "google.protobuf.Timestamp",
  20674. id: 4
  20675. }
  20676. }
  20677. },
  20678. ListenRequest: {
  20679. oneofs: {
  20680. targetChange: {
  20681. oneof: [
  20682. "addTarget",
  20683. "removeTarget"
  20684. ]
  20685. }
  20686. },
  20687. fields: {
  20688. database: {
  20689. type: "string",
  20690. id: 1,
  20691. options: {
  20692. "(google.api.field_behavior)": "REQUIRED"
  20693. }
  20694. },
  20695. addTarget: {
  20696. type: "Target",
  20697. id: 2
  20698. },
  20699. removeTarget: {
  20700. type: "int32",
  20701. id: 3
  20702. },
  20703. labels: {
  20704. keyType: "string",
  20705. type: "string",
  20706. id: 4
  20707. }
  20708. }
  20709. },
  20710. ListenResponse: {
  20711. oneofs: {
  20712. responseType: {
  20713. oneof: [
  20714. "targetChange",
  20715. "documentChange",
  20716. "documentDelete",
  20717. "documentRemove",
  20718. "filter"
  20719. ]
  20720. }
  20721. },
  20722. fields: {
  20723. targetChange: {
  20724. type: "TargetChange",
  20725. id: 2
  20726. },
  20727. documentChange: {
  20728. type: "DocumentChange",
  20729. id: 3
  20730. },
  20731. documentDelete: {
  20732. type: "DocumentDelete",
  20733. id: 4
  20734. },
  20735. documentRemove: {
  20736. type: "DocumentRemove",
  20737. id: 6
  20738. },
  20739. filter: {
  20740. type: "ExistenceFilter",
  20741. id: 5
  20742. }
  20743. }
  20744. },
  20745. Target: {
  20746. oneofs: {
  20747. targetType: {
  20748. oneof: [
  20749. "query",
  20750. "documents"
  20751. ]
  20752. },
  20753. resumeType: {
  20754. oneof: [
  20755. "resumeToken",
  20756. "readTime"
  20757. ]
  20758. }
  20759. },
  20760. fields: {
  20761. query: {
  20762. type: "QueryTarget",
  20763. id: 2
  20764. },
  20765. documents: {
  20766. type: "DocumentsTarget",
  20767. id: 3
  20768. },
  20769. resumeToken: {
  20770. type: "bytes",
  20771. id: 4
  20772. },
  20773. readTime: {
  20774. type: "google.protobuf.Timestamp",
  20775. id: 11
  20776. },
  20777. targetId: {
  20778. type: "int32",
  20779. id: 5
  20780. },
  20781. once: {
  20782. type: "bool",
  20783. id: 6
  20784. },
  20785. expectedCount: {
  20786. type: "google.protobuf.Int32Value",
  20787. id: 12
  20788. }
  20789. },
  20790. nested: {
  20791. DocumentsTarget: {
  20792. fields: {
  20793. documents: {
  20794. rule: "repeated",
  20795. type: "string",
  20796. id: 2
  20797. }
  20798. }
  20799. },
  20800. QueryTarget: {
  20801. oneofs: {
  20802. queryType: {
  20803. oneof: [
  20804. "structuredQuery"
  20805. ]
  20806. }
  20807. },
  20808. fields: {
  20809. parent: {
  20810. type: "string",
  20811. id: 1
  20812. },
  20813. structuredQuery: {
  20814. type: "StructuredQuery",
  20815. id: 2
  20816. }
  20817. }
  20818. }
  20819. }
  20820. },
  20821. TargetChange: {
  20822. fields: {
  20823. targetChangeType: {
  20824. type: "TargetChangeType",
  20825. id: 1
  20826. },
  20827. targetIds: {
  20828. rule: "repeated",
  20829. type: "int32",
  20830. id: 2
  20831. },
  20832. cause: {
  20833. type: "google.rpc.Status",
  20834. id: 3
  20835. },
  20836. resumeToken: {
  20837. type: "bytes",
  20838. id: 4
  20839. },
  20840. readTime: {
  20841. type: "google.protobuf.Timestamp",
  20842. id: 6
  20843. }
  20844. },
  20845. nested: {
  20846. TargetChangeType: {
  20847. values: {
  20848. NO_CHANGE: 0,
  20849. ADD: 1,
  20850. REMOVE: 2,
  20851. CURRENT: 3,
  20852. RESET: 4
  20853. }
  20854. }
  20855. }
  20856. },
  20857. ListCollectionIdsRequest: {
  20858. fields: {
  20859. parent: {
  20860. type: "string",
  20861. id: 1,
  20862. options: {
  20863. "(google.api.field_behavior)": "REQUIRED"
  20864. }
  20865. },
  20866. pageSize: {
  20867. type: "int32",
  20868. id: 2
  20869. },
  20870. pageToken: {
  20871. type: "string",
  20872. id: 3
  20873. }
  20874. }
  20875. },
  20876. ListCollectionIdsResponse: {
  20877. fields: {
  20878. collectionIds: {
  20879. rule: "repeated",
  20880. type: "string",
  20881. id: 1
  20882. },
  20883. nextPageToken: {
  20884. type: "string",
  20885. id: 2
  20886. }
  20887. }
  20888. },
  20889. BatchWriteRequest: {
  20890. fields: {
  20891. database: {
  20892. type: "string",
  20893. id: 1,
  20894. options: {
  20895. "(google.api.field_behavior)": "REQUIRED"
  20896. }
  20897. },
  20898. writes: {
  20899. rule: "repeated",
  20900. type: "Write",
  20901. id: 2
  20902. },
  20903. labels: {
  20904. keyType: "string",
  20905. type: "string",
  20906. id: 3
  20907. }
  20908. }
  20909. },
  20910. BatchWriteResponse: {
  20911. fields: {
  20912. writeResults: {
  20913. rule: "repeated",
  20914. type: "WriteResult",
  20915. id: 1
  20916. },
  20917. status: {
  20918. rule: "repeated",
  20919. type: "google.rpc.Status",
  20920. id: 2
  20921. }
  20922. }
  20923. },
  20924. StructuredQuery: {
  20925. fields: {
  20926. select: {
  20927. type: "Projection",
  20928. id: 1
  20929. },
  20930. from: {
  20931. rule: "repeated",
  20932. type: "CollectionSelector",
  20933. id: 2
  20934. },
  20935. where: {
  20936. type: "Filter",
  20937. id: 3
  20938. },
  20939. orderBy: {
  20940. rule: "repeated",
  20941. type: "Order",
  20942. id: 4
  20943. },
  20944. startAt: {
  20945. type: "Cursor",
  20946. id: 7
  20947. },
  20948. endAt: {
  20949. type: "Cursor",
  20950. id: 8
  20951. },
  20952. offset: {
  20953. type: "int32",
  20954. id: 6
  20955. },
  20956. limit: {
  20957. type: "google.protobuf.Int32Value",
  20958. id: 5
  20959. }
  20960. },
  20961. nested: {
  20962. CollectionSelector: {
  20963. fields: {
  20964. collectionId: {
  20965. type: "string",
  20966. id: 2
  20967. },
  20968. allDescendants: {
  20969. type: "bool",
  20970. id: 3
  20971. }
  20972. }
  20973. },
  20974. Filter: {
  20975. oneofs: {
  20976. filterType: {
  20977. oneof: [
  20978. "compositeFilter",
  20979. "fieldFilter",
  20980. "unaryFilter"
  20981. ]
  20982. }
  20983. },
  20984. fields: {
  20985. compositeFilter: {
  20986. type: "CompositeFilter",
  20987. id: 1
  20988. },
  20989. fieldFilter: {
  20990. type: "FieldFilter",
  20991. id: 2
  20992. },
  20993. unaryFilter: {
  20994. type: "UnaryFilter",
  20995. id: 3
  20996. }
  20997. }
  20998. },
  20999. CompositeFilter: {
  21000. fields: {
  21001. op: {
  21002. type: "Operator",
  21003. id: 1
  21004. },
  21005. filters: {
  21006. rule: "repeated",
  21007. type: "Filter",
  21008. id: 2
  21009. }
  21010. },
  21011. nested: {
  21012. Operator: {
  21013. values: {
  21014. OPERATOR_UNSPECIFIED: 0,
  21015. AND: 1,
  21016. OR: 2
  21017. }
  21018. }
  21019. }
  21020. },
  21021. FieldFilter: {
  21022. fields: {
  21023. field: {
  21024. type: "FieldReference",
  21025. id: 1
  21026. },
  21027. op: {
  21028. type: "Operator",
  21029. id: 2
  21030. },
  21031. value: {
  21032. type: "Value",
  21033. id: 3
  21034. }
  21035. },
  21036. nested: {
  21037. Operator: {
  21038. values: {
  21039. OPERATOR_UNSPECIFIED: 0,
  21040. LESS_THAN: 1,
  21041. LESS_THAN_OR_EQUAL: 2,
  21042. GREATER_THAN: 3,
  21043. GREATER_THAN_OR_EQUAL: 4,
  21044. EQUAL: 5,
  21045. NOT_EQUAL: 6,
  21046. ARRAY_CONTAINS: 7,
  21047. IN: 8,
  21048. ARRAY_CONTAINS_ANY: 9,
  21049. NOT_IN: 10
  21050. }
  21051. }
  21052. }
  21053. },
  21054. UnaryFilter: {
  21055. oneofs: {
  21056. operandType: {
  21057. oneof: [
  21058. "field"
  21059. ]
  21060. }
  21061. },
  21062. fields: {
  21063. op: {
  21064. type: "Operator",
  21065. id: 1
  21066. },
  21067. field: {
  21068. type: "FieldReference",
  21069. id: 2
  21070. }
  21071. },
  21072. nested: {
  21073. Operator: {
  21074. values: {
  21075. OPERATOR_UNSPECIFIED: 0,
  21076. IS_NAN: 2,
  21077. IS_NULL: 3,
  21078. IS_NOT_NAN: 4,
  21079. IS_NOT_NULL: 5
  21080. }
  21081. }
  21082. }
  21083. },
  21084. Order: {
  21085. fields: {
  21086. field: {
  21087. type: "FieldReference",
  21088. id: 1
  21089. },
  21090. direction: {
  21091. type: "Direction",
  21092. id: 2
  21093. }
  21094. }
  21095. },
  21096. FieldReference: {
  21097. fields: {
  21098. fieldPath: {
  21099. type: "string",
  21100. id: 2
  21101. }
  21102. }
  21103. },
  21104. Projection: {
  21105. fields: {
  21106. fields: {
  21107. rule: "repeated",
  21108. type: "FieldReference",
  21109. id: 2
  21110. }
  21111. }
  21112. },
  21113. Direction: {
  21114. values: {
  21115. DIRECTION_UNSPECIFIED: 0,
  21116. ASCENDING: 1,
  21117. DESCENDING: 2
  21118. }
  21119. }
  21120. }
  21121. },
  21122. StructuredAggregationQuery: {
  21123. oneofs: {
  21124. queryType: {
  21125. oneof: [
  21126. "structuredQuery"
  21127. ]
  21128. }
  21129. },
  21130. fields: {
  21131. structuredQuery: {
  21132. type: "StructuredQuery",
  21133. id: 1
  21134. },
  21135. aggregations: {
  21136. rule: "repeated",
  21137. type: "Aggregation",
  21138. id: 3
  21139. }
  21140. },
  21141. nested: {
  21142. Aggregation: {
  21143. oneofs: {
  21144. operator: {
  21145. oneof: [
  21146. "count",
  21147. "sum",
  21148. "avg"
  21149. ]
  21150. }
  21151. },
  21152. fields: {
  21153. count: {
  21154. type: "Count",
  21155. id: 1
  21156. },
  21157. sum: {
  21158. type: "Sum",
  21159. id: 2
  21160. },
  21161. avg: {
  21162. type: "Avg",
  21163. id: 3
  21164. },
  21165. alias: {
  21166. type: "string",
  21167. id: 7
  21168. }
  21169. },
  21170. nested: {
  21171. Count: {
  21172. fields: {
  21173. upTo: {
  21174. type: "google.protobuf.Int64Value",
  21175. id: 1
  21176. }
  21177. }
  21178. },
  21179. Sum: {
  21180. fields: {
  21181. field: {
  21182. type: "FieldReference",
  21183. id: 1
  21184. }
  21185. }
  21186. },
  21187. Avg: {
  21188. fields: {
  21189. field: {
  21190. type: "FieldReference",
  21191. id: 1
  21192. }
  21193. }
  21194. }
  21195. }
  21196. }
  21197. }
  21198. },
  21199. Cursor: {
  21200. fields: {
  21201. values: {
  21202. rule: "repeated",
  21203. type: "Value",
  21204. id: 1
  21205. },
  21206. before: {
  21207. type: "bool",
  21208. id: 2
  21209. }
  21210. }
  21211. },
  21212. Write: {
  21213. oneofs: {
  21214. operation: {
  21215. oneof: [
  21216. "update",
  21217. "delete",
  21218. "verify",
  21219. "transform"
  21220. ]
  21221. }
  21222. },
  21223. fields: {
  21224. update: {
  21225. type: "Document",
  21226. id: 1
  21227. },
  21228. "delete": {
  21229. type: "string",
  21230. id: 2
  21231. },
  21232. verify: {
  21233. type: "string",
  21234. id: 5
  21235. },
  21236. transform: {
  21237. type: "DocumentTransform",
  21238. id: 6
  21239. },
  21240. updateMask: {
  21241. type: "DocumentMask",
  21242. id: 3
  21243. },
  21244. updateTransforms: {
  21245. rule: "repeated",
  21246. type: "DocumentTransform.FieldTransform",
  21247. id: 7
  21248. },
  21249. currentDocument: {
  21250. type: "Precondition",
  21251. id: 4
  21252. }
  21253. }
  21254. },
  21255. DocumentTransform: {
  21256. fields: {
  21257. document: {
  21258. type: "string",
  21259. id: 1
  21260. },
  21261. fieldTransforms: {
  21262. rule: "repeated",
  21263. type: "FieldTransform",
  21264. id: 2
  21265. }
  21266. },
  21267. nested: {
  21268. FieldTransform: {
  21269. oneofs: {
  21270. transformType: {
  21271. oneof: [
  21272. "setToServerValue",
  21273. "increment",
  21274. "maximum",
  21275. "minimum",
  21276. "appendMissingElements",
  21277. "removeAllFromArray"
  21278. ]
  21279. }
  21280. },
  21281. fields: {
  21282. fieldPath: {
  21283. type: "string",
  21284. id: 1
  21285. },
  21286. setToServerValue: {
  21287. type: "ServerValue",
  21288. id: 2
  21289. },
  21290. increment: {
  21291. type: "Value",
  21292. id: 3
  21293. },
  21294. maximum: {
  21295. type: "Value",
  21296. id: 4
  21297. },
  21298. minimum: {
  21299. type: "Value",
  21300. id: 5
  21301. },
  21302. appendMissingElements: {
  21303. type: "ArrayValue",
  21304. id: 6
  21305. },
  21306. removeAllFromArray: {
  21307. type: "ArrayValue",
  21308. id: 7
  21309. }
  21310. },
  21311. nested: {
  21312. ServerValue: {
  21313. values: {
  21314. SERVER_VALUE_UNSPECIFIED: 0,
  21315. REQUEST_TIME: 1
  21316. }
  21317. }
  21318. }
  21319. }
  21320. }
  21321. },
  21322. WriteResult: {
  21323. fields: {
  21324. updateTime: {
  21325. type: "google.protobuf.Timestamp",
  21326. id: 1
  21327. },
  21328. transformResults: {
  21329. rule: "repeated",
  21330. type: "Value",
  21331. id: 2
  21332. }
  21333. }
  21334. },
  21335. DocumentChange: {
  21336. fields: {
  21337. document: {
  21338. type: "Document",
  21339. id: 1
  21340. },
  21341. targetIds: {
  21342. rule: "repeated",
  21343. type: "int32",
  21344. id: 5
  21345. },
  21346. removedTargetIds: {
  21347. rule: "repeated",
  21348. type: "int32",
  21349. id: 6
  21350. }
  21351. }
  21352. },
  21353. DocumentDelete: {
  21354. fields: {
  21355. document: {
  21356. type: "string",
  21357. id: 1
  21358. },
  21359. removedTargetIds: {
  21360. rule: "repeated",
  21361. type: "int32",
  21362. id: 6
  21363. },
  21364. readTime: {
  21365. type: "google.protobuf.Timestamp",
  21366. id: 4
  21367. }
  21368. }
  21369. },
  21370. DocumentRemove: {
  21371. fields: {
  21372. document: {
  21373. type: "string",
  21374. id: 1
  21375. },
  21376. removedTargetIds: {
  21377. rule: "repeated",
  21378. type: "int32",
  21379. id: 2
  21380. },
  21381. readTime: {
  21382. type: "google.protobuf.Timestamp",
  21383. id: 4
  21384. }
  21385. }
  21386. },
  21387. ExistenceFilter: {
  21388. fields: {
  21389. targetId: {
  21390. type: "int32",
  21391. id: 1
  21392. },
  21393. count: {
  21394. type: "int32",
  21395. id: 2
  21396. },
  21397. unchangedNames: {
  21398. type: "BloomFilter",
  21399. id: 3
  21400. }
  21401. }
  21402. }
  21403. }
  21404. }
  21405. }
  21406. },
  21407. api: {
  21408. options: {
  21409. go_package: "google.golang.org/genproto/googleapis/api/annotations;annotations",
  21410. java_multiple_files: true,
  21411. java_outer_classname: "HttpProto",
  21412. java_package: "com.google.api",
  21413. objc_class_prefix: "GAPI",
  21414. cc_enable_arenas: true
  21415. },
  21416. nested: {
  21417. http: {
  21418. type: "HttpRule",
  21419. id: 72295728,
  21420. extend: "google.protobuf.MethodOptions"
  21421. },
  21422. Http: {
  21423. fields: {
  21424. rules: {
  21425. rule: "repeated",
  21426. type: "HttpRule",
  21427. id: 1
  21428. }
  21429. }
  21430. },
  21431. HttpRule: {
  21432. oneofs: {
  21433. pattern: {
  21434. oneof: [
  21435. "get",
  21436. "put",
  21437. "post",
  21438. "delete",
  21439. "patch",
  21440. "custom"
  21441. ]
  21442. }
  21443. },
  21444. fields: {
  21445. get: {
  21446. type: "string",
  21447. id: 2
  21448. },
  21449. put: {
  21450. type: "string",
  21451. id: 3
  21452. },
  21453. post: {
  21454. type: "string",
  21455. id: 4
  21456. },
  21457. "delete": {
  21458. type: "string",
  21459. id: 5
  21460. },
  21461. patch: {
  21462. type: "string",
  21463. id: 6
  21464. },
  21465. custom: {
  21466. type: "CustomHttpPattern",
  21467. id: 8
  21468. },
  21469. selector: {
  21470. type: "string",
  21471. id: 1
  21472. },
  21473. body: {
  21474. type: "string",
  21475. id: 7
  21476. },
  21477. additionalBindings: {
  21478. rule: "repeated",
  21479. type: "HttpRule",
  21480. id: 11
  21481. }
  21482. }
  21483. },
  21484. CustomHttpPattern: {
  21485. fields: {
  21486. kind: {
  21487. type: "string",
  21488. id: 1
  21489. },
  21490. path: {
  21491. type: "string",
  21492. id: 2
  21493. }
  21494. }
  21495. },
  21496. methodSignature: {
  21497. rule: "repeated",
  21498. type: "string",
  21499. id: 1051,
  21500. extend: "google.protobuf.MethodOptions"
  21501. },
  21502. defaultHost: {
  21503. type: "string",
  21504. id: 1049,
  21505. extend: "google.protobuf.ServiceOptions"
  21506. },
  21507. oauthScopes: {
  21508. type: "string",
  21509. id: 1050,
  21510. extend: "google.protobuf.ServiceOptions"
  21511. },
  21512. fieldBehavior: {
  21513. rule: "repeated",
  21514. type: "google.api.FieldBehavior",
  21515. id: 1052,
  21516. extend: "google.protobuf.FieldOptions"
  21517. },
  21518. FieldBehavior: {
  21519. values: {
  21520. FIELD_BEHAVIOR_UNSPECIFIED: 0,
  21521. OPTIONAL: 1,
  21522. REQUIRED: 2,
  21523. OUTPUT_ONLY: 3,
  21524. INPUT_ONLY: 4,
  21525. IMMUTABLE: 5,
  21526. UNORDERED_LIST: 6,
  21527. NON_EMPTY_DEFAULT: 7
  21528. }
  21529. }
  21530. }
  21531. },
  21532. type: {
  21533. options: {
  21534. cc_enable_arenas: true,
  21535. go_package: "google.golang.org/genproto/googleapis/type/latlng;latlng",
  21536. java_multiple_files: true,
  21537. java_outer_classname: "LatLngProto",
  21538. java_package: "com.google.type",
  21539. objc_class_prefix: "GTP"
  21540. },
  21541. nested: {
  21542. LatLng: {
  21543. fields: {
  21544. latitude: {
  21545. type: "double",
  21546. id: 1
  21547. },
  21548. longitude: {
  21549. type: "double",
  21550. id: 2
  21551. }
  21552. }
  21553. }
  21554. }
  21555. },
  21556. rpc: {
  21557. options: {
  21558. cc_enable_arenas: true,
  21559. go_package: "google.golang.org/genproto/googleapis/rpc/status;status",
  21560. java_multiple_files: true,
  21561. java_outer_classname: "StatusProto",
  21562. java_package: "com.google.rpc",
  21563. objc_class_prefix: "RPC"
  21564. },
  21565. nested: {
  21566. Status: {
  21567. fields: {
  21568. code: {
  21569. type: "int32",
  21570. id: 1
  21571. },
  21572. message: {
  21573. type: "string",
  21574. id: 2
  21575. },
  21576. details: {
  21577. rule: "repeated",
  21578. type: "google.protobuf.Any",
  21579. id: 3
  21580. }
  21581. }
  21582. }
  21583. }
  21584. }
  21585. }
  21586. }
  21587. };
  21588. var protos = {
  21589. nested: nested
  21590. };
  21591. var protos$1 = /*#__PURE__*/Object.freeze({
  21592. __proto__: null,
  21593. nested: nested,
  21594. 'default': protos
  21595. });
  21596. /**
  21597. * @license
  21598. * Copyright 2020 Google LLC
  21599. *
  21600. * Licensed under the Apache License, Version 2.0 (the "License");
  21601. * you may not use this file except in compliance with the License.
  21602. * You may obtain a copy of the License at
  21603. *
  21604. * http://www.apache.org/licenses/LICENSE-2.0
  21605. *
  21606. * Unless required by applicable law or agreed to in writing, software
  21607. * distributed under the License is distributed on an "AS IS" BASIS,
  21608. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21609. * See the License for the specific language governing permissions and
  21610. * limitations under the License.
  21611. */
  21612. /** Used by tests so we can match @grpc/proto-loader behavior. */
  21613. const protoLoaderOptions = {
  21614. longs: String,
  21615. enums: String,
  21616. defaults: true,
  21617. oneofs: false
  21618. };
  21619. /**
  21620. * Loads the protocol buffer definitions for Firestore.
  21621. *
  21622. * @returns The GrpcObject representing our protos.
  21623. */
  21624. function loadProtos() {
  21625. const packageDefinition = protoLoader__namespace.fromJSON(protos$1, protoLoaderOptions);
  21626. return grpc__namespace.loadPackageDefinition(packageDefinition);
  21627. }
  21628. /**
  21629. * @license
  21630. * Copyright 2020 Google LLC
  21631. *
  21632. * Licensed under the Apache License, Version 2.0 (the "License");
  21633. * you may not use this file except in compliance with the License.
  21634. * You may obtain a copy of the License at
  21635. *
  21636. * http://www.apache.org/licenses/LICENSE-2.0
  21637. *
  21638. * Unless required by applicable law or agreed to in writing, software
  21639. * distributed under the License is distributed on an "AS IS" BASIS,
  21640. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21641. * See the License for the specific language governing permissions and
  21642. * limitations under the License.
  21643. */
  21644. /** Loads the GRPC stack */
  21645. function newConnection(databaseInfo) {
  21646. const protos = loadProtos();
  21647. return new GrpcConnection(protos, databaseInfo);
  21648. }
  21649. /** Return the Platform-specific connectivity monitor. */
  21650. function newConnectivityMonitor() {
  21651. return new NoopConnectivityMonitor();
  21652. }
  21653. /**
  21654. * @license
  21655. * Copyright 2020 Google LLC
  21656. *
  21657. * Licensed under the Apache License, Version 2.0 (the "License");
  21658. * you may not use this file except in compliance with the License.
  21659. * You may obtain a copy of the License at
  21660. *
  21661. * http://www.apache.org/licenses/LICENSE-2.0
  21662. *
  21663. * Unless required by applicable law or agreed to in writing, software
  21664. * distributed under the License is distributed on an "AS IS" BASIS,
  21665. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21666. * See the License for the specific language governing permissions and
  21667. * limitations under the License.
  21668. */
  21669. /** The Platform's 'window' implementation or null if not available. */
  21670. function getWindow() {
  21671. if (process.env.USE_MOCK_PERSISTENCE === 'YES') {
  21672. // eslint-disable-next-line no-restricted-globals
  21673. return window;
  21674. }
  21675. return null;
  21676. }
  21677. /** The Platform's 'document' implementation or null if not available. */
  21678. function getDocument() {
  21679. return null;
  21680. }
  21681. /**
  21682. * @license
  21683. * Copyright 2020 Google LLC
  21684. *
  21685. * Licensed under the Apache License, Version 2.0 (the "License");
  21686. * you may not use this file except in compliance with the License.
  21687. * You may obtain a copy of the License at
  21688. *
  21689. * http://www.apache.org/licenses/LICENSE-2.0
  21690. *
  21691. * Unless required by applicable law or agreed to in writing, software
  21692. * distributed under the License is distributed on an "AS IS" BASIS,
  21693. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21694. * See the License for the specific language governing permissions and
  21695. * limitations under the License.
  21696. */
  21697. function newSerializer(databaseId) {
  21698. return new JsonProtoSerializer(databaseId, /* useProto3Json= */ false);
  21699. }
  21700. /**
  21701. * @license
  21702. * Copyright 2017 Google LLC
  21703. *
  21704. * Licensed under the Apache License, Version 2.0 (the "License");
  21705. * you may not use this file except in compliance with the License.
  21706. * You may obtain a copy of the License at
  21707. *
  21708. * http://www.apache.org/licenses/LICENSE-2.0
  21709. *
  21710. * Unless required by applicable law or agreed to in writing, software
  21711. * distributed under the License is distributed on an "AS IS" BASIS,
  21712. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21713. * See the License for the specific language governing permissions and
  21714. * limitations under the License.
  21715. */
  21716. const LOG_TAG$8 = 'ExponentialBackoff';
  21717. /**
  21718. * Initial backoff time in milliseconds after an error.
  21719. * Set to 1s according to https://cloud.google.com/apis/design/errors.
  21720. */
  21721. const DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000;
  21722. const DEFAULT_BACKOFF_FACTOR = 1.5;
  21723. /** Maximum backoff time in milliseconds */
  21724. const DEFAULT_BACKOFF_MAX_DELAY_MS = 60 * 1000;
  21725. /**
  21726. * A helper for running delayed tasks following an exponential backoff curve
  21727. * between attempts.
  21728. *
  21729. * Each delay is made up of a "base" delay which follows the exponential
  21730. * backoff curve, and a +/- 50% "jitter" that is calculated and added to the
  21731. * base delay. This prevents clients from accidentally synchronizing their
  21732. * delays causing spikes of load to the backend.
  21733. */
  21734. class ExponentialBackoff {
  21735. constructor(
  21736. /**
  21737. * The AsyncQueue to run backoff operations on.
  21738. */
  21739. queue,
  21740. /**
  21741. * The ID to use when scheduling backoff operations on the AsyncQueue.
  21742. */
  21743. timerId,
  21744. /**
  21745. * The initial delay (used as the base delay on the first retry attempt).
  21746. * Note that jitter will still be applied, so the actual delay could be as
  21747. * little as 0.5*initialDelayMs.
  21748. */
  21749. initialDelayMs = DEFAULT_BACKOFF_INITIAL_DELAY_MS,
  21750. /**
  21751. * The multiplier to use to determine the extended base delay after each
  21752. * attempt.
  21753. */
  21754. backoffFactor = DEFAULT_BACKOFF_FACTOR,
  21755. /**
  21756. * The maximum base delay after which no further backoff is performed.
  21757. * Note that jitter will still be applied, so the actual delay could be as
  21758. * much as 1.5*maxDelayMs.
  21759. */
  21760. maxDelayMs = DEFAULT_BACKOFF_MAX_DELAY_MS) {
  21761. this.queue = queue;
  21762. this.timerId = timerId;
  21763. this.initialDelayMs = initialDelayMs;
  21764. this.backoffFactor = backoffFactor;
  21765. this.maxDelayMs = maxDelayMs;
  21766. this.currentBaseMs = 0;
  21767. this.timerPromise = null;
  21768. /** The last backoff attempt, as epoch milliseconds. */
  21769. this.lastAttemptTime = Date.now();
  21770. this.reset();
  21771. }
  21772. /**
  21773. * Resets the backoff delay.
  21774. *
  21775. * The very next backoffAndWait() will have no delay. If it is called again
  21776. * (i.e. due to an error), initialDelayMs (plus jitter) will be used, and
  21777. * subsequent ones will increase according to the backoffFactor.
  21778. */
  21779. reset() {
  21780. this.currentBaseMs = 0;
  21781. }
  21782. /**
  21783. * Resets the backoff delay to the maximum delay (e.g. for use after a
  21784. * RESOURCE_EXHAUSTED error).
  21785. */
  21786. resetToMax() {
  21787. this.currentBaseMs = this.maxDelayMs;
  21788. }
  21789. /**
  21790. * Returns a promise that resolves after currentDelayMs, and increases the
  21791. * delay for any subsequent attempts. If there was a pending backoff operation
  21792. * already, it will be canceled.
  21793. */
  21794. backoffAndRun(op) {
  21795. // Cancel any pending backoff operation.
  21796. this.cancel();
  21797. // First schedule using the current base (which may be 0 and should be
  21798. // honored as such).
  21799. const desiredDelayWithJitterMs = Math.floor(this.currentBaseMs + this.jitterDelayMs());
  21800. // Guard against lastAttemptTime being in the future due to a clock change.
  21801. const delaySoFarMs = Math.max(0, Date.now() - this.lastAttemptTime);
  21802. // Guard against the backoff delay already being past.
  21803. const remainingDelayMs = Math.max(0, desiredDelayWithJitterMs - delaySoFarMs);
  21804. if (remainingDelayMs > 0) {
  21805. logDebug(LOG_TAG$8, `Backing off for ${remainingDelayMs} ms ` +
  21806. `(base delay: ${this.currentBaseMs} ms, ` +
  21807. `delay with jitter: ${desiredDelayWithJitterMs} ms, ` +
  21808. `last attempt: ${delaySoFarMs} ms ago)`);
  21809. }
  21810. this.timerPromise = this.queue.enqueueAfterDelay(this.timerId, remainingDelayMs, () => {
  21811. this.lastAttemptTime = Date.now();
  21812. return op();
  21813. });
  21814. // Apply backoff factor to determine next delay and ensure it is within
  21815. // bounds.
  21816. this.currentBaseMs *= this.backoffFactor;
  21817. if (this.currentBaseMs < this.initialDelayMs) {
  21818. this.currentBaseMs = this.initialDelayMs;
  21819. }
  21820. if (this.currentBaseMs > this.maxDelayMs) {
  21821. this.currentBaseMs = this.maxDelayMs;
  21822. }
  21823. }
  21824. skipBackoff() {
  21825. if (this.timerPromise !== null) {
  21826. this.timerPromise.skipDelay();
  21827. this.timerPromise = null;
  21828. }
  21829. }
  21830. cancel() {
  21831. if (this.timerPromise !== null) {
  21832. this.timerPromise.cancel();
  21833. this.timerPromise = null;
  21834. }
  21835. }
  21836. /** Returns a random value in the range [-currentBaseMs/2, currentBaseMs/2] */
  21837. jitterDelayMs() {
  21838. return (Math.random() - 0.5) * this.currentBaseMs;
  21839. }
  21840. }
  21841. /**
  21842. * @license
  21843. * Copyright 2017 Google LLC
  21844. *
  21845. * Licensed under the Apache License, Version 2.0 (the "License");
  21846. * you may not use this file except in compliance with the License.
  21847. * You may obtain a copy of the License at
  21848. *
  21849. * http://www.apache.org/licenses/LICENSE-2.0
  21850. *
  21851. * Unless required by applicable law or agreed to in writing, software
  21852. * distributed under the License is distributed on an "AS IS" BASIS,
  21853. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21854. * See the License for the specific language governing permissions and
  21855. * limitations under the License.
  21856. */
  21857. const LOG_TAG$7 = 'PersistentStream';
  21858. /** The time a stream stays open after it is marked idle. */
  21859. const IDLE_TIMEOUT_MS = 60 * 1000;
  21860. /** The time a stream stays open until we consider it healthy. */
  21861. const HEALTHY_TIMEOUT_MS = 10 * 1000;
  21862. /**
  21863. * A PersistentStream is an abstract base class that represents a streaming RPC
  21864. * to the Firestore backend. It's built on top of the connections own support
  21865. * for streaming RPCs, and adds several critical features for our clients:
  21866. *
  21867. * - Exponential backoff on failure
  21868. * - Authentication via CredentialsProvider
  21869. * - Dispatching all callbacks into the shared worker queue
  21870. * - Closing idle streams after 60 seconds of inactivity
  21871. *
  21872. * Subclasses of PersistentStream implement serialization of models to and
  21873. * from the JSON representation of the protocol buffers for a specific
  21874. * streaming RPC.
  21875. *
  21876. * ## Starting and Stopping
  21877. *
  21878. * Streaming RPCs are stateful and need to be start()ed before messages can
  21879. * be sent and received. The PersistentStream will call the onOpen() function
  21880. * of the listener once the stream is ready to accept requests.
  21881. *
  21882. * Should a start() fail, PersistentStream will call the registered onClose()
  21883. * listener with a FirestoreError indicating what went wrong.
  21884. *
  21885. * A PersistentStream can be started and stopped repeatedly.
  21886. *
  21887. * Generic types:
  21888. * SendType: The type of the outgoing message of the underlying
  21889. * connection stream
  21890. * ReceiveType: The type of the incoming message of the underlying
  21891. * connection stream
  21892. * ListenerType: The type of the listener that will be used for callbacks
  21893. */
  21894. class PersistentStream {
  21895. constructor(queue, connectionTimerId, idleTimerId, healthTimerId, connection, authCredentialsProvider, appCheckCredentialsProvider, listener) {
  21896. this.queue = queue;
  21897. this.idleTimerId = idleTimerId;
  21898. this.healthTimerId = healthTimerId;
  21899. this.connection = connection;
  21900. this.authCredentialsProvider = authCredentialsProvider;
  21901. this.appCheckCredentialsProvider = appCheckCredentialsProvider;
  21902. this.listener = listener;
  21903. this.state = 0 /* PersistentStreamState.Initial */;
  21904. /**
  21905. * A close count that's incremented every time the stream is closed; used by
  21906. * getCloseGuardedDispatcher() to invalidate callbacks that happen after
  21907. * close.
  21908. */
  21909. this.closeCount = 0;
  21910. this.idleTimer = null;
  21911. this.healthCheck = null;
  21912. this.stream = null;
  21913. this.backoff = new ExponentialBackoff(queue, connectionTimerId);
  21914. }
  21915. /**
  21916. * Returns true if start() has been called and no error has occurred. True
  21917. * indicates the stream is open or in the process of opening (which
  21918. * encompasses respecting backoff, getting auth tokens, and starting the
  21919. * actual RPC). Use isOpen() to determine if the stream is open and ready for
  21920. * outbound requests.
  21921. */
  21922. isStarted() {
  21923. return (this.state === 1 /* PersistentStreamState.Starting */ ||
  21924. this.state === 5 /* PersistentStreamState.Backoff */ ||
  21925. this.isOpen());
  21926. }
  21927. /**
  21928. * Returns true if the underlying RPC is open (the onOpen() listener has been
  21929. * called) and the stream is ready for outbound requests.
  21930. */
  21931. isOpen() {
  21932. return (this.state === 2 /* PersistentStreamState.Open */ ||
  21933. this.state === 3 /* PersistentStreamState.Healthy */);
  21934. }
  21935. /**
  21936. * Starts the RPC. Only allowed if isStarted() returns false. The stream is
  21937. * not immediately ready for use: onOpen() will be invoked when the RPC is
  21938. * ready for outbound requests, at which point isOpen() will return true.
  21939. *
  21940. * When start returns, isStarted() will return true.
  21941. */
  21942. start() {
  21943. if (this.state === 4 /* PersistentStreamState.Error */) {
  21944. this.performBackoff();
  21945. return;
  21946. }
  21947. this.auth();
  21948. }
  21949. /**
  21950. * Stops the RPC. This call is idempotent and allowed regardless of the
  21951. * current isStarted() state.
  21952. *
  21953. * When stop returns, isStarted() and isOpen() will both return false.
  21954. */
  21955. async stop() {
  21956. if (this.isStarted()) {
  21957. await this.close(0 /* PersistentStreamState.Initial */);
  21958. }
  21959. }
  21960. /**
  21961. * After an error the stream will usually back off on the next attempt to
  21962. * start it. If the error warrants an immediate restart of the stream, the
  21963. * sender can use this to indicate that the receiver should not back off.
  21964. *
  21965. * Each error will call the onClose() listener. That function can decide to
  21966. * inhibit backoff if required.
  21967. */
  21968. inhibitBackoff() {
  21969. this.state = 0 /* PersistentStreamState.Initial */;
  21970. this.backoff.reset();
  21971. }
  21972. /**
  21973. * Marks this stream as idle. If no further actions are performed on the
  21974. * stream for one minute, the stream will automatically close itself and
  21975. * notify the stream's onClose() handler with Status.OK. The stream will then
  21976. * be in a !isStarted() state, requiring the caller to start the stream again
  21977. * before further use.
  21978. *
  21979. * Only streams that are in state 'Open' can be marked idle, as all other
  21980. * states imply pending network operations.
  21981. */
  21982. markIdle() {
  21983. // Starts the idle time if we are in state 'Open' and are not yet already
  21984. // running a timer (in which case the previous idle timeout still applies).
  21985. if (this.isOpen() && this.idleTimer === null) {
  21986. this.idleTimer = this.queue.enqueueAfterDelay(this.idleTimerId, IDLE_TIMEOUT_MS, () => this.handleIdleCloseTimer());
  21987. }
  21988. }
  21989. /** Sends a message to the underlying stream. */
  21990. sendRequest(msg) {
  21991. this.cancelIdleCheck();
  21992. this.stream.send(msg);
  21993. }
  21994. /** Called by the idle timer when the stream should close due to inactivity. */
  21995. async handleIdleCloseTimer() {
  21996. if (this.isOpen()) {
  21997. // When timing out an idle stream there's no reason to force the stream into backoff when
  21998. // it restarts so set the stream state to Initial instead of Error.
  21999. return this.close(0 /* PersistentStreamState.Initial */);
  22000. }
  22001. }
  22002. /** Marks the stream as active again. */
  22003. cancelIdleCheck() {
  22004. if (this.idleTimer) {
  22005. this.idleTimer.cancel();
  22006. this.idleTimer = null;
  22007. }
  22008. }
  22009. /** Cancels the health check delayed operation. */
  22010. cancelHealthCheck() {
  22011. if (this.healthCheck) {
  22012. this.healthCheck.cancel();
  22013. this.healthCheck = null;
  22014. }
  22015. }
  22016. /**
  22017. * Closes the stream and cleans up as necessary:
  22018. *
  22019. * * closes the underlying GRPC stream;
  22020. * * calls the onClose handler with the given 'error';
  22021. * * sets internal stream state to 'finalState';
  22022. * * adjusts the backoff timer based on the error
  22023. *
  22024. * A new stream can be opened by calling start().
  22025. *
  22026. * @param finalState - the intended state of the stream after closing.
  22027. * @param error - the error the connection was closed with.
  22028. */
  22029. async close(finalState, error) {
  22030. // Cancel any outstanding timers (they're guaranteed not to execute).
  22031. this.cancelIdleCheck();
  22032. this.cancelHealthCheck();
  22033. this.backoff.cancel();
  22034. // Invalidates any stream-related callbacks (e.g. from auth or the
  22035. // underlying stream), guaranteeing they won't execute.
  22036. this.closeCount++;
  22037. if (finalState !== 4 /* PersistentStreamState.Error */) {
  22038. // If this is an intentional close ensure we don't delay our next connection attempt.
  22039. this.backoff.reset();
  22040. }
  22041. else if (error && error.code === Code.RESOURCE_EXHAUSTED) {
  22042. // Log the error. (Probably either 'quota exceeded' or 'max queue length reached'.)
  22043. logError(error.toString());
  22044. logError('Using maximum backoff delay to prevent overloading the backend.');
  22045. this.backoff.resetToMax();
  22046. }
  22047. else if (error &&
  22048. error.code === Code.UNAUTHENTICATED &&
  22049. this.state !== 3 /* PersistentStreamState.Healthy */) {
  22050. // "unauthenticated" error means the token was rejected. This should rarely
  22051. // happen since both Auth and AppCheck ensure a sufficient TTL when we
  22052. // request a token. If a user manually resets their system clock this can
  22053. // fail, however. In this case, we should get a Code.UNAUTHENTICATED error
  22054. // before we received the first message and we need to invalidate the token
  22055. // to ensure that we fetch a new token.
  22056. this.authCredentialsProvider.invalidateToken();
  22057. this.appCheckCredentialsProvider.invalidateToken();
  22058. }
  22059. // Clean up the underlying stream because we are no longer interested in events.
  22060. if (this.stream !== null) {
  22061. this.tearDown();
  22062. this.stream.close();
  22063. this.stream = null;
  22064. }
  22065. // This state must be assigned before calling onClose() to allow the callback to
  22066. // inhibit backoff or otherwise manipulate the state in its non-started state.
  22067. this.state = finalState;
  22068. // Notify the listener that the stream closed.
  22069. await this.listener.onClose(error);
  22070. }
  22071. /**
  22072. * Can be overridden to perform additional cleanup before the stream is closed.
  22073. * Calling super.tearDown() is not required.
  22074. */
  22075. tearDown() { }
  22076. auth() {
  22077. this.state = 1 /* PersistentStreamState.Starting */;
  22078. const dispatchIfNotClosed = this.getCloseGuardedDispatcher(this.closeCount);
  22079. // TODO(mikelehen): Just use dispatchIfNotClosed, but see TODO below.
  22080. const closeCount = this.closeCount;
  22081. Promise.all([
  22082. this.authCredentialsProvider.getToken(),
  22083. this.appCheckCredentialsProvider.getToken()
  22084. ]).then(([authToken, appCheckToken]) => {
  22085. // Stream can be stopped while waiting for authentication.
  22086. // TODO(mikelehen): We really should just use dispatchIfNotClosed
  22087. // and let this dispatch onto the queue, but that opened a spec test can
  22088. // of worms that I don't want to deal with in this PR.
  22089. if (this.closeCount === closeCount) {
  22090. // Normally we'd have to schedule the callback on the AsyncQueue.
  22091. // However, the following calls are safe to be called outside the
  22092. // AsyncQueue since they don't chain asynchronous calls
  22093. this.startStream(authToken, appCheckToken);
  22094. }
  22095. }, (error) => {
  22096. dispatchIfNotClosed(() => {
  22097. const rpcError = new FirestoreError(Code.UNKNOWN, 'Fetching auth token failed: ' + error.message);
  22098. return this.handleStreamClose(rpcError);
  22099. });
  22100. });
  22101. }
  22102. startStream(authToken, appCheckToken) {
  22103. const dispatchIfNotClosed = this.getCloseGuardedDispatcher(this.closeCount);
  22104. this.stream = this.startRpc(authToken, appCheckToken);
  22105. this.stream.onOpen(() => {
  22106. dispatchIfNotClosed(() => {
  22107. this.state = 2 /* PersistentStreamState.Open */;
  22108. this.healthCheck = this.queue.enqueueAfterDelay(this.healthTimerId, HEALTHY_TIMEOUT_MS, () => {
  22109. if (this.isOpen()) {
  22110. this.state = 3 /* PersistentStreamState.Healthy */;
  22111. }
  22112. return Promise.resolve();
  22113. });
  22114. return this.listener.onOpen();
  22115. });
  22116. });
  22117. this.stream.onClose((error) => {
  22118. dispatchIfNotClosed(() => {
  22119. return this.handleStreamClose(error);
  22120. });
  22121. });
  22122. this.stream.onMessage((msg) => {
  22123. dispatchIfNotClosed(() => {
  22124. return this.onMessage(msg);
  22125. });
  22126. });
  22127. }
  22128. performBackoff() {
  22129. this.state = 5 /* PersistentStreamState.Backoff */;
  22130. this.backoff.backoffAndRun(async () => {
  22131. this.state = 0 /* PersistentStreamState.Initial */;
  22132. this.start();
  22133. });
  22134. }
  22135. // Visible for tests
  22136. handleStreamClose(error) {
  22137. logDebug(LOG_TAG$7, `close with error: ${error}`);
  22138. this.stream = null;
  22139. // In theory the stream could close cleanly, however, in our current model
  22140. // we never expect this to happen because if we stop a stream ourselves,
  22141. // this callback will never be called. To prevent cases where we retry
  22142. // without a backoff accidentally, we set the stream to error in all cases.
  22143. return this.close(4 /* PersistentStreamState.Error */, error);
  22144. }
  22145. /**
  22146. * Returns a "dispatcher" function that dispatches operations onto the
  22147. * AsyncQueue but only runs them if closeCount remains unchanged. This allows
  22148. * us to turn auth / stream callbacks into no-ops if the stream is closed /
  22149. * re-opened, etc.
  22150. */
  22151. getCloseGuardedDispatcher(startCloseCount) {
  22152. return (fn) => {
  22153. this.queue.enqueueAndForget(() => {
  22154. if (this.closeCount === startCloseCount) {
  22155. return fn();
  22156. }
  22157. else {
  22158. logDebug(LOG_TAG$7, 'stream callback skipped by getCloseGuardedDispatcher.');
  22159. return Promise.resolve();
  22160. }
  22161. });
  22162. };
  22163. }
  22164. }
  22165. /**
  22166. * A PersistentStream that implements the Listen RPC.
  22167. *
  22168. * Once the Listen stream has called the onOpen() listener, any number of
  22169. * listen() and unlisten() calls can be made to control what changes will be
  22170. * sent from the server for ListenResponses.
  22171. */
  22172. class PersistentListenStream extends PersistentStream {
  22173. constructor(queue, connection, authCredentials, appCheckCredentials, serializer, listener) {
  22174. super(queue, "listen_stream_connection_backoff" /* TimerId.ListenStreamConnectionBackoff */, "listen_stream_idle" /* TimerId.ListenStreamIdle */, "health_check_timeout" /* TimerId.HealthCheckTimeout */, connection, authCredentials, appCheckCredentials, listener);
  22175. this.serializer = serializer;
  22176. }
  22177. startRpc(authToken, appCheckToken) {
  22178. return this.connection.openStream('Listen', authToken, appCheckToken);
  22179. }
  22180. onMessage(watchChangeProto) {
  22181. // A successful response means the stream is healthy
  22182. this.backoff.reset();
  22183. const watchChange = fromWatchChange(this.serializer, watchChangeProto);
  22184. const snapshot = versionFromListenResponse(watchChangeProto);
  22185. return this.listener.onWatchChange(watchChange, snapshot);
  22186. }
  22187. /**
  22188. * Registers interest in the results of the given target. If the target
  22189. * includes a resumeToken it will be included in the request. Results that
  22190. * affect the target will be streamed back as WatchChange messages that
  22191. * reference the targetId.
  22192. */
  22193. watch(targetData) {
  22194. const request = {};
  22195. request.database = getEncodedDatabaseId(this.serializer);
  22196. request.addTarget = toTarget(this.serializer, targetData);
  22197. const labels = toListenRequestLabels(this.serializer, targetData);
  22198. if (labels) {
  22199. request.labels = labels;
  22200. }
  22201. this.sendRequest(request);
  22202. }
  22203. /**
  22204. * Unregisters interest in the results of the target associated with the
  22205. * given targetId.
  22206. */
  22207. unwatch(targetId) {
  22208. const request = {};
  22209. request.database = getEncodedDatabaseId(this.serializer);
  22210. request.removeTarget = targetId;
  22211. this.sendRequest(request);
  22212. }
  22213. }
  22214. /**
  22215. * A Stream that implements the Write RPC.
  22216. *
  22217. * The Write RPC requires the caller to maintain special streamToken
  22218. * state in between calls, to help the server understand which responses the
  22219. * client has processed by the time the next request is made. Every response
  22220. * will contain a streamToken; this value must be passed to the next
  22221. * request.
  22222. *
  22223. * After calling start() on this stream, the next request must be a handshake,
  22224. * containing whatever streamToken is on hand. Once a response to this
  22225. * request is received, all pending mutations may be submitted. When
  22226. * submitting multiple batches of mutations at the same time, it's
  22227. * okay to use the same streamToken for the calls to writeMutations.
  22228. *
  22229. * TODO(b/33271235): Use proto types
  22230. */
  22231. class PersistentWriteStream extends PersistentStream {
  22232. constructor(queue, connection, authCredentials, appCheckCredentials, serializer, listener) {
  22233. super(queue, "write_stream_connection_backoff" /* TimerId.WriteStreamConnectionBackoff */, "write_stream_idle" /* TimerId.WriteStreamIdle */, "health_check_timeout" /* TimerId.HealthCheckTimeout */, connection, authCredentials, appCheckCredentials, listener);
  22234. this.serializer = serializer;
  22235. this.handshakeComplete_ = false;
  22236. }
  22237. /**
  22238. * Tracks whether or not a handshake has been successfully exchanged and
  22239. * the stream is ready to accept mutations.
  22240. */
  22241. get handshakeComplete() {
  22242. return this.handshakeComplete_;
  22243. }
  22244. // Override of PersistentStream.start
  22245. start() {
  22246. this.handshakeComplete_ = false;
  22247. this.lastStreamToken = undefined;
  22248. super.start();
  22249. }
  22250. tearDown() {
  22251. if (this.handshakeComplete_) {
  22252. this.writeMutations([]);
  22253. }
  22254. }
  22255. startRpc(authToken, appCheckToken) {
  22256. return this.connection.openStream('Write', authToken, appCheckToken);
  22257. }
  22258. onMessage(responseProto) {
  22259. // Always capture the last stream token.
  22260. hardAssert(!!responseProto.streamToken);
  22261. this.lastStreamToken = responseProto.streamToken;
  22262. if (!this.handshakeComplete_) {
  22263. // The first response is always the handshake response
  22264. hardAssert(!responseProto.writeResults || responseProto.writeResults.length === 0);
  22265. this.handshakeComplete_ = true;
  22266. return this.listener.onHandshakeComplete();
  22267. }
  22268. else {
  22269. // A successful first write response means the stream is healthy,
  22270. // Note, that we could consider a successful handshake healthy, however,
  22271. // the write itself might be causing an error we want to back off from.
  22272. this.backoff.reset();
  22273. const results = fromWriteResults(responseProto.writeResults, responseProto.commitTime);
  22274. const commitVersion = fromVersion(responseProto.commitTime);
  22275. return this.listener.onMutationResult(commitVersion, results);
  22276. }
  22277. }
  22278. /**
  22279. * Sends an initial streamToken to the server, performing the handshake
  22280. * required to make the StreamingWrite RPC work. Subsequent
  22281. * calls should wait until onHandshakeComplete was called.
  22282. */
  22283. writeHandshake() {
  22284. // TODO(dimond): Support stream resumption. We intentionally do not set the
  22285. // stream token on the handshake, ignoring any stream token we might have.
  22286. const request = {};
  22287. request.database = getEncodedDatabaseId(this.serializer);
  22288. this.sendRequest(request);
  22289. }
  22290. /** Sends a group of mutations to the Firestore backend to apply. */
  22291. writeMutations(mutations) {
  22292. const request = {
  22293. streamToken: this.lastStreamToken,
  22294. writes: mutations.map(mutation => toMutation(this.serializer, mutation))
  22295. };
  22296. this.sendRequest(request);
  22297. }
  22298. }
  22299. /**
  22300. * @license
  22301. * Copyright 2017 Google LLC
  22302. *
  22303. * Licensed under the Apache License, Version 2.0 (the "License");
  22304. * you may not use this file except in compliance with the License.
  22305. * You may obtain a copy of the License at
  22306. *
  22307. * http://www.apache.org/licenses/LICENSE-2.0
  22308. *
  22309. * Unless required by applicable law or agreed to in writing, software
  22310. * distributed under the License is distributed on an "AS IS" BASIS,
  22311. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22312. * See the License for the specific language governing permissions and
  22313. * limitations under the License.
  22314. */
  22315. /**
  22316. * Datastore and its related methods are a wrapper around the external Google
  22317. * Cloud Datastore grpc API, which provides an interface that is more convenient
  22318. * for the rest of the client SDK architecture to consume.
  22319. */
  22320. class Datastore {
  22321. }
  22322. /**
  22323. * An implementation of Datastore that exposes additional state for internal
  22324. * consumption.
  22325. */
  22326. class DatastoreImpl extends Datastore {
  22327. constructor(authCredentials, appCheckCredentials, connection, serializer) {
  22328. super();
  22329. this.authCredentials = authCredentials;
  22330. this.appCheckCredentials = appCheckCredentials;
  22331. this.connection = connection;
  22332. this.serializer = serializer;
  22333. this.terminated = false;
  22334. }
  22335. verifyInitialized() {
  22336. if (this.terminated) {
  22337. throw new FirestoreError(Code.FAILED_PRECONDITION, 'The client has already been terminated.');
  22338. }
  22339. }
  22340. /** Invokes the provided RPC with auth and AppCheck tokens. */
  22341. invokeRPC(rpcName, path, request) {
  22342. this.verifyInitialized();
  22343. return Promise.all([
  22344. this.authCredentials.getToken(),
  22345. this.appCheckCredentials.getToken()
  22346. ])
  22347. .then(([authToken, appCheckToken]) => {
  22348. return this.connection.invokeRPC(rpcName, path, request, authToken, appCheckToken);
  22349. })
  22350. .catch((error) => {
  22351. if (error.name === 'FirebaseError') {
  22352. if (error.code === Code.UNAUTHENTICATED) {
  22353. this.authCredentials.invalidateToken();
  22354. this.appCheckCredentials.invalidateToken();
  22355. }
  22356. throw error;
  22357. }
  22358. else {
  22359. throw new FirestoreError(Code.UNKNOWN, error.toString());
  22360. }
  22361. });
  22362. }
  22363. /** Invokes the provided RPC with streamed results with auth and AppCheck tokens. */
  22364. invokeStreamingRPC(rpcName, path, request, expectedResponseCount) {
  22365. this.verifyInitialized();
  22366. return Promise.all([
  22367. this.authCredentials.getToken(),
  22368. this.appCheckCredentials.getToken()
  22369. ])
  22370. .then(([authToken, appCheckToken]) => {
  22371. return this.connection.invokeStreamingRPC(rpcName, path, request, authToken, appCheckToken, expectedResponseCount);
  22372. })
  22373. .catch((error) => {
  22374. if (error.name === 'FirebaseError') {
  22375. if (error.code === Code.UNAUTHENTICATED) {
  22376. this.authCredentials.invalidateToken();
  22377. this.appCheckCredentials.invalidateToken();
  22378. }
  22379. throw error;
  22380. }
  22381. else {
  22382. throw new FirestoreError(Code.UNKNOWN, error.toString());
  22383. }
  22384. });
  22385. }
  22386. terminate() {
  22387. this.terminated = true;
  22388. }
  22389. }
  22390. // TODO(firestorexp): Make sure there is only one Datastore instance per
  22391. // firestore-exp client.
  22392. function newDatastore(authCredentials, appCheckCredentials, connection, serializer) {
  22393. return new DatastoreImpl(authCredentials, appCheckCredentials, connection, serializer);
  22394. }
  22395. async function invokeCommitRpc(datastore, mutations) {
  22396. const datastoreImpl = debugCast(datastore);
  22397. const path = getEncodedDatabaseId(datastoreImpl.serializer) + '/documents';
  22398. const request = {
  22399. writes: mutations.map(m => toMutation(datastoreImpl.serializer, m))
  22400. };
  22401. await datastoreImpl.invokeRPC('Commit', path, request);
  22402. }
  22403. async function invokeBatchGetDocumentsRpc(datastore, keys) {
  22404. const datastoreImpl = debugCast(datastore);
  22405. const path = getEncodedDatabaseId(datastoreImpl.serializer) + '/documents';
  22406. const request = {
  22407. documents: keys.map(k => toName(datastoreImpl.serializer, k))
  22408. };
  22409. const response = await datastoreImpl.invokeStreamingRPC('BatchGetDocuments', path, request, keys.length);
  22410. const docs = new Map();
  22411. response.forEach(proto => {
  22412. const doc = fromBatchGetDocumentsResponse(datastoreImpl.serializer, proto);
  22413. docs.set(doc.key.toString(), doc);
  22414. });
  22415. const result = [];
  22416. keys.forEach(key => {
  22417. const doc = docs.get(key.toString());
  22418. hardAssert(!!doc);
  22419. result.push(doc);
  22420. });
  22421. return result;
  22422. }
  22423. async function invokeRunAggregationQueryRpc(datastore, query, aggregates) {
  22424. var _a;
  22425. const datastoreImpl = debugCast(datastore);
  22426. const { request, aliasMap } = toRunAggregationQueryRequest(datastoreImpl.serializer, queryToTarget(query), aggregates);
  22427. const parent = request.parent;
  22428. if (!datastoreImpl.connection.shouldResourcePathBeIncludedInRequest) {
  22429. delete request.parent;
  22430. }
  22431. const response = await datastoreImpl.invokeStreamingRPC('RunAggregationQuery', parent, request, /*expectedResponseCount=*/ 1);
  22432. // Omit RunAggregationQueryResponse that only contain readTimes.
  22433. const filteredResult = response.filter(proto => !!proto.result);
  22434. hardAssert(filteredResult.length === 1);
  22435. // Remap the short-form aliases that were sent to the server
  22436. // to the client-side aliases. Users will access the results
  22437. // using the client-side alias.
  22438. const unmappedAggregateFields = (_a = filteredResult[0].result) === null || _a === void 0 ? void 0 : _a.aggregateFields;
  22439. const remappedFields = Object.keys(unmappedAggregateFields).reduce((accumulator, key) => {
  22440. accumulator[aliasMap[key]] = unmappedAggregateFields[key];
  22441. return accumulator;
  22442. }, {});
  22443. return remappedFields;
  22444. }
  22445. function newPersistentWriteStream(datastore, queue, listener) {
  22446. const datastoreImpl = debugCast(datastore);
  22447. datastoreImpl.verifyInitialized();
  22448. return new PersistentWriteStream(queue, datastoreImpl.connection, datastoreImpl.authCredentials, datastoreImpl.appCheckCredentials, datastoreImpl.serializer, listener);
  22449. }
  22450. function newPersistentWatchStream(datastore, queue, listener) {
  22451. const datastoreImpl = debugCast(datastore);
  22452. datastoreImpl.verifyInitialized();
  22453. return new PersistentListenStream(queue, datastoreImpl.connection, datastoreImpl.authCredentials, datastoreImpl.appCheckCredentials, datastoreImpl.serializer, listener);
  22454. }
  22455. /**
  22456. * @license
  22457. * Copyright 2018 Google LLC
  22458. *
  22459. * Licensed under the Apache License, Version 2.0 (the "License");
  22460. * you may not use this file except in compliance with the License.
  22461. * You may obtain a copy of the License at
  22462. *
  22463. * http://www.apache.org/licenses/LICENSE-2.0
  22464. *
  22465. * Unless required by applicable law or agreed to in writing, software
  22466. * distributed under the License is distributed on an "AS IS" BASIS,
  22467. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22468. * See the License for the specific language governing permissions and
  22469. * limitations under the License.
  22470. */
  22471. const LOG_TAG$6 = 'OnlineStateTracker';
  22472. // To deal with transient failures, we allow multiple stream attempts before
  22473. // giving up and transitioning from OnlineState.Unknown to Offline.
  22474. // TODO(mikelehen): This used to be set to 2 as a mitigation for b/66228394.
  22475. // @jdimond thinks that bug is sufficiently fixed so that we can set this back
  22476. // to 1. If that works okay, we could potentially remove this logic entirely.
  22477. const MAX_WATCH_STREAM_FAILURES = 1;
  22478. // To deal with stream attempts that don't succeed or fail in a timely manner,
  22479. // we have a timeout for OnlineState to reach Online or Offline.
  22480. // If the timeout is reached, we transition to Offline rather than waiting
  22481. // indefinitely.
  22482. const ONLINE_STATE_TIMEOUT_MS = 10 * 1000;
  22483. /**
  22484. * A component used by the RemoteStore to track the OnlineState (that is,
  22485. * whether or not the client as a whole should be considered to be online or
  22486. * offline), implementing the appropriate heuristics.
  22487. *
  22488. * In particular, when the client is trying to connect to the backend, we
  22489. * allow up to MAX_WATCH_STREAM_FAILURES within ONLINE_STATE_TIMEOUT_MS for
  22490. * a connection to succeed. If we have too many failures or the timeout elapses,
  22491. * then we set the OnlineState to Offline, and the client will behave as if
  22492. * it is offline (get()s will return cached data, etc.).
  22493. */
  22494. class OnlineStateTracker {
  22495. constructor(asyncQueue, onlineStateHandler) {
  22496. this.asyncQueue = asyncQueue;
  22497. this.onlineStateHandler = onlineStateHandler;
  22498. /** The current OnlineState. */
  22499. this.state = "Unknown" /* OnlineState.Unknown */;
  22500. /**
  22501. * A count of consecutive failures to open the stream. If it reaches the
  22502. * maximum defined by MAX_WATCH_STREAM_FAILURES, we'll set the OnlineState to
  22503. * Offline.
  22504. */
  22505. this.watchStreamFailures = 0;
  22506. /**
  22507. * A timer that elapses after ONLINE_STATE_TIMEOUT_MS, at which point we
  22508. * transition from OnlineState.Unknown to OnlineState.Offline without waiting
  22509. * for the stream to actually fail (MAX_WATCH_STREAM_FAILURES times).
  22510. */
  22511. this.onlineStateTimer = null;
  22512. /**
  22513. * Whether the client should log a warning message if it fails to connect to
  22514. * the backend (initially true, cleared after a successful stream, or if we've
  22515. * logged the message already).
  22516. */
  22517. this.shouldWarnClientIsOffline = true;
  22518. }
  22519. /**
  22520. * Called by RemoteStore when a watch stream is started (including on each
  22521. * backoff attempt).
  22522. *
  22523. * If this is the first attempt, it sets the OnlineState to Unknown and starts
  22524. * the onlineStateTimer.
  22525. */
  22526. handleWatchStreamStart() {
  22527. if (this.watchStreamFailures === 0) {
  22528. this.setAndBroadcast("Unknown" /* OnlineState.Unknown */);
  22529. this.onlineStateTimer = this.asyncQueue.enqueueAfterDelay("online_state_timeout" /* TimerId.OnlineStateTimeout */, ONLINE_STATE_TIMEOUT_MS, () => {
  22530. this.onlineStateTimer = null;
  22531. this.logClientOfflineWarningIfNecessary(`Backend didn't respond within ${ONLINE_STATE_TIMEOUT_MS / 1000} ` +
  22532. `seconds.`);
  22533. this.setAndBroadcast("Offline" /* OnlineState.Offline */);
  22534. // NOTE: handleWatchStreamFailure() will continue to increment
  22535. // watchStreamFailures even though we are already marked Offline,
  22536. // but this is non-harmful.
  22537. return Promise.resolve();
  22538. });
  22539. }
  22540. }
  22541. /**
  22542. * Updates our OnlineState as appropriate after the watch stream reports a
  22543. * failure. The first failure moves us to the 'Unknown' state. We then may
  22544. * allow multiple failures (based on MAX_WATCH_STREAM_FAILURES) before we
  22545. * actually transition to the 'Offline' state.
  22546. */
  22547. handleWatchStreamFailure(error) {
  22548. if (this.state === "Online" /* OnlineState.Online */) {
  22549. this.setAndBroadcast("Unknown" /* OnlineState.Unknown */);
  22550. }
  22551. else {
  22552. this.watchStreamFailures++;
  22553. if (this.watchStreamFailures >= MAX_WATCH_STREAM_FAILURES) {
  22554. this.clearOnlineStateTimer();
  22555. this.logClientOfflineWarningIfNecessary(`Connection failed ${MAX_WATCH_STREAM_FAILURES} ` +
  22556. `times. Most recent error: ${error.toString()}`);
  22557. this.setAndBroadcast("Offline" /* OnlineState.Offline */);
  22558. }
  22559. }
  22560. }
  22561. /**
  22562. * Explicitly sets the OnlineState to the specified state.
  22563. *
  22564. * Note that this resets our timers / failure counters, etc. used by our
  22565. * Offline heuristics, so must not be used in place of
  22566. * handleWatchStreamStart() and handleWatchStreamFailure().
  22567. */
  22568. set(newState) {
  22569. this.clearOnlineStateTimer();
  22570. this.watchStreamFailures = 0;
  22571. if (newState === "Online" /* OnlineState.Online */) {
  22572. // We've connected to watch at least once. Don't warn the developer
  22573. // about being offline going forward.
  22574. this.shouldWarnClientIsOffline = false;
  22575. }
  22576. this.setAndBroadcast(newState);
  22577. }
  22578. setAndBroadcast(newState) {
  22579. if (newState !== this.state) {
  22580. this.state = newState;
  22581. this.onlineStateHandler(newState);
  22582. }
  22583. }
  22584. logClientOfflineWarningIfNecessary(details) {
  22585. const message = `Could not reach Cloud Firestore backend. ${details}\n` +
  22586. `This typically indicates that your device does not have a healthy ` +
  22587. `Internet connection at the moment. The client will operate in offline ` +
  22588. `mode until it is able to successfully connect to the backend.`;
  22589. if (this.shouldWarnClientIsOffline) {
  22590. logError(message);
  22591. this.shouldWarnClientIsOffline = false;
  22592. }
  22593. else {
  22594. logDebug(LOG_TAG$6, message);
  22595. }
  22596. }
  22597. clearOnlineStateTimer() {
  22598. if (this.onlineStateTimer !== null) {
  22599. this.onlineStateTimer.cancel();
  22600. this.onlineStateTimer = null;
  22601. }
  22602. }
  22603. }
  22604. /**
  22605. * @license
  22606. * Copyright 2017 Google LLC
  22607. *
  22608. * Licensed under the Apache License, Version 2.0 (the "License");
  22609. * you may not use this file except in compliance with the License.
  22610. * You may obtain a copy of the License at
  22611. *
  22612. * http://www.apache.org/licenses/LICENSE-2.0
  22613. *
  22614. * Unless required by applicable law or agreed to in writing, software
  22615. * distributed under the License is distributed on an "AS IS" BASIS,
  22616. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22617. * See the License for the specific language governing permissions and
  22618. * limitations under the License.
  22619. */
  22620. const LOG_TAG$5 = 'RemoteStore';
  22621. // TODO(b/35853402): Negotiate this with the stream.
  22622. const MAX_PENDING_WRITES = 10;
  22623. class RemoteStoreImpl {
  22624. constructor(
  22625. /**
  22626. * The local store, used to fill the write pipeline with outbound mutations.
  22627. */
  22628. localStore,
  22629. /** The client-side proxy for interacting with the backend. */
  22630. datastore, asyncQueue, onlineStateHandler, connectivityMonitor) {
  22631. this.localStore = localStore;
  22632. this.datastore = datastore;
  22633. this.asyncQueue = asyncQueue;
  22634. this.remoteSyncer = {};
  22635. /**
  22636. * A list of up to MAX_PENDING_WRITES writes that we have fetched from the
  22637. * LocalStore via fillWritePipeline() and have or will send to the write
  22638. * stream.
  22639. *
  22640. * Whenever writePipeline.length > 0 the RemoteStore will attempt to start or
  22641. * restart the write stream. When the stream is established the writes in the
  22642. * pipeline will be sent in order.
  22643. *
  22644. * Writes remain in writePipeline until they are acknowledged by the backend
  22645. * and thus will automatically be re-sent if the stream is interrupted /
  22646. * restarted before they're acknowledged.
  22647. *
  22648. * Write responses from the backend are linked to their originating request
  22649. * purely based on order, and so we can just shift() writes from the front of
  22650. * the writePipeline as we receive responses.
  22651. */
  22652. this.writePipeline = [];
  22653. /**
  22654. * A mapping of watched targets that the client cares about tracking and the
  22655. * user has explicitly called a 'listen' for this target.
  22656. *
  22657. * These targets may or may not have been sent to or acknowledged by the
  22658. * server. On re-establishing the listen stream, these targets should be sent
  22659. * to the server. The targets removed with unlistens are removed eagerly
  22660. * without waiting for confirmation from the listen stream.
  22661. */
  22662. this.listenTargets = new Map();
  22663. /**
  22664. * A set of reasons for why the RemoteStore may be offline. If empty, the
  22665. * RemoteStore may start its network connections.
  22666. */
  22667. this.offlineCauses = new Set();
  22668. /**
  22669. * Event handlers that get called when the network is disabled or enabled.
  22670. *
  22671. * PORTING NOTE: These functions are used on the Web client to create the
  22672. * underlying streams (to support tree-shakeable streams). On Android and iOS,
  22673. * the streams are created during construction of RemoteStore.
  22674. */
  22675. this.onNetworkStatusChange = [];
  22676. this.connectivityMonitor = connectivityMonitor;
  22677. this.connectivityMonitor.addCallback((_) => {
  22678. asyncQueue.enqueueAndForget(async () => {
  22679. // Porting Note: Unlike iOS, `restartNetwork()` is called even when the
  22680. // network becomes unreachable as we don't have any other way to tear
  22681. // down our streams.
  22682. if (canUseNetwork(this)) {
  22683. logDebug(LOG_TAG$5, 'Restarting streams for network reachability change.');
  22684. await restartNetwork(this);
  22685. }
  22686. });
  22687. });
  22688. this.onlineStateTracker = new OnlineStateTracker(asyncQueue, onlineStateHandler);
  22689. }
  22690. }
  22691. function newRemoteStore(localStore, datastore, asyncQueue, onlineStateHandler, connectivityMonitor) {
  22692. return new RemoteStoreImpl(localStore, datastore, asyncQueue, onlineStateHandler, connectivityMonitor);
  22693. }
  22694. /** Re-enables the network. Idempotent. */
  22695. function remoteStoreEnableNetwork(remoteStore) {
  22696. const remoteStoreImpl = debugCast(remoteStore);
  22697. remoteStoreImpl.offlineCauses.delete(0 /* OfflineCause.UserDisabled */);
  22698. return enableNetworkInternal(remoteStoreImpl);
  22699. }
  22700. async function enableNetworkInternal(remoteStoreImpl) {
  22701. if (canUseNetwork(remoteStoreImpl)) {
  22702. for (const networkStatusHandler of remoteStoreImpl.onNetworkStatusChange) {
  22703. await networkStatusHandler(/* enabled= */ true);
  22704. }
  22705. }
  22706. }
  22707. /**
  22708. * Temporarily disables the network. The network can be re-enabled using
  22709. * enableNetwork().
  22710. */
  22711. async function remoteStoreDisableNetwork(remoteStore) {
  22712. const remoteStoreImpl = debugCast(remoteStore);
  22713. remoteStoreImpl.offlineCauses.add(0 /* OfflineCause.UserDisabled */);
  22714. await disableNetworkInternal(remoteStoreImpl);
  22715. // Set the OnlineState to Offline so get()s return from cache, etc.
  22716. remoteStoreImpl.onlineStateTracker.set("Offline" /* OnlineState.Offline */);
  22717. }
  22718. async function disableNetworkInternal(remoteStoreImpl) {
  22719. for (const networkStatusHandler of remoteStoreImpl.onNetworkStatusChange) {
  22720. await networkStatusHandler(/* enabled= */ false);
  22721. }
  22722. }
  22723. async function remoteStoreShutdown(remoteStore) {
  22724. const remoteStoreImpl = debugCast(remoteStore);
  22725. logDebug(LOG_TAG$5, 'RemoteStore shutting down.');
  22726. remoteStoreImpl.offlineCauses.add(5 /* OfflineCause.Shutdown */);
  22727. await disableNetworkInternal(remoteStoreImpl);
  22728. remoteStoreImpl.connectivityMonitor.shutdown();
  22729. // Set the OnlineState to Unknown (rather than Offline) to avoid potentially
  22730. // triggering spurious listener events with cached data, etc.
  22731. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22732. }
  22733. /**
  22734. * Starts new listen for the given target. Uses resume token if provided. It
  22735. * is a no-op if the target of given `TargetData` is already being listened to.
  22736. */
  22737. function remoteStoreListen(remoteStore, targetData) {
  22738. const remoteStoreImpl = debugCast(remoteStore);
  22739. if (remoteStoreImpl.listenTargets.has(targetData.targetId)) {
  22740. return;
  22741. }
  22742. // Mark this as something the client is currently listening for.
  22743. remoteStoreImpl.listenTargets.set(targetData.targetId, targetData);
  22744. if (shouldStartWatchStream(remoteStoreImpl)) {
  22745. // The listen will be sent in onWatchStreamOpen
  22746. startWatchStream(remoteStoreImpl);
  22747. }
  22748. else if (ensureWatchStream(remoteStoreImpl).isOpen()) {
  22749. sendWatchRequest(remoteStoreImpl, targetData);
  22750. }
  22751. }
  22752. /**
  22753. * Removes the listen from server. It is a no-op if the given target id is
  22754. * not being listened to.
  22755. */
  22756. function remoteStoreUnlisten(remoteStore, targetId) {
  22757. const remoteStoreImpl = debugCast(remoteStore);
  22758. const watchStream = ensureWatchStream(remoteStoreImpl);
  22759. remoteStoreImpl.listenTargets.delete(targetId);
  22760. if (watchStream.isOpen()) {
  22761. sendUnwatchRequest(remoteStoreImpl, targetId);
  22762. }
  22763. if (remoteStoreImpl.listenTargets.size === 0) {
  22764. if (watchStream.isOpen()) {
  22765. watchStream.markIdle();
  22766. }
  22767. else if (canUseNetwork(remoteStoreImpl)) {
  22768. // Revert to OnlineState.Unknown if the watch stream is not open and we
  22769. // have no listeners, since without any listens to send we cannot
  22770. // confirm if the stream is healthy and upgrade to OnlineState.Online.
  22771. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22772. }
  22773. }
  22774. }
  22775. /**
  22776. * We need to increment the the expected number of pending responses we're due
  22777. * from watch so we wait for the ack to process any messages from this target.
  22778. */
  22779. function sendWatchRequest(remoteStoreImpl, targetData) {
  22780. remoteStoreImpl.watchChangeAggregator.recordPendingTargetRequest(targetData.targetId);
  22781. if (targetData.resumeToken.approximateByteSize() > 0 ||
  22782. targetData.snapshotVersion.compareTo(SnapshotVersion.min()) > 0) {
  22783. const expectedCount = remoteStoreImpl.remoteSyncer.getRemoteKeysForTarget(targetData.targetId).size;
  22784. targetData = targetData.withExpectedCount(expectedCount);
  22785. }
  22786. ensureWatchStream(remoteStoreImpl).watch(targetData);
  22787. }
  22788. /**
  22789. * We need to increment the expected number of pending responses we're due
  22790. * from watch so we wait for the removal on the server before we process any
  22791. * messages from this target.
  22792. */
  22793. function sendUnwatchRequest(remoteStoreImpl, targetId) {
  22794. remoteStoreImpl.watchChangeAggregator.recordPendingTargetRequest(targetId);
  22795. ensureWatchStream(remoteStoreImpl).unwatch(targetId);
  22796. }
  22797. function startWatchStream(remoteStoreImpl) {
  22798. remoteStoreImpl.watchChangeAggregator = new WatchChangeAggregator({
  22799. getRemoteKeysForTarget: targetId => remoteStoreImpl.remoteSyncer.getRemoteKeysForTarget(targetId),
  22800. getTargetDataForTarget: targetId => remoteStoreImpl.listenTargets.get(targetId) || null,
  22801. getDatabaseId: () => remoteStoreImpl.datastore.serializer.databaseId
  22802. });
  22803. ensureWatchStream(remoteStoreImpl).start();
  22804. remoteStoreImpl.onlineStateTracker.handleWatchStreamStart();
  22805. }
  22806. /**
  22807. * Returns whether the watch stream should be started because it's necessary
  22808. * and has not yet been started.
  22809. */
  22810. function shouldStartWatchStream(remoteStoreImpl) {
  22811. return (canUseNetwork(remoteStoreImpl) &&
  22812. !ensureWatchStream(remoteStoreImpl).isStarted() &&
  22813. remoteStoreImpl.listenTargets.size > 0);
  22814. }
  22815. function canUseNetwork(remoteStore) {
  22816. const remoteStoreImpl = debugCast(remoteStore);
  22817. return remoteStoreImpl.offlineCauses.size === 0;
  22818. }
  22819. function cleanUpWatchStreamState(remoteStoreImpl) {
  22820. remoteStoreImpl.watchChangeAggregator = undefined;
  22821. }
  22822. async function onWatchStreamOpen(remoteStoreImpl) {
  22823. remoteStoreImpl.listenTargets.forEach((targetData, targetId) => {
  22824. sendWatchRequest(remoteStoreImpl, targetData);
  22825. });
  22826. }
  22827. async function onWatchStreamClose(remoteStoreImpl, error) {
  22828. cleanUpWatchStreamState(remoteStoreImpl);
  22829. // If we still need the watch stream, retry the connection.
  22830. if (shouldStartWatchStream(remoteStoreImpl)) {
  22831. remoteStoreImpl.onlineStateTracker.handleWatchStreamFailure(error);
  22832. startWatchStream(remoteStoreImpl);
  22833. }
  22834. else {
  22835. // No need to restart watch stream because there are no active targets.
  22836. // The online state is set to unknown because there is no active attempt
  22837. // at establishing a connection
  22838. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22839. }
  22840. }
  22841. async function onWatchStreamChange(remoteStoreImpl, watchChange, snapshotVersion) {
  22842. // Mark the client as online since we got a message from the server
  22843. remoteStoreImpl.onlineStateTracker.set("Online" /* OnlineState.Online */);
  22844. if (watchChange instanceof WatchTargetChange &&
  22845. watchChange.state === 2 /* WatchTargetChangeState.Removed */ &&
  22846. watchChange.cause) {
  22847. // There was an error on a target, don't wait for a consistent snapshot
  22848. // to raise events
  22849. try {
  22850. await handleTargetError(remoteStoreImpl, watchChange);
  22851. }
  22852. catch (e) {
  22853. logDebug(LOG_TAG$5, 'Failed to remove targets %s: %s ', watchChange.targetIds.join(','), e);
  22854. await disableNetworkUntilRecovery(remoteStoreImpl, e);
  22855. }
  22856. return;
  22857. }
  22858. if (watchChange instanceof DocumentWatchChange) {
  22859. remoteStoreImpl.watchChangeAggregator.handleDocumentChange(watchChange);
  22860. }
  22861. else if (watchChange instanceof ExistenceFilterChange) {
  22862. remoteStoreImpl.watchChangeAggregator.handleExistenceFilter(watchChange);
  22863. }
  22864. else {
  22865. remoteStoreImpl.watchChangeAggregator.handleTargetChange(watchChange);
  22866. }
  22867. if (!snapshotVersion.isEqual(SnapshotVersion.min())) {
  22868. try {
  22869. const lastRemoteSnapshotVersion = await localStoreGetLastRemoteSnapshotVersion(remoteStoreImpl.localStore);
  22870. if (snapshotVersion.compareTo(lastRemoteSnapshotVersion) >= 0) {
  22871. // We have received a target change with a global snapshot if the snapshot
  22872. // version is not equal to SnapshotVersion.min().
  22873. await raiseWatchSnapshot(remoteStoreImpl, snapshotVersion);
  22874. }
  22875. }
  22876. catch (e) {
  22877. logDebug(LOG_TAG$5, 'Failed to raise snapshot:', e);
  22878. await disableNetworkUntilRecovery(remoteStoreImpl, e);
  22879. }
  22880. }
  22881. }
  22882. /**
  22883. * Recovery logic for IndexedDB errors that takes the network offline until
  22884. * `op` succeeds. Retries are scheduled with backoff using
  22885. * `enqueueRetryable()`. If `op()` is not provided, IndexedDB access is
  22886. * validated via a generic operation.
  22887. *
  22888. * The returned Promise is resolved once the network is disabled and before
  22889. * any retry attempt.
  22890. */
  22891. async function disableNetworkUntilRecovery(remoteStoreImpl, e, op) {
  22892. if (isIndexedDbTransactionError(e)) {
  22893. remoteStoreImpl.offlineCauses.add(1 /* OfflineCause.IndexedDbFailed */);
  22894. // Disable network and raise offline snapshots
  22895. await disableNetworkInternal(remoteStoreImpl);
  22896. remoteStoreImpl.onlineStateTracker.set("Offline" /* OnlineState.Offline */);
  22897. if (!op) {
  22898. // Use a simple read operation to determine if IndexedDB recovered.
  22899. // Ideally, we would expose a health check directly on SimpleDb, but
  22900. // RemoteStore only has access to persistence through LocalStore.
  22901. op = () => localStoreGetLastRemoteSnapshotVersion(remoteStoreImpl.localStore);
  22902. }
  22903. // Probe IndexedDB periodically and re-enable network
  22904. remoteStoreImpl.asyncQueue.enqueueRetryable(async () => {
  22905. logDebug(LOG_TAG$5, 'Retrying IndexedDB access');
  22906. await op();
  22907. remoteStoreImpl.offlineCauses.delete(1 /* OfflineCause.IndexedDbFailed */);
  22908. await enableNetworkInternal(remoteStoreImpl);
  22909. });
  22910. }
  22911. else {
  22912. throw e;
  22913. }
  22914. }
  22915. /**
  22916. * Executes `op`. If `op` fails, takes the network offline until `op`
  22917. * succeeds. Returns after the first attempt.
  22918. */
  22919. function executeWithRecovery(remoteStoreImpl, op) {
  22920. return op().catch(e => disableNetworkUntilRecovery(remoteStoreImpl, e, op));
  22921. }
  22922. /**
  22923. * Takes a batch of changes from the Datastore, repackages them as a
  22924. * RemoteEvent, and passes that on to the listener, which is typically the
  22925. * SyncEngine.
  22926. */
  22927. function raiseWatchSnapshot(remoteStoreImpl, snapshotVersion) {
  22928. const remoteEvent = remoteStoreImpl.watchChangeAggregator.createRemoteEvent(snapshotVersion);
  22929. // Update in-memory resume tokens. LocalStore will update the
  22930. // persistent view of these when applying the completed RemoteEvent.
  22931. remoteEvent.targetChanges.forEach((change, targetId) => {
  22932. if (change.resumeToken.approximateByteSize() > 0) {
  22933. const targetData = remoteStoreImpl.listenTargets.get(targetId);
  22934. // A watched target might have been removed already.
  22935. if (targetData) {
  22936. remoteStoreImpl.listenTargets.set(targetId, targetData.withResumeToken(change.resumeToken, snapshotVersion));
  22937. }
  22938. }
  22939. });
  22940. // Re-establish listens for the targets that have been invalidated by
  22941. // existence filter mismatches.
  22942. remoteEvent.targetMismatches.forEach((targetId, targetPurpose) => {
  22943. const targetData = remoteStoreImpl.listenTargets.get(targetId);
  22944. if (!targetData) {
  22945. // A watched target might have been removed already.
  22946. return;
  22947. }
  22948. // Clear the resume token for the target, since we're in a known mismatch
  22949. // state.
  22950. remoteStoreImpl.listenTargets.set(targetId, targetData.withResumeToken(ByteString.EMPTY_BYTE_STRING, targetData.snapshotVersion));
  22951. // Cause a hard reset by unwatching and rewatching immediately, but
  22952. // deliberately don't send a resume token so that we get a full update.
  22953. sendUnwatchRequest(remoteStoreImpl, targetId);
  22954. // Mark the target we send as being on behalf of an existence filter
  22955. // mismatch, but don't actually retain that in listenTargets. This ensures
  22956. // that we flag the first re-listen this way without impacting future
  22957. // listens of this target (that might happen e.g. on reconnect).
  22958. const requestTargetData = new TargetData(targetData.target, targetId, targetPurpose, targetData.sequenceNumber);
  22959. sendWatchRequest(remoteStoreImpl, requestTargetData);
  22960. });
  22961. return remoteStoreImpl.remoteSyncer.applyRemoteEvent(remoteEvent);
  22962. }
  22963. /** Handles an error on a target */
  22964. async function handleTargetError(remoteStoreImpl, watchChange) {
  22965. const error = watchChange.cause;
  22966. for (const targetId of watchChange.targetIds) {
  22967. // A watched target might have been removed already.
  22968. if (remoteStoreImpl.listenTargets.has(targetId)) {
  22969. await remoteStoreImpl.remoteSyncer.rejectListen(targetId, error);
  22970. remoteStoreImpl.listenTargets.delete(targetId);
  22971. remoteStoreImpl.watchChangeAggregator.removeTarget(targetId);
  22972. }
  22973. }
  22974. }
  22975. /**
  22976. * Attempts to fill our write pipeline with writes from the LocalStore.
  22977. *
  22978. * Called internally to bootstrap or refill the write pipeline and by
  22979. * SyncEngine whenever there are new mutations to process.
  22980. *
  22981. * Starts the write stream if necessary.
  22982. */
  22983. async function fillWritePipeline(remoteStore) {
  22984. const remoteStoreImpl = debugCast(remoteStore);
  22985. const writeStream = ensureWriteStream(remoteStoreImpl);
  22986. let lastBatchIdRetrieved = remoteStoreImpl.writePipeline.length > 0
  22987. ? remoteStoreImpl.writePipeline[remoteStoreImpl.writePipeline.length - 1]
  22988. .batchId
  22989. : BATCHID_UNKNOWN;
  22990. while (canAddToWritePipeline(remoteStoreImpl)) {
  22991. try {
  22992. const batch = await localStoreGetNextMutationBatch(remoteStoreImpl.localStore, lastBatchIdRetrieved);
  22993. if (batch === null) {
  22994. if (remoteStoreImpl.writePipeline.length === 0) {
  22995. writeStream.markIdle();
  22996. }
  22997. break;
  22998. }
  22999. else {
  23000. lastBatchIdRetrieved = batch.batchId;
  23001. addToWritePipeline(remoteStoreImpl, batch);
  23002. }
  23003. }
  23004. catch (e) {
  23005. await disableNetworkUntilRecovery(remoteStoreImpl, e);
  23006. }
  23007. }
  23008. if (shouldStartWriteStream(remoteStoreImpl)) {
  23009. startWriteStream(remoteStoreImpl);
  23010. }
  23011. }
  23012. /**
  23013. * Returns true if we can add to the write pipeline (i.e. the network is
  23014. * enabled and the write pipeline is not full).
  23015. */
  23016. function canAddToWritePipeline(remoteStoreImpl) {
  23017. return (canUseNetwork(remoteStoreImpl) &&
  23018. remoteStoreImpl.writePipeline.length < MAX_PENDING_WRITES);
  23019. }
  23020. /**
  23021. * Queues additional writes to be sent to the write stream, sending them
  23022. * immediately if the write stream is established.
  23023. */
  23024. function addToWritePipeline(remoteStoreImpl, batch) {
  23025. remoteStoreImpl.writePipeline.push(batch);
  23026. const writeStream = ensureWriteStream(remoteStoreImpl);
  23027. if (writeStream.isOpen() && writeStream.handshakeComplete) {
  23028. writeStream.writeMutations(batch.mutations);
  23029. }
  23030. }
  23031. function shouldStartWriteStream(remoteStoreImpl) {
  23032. return (canUseNetwork(remoteStoreImpl) &&
  23033. !ensureWriteStream(remoteStoreImpl).isStarted() &&
  23034. remoteStoreImpl.writePipeline.length > 0);
  23035. }
  23036. function startWriteStream(remoteStoreImpl) {
  23037. ensureWriteStream(remoteStoreImpl).start();
  23038. }
  23039. async function onWriteStreamOpen(remoteStoreImpl) {
  23040. ensureWriteStream(remoteStoreImpl).writeHandshake();
  23041. }
  23042. async function onWriteHandshakeComplete(remoteStoreImpl) {
  23043. const writeStream = ensureWriteStream(remoteStoreImpl);
  23044. // Send the write pipeline now that the stream is established.
  23045. for (const batch of remoteStoreImpl.writePipeline) {
  23046. writeStream.writeMutations(batch.mutations);
  23047. }
  23048. }
  23049. async function onMutationResult(remoteStoreImpl, commitVersion, results) {
  23050. const batch = remoteStoreImpl.writePipeline.shift();
  23051. const success = MutationBatchResult.from(batch, commitVersion, results);
  23052. await executeWithRecovery(remoteStoreImpl, () => remoteStoreImpl.remoteSyncer.applySuccessfulWrite(success));
  23053. // It's possible that with the completion of this mutation another
  23054. // slot has freed up.
  23055. await fillWritePipeline(remoteStoreImpl);
  23056. }
  23057. async function onWriteStreamClose(remoteStoreImpl, error) {
  23058. // If the write stream closed after the write handshake completes, a write
  23059. // operation failed and we fail the pending operation.
  23060. if (error && ensureWriteStream(remoteStoreImpl).handshakeComplete) {
  23061. // This error affects the actual write.
  23062. await handleWriteError(remoteStoreImpl, error);
  23063. }
  23064. // The write stream might have been started by refilling the write
  23065. // pipeline for failed writes
  23066. if (shouldStartWriteStream(remoteStoreImpl)) {
  23067. startWriteStream(remoteStoreImpl);
  23068. }
  23069. }
  23070. async function handleWriteError(remoteStoreImpl, error) {
  23071. // Only handle permanent errors here. If it's transient, just let the retry
  23072. // logic kick in.
  23073. if (isPermanentWriteError(error.code)) {
  23074. // This was a permanent error, the request itself was the problem
  23075. // so it's not going to succeed if we resend it.
  23076. const batch = remoteStoreImpl.writePipeline.shift();
  23077. // In this case it's also unlikely that the server itself is melting
  23078. // down -- this was just a bad request so inhibit backoff on the next
  23079. // restart.
  23080. ensureWriteStream(remoteStoreImpl).inhibitBackoff();
  23081. await executeWithRecovery(remoteStoreImpl, () => remoteStoreImpl.remoteSyncer.rejectFailedWrite(batch.batchId, error));
  23082. // It's possible that with the completion of this mutation
  23083. // another slot has freed up.
  23084. await fillWritePipeline(remoteStoreImpl);
  23085. }
  23086. }
  23087. async function restartNetwork(remoteStore) {
  23088. const remoteStoreImpl = debugCast(remoteStore);
  23089. remoteStoreImpl.offlineCauses.add(4 /* OfflineCause.ConnectivityChange */);
  23090. await disableNetworkInternal(remoteStoreImpl);
  23091. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  23092. remoteStoreImpl.offlineCauses.delete(4 /* OfflineCause.ConnectivityChange */);
  23093. await enableNetworkInternal(remoteStoreImpl);
  23094. }
  23095. async function remoteStoreHandleCredentialChange(remoteStore, user) {
  23096. const remoteStoreImpl = debugCast(remoteStore);
  23097. remoteStoreImpl.asyncQueue.verifyOperationInProgress();
  23098. logDebug(LOG_TAG$5, 'RemoteStore received new credentials');
  23099. const usesNetwork = canUseNetwork(remoteStoreImpl);
  23100. // Tear down and re-create our network streams. This will ensure we get a
  23101. // fresh auth token for the new user and re-fill the write pipeline with
  23102. // new mutations from the LocalStore (since mutations are per-user).
  23103. remoteStoreImpl.offlineCauses.add(3 /* OfflineCause.CredentialChange */);
  23104. await disableNetworkInternal(remoteStoreImpl);
  23105. if (usesNetwork) {
  23106. // Don't set the network status to Unknown if we are offline.
  23107. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  23108. }
  23109. await remoteStoreImpl.remoteSyncer.handleCredentialChange(user);
  23110. remoteStoreImpl.offlineCauses.delete(3 /* OfflineCause.CredentialChange */);
  23111. await enableNetworkInternal(remoteStoreImpl);
  23112. }
  23113. /**
  23114. * Toggles the network state when the client gains or loses its primary lease.
  23115. */
  23116. async function remoteStoreApplyPrimaryState(remoteStore, isPrimary) {
  23117. const remoteStoreImpl = debugCast(remoteStore);
  23118. if (isPrimary) {
  23119. remoteStoreImpl.offlineCauses.delete(2 /* OfflineCause.IsSecondary */);
  23120. await enableNetworkInternal(remoteStoreImpl);
  23121. }
  23122. else if (!isPrimary) {
  23123. remoteStoreImpl.offlineCauses.add(2 /* OfflineCause.IsSecondary */);
  23124. await disableNetworkInternal(remoteStoreImpl);
  23125. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  23126. }
  23127. }
  23128. /**
  23129. * If not yet initialized, registers the WatchStream and its network state
  23130. * callback with `remoteStoreImpl`. Returns the existing stream if one is
  23131. * already available.
  23132. *
  23133. * PORTING NOTE: On iOS and Android, the WatchStream gets registered on startup.
  23134. * This is not done on Web to allow it to be tree-shaken.
  23135. */
  23136. function ensureWatchStream(remoteStoreImpl) {
  23137. if (!remoteStoreImpl.watchStream) {
  23138. // Create stream (but note that it is not started yet).
  23139. remoteStoreImpl.watchStream = newPersistentWatchStream(remoteStoreImpl.datastore, remoteStoreImpl.asyncQueue, {
  23140. onOpen: onWatchStreamOpen.bind(null, remoteStoreImpl),
  23141. onClose: onWatchStreamClose.bind(null, remoteStoreImpl),
  23142. onWatchChange: onWatchStreamChange.bind(null, remoteStoreImpl)
  23143. });
  23144. remoteStoreImpl.onNetworkStatusChange.push(async (enabled) => {
  23145. if (enabled) {
  23146. remoteStoreImpl.watchStream.inhibitBackoff();
  23147. if (shouldStartWatchStream(remoteStoreImpl)) {
  23148. startWatchStream(remoteStoreImpl);
  23149. }
  23150. else {
  23151. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  23152. }
  23153. }
  23154. else {
  23155. await remoteStoreImpl.watchStream.stop();
  23156. cleanUpWatchStreamState(remoteStoreImpl);
  23157. }
  23158. });
  23159. }
  23160. return remoteStoreImpl.watchStream;
  23161. }
  23162. /**
  23163. * If not yet initialized, registers the WriteStream and its network state
  23164. * callback with `remoteStoreImpl`. Returns the existing stream if one is
  23165. * already available.
  23166. *
  23167. * PORTING NOTE: On iOS and Android, the WriteStream gets registered on startup.
  23168. * This is not done on Web to allow it to be tree-shaken.
  23169. */
  23170. function ensureWriteStream(remoteStoreImpl) {
  23171. if (!remoteStoreImpl.writeStream) {
  23172. // Create stream (but note that it is not started yet).
  23173. remoteStoreImpl.writeStream = newPersistentWriteStream(remoteStoreImpl.datastore, remoteStoreImpl.asyncQueue, {
  23174. onOpen: onWriteStreamOpen.bind(null, remoteStoreImpl),
  23175. onClose: onWriteStreamClose.bind(null, remoteStoreImpl),
  23176. onHandshakeComplete: onWriteHandshakeComplete.bind(null, remoteStoreImpl),
  23177. onMutationResult: onMutationResult.bind(null, remoteStoreImpl)
  23178. });
  23179. remoteStoreImpl.onNetworkStatusChange.push(async (enabled) => {
  23180. if (enabled) {
  23181. remoteStoreImpl.writeStream.inhibitBackoff();
  23182. // This will start the write stream if necessary.
  23183. await fillWritePipeline(remoteStoreImpl);
  23184. }
  23185. else {
  23186. await remoteStoreImpl.writeStream.stop();
  23187. if (remoteStoreImpl.writePipeline.length > 0) {
  23188. logDebug(LOG_TAG$5, `Stopping write stream with ${remoteStoreImpl.writePipeline.length} pending writes`);
  23189. remoteStoreImpl.writePipeline = [];
  23190. }
  23191. }
  23192. });
  23193. }
  23194. return remoteStoreImpl.writeStream;
  23195. }
  23196. /**
  23197. * @license
  23198. * Copyright 2017 Google LLC
  23199. *
  23200. * Licensed under the Apache License, Version 2.0 (the "License");
  23201. * you may not use this file except in compliance with the License.
  23202. * You may obtain a copy of the License at
  23203. *
  23204. * http://www.apache.org/licenses/LICENSE-2.0
  23205. *
  23206. * Unless required by applicable law or agreed to in writing, software
  23207. * distributed under the License is distributed on an "AS IS" BASIS,
  23208. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23209. * See the License for the specific language governing permissions and
  23210. * limitations under the License.
  23211. */
  23212. const LOG_TAG$4 = 'AsyncQueue';
  23213. /**
  23214. * Represents an operation scheduled to be run in the future on an AsyncQueue.
  23215. *
  23216. * It is created via DelayedOperation.createAndSchedule().
  23217. *
  23218. * Supports cancellation (via cancel()) and early execution (via skipDelay()).
  23219. *
  23220. * Note: We implement `PromiseLike` instead of `Promise`, as the `Promise` type
  23221. * in newer versions of TypeScript defines `finally`, which is not available in
  23222. * IE.
  23223. */
  23224. class DelayedOperation {
  23225. constructor(asyncQueue, timerId, targetTimeMs, op, removalCallback) {
  23226. this.asyncQueue = asyncQueue;
  23227. this.timerId = timerId;
  23228. this.targetTimeMs = targetTimeMs;
  23229. this.op = op;
  23230. this.removalCallback = removalCallback;
  23231. this.deferred = new Deferred();
  23232. this.then = this.deferred.promise.then.bind(this.deferred.promise);
  23233. // It's normal for the deferred promise to be canceled (due to cancellation)
  23234. // and so we attach a dummy catch callback to avoid
  23235. // 'UnhandledPromiseRejectionWarning' log spam.
  23236. this.deferred.promise.catch(err => { });
  23237. }
  23238. /**
  23239. * Creates and returns a DelayedOperation that has been scheduled to be
  23240. * executed on the provided asyncQueue after the provided delayMs.
  23241. *
  23242. * @param asyncQueue - The queue to schedule the operation on.
  23243. * @param id - A Timer ID identifying the type of operation this is.
  23244. * @param delayMs - The delay (ms) before the operation should be scheduled.
  23245. * @param op - The operation to run.
  23246. * @param removalCallback - A callback to be called synchronously once the
  23247. * operation is executed or canceled, notifying the AsyncQueue to remove it
  23248. * from its delayedOperations list.
  23249. * PORTING NOTE: This exists to prevent making removeDelayedOperation() and
  23250. * the DelayedOperation class public.
  23251. */
  23252. static createAndSchedule(asyncQueue, timerId, delayMs, op, removalCallback) {
  23253. const targetTime = Date.now() + delayMs;
  23254. const delayedOp = new DelayedOperation(asyncQueue, timerId, targetTime, op, removalCallback);
  23255. delayedOp.start(delayMs);
  23256. return delayedOp;
  23257. }
  23258. /**
  23259. * Starts the timer. This is called immediately after construction by
  23260. * createAndSchedule().
  23261. */
  23262. start(delayMs) {
  23263. this.timerHandle = setTimeout(() => this.handleDelayElapsed(), delayMs);
  23264. }
  23265. /**
  23266. * Queues the operation to run immediately (if it hasn't already been run or
  23267. * canceled).
  23268. */
  23269. skipDelay() {
  23270. return this.handleDelayElapsed();
  23271. }
  23272. /**
  23273. * Cancels the operation if it hasn't already been executed or canceled. The
  23274. * promise will be rejected.
  23275. *
  23276. * As long as the operation has not yet been run, calling cancel() provides a
  23277. * guarantee that the operation will not be run.
  23278. */
  23279. cancel(reason) {
  23280. if (this.timerHandle !== null) {
  23281. this.clearTimeout();
  23282. this.deferred.reject(new FirestoreError(Code.CANCELLED, 'Operation cancelled' + (reason ? ': ' + reason : '')));
  23283. }
  23284. }
  23285. handleDelayElapsed() {
  23286. this.asyncQueue.enqueueAndForget(() => {
  23287. if (this.timerHandle !== null) {
  23288. this.clearTimeout();
  23289. return this.op().then(result => {
  23290. return this.deferred.resolve(result);
  23291. });
  23292. }
  23293. else {
  23294. return Promise.resolve();
  23295. }
  23296. });
  23297. }
  23298. clearTimeout() {
  23299. if (this.timerHandle !== null) {
  23300. this.removalCallback(this);
  23301. clearTimeout(this.timerHandle);
  23302. this.timerHandle = null;
  23303. }
  23304. }
  23305. }
  23306. /**
  23307. * Returns a FirestoreError that can be surfaced to the user if the provided
  23308. * error is an IndexedDbTransactionError. Re-throws the error otherwise.
  23309. */
  23310. function wrapInUserErrorIfRecoverable(e, msg) {
  23311. logError(LOG_TAG$4, `${msg}: ${e}`);
  23312. if (isIndexedDbTransactionError(e)) {
  23313. return new FirestoreError(Code.UNAVAILABLE, `${msg}: ${e}`);
  23314. }
  23315. else {
  23316. throw e;
  23317. }
  23318. }
  23319. /**
  23320. * @license
  23321. * Copyright 2017 Google LLC
  23322. *
  23323. * Licensed under the Apache License, Version 2.0 (the "License");
  23324. * you may not use this file except in compliance with the License.
  23325. * You may obtain a copy of the License at
  23326. *
  23327. * http://www.apache.org/licenses/LICENSE-2.0
  23328. *
  23329. * Unless required by applicable law or agreed to in writing, software
  23330. * distributed under the License is distributed on an "AS IS" BASIS,
  23331. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23332. * See the License for the specific language governing permissions and
  23333. * limitations under the License.
  23334. */
  23335. /**
  23336. * DocumentSet is an immutable (copy-on-write) collection that holds documents
  23337. * in order specified by the provided comparator. We always add a document key
  23338. * comparator on top of what is provided to guarantee document equality based on
  23339. * the key.
  23340. */
  23341. class DocumentSet {
  23342. /** The default ordering is by key if the comparator is omitted */
  23343. constructor(comp) {
  23344. // We are adding document key comparator to the end as it's the only
  23345. // guaranteed unique property of a document.
  23346. if (comp) {
  23347. this.comparator = (d1, d2) => comp(d1, d2) || DocumentKey.comparator(d1.key, d2.key);
  23348. }
  23349. else {
  23350. this.comparator = (d1, d2) => DocumentKey.comparator(d1.key, d2.key);
  23351. }
  23352. this.keyedMap = documentMap();
  23353. this.sortedSet = new SortedMap(this.comparator);
  23354. }
  23355. /**
  23356. * Returns an empty copy of the existing DocumentSet, using the same
  23357. * comparator.
  23358. */
  23359. static emptySet(oldSet) {
  23360. return new DocumentSet(oldSet.comparator);
  23361. }
  23362. has(key) {
  23363. return this.keyedMap.get(key) != null;
  23364. }
  23365. get(key) {
  23366. return this.keyedMap.get(key);
  23367. }
  23368. first() {
  23369. return this.sortedSet.minKey();
  23370. }
  23371. last() {
  23372. return this.sortedSet.maxKey();
  23373. }
  23374. isEmpty() {
  23375. return this.sortedSet.isEmpty();
  23376. }
  23377. /**
  23378. * Returns the index of the provided key in the document set, or -1 if the
  23379. * document key is not present in the set;
  23380. */
  23381. indexOf(key) {
  23382. const doc = this.keyedMap.get(key);
  23383. return doc ? this.sortedSet.indexOf(doc) : -1;
  23384. }
  23385. get size() {
  23386. return this.sortedSet.size;
  23387. }
  23388. /** Iterates documents in order defined by "comparator" */
  23389. forEach(cb) {
  23390. this.sortedSet.inorderTraversal((k, v) => {
  23391. cb(k);
  23392. return false;
  23393. });
  23394. }
  23395. /** Inserts or updates a document with the same key */
  23396. add(doc) {
  23397. // First remove the element if we have it.
  23398. const set = this.delete(doc.key);
  23399. return set.copy(set.keyedMap.insert(doc.key, doc), set.sortedSet.insert(doc, null));
  23400. }
  23401. /** Deletes a document with a given key */
  23402. delete(key) {
  23403. const doc = this.get(key);
  23404. if (!doc) {
  23405. return this;
  23406. }
  23407. return this.copy(this.keyedMap.remove(key), this.sortedSet.remove(doc));
  23408. }
  23409. isEqual(other) {
  23410. if (!(other instanceof DocumentSet)) {
  23411. return false;
  23412. }
  23413. if (this.size !== other.size) {
  23414. return false;
  23415. }
  23416. const thisIt = this.sortedSet.getIterator();
  23417. const otherIt = other.sortedSet.getIterator();
  23418. while (thisIt.hasNext()) {
  23419. const thisDoc = thisIt.getNext().key;
  23420. const otherDoc = otherIt.getNext().key;
  23421. if (!thisDoc.isEqual(otherDoc)) {
  23422. return false;
  23423. }
  23424. }
  23425. return true;
  23426. }
  23427. toString() {
  23428. const docStrings = [];
  23429. this.forEach(doc => {
  23430. docStrings.push(doc.toString());
  23431. });
  23432. if (docStrings.length === 0) {
  23433. return 'DocumentSet ()';
  23434. }
  23435. else {
  23436. return 'DocumentSet (\n ' + docStrings.join(' \n') + '\n)';
  23437. }
  23438. }
  23439. copy(keyedMap, sortedSet) {
  23440. const newSet = new DocumentSet();
  23441. newSet.comparator = this.comparator;
  23442. newSet.keyedMap = keyedMap;
  23443. newSet.sortedSet = sortedSet;
  23444. return newSet;
  23445. }
  23446. }
  23447. /**
  23448. * @license
  23449. * Copyright 2017 Google LLC
  23450. *
  23451. * Licensed under the Apache License, Version 2.0 (the "License");
  23452. * you may not use this file except in compliance with the License.
  23453. * You may obtain a copy of the License at
  23454. *
  23455. * http://www.apache.org/licenses/LICENSE-2.0
  23456. *
  23457. * Unless required by applicable law or agreed to in writing, software
  23458. * distributed under the License is distributed on an "AS IS" BASIS,
  23459. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23460. * See the License for the specific language governing permissions and
  23461. * limitations under the License.
  23462. */
  23463. /**
  23464. * DocumentChangeSet keeps track of a set of changes to docs in a query, merging
  23465. * duplicate events for the same doc.
  23466. */
  23467. class DocumentChangeSet {
  23468. constructor() {
  23469. this.changeMap = new SortedMap(DocumentKey.comparator);
  23470. }
  23471. track(change) {
  23472. const key = change.doc.key;
  23473. const oldChange = this.changeMap.get(key);
  23474. if (!oldChange) {
  23475. this.changeMap = this.changeMap.insert(key, change);
  23476. return;
  23477. }
  23478. // Merge the new change with the existing change.
  23479. if (change.type !== 0 /* ChangeType.Added */ &&
  23480. oldChange.type === 3 /* ChangeType.Metadata */) {
  23481. this.changeMap = this.changeMap.insert(key, change);
  23482. }
  23483. else if (change.type === 3 /* ChangeType.Metadata */ &&
  23484. oldChange.type !== 1 /* ChangeType.Removed */) {
  23485. this.changeMap = this.changeMap.insert(key, {
  23486. type: oldChange.type,
  23487. doc: change.doc
  23488. });
  23489. }
  23490. else if (change.type === 2 /* ChangeType.Modified */ &&
  23491. oldChange.type === 2 /* ChangeType.Modified */) {
  23492. this.changeMap = this.changeMap.insert(key, {
  23493. type: 2 /* ChangeType.Modified */,
  23494. doc: change.doc
  23495. });
  23496. }
  23497. else if (change.type === 2 /* ChangeType.Modified */ &&
  23498. oldChange.type === 0 /* ChangeType.Added */) {
  23499. this.changeMap = this.changeMap.insert(key, {
  23500. type: 0 /* ChangeType.Added */,
  23501. doc: change.doc
  23502. });
  23503. }
  23504. else if (change.type === 1 /* ChangeType.Removed */ &&
  23505. oldChange.type === 0 /* ChangeType.Added */) {
  23506. this.changeMap = this.changeMap.remove(key);
  23507. }
  23508. else if (change.type === 1 /* ChangeType.Removed */ &&
  23509. oldChange.type === 2 /* ChangeType.Modified */) {
  23510. this.changeMap = this.changeMap.insert(key, {
  23511. type: 1 /* ChangeType.Removed */,
  23512. doc: oldChange.doc
  23513. });
  23514. }
  23515. else if (change.type === 0 /* ChangeType.Added */ &&
  23516. oldChange.type === 1 /* ChangeType.Removed */) {
  23517. this.changeMap = this.changeMap.insert(key, {
  23518. type: 2 /* ChangeType.Modified */,
  23519. doc: change.doc
  23520. });
  23521. }
  23522. else {
  23523. // This includes these cases, which don't make sense:
  23524. // Added->Added
  23525. // Removed->Removed
  23526. // Modified->Added
  23527. // Removed->Modified
  23528. // Metadata->Added
  23529. // Removed->Metadata
  23530. fail();
  23531. }
  23532. }
  23533. getChanges() {
  23534. const changes = [];
  23535. this.changeMap.inorderTraversal((key, change) => {
  23536. changes.push(change);
  23537. });
  23538. return changes;
  23539. }
  23540. }
  23541. class ViewSnapshot {
  23542. constructor(query, docs, oldDocs, docChanges, mutatedKeys, fromCache, syncStateChanged, excludesMetadataChanges, hasCachedResults) {
  23543. this.query = query;
  23544. this.docs = docs;
  23545. this.oldDocs = oldDocs;
  23546. this.docChanges = docChanges;
  23547. this.mutatedKeys = mutatedKeys;
  23548. this.fromCache = fromCache;
  23549. this.syncStateChanged = syncStateChanged;
  23550. this.excludesMetadataChanges = excludesMetadataChanges;
  23551. this.hasCachedResults = hasCachedResults;
  23552. }
  23553. /** Returns a view snapshot as if all documents in the snapshot were added. */
  23554. static fromInitialDocuments(query, documents, mutatedKeys, fromCache, hasCachedResults) {
  23555. const changes = [];
  23556. documents.forEach(doc => {
  23557. changes.push({ type: 0 /* ChangeType.Added */, doc });
  23558. });
  23559. return new ViewSnapshot(query, documents, DocumentSet.emptySet(documents), changes, mutatedKeys, fromCache,
  23560. /* syncStateChanged= */ true,
  23561. /* excludesMetadataChanges= */ false, hasCachedResults);
  23562. }
  23563. get hasPendingWrites() {
  23564. return !this.mutatedKeys.isEmpty();
  23565. }
  23566. isEqual(other) {
  23567. if (this.fromCache !== other.fromCache ||
  23568. this.hasCachedResults !== other.hasCachedResults ||
  23569. this.syncStateChanged !== other.syncStateChanged ||
  23570. !this.mutatedKeys.isEqual(other.mutatedKeys) ||
  23571. !queryEquals(this.query, other.query) ||
  23572. !this.docs.isEqual(other.docs) ||
  23573. !this.oldDocs.isEqual(other.oldDocs)) {
  23574. return false;
  23575. }
  23576. const changes = this.docChanges;
  23577. const otherChanges = other.docChanges;
  23578. if (changes.length !== otherChanges.length) {
  23579. return false;
  23580. }
  23581. for (let i = 0; i < changes.length; i++) {
  23582. if (changes[i].type !== otherChanges[i].type ||
  23583. !changes[i].doc.isEqual(otherChanges[i].doc)) {
  23584. return false;
  23585. }
  23586. }
  23587. return true;
  23588. }
  23589. }
  23590. /**
  23591. * @license
  23592. * Copyright 2017 Google LLC
  23593. *
  23594. * Licensed under the Apache License, Version 2.0 (the "License");
  23595. * you may not use this file except in compliance with the License.
  23596. * You may obtain a copy of the License at
  23597. *
  23598. * http://www.apache.org/licenses/LICENSE-2.0
  23599. *
  23600. * Unless required by applicable law or agreed to in writing, software
  23601. * distributed under the License is distributed on an "AS IS" BASIS,
  23602. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23603. * See the License for the specific language governing permissions and
  23604. * limitations under the License.
  23605. */
  23606. /**
  23607. * Holds the listeners and the last received ViewSnapshot for a query being
  23608. * tracked by EventManager.
  23609. */
  23610. class QueryListenersInfo {
  23611. constructor() {
  23612. this.viewSnap = undefined;
  23613. this.listeners = [];
  23614. }
  23615. }
  23616. function newEventManager() {
  23617. return new EventManagerImpl();
  23618. }
  23619. class EventManagerImpl {
  23620. constructor() {
  23621. this.queries = new ObjectMap(q => canonifyQuery(q), queryEquals);
  23622. this.onlineState = "Unknown" /* OnlineState.Unknown */;
  23623. this.snapshotsInSyncListeners = new Set();
  23624. }
  23625. }
  23626. async function eventManagerListen(eventManager, listener) {
  23627. const eventManagerImpl = debugCast(eventManager);
  23628. const query = listener.query;
  23629. let firstListen = false;
  23630. let queryInfo = eventManagerImpl.queries.get(query);
  23631. if (!queryInfo) {
  23632. firstListen = true;
  23633. queryInfo = new QueryListenersInfo();
  23634. }
  23635. if (firstListen) {
  23636. try {
  23637. queryInfo.viewSnap = await eventManagerImpl.onListen(query);
  23638. }
  23639. catch (e) {
  23640. const firestoreError = wrapInUserErrorIfRecoverable(e, `Initialization of query '${stringifyQuery(listener.query)}' failed`);
  23641. listener.onError(firestoreError);
  23642. return;
  23643. }
  23644. }
  23645. eventManagerImpl.queries.set(query, queryInfo);
  23646. queryInfo.listeners.push(listener);
  23647. // Run global snapshot listeners if a consistent snapshot has been emitted.
  23648. listener.applyOnlineStateChange(eventManagerImpl.onlineState);
  23649. if (queryInfo.viewSnap) {
  23650. const raisedEvent = listener.onViewSnapshot(queryInfo.viewSnap);
  23651. if (raisedEvent) {
  23652. raiseSnapshotsInSyncEvent(eventManagerImpl);
  23653. }
  23654. }
  23655. }
  23656. async function eventManagerUnlisten(eventManager, listener) {
  23657. const eventManagerImpl = debugCast(eventManager);
  23658. const query = listener.query;
  23659. let lastListen = false;
  23660. const queryInfo = eventManagerImpl.queries.get(query);
  23661. if (queryInfo) {
  23662. const i = queryInfo.listeners.indexOf(listener);
  23663. if (i >= 0) {
  23664. queryInfo.listeners.splice(i, 1);
  23665. lastListen = queryInfo.listeners.length === 0;
  23666. }
  23667. }
  23668. if (lastListen) {
  23669. eventManagerImpl.queries.delete(query);
  23670. return eventManagerImpl.onUnlisten(query);
  23671. }
  23672. }
  23673. function eventManagerOnWatchChange(eventManager, viewSnaps) {
  23674. const eventManagerImpl = debugCast(eventManager);
  23675. let raisedEvent = false;
  23676. for (const viewSnap of viewSnaps) {
  23677. const query = viewSnap.query;
  23678. const queryInfo = eventManagerImpl.queries.get(query);
  23679. if (queryInfo) {
  23680. for (const listener of queryInfo.listeners) {
  23681. if (listener.onViewSnapshot(viewSnap)) {
  23682. raisedEvent = true;
  23683. }
  23684. }
  23685. queryInfo.viewSnap = viewSnap;
  23686. }
  23687. }
  23688. if (raisedEvent) {
  23689. raiseSnapshotsInSyncEvent(eventManagerImpl);
  23690. }
  23691. }
  23692. function eventManagerOnWatchError(eventManager, query, error) {
  23693. const eventManagerImpl = debugCast(eventManager);
  23694. const queryInfo = eventManagerImpl.queries.get(query);
  23695. if (queryInfo) {
  23696. for (const listener of queryInfo.listeners) {
  23697. listener.onError(error);
  23698. }
  23699. }
  23700. // Remove all listeners. NOTE: We don't need to call syncEngine.unlisten()
  23701. // after an error.
  23702. eventManagerImpl.queries.delete(query);
  23703. }
  23704. function eventManagerOnOnlineStateChange(eventManager, onlineState) {
  23705. const eventManagerImpl = debugCast(eventManager);
  23706. eventManagerImpl.onlineState = onlineState;
  23707. let raisedEvent = false;
  23708. eventManagerImpl.queries.forEach((_, queryInfo) => {
  23709. for (const listener of queryInfo.listeners) {
  23710. // Run global snapshot listeners if a consistent snapshot has been emitted.
  23711. if (listener.applyOnlineStateChange(onlineState)) {
  23712. raisedEvent = true;
  23713. }
  23714. }
  23715. });
  23716. if (raisedEvent) {
  23717. raiseSnapshotsInSyncEvent(eventManagerImpl);
  23718. }
  23719. }
  23720. function addSnapshotsInSyncListener(eventManager, observer) {
  23721. const eventManagerImpl = debugCast(eventManager);
  23722. eventManagerImpl.snapshotsInSyncListeners.add(observer);
  23723. // Immediately fire an initial event, indicating all existing listeners
  23724. // are in-sync.
  23725. observer.next();
  23726. }
  23727. function removeSnapshotsInSyncListener(eventManager, observer) {
  23728. const eventManagerImpl = debugCast(eventManager);
  23729. eventManagerImpl.snapshotsInSyncListeners.delete(observer);
  23730. }
  23731. // Call all global snapshot listeners that have been set.
  23732. function raiseSnapshotsInSyncEvent(eventManagerImpl) {
  23733. eventManagerImpl.snapshotsInSyncListeners.forEach(observer => {
  23734. observer.next();
  23735. });
  23736. }
  23737. /**
  23738. * QueryListener takes a series of internal view snapshots and determines
  23739. * when to raise the event.
  23740. *
  23741. * It uses an Observer to dispatch events.
  23742. */
  23743. class QueryListener {
  23744. constructor(query, queryObserver, options) {
  23745. this.query = query;
  23746. this.queryObserver = queryObserver;
  23747. /**
  23748. * Initial snapshots (e.g. from cache) may not be propagated to the wrapped
  23749. * observer. This flag is set to true once we've actually raised an event.
  23750. */
  23751. this.raisedInitialEvent = false;
  23752. this.snap = null;
  23753. this.onlineState = "Unknown" /* OnlineState.Unknown */;
  23754. this.options = options || {};
  23755. }
  23756. /**
  23757. * Applies the new ViewSnapshot to this listener, raising a user-facing event
  23758. * if applicable (depending on what changed, whether the user has opted into
  23759. * metadata-only changes, etc.). Returns true if a user-facing event was
  23760. * indeed raised.
  23761. */
  23762. onViewSnapshot(snap) {
  23763. if (!this.options.includeMetadataChanges) {
  23764. // Remove the metadata only changes.
  23765. const docChanges = [];
  23766. for (const docChange of snap.docChanges) {
  23767. if (docChange.type !== 3 /* ChangeType.Metadata */) {
  23768. docChanges.push(docChange);
  23769. }
  23770. }
  23771. snap = new ViewSnapshot(snap.query, snap.docs, snap.oldDocs, docChanges, snap.mutatedKeys, snap.fromCache, snap.syncStateChanged,
  23772. /* excludesMetadataChanges= */ true, snap.hasCachedResults);
  23773. }
  23774. let raisedEvent = false;
  23775. if (!this.raisedInitialEvent) {
  23776. if (this.shouldRaiseInitialEvent(snap, this.onlineState)) {
  23777. this.raiseInitialEvent(snap);
  23778. raisedEvent = true;
  23779. }
  23780. }
  23781. else if (this.shouldRaiseEvent(snap)) {
  23782. this.queryObserver.next(snap);
  23783. raisedEvent = true;
  23784. }
  23785. this.snap = snap;
  23786. return raisedEvent;
  23787. }
  23788. onError(error) {
  23789. this.queryObserver.error(error);
  23790. }
  23791. /** Returns whether a snapshot was raised. */
  23792. applyOnlineStateChange(onlineState) {
  23793. this.onlineState = onlineState;
  23794. let raisedEvent = false;
  23795. if (this.snap &&
  23796. !this.raisedInitialEvent &&
  23797. this.shouldRaiseInitialEvent(this.snap, onlineState)) {
  23798. this.raiseInitialEvent(this.snap);
  23799. raisedEvent = true;
  23800. }
  23801. return raisedEvent;
  23802. }
  23803. shouldRaiseInitialEvent(snap, onlineState) {
  23804. // Always raise the first event when we're synced
  23805. if (!snap.fromCache) {
  23806. return true;
  23807. }
  23808. // NOTE: We consider OnlineState.Unknown as online (it should become Offline
  23809. // or Online if we wait long enough).
  23810. const maybeOnline = onlineState !== "Offline" /* OnlineState.Offline */;
  23811. // Don't raise the event if we're online, aren't synced yet (checked
  23812. // above) and are waiting for a sync.
  23813. if (this.options.waitForSyncWhenOnline && maybeOnline) {
  23814. return false;
  23815. }
  23816. // Raise data from cache if we have any documents, have cached results before,
  23817. // or we are offline.
  23818. return (!snap.docs.isEmpty() ||
  23819. snap.hasCachedResults ||
  23820. onlineState === "Offline" /* OnlineState.Offline */);
  23821. }
  23822. shouldRaiseEvent(snap) {
  23823. // We don't need to handle includeDocumentMetadataChanges here because
  23824. // the Metadata only changes have already been stripped out if needed.
  23825. // At this point the only changes we will see are the ones we should
  23826. // propagate.
  23827. if (snap.docChanges.length > 0) {
  23828. return true;
  23829. }
  23830. const hasPendingWritesChanged = this.snap && this.snap.hasPendingWrites !== snap.hasPendingWrites;
  23831. if (snap.syncStateChanged || hasPendingWritesChanged) {
  23832. return this.options.includeMetadataChanges === true;
  23833. }
  23834. // Generally we should have hit one of the cases above, but it's possible
  23835. // to get here if there were only metadata docChanges and they got
  23836. // stripped out.
  23837. return false;
  23838. }
  23839. raiseInitialEvent(snap) {
  23840. snap = ViewSnapshot.fromInitialDocuments(snap.query, snap.docs, snap.mutatedKeys, snap.fromCache, snap.hasCachedResults);
  23841. this.raisedInitialEvent = true;
  23842. this.queryObserver.next(snap);
  23843. }
  23844. }
  23845. /**
  23846. * @license
  23847. * Copyright 2017 Google LLC
  23848. *
  23849. * Licensed under the Apache License, Version 2.0 (the "License");
  23850. * you may not use this file except in compliance with the License.
  23851. * You may obtain a copy of the License at
  23852. *
  23853. * http://www.apache.org/licenses/LICENSE-2.0
  23854. *
  23855. * Unless required by applicable law or agreed to in writing, software
  23856. * distributed under the License is distributed on an "AS IS" BASIS,
  23857. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23858. * See the License for the specific language governing permissions and
  23859. * limitations under the License.
  23860. */
  23861. /**
  23862. * A set of changes to what documents are currently in view and out of view for
  23863. * a given query. These changes are sent to the LocalStore by the View (via
  23864. * the SyncEngine) and are used to pin / unpin documents as appropriate.
  23865. */
  23866. class LocalViewChanges {
  23867. constructor(targetId, fromCache, addedKeys, removedKeys) {
  23868. this.targetId = targetId;
  23869. this.fromCache = fromCache;
  23870. this.addedKeys = addedKeys;
  23871. this.removedKeys = removedKeys;
  23872. }
  23873. static fromSnapshot(targetId, viewSnapshot) {
  23874. let addedKeys = documentKeySet();
  23875. let removedKeys = documentKeySet();
  23876. for (const docChange of viewSnapshot.docChanges) {
  23877. switch (docChange.type) {
  23878. case 0 /* ChangeType.Added */:
  23879. addedKeys = addedKeys.add(docChange.doc.key);
  23880. break;
  23881. case 1 /* ChangeType.Removed */:
  23882. removedKeys = removedKeys.add(docChange.doc.key);
  23883. break;
  23884. // do nothing
  23885. }
  23886. }
  23887. return new LocalViewChanges(targetId, viewSnapshot.fromCache, addedKeys, removedKeys);
  23888. }
  23889. }
  23890. /**
  23891. * @license
  23892. * Copyright 2020 Google LLC
  23893. *
  23894. * Licensed under the Apache License, Version 2.0 (the "License");
  23895. * you may not use this file except in compliance with the License.
  23896. * You may obtain a copy of the License at
  23897. *
  23898. * http://www.apache.org/licenses/LICENSE-2.0
  23899. *
  23900. * Unless required by applicable law or agreed to in writing, software
  23901. * distributed under the License is distributed on an "AS IS" BASIS,
  23902. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23903. * See the License for the specific language governing permissions and
  23904. * limitations under the License.
  23905. */
  23906. /**
  23907. * Helper to convert objects from bundles to model objects in the SDK.
  23908. */
  23909. class BundleConverterImpl {
  23910. constructor(serializer) {
  23911. this.serializer = serializer;
  23912. }
  23913. toDocumentKey(name) {
  23914. return fromName(this.serializer, name);
  23915. }
  23916. /**
  23917. * Converts a BundleDocument to a MutableDocument.
  23918. */
  23919. toMutableDocument(bundledDoc) {
  23920. if (bundledDoc.metadata.exists) {
  23921. return fromDocument(this.serializer, bundledDoc.document, false);
  23922. }
  23923. else {
  23924. return MutableDocument.newNoDocument(this.toDocumentKey(bundledDoc.metadata.name), this.toSnapshotVersion(bundledDoc.metadata.readTime));
  23925. }
  23926. }
  23927. toSnapshotVersion(time) {
  23928. return fromVersion(time);
  23929. }
  23930. }
  23931. /**
  23932. * A class to process the elements from a bundle, load them into local
  23933. * storage and provide progress update while loading.
  23934. */
  23935. class BundleLoader {
  23936. constructor(bundleMetadata, localStore, serializer) {
  23937. this.bundleMetadata = bundleMetadata;
  23938. this.localStore = localStore;
  23939. this.serializer = serializer;
  23940. /** Batched queries to be saved into storage */
  23941. this.queries = [];
  23942. /** Batched documents to be saved into storage */
  23943. this.documents = [];
  23944. /** The collection groups affected by this bundle. */
  23945. this.collectionGroups = new Set();
  23946. this.progress = bundleInitialProgress(bundleMetadata);
  23947. }
  23948. /**
  23949. * Adds an element from the bundle to the loader.
  23950. *
  23951. * Returns a new progress if adding the element leads to a new progress,
  23952. * otherwise returns null.
  23953. */
  23954. addSizedElement(element) {
  23955. this.progress.bytesLoaded += element.byteLength;
  23956. let documentsLoaded = this.progress.documentsLoaded;
  23957. if (element.payload.namedQuery) {
  23958. this.queries.push(element.payload.namedQuery);
  23959. }
  23960. else if (element.payload.documentMetadata) {
  23961. this.documents.push({ metadata: element.payload.documentMetadata });
  23962. if (!element.payload.documentMetadata.exists) {
  23963. ++documentsLoaded;
  23964. }
  23965. const path = ResourcePath.fromString(element.payload.documentMetadata.name);
  23966. this.collectionGroups.add(path.get(path.length - 2));
  23967. }
  23968. else if (element.payload.document) {
  23969. this.documents[this.documents.length - 1].document =
  23970. element.payload.document;
  23971. ++documentsLoaded;
  23972. }
  23973. if (documentsLoaded !== this.progress.documentsLoaded) {
  23974. this.progress.documentsLoaded = documentsLoaded;
  23975. return Object.assign({}, this.progress);
  23976. }
  23977. return null;
  23978. }
  23979. getQueryDocumentMapping(documents) {
  23980. const queryDocumentMap = new Map();
  23981. const bundleConverter = new BundleConverterImpl(this.serializer);
  23982. for (const bundleDoc of documents) {
  23983. if (bundleDoc.metadata.queries) {
  23984. const documentKey = bundleConverter.toDocumentKey(bundleDoc.metadata.name);
  23985. for (const queryName of bundleDoc.metadata.queries) {
  23986. const documentKeys = (queryDocumentMap.get(queryName) || documentKeySet()).add(documentKey);
  23987. queryDocumentMap.set(queryName, documentKeys);
  23988. }
  23989. }
  23990. }
  23991. return queryDocumentMap;
  23992. }
  23993. /**
  23994. * Update the progress to 'Success' and return the updated progress.
  23995. */
  23996. async complete() {
  23997. const changedDocs = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
  23998. const queryDocumentMap = this.getQueryDocumentMapping(this.documents);
  23999. for (const q of this.queries) {
  24000. await localStoreSaveNamedQuery(this.localStore, q, queryDocumentMap.get(q.name));
  24001. }
  24002. this.progress.taskState = 'Success';
  24003. return {
  24004. progress: this.progress,
  24005. changedCollectionGroups: this.collectionGroups,
  24006. changedDocs
  24007. };
  24008. }
  24009. }
  24010. /**
  24011. * Returns a `LoadBundleTaskProgress` representing the initial progress of
  24012. * loading a bundle.
  24013. */
  24014. function bundleInitialProgress(metadata) {
  24015. return {
  24016. taskState: 'Running',
  24017. documentsLoaded: 0,
  24018. bytesLoaded: 0,
  24019. totalDocuments: metadata.totalDocuments,
  24020. totalBytes: metadata.totalBytes
  24021. };
  24022. }
  24023. /**
  24024. * Returns a `LoadBundleTaskProgress` representing the progress that the loading
  24025. * has succeeded.
  24026. */
  24027. function bundleSuccessProgress(metadata) {
  24028. return {
  24029. taskState: 'Success',
  24030. documentsLoaded: metadata.totalDocuments,
  24031. bytesLoaded: metadata.totalBytes,
  24032. totalDocuments: metadata.totalDocuments,
  24033. totalBytes: metadata.totalBytes
  24034. };
  24035. }
  24036. /**
  24037. * @license
  24038. * Copyright 2017 Google LLC
  24039. *
  24040. * Licensed under the Apache License, Version 2.0 (the "License");
  24041. * you may not use this file except in compliance with the License.
  24042. * You may obtain a copy of the License at
  24043. *
  24044. * http://www.apache.org/licenses/LICENSE-2.0
  24045. *
  24046. * Unless required by applicable law or agreed to in writing, software
  24047. * distributed under the License is distributed on an "AS IS" BASIS,
  24048. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24049. * See the License for the specific language governing permissions and
  24050. * limitations under the License.
  24051. */
  24052. class AddedLimboDocument {
  24053. constructor(key) {
  24054. this.key = key;
  24055. }
  24056. }
  24057. class RemovedLimboDocument {
  24058. constructor(key) {
  24059. this.key = key;
  24060. }
  24061. }
  24062. /**
  24063. * View is responsible for computing the final merged truth of what docs are in
  24064. * a query. It gets notified of local and remote changes to docs, and applies
  24065. * the query filters and limits to determine the most correct possible results.
  24066. */
  24067. class View {
  24068. constructor(query,
  24069. /** Documents included in the remote target */
  24070. _syncedDocuments) {
  24071. this.query = query;
  24072. this._syncedDocuments = _syncedDocuments;
  24073. this.syncState = null;
  24074. this.hasCachedResults = false;
  24075. /**
  24076. * A flag whether the view is current with the backend. A view is considered
  24077. * current after it has seen the current flag from the backend and did not
  24078. * lose consistency within the watch stream (e.g. because of an existence
  24079. * filter mismatch).
  24080. */
  24081. this.current = false;
  24082. /** Documents in the view but not in the remote target */
  24083. this.limboDocuments = documentKeySet();
  24084. /** Document Keys that have local changes */
  24085. this.mutatedKeys = documentKeySet();
  24086. this.docComparator = newQueryComparator(query);
  24087. this.documentSet = new DocumentSet(this.docComparator);
  24088. }
  24089. /**
  24090. * The set of remote documents that the server has told us belongs to the target associated with
  24091. * this view.
  24092. */
  24093. get syncedDocuments() {
  24094. return this._syncedDocuments;
  24095. }
  24096. /**
  24097. * Iterates over a set of doc changes, applies the query limit, and computes
  24098. * what the new results should be, what the changes were, and whether we may
  24099. * need to go back to the local cache for more results. Does not make any
  24100. * changes to the view.
  24101. * @param docChanges - The doc changes to apply to this view.
  24102. * @param previousChanges - If this is being called with a refill, then start
  24103. * with this set of docs and changes instead of the current view.
  24104. * @returns a new set of docs, changes, and refill flag.
  24105. */
  24106. computeDocChanges(docChanges, previousChanges) {
  24107. const changeSet = previousChanges
  24108. ? previousChanges.changeSet
  24109. : new DocumentChangeSet();
  24110. const oldDocumentSet = previousChanges
  24111. ? previousChanges.documentSet
  24112. : this.documentSet;
  24113. let newMutatedKeys = previousChanges
  24114. ? previousChanges.mutatedKeys
  24115. : this.mutatedKeys;
  24116. let newDocumentSet = oldDocumentSet;
  24117. let needsRefill = false;
  24118. // Track the last doc in a (full) limit. This is necessary, because some
  24119. // update (a delete, or an update moving a doc past the old limit) might
  24120. // mean there is some other document in the local cache that either should
  24121. // come (1) between the old last limit doc and the new last document, in the
  24122. // case of updates, or (2) after the new last document, in the case of
  24123. // deletes. So we keep this doc at the old limit to compare the updates to.
  24124. //
  24125. // Note that this should never get used in a refill (when previousChanges is
  24126. // set), because there will only be adds -- no deletes or updates.
  24127. const lastDocInLimit = this.query.limitType === "F" /* LimitType.First */ &&
  24128. oldDocumentSet.size === this.query.limit
  24129. ? oldDocumentSet.last()
  24130. : null;
  24131. const firstDocInLimit = this.query.limitType === "L" /* LimitType.Last */ &&
  24132. oldDocumentSet.size === this.query.limit
  24133. ? oldDocumentSet.first()
  24134. : null;
  24135. docChanges.inorderTraversal((key, entry) => {
  24136. const oldDoc = oldDocumentSet.get(key);
  24137. const newDoc = queryMatches(this.query, entry) ? entry : null;
  24138. const oldDocHadPendingMutations = oldDoc
  24139. ? this.mutatedKeys.has(oldDoc.key)
  24140. : false;
  24141. const newDocHasPendingMutations = newDoc
  24142. ? newDoc.hasLocalMutations ||
  24143. // We only consider committed mutations for documents that were
  24144. // mutated during the lifetime of the view.
  24145. (this.mutatedKeys.has(newDoc.key) && newDoc.hasCommittedMutations)
  24146. : false;
  24147. let changeApplied = false;
  24148. // Calculate change
  24149. if (oldDoc && newDoc) {
  24150. const docsEqual = oldDoc.data.isEqual(newDoc.data);
  24151. if (!docsEqual) {
  24152. if (!this.shouldWaitForSyncedDocument(oldDoc, newDoc)) {
  24153. changeSet.track({
  24154. type: 2 /* ChangeType.Modified */,
  24155. doc: newDoc
  24156. });
  24157. changeApplied = true;
  24158. if ((lastDocInLimit &&
  24159. this.docComparator(newDoc, lastDocInLimit) > 0) ||
  24160. (firstDocInLimit &&
  24161. this.docComparator(newDoc, firstDocInLimit) < 0)) {
  24162. // This doc moved from inside the limit to outside the limit.
  24163. // That means there may be some other doc in the local cache
  24164. // that should be included instead.
  24165. needsRefill = true;
  24166. }
  24167. }
  24168. }
  24169. else if (oldDocHadPendingMutations !== newDocHasPendingMutations) {
  24170. changeSet.track({ type: 3 /* ChangeType.Metadata */, doc: newDoc });
  24171. changeApplied = true;
  24172. }
  24173. }
  24174. else if (!oldDoc && newDoc) {
  24175. changeSet.track({ type: 0 /* ChangeType.Added */, doc: newDoc });
  24176. changeApplied = true;
  24177. }
  24178. else if (oldDoc && !newDoc) {
  24179. changeSet.track({ type: 1 /* ChangeType.Removed */, doc: oldDoc });
  24180. changeApplied = true;
  24181. if (lastDocInLimit || firstDocInLimit) {
  24182. // A doc was removed from a full limit query. We'll need to
  24183. // requery from the local cache to see if we know about some other
  24184. // doc that should be in the results.
  24185. needsRefill = true;
  24186. }
  24187. }
  24188. if (changeApplied) {
  24189. if (newDoc) {
  24190. newDocumentSet = newDocumentSet.add(newDoc);
  24191. if (newDocHasPendingMutations) {
  24192. newMutatedKeys = newMutatedKeys.add(key);
  24193. }
  24194. else {
  24195. newMutatedKeys = newMutatedKeys.delete(key);
  24196. }
  24197. }
  24198. else {
  24199. newDocumentSet = newDocumentSet.delete(key);
  24200. newMutatedKeys = newMutatedKeys.delete(key);
  24201. }
  24202. }
  24203. });
  24204. // Drop documents out to meet limit/limitToLast requirement.
  24205. if (this.query.limit !== null) {
  24206. while (newDocumentSet.size > this.query.limit) {
  24207. const oldDoc = this.query.limitType === "F" /* LimitType.First */
  24208. ? newDocumentSet.last()
  24209. : newDocumentSet.first();
  24210. newDocumentSet = newDocumentSet.delete(oldDoc.key);
  24211. newMutatedKeys = newMutatedKeys.delete(oldDoc.key);
  24212. changeSet.track({ type: 1 /* ChangeType.Removed */, doc: oldDoc });
  24213. }
  24214. }
  24215. return {
  24216. documentSet: newDocumentSet,
  24217. changeSet,
  24218. needsRefill,
  24219. mutatedKeys: newMutatedKeys
  24220. };
  24221. }
  24222. shouldWaitForSyncedDocument(oldDoc, newDoc) {
  24223. // We suppress the initial change event for documents that were modified as
  24224. // part of a write acknowledgment (e.g. when the value of a server transform
  24225. // is applied) as Watch will send us the same document again.
  24226. // By suppressing the event, we only raise two user visible events (one with
  24227. // `hasPendingWrites` and the final state of the document) instead of three
  24228. // (one with `hasPendingWrites`, the modified document with
  24229. // `hasPendingWrites` and the final state of the document).
  24230. return (oldDoc.hasLocalMutations &&
  24231. newDoc.hasCommittedMutations &&
  24232. !newDoc.hasLocalMutations);
  24233. }
  24234. /**
  24235. * Updates the view with the given ViewDocumentChanges and optionally updates
  24236. * limbo docs and sync state from the provided target change.
  24237. * @param docChanges - The set of changes to make to the view's docs.
  24238. * @param updateLimboDocuments - Whether to update limbo documents based on
  24239. * this change.
  24240. * @param targetChange - A target change to apply for computing limbo docs and
  24241. * sync state.
  24242. * @returns A new ViewChange with the given docs, changes, and sync state.
  24243. */
  24244. // PORTING NOTE: The iOS/Android clients always compute limbo document changes.
  24245. applyChanges(docChanges, updateLimboDocuments, targetChange) {
  24246. const oldDocs = this.documentSet;
  24247. this.documentSet = docChanges.documentSet;
  24248. this.mutatedKeys = docChanges.mutatedKeys;
  24249. // Sort changes based on type and query comparator
  24250. const changes = docChanges.changeSet.getChanges();
  24251. changes.sort((c1, c2) => {
  24252. return (compareChangeType(c1.type, c2.type) ||
  24253. this.docComparator(c1.doc, c2.doc));
  24254. });
  24255. this.applyTargetChange(targetChange);
  24256. const limboChanges = updateLimboDocuments
  24257. ? this.updateLimboDocuments()
  24258. : [];
  24259. const synced = this.limboDocuments.size === 0 && this.current;
  24260. const newSyncState = synced ? 1 /* SyncState.Synced */ : 0 /* SyncState.Local */;
  24261. const syncStateChanged = newSyncState !== this.syncState;
  24262. this.syncState = newSyncState;
  24263. if (changes.length === 0 && !syncStateChanged) {
  24264. // no changes
  24265. return { limboChanges };
  24266. }
  24267. else {
  24268. const snap = new ViewSnapshot(this.query, docChanges.documentSet, oldDocs, changes, docChanges.mutatedKeys, newSyncState === 0 /* SyncState.Local */, syncStateChanged,
  24269. /* excludesMetadataChanges= */ false, targetChange
  24270. ? targetChange.resumeToken.approximateByteSize() > 0
  24271. : false);
  24272. return {
  24273. snapshot: snap,
  24274. limboChanges
  24275. };
  24276. }
  24277. }
  24278. /**
  24279. * Applies an OnlineState change to the view, potentially generating a
  24280. * ViewChange if the view's syncState changes as a result.
  24281. */
  24282. applyOnlineStateChange(onlineState) {
  24283. if (this.current && onlineState === "Offline" /* OnlineState.Offline */) {
  24284. // If we're offline, set `current` to false and then call applyChanges()
  24285. // to refresh our syncState and generate a ViewChange as appropriate. We
  24286. // are guaranteed to get a new TargetChange that sets `current` back to
  24287. // true once the client is back online.
  24288. this.current = false;
  24289. return this.applyChanges({
  24290. documentSet: this.documentSet,
  24291. changeSet: new DocumentChangeSet(),
  24292. mutatedKeys: this.mutatedKeys,
  24293. needsRefill: false
  24294. },
  24295. /* updateLimboDocuments= */ false);
  24296. }
  24297. else {
  24298. // No effect, just return a no-op ViewChange.
  24299. return { limboChanges: [] };
  24300. }
  24301. }
  24302. /**
  24303. * Returns whether the doc for the given key should be in limbo.
  24304. */
  24305. shouldBeInLimbo(key) {
  24306. // If the remote end says it's part of this query, it's not in limbo.
  24307. if (this._syncedDocuments.has(key)) {
  24308. return false;
  24309. }
  24310. // The local store doesn't think it's a result, so it shouldn't be in limbo.
  24311. if (!this.documentSet.has(key)) {
  24312. return false;
  24313. }
  24314. // If there are local changes to the doc, they might explain why the server
  24315. // doesn't know that it's part of the query. So don't put it in limbo.
  24316. // TODO(klimt): Ideally, we would only consider changes that might actually
  24317. // affect this specific query.
  24318. if (this.documentSet.get(key).hasLocalMutations) {
  24319. return false;
  24320. }
  24321. // Everything else is in limbo.
  24322. return true;
  24323. }
  24324. /**
  24325. * Updates syncedDocuments, current, and limbo docs based on the given change.
  24326. * Returns the list of changes to which docs are in limbo.
  24327. */
  24328. applyTargetChange(targetChange) {
  24329. if (targetChange) {
  24330. targetChange.addedDocuments.forEach(key => (this._syncedDocuments = this._syncedDocuments.add(key)));
  24331. targetChange.modifiedDocuments.forEach(key => {
  24332. });
  24333. targetChange.removedDocuments.forEach(key => (this._syncedDocuments = this._syncedDocuments.delete(key)));
  24334. this.current = targetChange.current;
  24335. }
  24336. }
  24337. updateLimboDocuments() {
  24338. // We can only determine limbo documents when we're in-sync with the server.
  24339. if (!this.current) {
  24340. return [];
  24341. }
  24342. // TODO(klimt): Do this incrementally so that it's not quadratic when
  24343. // updating many documents.
  24344. const oldLimboDocuments = this.limboDocuments;
  24345. this.limboDocuments = documentKeySet();
  24346. this.documentSet.forEach(doc => {
  24347. if (this.shouldBeInLimbo(doc.key)) {
  24348. this.limboDocuments = this.limboDocuments.add(doc.key);
  24349. }
  24350. });
  24351. // Diff the new limbo docs with the old limbo docs.
  24352. const changes = [];
  24353. oldLimboDocuments.forEach(key => {
  24354. if (!this.limboDocuments.has(key)) {
  24355. changes.push(new RemovedLimboDocument(key));
  24356. }
  24357. });
  24358. this.limboDocuments.forEach(key => {
  24359. if (!oldLimboDocuments.has(key)) {
  24360. changes.push(new AddedLimboDocument(key));
  24361. }
  24362. });
  24363. return changes;
  24364. }
  24365. /**
  24366. * Update the in-memory state of the current view with the state read from
  24367. * persistence.
  24368. *
  24369. * We update the query view whenever a client's primary status changes:
  24370. * - When a client transitions from primary to secondary, it can miss
  24371. * LocalStorage updates and its query views may temporarily not be
  24372. * synchronized with the state on disk.
  24373. * - For secondary to primary transitions, the client needs to update the list
  24374. * of `syncedDocuments` since secondary clients update their query views
  24375. * based purely on synthesized RemoteEvents.
  24376. *
  24377. * @param queryResult.documents - The documents that match the query according
  24378. * to the LocalStore.
  24379. * @param queryResult.remoteKeys - The keys of the documents that match the
  24380. * query according to the backend.
  24381. *
  24382. * @returns The ViewChange that resulted from this synchronization.
  24383. */
  24384. // PORTING NOTE: Multi-tab only.
  24385. synchronizeWithPersistedState(queryResult) {
  24386. this._syncedDocuments = queryResult.remoteKeys;
  24387. this.limboDocuments = documentKeySet();
  24388. const docChanges = this.computeDocChanges(queryResult.documents);
  24389. return this.applyChanges(docChanges, /*updateLimboDocuments=*/ true);
  24390. }
  24391. /**
  24392. * Returns a view snapshot as if this query was just listened to. Contains
  24393. * a document add for every existing document and the `fromCache` and
  24394. * `hasPendingWrites` status of the already established view.
  24395. */
  24396. // PORTING NOTE: Multi-tab only.
  24397. computeInitialSnapshot() {
  24398. return ViewSnapshot.fromInitialDocuments(this.query, this.documentSet, this.mutatedKeys, this.syncState === 0 /* SyncState.Local */, this.hasCachedResults);
  24399. }
  24400. }
  24401. function compareChangeType(c1, c2) {
  24402. const order = (change) => {
  24403. switch (change) {
  24404. case 0 /* ChangeType.Added */:
  24405. return 1;
  24406. case 2 /* ChangeType.Modified */:
  24407. return 2;
  24408. case 3 /* ChangeType.Metadata */:
  24409. // A metadata change is converted to a modified change at the public
  24410. // api layer. Since we sort by document key and then change type,
  24411. // metadata and modified changes must be sorted equivalently.
  24412. return 2;
  24413. case 1 /* ChangeType.Removed */:
  24414. return 0;
  24415. default:
  24416. return fail();
  24417. }
  24418. };
  24419. return order(c1) - order(c2);
  24420. }
  24421. /**
  24422. * @license
  24423. * Copyright 2020 Google LLC
  24424. *
  24425. * Licensed under the Apache License, Version 2.0 (the "License");
  24426. * you may not use this file except in compliance with the License.
  24427. * You may obtain a copy of the License at
  24428. *
  24429. * http://www.apache.org/licenses/LICENSE-2.0
  24430. *
  24431. * Unless required by applicable law or agreed to in writing, software
  24432. * distributed under the License is distributed on an "AS IS" BASIS,
  24433. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24434. * See the License for the specific language governing permissions and
  24435. * limitations under the License.
  24436. */
  24437. const LOG_TAG$3 = 'SyncEngine';
  24438. /**
  24439. * QueryView contains all of the data that SyncEngine needs to keep track of for
  24440. * a particular query.
  24441. */
  24442. class QueryView {
  24443. constructor(
  24444. /**
  24445. * The query itself.
  24446. */
  24447. query,
  24448. /**
  24449. * The target number created by the client that is used in the watch
  24450. * stream to identify this query.
  24451. */
  24452. targetId,
  24453. /**
  24454. * The view is responsible for computing the final merged truth of what
  24455. * docs are in the query. It gets notified of local and remote changes,
  24456. * and applies the query filters and limits to determine the most correct
  24457. * possible results.
  24458. */
  24459. view) {
  24460. this.query = query;
  24461. this.targetId = targetId;
  24462. this.view = view;
  24463. }
  24464. }
  24465. /** Tracks a limbo resolution. */
  24466. class LimboResolution {
  24467. constructor(key) {
  24468. this.key = key;
  24469. /**
  24470. * Set to true once we've received a document. This is used in
  24471. * getRemoteKeysForTarget() and ultimately used by WatchChangeAggregator to
  24472. * decide whether it needs to manufacture a delete event for the target once
  24473. * the target is CURRENT.
  24474. */
  24475. this.receivedDocument = false;
  24476. }
  24477. }
  24478. /**
  24479. * An implementation of `SyncEngine` coordinating with other parts of SDK.
  24480. *
  24481. * The parts of SyncEngine that act as a callback to RemoteStore need to be
  24482. * registered individually. This is done in `syncEngineWrite()` and
  24483. * `syncEngineListen()` (as well as `applyPrimaryState()`) as these methods
  24484. * serve as entry points to RemoteStore's functionality.
  24485. *
  24486. * Note: some field defined in this class might have public access level, but
  24487. * the class is not exported so they are only accessible from this module.
  24488. * This is useful to implement optional features (like bundles) in free
  24489. * functions, such that they are tree-shakeable.
  24490. */
  24491. class SyncEngineImpl {
  24492. constructor(localStore, remoteStore, eventManager,
  24493. // PORTING NOTE: Manages state synchronization in multi-tab environments.
  24494. sharedClientState, currentUser, maxConcurrentLimboResolutions) {
  24495. this.localStore = localStore;
  24496. this.remoteStore = remoteStore;
  24497. this.eventManager = eventManager;
  24498. this.sharedClientState = sharedClientState;
  24499. this.currentUser = currentUser;
  24500. this.maxConcurrentLimboResolutions = maxConcurrentLimboResolutions;
  24501. this.syncEngineListener = {};
  24502. this.queryViewsByQuery = new ObjectMap(q => canonifyQuery(q), queryEquals);
  24503. this.queriesByTarget = new Map();
  24504. /**
  24505. * The keys of documents that are in limbo for which we haven't yet started a
  24506. * limbo resolution query. The strings in this set are the result of calling
  24507. * `key.path.canonicalString()` where `key` is a `DocumentKey` object.
  24508. *
  24509. * The `Set` type was chosen because it provides efficient lookup and removal
  24510. * of arbitrary elements and it also maintains insertion order, providing the
  24511. * desired queue-like FIFO semantics.
  24512. */
  24513. this.enqueuedLimboResolutions = new Set();
  24514. /**
  24515. * Keeps track of the target ID for each document that is in limbo with an
  24516. * active target.
  24517. */
  24518. this.activeLimboTargetsByKey = new SortedMap(DocumentKey.comparator);
  24519. /**
  24520. * Keeps track of the information about an active limbo resolution for each
  24521. * active target ID that was started for the purpose of limbo resolution.
  24522. */
  24523. this.activeLimboResolutionsByTarget = new Map();
  24524. this.limboDocumentRefs = new ReferenceSet();
  24525. /** Stores user completion handlers, indexed by User and BatchId. */
  24526. this.mutationUserCallbacks = {};
  24527. /** Stores user callbacks waiting for all pending writes to be acknowledged. */
  24528. this.pendingWritesCallbacks = new Map();
  24529. this.limboTargetIdGenerator = TargetIdGenerator.forSyncEngine();
  24530. this.onlineState = "Unknown" /* OnlineState.Unknown */;
  24531. // The primary state is set to `true` or `false` immediately after Firestore
  24532. // startup. In the interim, a client should only be considered primary if
  24533. // `isPrimary` is true.
  24534. this._isPrimaryClient = undefined;
  24535. }
  24536. get isPrimaryClient() {
  24537. return this._isPrimaryClient === true;
  24538. }
  24539. }
  24540. function newSyncEngine(localStore, remoteStore, eventManager,
  24541. // PORTING NOTE: Manages state synchronization in multi-tab environments.
  24542. sharedClientState, currentUser, maxConcurrentLimboResolutions, isPrimary) {
  24543. const syncEngine = new SyncEngineImpl(localStore, remoteStore, eventManager, sharedClientState, currentUser, maxConcurrentLimboResolutions);
  24544. if (isPrimary) {
  24545. syncEngine._isPrimaryClient = true;
  24546. }
  24547. return syncEngine;
  24548. }
  24549. /**
  24550. * Initiates the new listen, resolves promise when listen enqueued to the
  24551. * server. All the subsequent view snapshots or errors are sent to the
  24552. * subscribed handlers. Returns the initial snapshot.
  24553. */
  24554. async function syncEngineListen(syncEngine, query) {
  24555. const syncEngineImpl = ensureWatchCallbacks(syncEngine);
  24556. let targetId;
  24557. let viewSnapshot;
  24558. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  24559. if (queryView) {
  24560. // PORTING NOTE: With Multi-Tab Web, it is possible that a query view
  24561. // already exists when EventManager calls us for the first time. This
  24562. // happens when the primary tab is already listening to this query on
  24563. // behalf of another tab and the user of the primary also starts listening
  24564. // to the query. EventManager will not have an assigned target ID in this
  24565. // case and calls `listen` to obtain this ID.
  24566. targetId = queryView.targetId;
  24567. syncEngineImpl.sharedClientState.addLocalQueryTarget(targetId);
  24568. viewSnapshot = queryView.view.computeInitialSnapshot();
  24569. }
  24570. else {
  24571. const targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, queryToTarget(query));
  24572. const status = syncEngineImpl.sharedClientState.addLocalQueryTarget(targetData.targetId);
  24573. targetId = targetData.targetId;
  24574. viewSnapshot = await initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, status === 'current', targetData.resumeToken);
  24575. if (syncEngineImpl.isPrimaryClient) {
  24576. remoteStoreListen(syncEngineImpl.remoteStore, targetData);
  24577. }
  24578. }
  24579. return viewSnapshot;
  24580. }
  24581. /**
  24582. * Registers a view for a previously unknown query and computes its initial
  24583. * snapshot.
  24584. */
  24585. async function initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, current, resumeToken) {
  24586. // PORTING NOTE: On Web only, we inject the code that registers new Limbo
  24587. // targets based on view changes. This allows us to only depend on Limbo
  24588. // changes when user code includes queries.
  24589. syncEngineImpl.applyDocChanges = (queryView, changes, remoteEvent) => applyDocChanges(syncEngineImpl, queryView, changes, remoteEvent);
  24590. const queryResult = await localStoreExecuteQuery(syncEngineImpl.localStore, query,
  24591. /* usePreviousResults= */ true);
  24592. const view = new View(query, queryResult.remoteKeys);
  24593. const viewDocChanges = view.computeDocChanges(queryResult.documents);
  24594. const synthesizedTargetChange = TargetChange.createSynthesizedTargetChangeForCurrentChange(targetId, current && syncEngineImpl.onlineState !== "Offline" /* OnlineState.Offline */, resumeToken);
  24595. const viewChange = view.applyChanges(viewDocChanges,
  24596. /* updateLimboDocuments= */ syncEngineImpl.isPrimaryClient, synthesizedTargetChange);
  24597. updateTrackedLimbos(syncEngineImpl, targetId, viewChange.limboChanges);
  24598. const data = new QueryView(query, targetId, view);
  24599. syncEngineImpl.queryViewsByQuery.set(query, data);
  24600. if (syncEngineImpl.queriesByTarget.has(targetId)) {
  24601. syncEngineImpl.queriesByTarget.get(targetId).push(query);
  24602. }
  24603. else {
  24604. syncEngineImpl.queriesByTarget.set(targetId, [query]);
  24605. }
  24606. return viewChange.snapshot;
  24607. }
  24608. /** Stops listening to the query. */
  24609. async function syncEngineUnlisten(syncEngine, query) {
  24610. const syncEngineImpl = debugCast(syncEngine);
  24611. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  24612. // Only clean up the query view and target if this is the only query mapped
  24613. // to the target.
  24614. const queries = syncEngineImpl.queriesByTarget.get(queryView.targetId);
  24615. if (queries.length > 1) {
  24616. syncEngineImpl.queriesByTarget.set(queryView.targetId, queries.filter(q => !queryEquals(q, query)));
  24617. syncEngineImpl.queryViewsByQuery.delete(query);
  24618. return;
  24619. }
  24620. // No other queries are mapped to the target, clean up the query and the target.
  24621. if (syncEngineImpl.isPrimaryClient) {
  24622. // We need to remove the local query target first to allow us to verify
  24623. // whether any other client is still interested in this target.
  24624. syncEngineImpl.sharedClientState.removeLocalQueryTarget(queryView.targetId);
  24625. const targetRemainsActive = syncEngineImpl.sharedClientState.isActiveQueryTarget(queryView.targetId);
  24626. if (!targetRemainsActive) {
  24627. await localStoreReleaseTarget(syncEngineImpl.localStore, queryView.targetId,
  24628. /*keepPersistedTargetData=*/ false)
  24629. .then(() => {
  24630. syncEngineImpl.sharedClientState.clearQueryState(queryView.targetId);
  24631. remoteStoreUnlisten(syncEngineImpl.remoteStore, queryView.targetId);
  24632. removeAndCleanupTarget(syncEngineImpl, queryView.targetId);
  24633. })
  24634. .catch(ignoreIfPrimaryLeaseLoss);
  24635. }
  24636. }
  24637. else {
  24638. removeAndCleanupTarget(syncEngineImpl, queryView.targetId);
  24639. await localStoreReleaseTarget(syncEngineImpl.localStore, queryView.targetId,
  24640. /*keepPersistedTargetData=*/ true);
  24641. }
  24642. }
  24643. /**
  24644. * Initiates the write of local mutation batch which involves adding the
  24645. * writes to the mutation queue, notifying the remote store about new
  24646. * mutations and raising events for any changes this write caused.
  24647. *
  24648. * The promise returned by this call is resolved when the above steps
  24649. * have completed, *not* when the write was acked by the backend. The
  24650. * userCallback is resolved once the write was acked/rejected by the
  24651. * backend (or failed locally for any other reason).
  24652. */
  24653. async function syncEngineWrite(syncEngine, batch, userCallback) {
  24654. const syncEngineImpl = syncEngineEnsureWriteCallbacks(syncEngine);
  24655. try {
  24656. const result = await localStoreWriteLocally(syncEngineImpl.localStore, batch);
  24657. syncEngineImpl.sharedClientState.addPendingMutation(result.batchId);
  24658. addMutationCallback(syncEngineImpl, result.batchId, userCallback);
  24659. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, result.changes);
  24660. await fillWritePipeline(syncEngineImpl.remoteStore);
  24661. }
  24662. catch (e) {
  24663. // If we can't persist the mutation, we reject the user callback and
  24664. // don't send the mutation. The user can then retry the write.
  24665. const error = wrapInUserErrorIfRecoverable(e, `Failed to persist write`);
  24666. userCallback.reject(error);
  24667. }
  24668. }
  24669. /**
  24670. * Applies one remote event to the sync engine, notifying any views of the
  24671. * changes, and releasing any pending mutation batches that would become
  24672. * visible because of the snapshot version the remote event contains.
  24673. */
  24674. async function syncEngineApplyRemoteEvent(syncEngine, remoteEvent) {
  24675. const syncEngineImpl = debugCast(syncEngine);
  24676. try {
  24677. const changes = await localStoreApplyRemoteEventToLocalCache(syncEngineImpl.localStore, remoteEvent);
  24678. // Update `receivedDocument` as appropriate for any limbo targets.
  24679. remoteEvent.targetChanges.forEach((targetChange, targetId) => {
  24680. const limboResolution = syncEngineImpl.activeLimboResolutionsByTarget.get(targetId);
  24681. if (limboResolution) {
  24682. // Since this is a limbo resolution lookup, it's for a single document
  24683. // and it could be added, modified, or removed, but not a combination.
  24684. hardAssert(targetChange.addedDocuments.size +
  24685. targetChange.modifiedDocuments.size +
  24686. targetChange.removedDocuments.size <=
  24687. 1);
  24688. if (targetChange.addedDocuments.size > 0) {
  24689. limboResolution.receivedDocument = true;
  24690. }
  24691. else if (targetChange.modifiedDocuments.size > 0) {
  24692. hardAssert(limboResolution.receivedDocument);
  24693. }
  24694. else if (targetChange.removedDocuments.size > 0) {
  24695. hardAssert(limboResolution.receivedDocument);
  24696. limboResolution.receivedDocument = false;
  24697. }
  24698. else {
  24699. // This was probably just a CURRENT targetChange or similar.
  24700. }
  24701. }
  24702. });
  24703. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, remoteEvent);
  24704. }
  24705. catch (error) {
  24706. await ignoreIfPrimaryLeaseLoss(error);
  24707. }
  24708. }
  24709. /**
  24710. * Applies an OnlineState change to the sync engine and notifies any views of
  24711. * the change.
  24712. */
  24713. function syncEngineApplyOnlineStateChange(syncEngine, onlineState, source) {
  24714. const syncEngineImpl = debugCast(syncEngine);
  24715. // If we are the secondary client, we explicitly ignore the remote store's
  24716. // online state (the local client may go offline, even though the primary
  24717. // tab remains online) and only apply the primary tab's online state from
  24718. // SharedClientState.
  24719. if ((syncEngineImpl.isPrimaryClient &&
  24720. source === 0 /* OnlineStateSource.RemoteStore */) ||
  24721. (!syncEngineImpl.isPrimaryClient &&
  24722. source === 1 /* OnlineStateSource.SharedClientState */)) {
  24723. const newViewSnapshots = [];
  24724. syncEngineImpl.queryViewsByQuery.forEach((query, queryView) => {
  24725. const viewChange = queryView.view.applyOnlineStateChange(onlineState);
  24726. if (viewChange.snapshot) {
  24727. newViewSnapshots.push(viewChange.snapshot);
  24728. }
  24729. });
  24730. eventManagerOnOnlineStateChange(syncEngineImpl.eventManager, onlineState);
  24731. if (newViewSnapshots.length) {
  24732. syncEngineImpl.syncEngineListener.onWatchChange(newViewSnapshots);
  24733. }
  24734. syncEngineImpl.onlineState = onlineState;
  24735. if (syncEngineImpl.isPrimaryClient) {
  24736. syncEngineImpl.sharedClientState.setOnlineState(onlineState);
  24737. }
  24738. }
  24739. }
  24740. /**
  24741. * Rejects the listen for the given targetID. This can be triggered by the
  24742. * backend for any active target.
  24743. *
  24744. * @param syncEngine - The sync engine implementation.
  24745. * @param targetId - The targetID corresponds to one previously initiated by the
  24746. * user as part of TargetData passed to listen() on RemoteStore.
  24747. * @param err - A description of the condition that has forced the rejection.
  24748. * Nearly always this will be an indication that the user is no longer
  24749. * authorized to see the data matching the target.
  24750. */
  24751. async function syncEngineRejectListen(syncEngine, targetId, err) {
  24752. const syncEngineImpl = debugCast(syncEngine);
  24753. // PORTING NOTE: Multi-tab only.
  24754. syncEngineImpl.sharedClientState.updateQueryState(targetId, 'rejected', err);
  24755. const limboResolution = syncEngineImpl.activeLimboResolutionsByTarget.get(targetId);
  24756. const limboKey = limboResolution && limboResolution.key;
  24757. if (limboKey) {
  24758. // TODO(klimt): We really only should do the following on permission
  24759. // denied errors, but we don't have the cause code here.
  24760. // It's a limbo doc. Create a synthetic event saying it was deleted.
  24761. // This is kind of a hack. Ideally, we would have a method in the local
  24762. // store to purge a document. However, it would be tricky to keep all of
  24763. // the local store's invariants with another method.
  24764. let documentUpdates = new SortedMap(DocumentKey.comparator);
  24765. // TODO(b/217189216): This limbo document should ideally have a read time,
  24766. // so that it is picked up by any read-time based scans. The backend,
  24767. // however, does not send a read time for target removals.
  24768. documentUpdates = documentUpdates.insert(limboKey, MutableDocument.newNoDocument(limboKey, SnapshotVersion.min()));
  24769. const resolvedLimboDocuments = documentKeySet().add(limboKey);
  24770. const event = new RemoteEvent(SnapshotVersion.min(),
  24771. /* targetChanges= */ new Map(),
  24772. /* targetMismatches= */ new SortedMap(primitiveComparator), documentUpdates, resolvedLimboDocuments);
  24773. await syncEngineApplyRemoteEvent(syncEngineImpl, event);
  24774. // Since this query failed, we won't want to manually unlisten to it.
  24775. // We only remove it from bookkeeping after we successfully applied the
  24776. // RemoteEvent. If `applyRemoteEvent()` throws, we want to re-listen to
  24777. // this query when the RemoteStore restarts the Watch stream, which should
  24778. // re-trigger the target failure.
  24779. syncEngineImpl.activeLimboTargetsByKey =
  24780. syncEngineImpl.activeLimboTargetsByKey.remove(limboKey);
  24781. syncEngineImpl.activeLimboResolutionsByTarget.delete(targetId);
  24782. pumpEnqueuedLimboResolutions(syncEngineImpl);
  24783. }
  24784. else {
  24785. await localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  24786. /* keepPersistedTargetData */ false)
  24787. .then(() => removeAndCleanupTarget(syncEngineImpl, targetId, err))
  24788. .catch(ignoreIfPrimaryLeaseLoss);
  24789. }
  24790. }
  24791. async function syncEngineApplySuccessfulWrite(syncEngine, mutationBatchResult) {
  24792. const syncEngineImpl = debugCast(syncEngine);
  24793. const batchId = mutationBatchResult.batch.batchId;
  24794. try {
  24795. const changes = await localStoreAcknowledgeBatch(syncEngineImpl.localStore, mutationBatchResult);
  24796. // The local store may or may not be able to apply the write result and
  24797. // raise events immediately (depending on whether the watcher is caught
  24798. // up), so we raise user callbacks first so that they consistently happen
  24799. // before listen events.
  24800. processUserCallback(syncEngineImpl, batchId, /*error=*/ null);
  24801. triggerPendingWritesCallbacks(syncEngineImpl, batchId);
  24802. syncEngineImpl.sharedClientState.updateMutationState(batchId, 'acknowledged');
  24803. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes);
  24804. }
  24805. catch (error) {
  24806. await ignoreIfPrimaryLeaseLoss(error);
  24807. }
  24808. }
  24809. async function syncEngineRejectFailedWrite(syncEngine, batchId, error) {
  24810. const syncEngineImpl = debugCast(syncEngine);
  24811. try {
  24812. const changes = await localStoreRejectBatch(syncEngineImpl.localStore, batchId);
  24813. // The local store may or may not be able to apply the write result and
  24814. // raise events immediately (depending on whether the watcher is caught up),
  24815. // so we raise user callbacks first so that they consistently happen before
  24816. // listen events.
  24817. processUserCallback(syncEngineImpl, batchId, error);
  24818. triggerPendingWritesCallbacks(syncEngineImpl, batchId);
  24819. syncEngineImpl.sharedClientState.updateMutationState(batchId, 'rejected', error);
  24820. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes);
  24821. }
  24822. catch (error) {
  24823. await ignoreIfPrimaryLeaseLoss(error);
  24824. }
  24825. }
  24826. /**
  24827. * Registers a user callback that resolves when all pending mutations at the moment of calling
  24828. * are acknowledged .
  24829. */
  24830. async function syncEngineRegisterPendingWritesCallback(syncEngine, callback) {
  24831. const syncEngineImpl = debugCast(syncEngine);
  24832. if (!canUseNetwork(syncEngineImpl.remoteStore)) {
  24833. logDebug(LOG_TAG$3, 'The network is disabled. The task returned by ' +
  24834. "'awaitPendingWrites()' will not complete until the network is enabled.");
  24835. }
  24836. try {
  24837. const highestBatchId = await localStoreGetHighestUnacknowledgedBatchId(syncEngineImpl.localStore);
  24838. if (highestBatchId === BATCHID_UNKNOWN) {
  24839. // Trigger the callback right away if there is no pending writes at the moment.
  24840. callback.resolve();
  24841. return;
  24842. }
  24843. const callbacks = syncEngineImpl.pendingWritesCallbacks.get(highestBatchId) || [];
  24844. callbacks.push(callback);
  24845. syncEngineImpl.pendingWritesCallbacks.set(highestBatchId, callbacks);
  24846. }
  24847. catch (e) {
  24848. const firestoreError = wrapInUserErrorIfRecoverable(e, 'Initialization of waitForPendingWrites() operation failed');
  24849. callback.reject(firestoreError);
  24850. }
  24851. }
  24852. /**
  24853. * Triggers the callbacks that are waiting for this batch id to get acknowledged by server,
  24854. * if there are any.
  24855. */
  24856. function triggerPendingWritesCallbacks(syncEngineImpl, batchId) {
  24857. (syncEngineImpl.pendingWritesCallbacks.get(batchId) || []).forEach(callback => {
  24858. callback.resolve();
  24859. });
  24860. syncEngineImpl.pendingWritesCallbacks.delete(batchId);
  24861. }
  24862. /** Reject all outstanding callbacks waiting for pending writes to complete. */
  24863. function rejectOutstandingPendingWritesCallbacks(syncEngineImpl, errorMessage) {
  24864. syncEngineImpl.pendingWritesCallbacks.forEach(callbacks => {
  24865. callbacks.forEach(callback => {
  24866. callback.reject(new FirestoreError(Code.CANCELLED, errorMessage));
  24867. });
  24868. });
  24869. syncEngineImpl.pendingWritesCallbacks.clear();
  24870. }
  24871. function addMutationCallback(syncEngineImpl, batchId, callback) {
  24872. let newCallbacks = syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()];
  24873. if (!newCallbacks) {
  24874. newCallbacks = new SortedMap(primitiveComparator);
  24875. }
  24876. newCallbacks = newCallbacks.insert(batchId, callback);
  24877. syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()] =
  24878. newCallbacks;
  24879. }
  24880. /**
  24881. * Resolves or rejects the user callback for the given batch and then discards
  24882. * it.
  24883. */
  24884. function processUserCallback(syncEngine, batchId, error) {
  24885. const syncEngineImpl = debugCast(syncEngine);
  24886. let newCallbacks = syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()];
  24887. // NOTE: Mutations restored from persistence won't have callbacks, so it's
  24888. // okay for there to be no callback for this ID.
  24889. if (newCallbacks) {
  24890. const callback = newCallbacks.get(batchId);
  24891. if (callback) {
  24892. if (error) {
  24893. callback.reject(error);
  24894. }
  24895. else {
  24896. callback.resolve();
  24897. }
  24898. newCallbacks = newCallbacks.remove(batchId);
  24899. }
  24900. syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()] =
  24901. newCallbacks;
  24902. }
  24903. }
  24904. function removeAndCleanupTarget(syncEngineImpl, targetId, error = null) {
  24905. syncEngineImpl.sharedClientState.removeLocalQueryTarget(targetId);
  24906. for (const query of syncEngineImpl.queriesByTarget.get(targetId)) {
  24907. syncEngineImpl.queryViewsByQuery.delete(query);
  24908. if (error) {
  24909. syncEngineImpl.syncEngineListener.onWatchError(query, error);
  24910. }
  24911. }
  24912. syncEngineImpl.queriesByTarget.delete(targetId);
  24913. if (syncEngineImpl.isPrimaryClient) {
  24914. const limboKeys = syncEngineImpl.limboDocumentRefs.removeReferencesForId(targetId);
  24915. limboKeys.forEach(limboKey => {
  24916. const isReferenced = syncEngineImpl.limboDocumentRefs.containsKey(limboKey);
  24917. if (!isReferenced) {
  24918. // We removed the last reference for this key
  24919. removeLimboTarget(syncEngineImpl, limboKey);
  24920. }
  24921. });
  24922. }
  24923. }
  24924. function removeLimboTarget(syncEngineImpl, key) {
  24925. syncEngineImpl.enqueuedLimboResolutions.delete(key.path.canonicalString());
  24926. // It's possible that the target already got removed because the query failed. In that case,
  24927. // the key won't exist in `limboTargetsByKey`. Only do the cleanup if we still have the target.
  24928. const limboTargetId = syncEngineImpl.activeLimboTargetsByKey.get(key);
  24929. if (limboTargetId === null) {
  24930. // This target already got removed, because the query failed.
  24931. return;
  24932. }
  24933. remoteStoreUnlisten(syncEngineImpl.remoteStore, limboTargetId);
  24934. syncEngineImpl.activeLimboTargetsByKey =
  24935. syncEngineImpl.activeLimboTargetsByKey.remove(key);
  24936. syncEngineImpl.activeLimboResolutionsByTarget.delete(limboTargetId);
  24937. pumpEnqueuedLimboResolutions(syncEngineImpl);
  24938. }
  24939. function updateTrackedLimbos(syncEngineImpl, targetId, limboChanges) {
  24940. for (const limboChange of limboChanges) {
  24941. if (limboChange instanceof AddedLimboDocument) {
  24942. syncEngineImpl.limboDocumentRefs.addReference(limboChange.key, targetId);
  24943. trackLimboChange(syncEngineImpl, limboChange);
  24944. }
  24945. else if (limboChange instanceof RemovedLimboDocument) {
  24946. logDebug(LOG_TAG$3, 'Document no longer in limbo: ' + limboChange.key);
  24947. syncEngineImpl.limboDocumentRefs.removeReference(limboChange.key, targetId);
  24948. const isReferenced = syncEngineImpl.limboDocumentRefs.containsKey(limboChange.key);
  24949. if (!isReferenced) {
  24950. // We removed the last reference for this key
  24951. removeLimboTarget(syncEngineImpl, limboChange.key);
  24952. }
  24953. }
  24954. else {
  24955. fail();
  24956. }
  24957. }
  24958. }
  24959. function trackLimboChange(syncEngineImpl, limboChange) {
  24960. const key = limboChange.key;
  24961. const keyString = key.path.canonicalString();
  24962. if (!syncEngineImpl.activeLimboTargetsByKey.get(key) &&
  24963. !syncEngineImpl.enqueuedLimboResolutions.has(keyString)) {
  24964. logDebug(LOG_TAG$3, 'New document in limbo: ' + key);
  24965. syncEngineImpl.enqueuedLimboResolutions.add(keyString);
  24966. pumpEnqueuedLimboResolutions(syncEngineImpl);
  24967. }
  24968. }
  24969. /**
  24970. * Starts listens for documents in limbo that are enqueued for resolution,
  24971. * subject to a maximum number of concurrent resolutions.
  24972. *
  24973. * Without bounding the number of concurrent resolutions, the server can fail
  24974. * with "resource exhausted" errors which can lead to pathological client
  24975. * behavior as seen in https://github.com/firebase/firebase-js-sdk/issues/2683.
  24976. */
  24977. function pumpEnqueuedLimboResolutions(syncEngineImpl) {
  24978. while (syncEngineImpl.enqueuedLimboResolutions.size > 0 &&
  24979. syncEngineImpl.activeLimboTargetsByKey.size <
  24980. syncEngineImpl.maxConcurrentLimboResolutions) {
  24981. const keyString = syncEngineImpl.enqueuedLimboResolutions
  24982. .values()
  24983. .next().value;
  24984. syncEngineImpl.enqueuedLimboResolutions.delete(keyString);
  24985. const key = new DocumentKey(ResourcePath.fromString(keyString));
  24986. const limboTargetId = syncEngineImpl.limboTargetIdGenerator.next();
  24987. syncEngineImpl.activeLimboResolutionsByTarget.set(limboTargetId, new LimboResolution(key));
  24988. syncEngineImpl.activeLimboTargetsByKey =
  24989. syncEngineImpl.activeLimboTargetsByKey.insert(key, limboTargetId);
  24990. remoteStoreListen(syncEngineImpl.remoteStore, new TargetData(queryToTarget(newQueryForPath(key.path)), limboTargetId, "TargetPurposeLimboResolution" /* TargetPurpose.LimboResolution */, ListenSequence.INVALID));
  24991. }
  24992. }
  24993. async function syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, changes, remoteEvent) {
  24994. const syncEngineImpl = debugCast(syncEngine);
  24995. const newSnaps = [];
  24996. const docChangesInAllViews = [];
  24997. const queriesProcessed = [];
  24998. if (syncEngineImpl.queryViewsByQuery.isEmpty()) {
  24999. // Return early since `onWatchChange()` might not have been assigned yet.
  25000. return;
  25001. }
  25002. syncEngineImpl.queryViewsByQuery.forEach((_, queryView) => {
  25003. queriesProcessed.push(syncEngineImpl
  25004. .applyDocChanges(queryView, changes, remoteEvent)
  25005. .then(viewSnapshot => {
  25006. // If there are changes, or we are handling a global snapshot, notify
  25007. // secondary clients to update query state.
  25008. if (viewSnapshot || remoteEvent) {
  25009. if (syncEngineImpl.isPrimaryClient) {
  25010. syncEngineImpl.sharedClientState.updateQueryState(queryView.targetId, (viewSnapshot === null || viewSnapshot === void 0 ? void 0 : viewSnapshot.fromCache) ? 'not-current' : 'current');
  25011. }
  25012. }
  25013. // Update views if there are actual changes.
  25014. if (!!viewSnapshot) {
  25015. newSnaps.push(viewSnapshot);
  25016. const docChanges = LocalViewChanges.fromSnapshot(queryView.targetId, viewSnapshot);
  25017. docChangesInAllViews.push(docChanges);
  25018. }
  25019. }));
  25020. });
  25021. await Promise.all(queriesProcessed);
  25022. syncEngineImpl.syncEngineListener.onWatchChange(newSnaps);
  25023. await localStoreNotifyLocalViewChanges(syncEngineImpl.localStore, docChangesInAllViews);
  25024. }
  25025. async function applyDocChanges(syncEngineImpl, queryView, changes, remoteEvent) {
  25026. let viewDocChanges = queryView.view.computeDocChanges(changes);
  25027. if (viewDocChanges.needsRefill) {
  25028. // The query has a limit and some docs were removed, so we need
  25029. // to re-run the query against the local store to make sure we
  25030. // didn't lose any good docs that had been past the limit.
  25031. viewDocChanges = await localStoreExecuteQuery(syncEngineImpl.localStore, queryView.query,
  25032. /* usePreviousResults= */ false).then(({ documents }) => {
  25033. return queryView.view.computeDocChanges(documents, viewDocChanges);
  25034. });
  25035. }
  25036. const targetChange = remoteEvent && remoteEvent.targetChanges.get(queryView.targetId);
  25037. const viewChange = queryView.view.applyChanges(viewDocChanges,
  25038. /* updateLimboDocuments= */ syncEngineImpl.isPrimaryClient, targetChange);
  25039. updateTrackedLimbos(syncEngineImpl, queryView.targetId, viewChange.limboChanges);
  25040. return viewChange.snapshot;
  25041. }
  25042. async function syncEngineHandleCredentialChange(syncEngine, user) {
  25043. const syncEngineImpl = debugCast(syncEngine);
  25044. const userChanged = !syncEngineImpl.currentUser.isEqual(user);
  25045. if (userChanged) {
  25046. logDebug(LOG_TAG$3, 'User change. New user:', user.toKey());
  25047. const result = await localStoreHandleUserChange(syncEngineImpl.localStore, user);
  25048. syncEngineImpl.currentUser = user;
  25049. // Fails tasks waiting for pending writes requested by previous user.
  25050. rejectOutstandingPendingWritesCallbacks(syncEngineImpl, "'waitForPendingWrites' promise is rejected due to a user change.");
  25051. // TODO(b/114226417): Consider calling this only in the primary tab.
  25052. syncEngineImpl.sharedClientState.handleUserChange(user, result.removedBatchIds, result.addedBatchIds);
  25053. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, result.affectedDocuments);
  25054. }
  25055. }
  25056. function syncEngineGetRemoteKeysForTarget(syncEngine, targetId) {
  25057. const syncEngineImpl = debugCast(syncEngine);
  25058. const limboResolution = syncEngineImpl.activeLimboResolutionsByTarget.get(targetId);
  25059. if (limboResolution && limboResolution.receivedDocument) {
  25060. return documentKeySet().add(limboResolution.key);
  25061. }
  25062. else {
  25063. let keySet = documentKeySet();
  25064. const queries = syncEngineImpl.queriesByTarget.get(targetId);
  25065. if (!queries) {
  25066. return keySet;
  25067. }
  25068. for (const query of queries) {
  25069. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  25070. keySet = keySet.unionWith(queryView.view.syncedDocuments);
  25071. }
  25072. return keySet;
  25073. }
  25074. }
  25075. /**
  25076. * Reconcile the list of synced documents in an existing view with those
  25077. * from persistence.
  25078. */
  25079. async function synchronizeViewAndComputeSnapshot(syncEngine, queryView) {
  25080. const syncEngineImpl = debugCast(syncEngine);
  25081. const queryResult = await localStoreExecuteQuery(syncEngineImpl.localStore, queryView.query,
  25082. /* usePreviousResults= */ true);
  25083. const viewSnapshot = queryView.view.synchronizeWithPersistedState(queryResult);
  25084. if (syncEngineImpl.isPrimaryClient) {
  25085. updateTrackedLimbos(syncEngineImpl, queryView.targetId, viewSnapshot.limboChanges);
  25086. }
  25087. return viewSnapshot;
  25088. }
  25089. /**
  25090. * Retrieves newly changed documents from remote document cache and raises
  25091. * snapshots if needed.
  25092. */
  25093. // PORTING NOTE: Multi-Tab only.
  25094. async function syncEngineSynchronizeWithChangedDocuments(syncEngine, collectionGroup) {
  25095. const syncEngineImpl = debugCast(syncEngine);
  25096. return localStoreGetNewDocumentChanges(syncEngineImpl.localStore, collectionGroup).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
  25097. }
  25098. /** Applies a mutation state to an existing batch. */
  25099. // PORTING NOTE: Multi-Tab only.
  25100. async function syncEngineApplyBatchState(syncEngine, batchId, batchState, error) {
  25101. const syncEngineImpl = debugCast(syncEngine);
  25102. const documents = await localStoreLookupMutationDocuments(syncEngineImpl.localStore, batchId);
  25103. if (documents === null) {
  25104. // A throttled tab may not have seen the mutation before it was completed
  25105. // and removed from the mutation queue, in which case we won't have cached
  25106. // the affected documents. In this case we can safely ignore the update
  25107. // since that means we didn't apply the mutation locally at all (if we
  25108. // had, we would have cached the affected documents), and so we will just
  25109. // see any resulting document changes via normal remote document updates
  25110. // as applicable.
  25111. logDebug(LOG_TAG$3, 'Cannot apply mutation batch with id: ' + batchId);
  25112. return;
  25113. }
  25114. if (batchState === 'pending') {
  25115. // If we are the primary client, we need to send this write to the
  25116. // backend. Secondary clients will ignore these writes since their remote
  25117. // connection is disabled.
  25118. await fillWritePipeline(syncEngineImpl.remoteStore);
  25119. }
  25120. else if (batchState === 'acknowledged' || batchState === 'rejected') {
  25121. // NOTE: Both these methods are no-ops for batches that originated from
  25122. // other clients.
  25123. processUserCallback(syncEngineImpl, batchId, error ? error : null);
  25124. triggerPendingWritesCallbacks(syncEngineImpl, batchId);
  25125. localStoreRemoveCachedMutationBatchMetadata(syncEngineImpl.localStore, batchId);
  25126. }
  25127. else {
  25128. fail();
  25129. }
  25130. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, documents);
  25131. }
  25132. /** Applies a query target change from a different tab. */
  25133. // PORTING NOTE: Multi-Tab only.
  25134. async function syncEngineApplyPrimaryState(syncEngine, isPrimary) {
  25135. const syncEngineImpl = debugCast(syncEngine);
  25136. ensureWatchCallbacks(syncEngineImpl);
  25137. syncEngineEnsureWriteCallbacks(syncEngineImpl);
  25138. if (isPrimary === true && syncEngineImpl._isPrimaryClient !== true) {
  25139. // Secondary tabs only maintain Views for their local listeners and the
  25140. // Views internal state may not be 100% populated (in particular
  25141. // secondary tabs don't track syncedDocuments, the set of documents the
  25142. // server considers to be in the target). So when a secondary becomes
  25143. // primary, we need to need to make sure that all views for all targets
  25144. // match the state on disk.
  25145. const activeTargets = syncEngineImpl.sharedClientState.getAllActiveQueryTargets();
  25146. const activeQueries = await synchronizeQueryViewsAndRaiseSnapshots(syncEngineImpl, activeTargets.toArray());
  25147. syncEngineImpl._isPrimaryClient = true;
  25148. await remoteStoreApplyPrimaryState(syncEngineImpl.remoteStore, true);
  25149. for (const targetData of activeQueries) {
  25150. remoteStoreListen(syncEngineImpl.remoteStore, targetData);
  25151. }
  25152. }
  25153. else if (isPrimary === false && syncEngineImpl._isPrimaryClient !== false) {
  25154. const activeTargets = [];
  25155. let p = Promise.resolve();
  25156. syncEngineImpl.queriesByTarget.forEach((_, targetId) => {
  25157. if (syncEngineImpl.sharedClientState.isLocalQueryTarget(targetId)) {
  25158. activeTargets.push(targetId);
  25159. }
  25160. else {
  25161. p = p.then(() => {
  25162. removeAndCleanupTarget(syncEngineImpl, targetId);
  25163. return localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  25164. /*keepPersistedTargetData=*/ true);
  25165. });
  25166. }
  25167. remoteStoreUnlisten(syncEngineImpl.remoteStore, targetId);
  25168. });
  25169. await p;
  25170. await synchronizeQueryViewsAndRaiseSnapshots(syncEngineImpl, activeTargets);
  25171. resetLimboDocuments(syncEngineImpl);
  25172. syncEngineImpl._isPrimaryClient = false;
  25173. await remoteStoreApplyPrimaryState(syncEngineImpl.remoteStore, false);
  25174. }
  25175. }
  25176. // PORTING NOTE: Multi-Tab only.
  25177. function resetLimboDocuments(syncEngine) {
  25178. const syncEngineImpl = debugCast(syncEngine);
  25179. syncEngineImpl.activeLimboResolutionsByTarget.forEach((_, targetId) => {
  25180. remoteStoreUnlisten(syncEngineImpl.remoteStore, targetId);
  25181. });
  25182. syncEngineImpl.limboDocumentRefs.removeAllReferences();
  25183. syncEngineImpl.activeLimboResolutionsByTarget = new Map();
  25184. syncEngineImpl.activeLimboTargetsByKey = new SortedMap(DocumentKey.comparator);
  25185. }
  25186. /**
  25187. * Reconcile the query views of the provided query targets with the state from
  25188. * persistence. Raises snapshots for any changes that affect the local
  25189. * client and returns the updated state of all target's query data.
  25190. *
  25191. * @param syncEngine - The sync engine implementation
  25192. * @param targets - the list of targets with views that need to be recomputed
  25193. * @param transitionToPrimary - `true` iff the tab transitions from a secondary
  25194. * tab to a primary tab
  25195. */
  25196. // PORTING NOTE: Multi-Tab only.
  25197. async function synchronizeQueryViewsAndRaiseSnapshots(syncEngine, targets, transitionToPrimary) {
  25198. const syncEngineImpl = debugCast(syncEngine);
  25199. const activeQueries = [];
  25200. const newViewSnapshots = [];
  25201. for (const targetId of targets) {
  25202. let targetData;
  25203. const queries = syncEngineImpl.queriesByTarget.get(targetId);
  25204. if (queries && queries.length !== 0) {
  25205. // For queries that have a local View, we fetch their current state
  25206. // from LocalStore (as the resume token and the snapshot version
  25207. // might have changed) and reconcile their views with the persisted
  25208. // state (the list of syncedDocuments may have gotten out of sync).
  25209. targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, queryToTarget(queries[0]));
  25210. for (const query of queries) {
  25211. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  25212. const viewChange = await synchronizeViewAndComputeSnapshot(syncEngineImpl, queryView);
  25213. if (viewChange.snapshot) {
  25214. newViewSnapshots.push(viewChange.snapshot);
  25215. }
  25216. }
  25217. }
  25218. else {
  25219. // For queries that never executed on this client, we need to
  25220. // allocate the target in LocalStore and initialize a new View.
  25221. const target = await localStoreGetCachedTarget(syncEngineImpl.localStore, targetId);
  25222. targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, target);
  25223. await initializeViewAndComputeSnapshot(syncEngineImpl, synthesizeTargetToQuery(target), targetId,
  25224. /*current=*/ false, targetData.resumeToken);
  25225. }
  25226. activeQueries.push(targetData);
  25227. }
  25228. syncEngineImpl.syncEngineListener.onWatchChange(newViewSnapshots);
  25229. return activeQueries;
  25230. }
  25231. /**
  25232. * Creates a `Query` object from the specified `Target`. There is no way to
  25233. * obtain the original `Query`, so we synthesize a `Query` from the `Target`
  25234. * object.
  25235. *
  25236. * The synthesized result might be different from the original `Query`, but
  25237. * since the synthesized `Query` should return the same results as the
  25238. * original one (only the presentation of results might differ), the potential
  25239. * difference will not cause issues.
  25240. */
  25241. // PORTING NOTE: Multi-Tab only.
  25242. function synthesizeTargetToQuery(target) {
  25243. return newQuery(target.path, target.collectionGroup, target.orderBy, target.filters, target.limit, "F" /* LimitType.First */, target.startAt, target.endAt);
  25244. }
  25245. /** Returns the IDs of the clients that are currently active. */
  25246. // PORTING NOTE: Multi-Tab only.
  25247. function syncEngineGetActiveClients(syncEngine) {
  25248. const syncEngineImpl = debugCast(syncEngine);
  25249. return localStoreGetActiveClients(syncEngineImpl.localStore);
  25250. }
  25251. /** Applies a query target change from a different tab. */
  25252. // PORTING NOTE: Multi-Tab only.
  25253. async function syncEngineApplyTargetState(syncEngine, targetId, state, error) {
  25254. const syncEngineImpl = debugCast(syncEngine);
  25255. if (syncEngineImpl._isPrimaryClient) {
  25256. // If we receive a target state notification via WebStorage, we are
  25257. // either already secondary or another tab has taken the primary lease.
  25258. logDebug(LOG_TAG$3, 'Ignoring unexpected query state notification.');
  25259. return;
  25260. }
  25261. const query = syncEngineImpl.queriesByTarget.get(targetId);
  25262. if (query && query.length > 0) {
  25263. switch (state) {
  25264. case 'current':
  25265. case 'not-current': {
  25266. const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore, queryCollectionGroup(query[0]));
  25267. const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(targetId, state === 'current', ByteString.EMPTY_BYTE_STRING);
  25268. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, synthesizedRemoteEvent);
  25269. break;
  25270. }
  25271. case 'rejected': {
  25272. await localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  25273. /* keepPersistedTargetData */ true);
  25274. removeAndCleanupTarget(syncEngineImpl, targetId, error);
  25275. break;
  25276. }
  25277. default:
  25278. fail();
  25279. }
  25280. }
  25281. }
  25282. /** Adds or removes Watch targets for queries from different tabs. */
  25283. async function syncEngineApplyActiveTargetsChange(syncEngine, added, removed) {
  25284. const syncEngineImpl = ensureWatchCallbacks(syncEngine);
  25285. if (!syncEngineImpl._isPrimaryClient) {
  25286. return;
  25287. }
  25288. for (const targetId of added) {
  25289. if (syncEngineImpl.queriesByTarget.has(targetId)) {
  25290. // A target might have been added in a previous attempt
  25291. logDebug(LOG_TAG$3, 'Adding an already active target ' + targetId);
  25292. continue;
  25293. }
  25294. const target = await localStoreGetCachedTarget(syncEngineImpl.localStore, targetId);
  25295. const targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, target);
  25296. await initializeViewAndComputeSnapshot(syncEngineImpl, synthesizeTargetToQuery(target), targetData.targetId,
  25297. /*current=*/ false, targetData.resumeToken);
  25298. remoteStoreListen(syncEngineImpl.remoteStore, targetData);
  25299. }
  25300. for (const targetId of removed) {
  25301. // Check that the target is still active since the target might have been
  25302. // removed if it has been rejected by the backend.
  25303. if (!syncEngineImpl.queriesByTarget.has(targetId)) {
  25304. continue;
  25305. }
  25306. // Release queries that are still active.
  25307. await localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  25308. /* keepPersistedTargetData */ false)
  25309. .then(() => {
  25310. remoteStoreUnlisten(syncEngineImpl.remoteStore, targetId);
  25311. removeAndCleanupTarget(syncEngineImpl, targetId);
  25312. })
  25313. .catch(ignoreIfPrimaryLeaseLoss);
  25314. }
  25315. }
  25316. function ensureWatchCallbacks(syncEngine) {
  25317. const syncEngineImpl = debugCast(syncEngine);
  25318. syncEngineImpl.remoteStore.remoteSyncer.applyRemoteEvent =
  25319. syncEngineApplyRemoteEvent.bind(null, syncEngineImpl);
  25320. syncEngineImpl.remoteStore.remoteSyncer.getRemoteKeysForTarget =
  25321. syncEngineGetRemoteKeysForTarget.bind(null, syncEngineImpl);
  25322. syncEngineImpl.remoteStore.remoteSyncer.rejectListen =
  25323. syncEngineRejectListen.bind(null, syncEngineImpl);
  25324. syncEngineImpl.syncEngineListener.onWatchChange =
  25325. eventManagerOnWatchChange.bind(null, syncEngineImpl.eventManager);
  25326. syncEngineImpl.syncEngineListener.onWatchError =
  25327. eventManagerOnWatchError.bind(null, syncEngineImpl.eventManager);
  25328. return syncEngineImpl;
  25329. }
  25330. function syncEngineEnsureWriteCallbacks(syncEngine) {
  25331. const syncEngineImpl = debugCast(syncEngine);
  25332. syncEngineImpl.remoteStore.remoteSyncer.applySuccessfulWrite =
  25333. syncEngineApplySuccessfulWrite.bind(null, syncEngineImpl);
  25334. syncEngineImpl.remoteStore.remoteSyncer.rejectFailedWrite =
  25335. syncEngineRejectFailedWrite.bind(null, syncEngineImpl);
  25336. return syncEngineImpl;
  25337. }
  25338. /**
  25339. * Loads a Firestore bundle into the SDK. The returned promise resolves when
  25340. * the bundle finished loading.
  25341. *
  25342. * @param syncEngine - SyncEngine to use.
  25343. * @param bundleReader - Bundle to load into the SDK.
  25344. * @param task - LoadBundleTask used to update the loading progress to public API.
  25345. */
  25346. function syncEngineLoadBundle(syncEngine, bundleReader, task) {
  25347. const syncEngineImpl = debugCast(syncEngine);
  25348. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  25349. loadBundleImpl(syncEngineImpl, bundleReader, task).then(collectionGroups => {
  25350. syncEngineImpl.sharedClientState.notifyBundleLoaded(collectionGroups);
  25351. });
  25352. }
  25353. /** Loads a bundle and returns the list of affected collection groups. */
  25354. async function loadBundleImpl(syncEngine, reader, task) {
  25355. try {
  25356. const metadata = await reader.getMetadata();
  25357. const skip = await localStoreHasNewerBundle(syncEngine.localStore, metadata);
  25358. if (skip) {
  25359. await reader.close();
  25360. task._completeWith(bundleSuccessProgress(metadata));
  25361. return Promise.resolve(new Set());
  25362. }
  25363. task._updateProgress(bundleInitialProgress(metadata));
  25364. const loader = new BundleLoader(metadata, syncEngine.localStore, reader.serializer);
  25365. let element = await reader.nextElement();
  25366. while (element) {
  25367. ;
  25368. const progress = await loader.addSizedElement(element);
  25369. if (progress) {
  25370. task._updateProgress(progress);
  25371. }
  25372. element = await reader.nextElement();
  25373. }
  25374. const result = await loader.complete();
  25375. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, result.changedDocs,
  25376. /* remoteEvent */ undefined);
  25377. // Save metadata, so loading the same bundle will skip.
  25378. await localStoreSaveBundle(syncEngine.localStore, metadata);
  25379. task._completeWith(result.progress);
  25380. return Promise.resolve(result.changedCollectionGroups);
  25381. }
  25382. catch (e) {
  25383. logWarn(LOG_TAG$3, `Loading bundle failed with ${e}`);
  25384. task._failWith(e);
  25385. return Promise.resolve(new Set());
  25386. }
  25387. }
  25388. /**
  25389. * @license
  25390. * Copyright 2020 Google LLC
  25391. *
  25392. * Licensed under the Apache License, Version 2.0 (the "License");
  25393. * you may not use this file except in compliance with the License.
  25394. * You may obtain a copy of the License at
  25395. *
  25396. * http://www.apache.org/licenses/LICENSE-2.0
  25397. *
  25398. * Unless required by applicable law or agreed to in writing, software
  25399. * distributed under the License is distributed on an "AS IS" BASIS,
  25400. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25401. * See the License for the specific language governing permissions and
  25402. * limitations under the License.
  25403. */
  25404. /**
  25405. * Provides all components needed for Firestore with in-memory persistence.
  25406. * Uses EagerGC garbage collection.
  25407. */
  25408. class MemoryOfflineComponentProvider {
  25409. constructor() {
  25410. this.synchronizeTabs = false;
  25411. }
  25412. async initialize(cfg) {
  25413. this.serializer = newSerializer(cfg.databaseInfo.databaseId);
  25414. this.sharedClientState = this.createSharedClientState(cfg);
  25415. this.persistence = this.createPersistence(cfg);
  25416. await this.persistence.start();
  25417. this.localStore = this.createLocalStore(cfg);
  25418. this.gcScheduler = this.createGarbageCollectionScheduler(cfg, this.localStore);
  25419. this.indexBackfillerScheduler = this.createIndexBackfillerScheduler(cfg, this.localStore);
  25420. }
  25421. createGarbageCollectionScheduler(cfg, localStore) {
  25422. return null;
  25423. }
  25424. createIndexBackfillerScheduler(cfg, localStore) {
  25425. return null;
  25426. }
  25427. createLocalStore(cfg) {
  25428. return newLocalStore(this.persistence, new QueryEngine(), cfg.initialUser, this.serializer);
  25429. }
  25430. createPersistence(cfg) {
  25431. return new MemoryPersistence(MemoryEagerDelegate.factory, this.serializer);
  25432. }
  25433. createSharedClientState(cfg) {
  25434. return new MemorySharedClientState();
  25435. }
  25436. async terminate() {
  25437. if (this.gcScheduler) {
  25438. this.gcScheduler.stop();
  25439. }
  25440. await this.sharedClientState.shutdown();
  25441. await this.persistence.shutdown();
  25442. }
  25443. }
  25444. class LruGcMemoryOfflineComponentProvider extends MemoryOfflineComponentProvider {
  25445. constructor(cacheSizeBytes) {
  25446. super();
  25447. this.cacheSizeBytes = cacheSizeBytes;
  25448. }
  25449. createGarbageCollectionScheduler(cfg, localStore) {
  25450. hardAssert(this.persistence.referenceDelegate instanceof MemoryLruDelegate);
  25451. const garbageCollector = this.persistence.referenceDelegate.garbageCollector;
  25452. return new LruScheduler(garbageCollector, cfg.asyncQueue, localStore);
  25453. }
  25454. createPersistence(cfg) {
  25455. const lruParams = this.cacheSizeBytes !== undefined
  25456. ? LruParams.withCacheSize(this.cacheSizeBytes)
  25457. : LruParams.DEFAULT;
  25458. return new MemoryPersistence(p => MemoryLruDelegate.factory(p, lruParams), this.serializer);
  25459. }
  25460. }
  25461. /**
  25462. * Provides all components needed for Firestore with IndexedDB persistence.
  25463. */
  25464. class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
  25465. constructor(onlineComponentProvider, cacheSizeBytes, forceOwnership) {
  25466. super();
  25467. this.onlineComponentProvider = onlineComponentProvider;
  25468. this.cacheSizeBytes = cacheSizeBytes;
  25469. this.forceOwnership = forceOwnership;
  25470. this.synchronizeTabs = false;
  25471. }
  25472. async initialize(cfg) {
  25473. await super.initialize(cfg);
  25474. await this.onlineComponentProvider.initialize(this, cfg);
  25475. // Enqueue writes from a previous session
  25476. await syncEngineEnsureWriteCallbacks(this.onlineComponentProvider.syncEngine);
  25477. await fillWritePipeline(this.onlineComponentProvider.remoteStore);
  25478. // NOTE: This will immediately call the listener, so we make sure to
  25479. // set it after localStore / remoteStore are started.
  25480. await this.persistence.setPrimaryStateListener(() => {
  25481. if (this.gcScheduler && !this.gcScheduler.started) {
  25482. this.gcScheduler.start();
  25483. }
  25484. if (this.indexBackfillerScheduler &&
  25485. !this.indexBackfillerScheduler.started) {
  25486. this.indexBackfillerScheduler.start();
  25487. }
  25488. return Promise.resolve();
  25489. });
  25490. }
  25491. createLocalStore(cfg) {
  25492. return newLocalStore(this.persistence, new QueryEngine(), cfg.initialUser, this.serializer);
  25493. }
  25494. createGarbageCollectionScheduler(cfg, localStore) {
  25495. const garbageCollector = this.persistence.referenceDelegate.garbageCollector;
  25496. return new LruScheduler(garbageCollector, cfg.asyncQueue, localStore);
  25497. }
  25498. createIndexBackfillerScheduler(cfg, localStore) {
  25499. const indexBackfiller = new IndexBackfiller(localStore, this.persistence);
  25500. return new IndexBackfillerScheduler(cfg.asyncQueue, indexBackfiller);
  25501. }
  25502. createPersistence(cfg) {
  25503. const persistenceKey = indexedDbStoragePrefix(cfg.databaseInfo.databaseId, cfg.databaseInfo.persistenceKey);
  25504. const lruParams = this.cacheSizeBytes !== undefined
  25505. ? LruParams.withCacheSize(this.cacheSizeBytes)
  25506. : LruParams.DEFAULT;
  25507. return new IndexedDbPersistence(this.synchronizeTabs, persistenceKey, cfg.clientId, lruParams, cfg.asyncQueue, getWindow(), getDocument(), this.serializer, this.sharedClientState, !!this.forceOwnership);
  25508. }
  25509. createSharedClientState(cfg) {
  25510. return new MemorySharedClientState();
  25511. }
  25512. }
  25513. /**
  25514. * Provides all components needed for Firestore with multi-tab IndexedDB
  25515. * persistence.
  25516. *
  25517. * In the legacy client, this provider is used to provide both multi-tab and
  25518. * non-multi-tab persistence since we cannot tell at build time whether
  25519. * `synchronizeTabs` will be enabled.
  25520. */
  25521. class MultiTabOfflineComponentProvider extends IndexedDbOfflineComponentProvider {
  25522. constructor(onlineComponentProvider, cacheSizeBytes) {
  25523. super(onlineComponentProvider, cacheSizeBytes, /* forceOwnership= */ false);
  25524. this.onlineComponentProvider = onlineComponentProvider;
  25525. this.cacheSizeBytes = cacheSizeBytes;
  25526. this.synchronizeTabs = true;
  25527. }
  25528. async initialize(cfg) {
  25529. await super.initialize(cfg);
  25530. const syncEngine = this.onlineComponentProvider.syncEngine;
  25531. if (this.sharedClientState instanceof WebStorageSharedClientState) {
  25532. this.sharedClientState.syncEngine = {
  25533. applyBatchState: syncEngineApplyBatchState.bind(null, syncEngine),
  25534. applyTargetState: syncEngineApplyTargetState.bind(null, syncEngine),
  25535. applyActiveTargetsChange: syncEngineApplyActiveTargetsChange.bind(null, syncEngine),
  25536. getActiveClients: syncEngineGetActiveClients.bind(null, syncEngine),
  25537. synchronizeWithChangedDocuments: syncEngineSynchronizeWithChangedDocuments.bind(null, syncEngine)
  25538. };
  25539. await this.sharedClientState.start();
  25540. }
  25541. // NOTE: This will immediately call the listener, so we make sure to
  25542. // set it after localStore / remoteStore are started.
  25543. await this.persistence.setPrimaryStateListener(async (isPrimary) => {
  25544. await syncEngineApplyPrimaryState(this.onlineComponentProvider.syncEngine, isPrimary);
  25545. if (this.gcScheduler) {
  25546. if (isPrimary && !this.gcScheduler.started) {
  25547. this.gcScheduler.start();
  25548. }
  25549. else if (!isPrimary) {
  25550. this.gcScheduler.stop();
  25551. }
  25552. }
  25553. if (this.indexBackfillerScheduler) {
  25554. if (isPrimary && !this.indexBackfillerScheduler.started) {
  25555. this.indexBackfillerScheduler.start();
  25556. }
  25557. else if (!isPrimary) {
  25558. this.indexBackfillerScheduler.stop();
  25559. }
  25560. }
  25561. });
  25562. }
  25563. createSharedClientState(cfg) {
  25564. const window = getWindow();
  25565. if (!WebStorageSharedClientState.isAvailable(window)) {
  25566. throw new FirestoreError(Code.UNIMPLEMENTED, 'IndexedDB persistence is only available on platforms that support LocalStorage.');
  25567. }
  25568. const persistenceKey = indexedDbStoragePrefix(cfg.databaseInfo.databaseId, cfg.databaseInfo.persistenceKey);
  25569. return new WebStorageSharedClientState(window, cfg.asyncQueue, persistenceKey, cfg.clientId, cfg.initialUser);
  25570. }
  25571. }
  25572. /**
  25573. * Initializes and wires the components that are needed to interface with the
  25574. * network.
  25575. */
  25576. class OnlineComponentProvider {
  25577. async initialize(offlineComponentProvider, cfg) {
  25578. if (this.localStore) {
  25579. // OnlineComponentProvider may get initialized multiple times if
  25580. // multi-tab persistence is used.
  25581. return;
  25582. }
  25583. this.localStore = offlineComponentProvider.localStore;
  25584. this.sharedClientState = offlineComponentProvider.sharedClientState;
  25585. this.datastore = this.createDatastore(cfg);
  25586. this.remoteStore = this.createRemoteStore(cfg);
  25587. this.eventManager = this.createEventManager(cfg);
  25588. this.syncEngine = this.createSyncEngine(cfg,
  25589. /* startAsPrimary=*/ !offlineComponentProvider.synchronizeTabs);
  25590. this.sharedClientState.onlineStateHandler = onlineState => syncEngineApplyOnlineStateChange(this.syncEngine, onlineState, 1 /* OnlineStateSource.SharedClientState */);
  25591. this.remoteStore.remoteSyncer.handleCredentialChange =
  25592. syncEngineHandleCredentialChange.bind(null, this.syncEngine);
  25593. await remoteStoreApplyPrimaryState(this.remoteStore, this.syncEngine.isPrimaryClient);
  25594. }
  25595. createEventManager(cfg) {
  25596. return newEventManager();
  25597. }
  25598. createDatastore(cfg) {
  25599. const serializer = newSerializer(cfg.databaseInfo.databaseId);
  25600. const connection = newConnection(cfg.databaseInfo);
  25601. return newDatastore(cfg.authCredentials, cfg.appCheckCredentials, connection, serializer);
  25602. }
  25603. createRemoteStore(cfg) {
  25604. return newRemoteStore(this.localStore, this.datastore, cfg.asyncQueue, onlineState => syncEngineApplyOnlineStateChange(this.syncEngine, onlineState, 0 /* OnlineStateSource.RemoteStore */), newConnectivityMonitor());
  25605. }
  25606. createSyncEngine(cfg, startAsPrimary) {
  25607. return newSyncEngine(this.localStore, this.remoteStore, this.eventManager, this.sharedClientState, cfg.initialUser, cfg.maxConcurrentLimboResolutions, startAsPrimary);
  25608. }
  25609. terminate() {
  25610. return remoteStoreShutdown(this.remoteStore);
  25611. }
  25612. }
  25613. /**
  25614. * @license
  25615. * Copyright 2020 Google LLC
  25616. *
  25617. * Licensed under the Apache License, Version 2.0 (the "License");
  25618. * you may not use this file except in compliance with the License.
  25619. * You may obtain a copy of the License at
  25620. *
  25621. * http://www.apache.org/licenses/LICENSE-2.0
  25622. *
  25623. * Unless required by applicable law or agreed to in writing, software
  25624. * distributed under the License is distributed on an "AS IS" BASIS,
  25625. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25626. * See the License for the specific language governing permissions and
  25627. * limitations under the License.
  25628. */
  25629. /**
  25630. * How many bytes to read each time when `ReadableStreamReader.read()` is
  25631. * called. Only applicable for byte streams that we control (e.g. those backed
  25632. * by an UInt8Array).
  25633. */
  25634. const DEFAULT_BYTES_PER_READ = 10240;
  25635. /**
  25636. * Builds a `ByteStreamReader` from a UInt8Array.
  25637. * @param source - The data source to use.
  25638. * @param bytesPerRead - How many bytes each `read()` from the returned reader
  25639. * will read.
  25640. */
  25641. function toByteStreamReaderHelper(source, bytesPerRead = DEFAULT_BYTES_PER_READ) {
  25642. let readFrom = 0;
  25643. // The TypeScript definition for ReadableStreamReader changed. We use
  25644. // `any` here to allow this code to compile with different versions.
  25645. // See https://github.com/microsoft/TypeScript/issues/42970
  25646. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25647. const reader = {
  25648. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25649. async read() {
  25650. if (readFrom < source.byteLength) {
  25651. const result = {
  25652. value: source.slice(readFrom, readFrom + bytesPerRead),
  25653. done: false
  25654. };
  25655. readFrom += bytesPerRead;
  25656. return result;
  25657. }
  25658. return { done: true };
  25659. },
  25660. async cancel() { },
  25661. releaseLock() { },
  25662. closed: Promise.resolve()
  25663. };
  25664. return reader;
  25665. }
  25666. /**
  25667. * @license
  25668. * Copyright 2017 Google LLC
  25669. *
  25670. * Licensed under the Apache License, Version 2.0 (the "License");
  25671. * you may not use this file except in compliance with the License.
  25672. * You may obtain a copy of the License at
  25673. *
  25674. * http://www.apache.org/licenses/LICENSE-2.0
  25675. *
  25676. * Unless required by applicable law or agreed to in writing, software
  25677. * distributed under the License is distributed on an "AS IS" BASIS,
  25678. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25679. * See the License for the specific language governing permissions and
  25680. * limitations under the License.
  25681. */
  25682. function validateNonEmptyArgument(functionName, argumentName, argument) {
  25683. if (!argument) {
  25684. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() cannot be called with an empty ${argumentName}.`);
  25685. }
  25686. }
  25687. /**
  25688. * Validates that two boolean options are not set at the same time.
  25689. * @internal
  25690. */
  25691. function validateIsNotUsedTogether(optionName1, argument1, optionName2, argument2) {
  25692. if (argument1 === true && argument2 === true) {
  25693. throw new FirestoreError(Code.INVALID_ARGUMENT, `${optionName1} and ${optionName2} cannot be used together.`);
  25694. }
  25695. }
  25696. /**
  25697. * Validates that `path` refers to a document (indicated by the fact it contains
  25698. * an even numbers of segments).
  25699. */
  25700. function validateDocumentPath(path) {
  25701. if (!DocumentKey.isDocumentKey(path)) {
  25702. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid document reference. Document references must have an even number of segments, but ${path} has ${path.length}.`);
  25703. }
  25704. }
  25705. /**
  25706. * Validates that `path` refers to a collection (indicated by the fact it
  25707. * contains an odd numbers of segments).
  25708. */
  25709. function validateCollectionPath(path) {
  25710. if (DocumentKey.isDocumentKey(path)) {
  25711. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid collection reference. Collection references must have an odd number of segments, but ${path} has ${path.length}.`);
  25712. }
  25713. }
  25714. /**
  25715. * Returns true if it's a non-null object without a custom prototype
  25716. * (i.e. excludes Array, Date, etc.).
  25717. */
  25718. function isPlainObject(input) {
  25719. return (typeof input === 'object' &&
  25720. input !== null &&
  25721. (Object.getPrototypeOf(input) === Object.prototype ||
  25722. Object.getPrototypeOf(input) === null));
  25723. }
  25724. /** Returns a string describing the type / value of the provided input. */
  25725. function valueDescription(input) {
  25726. if (input === undefined) {
  25727. return 'undefined';
  25728. }
  25729. else if (input === null) {
  25730. return 'null';
  25731. }
  25732. else if (typeof input === 'string') {
  25733. if (input.length > 20) {
  25734. input = `${input.substring(0, 20)}...`;
  25735. }
  25736. return JSON.stringify(input);
  25737. }
  25738. else if (typeof input === 'number' || typeof input === 'boolean') {
  25739. return '' + input;
  25740. }
  25741. else if (typeof input === 'object') {
  25742. if (input instanceof Array) {
  25743. return 'an array';
  25744. }
  25745. else {
  25746. const customObjectName = tryGetCustomObjectType(input);
  25747. if (customObjectName) {
  25748. return `a custom ${customObjectName} object`;
  25749. }
  25750. else {
  25751. return 'an object';
  25752. }
  25753. }
  25754. }
  25755. else if (typeof input === 'function') {
  25756. return 'a function';
  25757. }
  25758. else {
  25759. return fail();
  25760. }
  25761. }
  25762. /** try to get the constructor name for an object. */
  25763. function tryGetCustomObjectType(input) {
  25764. if (input.constructor) {
  25765. return input.constructor.name;
  25766. }
  25767. return null;
  25768. }
  25769. /**
  25770. * Casts `obj` to `T`, optionally unwrapping Compat types to expose the
  25771. * underlying instance. Throws if `obj` is not an instance of `T`.
  25772. *
  25773. * This cast is used in the Lite and Full SDK to verify instance types for
  25774. * arguments passed to the public API.
  25775. * @internal
  25776. */
  25777. function cast(obj,
  25778. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25779. constructor) {
  25780. if ('_delegate' in obj) {
  25781. // Unwrap Compat types
  25782. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25783. obj = obj._delegate;
  25784. }
  25785. if (!(obj instanceof constructor)) {
  25786. if (constructor.name === obj.constructor.name) {
  25787. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Type does not match the expected instance. Did you pass a ' +
  25788. `reference from a different Firestore SDK?`);
  25789. }
  25790. else {
  25791. const description = valueDescription(obj);
  25792. throw new FirestoreError(Code.INVALID_ARGUMENT, `Expected type '${constructor.name}', but it was: ${description}`);
  25793. }
  25794. }
  25795. return obj;
  25796. }
  25797. function validatePositiveNumber(functionName, n) {
  25798. if (n <= 0) {
  25799. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires a positive number, but it was: ${n}.`);
  25800. }
  25801. }
  25802. /**
  25803. * @license
  25804. * Copyright 2020 Google LLC
  25805. *
  25806. * Licensed under the Apache License, Version 2.0 (the "License");
  25807. * you may not use this file except in compliance with the License.
  25808. * You may obtain a copy of the License at
  25809. *
  25810. * http://www.apache.org/licenses/LICENSE-2.0
  25811. *
  25812. * Unless required by applicable law or agreed to in writing, software
  25813. * distributed under the License is distributed on an "AS IS" BASIS,
  25814. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25815. * See the License for the specific language governing permissions and
  25816. * limitations under the License.
  25817. */
  25818. /**
  25819. * On Node, only supported data source is a `Uint8Array` for now.
  25820. */
  25821. function toByteStreamReader(source, bytesPerRead) {
  25822. if (!(source instanceof Uint8Array)) {
  25823. throw new FirestoreError(Code.INVALID_ARGUMENT, `NodePlatform.toByteStreamReader expects source to be Uint8Array, got ${valueDescription(source)}`);
  25824. }
  25825. return toByteStreamReaderHelper(source, bytesPerRead);
  25826. }
  25827. /**
  25828. * @license
  25829. * Copyright 2017 Google LLC
  25830. *
  25831. * Licensed under the Apache License, Version 2.0 (the "License");
  25832. * you may not use this file except in compliance with the License.
  25833. * You may obtain a copy of the License at
  25834. *
  25835. * http://www.apache.org/licenses/LICENSE-2.0
  25836. *
  25837. * Unless required by applicable law or agreed to in writing, software
  25838. * distributed under the License is distributed on an "AS IS" BASIS,
  25839. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25840. * See the License for the specific language governing permissions and
  25841. * limitations under the License.
  25842. */
  25843. /*
  25844. * A wrapper implementation of Observer<T> that will dispatch events
  25845. * asynchronously. To allow immediate silencing, a mute call is added which
  25846. * causes events scheduled to no longer be raised.
  25847. */
  25848. class AsyncObserver {
  25849. constructor(observer) {
  25850. this.observer = observer;
  25851. /**
  25852. * When set to true, will not raise future events. Necessary to deal with
  25853. * async detachment of listener.
  25854. */
  25855. this.muted = false;
  25856. }
  25857. next(value) {
  25858. if (this.observer.next) {
  25859. this.scheduleEvent(this.observer.next, value);
  25860. }
  25861. }
  25862. error(error) {
  25863. if (this.observer.error) {
  25864. this.scheduleEvent(this.observer.error, error);
  25865. }
  25866. else {
  25867. logError('Uncaught Error in snapshot listener:', error.toString());
  25868. }
  25869. }
  25870. mute() {
  25871. this.muted = true;
  25872. }
  25873. scheduleEvent(eventHandler, event) {
  25874. if (!this.muted) {
  25875. setTimeout(() => {
  25876. if (!this.muted) {
  25877. eventHandler(event);
  25878. }
  25879. }, 0);
  25880. }
  25881. }
  25882. }
  25883. /**
  25884. * @license
  25885. * Copyright 2020 Google LLC
  25886. *
  25887. * Licensed under the Apache License, Version 2.0 (the "License");
  25888. * you may not use this file except in compliance with the License.
  25889. * You may obtain a copy of the License at
  25890. *
  25891. * http://www.apache.org/licenses/LICENSE-2.0
  25892. *
  25893. * Unless required by applicable law or agreed to in writing, software
  25894. * distributed under the License is distributed on an "AS IS" BASIS,
  25895. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25896. * See the License for the specific language governing permissions and
  25897. * limitations under the License.
  25898. */
  25899. /**
  25900. * A complete element in the bundle stream, together with the byte length it
  25901. * occupies in the stream.
  25902. */
  25903. class SizedBundleElement {
  25904. constructor(payload,
  25905. // How many bytes this element takes to store in the bundle.
  25906. byteLength) {
  25907. this.payload = payload;
  25908. this.byteLength = byteLength;
  25909. }
  25910. isBundleMetadata() {
  25911. return 'metadata' in this.payload;
  25912. }
  25913. }
  25914. /**
  25915. * @license
  25916. * Copyright 2020 Google LLC
  25917. *
  25918. * Licensed under the Apache License, Version 2.0 (the "License");
  25919. * you may not use this file except in compliance with the License.
  25920. * You may obtain a copy of the License at
  25921. *
  25922. * http://www.apache.org/licenses/LICENSE-2.0
  25923. *
  25924. * Unless required by applicable law or agreed to in writing, software
  25925. * distributed under the License is distributed on an "AS IS" BASIS,
  25926. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25927. * See the License for the specific language governing permissions and
  25928. * limitations under the License.
  25929. */
  25930. /**
  25931. * A class representing a bundle.
  25932. *
  25933. * Takes a bundle stream or buffer, and presents abstractions to read bundled
  25934. * elements out of the underlying content.
  25935. */
  25936. class BundleReaderImpl {
  25937. constructor(
  25938. /** The reader to read from underlying binary bundle data source. */
  25939. reader, serializer) {
  25940. this.reader = reader;
  25941. this.serializer = serializer;
  25942. /** Cached bundle metadata. */
  25943. this.metadata = new Deferred();
  25944. /**
  25945. * Internal buffer to hold bundle content, accumulating incomplete element
  25946. * content.
  25947. */
  25948. this.buffer = new Uint8Array();
  25949. this.textDecoder = newTextDecoder();
  25950. // Read the metadata (which is the first element).
  25951. this.nextElementImpl().then(element => {
  25952. if (element && element.isBundleMetadata()) {
  25953. this.metadata.resolve(element.payload.metadata);
  25954. }
  25955. else {
  25956. this.metadata.reject(new Error(`The first element of the bundle is not a metadata, it is
  25957. ${JSON.stringify(element === null || element === void 0 ? void 0 : element.payload)}`));
  25958. }
  25959. }, error => this.metadata.reject(error));
  25960. }
  25961. close() {
  25962. return this.reader.cancel();
  25963. }
  25964. async getMetadata() {
  25965. return this.metadata.promise;
  25966. }
  25967. async nextElement() {
  25968. // Makes sure metadata is read before proceeding.
  25969. await this.getMetadata();
  25970. return this.nextElementImpl();
  25971. }
  25972. /**
  25973. * Reads from the head of internal buffer, and pulling more data from
  25974. * underlying stream if a complete element cannot be found, until an
  25975. * element(including the prefixed length and the JSON string) is found.
  25976. *
  25977. * Once a complete element is read, it is dropped from internal buffer.
  25978. *
  25979. * Returns either the bundled element, or null if we have reached the end of
  25980. * the stream.
  25981. */
  25982. async nextElementImpl() {
  25983. const lengthBuffer = await this.readLength();
  25984. if (lengthBuffer === null) {
  25985. return null;
  25986. }
  25987. const lengthString = this.textDecoder.decode(lengthBuffer);
  25988. const length = Number(lengthString);
  25989. if (isNaN(length)) {
  25990. this.raiseError(`length string (${lengthString}) is not valid number`);
  25991. }
  25992. const jsonString = await this.readJsonString(length);
  25993. return new SizedBundleElement(JSON.parse(jsonString), lengthBuffer.length + length);
  25994. }
  25995. /** First index of '{' from the underlying buffer. */
  25996. indexOfOpenBracket() {
  25997. return this.buffer.findIndex(v => v === '{'.charCodeAt(0));
  25998. }
  25999. /**
  26000. * Reads from the beginning of the internal buffer, until the first '{', and
  26001. * return the content.
  26002. *
  26003. * If reached end of the stream, returns a null.
  26004. */
  26005. async readLength() {
  26006. while (this.indexOfOpenBracket() < 0) {
  26007. const done = await this.pullMoreDataToBuffer();
  26008. if (done) {
  26009. break;
  26010. }
  26011. }
  26012. // Broke out of the loop because underlying stream is closed, and there
  26013. // happens to be no more data to process.
  26014. if (this.buffer.length === 0) {
  26015. return null;
  26016. }
  26017. const position = this.indexOfOpenBracket();
  26018. // Broke out of the loop because underlying stream is closed, but still
  26019. // cannot find an open bracket.
  26020. if (position < 0) {
  26021. this.raiseError('Reached the end of bundle when a length string is expected.');
  26022. }
  26023. const result = this.buffer.slice(0, position);
  26024. // Update the internal buffer to drop the read length.
  26025. this.buffer = this.buffer.slice(position);
  26026. return result;
  26027. }
  26028. /**
  26029. * Reads from a specified position from the internal buffer, for a specified
  26030. * number of bytes, pulling more data from the underlying stream if needed.
  26031. *
  26032. * Returns a string decoded from the read bytes.
  26033. */
  26034. async readJsonString(length) {
  26035. while (this.buffer.length < length) {
  26036. const done = await this.pullMoreDataToBuffer();
  26037. if (done) {
  26038. this.raiseError('Reached the end of bundle when more is expected.');
  26039. }
  26040. }
  26041. const result = this.textDecoder.decode(this.buffer.slice(0, length));
  26042. // Update the internal buffer to drop the read json string.
  26043. this.buffer = this.buffer.slice(length);
  26044. return result;
  26045. }
  26046. raiseError(message) {
  26047. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  26048. this.reader.cancel();
  26049. throw new Error(`Invalid bundle format: ${message}`);
  26050. }
  26051. /**
  26052. * Pulls more data from underlying stream to internal buffer.
  26053. * Returns a boolean indicating whether the stream is finished.
  26054. */
  26055. async pullMoreDataToBuffer() {
  26056. const result = await this.reader.read();
  26057. if (!result.done) {
  26058. const newBuffer = new Uint8Array(this.buffer.length + result.value.length);
  26059. newBuffer.set(this.buffer);
  26060. newBuffer.set(result.value, this.buffer.length);
  26061. this.buffer = newBuffer;
  26062. }
  26063. return result.done;
  26064. }
  26065. }
  26066. function newBundleReader(reader, serializer) {
  26067. return new BundleReaderImpl(reader, serializer);
  26068. }
  26069. /**
  26070. * @license
  26071. * Copyright 2017 Google LLC
  26072. *
  26073. * Licensed under the Apache License, Version 2.0 (the "License");
  26074. * you may not use this file except in compliance with the License.
  26075. * You may obtain a copy of the License at
  26076. *
  26077. * http://www.apache.org/licenses/LICENSE-2.0
  26078. *
  26079. * Unless required by applicable law or agreed to in writing, software
  26080. * distributed under the License is distributed on an "AS IS" BASIS,
  26081. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26082. * See the License for the specific language governing permissions and
  26083. * limitations under the License.
  26084. */
  26085. /**
  26086. * Internal transaction object responsible for accumulating the mutations to
  26087. * perform and the base versions for any documents read.
  26088. */
  26089. class Transaction$2 {
  26090. constructor(datastore) {
  26091. this.datastore = datastore;
  26092. // The version of each document that was read during this transaction.
  26093. this.readVersions = new Map();
  26094. this.mutations = [];
  26095. this.committed = false;
  26096. /**
  26097. * A deferred usage error that occurred previously in this transaction that
  26098. * will cause the transaction to fail once it actually commits.
  26099. */
  26100. this.lastWriteError = null;
  26101. /**
  26102. * Set of documents that have been written in the transaction.
  26103. *
  26104. * When there's more than one write to the same key in a transaction, any
  26105. * writes after the first are handled differently.
  26106. */
  26107. this.writtenDocs = new Set();
  26108. }
  26109. async lookup(keys) {
  26110. this.ensureCommitNotCalled();
  26111. if (this.mutations.length > 0) {
  26112. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Firestore transactions require all reads to be executed before all writes.');
  26113. }
  26114. const docs = await invokeBatchGetDocumentsRpc(this.datastore, keys);
  26115. docs.forEach(doc => this.recordVersion(doc));
  26116. return docs;
  26117. }
  26118. set(key, data) {
  26119. this.write(data.toMutation(key, this.precondition(key)));
  26120. this.writtenDocs.add(key.toString());
  26121. }
  26122. update(key, data) {
  26123. try {
  26124. this.write(data.toMutation(key, this.preconditionForUpdate(key)));
  26125. }
  26126. catch (e) {
  26127. this.lastWriteError = e;
  26128. }
  26129. this.writtenDocs.add(key.toString());
  26130. }
  26131. delete(key) {
  26132. this.write(new DeleteMutation(key, this.precondition(key)));
  26133. this.writtenDocs.add(key.toString());
  26134. }
  26135. async commit() {
  26136. this.ensureCommitNotCalled();
  26137. if (this.lastWriteError) {
  26138. throw this.lastWriteError;
  26139. }
  26140. const unwritten = this.readVersions;
  26141. // For each mutation, note that the doc was written.
  26142. this.mutations.forEach(mutation => {
  26143. unwritten.delete(mutation.key.toString());
  26144. });
  26145. // For each document that was read but not written to, we want to perform
  26146. // a `verify` operation.
  26147. unwritten.forEach((_, path) => {
  26148. const key = DocumentKey.fromPath(path);
  26149. this.mutations.push(new VerifyMutation(key, this.precondition(key)));
  26150. });
  26151. await invokeCommitRpc(this.datastore, this.mutations);
  26152. this.committed = true;
  26153. }
  26154. recordVersion(doc) {
  26155. let docVersion;
  26156. if (doc.isFoundDocument()) {
  26157. docVersion = doc.version;
  26158. }
  26159. else if (doc.isNoDocument()) {
  26160. // Represent a deleted doc using SnapshotVersion.min().
  26161. docVersion = SnapshotVersion.min();
  26162. }
  26163. else {
  26164. throw fail();
  26165. }
  26166. const existingVersion = this.readVersions.get(doc.key.toString());
  26167. if (existingVersion) {
  26168. if (!docVersion.isEqual(existingVersion)) {
  26169. // This transaction will fail no matter what.
  26170. throw new FirestoreError(Code.ABORTED, 'Document version changed between two reads.');
  26171. }
  26172. }
  26173. else {
  26174. this.readVersions.set(doc.key.toString(), docVersion);
  26175. }
  26176. }
  26177. /**
  26178. * Returns the version of this document when it was read in this transaction,
  26179. * as a precondition, or no precondition if it was not read.
  26180. */
  26181. precondition(key) {
  26182. const version = this.readVersions.get(key.toString());
  26183. if (!this.writtenDocs.has(key.toString()) && version) {
  26184. if (version.isEqual(SnapshotVersion.min())) {
  26185. return Precondition.exists(false);
  26186. }
  26187. else {
  26188. return Precondition.updateTime(version);
  26189. }
  26190. }
  26191. else {
  26192. return Precondition.none();
  26193. }
  26194. }
  26195. /**
  26196. * Returns the precondition for a document if the operation is an update.
  26197. */
  26198. preconditionForUpdate(key) {
  26199. const version = this.readVersions.get(key.toString());
  26200. // The first time a document is written, we want to take into account the
  26201. // read time and existence
  26202. if (!this.writtenDocs.has(key.toString()) && version) {
  26203. if (version.isEqual(SnapshotVersion.min())) {
  26204. // The document doesn't exist, so fail the transaction.
  26205. // This has to be validated locally because you can't send a
  26206. // precondition that a document does not exist without changing the
  26207. // semantics of the backend write to be an insert. This is the reverse
  26208. // of what we want, since we want to assert that the document doesn't
  26209. // exist but then send the update and have it fail. Since we can't
  26210. // express that to the backend, we have to validate locally.
  26211. // Note: this can change once we can send separate verify writes in the
  26212. // transaction.
  26213. throw new FirestoreError(Code.INVALID_ARGUMENT, "Can't update a document that doesn't exist.");
  26214. }
  26215. // Document exists, base precondition on document update time.
  26216. return Precondition.updateTime(version);
  26217. }
  26218. else {
  26219. // Document was not read, so we just use the preconditions for a blind
  26220. // update.
  26221. return Precondition.exists(true);
  26222. }
  26223. }
  26224. write(mutation) {
  26225. this.ensureCommitNotCalled();
  26226. this.mutations.push(mutation);
  26227. }
  26228. ensureCommitNotCalled() {
  26229. }
  26230. }
  26231. /**
  26232. * @license
  26233. * Copyright 2019 Google LLC
  26234. *
  26235. * Licensed under the Apache License, Version 2.0 (the "License");
  26236. * you may not use this file except in compliance with the License.
  26237. * You may obtain a copy of the License at
  26238. *
  26239. * http://www.apache.org/licenses/LICENSE-2.0
  26240. *
  26241. * Unless required by applicable law or agreed to in writing, software
  26242. * distributed under the License is distributed on an "AS IS" BASIS,
  26243. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26244. * See the License for the specific language governing permissions and
  26245. * limitations under the License.
  26246. */
  26247. /**
  26248. * TransactionRunner encapsulates the logic needed to run and retry transactions
  26249. * with backoff.
  26250. */
  26251. class TransactionRunner {
  26252. constructor(asyncQueue, datastore, options, updateFunction, deferred) {
  26253. this.asyncQueue = asyncQueue;
  26254. this.datastore = datastore;
  26255. this.options = options;
  26256. this.updateFunction = updateFunction;
  26257. this.deferred = deferred;
  26258. this.attemptsRemaining = options.maxAttempts;
  26259. this.backoff = new ExponentialBackoff(this.asyncQueue, "transaction_retry" /* TimerId.TransactionRetry */);
  26260. }
  26261. /** Runs the transaction and sets the result on deferred. */
  26262. run() {
  26263. this.attemptsRemaining -= 1;
  26264. this.runWithBackOff();
  26265. }
  26266. runWithBackOff() {
  26267. this.backoff.backoffAndRun(async () => {
  26268. const transaction = new Transaction$2(this.datastore);
  26269. const userPromise = this.tryRunUpdateFunction(transaction);
  26270. if (userPromise) {
  26271. userPromise
  26272. .then(result => {
  26273. this.asyncQueue.enqueueAndForget(() => {
  26274. return transaction
  26275. .commit()
  26276. .then(() => {
  26277. this.deferred.resolve(result);
  26278. })
  26279. .catch(commitError => {
  26280. this.handleTransactionError(commitError);
  26281. });
  26282. });
  26283. })
  26284. .catch(userPromiseError => {
  26285. this.handleTransactionError(userPromiseError);
  26286. });
  26287. }
  26288. });
  26289. }
  26290. tryRunUpdateFunction(transaction) {
  26291. try {
  26292. const userPromise = this.updateFunction(transaction);
  26293. if (isNullOrUndefined(userPromise) ||
  26294. !userPromise.catch ||
  26295. !userPromise.then) {
  26296. this.deferred.reject(Error('Transaction callback must return a Promise'));
  26297. return null;
  26298. }
  26299. return userPromise;
  26300. }
  26301. catch (error) {
  26302. // Do not retry errors thrown by user provided updateFunction.
  26303. this.deferred.reject(error);
  26304. return null;
  26305. }
  26306. }
  26307. handleTransactionError(error) {
  26308. if (this.attemptsRemaining > 0 && this.isRetryableTransactionError(error)) {
  26309. this.attemptsRemaining -= 1;
  26310. this.asyncQueue.enqueueAndForget(() => {
  26311. this.runWithBackOff();
  26312. return Promise.resolve();
  26313. });
  26314. }
  26315. else {
  26316. this.deferred.reject(error);
  26317. }
  26318. }
  26319. isRetryableTransactionError(error) {
  26320. if (error.name === 'FirebaseError') {
  26321. // In transactions, the backend will fail outdated reads with FAILED_PRECONDITION and
  26322. // non-matching document versions with ABORTED. These errors should be retried.
  26323. const code = error.code;
  26324. return (code === 'aborted' ||
  26325. code === 'failed-precondition' ||
  26326. code === 'already-exists' ||
  26327. !isPermanentError(code));
  26328. }
  26329. return false;
  26330. }
  26331. }
  26332. /**
  26333. * @license
  26334. * Copyright 2017 Google LLC
  26335. *
  26336. * Licensed under the Apache License, Version 2.0 (the "License");
  26337. * you may not use this file except in compliance with the License.
  26338. * You may obtain a copy of the License at
  26339. *
  26340. * http://www.apache.org/licenses/LICENSE-2.0
  26341. *
  26342. * Unless required by applicable law or agreed to in writing, software
  26343. * distributed under the License is distributed on an "AS IS" BASIS,
  26344. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26345. * See the License for the specific language governing permissions and
  26346. * limitations under the License.
  26347. */
  26348. const LOG_TAG$2 = 'FirestoreClient';
  26349. const MAX_CONCURRENT_LIMBO_RESOLUTIONS = 100;
  26350. /** DOMException error code constants. */
  26351. const DOM_EXCEPTION_INVALID_STATE = 11;
  26352. const DOM_EXCEPTION_ABORTED = 20;
  26353. const DOM_EXCEPTION_QUOTA_EXCEEDED = 22;
  26354. /**
  26355. * FirestoreClient is a top-level class that constructs and owns all of the //
  26356. * pieces of the client SDK architecture. It is responsible for creating the //
  26357. * async queue that is shared by all of the other components in the system. //
  26358. */
  26359. class FirestoreClient {
  26360. constructor(authCredentials, appCheckCredentials,
  26361. /**
  26362. * Asynchronous queue responsible for all of our internal processing. When
  26363. * we get incoming work from the user (via public API) or the network
  26364. * (incoming GRPC messages), we should always schedule onto this queue.
  26365. * This ensures all of our work is properly serialized (e.g. we don't
  26366. * start processing a new operation while the previous one is waiting for
  26367. * an async I/O to complete).
  26368. */
  26369. asyncQueue, databaseInfo) {
  26370. this.authCredentials = authCredentials;
  26371. this.appCheckCredentials = appCheckCredentials;
  26372. this.asyncQueue = asyncQueue;
  26373. this.databaseInfo = databaseInfo;
  26374. this.user = User.UNAUTHENTICATED;
  26375. this.clientId = AutoId.newId();
  26376. this.authCredentialListener = () => Promise.resolve();
  26377. this.appCheckCredentialListener = () => Promise.resolve();
  26378. this.authCredentials.start(asyncQueue, async (user) => {
  26379. logDebug(LOG_TAG$2, 'Received user=', user.uid);
  26380. await this.authCredentialListener(user);
  26381. this.user = user;
  26382. });
  26383. this.appCheckCredentials.start(asyncQueue, newAppCheckToken => {
  26384. logDebug(LOG_TAG$2, 'Received new app check token=', newAppCheckToken);
  26385. return this.appCheckCredentialListener(newAppCheckToken, this.user);
  26386. });
  26387. }
  26388. async getConfiguration() {
  26389. return {
  26390. asyncQueue: this.asyncQueue,
  26391. databaseInfo: this.databaseInfo,
  26392. clientId: this.clientId,
  26393. authCredentials: this.authCredentials,
  26394. appCheckCredentials: this.appCheckCredentials,
  26395. initialUser: this.user,
  26396. maxConcurrentLimboResolutions: MAX_CONCURRENT_LIMBO_RESOLUTIONS
  26397. };
  26398. }
  26399. setCredentialChangeListener(listener) {
  26400. this.authCredentialListener = listener;
  26401. }
  26402. setAppCheckTokenChangeListener(listener) {
  26403. this.appCheckCredentialListener = listener;
  26404. }
  26405. /**
  26406. * Checks that the client has not been terminated. Ensures that other methods on //
  26407. * this class cannot be called after the client is terminated. //
  26408. */
  26409. verifyNotTerminated() {
  26410. if (this.asyncQueue.isShuttingDown) {
  26411. throw new FirestoreError(Code.FAILED_PRECONDITION, 'The client has already been terminated.');
  26412. }
  26413. }
  26414. terminate() {
  26415. this.asyncQueue.enterRestrictedMode();
  26416. const deferred = new Deferred();
  26417. this.asyncQueue.enqueueAndForgetEvenWhileRestricted(async () => {
  26418. try {
  26419. if (this._onlineComponents) {
  26420. await this._onlineComponents.terminate();
  26421. }
  26422. if (this._offlineComponents) {
  26423. await this._offlineComponents.terminate();
  26424. }
  26425. // The credentials provider must be terminated after shutting down the
  26426. // RemoteStore as it will prevent the RemoteStore from retrieving auth
  26427. // tokens.
  26428. this.authCredentials.shutdown();
  26429. this.appCheckCredentials.shutdown();
  26430. deferred.resolve();
  26431. }
  26432. catch (e) {
  26433. const firestoreError = wrapInUserErrorIfRecoverable(e, `Failed to shutdown persistence`);
  26434. deferred.reject(firestoreError);
  26435. }
  26436. });
  26437. return deferred.promise;
  26438. }
  26439. }
  26440. async function setOfflineComponentProvider(client, offlineComponentProvider) {
  26441. client.asyncQueue.verifyOperationInProgress();
  26442. logDebug(LOG_TAG$2, 'Initializing OfflineComponentProvider');
  26443. const configuration = await client.getConfiguration();
  26444. await offlineComponentProvider.initialize(configuration);
  26445. let currentUser = configuration.initialUser;
  26446. client.setCredentialChangeListener(async (user) => {
  26447. if (!currentUser.isEqual(user)) {
  26448. await localStoreHandleUserChange(offlineComponentProvider.localStore, user);
  26449. currentUser = user;
  26450. }
  26451. });
  26452. // When a user calls clearPersistence() in one client, all other clients
  26453. // need to be terminated to allow the delete to succeed.
  26454. offlineComponentProvider.persistence.setDatabaseDeletedListener(() => client.terminate());
  26455. client._offlineComponents = offlineComponentProvider;
  26456. }
  26457. async function setOnlineComponentProvider(client, onlineComponentProvider) {
  26458. client.asyncQueue.verifyOperationInProgress();
  26459. const offlineComponentProvider = await ensureOfflineComponents(client);
  26460. logDebug(LOG_TAG$2, 'Initializing OnlineComponentProvider');
  26461. const configuration = await client.getConfiguration();
  26462. await onlineComponentProvider.initialize(offlineComponentProvider, configuration);
  26463. // The CredentialChangeListener of the online component provider takes
  26464. // precedence over the offline component provider.
  26465. client.setCredentialChangeListener(user => remoteStoreHandleCredentialChange(onlineComponentProvider.remoteStore, user));
  26466. client.setAppCheckTokenChangeListener((_, user) => remoteStoreHandleCredentialChange(onlineComponentProvider.remoteStore, user));
  26467. client._onlineComponents = onlineComponentProvider;
  26468. }
  26469. /**
  26470. * Decides whether the provided error allows us to gracefully disable
  26471. * persistence (as opposed to crashing the client).
  26472. */
  26473. function canFallbackFromIndexedDbError(error) {
  26474. if (error.name === 'FirebaseError') {
  26475. return (error.code === Code.FAILED_PRECONDITION ||
  26476. error.code === Code.UNIMPLEMENTED);
  26477. }
  26478. else if (typeof DOMException !== 'undefined' &&
  26479. error instanceof DOMException) {
  26480. // There are a few known circumstances where we can open IndexedDb but
  26481. // trying to read/write will fail (e.g. quota exceeded). For
  26482. // well-understood cases, we attempt to detect these and then gracefully
  26483. // fall back to memory persistence.
  26484. // NOTE: Rather than continue to add to this list, we could decide to
  26485. // always fall back, with the risk that we might accidentally hide errors
  26486. // representing actual SDK bugs.
  26487. return (
  26488. // When the browser is out of quota we could get either quota exceeded
  26489. // or an aborted error depending on whether the error happened during
  26490. // schema migration.
  26491. error.code === DOM_EXCEPTION_QUOTA_EXCEEDED ||
  26492. error.code === DOM_EXCEPTION_ABORTED ||
  26493. // Firefox Private Browsing mode disables IndexedDb and returns
  26494. // INVALID_STATE for any usage.
  26495. error.code === DOM_EXCEPTION_INVALID_STATE);
  26496. }
  26497. return true;
  26498. }
  26499. async function ensureOfflineComponents(client) {
  26500. if (!client._offlineComponents) {
  26501. if (client._uninitializedComponentsProvider) {
  26502. logDebug(LOG_TAG$2, 'Using user provided OfflineComponentProvider');
  26503. try {
  26504. await setOfflineComponentProvider(client, client._uninitializedComponentsProvider._offline);
  26505. }
  26506. catch (e) {
  26507. const error = e;
  26508. if (!canFallbackFromIndexedDbError(error)) {
  26509. throw error;
  26510. }
  26511. logWarn('Error using user provided cache. Falling back to ' +
  26512. 'memory cache: ' +
  26513. error);
  26514. await setOfflineComponentProvider(client, new MemoryOfflineComponentProvider());
  26515. }
  26516. }
  26517. else {
  26518. logDebug(LOG_TAG$2, 'Using default OfflineComponentProvider');
  26519. await setOfflineComponentProvider(client, new MemoryOfflineComponentProvider());
  26520. }
  26521. }
  26522. return client._offlineComponents;
  26523. }
  26524. async function ensureOnlineComponents(client) {
  26525. if (!client._onlineComponents) {
  26526. if (client._uninitializedComponentsProvider) {
  26527. logDebug(LOG_TAG$2, 'Using user provided OnlineComponentProvider');
  26528. await setOnlineComponentProvider(client, client._uninitializedComponentsProvider._online);
  26529. }
  26530. else {
  26531. logDebug(LOG_TAG$2, 'Using default OnlineComponentProvider');
  26532. await setOnlineComponentProvider(client, new OnlineComponentProvider());
  26533. }
  26534. }
  26535. return client._onlineComponents;
  26536. }
  26537. function getPersistence(client) {
  26538. return ensureOfflineComponents(client).then(c => c.persistence);
  26539. }
  26540. function getLocalStore(client) {
  26541. return ensureOfflineComponents(client).then(c => c.localStore);
  26542. }
  26543. function getRemoteStore(client) {
  26544. return ensureOnlineComponents(client).then(c => c.remoteStore);
  26545. }
  26546. function getSyncEngine(client) {
  26547. return ensureOnlineComponents(client).then(c => c.syncEngine);
  26548. }
  26549. function getDatastore(client) {
  26550. return ensureOnlineComponents(client).then(c => c.datastore);
  26551. }
  26552. async function getEventManager(client) {
  26553. const onlineComponentProvider = await ensureOnlineComponents(client);
  26554. const eventManager = onlineComponentProvider.eventManager;
  26555. eventManager.onListen = syncEngineListen.bind(null, onlineComponentProvider.syncEngine);
  26556. eventManager.onUnlisten = syncEngineUnlisten.bind(null, onlineComponentProvider.syncEngine);
  26557. return eventManager;
  26558. }
  26559. /** Enables the network connection and re-enqueues all pending operations. */
  26560. function firestoreClientEnableNetwork(client) {
  26561. return client.asyncQueue.enqueue(async () => {
  26562. const persistence = await getPersistence(client);
  26563. const remoteStore = await getRemoteStore(client);
  26564. persistence.setNetworkEnabled(true);
  26565. return remoteStoreEnableNetwork(remoteStore);
  26566. });
  26567. }
  26568. /** Disables the network connection. Pending operations will not complete. */
  26569. function firestoreClientDisableNetwork(client) {
  26570. return client.asyncQueue.enqueue(async () => {
  26571. const persistence = await getPersistence(client);
  26572. const remoteStore = await getRemoteStore(client);
  26573. persistence.setNetworkEnabled(false);
  26574. return remoteStoreDisableNetwork(remoteStore);
  26575. });
  26576. }
  26577. /**
  26578. * Returns a Promise that resolves when all writes that were pending at the time
  26579. * this method was called received server acknowledgement. An acknowledgement
  26580. * can be either acceptance or rejection.
  26581. */
  26582. function firestoreClientWaitForPendingWrites(client) {
  26583. const deferred = new Deferred();
  26584. client.asyncQueue.enqueueAndForget(async () => {
  26585. const syncEngine = await getSyncEngine(client);
  26586. return syncEngineRegisterPendingWritesCallback(syncEngine, deferred);
  26587. });
  26588. return deferred.promise;
  26589. }
  26590. function firestoreClientListen(client, query, options, observer) {
  26591. const wrappedObserver = new AsyncObserver(observer);
  26592. const listener = new QueryListener(query, wrappedObserver, options);
  26593. client.asyncQueue.enqueueAndForget(async () => {
  26594. const eventManager = await getEventManager(client);
  26595. return eventManagerListen(eventManager, listener);
  26596. });
  26597. return () => {
  26598. wrappedObserver.mute();
  26599. client.asyncQueue.enqueueAndForget(async () => {
  26600. const eventManager = await getEventManager(client);
  26601. return eventManagerUnlisten(eventManager, listener);
  26602. });
  26603. };
  26604. }
  26605. function firestoreClientGetDocumentFromLocalCache(client, docKey) {
  26606. const deferred = new Deferred();
  26607. client.asyncQueue.enqueueAndForget(async () => {
  26608. const localStore = await getLocalStore(client);
  26609. return readDocumentFromCache(localStore, docKey, deferred);
  26610. });
  26611. return deferred.promise;
  26612. }
  26613. function firestoreClientGetDocumentViaSnapshotListener(client, key, options = {}) {
  26614. const deferred = new Deferred();
  26615. client.asyncQueue.enqueueAndForget(async () => {
  26616. const eventManager = await getEventManager(client);
  26617. return readDocumentViaSnapshotListener(eventManager, client.asyncQueue, key, options, deferred);
  26618. });
  26619. return deferred.promise;
  26620. }
  26621. function firestoreClientGetDocumentsFromLocalCache(client, query) {
  26622. const deferred = new Deferred();
  26623. client.asyncQueue.enqueueAndForget(async () => {
  26624. const localStore = await getLocalStore(client);
  26625. return executeQueryFromCache(localStore, query, deferred);
  26626. });
  26627. return deferred.promise;
  26628. }
  26629. function firestoreClientGetDocumentsViaSnapshotListener(client, query, options = {}) {
  26630. const deferred = new Deferred();
  26631. client.asyncQueue.enqueueAndForget(async () => {
  26632. const eventManager = await getEventManager(client);
  26633. return executeQueryViaSnapshotListener(eventManager, client.asyncQueue, query, options, deferred);
  26634. });
  26635. return deferred.promise;
  26636. }
  26637. function firestoreClientRunAggregateQuery(client, query, aggregates) {
  26638. const deferred = new Deferred();
  26639. client.asyncQueue.enqueueAndForget(async () => {
  26640. // TODO (sum/avg) should we update this to use the event manager?
  26641. // Implement and call executeAggregateQueryViaSnapshotListener, similar
  26642. // to the implementation in firestoreClientGetDocumentsViaSnapshotListener
  26643. // above
  26644. try {
  26645. // TODO(b/277628384): check `canUseNetwork()` and handle multi-tab.
  26646. const datastore = await getDatastore(client);
  26647. deferred.resolve(invokeRunAggregationQueryRpc(datastore, query, aggregates));
  26648. }
  26649. catch (e) {
  26650. deferred.reject(e);
  26651. }
  26652. });
  26653. return deferred.promise;
  26654. }
  26655. function firestoreClientWrite(client, mutations) {
  26656. const deferred = new Deferred();
  26657. client.asyncQueue.enqueueAndForget(async () => {
  26658. const syncEngine = await getSyncEngine(client);
  26659. return syncEngineWrite(syncEngine, mutations, deferred);
  26660. });
  26661. return deferred.promise;
  26662. }
  26663. function firestoreClientAddSnapshotsInSyncListener(client, observer) {
  26664. const wrappedObserver = new AsyncObserver(observer);
  26665. client.asyncQueue.enqueueAndForget(async () => {
  26666. const eventManager = await getEventManager(client);
  26667. return addSnapshotsInSyncListener(eventManager, wrappedObserver);
  26668. });
  26669. return () => {
  26670. wrappedObserver.mute();
  26671. client.asyncQueue.enqueueAndForget(async () => {
  26672. const eventManager = await getEventManager(client);
  26673. return removeSnapshotsInSyncListener(eventManager, wrappedObserver);
  26674. });
  26675. };
  26676. }
  26677. /**
  26678. * Takes an updateFunction in which a set of reads and writes can be performed
  26679. * atomically. In the updateFunction, the client can read and write values
  26680. * using the supplied transaction object. After the updateFunction, all
  26681. * changes will be committed. If a retryable error occurs (ex: some other
  26682. * client has changed any of the data referenced), then the updateFunction
  26683. * will be called again after a backoff. If the updateFunction still fails
  26684. * after all retries, then the transaction will be rejected.
  26685. *
  26686. * The transaction object passed to the updateFunction contains methods for
  26687. * accessing documents and collections. Unlike other datastore access, data
  26688. * accessed with the transaction will not reflect local changes that have not
  26689. * been committed. For this reason, it is required that all reads are
  26690. * performed before any writes. Transactions must be performed while online.
  26691. */
  26692. function firestoreClientTransaction(client, updateFunction, options) {
  26693. const deferred = new Deferred();
  26694. client.asyncQueue.enqueueAndForget(async () => {
  26695. const datastore = await getDatastore(client);
  26696. new TransactionRunner(client.asyncQueue, datastore, options, updateFunction, deferred).run();
  26697. });
  26698. return deferred.promise;
  26699. }
  26700. async function readDocumentFromCache(localStore, docKey, result) {
  26701. try {
  26702. const document = await localStoreReadDocument(localStore, docKey);
  26703. if (document.isFoundDocument()) {
  26704. result.resolve(document);
  26705. }
  26706. else if (document.isNoDocument()) {
  26707. result.resolve(null);
  26708. }
  26709. else {
  26710. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get document from cache. (However, this document may ' +
  26711. "exist on the server. Run again without setting 'source' in " +
  26712. 'the GetOptions to attempt to retrieve the document from the ' +
  26713. 'server.)'));
  26714. }
  26715. }
  26716. catch (e) {
  26717. const firestoreError = wrapInUserErrorIfRecoverable(e, `Failed to get document '${docKey} from cache`);
  26718. result.reject(firestoreError);
  26719. }
  26720. }
  26721. /**
  26722. * Retrieves a latency-compensated document from the backend via a
  26723. * SnapshotListener.
  26724. */
  26725. function readDocumentViaSnapshotListener(eventManager, asyncQueue, key, options, result) {
  26726. const wrappedObserver = new AsyncObserver({
  26727. next: (snap) => {
  26728. // Remove query first before passing event to user to avoid
  26729. // user actions affecting the now stale query.
  26730. asyncQueue.enqueueAndForget(() => eventManagerUnlisten(eventManager, listener));
  26731. const exists = snap.docs.has(key);
  26732. if (!exists && snap.fromCache) {
  26733. // TODO(dimond): If we're online and the document doesn't
  26734. // exist then we resolve with a doc.exists set to false. If
  26735. // we're offline however, we reject the Promise in this
  26736. // case. Two options: 1) Cache the negative response from
  26737. // the server so we can deliver that even when you're
  26738. // offline 2) Actually reject the Promise in the online case
  26739. // if the document doesn't exist.
  26740. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get document because the client is offline.'));
  26741. }
  26742. else if (exists &&
  26743. snap.fromCache &&
  26744. options &&
  26745. options.source === 'server') {
  26746. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get document from server. (However, this ' +
  26747. 'document does exist in the local cache. Run again ' +
  26748. 'without setting source to "server" to ' +
  26749. 'retrieve the cached document.)'));
  26750. }
  26751. else {
  26752. result.resolve(snap);
  26753. }
  26754. },
  26755. error: e => result.reject(e)
  26756. });
  26757. const listener = new QueryListener(newQueryForPath(key.path), wrappedObserver, {
  26758. includeMetadataChanges: true,
  26759. waitForSyncWhenOnline: true
  26760. });
  26761. return eventManagerListen(eventManager, listener);
  26762. }
  26763. async function executeQueryFromCache(localStore, query, result) {
  26764. try {
  26765. const queryResult = await localStoreExecuteQuery(localStore, query,
  26766. /* usePreviousResults= */ true);
  26767. const view = new View(query, queryResult.remoteKeys);
  26768. const viewDocChanges = view.computeDocChanges(queryResult.documents);
  26769. const viewChange = view.applyChanges(viewDocChanges,
  26770. /* updateLimboDocuments= */ false);
  26771. result.resolve(viewChange.snapshot);
  26772. }
  26773. catch (e) {
  26774. const firestoreError = wrapInUserErrorIfRecoverable(e, `Failed to execute query '${query} against cache`);
  26775. result.reject(firestoreError);
  26776. }
  26777. }
  26778. /**
  26779. * Retrieves a latency-compensated query snapshot from the backend via a
  26780. * SnapshotListener.
  26781. */
  26782. function executeQueryViaSnapshotListener(eventManager, asyncQueue, query, options, result) {
  26783. const wrappedObserver = new AsyncObserver({
  26784. next: snapshot => {
  26785. // Remove query first before passing event to user to avoid
  26786. // user actions affecting the now stale query.
  26787. asyncQueue.enqueueAndForget(() => eventManagerUnlisten(eventManager, listener));
  26788. if (snapshot.fromCache && options.source === 'server') {
  26789. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get documents from server. (However, these ' +
  26790. 'documents may exist in the local cache. Run again ' +
  26791. 'without setting source to "server" to ' +
  26792. 'retrieve the cached documents.)'));
  26793. }
  26794. else {
  26795. result.resolve(snapshot);
  26796. }
  26797. },
  26798. error: e => result.reject(e)
  26799. });
  26800. const listener = new QueryListener(query, wrappedObserver, {
  26801. includeMetadataChanges: true,
  26802. waitForSyncWhenOnline: true
  26803. });
  26804. return eventManagerListen(eventManager, listener);
  26805. }
  26806. function firestoreClientLoadBundle(client, databaseId, data, resultTask) {
  26807. const reader = createBundleReader(data, newSerializer(databaseId));
  26808. client.asyncQueue.enqueueAndForget(async () => {
  26809. syncEngineLoadBundle(await getSyncEngine(client), reader, resultTask);
  26810. });
  26811. }
  26812. function firestoreClientGetNamedQuery(client, queryName) {
  26813. return client.asyncQueue.enqueue(async () => localStoreGetNamedQuery(await getLocalStore(client), queryName));
  26814. }
  26815. function createBundleReader(data, serializer) {
  26816. let content;
  26817. if (typeof data === 'string') {
  26818. content = newTextEncoder().encode(data);
  26819. }
  26820. else {
  26821. content = data;
  26822. }
  26823. return newBundleReader(toByteStreamReader(content), serializer);
  26824. }
  26825. function firestoreClientSetIndexConfiguration(client, indexes) {
  26826. return client.asyncQueue.enqueue(async () => {
  26827. return localStoreConfigureFieldIndexes(await getLocalStore(client), indexes);
  26828. });
  26829. }
  26830. /**
  26831. * @license
  26832. * Copyright 2023 Google LLC
  26833. *
  26834. * Licensed under the Apache License, Version 2.0 (the "License");
  26835. * you may not use this file except in compliance with the License.
  26836. * You may obtain a copy of the License at
  26837. *
  26838. * http://www.apache.org/licenses/LICENSE-2.0
  26839. *
  26840. * Unless required by applicable law or agreed to in writing, software
  26841. * distributed under the License is distributed on an "AS IS" BASIS,
  26842. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26843. * See the License for the specific language governing permissions and
  26844. * limitations under the License.
  26845. */
  26846. /**
  26847. * Compares two `ExperimentalLongPollingOptions` objects for equality.
  26848. */
  26849. function longPollingOptionsEqual(options1, options2) {
  26850. return options1.timeoutSeconds === options2.timeoutSeconds;
  26851. }
  26852. /**
  26853. * Creates and returns a new `ExperimentalLongPollingOptions` with the same
  26854. * option values as the given instance.
  26855. */
  26856. function cloneLongPollingOptions(options) {
  26857. const clone = {};
  26858. if (options.timeoutSeconds !== undefined) {
  26859. clone.timeoutSeconds = options.timeoutSeconds;
  26860. }
  26861. return clone;
  26862. }
  26863. /**
  26864. * @license
  26865. * Copyright 2020 Google LLC
  26866. *
  26867. * Licensed under the Apache License, Version 2.0 (the "License");
  26868. * you may not use this file except in compliance with the License.
  26869. * You may obtain a copy of the License at
  26870. *
  26871. * http://www.apache.org/licenses/LICENSE-2.0
  26872. *
  26873. * Unless required by applicable law or agreed to in writing, software
  26874. * distributed under the License is distributed on an "AS IS" BASIS,
  26875. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26876. * See the License for the specific language governing permissions and
  26877. * limitations under the License.
  26878. */
  26879. const LOG_TAG$1 = 'ComponentProvider';
  26880. /**
  26881. * An instance map that ensures only one Datastore exists per Firestore
  26882. * instance.
  26883. */
  26884. const datastoreInstances = new Map();
  26885. /**
  26886. * Removes all components associated with the provided instance. Must be called
  26887. * when the `Firestore` instance is terminated.
  26888. */
  26889. function removeComponents(firestore) {
  26890. const datastore = datastoreInstances.get(firestore);
  26891. if (datastore) {
  26892. logDebug(LOG_TAG$1, 'Removing Datastore');
  26893. datastoreInstances.delete(firestore);
  26894. datastore.terminate();
  26895. }
  26896. }
  26897. function makeDatabaseInfo(databaseId, appId, persistenceKey, settings) {
  26898. return new DatabaseInfo(databaseId, appId, persistenceKey, settings.host, settings.ssl, settings.experimentalForceLongPolling, settings.experimentalAutoDetectLongPolling, cloneLongPollingOptions(settings.experimentalLongPollingOptions), settings.useFetchStreams);
  26899. }
  26900. /**
  26901. * @license
  26902. * Copyright 2020 Google LLC
  26903. *
  26904. * Licensed under the Apache License, Version 2.0 (the "License");
  26905. * you may not use this file except in compliance with the License.
  26906. * You may obtain a copy of the License at
  26907. *
  26908. * http://www.apache.org/licenses/LICENSE-2.0
  26909. *
  26910. * Unless required by applicable law or agreed to in writing, software
  26911. * distributed under the License is distributed on an "AS IS" BASIS,
  26912. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26913. * See the License for the specific language governing permissions and
  26914. * limitations under the License.
  26915. */
  26916. // settings() defaults:
  26917. const DEFAULT_HOST = 'firestore.googleapis.com';
  26918. const DEFAULT_SSL = true;
  26919. // The minimum long-polling timeout is hardcoded on the server. The value here
  26920. // should be kept in sync with the value used by the server, as the server will
  26921. // silently ignore a value below the minimum and fall back to the default.
  26922. // Googlers see b/266868871 for relevant discussion.
  26923. const MIN_LONG_POLLING_TIMEOUT_SECONDS = 5;
  26924. // No maximum long-polling timeout is configured in the server, and defaults to
  26925. // 30 seconds, which is what Watch appears to use.
  26926. // Googlers see b/266868871 for relevant discussion.
  26927. const MAX_LONG_POLLING_TIMEOUT_SECONDS = 30;
  26928. // Whether long-polling auto-detected is enabled by default.
  26929. const DEFAULT_AUTO_DETECT_LONG_POLLING = true;
  26930. /**
  26931. * A concrete type describing all the values that can be applied via a
  26932. * user-supplied `FirestoreSettings` object. This is a separate type so that
  26933. * defaults can be supplied and the value can be checked for equality.
  26934. */
  26935. class FirestoreSettingsImpl {
  26936. constructor(settings) {
  26937. var _a, _b;
  26938. if (settings.host === undefined) {
  26939. if (settings.ssl !== undefined) {
  26940. throw new FirestoreError(Code.INVALID_ARGUMENT, "Can't provide ssl option if host option is not set");
  26941. }
  26942. this.host = DEFAULT_HOST;
  26943. this.ssl = DEFAULT_SSL;
  26944. }
  26945. else {
  26946. this.host = settings.host;
  26947. this.ssl = (_a = settings.ssl) !== null && _a !== void 0 ? _a : DEFAULT_SSL;
  26948. }
  26949. this.credentials = settings.credentials;
  26950. this.ignoreUndefinedProperties = !!settings.ignoreUndefinedProperties;
  26951. this.cache = settings.localCache;
  26952. if (settings.cacheSizeBytes === undefined) {
  26953. this.cacheSizeBytes = LRU_DEFAULT_CACHE_SIZE_BYTES;
  26954. }
  26955. else {
  26956. if (settings.cacheSizeBytes !== LRU_COLLECTION_DISABLED &&
  26957. settings.cacheSizeBytes < LRU_MINIMUM_CACHE_SIZE_BYTES) {
  26958. throw new FirestoreError(Code.INVALID_ARGUMENT, `cacheSizeBytes must be at least ${LRU_MINIMUM_CACHE_SIZE_BYTES}`);
  26959. }
  26960. else {
  26961. this.cacheSizeBytes = settings.cacheSizeBytes;
  26962. }
  26963. }
  26964. validateIsNotUsedTogether('experimentalForceLongPolling', settings.experimentalForceLongPolling, 'experimentalAutoDetectLongPolling', settings.experimentalAutoDetectLongPolling);
  26965. this.experimentalForceLongPolling = !!settings.experimentalForceLongPolling;
  26966. if (this.experimentalForceLongPolling) {
  26967. this.experimentalAutoDetectLongPolling = false;
  26968. }
  26969. else if (settings.experimentalAutoDetectLongPolling === undefined) {
  26970. this.experimentalAutoDetectLongPolling = DEFAULT_AUTO_DETECT_LONG_POLLING;
  26971. }
  26972. else {
  26973. // For backwards compatibility, coerce the value to boolean even though
  26974. // the TypeScript compiler has narrowed the type to boolean already.
  26975. // noinspection PointlessBooleanExpressionJS
  26976. this.experimentalAutoDetectLongPolling =
  26977. !!settings.experimentalAutoDetectLongPolling;
  26978. }
  26979. this.experimentalLongPollingOptions = cloneLongPollingOptions((_b = settings.experimentalLongPollingOptions) !== null && _b !== void 0 ? _b : {});
  26980. validateLongPollingOptions(this.experimentalLongPollingOptions);
  26981. this.useFetchStreams = !!settings.useFetchStreams;
  26982. }
  26983. isEqual(other) {
  26984. return (this.host === other.host &&
  26985. this.ssl === other.ssl &&
  26986. this.credentials === other.credentials &&
  26987. this.cacheSizeBytes === other.cacheSizeBytes &&
  26988. this.experimentalForceLongPolling ===
  26989. other.experimentalForceLongPolling &&
  26990. this.experimentalAutoDetectLongPolling ===
  26991. other.experimentalAutoDetectLongPolling &&
  26992. longPollingOptionsEqual(this.experimentalLongPollingOptions, other.experimentalLongPollingOptions) &&
  26993. this.ignoreUndefinedProperties === other.ignoreUndefinedProperties &&
  26994. this.useFetchStreams === other.useFetchStreams);
  26995. }
  26996. }
  26997. function validateLongPollingOptions(options) {
  26998. if (options.timeoutSeconds !== undefined) {
  26999. if (isNaN(options.timeoutSeconds)) {
  27000. throw new FirestoreError(Code.INVALID_ARGUMENT, `invalid long polling timeout: ` +
  27001. `${options.timeoutSeconds} (must not be NaN)`);
  27002. }
  27003. if (options.timeoutSeconds < MIN_LONG_POLLING_TIMEOUT_SECONDS) {
  27004. throw new FirestoreError(Code.INVALID_ARGUMENT, `invalid long polling timeout: ${options.timeoutSeconds} ` +
  27005. `(minimum allowed value is ${MIN_LONG_POLLING_TIMEOUT_SECONDS})`);
  27006. }
  27007. if (options.timeoutSeconds > MAX_LONG_POLLING_TIMEOUT_SECONDS) {
  27008. throw new FirestoreError(Code.INVALID_ARGUMENT, `invalid long polling timeout: ${options.timeoutSeconds} ` +
  27009. `(maximum allowed value is ${MAX_LONG_POLLING_TIMEOUT_SECONDS})`);
  27010. }
  27011. }
  27012. }
  27013. /**
  27014. * @license
  27015. * Copyright 2020 Google LLC
  27016. *
  27017. * Licensed under the Apache License, Version 2.0 (the "License");
  27018. * you may not use this file except in compliance with the License.
  27019. * You may obtain a copy of the License at
  27020. *
  27021. * http://www.apache.org/licenses/LICENSE-2.0
  27022. *
  27023. * Unless required by applicable law or agreed to in writing, software
  27024. * distributed under the License is distributed on an "AS IS" BASIS,
  27025. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27026. * See the License for the specific language governing permissions and
  27027. * limitations under the License.
  27028. */
  27029. /**
  27030. * The Cloud Firestore service interface.
  27031. *
  27032. * Do not call this constructor directly. Instead, use {@link (getFirestore:1)}.
  27033. */
  27034. class Firestore$1 {
  27035. /** @hideconstructor */
  27036. constructor(_authCredentials, _appCheckCredentials, _databaseId, _app) {
  27037. this._authCredentials = _authCredentials;
  27038. this._appCheckCredentials = _appCheckCredentials;
  27039. this._databaseId = _databaseId;
  27040. this._app = _app;
  27041. /**
  27042. * Whether it's a Firestore or Firestore Lite instance.
  27043. */
  27044. this.type = 'firestore-lite';
  27045. this._persistenceKey = '(lite)';
  27046. this._settings = new FirestoreSettingsImpl({});
  27047. this._settingsFrozen = false;
  27048. }
  27049. /**
  27050. * The {@link @firebase/app#FirebaseApp} associated with this `Firestore` service
  27051. * instance.
  27052. */
  27053. get app() {
  27054. if (!this._app) {
  27055. throw new FirestoreError(Code.FAILED_PRECONDITION, "Firestore was not initialized using the Firebase SDK. 'app' is " +
  27056. 'not available');
  27057. }
  27058. return this._app;
  27059. }
  27060. get _initialized() {
  27061. return this._settingsFrozen;
  27062. }
  27063. get _terminated() {
  27064. return this._terminateTask !== undefined;
  27065. }
  27066. _setSettings(settings) {
  27067. if (this._settingsFrozen) {
  27068. throw new FirestoreError(Code.FAILED_PRECONDITION, 'Firestore has already been started and its settings can no longer ' +
  27069. 'be changed. You can only modify settings before calling any other ' +
  27070. 'methods on a Firestore object.');
  27071. }
  27072. this._settings = new FirestoreSettingsImpl(settings);
  27073. if (settings.credentials !== undefined) {
  27074. this._authCredentials = makeAuthCredentialsProvider(settings.credentials);
  27075. }
  27076. }
  27077. _getSettings() {
  27078. return this._settings;
  27079. }
  27080. _freezeSettings() {
  27081. this._settingsFrozen = true;
  27082. return this._settings;
  27083. }
  27084. _delete() {
  27085. if (!this._terminateTask) {
  27086. this._terminateTask = this._terminate();
  27087. }
  27088. return this._terminateTask;
  27089. }
  27090. /** Returns a JSON-serializable representation of this `Firestore` instance. */
  27091. toJSON() {
  27092. return {
  27093. app: this._app,
  27094. databaseId: this._databaseId,
  27095. settings: this._settings
  27096. };
  27097. }
  27098. /**
  27099. * Terminates all components used by this client. Subclasses can override
  27100. * this method to clean up their own dependencies, but must also call this
  27101. * method.
  27102. *
  27103. * Only ever called once.
  27104. */
  27105. _terminate() {
  27106. removeComponents(this);
  27107. return Promise.resolve();
  27108. }
  27109. }
  27110. /**
  27111. * Modify this instance to communicate with the Cloud Firestore emulator.
  27112. *
  27113. * Note: This must be called before this instance has been used to do any
  27114. * operations.
  27115. *
  27116. * @param firestore - The `Firestore` instance to configure to connect to the
  27117. * emulator.
  27118. * @param host - the emulator host (ex: localhost).
  27119. * @param port - the emulator port (ex: 9000).
  27120. * @param options.mockUserToken - the mock auth token to use for unit testing
  27121. * Security Rules.
  27122. */
  27123. function connectFirestoreEmulator(firestore, host, port, options = {}) {
  27124. var _a;
  27125. firestore = cast(firestore, Firestore$1);
  27126. const settings = firestore._getSettings();
  27127. const newHostSetting = `${host}:${port}`;
  27128. if (settings.host !== DEFAULT_HOST && settings.host !== newHostSetting) {
  27129. logWarn('Host has been set in both settings() and connectFirestoreEmulator(), emulator host ' +
  27130. 'will be used.');
  27131. }
  27132. firestore._setSettings(Object.assign(Object.assign({}, settings), { host: newHostSetting, ssl: false }));
  27133. if (options.mockUserToken) {
  27134. let token;
  27135. let user;
  27136. if (typeof options.mockUserToken === 'string') {
  27137. token = options.mockUserToken;
  27138. user = User.MOCK_USER;
  27139. }
  27140. else {
  27141. // Let createMockUserToken validate first (catches common mistakes like
  27142. // invalid field "uid" and missing field "sub" / "user_id".)
  27143. token = util.createMockUserToken(options.mockUserToken, (_a = firestore._app) === null || _a === void 0 ? void 0 : _a.options.projectId);
  27144. const uid = options.mockUserToken.sub || options.mockUserToken.user_id;
  27145. if (!uid) {
  27146. throw new FirestoreError(Code.INVALID_ARGUMENT, "mockUserToken must contain 'sub' or 'user_id' field!");
  27147. }
  27148. user = new User(uid);
  27149. }
  27150. firestore._authCredentials = new EmulatorAuthCredentialsProvider(new OAuthToken(token, user));
  27151. }
  27152. }
  27153. /**
  27154. * @license
  27155. * Copyright 2020 Google LLC
  27156. *
  27157. * Licensed under the Apache License, Version 2.0 (the "License");
  27158. * you may not use this file except in compliance with the License.
  27159. * You may obtain a copy of the License at
  27160. *
  27161. * http://www.apache.org/licenses/LICENSE-2.0
  27162. *
  27163. * Unless required by applicable law or agreed to in writing, software
  27164. * distributed under the License is distributed on an "AS IS" BASIS,
  27165. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27166. * See the License for the specific language governing permissions and
  27167. * limitations under the License.
  27168. */
  27169. /**
  27170. * A `DocumentReference` refers to a document location in a Firestore database
  27171. * and can be used to write, read, or listen to the location. The document at
  27172. * the referenced location may or may not exist.
  27173. */
  27174. class DocumentReference {
  27175. /** @hideconstructor */
  27176. constructor(firestore,
  27177. /**
  27178. * If provided, the `FirestoreDataConverter` associated with this instance.
  27179. */
  27180. converter, _key) {
  27181. this.converter = converter;
  27182. this._key = _key;
  27183. /** The type of this Firestore reference. */
  27184. this.type = 'document';
  27185. this.firestore = firestore;
  27186. }
  27187. get _path() {
  27188. return this._key.path;
  27189. }
  27190. /**
  27191. * The document's identifier within its collection.
  27192. */
  27193. get id() {
  27194. return this._key.path.lastSegment();
  27195. }
  27196. /**
  27197. * A string representing the path of the referenced document (relative
  27198. * to the root of the database).
  27199. */
  27200. get path() {
  27201. return this._key.path.canonicalString();
  27202. }
  27203. /**
  27204. * The collection this `DocumentReference` belongs to.
  27205. */
  27206. get parent() {
  27207. return new CollectionReference(this.firestore, this.converter, this._key.path.popLast());
  27208. }
  27209. withConverter(converter) {
  27210. return new DocumentReference(this.firestore, converter, this._key);
  27211. }
  27212. }
  27213. /**
  27214. * A `Query` refers to a query which you can read or listen to. You can also
  27215. * construct refined `Query` objects by adding filters and ordering.
  27216. */
  27217. class Query {
  27218. // This is the lite version of the Query class in the main SDK.
  27219. /** @hideconstructor protected */
  27220. constructor(firestore,
  27221. /**
  27222. * If provided, the `FirestoreDataConverter` associated with this instance.
  27223. */
  27224. converter, _query) {
  27225. this.converter = converter;
  27226. this._query = _query;
  27227. /** The type of this Firestore reference. */
  27228. this.type = 'query';
  27229. this.firestore = firestore;
  27230. }
  27231. withConverter(converter) {
  27232. return new Query(this.firestore, converter, this._query);
  27233. }
  27234. }
  27235. /**
  27236. * A `CollectionReference` object can be used for adding documents, getting
  27237. * document references, and querying for documents (using {@link (query:1)}).
  27238. */
  27239. class CollectionReference extends Query {
  27240. /** @hideconstructor */
  27241. constructor(firestore, converter, _path) {
  27242. super(firestore, converter, newQueryForPath(_path));
  27243. this._path = _path;
  27244. /** The type of this Firestore reference. */
  27245. this.type = 'collection';
  27246. }
  27247. /** The collection's identifier. */
  27248. get id() {
  27249. return this._query.path.lastSegment();
  27250. }
  27251. /**
  27252. * A string representing the path of the referenced collection (relative
  27253. * to the root of the database).
  27254. */
  27255. get path() {
  27256. return this._query.path.canonicalString();
  27257. }
  27258. /**
  27259. * A reference to the containing `DocumentReference` if this is a
  27260. * subcollection. If this isn't a subcollection, the reference is null.
  27261. */
  27262. get parent() {
  27263. const parentPath = this._path.popLast();
  27264. if (parentPath.isEmpty()) {
  27265. return null;
  27266. }
  27267. else {
  27268. return new DocumentReference(this.firestore,
  27269. /* converter= */ null, new DocumentKey(parentPath));
  27270. }
  27271. }
  27272. withConverter(converter) {
  27273. return new CollectionReference(this.firestore, converter, this._path);
  27274. }
  27275. }
  27276. function collection(parent, path, ...pathSegments) {
  27277. parent = util.getModularInstance(parent);
  27278. validateNonEmptyArgument('collection', 'path', path);
  27279. if (parent instanceof Firestore$1) {
  27280. const absolutePath = ResourcePath.fromString(path, ...pathSegments);
  27281. validateCollectionPath(absolutePath);
  27282. return new CollectionReference(parent, /* converter= */ null, absolutePath);
  27283. }
  27284. else {
  27285. if (!(parent instanceof DocumentReference) &&
  27286. !(parent instanceof CollectionReference)) {
  27287. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Expected first argument to collection() to be a CollectionReference, ' +
  27288. 'a DocumentReference or FirebaseFirestore');
  27289. }
  27290. const absolutePath = parent._path.child(ResourcePath.fromString(path, ...pathSegments));
  27291. validateCollectionPath(absolutePath);
  27292. return new CollectionReference(parent.firestore,
  27293. /* converter= */ null, absolutePath);
  27294. }
  27295. }
  27296. // TODO(firestorelite): Consider using ErrorFactory -
  27297. // https://github.com/firebase/firebase-js-sdk/blob/0131e1f/packages/util/src/errors.ts#L106
  27298. /**
  27299. * Creates and returns a new `Query` instance that includes all documents in the
  27300. * database that are contained in a collection or subcollection with the
  27301. * given `collectionId`.
  27302. *
  27303. * @param firestore - A reference to the root `Firestore` instance.
  27304. * @param collectionId - Identifies the collections to query over. Every
  27305. * collection or subcollection with this ID as the last segment of its path
  27306. * will be included. Cannot contain a slash.
  27307. * @returns The created `Query`.
  27308. */
  27309. function collectionGroup(firestore, collectionId) {
  27310. firestore = cast(firestore, Firestore$1);
  27311. validateNonEmptyArgument('collectionGroup', 'collection id', collectionId);
  27312. if (collectionId.indexOf('/') >= 0) {
  27313. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid collection ID '${collectionId}' passed to function ` +
  27314. `collectionGroup(). Collection IDs must not contain '/'.`);
  27315. }
  27316. return new Query(firestore,
  27317. /* converter= */ null, newQueryForCollectionGroup(collectionId));
  27318. }
  27319. function doc(parent, path, ...pathSegments) {
  27320. parent = util.getModularInstance(parent);
  27321. // We allow omission of 'pathString' but explicitly prohibit passing in both
  27322. // 'undefined' and 'null'.
  27323. if (arguments.length === 1) {
  27324. path = AutoId.newId();
  27325. }
  27326. validateNonEmptyArgument('doc', 'path', path);
  27327. if (parent instanceof Firestore$1) {
  27328. const absolutePath = ResourcePath.fromString(path, ...pathSegments);
  27329. validateDocumentPath(absolutePath);
  27330. return new DocumentReference(parent,
  27331. /* converter= */ null, new DocumentKey(absolutePath));
  27332. }
  27333. else {
  27334. if (!(parent instanceof DocumentReference) &&
  27335. !(parent instanceof CollectionReference)) {
  27336. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Expected first argument to collection() to be a CollectionReference, ' +
  27337. 'a DocumentReference or FirebaseFirestore');
  27338. }
  27339. const absolutePath = parent._path.child(ResourcePath.fromString(path, ...pathSegments));
  27340. validateDocumentPath(absolutePath);
  27341. return new DocumentReference(parent.firestore, parent instanceof CollectionReference ? parent.converter : null, new DocumentKey(absolutePath));
  27342. }
  27343. }
  27344. /**
  27345. * Returns true if the provided references are equal.
  27346. *
  27347. * @param left - A reference to compare.
  27348. * @param right - A reference to compare.
  27349. * @returns true if the references point to the same location in the same
  27350. * Firestore database.
  27351. */
  27352. function refEqual(left, right) {
  27353. left = util.getModularInstance(left);
  27354. right = util.getModularInstance(right);
  27355. if ((left instanceof DocumentReference ||
  27356. left instanceof CollectionReference) &&
  27357. (right instanceof DocumentReference || right instanceof CollectionReference)) {
  27358. return (left.firestore === right.firestore &&
  27359. left.path === right.path &&
  27360. left.converter === right.converter);
  27361. }
  27362. return false;
  27363. }
  27364. /**
  27365. * Returns true if the provided queries point to the same collection and apply
  27366. * the same constraints.
  27367. *
  27368. * @param left - A `Query` to compare.
  27369. * @param right - A `Query` to compare.
  27370. * @returns true if the references point to the same location in the same
  27371. * Firestore database.
  27372. */
  27373. function queryEqual(left, right) {
  27374. left = util.getModularInstance(left);
  27375. right = util.getModularInstance(right);
  27376. if (left instanceof Query && right instanceof Query) {
  27377. return (left.firestore === right.firestore &&
  27378. queryEquals(left._query, right._query) &&
  27379. left.converter === right.converter);
  27380. }
  27381. return false;
  27382. }
  27383. /**
  27384. * @license
  27385. * Copyright 2020 Google LLC
  27386. *
  27387. * Licensed under the Apache License, Version 2.0 (the "License");
  27388. * you may not use this file except in compliance with the License.
  27389. * You may obtain a copy of the License at
  27390. *
  27391. * http://www.apache.org/licenses/LICENSE-2.0
  27392. *
  27393. * Unless required by applicable law or agreed to in writing, software
  27394. * distributed under the License is distributed on an "AS IS" BASIS,
  27395. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27396. * See the License for the specific language governing permissions and
  27397. * limitations under the License.
  27398. */
  27399. const LOG_TAG = 'AsyncQueue';
  27400. class AsyncQueueImpl {
  27401. constructor() {
  27402. // The last promise in the queue.
  27403. this.tail = Promise.resolve();
  27404. // A list of retryable operations. Retryable operations are run in order and
  27405. // retried with backoff.
  27406. this.retryableOps = [];
  27407. // Is this AsyncQueue being shut down? Once it is set to true, it will not
  27408. // be changed again.
  27409. this._isShuttingDown = false;
  27410. // Operations scheduled to be queued in the future. Operations are
  27411. // automatically removed after they are run or canceled.
  27412. this.delayedOperations = [];
  27413. // visible for testing
  27414. this.failure = null;
  27415. // Flag set while there's an outstanding AsyncQueue operation, used for
  27416. // assertion sanity-checks.
  27417. this.operationInProgress = false;
  27418. // Enabled during shutdown on Safari to prevent future access to IndexedDB.
  27419. this.skipNonRestrictedTasks = false;
  27420. // List of TimerIds to fast-forward delays for.
  27421. this.timerIdsToSkip = [];
  27422. // Backoff timer used to schedule retries for retryable operations
  27423. this.backoff = new ExponentialBackoff(this, "async_queue_retry" /* TimerId.AsyncQueueRetry */);
  27424. // Visibility handler that triggers an immediate retry of all retryable
  27425. // operations. Meant to speed up recovery when we regain file system access
  27426. // after page comes into foreground.
  27427. this.visibilityHandler = () => {
  27428. this.backoff.skipBackoff();
  27429. };
  27430. }
  27431. get isShuttingDown() {
  27432. return this._isShuttingDown;
  27433. }
  27434. /**
  27435. * Adds a new operation to the queue without waiting for it to complete (i.e.
  27436. * we ignore the Promise result).
  27437. */
  27438. enqueueAndForget(op) {
  27439. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  27440. this.enqueue(op);
  27441. }
  27442. enqueueAndForgetEvenWhileRestricted(op) {
  27443. this.verifyNotFailed();
  27444. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  27445. this.enqueueInternal(op);
  27446. }
  27447. enterRestrictedMode(purgeExistingTasks) {
  27448. if (!this._isShuttingDown) {
  27449. this._isShuttingDown = true;
  27450. this.skipNonRestrictedTasks = purgeExistingTasks || false;
  27451. }
  27452. }
  27453. enqueue(op) {
  27454. this.verifyNotFailed();
  27455. if (this._isShuttingDown) {
  27456. // Return a Promise which never resolves.
  27457. return new Promise(() => { });
  27458. }
  27459. // Create a deferred Promise that we can return to the callee. This
  27460. // allows us to return a "hanging Promise" only to the callee and still
  27461. // advance the queue even when the operation is not run.
  27462. const task = new Deferred();
  27463. return this.enqueueInternal(() => {
  27464. if (this._isShuttingDown && this.skipNonRestrictedTasks) {
  27465. // We do not resolve 'task'
  27466. return Promise.resolve();
  27467. }
  27468. op().then(task.resolve, task.reject);
  27469. return task.promise;
  27470. }).then(() => task.promise);
  27471. }
  27472. enqueueRetryable(op) {
  27473. this.enqueueAndForget(() => {
  27474. this.retryableOps.push(op);
  27475. return this.retryNextOp();
  27476. });
  27477. }
  27478. /**
  27479. * Runs the next operation from the retryable queue. If the operation fails,
  27480. * reschedules with backoff.
  27481. */
  27482. async retryNextOp() {
  27483. if (this.retryableOps.length === 0) {
  27484. return;
  27485. }
  27486. try {
  27487. await this.retryableOps[0]();
  27488. this.retryableOps.shift();
  27489. this.backoff.reset();
  27490. }
  27491. catch (e) {
  27492. if (isIndexedDbTransactionError(e)) {
  27493. logDebug(LOG_TAG, 'Operation failed with retryable error: ' + e);
  27494. }
  27495. else {
  27496. throw e; // Failure will be handled by AsyncQueue
  27497. }
  27498. }
  27499. if (this.retryableOps.length > 0) {
  27500. // If there are additional operations, we re-schedule `retryNextOp()`.
  27501. // This is necessary to run retryable operations that failed during
  27502. // their initial attempt since we don't know whether they are already
  27503. // enqueued. If, for example, `op1`, `op2`, `op3` are enqueued and `op1`
  27504. // needs to be re-run, we will run `op1`, `op1`, `op2` using the
  27505. // already enqueued calls to `retryNextOp()`. `op3()` will then run in the
  27506. // call scheduled here.
  27507. // Since `backoffAndRun()` cancels an existing backoff and schedules a
  27508. // new backoff on every call, there is only ever a single additional
  27509. // operation in the queue.
  27510. this.backoff.backoffAndRun(() => this.retryNextOp());
  27511. }
  27512. }
  27513. enqueueInternal(op) {
  27514. const newTail = this.tail.then(() => {
  27515. this.operationInProgress = true;
  27516. return op()
  27517. .catch((error) => {
  27518. this.failure = error;
  27519. this.operationInProgress = false;
  27520. const message = getMessageOrStack(error);
  27521. logError('INTERNAL UNHANDLED ERROR: ', message);
  27522. // Re-throw the error so that this.tail becomes a rejected Promise and
  27523. // all further attempts to chain (via .then) will just short-circuit
  27524. // and return the rejected Promise.
  27525. throw error;
  27526. })
  27527. .then(result => {
  27528. this.operationInProgress = false;
  27529. return result;
  27530. });
  27531. });
  27532. this.tail = newTail;
  27533. return newTail;
  27534. }
  27535. enqueueAfterDelay(timerId, delayMs, op) {
  27536. this.verifyNotFailed();
  27537. // Fast-forward delays for timerIds that have been overriden.
  27538. if (this.timerIdsToSkip.indexOf(timerId) > -1) {
  27539. delayMs = 0;
  27540. }
  27541. const delayedOp = DelayedOperation.createAndSchedule(this, timerId, delayMs, op, removedOp => this.removeDelayedOperation(removedOp));
  27542. this.delayedOperations.push(delayedOp);
  27543. return delayedOp;
  27544. }
  27545. verifyNotFailed() {
  27546. if (this.failure) {
  27547. fail();
  27548. }
  27549. }
  27550. verifyOperationInProgress() {
  27551. }
  27552. /**
  27553. * Waits until all currently queued tasks are finished executing. Delayed
  27554. * operations are not run.
  27555. */
  27556. async drain() {
  27557. // Operations in the queue prior to draining may have enqueued additional
  27558. // operations. Keep draining the queue until the tail is no longer advanced,
  27559. // which indicates that no more new operations were enqueued and that all
  27560. // operations were executed.
  27561. let currentTail;
  27562. do {
  27563. currentTail = this.tail;
  27564. await currentTail;
  27565. } while (currentTail !== this.tail);
  27566. }
  27567. /**
  27568. * For Tests: Determine if a delayed operation with a particular TimerId
  27569. * exists.
  27570. */
  27571. containsDelayedOperation(timerId) {
  27572. for (const op of this.delayedOperations) {
  27573. if (op.timerId === timerId) {
  27574. return true;
  27575. }
  27576. }
  27577. return false;
  27578. }
  27579. /**
  27580. * For Tests: Runs some or all delayed operations early.
  27581. *
  27582. * @param lastTimerId - Delayed operations up to and including this TimerId
  27583. * will be drained. Pass TimerId.All to run all delayed operations.
  27584. * @returns a Promise that resolves once all operations have been run.
  27585. */
  27586. runAllDelayedOperationsUntil(lastTimerId) {
  27587. // Note that draining may generate more delayed ops, so we do that first.
  27588. return this.drain().then(() => {
  27589. // Run ops in the same order they'd run if they ran naturally.
  27590. this.delayedOperations.sort((a, b) => a.targetTimeMs - b.targetTimeMs);
  27591. for (const op of this.delayedOperations) {
  27592. op.skipDelay();
  27593. if (lastTimerId !== "all" /* TimerId.All */ && op.timerId === lastTimerId) {
  27594. break;
  27595. }
  27596. }
  27597. return this.drain();
  27598. });
  27599. }
  27600. /**
  27601. * For Tests: Skip all subsequent delays for a timer id.
  27602. */
  27603. skipDelaysForTimerId(timerId) {
  27604. this.timerIdsToSkip.push(timerId);
  27605. }
  27606. /** Called once a DelayedOperation is run or canceled. */
  27607. removeDelayedOperation(op) {
  27608. // NOTE: indexOf / slice are O(n), but delayedOperations is expected to be small.
  27609. const index = this.delayedOperations.indexOf(op);
  27610. this.delayedOperations.splice(index, 1);
  27611. }
  27612. }
  27613. function newAsyncQueue() {
  27614. return new AsyncQueueImpl();
  27615. }
  27616. /**
  27617. * Chrome includes Error.message in Error.stack. Other browsers do not.
  27618. * This returns expected output of message + stack when available.
  27619. * @param error - Error or FirestoreError
  27620. */
  27621. function getMessageOrStack(error) {
  27622. let message = error.message || '';
  27623. if (error.stack) {
  27624. if (error.stack.includes(error.message)) {
  27625. message = error.stack;
  27626. }
  27627. else {
  27628. message = error.message + '\n' + error.stack;
  27629. }
  27630. }
  27631. return message;
  27632. }
  27633. /**
  27634. * @license
  27635. * Copyright 2020 Google LLC
  27636. *
  27637. * Licensed under the Apache License, Version 2.0 (the "License");
  27638. * you may not use this file except in compliance with the License.
  27639. * You may obtain a copy of the License at
  27640. *
  27641. * http://www.apache.org/licenses/LICENSE-2.0
  27642. *
  27643. * Unless required by applicable law or agreed to in writing, software
  27644. * distributed under the License is distributed on an "AS IS" BASIS,
  27645. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27646. * See the License for the specific language governing permissions and
  27647. * limitations under the License.
  27648. */
  27649. /**
  27650. * Represents the task of loading a Firestore bundle. It provides progress of bundle
  27651. * loading, as well as task completion and error events.
  27652. *
  27653. * The API is compatible with `Promise<LoadBundleTaskProgress>`.
  27654. */
  27655. class LoadBundleTask {
  27656. constructor() {
  27657. this._progressObserver = {};
  27658. this._taskCompletionResolver = new Deferred();
  27659. this._lastProgress = {
  27660. taskState: 'Running',
  27661. totalBytes: 0,
  27662. totalDocuments: 0,
  27663. bytesLoaded: 0,
  27664. documentsLoaded: 0
  27665. };
  27666. }
  27667. /**
  27668. * Registers functions to listen to bundle loading progress events.
  27669. * @param next - Called when there is a progress update from bundle loading. Typically `next` calls occur
  27670. * each time a Firestore document is loaded from the bundle.
  27671. * @param error - Called when an error occurs during bundle loading. The task aborts after reporting the
  27672. * error, and there should be no more updates after this.
  27673. * @param complete - Called when the loading task is complete.
  27674. */
  27675. onProgress(next, error, complete) {
  27676. this._progressObserver = {
  27677. next,
  27678. error,
  27679. complete
  27680. };
  27681. }
  27682. /**
  27683. * Implements the `Promise<LoadBundleTaskProgress>.catch` interface.
  27684. *
  27685. * @param onRejected - Called when an error occurs during bundle loading.
  27686. */
  27687. catch(onRejected) {
  27688. return this._taskCompletionResolver.promise.catch(onRejected);
  27689. }
  27690. /**
  27691. * Implements the `Promise<LoadBundleTaskProgress>.then` interface.
  27692. *
  27693. * @param onFulfilled - Called on the completion of the loading task with a final `LoadBundleTaskProgress` update.
  27694. * The update will always have its `taskState` set to `"Success"`.
  27695. * @param onRejected - Called when an error occurs during bundle loading.
  27696. */
  27697. then(onFulfilled, onRejected) {
  27698. return this._taskCompletionResolver.promise.then(onFulfilled, onRejected);
  27699. }
  27700. /**
  27701. * Notifies all observers that bundle loading has completed, with a provided
  27702. * `LoadBundleTaskProgress` object.
  27703. *
  27704. * @private
  27705. */
  27706. _completeWith(progress) {
  27707. this._updateProgress(progress);
  27708. if (this._progressObserver.complete) {
  27709. this._progressObserver.complete();
  27710. }
  27711. this._taskCompletionResolver.resolve(progress);
  27712. }
  27713. /**
  27714. * Notifies all observers that bundle loading has failed, with a provided
  27715. * `Error` as the reason.
  27716. *
  27717. * @private
  27718. */
  27719. _failWith(error) {
  27720. this._lastProgress.taskState = 'Error';
  27721. if (this._progressObserver.next) {
  27722. this._progressObserver.next(this._lastProgress);
  27723. }
  27724. if (this._progressObserver.error) {
  27725. this._progressObserver.error(error);
  27726. }
  27727. this._taskCompletionResolver.reject(error);
  27728. }
  27729. /**
  27730. * Notifies a progress update of loading a bundle.
  27731. * @param progress - The new progress.
  27732. *
  27733. * @private
  27734. */
  27735. _updateProgress(progress) {
  27736. this._lastProgress = progress;
  27737. if (this._progressObserver.next) {
  27738. this._progressObserver.next(progress);
  27739. }
  27740. }
  27741. }
  27742. /**
  27743. * @license
  27744. * Copyright 2020 Google LLC
  27745. *
  27746. * Licensed under the Apache License, Version 2.0 (the "License");
  27747. * you may not use this file except in compliance with the License.
  27748. * You may obtain a copy of the License at
  27749. *
  27750. * http://www.apache.org/licenses/LICENSE-2.0
  27751. *
  27752. * Unless required by applicable law or agreed to in writing, software
  27753. * distributed under the License is distributed on an "AS IS" BASIS,
  27754. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27755. * See the License for the specific language governing permissions and
  27756. * limitations under the License.
  27757. */
  27758. /**
  27759. * Constant used to indicate the LRU garbage collection should be disabled.
  27760. * Set this value as the `cacheSizeBytes` on the settings passed to the
  27761. * {@link Firestore} instance.
  27762. */
  27763. const CACHE_SIZE_UNLIMITED = LRU_COLLECTION_DISABLED;
  27764. /**
  27765. * The Cloud Firestore service interface.
  27766. *
  27767. * Do not call this constructor directly. Instead, use {@link (getFirestore:1)}.
  27768. */
  27769. class Firestore extends Firestore$1 {
  27770. /** @hideconstructor */
  27771. constructor(authCredentialsProvider, appCheckCredentialsProvider, databaseId, app) {
  27772. super(authCredentialsProvider, appCheckCredentialsProvider, databaseId, app);
  27773. /**
  27774. * Whether it's a {@link Firestore} or Firestore Lite instance.
  27775. */
  27776. this.type = 'firestore';
  27777. this._queue = newAsyncQueue();
  27778. this._persistenceKey = (app === null || app === void 0 ? void 0 : app.name) || '[DEFAULT]';
  27779. }
  27780. _terminate() {
  27781. if (!this._firestoreClient) {
  27782. // The client must be initialized to ensure that all subsequent API
  27783. // usage throws an exception.
  27784. configureFirestore(this);
  27785. }
  27786. return this._firestoreClient.terminate();
  27787. }
  27788. }
  27789. /**
  27790. * Initializes a new instance of {@link Firestore} with the provided settings.
  27791. * Can only be called before any other function, including
  27792. * {@link (getFirestore:1)}. If the custom settings are empty, this function is
  27793. * equivalent to calling {@link (getFirestore:1)}.
  27794. *
  27795. * @param app - The {@link @firebase/app#FirebaseApp} with which the {@link Firestore} instance will
  27796. * be associated.
  27797. * @param settings - A settings object to configure the {@link Firestore} instance.
  27798. * @param databaseId - The name of the database.
  27799. * @returns A newly initialized {@link Firestore} instance.
  27800. */
  27801. function initializeFirestore(app$1, settings, databaseId) {
  27802. if (!databaseId) {
  27803. databaseId = DEFAULT_DATABASE_NAME;
  27804. }
  27805. const provider = app._getProvider(app$1, 'firestore');
  27806. if (provider.isInitialized(databaseId)) {
  27807. const existingInstance = provider.getImmediate({
  27808. identifier: databaseId
  27809. });
  27810. const initialSettings = provider.getOptions(databaseId);
  27811. if (util.deepEqual(initialSettings, settings)) {
  27812. return existingInstance;
  27813. }
  27814. else {
  27815. throw new FirestoreError(Code.FAILED_PRECONDITION, 'initializeFirestore() has already been called with ' +
  27816. 'different options. To avoid this error, call initializeFirestore() with the ' +
  27817. 'same options as when it was originally called, or call getFirestore() to return the' +
  27818. ' already initialized instance.');
  27819. }
  27820. }
  27821. if (settings.cacheSizeBytes !== undefined &&
  27822. settings.localCache !== undefined) {
  27823. throw new FirestoreError(Code.INVALID_ARGUMENT, `cache and cacheSizeBytes cannot be specified at the same time as cacheSizeBytes will` +
  27824. `be deprecated. Instead, specify the cache size in the cache object`);
  27825. }
  27826. if (settings.cacheSizeBytes !== undefined &&
  27827. settings.cacheSizeBytes !== CACHE_SIZE_UNLIMITED &&
  27828. settings.cacheSizeBytes < LRU_MINIMUM_CACHE_SIZE_BYTES) {
  27829. throw new FirestoreError(Code.INVALID_ARGUMENT, `cacheSizeBytes must be at least ${LRU_MINIMUM_CACHE_SIZE_BYTES}`);
  27830. }
  27831. return provider.initialize({
  27832. options: settings,
  27833. instanceIdentifier: databaseId
  27834. });
  27835. }
  27836. function getFirestore(appOrDatabaseId, optionalDatabaseId) {
  27837. const app$1 = typeof appOrDatabaseId === 'object' ? appOrDatabaseId : app.getApp();
  27838. const databaseId = typeof appOrDatabaseId === 'string'
  27839. ? appOrDatabaseId
  27840. : optionalDatabaseId || DEFAULT_DATABASE_NAME;
  27841. const db = app._getProvider(app$1, 'firestore').getImmediate({
  27842. identifier: databaseId
  27843. });
  27844. if (!db._initialized) {
  27845. const emulator = util.getDefaultEmulatorHostnameAndPort('firestore');
  27846. if (emulator) {
  27847. connectFirestoreEmulator(db, ...emulator);
  27848. }
  27849. }
  27850. return db;
  27851. }
  27852. /**
  27853. * @internal
  27854. */
  27855. function ensureFirestoreConfigured(firestore) {
  27856. if (!firestore._firestoreClient) {
  27857. configureFirestore(firestore);
  27858. }
  27859. firestore._firestoreClient.verifyNotTerminated();
  27860. return firestore._firestoreClient;
  27861. }
  27862. function configureFirestore(firestore) {
  27863. var _a, _b, _c;
  27864. const settings = firestore._freezeSettings();
  27865. const databaseInfo = makeDatabaseInfo(firestore._databaseId, ((_a = firestore._app) === null || _a === void 0 ? void 0 : _a.options.appId) || '', firestore._persistenceKey, settings);
  27866. firestore._firestoreClient = new FirestoreClient(firestore._authCredentials, firestore._appCheckCredentials, firestore._queue, databaseInfo);
  27867. if (((_b = settings.cache) === null || _b === void 0 ? void 0 : _b._offlineComponentProvider) &&
  27868. ((_c = settings.cache) === null || _c === void 0 ? void 0 : _c._onlineComponentProvider)) {
  27869. firestore._firestoreClient._uninitializedComponentsProvider = {
  27870. _offlineKind: settings.cache.kind,
  27871. _offline: settings.cache._offlineComponentProvider,
  27872. _online: settings.cache._onlineComponentProvider
  27873. };
  27874. }
  27875. }
  27876. /**
  27877. * Attempts to enable persistent storage, if possible.
  27878. *
  27879. * Must be called before any other functions (other than
  27880. * {@link initializeFirestore}, {@link (getFirestore:1)} or
  27881. * {@link clearIndexedDbPersistence}.
  27882. *
  27883. * If this fails, `enableIndexedDbPersistence()` will reject the promise it
  27884. * returns. Note that even after this failure, the {@link Firestore} instance will
  27885. * remain usable, however offline persistence will be disabled.
  27886. *
  27887. * There are several reasons why this can fail, which can be identified by
  27888. * the `code` on the error.
  27889. *
  27890. * * failed-precondition: The app is already open in another browser tab.
  27891. * * unimplemented: The browser is incompatible with the offline
  27892. * persistence implementation.
  27893. *
  27894. * Persistence cannot be used in a Node.js environment.
  27895. *
  27896. * @param firestore - The {@link Firestore} instance to enable persistence for.
  27897. * @param persistenceSettings - Optional settings object to configure
  27898. * persistence.
  27899. * @returns A `Promise` that represents successfully enabling persistent storage.
  27900. * @deprecated This function will be removed in a future major release. Instead, set
  27901. * `FirestoreSettings.cache` to an instance of `IndexedDbLocalCache` to
  27902. * turn on IndexedDb cache. Calling this function when `FirestoreSettings.cache`
  27903. * is already specified will throw an exception.
  27904. */
  27905. function enableIndexedDbPersistence(firestore, persistenceSettings) {
  27906. firestore = cast(firestore, Firestore);
  27907. verifyNotInitialized(firestore);
  27908. const client = ensureFirestoreConfigured(firestore);
  27909. if (client._uninitializedComponentsProvider) {
  27910. throw new FirestoreError(Code.FAILED_PRECONDITION, 'SDK cache is already specified.');
  27911. }
  27912. logWarn('enableIndexedDbPersistence() will be deprecated in the future, ' +
  27913. 'you can use `FirestoreSettings.cache` instead.');
  27914. const settings = firestore._freezeSettings();
  27915. const onlineComponentProvider = new OnlineComponentProvider();
  27916. const offlineComponentProvider = new IndexedDbOfflineComponentProvider(onlineComponentProvider, settings.cacheSizeBytes, persistenceSettings === null || persistenceSettings === void 0 ? void 0 : persistenceSettings.forceOwnership);
  27917. return setPersistenceProviders(client, onlineComponentProvider, offlineComponentProvider);
  27918. }
  27919. /**
  27920. * Attempts to enable multi-tab persistent storage, if possible. If enabled
  27921. * across all tabs, all operations share access to local persistence, including
  27922. * shared execution of queries and latency-compensated local document updates
  27923. * across all connected instances.
  27924. *
  27925. * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise
  27926. * it returns. Note that even after this failure, the {@link Firestore} instance will
  27927. * remain usable, however offline persistence will be disabled.
  27928. *
  27929. * There are several reasons why this can fail, which can be identified by
  27930. * the `code` on the error.
  27931. *
  27932. * * failed-precondition: The app is already open in another browser tab and
  27933. * multi-tab is not enabled.
  27934. * * unimplemented: The browser is incompatible with the offline
  27935. * persistence implementation.
  27936. *
  27937. * @param firestore - The {@link Firestore} instance to enable persistence for.
  27938. * @returns A `Promise` that represents successfully enabling persistent
  27939. * storage.
  27940. * @deprecated This function will be removed in a future major release. Instead, set
  27941. * `FirestoreSettings.cache` to an instance of `IndexedDbLocalCache` to
  27942. * turn on indexeddb cache. Calling this function when `FirestoreSettings.cache`
  27943. * is already specified will throw an exception.
  27944. */
  27945. function enableMultiTabIndexedDbPersistence(firestore) {
  27946. firestore = cast(firestore, Firestore);
  27947. verifyNotInitialized(firestore);
  27948. const client = ensureFirestoreConfigured(firestore);
  27949. if (client._uninitializedComponentsProvider) {
  27950. throw new FirestoreError(Code.FAILED_PRECONDITION, 'SDK cache is already specified.');
  27951. }
  27952. logWarn('enableMultiTabIndexedDbPersistence() will be deprecated in the future, ' +
  27953. 'you can use `FirestoreSettings.cache` instead.');
  27954. const settings = firestore._freezeSettings();
  27955. const onlineComponentProvider = new OnlineComponentProvider();
  27956. const offlineComponentProvider = new MultiTabOfflineComponentProvider(onlineComponentProvider, settings.cacheSizeBytes);
  27957. return setPersistenceProviders(client, onlineComponentProvider, offlineComponentProvider);
  27958. }
  27959. /**
  27960. * Registers both the `OfflineComponentProvider` and `OnlineComponentProvider`.
  27961. * If the operation fails with a recoverable error (see
  27962. * `canRecoverFromIndexedDbError()` below), the returned Promise is rejected
  27963. * but the client remains usable.
  27964. */
  27965. function setPersistenceProviders(client, onlineComponentProvider, offlineComponentProvider) {
  27966. const persistenceResult = new Deferred();
  27967. return client.asyncQueue
  27968. .enqueue(async () => {
  27969. try {
  27970. await setOfflineComponentProvider(client, offlineComponentProvider);
  27971. await setOnlineComponentProvider(client, onlineComponentProvider);
  27972. persistenceResult.resolve();
  27973. }
  27974. catch (e) {
  27975. const error = e;
  27976. if (!canFallbackFromIndexedDbError(error)) {
  27977. throw error;
  27978. }
  27979. logWarn('Error enabling indexeddb cache. Falling back to ' +
  27980. 'memory cache: ' +
  27981. error);
  27982. persistenceResult.reject(error);
  27983. }
  27984. })
  27985. .then(() => persistenceResult.promise);
  27986. }
  27987. /**
  27988. * Clears the persistent storage. This includes pending writes and cached
  27989. * documents.
  27990. *
  27991. * Must be called while the {@link Firestore} instance is not started (after the app is
  27992. * terminated or when the app is first initialized). On startup, this function
  27993. * must be called before other functions (other than {@link
  27994. * initializeFirestore} or {@link (getFirestore:1)})). If the {@link Firestore}
  27995. * instance is still running, the promise will be rejected with the error code
  27996. * of `failed-precondition`.
  27997. *
  27998. * Note: `clearIndexedDbPersistence()` is primarily intended to help write
  27999. * reliable tests that use Cloud Firestore. It uses an efficient mechanism for
  28000. * dropping existing data but does not attempt to securely overwrite or
  28001. * otherwise make cached data unrecoverable. For applications that are sensitive
  28002. * to the disclosure of cached data in between user sessions, we strongly
  28003. * recommend not enabling persistence at all.
  28004. *
  28005. * @param firestore - The {@link Firestore} instance to clear persistence for.
  28006. * @returns A `Promise` that is resolved when the persistent storage is
  28007. * cleared. Otherwise, the promise is rejected with an error.
  28008. */
  28009. function clearIndexedDbPersistence(firestore) {
  28010. if (firestore._initialized && !firestore._terminated) {
  28011. throw new FirestoreError(Code.FAILED_PRECONDITION, 'Persistence can only be cleared before a Firestore instance is ' +
  28012. 'initialized or after it is terminated.');
  28013. }
  28014. const deferred = new Deferred();
  28015. firestore._queue.enqueueAndForgetEvenWhileRestricted(async () => {
  28016. try {
  28017. await indexedDbClearPersistence(indexedDbStoragePrefix(firestore._databaseId, firestore._persistenceKey));
  28018. deferred.resolve();
  28019. }
  28020. catch (e) {
  28021. deferred.reject(e);
  28022. }
  28023. });
  28024. return deferred.promise;
  28025. }
  28026. /**
  28027. * Waits until all currently pending writes for the active user have been
  28028. * acknowledged by the backend.
  28029. *
  28030. * The returned promise resolves immediately if there are no outstanding writes.
  28031. * Otherwise, the promise waits for all previously issued writes (including
  28032. * those written in a previous app session), but it does not wait for writes
  28033. * that were added after the function is called. If you want to wait for
  28034. * additional writes, call `waitForPendingWrites()` again.
  28035. *
  28036. * Any outstanding `waitForPendingWrites()` promises are rejected during user
  28037. * changes.
  28038. *
  28039. * @returns A `Promise` which resolves when all currently pending writes have been
  28040. * acknowledged by the backend.
  28041. */
  28042. function waitForPendingWrites(firestore) {
  28043. firestore = cast(firestore, Firestore);
  28044. const client = ensureFirestoreConfigured(firestore);
  28045. return firestoreClientWaitForPendingWrites(client);
  28046. }
  28047. /**
  28048. * Re-enables use of the network for this {@link Firestore} instance after a prior
  28049. * call to {@link disableNetwork}.
  28050. *
  28051. * @returns A `Promise` that is resolved once the network has been enabled.
  28052. */
  28053. function enableNetwork(firestore) {
  28054. firestore = cast(firestore, Firestore);
  28055. const client = ensureFirestoreConfigured(firestore);
  28056. return firestoreClientEnableNetwork(client);
  28057. }
  28058. /**
  28059. * Disables network usage for this instance. It can be re-enabled via {@link
  28060. * enableNetwork}. While the network is disabled, any snapshot listeners,
  28061. * `getDoc()` or `getDocs()` calls will return results from cache, and any write
  28062. * operations will be queued until the network is restored.
  28063. *
  28064. * @returns A `Promise` that is resolved once the network has been disabled.
  28065. */
  28066. function disableNetwork(firestore) {
  28067. firestore = cast(firestore, Firestore);
  28068. const client = ensureFirestoreConfigured(firestore);
  28069. return firestoreClientDisableNetwork(client);
  28070. }
  28071. /**
  28072. * Terminates the provided {@link Firestore} instance.
  28073. *
  28074. * After calling `terminate()` only the `clearIndexedDbPersistence()` function
  28075. * may be used. Any other function will throw a `FirestoreError`.
  28076. *
  28077. * To restart after termination, create a new instance of FirebaseFirestore with
  28078. * {@link (getFirestore:1)}.
  28079. *
  28080. * Termination does not cancel any pending writes, and any promises that are
  28081. * awaiting a response from the server will not be resolved. If you have
  28082. * persistence enabled, the next time you start this instance, it will resume
  28083. * sending these writes to the server.
  28084. *
  28085. * Note: Under normal circumstances, calling `terminate()` is not required. This
  28086. * function is useful only when you want to force this instance to release all
  28087. * of its resources or in combination with `clearIndexedDbPersistence()` to
  28088. * ensure that all local state is destroyed between test runs.
  28089. *
  28090. * @returns A `Promise` that is resolved when the instance has been successfully
  28091. * terminated.
  28092. */
  28093. function terminate(firestore) {
  28094. app._removeServiceInstance(firestore.app, 'firestore', firestore._databaseId.database);
  28095. return firestore._delete();
  28096. }
  28097. /**
  28098. * Loads a Firestore bundle into the local cache.
  28099. *
  28100. * @param firestore - The {@link Firestore} instance to load bundles for.
  28101. * @param bundleData - An object representing the bundle to be loaded. Valid
  28102. * objects are `ArrayBuffer`, `ReadableStream<Uint8Array>` or `string`.
  28103. *
  28104. * @returns A `LoadBundleTask` object, which notifies callers with progress
  28105. * updates, and completion or error events. It can be used as a
  28106. * `Promise<LoadBundleTaskProgress>`.
  28107. */
  28108. function loadBundle(firestore, bundleData) {
  28109. firestore = cast(firestore, Firestore);
  28110. const client = ensureFirestoreConfigured(firestore);
  28111. const resultTask = new LoadBundleTask();
  28112. firestoreClientLoadBundle(client, firestore._databaseId, bundleData, resultTask);
  28113. return resultTask;
  28114. }
  28115. /**
  28116. * Reads a Firestore {@link Query} from local cache, identified by the given
  28117. * name.
  28118. *
  28119. * The named queries are packaged into bundles on the server side (along
  28120. * with resulting documents), and loaded to local cache using `loadBundle`. Once
  28121. * in local cache, use this method to extract a {@link Query} by name.
  28122. *
  28123. * @param firestore - The {@link Firestore} instance to read the query from.
  28124. * @param name - The name of the query.
  28125. * @returns A `Promise` that is resolved with the Query or `null`.
  28126. */
  28127. function namedQuery(firestore, name) {
  28128. firestore = cast(firestore, Firestore);
  28129. const client = ensureFirestoreConfigured(firestore);
  28130. return firestoreClientGetNamedQuery(client, name).then(namedQuery => {
  28131. if (!namedQuery) {
  28132. return null;
  28133. }
  28134. return new Query(firestore, null, namedQuery.query);
  28135. });
  28136. }
  28137. function verifyNotInitialized(firestore) {
  28138. if (firestore._initialized || firestore._terminated) {
  28139. throw new FirestoreError(Code.FAILED_PRECONDITION, 'Firestore has already been started and persistence can no longer be ' +
  28140. 'enabled. You can only enable persistence before calling any other ' +
  28141. 'methods on a Firestore object.');
  28142. }
  28143. }
  28144. /**
  28145. * @license
  28146. * Copyright 2020 Google LLC
  28147. *
  28148. * Licensed under the Apache License, Version 2.0 (the "License");
  28149. * you may not use this file except in compliance with the License.
  28150. * You may obtain a copy of the License at
  28151. *
  28152. * http://www.apache.org/licenses/LICENSE-2.0
  28153. *
  28154. * Unless required by applicable law or agreed to in writing, software
  28155. * distributed under the License is distributed on an "AS IS" BASIS,
  28156. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28157. * See the License for the specific language governing permissions and
  28158. * limitations under the License.
  28159. */
  28160. function registerFirestore(variant, useFetchStreams = true) {
  28161. setSDKVersion(app.SDK_VERSION);
  28162. app._registerComponent(new component.Component('firestore', (container, { instanceIdentifier: databaseId, options: settings }) => {
  28163. const app = container.getProvider('app').getImmediate();
  28164. const firestoreInstance = new Firestore(new FirebaseAuthCredentialsProvider(container.getProvider('auth-internal')), new FirebaseAppCheckTokenProvider(container.getProvider('app-check-internal')), databaseIdFromApp(app, databaseId), app);
  28165. settings = Object.assign({ useFetchStreams }, settings);
  28166. firestoreInstance._setSettings(settings);
  28167. return firestoreInstance;
  28168. }, 'PUBLIC').setMultipleInstances(true));
  28169. app.registerVersion(name, version$1, variant);
  28170. // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
  28171. app.registerVersion(name, version$1, 'cjs2017');
  28172. }
  28173. /**
  28174. * @license
  28175. * Copyright 2023 Google LLC
  28176. *
  28177. * Licensed under the Apache License, Version 2.0 (the "License");
  28178. * you may not use this file except in compliance with the License.
  28179. * You may obtain a copy of the License at
  28180. *
  28181. * http://www.apache.org/licenses/LICENSE-2.0
  28182. *
  28183. * Unless required by applicable law or agreed to in writing, software
  28184. * distributed under the License is distributed on an "AS IS" BASIS,
  28185. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28186. * See the License for the specific language governing permissions and
  28187. * limitations under the License.
  28188. */
  28189. /**
  28190. * Concrete implementation of the Aggregate type.
  28191. */
  28192. class AggregateImpl {
  28193. constructor(alias, aggregateType, fieldPath) {
  28194. this.alias = alias;
  28195. this.aggregateType = aggregateType;
  28196. this.fieldPath = fieldPath;
  28197. }
  28198. }
  28199. /**
  28200. * @license
  28201. * Copyright 2022 Google LLC
  28202. *
  28203. * Licensed under the Apache License, Version 2.0 (the "License");
  28204. * you may not use this file except in compliance with the License.
  28205. * You may obtain a copy of the License at
  28206. *
  28207. * http://www.apache.org/licenses/LICENSE-2.0
  28208. *
  28209. * Unless required by applicable law or agreed to in writing, software
  28210. * distributed under the License is distributed on an "AS IS" BASIS,
  28211. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28212. * See the License for the specific language governing permissions and
  28213. * limitations under the License.
  28214. */
  28215. /**
  28216. * Represents an aggregation that can be performed by Firestore.
  28217. */
  28218. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  28219. class AggregateField {
  28220. /**
  28221. * Create a new AggregateField<T>
  28222. * @param _aggregateType Specifies the type of aggregation operation to perform.
  28223. * @param _internalFieldPath Optionally specifies the field that is aggregated.
  28224. * @internal
  28225. */
  28226. constructor(
  28227. // TODO (sum/avg) make aggregateType public when the feature is supported
  28228. _aggregateType = 'count', _internalFieldPath) {
  28229. this._aggregateType = _aggregateType;
  28230. this._internalFieldPath = _internalFieldPath;
  28231. /** A type string to uniquely identify instances of this class. */
  28232. this.type = 'AggregateField';
  28233. }
  28234. }
  28235. /**
  28236. * The results of executing an aggregation query.
  28237. */
  28238. class AggregateQuerySnapshot {
  28239. /** @hideconstructor */
  28240. constructor(query, _userDataWriter, _data) {
  28241. this._userDataWriter = _userDataWriter;
  28242. this._data = _data;
  28243. /** A type string to uniquely identify instances of this class. */
  28244. this.type = 'AggregateQuerySnapshot';
  28245. this.query = query;
  28246. }
  28247. /**
  28248. * Returns the results of the aggregations performed over the underlying
  28249. * query.
  28250. *
  28251. * The keys of the returned object will be the same as those of the
  28252. * `AggregateSpec` object specified to the aggregation method, and the values
  28253. * will be the corresponding aggregation result.
  28254. *
  28255. * @returns The results of the aggregations performed over the underlying
  28256. * query.
  28257. */
  28258. data() {
  28259. return this._userDataWriter.convertObjectMap(this._data);
  28260. }
  28261. }
  28262. /**
  28263. * @license
  28264. * Copyright 2020 Google LLC
  28265. *
  28266. * Licensed under the Apache License, Version 2.0 (the "License");
  28267. * you may not use this file except in compliance with the License.
  28268. * You may obtain a copy of the License at
  28269. *
  28270. * http://www.apache.org/licenses/LICENSE-2.0
  28271. *
  28272. * Unless required by applicable law or agreed to in writing, software
  28273. * distributed under the License is distributed on an "AS IS" BASIS,
  28274. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28275. * See the License for the specific language governing permissions and
  28276. * limitations under the License.
  28277. */
  28278. /**
  28279. * An immutable object representing an array of bytes.
  28280. */
  28281. class Bytes {
  28282. /** @hideconstructor */
  28283. constructor(byteString) {
  28284. this._byteString = byteString;
  28285. }
  28286. /**
  28287. * Creates a new `Bytes` object from the given Base64 string, converting it to
  28288. * bytes.
  28289. *
  28290. * @param base64 - The Base64 string used to create the `Bytes` object.
  28291. */
  28292. static fromBase64String(base64) {
  28293. try {
  28294. return new Bytes(ByteString.fromBase64String(base64));
  28295. }
  28296. catch (e) {
  28297. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Failed to construct data from Base64 string: ' + e);
  28298. }
  28299. }
  28300. /**
  28301. * Creates a new `Bytes` object from the given Uint8Array.
  28302. *
  28303. * @param array - The Uint8Array used to create the `Bytes` object.
  28304. */
  28305. static fromUint8Array(array) {
  28306. return new Bytes(ByteString.fromUint8Array(array));
  28307. }
  28308. /**
  28309. * Returns the underlying bytes as a Base64-encoded string.
  28310. *
  28311. * @returns The Base64-encoded string created from the `Bytes` object.
  28312. */
  28313. toBase64() {
  28314. return this._byteString.toBase64();
  28315. }
  28316. /**
  28317. * Returns the underlying bytes in a new `Uint8Array`.
  28318. *
  28319. * @returns The Uint8Array created from the `Bytes` object.
  28320. */
  28321. toUint8Array() {
  28322. return this._byteString.toUint8Array();
  28323. }
  28324. /**
  28325. * Returns a string representation of the `Bytes` object.
  28326. *
  28327. * @returns A string representation of the `Bytes` object.
  28328. */
  28329. toString() {
  28330. return 'Bytes(base64: ' + this.toBase64() + ')';
  28331. }
  28332. /**
  28333. * Returns true if this `Bytes` object is equal to the provided one.
  28334. *
  28335. * @param other - The `Bytes` object to compare against.
  28336. * @returns true if this `Bytes` object is equal to the provided one.
  28337. */
  28338. isEqual(other) {
  28339. return this._byteString.isEqual(other._byteString);
  28340. }
  28341. }
  28342. /**
  28343. * @license
  28344. * Copyright 2020 Google LLC
  28345. *
  28346. * Licensed under the Apache License, Version 2.0 (the "License");
  28347. * you may not use this file except in compliance with the License.
  28348. * You may obtain a copy of the License at
  28349. *
  28350. * http://www.apache.org/licenses/LICENSE-2.0
  28351. *
  28352. * Unless required by applicable law or agreed to in writing, software
  28353. * distributed under the License is distributed on an "AS IS" BASIS,
  28354. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28355. * See the License for the specific language governing permissions and
  28356. * limitations under the License.
  28357. */
  28358. /**
  28359. * A `FieldPath` refers to a field in a document. The path may consist of a
  28360. * single field name (referring to a top-level field in the document), or a
  28361. * list of field names (referring to a nested field in the document).
  28362. *
  28363. * Create a `FieldPath` by providing field names. If more than one field
  28364. * name is provided, the path will point to a nested field in a document.
  28365. */
  28366. class FieldPath {
  28367. /**
  28368. * Creates a `FieldPath` from the provided field names. If more than one field
  28369. * name is provided, the path will point to a nested field in a document.
  28370. *
  28371. * @param fieldNames - A list of field names.
  28372. */
  28373. constructor(...fieldNames) {
  28374. for (let i = 0; i < fieldNames.length; ++i) {
  28375. if (fieldNames[i].length === 0) {
  28376. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid field name at argument $(i + 1). ` +
  28377. 'Field names must not be empty.');
  28378. }
  28379. }
  28380. this._internalPath = new FieldPath$1(fieldNames);
  28381. }
  28382. /**
  28383. * Returns true if this `FieldPath` is equal to the provided one.
  28384. *
  28385. * @param other - The `FieldPath` to compare against.
  28386. * @returns true if this `FieldPath` is equal to the provided one.
  28387. */
  28388. isEqual(other) {
  28389. return this._internalPath.isEqual(other._internalPath);
  28390. }
  28391. }
  28392. /**
  28393. * Returns a special sentinel `FieldPath` to refer to the ID of a document.
  28394. * It can be used in queries to sort or filter by the document ID.
  28395. */
  28396. function documentId() {
  28397. return new FieldPath(DOCUMENT_KEY_NAME);
  28398. }
  28399. /**
  28400. * @license
  28401. * Copyright 2020 Google LLC
  28402. *
  28403. * Licensed under the Apache License, Version 2.0 (the "License");
  28404. * you may not use this file except in compliance with the License.
  28405. * You may obtain a copy of the License at
  28406. *
  28407. * http://www.apache.org/licenses/LICENSE-2.0
  28408. *
  28409. * Unless required by applicable law or agreed to in writing, software
  28410. * distributed under the License is distributed on an "AS IS" BASIS,
  28411. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28412. * See the License for the specific language governing permissions and
  28413. * limitations under the License.
  28414. */
  28415. /**
  28416. * Sentinel values that can be used when writing document fields with `set()`
  28417. * or `update()`.
  28418. */
  28419. class FieldValue {
  28420. /**
  28421. * @param _methodName - The public API endpoint that returns this class.
  28422. * @hideconstructor
  28423. */
  28424. constructor(_methodName) {
  28425. this._methodName = _methodName;
  28426. }
  28427. }
  28428. /**
  28429. * @license
  28430. * Copyright 2017 Google LLC
  28431. *
  28432. * Licensed under the Apache License, Version 2.0 (the "License");
  28433. * you may not use this file except in compliance with the License.
  28434. * You may obtain a copy of the License at
  28435. *
  28436. * http://www.apache.org/licenses/LICENSE-2.0
  28437. *
  28438. * Unless required by applicable law or agreed to in writing, software
  28439. * distributed under the License is distributed on an "AS IS" BASIS,
  28440. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28441. * See the License for the specific language governing permissions and
  28442. * limitations under the License.
  28443. */
  28444. /**
  28445. * An immutable object representing a geographic location in Firestore. The
  28446. * location is represented as latitude/longitude pair.
  28447. *
  28448. * Latitude values are in the range of [-90, 90].
  28449. * Longitude values are in the range of [-180, 180].
  28450. */
  28451. class GeoPoint {
  28452. /**
  28453. * Creates a new immutable `GeoPoint` object with the provided latitude and
  28454. * longitude values.
  28455. * @param latitude - The latitude as number between -90 and 90.
  28456. * @param longitude - The longitude as number between -180 and 180.
  28457. */
  28458. constructor(latitude, longitude) {
  28459. if (!isFinite(latitude) || latitude < -90 || latitude > 90) {
  28460. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Latitude must be a number between -90 and 90, but was: ' + latitude);
  28461. }
  28462. if (!isFinite(longitude) || longitude < -180 || longitude > 180) {
  28463. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Longitude must be a number between -180 and 180, but was: ' + longitude);
  28464. }
  28465. this._lat = latitude;
  28466. this._long = longitude;
  28467. }
  28468. /**
  28469. * The latitude of this `GeoPoint` instance.
  28470. */
  28471. get latitude() {
  28472. return this._lat;
  28473. }
  28474. /**
  28475. * The longitude of this `GeoPoint` instance.
  28476. */
  28477. get longitude() {
  28478. return this._long;
  28479. }
  28480. /**
  28481. * Returns true if this `GeoPoint` is equal to the provided one.
  28482. *
  28483. * @param other - The `GeoPoint` to compare against.
  28484. * @returns true if this `GeoPoint` is equal to the provided one.
  28485. */
  28486. isEqual(other) {
  28487. return this._lat === other._lat && this._long === other._long;
  28488. }
  28489. /** Returns a JSON-serializable representation of this GeoPoint. */
  28490. toJSON() {
  28491. return { latitude: this._lat, longitude: this._long };
  28492. }
  28493. /**
  28494. * Actually private to JS consumers of our API, so this function is prefixed
  28495. * with an underscore.
  28496. */
  28497. _compareTo(other) {
  28498. return (primitiveComparator(this._lat, other._lat) ||
  28499. primitiveComparator(this._long, other._long));
  28500. }
  28501. }
  28502. /**
  28503. * @license
  28504. * Copyright 2017 Google LLC
  28505. *
  28506. * Licensed under the Apache License, Version 2.0 (the "License");
  28507. * you may not use this file except in compliance with the License.
  28508. * You may obtain a copy of the License at
  28509. *
  28510. * http://www.apache.org/licenses/LICENSE-2.0
  28511. *
  28512. * Unless required by applicable law or agreed to in writing, software
  28513. * distributed under the License is distributed on an "AS IS" BASIS,
  28514. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28515. * See the License for the specific language governing permissions and
  28516. * limitations under the License.
  28517. */
  28518. const RESERVED_FIELD_REGEX = /^__.*__$/;
  28519. /** The result of parsing document data (e.g. for a setData call). */
  28520. class ParsedSetData {
  28521. constructor(data, fieldMask, fieldTransforms) {
  28522. this.data = data;
  28523. this.fieldMask = fieldMask;
  28524. this.fieldTransforms = fieldTransforms;
  28525. }
  28526. toMutation(key, precondition) {
  28527. if (this.fieldMask !== null) {
  28528. return new PatchMutation(key, this.data, this.fieldMask, precondition, this.fieldTransforms);
  28529. }
  28530. else {
  28531. return new SetMutation(key, this.data, precondition, this.fieldTransforms);
  28532. }
  28533. }
  28534. }
  28535. /** The result of parsing "update" data (i.e. for an updateData call). */
  28536. class ParsedUpdateData {
  28537. constructor(data,
  28538. // The fieldMask does not include document transforms.
  28539. fieldMask, fieldTransforms) {
  28540. this.data = data;
  28541. this.fieldMask = fieldMask;
  28542. this.fieldTransforms = fieldTransforms;
  28543. }
  28544. toMutation(key, precondition) {
  28545. return new PatchMutation(key, this.data, this.fieldMask, precondition, this.fieldTransforms);
  28546. }
  28547. }
  28548. function isWrite(dataSource) {
  28549. switch (dataSource) {
  28550. case 0 /* UserDataSource.Set */: // fall through
  28551. case 2 /* UserDataSource.MergeSet */: // fall through
  28552. case 1 /* UserDataSource.Update */:
  28553. return true;
  28554. case 3 /* UserDataSource.Argument */:
  28555. case 4 /* UserDataSource.ArrayArgument */:
  28556. return false;
  28557. default:
  28558. throw fail();
  28559. }
  28560. }
  28561. /** A "context" object passed around while parsing user data. */
  28562. class ParseContextImpl {
  28563. /**
  28564. * Initializes a ParseContext with the given source and path.
  28565. *
  28566. * @param settings - The settings for the parser.
  28567. * @param databaseId - The database ID of the Firestore instance.
  28568. * @param serializer - The serializer to use to generate the Value proto.
  28569. * @param ignoreUndefinedProperties - Whether to ignore undefined properties
  28570. * rather than throw.
  28571. * @param fieldTransforms - A mutable list of field transforms encountered
  28572. * while parsing the data.
  28573. * @param fieldMask - A mutable list of field paths encountered while parsing
  28574. * the data.
  28575. *
  28576. * TODO(b/34871131): We don't support array paths right now, so path can be
  28577. * null to indicate the context represents any location within an array (in
  28578. * which case certain features will not work and errors will be somewhat
  28579. * compromised).
  28580. */
  28581. constructor(settings, databaseId, serializer, ignoreUndefinedProperties, fieldTransforms, fieldMask) {
  28582. this.settings = settings;
  28583. this.databaseId = databaseId;
  28584. this.serializer = serializer;
  28585. this.ignoreUndefinedProperties = ignoreUndefinedProperties;
  28586. // Minor hack: If fieldTransforms is undefined, we assume this is an
  28587. // external call and we need to validate the entire path.
  28588. if (fieldTransforms === undefined) {
  28589. this.validatePath();
  28590. }
  28591. this.fieldTransforms = fieldTransforms || [];
  28592. this.fieldMask = fieldMask || [];
  28593. }
  28594. get path() {
  28595. return this.settings.path;
  28596. }
  28597. get dataSource() {
  28598. return this.settings.dataSource;
  28599. }
  28600. /** Returns a new context with the specified settings overwritten. */
  28601. contextWith(configuration) {
  28602. return new ParseContextImpl(Object.assign(Object.assign({}, this.settings), configuration), this.databaseId, this.serializer, this.ignoreUndefinedProperties, this.fieldTransforms, this.fieldMask);
  28603. }
  28604. childContextForField(field) {
  28605. var _a;
  28606. const childPath = (_a = this.path) === null || _a === void 0 ? void 0 : _a.child(field);
  28607. const context = this.contextWith({ path: childPath, arrayElement: false });
  28608. context.validatePathSegment(field);
  28609. return context;
  28610. }
  28611. childContextForFieldPath(field) {
  28612. var _a;
  28613. const childPath = (_a = this.path) === null || _a === void 0 ? void 0 : _a.child(field);
  28614. const context = this.contextWith({ path: childPath, arrayElement: false });
  28615. context.validatePath();
  28616. return context;
  28617. }
  28618. childContextForArray(index) {
  28619. // TODO(b/34871131): We don't support array paths right now; so make path
  28620. // undefined.
  28621. return this.contextWith({ path: undefined, arrayElement: true });
  28622. }
  28623. createError(reason) {
  28624. return createError(reason, this.settings.methodName, this.settings.hasConverter || false, this.path, this.settings.targetDoc);
  28625. }
  28626. /** Returns 'true' if 'fieldPath' was traversed when creating this context. */
  28627. contains(fieldPath) {
  28628. return (this.fieldMask.find(field => fieldPath.isPrefixOf(field)) !== undefined ||
  28629. this.fieldTransforms.find(transform => fieldPath.isPrefixOf(transform.field)) !== undefined);
  28630. }
  28631. validatePath() {
  28632. // TODO(b/34871131): Remove null check once we have proper paths for fields
  28633. // within arrays.
  28634. if (!this.path) {
  28635. return;
  28636. }
  28637. for (let i = 0; i < this.path.length; i++) {
  28638. this.validatePathSegment(this.path.get(i));
  28639. }
  28640. }
  28641. validatePathSegment(segment) {
  28642. if (segment.length === 0) {
  28643. throw this.createError('Document fields must not be empty');
  28644. }
  28645. if (isWrite(this.dataSource) && RESERVED_FIELD_REGEX.test(segment)) {
  28646. throw this.createError('Document fields cannot begin and end with "__"');
  28647. }
  28648. }
  28649. }
  28650. /**
  28651. * Helper for parsing raw user input (provided via the API) into internal model
  28652. * classes.
  28653. */
  28654. class UserDataReader {
  28655. constructor(databaseId, ignoreUndefinedProperties, serializer) {
  28656. this.databaseId = databaseId;
  28657. this.ignoreUndefinedProperties = ignoreUndefinedProperties;
  28658. this.serializer = serializer || newSerializer(databaseId);
  28659. }
  28660. /** Creates a new top-level parse context. */
  28661. createContext(dataSource, methodName, targetDoc, hasConverter = false) {
  28662. return new ParseContextImpl({
  28663. dataSource,
  28664. methodName,
  28665. targetDoc,
  28666. path: FieldPath$1.emptyPath(),
  28667. arrayElement: false,
  28668. hasConverter
  28669. }, this.databaseId, this.serializer, this.ignoreUndefinedProperties);
  28670. }
  28671. }
  28672. function newUserDataReader(firestore) {
  28673. const settings = firestore._freezeSettings();
  28674. const serializer = newSerializer(firestore._databaseId);
  28675. return new UserDataReader(firestore._databaseId, !!settings.ignoreUndefinedProperties, serializer);
  28676. }
  28677. /** Parse document data from a set() call. */
  28678. function parseSetData(userDataReader, methodName, targetDoc, input, hasConverter, options = {}) {
  28679. const context = userDataReader.createContext(options.merge || options.mergeFields
  28680. ? 2 /* UserDataSource.MergeSet */
  28681. : 0 /* UserDataSource.Set */, methodName, targetDoc, hasConverter);
  28682. validatePlainObject('Data must be an object, but it was:', context, input);
  28683. const updateData = parseObject(input, context);
  28684. let fieldMask;
  28685. let fieldTransforms;
  28686. if (options.merge) {
  28687. fieldMask = new FieldMask(context.fieldMask);
  28688. fieldTransforms = context.fieldTransforms;
  28689. }
  28690. else if (options.mergeFields) {
  28691. const validatedFieldPaths = [];
  28692. for (const stringOrFieldPath of options.mergeFields) {
  28693. const fieldPath = fieldPathFromArgument$1(methodName, stringOrFieldPath, targetDoc);
  28694. if (!context.contains(fieldPath)) {
  28695. throw new FirestoreError(Code.INVALID_ARGUMENT, `Field '${fieldPath}' is specified in your field mask but missing from your input data.`);
  28696. }
  28697. if (!fieldMaskContains(validatedFieldPaths, fieldPath)) {
  28698. validatedFieldPaths.push(fieldPath);
  28699. }
  28700. }
  28701. fieldMask = new FieldMask(validatedFieldPaths);
  28702. fieldTransforms = context.fieldTransforms.filter(transform => fieldMask.covers(transform.field));
  28703. }
  28704. else {
  28705. fieldMask = null;
  28706. fieldTransforms = context.fieldTransforms;
  28707. }
  28708. return new ParsedSetData(new ObjectValue(updateData), fieldMask, fieldTransforms);
  28709. }
  28710. class DeleteFieldValueImpl extends FieldValue {
  28711. _toFieldTransform(context) {
  28712. if (context.dataSource === 2 /* UserDataSource.MergeSet */) {
  28713. // No transform to add for a delete, but we need to add it to our
  28714. // fieldMask so it gets deleted.
  28715. context.fieldMask.push(context.path);
  28716. }
  28717. else if (context.dataSource === 1 /* UserDataSource.Update */) {
  28718. throw context.createError(`${this._methodName}() can only appear at the top level ` +
  28719. 'of your update data');
  28720. }
  28721. else {
  28722. // We shouldn't encounter delete sentinels for queries or non-merge set() calls.
  28723. throw context.createError(`${this._methodName}() cannot be used with set() unless you pass ` +
  28724. '{merge:true}');
  28725. }
  28726. return null;
  28727. }
  28728. isEqual(other) {
  28729. return other instanceof DeleteFieldValueImpl;
  28730. }
  28731. }
  28732. /**
  28733. * Creates a child context for parsing SerializableFieldValues.
  28734. *
  28735. * This is different than calling `ParseContext.contextWith` because it keeps
  28736. * the fieldTransforms and fieldMask separate.
  28737. *
  28738. * The created context has its `dataSource` set to `UserDataSource.Argument`.
  28739. * Although these values are used with writes, any elements in these FieldValues
  28740. * are not considered writes since they cannot contain any FieldValue sentinels,
  28741. * etc.
  28742. *
  28743. * @param fieldValue - The sentinel FieldValue for which to create a child
  28744. * context.
  28745. * @param context - The parent context.
  28746. * @param arrayElement - Whether or not the FieldValue has an array.
  28747. */
  28748. function createSentinelChildContext(fieldValue, context, arrayElement) {
  28749. return new ParseContextImpl({
  28750. dataSource: 3 /* UserDataSource.Argument */,
  28751. targetDoc: context.settings.targetDoc,
  28752. methodName: fieldValue._methodName,
  28753. arrayElement
  28754. }, context.databaseId, context.serializer, context.ignoreUndefinedProperties);
  28755. }
  28756. class ServerTimestampFieldValueImpl extends FieldValue {
  28757. _toFieldTransform(context) {
  28758. return new FieldTransform(context.path, new ServerTimestampTransform());
  28759. }
  28760. isEqual(other) {
  28761. return other instanceof ServerTimestampFieldValueImpl;
  28762. }
  28763. }
  28764. class ArrayUnionFieldValueImpl extends FieldValue {
  28765. constructor(methodName, _elements) {
  28766. super(methodName);
  28767. this._elements = _elements;
  28768. }
  28769. _toFieldTransform(context) {
  28770. const parseContext = createSentinelChildContext(this, context,
  28771. /*array=*/ true);
  28772. const parsedElements = this._elements.map(element => parseData(element, parseContext));
  28773. const arrayUnion = new ArrayUnionTransformOperation(parsedElements);
  28774. return new FieldTransform(context.path, arrayUnion);
  28775. }
  28776. isEqual(other) {
  28777. // TODO(mrschmidt): Implement isEquals
  28778. return this === other;
  28779. }
  28780. }
  28781. class ArrayRemoveFieldValueImpl extends FieldValue {
  28782. constructor(methodName, _elements) {
  28783. super(methodName);
  28784. this._elements = _elements;
  28785. }
  28786. _toFieldTransform(context) {
  28787. const parseContext = createSentinelChildContext(this, context,
  28788. /*array=*/ true);
  28789. const parsedElements = this._elements.map(element => parseData(element, parseContext));
  28790. const arrayUnion = new ArrayRemoveTransformOperation(parsedElements);
  28791. return new FieldTransform(context.path, arrayUnion);
  28792. }
  28793. isEqual(other) {
  28794. // TODO(mrschmidt): Implement isEquals
  28795. return this === other;
  28796. }
  28797. }
  28798. class NumericIncrementFieldValueImpl extends FieldValue {
  28799. constructor(methodName, _operand) {
  28800. super(methodName);
  28801. this._operand = _operand;
  28802. }
  28803. _toFieldTransform(context) {
  28804. const numericIncrement = new NumericIncrementTransformOperation(context.serializer, toNumber(context.serializer, this._operand));
  28805. return new FieldTransform(context.path, numericIncrement);
  28806. }
  28807. isEqual(other) {
  28808. // TODO(mrschmidt): Implement isEquals
  28809. return this === other;
  28810. }
  28811. }
  28812. /** Parse update data from an update() call. */
  28813. function parseUpdateData(userDataReader, methodName, targetDoc, input) {
  28814. const context = userDataReader.createContext(1 /* UserDataSource.Update */, methodName, targetDoc);
  28815. validatePlainObject('Data must be an object, but it was:', context, input);
  28816. const fieldMaskPaths = [];
  28817. const updateData = ObjectValue.empty();
  28818. forEach(input, (key, value) => {
  28819. const path = fieldPathFromDotSeparatedString(methodName, key, targetDoc);
  28820. // For Compat types, we have to "extract" the underlying types before
  28821. // performing validation.
  28822. value = util.getModularInstance(value);
  28823. const childContext = context.childContextForFieldPath(path);
  28824. if (value instanceof DeleteFieldValueImpl) {
  28825. // Add it to the field mask, but don't add anything to updateData.
  28826. fieldMaskPaths.push(path);
  28827. }
  28828. else {
  28829. const parsedValue = parseData(value, childContext);
  28830. if (parsedValue != null) {
  28831. fieldMaskPaths.push(path);
  28832. updateData.set(path, parsedValue);
  28833. }
  28834. }
  28835. });
  28836. const mask = new FieldMask(fieldMaskPaths);
  28837. return new ParsedUpdateData(updateData, mask, context.fieldTransforms);
  28838. }
  28839. /** Parse update data from a list of field/value arguments. */
  28840. function parseUpdateVarargs(userDataReader, methodName, targetDoc, field, value, moreFieldsAndValues) {
  28841. const context = userDataReader.createContext(1 /* UserDataSource.Update */, methodName, targetDoc);
  28842. const keys = [fieldPathFromArgument$1(methodName, field, targetDoc)];
  28843. const values = [value];
  28844. if (moreFieldsAndValues.length % 2 !== 0) {
  28845. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${methodName}() needs to be called with an even number ` +
  28846. 'of arguments that alternate between field names and values.');
  28847. }
  28848. for (let i = 0; i < moreFieldsAndValues.length; i += 2) {
  28849. keys.push(fieldPathFromArgument$1(methodName, moreFieldsAndValues[i]));
  28850. values.push(moreFieldsAndValues[i + 1]);
  28851. }
  28852. const fieldMaskPaths = [];
  28853. const updateData = ObjectValue.empty();
  28854. // We iterate in reverse order to pick the last value for a field if the
  28855. // user specified the field multiple times.
  28856. for (let i = keys.length - 1; i >= 0; --i) {
  28857. if (!fieldMaskContains(fieldMaskPaths, keys[i])) {
  28858. const path = keys[i];
  28859. let value = values[i];
  28860. // For Compat types, we have to "extract" the underlying types before
  28861. // performing validation.
  28862. value = util.getModularInstance(value);
  28863. const childContext = context.childContextForFieldPath(path);
  28864. if (value instanceof DeleteFieldValueImpl) {
  28865. // Add it to the field mask, but don't add anything to updateData.
  28866. fieldMaskPaths.push(path);
  28867. }
  28868. else {
  28869. const parsedValue = parseData(value, childContext);
  28870. if (parsedValue != null) {
  28871. fieldMaskPaths.push(path);
  28872. updateData.set(path, parsedValue);
  28873. }
  28874. }
  28875. }
  28876. }
  28877. const mask = new FieldMask(fieldMaskPaths);
  28878. return new ParsedUpdateData(updateData, mask, context.fieldTransforms);
  28879. }
  28880. /**
  28881. * Parse a "query value" (e.g. value in a where filter or a value in a cursor
  28882. * bound).
  28883. *
  28884. * @param allowArrays - Whether the query value is an array that may directly
  28885. * contain additional arrays (e.g. the operand of an `in` query).
  28886. */
  28887. function parseQueryValue(userDataReader, methodName, input, allowArrays = false) {
  28888. const context = userDataReader.createContext(allowArrays ? 4 /* UserDataSource.ArrayArgument */ : 3 /* UserDataSource.Argument */, methodName);
  28889. const parsed = parseData(input, context);
  28890. return parsed;
  28891. }
  28892. /**
  28893. * Parses user data to Protobuf Values.
  28894. *
  28895. * @param input - Data to be parsed.
  28896. * @param context - A context object representing the current path being parsed,
  28897. * the source of the data being parsed, etc.
  28898. * @returns The parsed value, or null if the value was a FieldValue sentinel
  28899. * that should not be included in the resulting parsed data.
  28900. */
  28901. function parseData(input, context) {
  28902. // Unwrap the API type from the Compat SDK. This will return the API type
  28903. // from firestore-exp.
  28904. input = util.getModularInstance(input);
  28905. if (looksLikeJsonObject(input)) {
  28906. validatePlainObject('Unsupported field value:', context, input);
  28907. return parseObject(input, context);
  28908. }
  28909. else if (input instanceof FieldValue) {
  28910. // FieldValues usually parse into transforms (except deleteField())
  28911. // in which case we do not want to include this field in our parsed data
  28912. // (as doing so will overwrite the field directly prior to the transform
  28913. // trying to transform it). So we don't add this location to
  28914. // context.fieldMask and we return null as our parsing result.
  28915. parseSentinelFieldValue(input, context);
  28916. return null;
  28917. }
  28918. else if (input === undefined && context.ignoreUndefinedProperties) {
  28919. // If the input is undefined it can never participate in the fieldMask, so
  28920. // don't handle this below. If `ignoreUndefinedProperties` is false,
  28921. // `parseScalarValue` will reject an undefined value.
  28922. return null;
  28923. }
  28924. else {
  28925. // If context.path is null we are inside an array and we don't support
  28926. // field mask paths more granular than the top-level array.
  28927. if (context.path) {
  28928. context.fieldMask.push(context.path);
  28929. }
  28930. if (input instanceof Array) {
  28931. // TODO(b/34871131): Include the path containing the array in the error
  28932. // message.
  28933. // In the case of IN queries, the parsed data is an array (representing
  28934. // the set of values to be included for the IN query) that may directly
  28935. // contain additional arrays (each representing an individual field
  28936. // value), so we disable this validation.
  28937. if (context.settings.arrayElement &&
  28938. context.dataSource !== 4 /* UserDataSource.ArrayArgument */) {
  28939. throw context.createError('Nested arrays are not supported');
  28940. }
  28941. return parseArray(input, context);
  28942. }
  28943. else {
  28944. return parseScalarValue(input, context);
  28945. }
  28946. }
  28947. }
  28948. function parseObject(obj, context) {
  28949. const fields = {};
  28950. if (isEmpty(obj)) {
  28951. // If we encounter an empty object, we explicitly add it to the update
  28952. // mask to ensure that the server creates a map entry.
  28953. if (context.path && context.path.length > 0) {
  28954. context.fieldMask.push(context.path);
  28955. }
  28956. }
  28957. else {
  28958. forEach(obj, (key, val) => {
  28959. const parsedValue = parseData(val, context.childContextForField(key));
  28960. if (parsedValue != null) {
  28961. fields[key] = parsedValue;
  28962. }
  28963. });
  28964. }
  28965. return { mapValue: { fields } };
  28966. }
  28967. function parseArray(array, context) {
  28968. const values = [];
  28969. let entryIndex = 0;
  28970. for (const entry of array) {
  28971. let parsedEntry = parseData(entry, context.childContextForArray(entryIndex));
  28972. if (parsedEntry == null) {
  28973. // Just include nulls in the array for fields being replaced with a
  28974. // sentinel.
  28975. parsedEntry = { nullValue: 'NULL_VALUE' };
  28976. }
  28977. values.push(parsedEntry);
  28978. entryIndex++;
  28979. }
  28980. return { arrayValue: { values } };
  28981. }
  28982. /**
  28983. * "Parses" the provided FieldValueImpl, adding any necessary transforms to
  28984. * context.fieldTransforms.
  28985. */
  28986. function parseSentinelFieldValue(value, context) {
  28987. // Sentinels are only supported with writes, and not within arrays.
  28988. if (!isWrite(context.dataSource)) {
  28989. throw context.createError(`${value._methodName}() can only be used with update() and set()`);
  28990. }
  28991. if (!context.path) {
  28992. throw context.createError(`${value._methodName}() is not currently supported inside arrays`);
  28993. }
  28994. const fieldTransform = value._toFieldTransform(context);
  28995. if (fieldTransform) {
  28996. context.fieldTransforms.push(fieldTransform);
  28997. }
  28998. }
  28999. /**
  29000. * Helper to parse a scalar value (i.e. not an Object, Array, or FieldValue)
  29001. *
  29002. * @returns The parsed value
  29003. */
  29004. function parseScalarValue(value, context) {
  29005. value = util.getModularInstance(value);
  29006. if (value === null) {
  29007. return { nullValue: 'NULL_VALUE' };
  29008. }
  29009. else if (typeof value === 'number') {
  29010. return toNumber(context.serializer, value);
  29011. }
  29012. else if (typeof value === 'boolean') {
  29013. return { booleanValue: value };
  29014. }
  29015. else if (typeof value === 'string') {
  29016. return { stringValue: value };
  29017. }
  29018. else if (value instanceof Date) {
  29019. const timestamp = Timestamp.fromDate(value);
  29020. return {
  29021. timestampValue: toTimestamp(context.serializer, timestamp)
  29022. };
  29023. }
  29024. else if (value instanceof Timestamp) {
  29025. // Firestore backend truncates precision down to microseconds. To ensure
  29026. // offline mode works the same with regards to truncation, perform the
  29027. // truncation immediately without waiting for the backend to do that.
  29028. const timestamp = new Timestamp(value.seconds, Math.floor(value.nanoseconds / 1000) * 1000);
  29029. return {
  29030. timestampValue: toTimestamp(context.serializer, timestamp)
  29031. };
  29032. }
  29033. else if (value instanceof GeoPoint) {
  29034. return {
  29035. geoPointValue: {
  29036. latitude: value.latitude,
  29037. longitude: value.longitude
  29038. }
  29039. };
  29040. }
  29041. else if (value instanceof Bytes) {
  29042. return { bytesValue: toBytes(context.serializer, value._byteString) };
  29043. }
  29044. else if (value instanceof DocumentReference) {
  29045. const thisDb = context.databaseId;
  29046. const otherDb = value.firestore._databaseId;
  29047. if (!otherDb.isEqual(thisDb)) {
  29048. throw context.createError('Document reference is for database ' +
  29049. `${otherDb.projectId}/${otherDb.database} but should be ` +
  29050. `for database ${thisDb.projectId}/${thisDb.database}`);
  29051. }
  29052. return {
  29053. referenceValue: toResourceName(value.firestore._databaseId || context.databaseId, value._key.path)
  29054. };
  29055. }
  29056. else {
  29057. throw context.createError(`Unsupported field value: ${valueDescription(value)}`);
  29058. }
  29059. }
  29060. /**
  29061. * Checks whether an object looks like a JSON object that should be converted
  29062. * into a struct. Normal class/prototype instances are considered to look like
  29063. * JSON objects since they should be converted to a struct value. Arrays, Dates,
  29064. * GeoPoints, etc. are not considered to look like JSON objects since they map
  29065. * to specific FieldValue types other than ObjectValue.
  29066. */
  29067. function looksLikeJsonObject(input) {
  29068. return (typeof input === 'object' &&
  29069. input !== null &&
  29070. !(input instanceof Array) &&
  29071. !(input instanceof Date) &&
  29072. !(input instanceof Timestamp) &&
  29073. !(input instanceof GeoPoint) &&
  29074. !(input instanceof Bytes) &&
  29075. !(input instanceof DocumentReference) &&
  29076. !(input instanceof FieldValue));
  29077. }
  29078. function validatePlainObject(message, context, input) {
  29079. if (!looksLikeJsonObject(input) || !isPlainObject(input)) {
  29080. const description = valueDescription(input);
  29081. if (description === 'an object') {
  29082. // Massage the error if it was an object.
  29083. throw context.createError(message + ' a custom object');
  29084. }
  29085. else {
  29086. throw context.createError(message + ' ' + description);
  29087. }
  29088. }
  29089. }
  29090. /**
  29091. * Helper that calls fromDotSeparatedString() but wraps any error thrown.
  29092. */
  29093. function fieldPathFromArgument$1(methodName, path, targetDoc) {
  29094. // If required, replace the FieldPath Compat class with with the firestore-exp
  29095. // FieldPath.
  29096. path = util.getModularInstance(path);
  29097. if (path instanceof FieldPath) {
  29098. return path._internalPath;
  29099. }
  29100. else if (typeof path === 'string') {
  29101. return fieldPathFromDotSeparatedString(methodName, path);
  29102. }
  29103. else {
  29104. const message = 'Field path arguments must be of type string or ';
  29105. throw createError(message, methodName,
  29106. /* hasConverter= */ false,
  29107. /* path= */ undefined, targetDoc);
  29108. }
  29109. }
  29110. /**
  29111. * Matches any characters in a field path string that are reserved.
  29112. */
  29113. const FIELD_PATH_RESERVED = new RegExp('[~\\*/\\[\\]]');
  29114. /**
  29115. * Wraps fromDotSeparatedString with an error message about the method that
  29116. * was thrown.
  29117. * @param methodName - The publicly visible method name
  29118. * @param path - The dot-separated string form of a field path which will be
  29119. * split on dots.
  29120. * @param targetDoc - The document against which the field path will be
  29121. * evaluated.
  29122. */
  29123. function fieldPathFromDotSeparatedString(methodName, path, targetDoc) {
  29124. const found = path.search(FIELD_PATH_RESERVED);
  29125. if (found >= 0) {
  29126. throw createError(`Invalid field path (${path}). Paths must not contain ` +
  29127. `'~', '*', '/', '[', or ']'`, methodName,
  29128. /* hasConverter= */ false,
  29129. /* path= */ undefined, targetDoc);
  29130. }
  29131. try {
  29132. return new FieldPath(...path.split('.'))._internalPath;
  29133. }
  29134. catch (e) {
  29135. throw createError(`Invalid field path (${path}). Paths must not be empty, ` +
  29136. `begin with '.', end with '.', or contain '..'`, methodName,
  29137. /* hasConverter= */ false,
  29138. /* path= */ undefined, targetDoc);
  29139. }
  29140. }
  29141. function createError(reason, methodName, hasConverter, path, targetDoc) {
  29142. const hasPath = path && !path.isEmpty();
  29143. const hasDocument = targetDoc !== undefined;
  29144. let message = `Function ${methodName}() called with invalid data`;
  29145. if (hasConverter) {
  29146. message += ' (via `toFirestore()`)';
  29147. }
  29148. message += '. ';
  29149. let description = '';
  29150. if (hasPath || hasDocument) {
  29151. description += ' (found';
  29152. if (hasPath) {
  29153. description += ` in field ${path}`;
  29154. }
  29155. if (hasDocument) {
  29156. description += ` in document ${targetDoc}`;
  29157. }
  29158. description += ')';
  29159. }
  29160. return new FirestoreError(Code.INVALID_ARGUMENT, message + reason + description);
  29161. }
  29162. /** Checks `haystack` if FieldPath `needle` is present. Runs in O(n). */
  29163. function fieldMaskContains(haystack, needle) {
  29164. return haystack.some(v => v.isEqual(needle));
  29165. }
  29166. /**
  29167. * @license
  29168. * Copyright 2020 Google LLC
  29169. *
  29170. * Licensed under the Apache License, Version 2.0 (the "License");
  29171. * you may not use this file except in compliance with the License.
  29172. * You may obtain a copy of the License at
  29173. *
  29174. * http://www.apache.org/licenses/LICENSE-2.0
  29175. *
  29176. * Unless required by applicable law or agreed to in writing, software
  29177. * distributed under the License is distributed on an "AS IS" BASIS,
  29178. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29179. * See the License for the specific language governing permissions and
  29180. * limitations under the License.
  29181. */
  29182. /**
  29183. * A `DocumentSnapshot` contains data read from a document in your Firestore
  29184. * database. The data can be extracted with `.data()` or `.get(<field>)` to
  29185. * get a specific field.
  29186. *
  29187. * For a `DocumentSnapshot` that points to a non-existing document, any data
  29188. * access will return 'undefined'. You can use the `exists()` method to
  29189. * explicitly verify a document's existence.
  29190. */
  29191. class DocumentSnapshot$1 {
  29192. // Note: This class is stripped down version of the DocumentSnapshot in
  29193. // the legacy SDK. The changes are:
  29194. // - No support for SnapshotMetadata.
  29195. // - No support for SnapshotOptions.
  29196. /** @hideconstructor protected */
  29197. constructor(_firestore, _userDataWriter, _key, _document, _converter) {
  29198. this._firestore = _firestore;
  29199. this._userDataWriter = _userDataWriter;
  29200. this._key = _key;
  29201. this._document = _document;
  29202. this._converter = _converter;
  29203. }
  29204. /** Property of the `DocumentSnapshot` that provides the document's ID. */
  29205. get id() {
  29206. return this._key.path.lastSegment();
  29207. }
  29208. /**
  29209. * The `DocumentReference` for the document included in the `DocumentSnapshot`.
  29210. */
  29211. get ref() {
  29212. return new DocumentReference(this._firestore, this._converter, this._key);
  29213. }
  29214. /**
  29215. * Signals whether or not the document at the snapshot's location exists.
  29216. *
  29217. * @returns true if the document exists.
  29218. */
  29219. exists() {
  29220. return this._document !== null;
  29221. }
  29222. /**
  29223. * Retrieves all fields in the document as an `Object`. Returns `undefined` if
  29224. * the document doesn't exist.
  29225. *
  29226. * @returns An `Object` containing all fields in the document or `undefined`
  29227. * if the document doesn't exist.
  29228. */
  29229. data() {
  29230. if (!this._document) {
  29231. return undefined;
  29232. }
  29233. else if (this._converter) {
  29234. // We only want to use the converter and create a new DocumentSnapshot
  29235. // if a converter has been provided.
  29236. const snapshot = new QueryDocumentSnapshot$1(this._firestore, this._userDataWriter, this._key, this._document,
  29237. /* converter= */ null);
  29238. return this._converter.fromFirestore(snapshot);
  29239. }
  29240. else {
  29241. return this._userDataWriter.convertValue(this._document.data.value);
  29242. }
  29243. }
  29244. /**
  29245. * Retrieves the field specified by `fieldPath`. Returns `undefined` if the
  29246. * document or field doesn't exist.
  29247. *
  29248. * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific
  29249. * field.
  29250. * @returns The data at the specified field location or undefined if no such
  29251. * field exists in the document.
  29252. */
  29253. // We are using `any` here to avoid an explicit cast by our users.
  29254. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  29255. get(fieldPath) {
  29256. if (this._document) {
  29257. const value = this._document.data.field(fieldPathFromArgument('DocumentSnapshot.get', fieldPath));
  29258. if (value !== null) {
  29259. return this._userDataWriter.convertValue(value);
  29260. }
  29261. }
  29262. return undefined;
  29263. }
  29264. }
  29265. /**
  29266. * A `QueryDocumentSnapshot` contains data read from a document in your
  29267. * Firestore database as part of a query. The document is guaranteed to exist
  29268. * and its data can be extracted with `.data()` or `.get(<field>)` to get a
  29269. * specific field.
  29270. *
  29271. * A `QueryDocumentSnapshot` offers the same API surface as a
  29272. * `DocumentSnapshot`. Since query results contain only existing documents, the
  29273. * `exists` property will always be true and `data()` will never return
  29274. * 'undefined'.
  29275. */
  29276. class QueryDocumentSnapshot$1 extends DocumentSnapshot$1 {
  29277. /**
  29278. * Retrieves all fields in the document as an `Object`.
  29279. *
  29280. * @override
  29281. * @returns An `Object` containing all fields in the document.
  29282. */
  29283. data() {
  29284. return super.data();
  29285. }
  29286. }
  29287. /**
  29288. * Helper that calls `fromDotSeparatedString()` but wraps any error thrown.
  29289. */
  29290. function fieldPathFromArgument(methodName, arg) {
  29291. if (typeof arg === 'string') {
  29292. return fieldPathFromDotSeparatedString(methodName, arg);
  29293. }
  29294. else if (arg instanceof FieldPath) {
  29295. return arg._internalPath;
  29296. }
  29297. else {
  29298. return arg._delegate._internalPath;
  29299. }
  29300. }
  29301. /**
  29302. * @license
  29303. * Copyright 2020 Google LLC
  29304. *
  29305. * Licensed under the Apache License, Version 2.0 (the "License");
  29306. * you may not use this file except in compliance with the License.
  29307. * You may obtain a copy of the License at
  29308. *
  29309. * http://www.apache.org/licenses/LICENSE-2.0
  29310. *
  29311. * Unless required by applicable law or agreed to in writing, software
  29312. * distributed under the License is distributed on an "AS IS" BASIS,
  29313. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29314. * See the License for the specific language governing permissions and
  29315. * limitations under the License.
  29316. */
  29317. function validateHasExplicitOrderByForLimitToLast(query) {
  29318. if (query.limitType === "L" /* LimitType.Last */ &&
  29319. query.explicitOrderBy.length === 0) {
  29320. throw new FirestoreError(Code.UNIMPLEMENTED, 'limitToLast() queries require specifying at least one orderBy() clause');
  29321. }
  29322. }
  29323. /**
  29324. * An `AppliableConstraint` is an abstraction of a constraint that can be applied
  29325. * to a Firestore query.
  29326. */
  29327. class AppliableConstraint {
  29328. }
  29329. /**
  29330. * A `QueryConstraint` is used to narrow the set of documents returned by a
  29331. * Firestore query. `QueryConstraint`s are created by invoking {@link where},
  29332. * {@link orderBy}, {@link (startAt:1)}, {@link (startAfter:1)}, {@link
  29333. * (endBefore:1)}, {@link (endAt:1)}, {@link limit}, {@link limitToLast} and
  29334. * can then be passed to {@link (query:1)} to create a new query instance that
  29335. * also contains this `QueryConstraint`.
  29336. */
  29337. class QueryConstraint extends AppliableConstraint {
  29338. }
  29339. function query(query, queryConstraint, ...additionalQueryConstraints) {
  29340. let queryConstraints = [];
  29341. if (queryConstraint instanceof AppliableConstraint) {
  29342. queryConstraints.push(queryConstraint);
  29343. }
  29344. queryConstraints = queryConstraints.concat(additionalQueryConstraints);
  29345. validateQueryConstraintArray(queryConstraints);
  29346. for (const constraint of queryConstraints) {
  29347. query = constraint._apply(query);
  29348. }
  29349. return query;
  29350. }
  29351. /**
  29352. * A `QueryFieldFilterConstraint` is used to narrow the set of documents returned by
  29353. * a Firestore query by filtering on one or more document fields.
  29354. * `QueryFieldFilterConstraint`s are created by invoking {@link where} and can then
  29355. * be passed to {@link (query:1)} to create a new query instance that also contains
  29356. * this `QueryFieldFilterConstraint`.
  29357. */
  29358. class QueryFieldFilterConstraint extends QueryConstraint {
  29359. /**
  29360. * @internal
  29361. */
  29362. constructor(_field, _op, _value) {
  29363. super();
  29364. this._field = _field;
  29365. this._op = _op;
  29366. this._value = _value;
  29367. /** The type of this query constraint */
  29368. this.type = 'where';
  29369. }
  29370. static _create(_field, _op, _value) {
  29371. return new QueryFieldFilterConstraint(_field, _op, _value);
  29372. }
  29373. _apply(query) {
  29374. const filter = this._parse(query);
  29375. validateNewFieldFilter(query._query, filter);
  29376. return new Query(query.firestore, query.converter, queryWithAddedFilter(query._query, filter));
  29377. }
  29378. _parse(query) {
  29379. const reader = newUserDataReader(query.firestore);
  29380. const filter = newQueryFilter(query._query, 'where', reader, query.firestore._databaseId, this._field, this._op, this._value);
  29381. return filter;
  29382. }
  29383. }
  29384. /**
  29385. * Creates a {@link QueryFieldFilterConstraint} that enforces that documents
  29386. * must contain the specified field and that the value should satisfy the
  29387. * relation constraint provided.
  29388. *
  29389. * @param fieldPath - The path to compare
  29390. * @param opStr - The operation string (e.g "&lt;", "&lt;=", "==", "&lt;",
  29391. * "&lt;=", "!=").
  29392. * @param value - The value for comparison
  29393. * @returns The created {@link QueryFieldFilterConstraint}.
  29394. */
  29395. function where(fieldPath, opStr, value) {
  29396. const op = opStr;
  29397. const field = fieldPathFromArgument('where', fieldPath);
  29398. return QueryFieldFilterConstraint._create(field, op, value);
  29399. }
  29400. /**
  29401. * A `QueryCompositeFilterConstraint` is used to narrow the set of documents
  29402. * returned by a Firestore query by performing the logical OR or AND of multiple
  29403. * {@link QueryFieldFilterConstraint}s or {@link QueryCompositeFilterConstraint}s.
  29404. * `QueryCompositeFilterConstraint`s are created by invoking {@link or} or
  29405. * {@link and} and can then be passed to {@link (query:1)} to create a new query
  29406. * instance that also contains the `QueryCompositeFilterConstraint`.
  29407. */
  29408. class QueryCompositeFilterConstraint extends AppliableConstraint {
  29409. /**
  29410. * @internal
  29411. */
  29412. constructor(
  29413. /** The type of this query constraint */
  29414. type, _queryConstraints) {
  29415. super();
  29416. this.type = type;
  29417. this._queryConstraints = _queryConstraints;
  29418. }
  29419. static _create(type, _queryConstraints) {
  29420. return new QueryCompositeFilterConstraint(type, _queryConstraints);
  29421. }
  29422. _parse(query) {
  29423. const parsedFilters = this._queryConstraints
  29424. .map(queryConstraint => {
  29425. return queryConstraint._parse(query);
  29426. })
  29427. .filter(parsedFilter => parsedFilter.getFilters().length > 0);
  29428. if (parsedFilters.length === 1) {
  29429. return parsedFilters[0];
  29430. }
  29431. return CompositeFilter.create(parsedFilters, this._getOperator());
  29432. }
  29433. _apply(query) {
  29434. const parsedFilter = this._parse(query);
  29435. if (parsedFilter.getFilters().length === 0) {
  29436. // Return the existing query if not adding any more filters (e.g. an empty
  29437. // composite filter).
  29438. return query;
  29439. }
  29440. validateNewFilter(query._query, parsedFilter);
  29441. return new Query(query.firestore, query.converter, queryWithAddedFilter(query._query, parsedFilter));
  29442. }
  29443. _getQueryConstraints() {
  29444. return this._queryConstraints;
  29445. }
  29446. _getOperator() {
  29447. return this.type === 'and' ? "and" /* CompositeOperator.AND */ : "or" /* CompositeOperator.OR */;
  29448. }
  29449. }
  29450. /**
  29451. * Creates a new {@link QueryCompositeFilterConstraint} that is a disjunction of
  29452. * the given filter constraints. A disjunction filter includes a document if it
  29453. * satisfies any of the given filters.
  29454. *
  29455. * @param queryConstraints - Optional. The list of
  29456. * {@link QueryFilterConstraint}s to perform a disjunction for. These must be
  29457. * created with calls to {@link where}, {@link or}, or {@link and}.
  29458. * @returns The newly created {@link QueryCompositeFilterConstraint}.
  29459. */
  29460. function or(...queryConstraints) {
  29461. // Only support QueryFilterConstraints
  29462. queryConstraints.forEach(queryConstraint => validateQueryFilterConstraint('or', queryConstraint));
  29463. return QueryCompositeFilterConstraint._create("or" /* CompositeOperator.OR */, queryConstraints);
  29464. }
  29465. /**
  29466. * Creates a new {@link QueryCompositeFilterConstraint} that is a conjunction of
  29467. * the given filter constraints. A conjunction filter includes a document if it
  29468. * satisfies all of the given filters.
  29469. *
  29470. * @param queryConstraints - Optional. The list of
  29471. * {@link QueryFilterConstraint}s to perform a conjunction for. These must be
  29472. * created with calls to {@link where}, {@link or}, or {@link and}.
  29473. * @returns The newly created {@link QueryCompositeFilterConstraint}.
  29474. */
  29475. function and(...queryConstraints) {
  29476. // Only support QueryFilterConstraints
  29477. queryConstraints.forEach(queryConstraint => validateQueryFilterConstraint('and', queryConstraint));
  29478. return QueryCompositeFilterConstraint._create("and" /* CompositeOperator.AND */, queryConstraints);
  29479. }
  29480. /**
  29481. * A `QueryOrderByConstraint` is used to sort the set of documents returned by a
  29482. * Firestore query. `QueryOrderByConstraint`s are created by invoking
  29483. * {@link orderBy} and can then be passed to {@link (query:1)} to create a new query
  29484. * instance that also contains this `QueryOrderByConstraint`.
  29485. *
  29486. * Note: Documents that do not contain the orderBy field will not be present in
  29487. * the query result.
  29488. */
  29489. class QueryOrderByConstraint extends QueryConstraint {
  29490. /**
  29491. * @internal
  29492. */
  29493. constructor(_field, _direction) {
  29494. super();
  29495. this._field = _field;
  29496. this._direction = _direction;
  29497. /** The type of this query constraint */
  29498. this.type = 'orderBy';
  29499. }
  29500. static _create(_field, _direction) {
  29501. return new QueryOrderByConstraint(_field, _direction);
  29502. }
  29503. _apply(query) {
  29504. const orderBy = newQueryOrderBy(query._query, this._field, this._direction);
  29505. return new Query(query.firestore, query.converter, queryWithAddedOrderBy(query._query, orderBy));
  29506. }
  29507. }
  29508. /**
  29509. * Creates a {@link QueryOrderByConstraint} that sorts the query result by the
  29510. * specified field, optionally in descending order instead of ascending.
  29511. *
  29512. * Note: Documents that do not contain the specified field will not be present
  29513. * in the query result.
  29514. *
  29515. * @param fieldPath - The field to sort by.
  29516. * @param directionStr - Optional direction to sort by ('asc' or 'desc'). If
  29517. * not specified, order will be ascending.
  29518. * @returns The created {@link QueryOrderByConstraint}.
  29519. */
  29520. function orderBy(fieldPath, directionStr = 'asc') {
  29521. const direction = directionStr;
  29522. const path = fieldPathFromArgument('orderBy', fieldPath);
  29523. return QueryOrderByConstraint._create(path, direction);
  29524. }
  29525. /**
  29526. * A `QueryLimitConstraint` is used to limit the number of documents returned by
  29527. * a Firestore query.
  29528. * `QueryLimitConstraint`s are created by invoking {@link limit} or
  29529. * {@link limitToLast} and can then be passed to {@link (query:1)} to create a new
  29530. * query instance that also contains this `QueryLimitConstraint`.
  29531. */
  29532. class QueryLimitConstraint extends QueryConstraint {
  29533. /**
  29534. * @internal
  29535. */
  29536. constructor(
  29537. /** The type of this query constraint */
  29538. type, _limit, _limitType) {
  29539. super();
  29540. this.type = type;
  29541. this._limit = _limit;
  29542. this._limitType = _limitType;
  29543. }
  29544. static _create(type, _limit, _limitType) {
  29545. return new QueryLimitConstraint(type, _limit, _limitType);
  29546. }
  29547. _apply(query) {
  29548. return new Query(query.firestore, query.converter, queryWithLimit(query._query, this._limit, this._limitType));
  29549. }
  29550. }
  29551. /**
  29552. * Creates a {@link QueryLimitConstraint} that only returns the first matching
  29553. * documents.
  29554. *
  29555. * @param limit - The maximum number of items to return.
  29556. * @returns The created {@link QueryLimitConstraint}.
  29557. */
  29558. function limit(limit) {
  29559. validatePositiveNumber('limit', limit);
  29560. return QueryLimitConstraint._create('limit', limit, "F" /* LimitType.First */);
  29561. }
  29562. /**
  29563. * Creates a {@link QueryLimitConstraint} that only returns the last matching
  29564. * documents.
  29565. *
  29566. * You must specify at least one `orderBy` clause for `limitToLast` queries,
  29567. * otherwise an exception will be thrown during execution.
  29568. *
  29569. * @param limit - The maximum number of items to return.
  29570. * @returns The created {@link QueryLimitConstraint}.
  29571. */
  29572. function limitToLast(limit) {
  29573. validatePositiveNumber('limitToLast', limit);
  29574. return QueryLimitConstraint._create('limitToLast', limit, "L" /* LimitType.Last */);
  29575. }
  29576. /**
  29577. * A `QueryStartAtConstraint` is used to exclude documents from the start of a
  29578. * result set returned by a Firestore query.
  29579. * `QueryStartAtConstraint`s are created by invoking {@link (startAt:1)} or
  29580. * {@link (startAfter:1)} and can then be passed to {@link (query:1)} to create a
  29581. * new query instance that also contains this `QueryStartAtConstraint`.
  29582. */
  29583. class QueryStartAtConstraint extends QueryConstraint {
  29584. /**
  29585. * @internal
  29586. */
  29587. constructor(
  29588. /** The type of this query constraint */
  29589. type, _docOrFields, _inclusive) {
  29590. super();
  29591. this.type = type;
  29592. this._docOrFields = _docOrFields;
  29593. this._inclusive = _inclusive;
  29594. }
  29595. static _create(type, _docOrFields, _inclusive) {
  29596. return new QueryStartAtConstraint(type, _docOrFields, _inclusive);
  29597. }
  29598. _apply(query) {
  29599. const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
  29600. return new Query(query.firestore, query.converter, queryWithStartAt(query._query, bound));
  29601. }
  29602. }
  29603. function startAt(...docOrFields) {
  29604. return QueryStartAtConstraint._create('startAt', docOrFields,
  29605. /*inclusive=*/ true);
  29606. }
  29607. function startAfter(...docOrFields) {
  29608. return QueryStartAtConstraint._create('startAfter', docOrFields,
  29609. /*inclusive=*/ false);
  29610. }
  29611. /**
  29612. * A `QueryEndAtConstraint` is used to exclude documents from the end of a
  29613. * result set returned by a Firestore query.
  29614. * `QueryEndAtConstraint`s are created by invoking {@link (endAt:1)} or
  29615. * {@link (endBefore:1)} and can then be passed to {@link (query:1)} to create a new
  29616. * query instance that also contains this `QueryEndAtConstraint`.
  29617. */
  29618. class QueryEndAtConstraint extends QueryConstraint {
  29619. /**
  29620. * @internal
  29621. */
  29622. constructor(
  29623. /** The type of this query constraint */
  29624. type, _docOrFields, _inclusive) {
  29625. super();
  29626. this.type = type;
  29627. this._docOrFields = _docOrFields;
  29628. this._inclusive = _inclusive;
  29629. }
  29630. static _create(type, _docOrFields, _inclusive) {
  29631. return new QueryEndAtConstraint(type, _docOrFields, _inclusive);
  29632. }
  29633. _apply(query) {
  29634. const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
  29635. return new Query(query.firestore, query.converter, queryWithEndAt(query._query, bound));
  29636. }
  29637. }
  29638. function endBefore(...docOrFields) {
  29639. return QueryEndAtConstraint._create('endBefore', docOrFields,
  29640. /*inclusive=*/ false);
  29641. }
  29642. function endAt(...docOrFields) {
  29643. return QueryEndAtConstraint._create('endAt', docOrFields,
  29644. /*inclusive=*/ true);
  29645. }
  29646. /** Helper function to create a bound from a document or fields */
  29647. function newQueryBoundFromDocOrFields(query, methodName, docOrFields, inclusive) {
  29648. docOrFields[0] = util.getModularInstance(docOrFields[0]);
  29649. if (docOrFields[0] instanceof DocumentSnapshot$1) {
  29650. return newQueryBoundFromDocument(query._query, query.firestore._databaseId, methodName, docOrFields[0]._document, inclusive);
  29651. }
  29652. else {
  29653. const reader = newUserDataReader(query.firestore);
  29654. return newQueryBoundFromFields(query._query, query.firestore._databaseId, reader, methodName, docOrFields, inclusive);
  29655. }
  29656. }
  29657. function newQueryFilter(query, methodName, dataReader, databaseId, fieldPath, op, value) {
  29658. let fieldValue;
  29659. if (fieldPath.isKeyField()) {
  29660. if (op === "array-contains" /* Operator.ARRAY_CONTAINS */ || op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  29661. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid Query. You can't perform '${op}' queries on documentId().`);
  29662. }
  29663. else if (op === "in" /* Operator.IN */ || op === "not-in" /* Operator.NOT_IN */) {
  29664. validateDisjunctiveFilterElements(value, op);
  29665. const referenceList = [];
  29666. for (const arrayValue of value) {
  29667. referenceList.push(parseDocumentIdValue(databaseId, query, arrayValue));
  29668. }
  29669. fieldValue = { arrayValue: { values: referenceList } };
  29670. }
  29671. else {
  29672. fieldValue = parseDocumentIdValue(databaseId, query, value);
  29673. }
  29674. }
  29675. else {
  29676. if (op === "in" /* Operator.IN */ ||
  29677. op === "not-in" /* Operator.NOT_IN */ ||
  29678. op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  29679. validateDisjunctiveFilterElements(value, op);
  29680. }
  29681. fieldValue = parseQueryValue(dataReader, methodName, value,
  29682. /* allowArrays= */ op === "in" /* Operator.IN */ || op === "not-in" /* Operator.NOT_IN */);
  29683. }
  29684. const filter = FieldFilter.create(fieldPath, op, fieldValue);
  29685. return filter;
  29686. }
  29687. function newQueryOrderBy(query, fieldPath, direction) {
  29688. if (query.startAt !== null) {
  29689. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You must not call startAt() or startAfter() before ' +
  29690. 'calling orderBy().');
  29691. }
  29692. if (query.endAt !== null) {
  29693. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You must not call endAt() or endBefore() before ' +
  29694. 'calling orderBy().');
  29695. }
  29696. const orderBy = new OrderBy(fieldPath, direction);
  29697. validateNewOrderBy(query, orderBy);
  29698. return orderBy;
  29699. }
  29700. /**
  29701. * Create a `Bound` from a query and a document.
  29702. *
  29703. * Note that the `Bound` will always include the key of the document
  29704. * and so only the provided document will compare equal to the returned
  29705. * position.
  29706. *
  29707. * Will throw if the document does not contain all fields of the order by
  29708. * of the query or if any of the fields in the order by are an uncommitted
  29709. * server timestamp.
  29710. */
  29711. function newQueryBoundFromDocument(query, databaseId, methodName, doc, inclusive) {
  29712. if (!doc) {
  29713. throw new FirestoreError(Code.NOT_FOUND, `Can't use a DocumentSnapshot that doesn't exist for ` +
  29714. `${methodName}().`);
  29715. }
  29716. const components = [];
  29717. // Because people expect to continue/end a query at the exact document
  29718. // provided, we need to use the implicit sort order rather than the explicit
  29719. // sort order, because it's guaranteed to contain the document key. That way
  29720. // the position becomes unambiguous and the query continues/ends exactly at
  29721. // the provided document. Without the key (by using the explicit sort
  29722. // orders), multiple documents could match the position, yielding duplicate
  29723. // results.
  29724. for (const orderBy of queryOrderBy(query)) {
  29725. if (orderBy.field.isKeyField()) {
  29726. components.push(refValue(databaseId, doc.key));
  29727. }
  29728. else {
  29729. const value = doc.data.field(orderBy.field);
  29730. if (isServerTimestamp(value)) {
  29731. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You are trying to start or end a query using a ' +
  29732. 'document for which the field "' +
  29733. orderBy.field +
  29734. '" is an uncommitted server timestamp. (Since the value of ' +
  29735. 'this field is unknown, you cannot start/end a query with it.)');
  29736. }
  29737. else if (value !== null) {
  29738. components.push(value);
  29739. }
  29740. else {
  29741. const field = orderBy.field.canonicalString();
  29742. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. You are trying to start or end a query using a ` +
  29743. `document for which the field '${field}' (used as the ` +
  29744. `orderBy) does not exist.`);
  29745. }
  29746. }
  29747. }
  29748. return new Bound(components, inclusive);
  29749. }
  29750. /**
  29751. * Converts a list of field values to a `Bound` for the given query.
  29752. */
  29753. function newQueryBoundFromFields(query, databaseId, dataReader, methodName, values, inclusive) {
  29754. // Use explicit order by's because it has to match the query the user made
  29755. const orderBy = query.explicitOrderBy;
  29756. if (values.length > orderBy.length) {
  29757. throw new FirestoreError(Code.INVALID_ARGUMENT, `Too many arguments provided to ${methodName}(). ` +
  29758. `The number of arguments must be less than or equal to the ` +
  29759. `number of orderBy() clauses`);
  29760. }
  29761. const components = [];
  29762. for (let i = 0; i < values.length; i++) {
  29763. const rawValue = values[i];
  29764. const orderByComponent = orderBy[i];
  29765. if (orderByComponent.field.isKeyField()) {
  29766. if (typeof rawValue !== 'string') {
  29767. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. Expected a string for document ID in ` +
  29768. `${methodName}(), but got a ${typeof rawValue}`);
  29769. }
  29770. if (!isCollectionGroupQuery(query) && rawValue.indexOf('/') !== -1) {
  29771. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection and ordering by documentId(), ` +
  29772. `the value passed to ${methodName}() must be a plain document ID, but ` +
  29773. `'${rawValue}' contains a slash.`);
  29774. }
  29775. const path = query.path.child(ResourcePath.fromString(rawValue));
  29776. if (!DocumentKey.isDocumentKey(path)) {
  29777. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection group and ordering by ` +
  29778. `documentId(), the value passed to ${methodName}() must result in a ` +
  29779. `valid document path, but '${path}' is not because it contains an odd number ` +
  29780. `of segments.`);
  29781. }
  29782. const key = new DocumentKey(path);
  29783. components.push(refValue(databaseId, key));
  29784. }
  29785. else {
  29786. const wrapped = parseQueryValue(dataReader, methodName, rawValue);
  29787. components.push(wrapped);
  29788. }
  29789. }
  29790. return new Bound(components, inclusive);
  29791. }
  29792. /**
  29793. * Parses the given `documentIdValue` into a `ReferenceValue`, throwing
  29794. * appropriate errors if the value is anything other than a `DocumentReference`
  29795. * or `string`, or if the string is malformed.
  29796. */
  29797. function parseDocumentIdValue(databaseId, query, documentIdValue) {
  29798. documentIdValue = util.getModularInstance(documentIdValue);
  29799. if (typeof documentIdValue === 'string') {
  29800. if (documentIdValue === '') {
  29801. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. When querying with documentId(), you ' +
  29802. 'must provide a valid document ID, but it was an empty string.');
  29803. }
  29804. if (!isCollectionGroupQuery(query) && documentIdValue.indexOf('/') !== -1) {
  29805. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection by ` +
  29806. `documentId(), you must provide a plain document ID, but ` +
  29807. `'${documentIdValue}' contains a '/' character.`);
  29808. }
  29809. const path = query.path.child(ResourcePath.fromString(documentIdValue));
  29810. if (!DocumentKey.isDocumentKey(path)) {
  29811. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection group by ` +
  29812. `documentId(), the value provided must result in a valid document path, ` +
  29813. `but '${path}' is not because it has an odd number of segments (${path.length}).`);
  29814. }
  29815. return refValue(databaseId, new DocumentKey(path));
  29816. }
  29817. else if (documentIdValue instanceof DocumentReference) {
  29818. return refValue(databaseId, documentIdValue._key);
  29819. }
  29820. else {
  29821. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying with documentId(), you must provide a valid ` +
  29822. `string or a DocumentReference, but it was: ` +
  29823. `${valueDescription(documentIdValue)}.`);
  29824. }
  29825. }
  29826. /**
  29827. * Validates that the value passed into a disjunctive filter satisfies all
  29828. * array requirements.
  29829. */
  29830. function validateDisjunctiveFilterElements(value, operator) {
  29831. if (!Array.isArray(value) || value.length === 0) {
  29832. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid Query. A non-empty array is required for ' +
  29833. `'${operator.toString()}' filters.`);
  29834. }
  29835. }
  29836. /**
  29837. * Given an operator, returns the set of operators that cannot be used with it.
  29838. *
  29839. * This is not a comprehensive check, and this function should be removed in the
  29840. * long term. Validations should occur in the Firestore backend.
  29841. *
  29842. * Operators in a query must adhere to the following set of rules:
  29843. * 1. Only one inequality per query.
  29844. * 2. `NOT_IN` cannot be used with array, disjunctive, or `NOT_EQUAL` operators.
  29845. */
  29846. function conflictingOps(op) {
  29847. switch (op) {
  29848. case "!=" /* Operator.NOT_EQUAL */:
  29849. return ["!=" /* Operator.NOT_EQUAL */, "not-in" /* Operator.NOT_IN */];
  29850. case "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */:
  29851. case "in" /* Operator.IN */:
  29852. return ["not-in" /* Operator.NOT_IN */];
  29853. case "not-in" /* Operator.NOT_IN */:
  29854. return [
  29855. "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */,
  29856. "in" /* Operator.IN */,
  29857. "not-in" /* Operator.NOT_IN */,
  29858. "!=" /* Operator.NOT_EQUAL */
  29859. ];
  29860. default:
  29861. return [];
  29862. }
  29863. }
  29864. function validateNewFieldFilter(query, fieldFilter) {
  29865. if (fieldFilter.isInequality()) {
  29866. const existingInequality = getInequalityFilterField(query);
  29867. const newInequality = fieldFilter.field;
  29868. if (existingInequality !== null &&
  29869. !existingInequality.isEqual(newInequality)) {
  29870. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. All where filters with an inequality' +
  29871. ' (<, <=, !=, not-in, >, or >=) must be on the same field. But you have' +
  29872. ` inequality filters on '${existingInequality.toString()}'` +
  29873. ` and '${newInequality.toString()}'`);
  29874. }
  29875. const firstOrderByField = getFirstOrderByField(query);
  29876. if (firstOrderByField !== null) {
  29877. validateOrderByAndInequalityMatch(query, newInequality, firstOrderByField);
  29878. }
  29879. }
  29880. const conflictingOp = findOpInsideFilters(query.filters, conflictingOps(fieldFilter.op));
  29881. if (conflictingOp !== null) {
  29882. // Special case when it's a duplicate op to give a slightly clearer error message.
  29883. if (conflictingOp === fieldFilter.op) {
  29884. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You cannot use more than one ' +
  29885. `'${fieldFilter.op.toString()}' filter.`);
  29886. }
  29887. else {
  29888. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. You cannot use '${fieldFilter.op.toString()}' filters ` +
  29889. `with '${conflictingOp.toString()}' filters.`);
  29890. }
  29891. }
  29892. }
  29893. function validateNewFilter(query, filter) {
  29894. let testQuery = query;
  29895. const subFilters = filter.getFlattenedFilters();
  29896. for (const subFilter of subFilters) {
  29897. validateNewFieldFilter(testQuery, subFilter);
  29898. testQuery = queryWithAddedFilter(testQuery, subFilter);
  29899. }
  29900. }
  29901. // Checks if any of the provided filter operators are included in the given list of filters and
  29902. // returns the first one that is, or null if none are.
  29903. function findOpInsideFilters(filters, operators) {
  29904. for (const filter of filters) {
  29905. for (const fieldFilter of filter.getFlattenedFilters()) {
  29906. if (operators.indexOf(fieldFilter.op) >= 0) {
  29907. return fieldFilter.op;
  29908. }
  29909. }
  29910. }
  29911. return null;
  29912. }
  29913. function validateNewOrderBy(query, orderBy) {
  29914. if (getFirstOrderByField(query) === null) {
  29915. // This is the first order by. It must match any inequality.
  29916. const inequalityField = getInequalityFilterField(query);
  29917. if (inequalityField !== null) {
  29918. validateOrderByAndInequalityMatch(query, inequalityField, orderBy.field);
  29919. }
  29920. }
  29921. }
  29922. function validateOrderByAndInequalityMatch(baseQuery, inequality, orderBy) {
  29923. if (!orderBy.isEqual(inequality)) {
  29924. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. You have a where filter with an inequality ` +
  29925. `(<, <=, !=, not-in, >, or >=) on field '${inequality.toString()}' ` +
  29926. `and so you must also use '${inequality.toString()}' ` +
  29927. `as your first argument to orderBy(), but your first orderBy() ` +
  29928. `is on field '${orderBy.toString()}' instead.`);
  29929. }
  29930. }
  29931. function validateQueryFilterConstraint(functionName, queryConstraint) {
  29932. if (!(queryConstraint instanceof QueryFieldFilterConstraint) &&
  29933. !(queryConstraint instanceof QueryCompositeFilterConstraint)) {
  29934. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires AppliableConstraints created with a call to 'where(...)', 'or(...)', or 'and(...)'.`);
  29935. }
  29936. }
  29937. function validateQueryConstraintArray(queryConstraint) {
  29938. const compositeFilterCount = queryConstraint.filter(filter => filter instanceof QueryCompositeFilterConstraint).length;
  29939. const fieldFilterCount = queryConstraint.filter(filter => filter instanceof QueryFieldFilterConstraint).length;
  29940. if (compositeFilterCount > 1 ||
  29941. (compositeFilterCount > 0 && fieldFilterCount > 0)) {
  29942. throw new FirestoreError(Code.INVALID_ARGUMENT, 'InvalidQuery. When using composite filters, you cannot use ' +
  29943. 'more than one filter at the top level. Consider nesting the multiple ' +
  29944. 'filters within an `and(...)` statement. For example: ' +
  29945. 'change `query(query, where(...), or(...))` to ' +
  29946. '`query(query, and(where(...), or(...)))`.');
  29947. }
  29948. }
  29949. /**
  29950. * @license
  29951. * Copyright 2020 Google LLC
  29952. *
  29953. * Licensed under the Apache License, Version 2.0 (the "License");
  29954. * you may not use this file except in compliance with the License.
  29955. * You may obtain a copy of the License at
  29956. *
  29957. * http://www.apache.org/licenses/LICENSE-2.0
  29958. *
  29959. * Unless required by applicable law or agreed to in writing, software
  29960. * distributed under the License is distributed on an "AS IS" BASIS,
  29961. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29962. * See the License for the specific language governing permissions and
  29963. * limitations under the License.
  29964. */
  29965. /**
  29966. * Converts Firestore's internal types to the JavaScript types that we expose
  29967. * to the user.
  29968. *
  29969. * @internal
  29970. */
  29971. class AbstractUserDataWriter {
  29972. convertValue(value, serverTimestampBehavior = 'none') {
  29973. switch (typeOrder(value)) {
  29974. case 0 /* TypeOrder.NullValue */:
  29975. return null;
  29976. case 1 /* TypeOrder.BooleanValue */:
  29977. return value.booleanValue;
  29978. case 2 /* TypeOrder.NumberValue */:
  29979. return normalizeNumber(value.integerValue || value.doubleValue);
  29980. case 3 /* TypeOrder.TimestampValue */:
  29981. return this.convertTimestamp(value.timestampValue);
  29982. case 4 /* TypeOrder.ServerTimestampValue */:
  29983. return this.convertServerTimestamp(value, serverTimestampBehavior);
  29984. case 5 /* TypeOrder.StringValue */:
  29985. return value.stringValue;
  29986. case 6 /* TypeOrder.BlobValue */:
  29987. return this.convertBytes(normalizeByteString(value.bytesValue));
  29988. case 7 /* TypeOrder.RefValue */:
  29989. return this.convertReference(value.referenceValue);
  29990. case 8 /* TypeOrder.GeoPointValue */:
  29991. return this.convertGeoPoint(value.geoPointValue);
  29992. case 9 /* TypeOrder.ArrayValue */:
  29993. return this.convertArray(value.arrayValue, serverTimestampBehavior);
  29994. case 10 /* TypeOrder.ObjectValue */:
  29995. return this.convertObject(value.mapValue, serverTimestampBehavior);
  29996. default:
  29997. throw fail();
  29998. }
  29999. }
  30000. convertObject(mapValue, serverTimestampBehavior) {
  30001. return this.convertObjectMap(mapValue.fields, serverTimestampBehavior);
  30002. }
  30003. /**
  30004. * @internal
  30005. */
  30006. convertObjectMap(fields, serverTimestampBehavior = 'none') {
  30007. const result = {};
  30008. forEach(fields, (key, value) => {
  30009. result[key] = this.convertValue(value, serverTimestampBehavior);
  30010. });
  30011. return result;
  30012. }
  30013. convertGeoPoint(value) {
  30014. return new GeoPoint(normalizeNumber(value.latitude), normalizeNumber(value.longitude));
  30015. }
  30016. convertArray(arrayValue, serverTimestampBehavior) {
  30017. return (arrayValue.values || []).map(value => this.convertValue(value, serverTimestampBehavior));
  30018. }
  30019. convertServerTimestamp(value, serverTimestampBehavior) {
  30020. switch (serverTimestampBehavior) {
  30021. case 'previous':
  30022. const previousValue = getPreviousValue(value);
  30023. if (previousValue == null) {
  30024. return null;
  30025. }
  30026. return this.convertValue(previousValue, serverTimestampBehavior);
  30027. case 'estimate':
  30028. return this.convertTimestamp(getLocalWriteTime(value));
  30029. default:
  30030. return null;
  30031. }
  30032. }
  30033. convertTimestamp(value) {
  30034. const normalizedValue = normalizeTimestamp(value);
  30035. return new Timestamp(normalizedValue.seconds, normalizedValue.nanos);
  30036. }
  30037. convertDocumentKey(name, expectedDatabaseId) {
  30038. const resourcePath = ResourcePath.fromString(name);
  30039. hardAssert(isValidResourceName(resourcePath));
  30040. const databaseId = new DatabaseId(resourcePath.get(1), resourcePath.get(3));
  30041. const key = new DocumentKey(resourcePath.popFirst(5));
  30042. if (!databaseId.isEqual(expectedDatabaseId)) {
  30043. // TODO(b/64130202): Somehow support foreign references.
  30044. logError(`Document ${key} contains a document ` +
  30045. `reference within a different database (` +
  30046. `${databaseId.projectId}/${databaseId.database}) which is not ` +
  30047. `supported. It will be treated as a reference in the current ` +
  30048. `database (${expectedDatabaseId.projectId}/${expectedDatabaseId.database}) ` +
  30049. `instead.`);
  30050. }
  30051. return key;
  30052. }
  30053. }
  30054. /**
  30055. * @license
  30056. * Copyright 2020 Google LLC
  30057. *
  30058. * Licensed under the Apache License, Version 2.0 (the "License");
  30059. * you may not use this file except in compliance with the License.
  30060. * You may obtain a copy of the License at
  30061. *
  30062. * http://www.apache.org/licenses/LICENSE-2.0
  30063. *
  30064. * Unless required by applicable law or agreed to in writing, software
  30065. * distributed under the License is distributed on an "AS IS" BASIS,
  30066. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30067. * See the License for the specific language governing permissions and
  30068. * limitations under the License.
  30069. */
  30070. /**
  30071. * Converts custom model object of type T into `DocumentData` by applying the
  30072. * converter if it exists.
  30073. *
  30074. * This function is used when converting user objects to `DocumentData`
  30075. * because we want to provide the user with a more specific error message if
  30076. * their `set()` or fails due to invalid data originating from a `toFirestore()`
  30077. * call.
  30078. */
  30079. function applyFirestoreDataConverter(converter, value, options) {
  30080. let convertedValue;
  30081. if (converter) {
  30082. if (options && (options.merge || options.mergeFields)) {
  30083. // Cast to `any` in order to satisfy the union type constraint on
  30084. // toFirestore().
  30085. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  30086. convertedValue = converter.toFirestore(value, options);
  30087. }
  30088. else {
  30089. convertedValue = converter.toFirestore(value);
  30090. }
  30091. }
  30092. else {
  30093. convertedValue = value;
  30094. }
  30095. return convertedValue;
  30096. }
  30097. class LiteUserDataWriter extends AbstractUserDataWriter {
  30098. constructor(firestore) {
  30099. super();
  30100. this.firestore = firestore;
  30101. }
  30102. convertBytes(bytes) {
  30103. return new Bytes(bytes);
  30104. }
  30105. convertReference(name) {
  30106. const key = this.convertDocumentKey(name, this.firestore._databaseId);
  30107. return new DocumentReference(this.firestore, /* converter= */ null, key);
  30108. }
  30109. }
  30110. /**
  30111. * @license
  30112. * Copyright 2022 Google LLC
  30113. *
  30114. * Licensed under the Apache License, Version 2.0 (the "License");
  30115. * you may not use this file except in compliance with the License.
  30116. * You may obtain a copy of the License at
  30117. *
  30118. * http://www.apache.org/licenses/LICENSE-2.0
  30119. *
  30120. * Unless required by applicable law or agreed to in writing, software
  30121. * distributed under the License is distributed on an "AS IS" BASIS,
  30122. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30123. * See the License for the specific language governing permissions and
  30124. * limitations under the License.
  30125. */
  30126. /**
  30127. * Create an AggregateField object that can be used to compute the sum of
  30128. * a specified field over a range of documents in the result set of a query.
  30129. * @param field Specifies the field to sum across the result set.
  30130. * @internal TODO (sum/avg) remove when public
  30131. */
  30132. function sum(field) {
  30133. return new AggregateField('sum', fieldPathFromArgument$1('sum', field));
  30134. }
  30135. /**
  30136. * Create an AggregateField object that can be used to compute the average of
  30137. * a specified field over a range of documents in the result set of a query.
  30138. * @param field Specifies the field to average across the result set.
  30139. * @internal TODO (sum/avg) remove when public
  30140. */
  30141. function average(field) {
  30142. return new AggregateField('avg', fieldPathFromArgument$1('average', field));
  30143. }
  30144. /**
  30145. * Create an AggregateField object that can be used to compute the count of
  30146. * documents in the result set of a query.
  30147. * @internal TODO (sum/avg) remove when public
  30148. */
  30149. function count() {
  30150. return new AggregateField('count');
  30151. }
  30152. /**
  30153. * Compares two 'AggregateField` instances for equality.
  30154. *
  30155. * @param left Compare this AggregateField to the `right`.
  30156. * @param right Compare this AggregateField to the `left`.
  30157. * @internal TODO (sum/avg) remove when public
  30158. */
  30159. function aggregateFieldEqual(left, right) {
  30160. var _a, _b;
  30161. return (left instanceof AggregateField &&
  30162. right instanceof AggregateField &&
  30163. left._aggregateType === right._aggregateType &&
  30164. ((_a = left._internalFieldPath) === null || _a === void 0 ? void 0 : _a.canonicalString()) ===
  30165. ((_b = right._internalFieldPath) === null || _b === void 0 ? void 0 : _b.canonicalString()));
  30166. }
  30167. /**
  30168. * Compares two `AggregateQuerySnapshot` instances for equality.
  30169. *
  30170. * Two `AggregateQuerySnapshot` instances are considered "equal" if they have
  30171. * underlying queries that compare equal, and the same data.
  30172. *
  30173. * @param left - The first `AggregateQuerySnapshot` to compare.
  30174. * @param right - The second `AggregateQuerySnapshot` to compare.
  30175. *
  30176. * @returns `true` if the objects are "equal", as defined above, or `false`
  30177. * otherwise.
  30178. */
  30179. function aggregateQuerySnapshotEqual(left, right) {
  30180. return (queryEqual(left.query, right.query) && util.deepEqual(left.data(), right.data()));
  30181. }
  30182. /**
  30183. * @license
  30184. * Copyright 2017 Google LLC
  30185. *
  30186. * Licensed under the Apache License, Version 2.0 (the "License");
  30187. * you may not use this file except in compliance with the License.
  30188. * You may obtain a copy of the License at
  30189. *
  30190. * http://www.apache.org/licenses/LICENSE-2.0
  30191. *
  30192. * Unless required by applicable law or agreed to in writing, software
  30193. * distributed under the License is distributed on an "AS IS" BASIS,
  30194. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30195. * See the License for the specific language governing permissions and
  30196. * limitations under the License.
  30197. */
  30198. function isPartialObserver(obj) {
  30199. return implementsAnyMethods(obj, ['next', 'error', 'complete']);
  30200. }
  30201. /**
  30202. * Returns true if obj is an object and contains at least one of the specified
  30203. * methods.
  30204. */
  30205. function implementsAnyMethods(obj, methods) {
  30206. if (typeof obj !== 'object' || obj === null) {
  30207. return false;
  30208. }
  30209. const object = obj;
  30210. for (const method of methods) {
  30211. if (method in object && typeof object[method] === 'function') {
  30212. return true;
  30213. }
  30214. }
  30215. return false;
  30216. }
  30217. /**
  30218. * @license
  30219. * Copyright 2020 Google LLC
  30220. *
  30221. * Licensed under the Apache License, Version 2.0 (the "License");
  30222. * you may not use this file except in compliance with the License.
  30223. * You may obtain a copy of the License at
  30224. *
  30225. * http://www.apache.org/licenses/LICENSE-2.0
  30226. *
  30227. * Unless required by applicable law or agreed to in writing, software
  30228. * distributed under the License is distributed on an "AS IS" BASIS,
  30229. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30230. * See the License for the specific language governing permissions and
  30231. * limitations under the License.
  30232. */
  30233. /**
  30234. * Metadata about a snapshot, describing the state of the snapshot.
  30235. */
  30236. class SnapshotMetadata {
  30237. /** @hideconstructor */
  30238. constructor(hasPendingWrites, fromCache) {
  30239. this.hasPendingWrites = hasPendingWrites;
  30240. this.fromCache = fromCache;
  30241. }
  30242. /**
  30243. * Returns true if this `SnapshotMetadata` is equal to the provided one.
  30244. *
  30245. * @param other - The `SnapshotMetadata` to compare against.
  30246. * @returns true if this `SnapshotMetadata` is equal to the provided one.
  30247. */
  30248. isEqual(other) {
  30249. return (this.hasPendingWrites === other.hasPendingWrites &&
  30250. this.fromCache === other.fromCache);
  30251. }
  30252. }
  30253. /**
  30254. * A `DocumentSnapshot` contains data read from a document in your Firestore
  30255. * database. The data can be extracted with `.data()` or `.get(<field>)` to
  30256. * get a specific field.
  30257. *
  30258. * For a `DocumentSnapshot` that points to a non-existing document, any data
  30259. * access will return 'undefined'. You can use the `exists()` method to
  30260. * explicitly verify a document's existence.
  30261. */
  30262. class DocumentSnapshot extends DocumentSnapshot$1 {
  30263. /** @hideconstructor protected */
  30264. constructor(_firestore, userDataWriter, key, document, metadata, converter) {
  30265. super(_firestore, userDataWriter, key, document, converter);
  30266. this._firestore = _firestore;
  30267. this._firestoreImpl = _firestore;
  30268. this.metadata = metadata;
  30269. }
  30270. /**
  30271. * Returns whether or not the data exists. True if the document exists.
  30272. */
  30273. exists() {
  30274. return super.exists();
  30275. }
  30276. /**
  30277. * Retrieves all fields in the document as an `Object`. Returns `undefined` if
  30278. * the document doesn't exist.
  30279. *
  30280. * By default, `serverTimestamp()` values that have not yet been
  30281. * set to their final value will be returned as `null`. You can override
  30282. * this by passing an options object.
  30283. *
  30284. * @param options - An options object to configure how data is retrieved from
  30285. * the snapshot (for example the desired behavior for server timestamps that
  30286. * have not yet been set to their final value).
  30287. * @returns An `Object` containing all fields in the document or `undefined` if
  30288. * the document doesn't exist.
  30289. */
  30290. data(options = {}) {
  30291. if (!this._document) {
  30292. return undefined;
  30293. }
  30294. else if (this._converter) {
  30295. // We only want to use the converter and create a new DocumentSnapshot
  30296. // if a converter has been provided.
  30297. const snapshot = new QueryDocumentSnapshot(this._firestore, this._userDataWriter, this._key, this._document, this.metadata,
  30298. /* converter= */ null);
  30299. return this._converter.fromFirestore(snapshot, options);
  30300. }
  30301. else {
  30302. return this._userDataWriter.convertValue(this._document.data.value, options.serverTimestamps);
  30303. }
  30304. }
  30305. /**
  30306. * Retrieves the field specified by `fieldPath`. Returns `undefined` if the
  30307. * document or field doesn't exist.
  30308. *
  30309. * By default, a `serverTimestamp()` that has not yet been set to
  30310. * its final value will be returned as `null`. You can override this by
  30311. * passing an options object.
  30312. *
  30313. * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific
  30314. * field.
  30315. * @param options - An options object to configure how the field is retrieved
  30316. * from the snapshot (for example the desired behavior for server timestamps
  30317. * that have not yet been set to their final value).
  30318. * @returns The data at the specified field location or undefined if no such
  30319. * field exists in the document.
  30320. */
  30321. // We are using `any` here to avoid an explicit cast by our users.
  30322. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  30323. get(fieldPath, options = {}) {
  30324. if (this._document) {
  30325. const value = this._document.data.field(fieldPathFromArgument('DocumentSnapshot.get', fieldPath));
  30326. if (value !== null) {
  30327. return this._userDataWriter.convertValue(value, options.serverTimestamps);
  30328. }
  30329. }
  30330. return undefined;
  30331. }
  30332. }
  30333. /**
  30334. * A `QueryDocumentSnapshot` contains data read from a document in your
  30335. * Firestore database as part of a query. The document is guaranteed to exist
  30336. * and its data can be extracted with `.data()` or `.get(<field>)` to get a
  30337. * specific field.
  30338. *
  30339. * A `QueryDocumentSnapshot` offers the same API surface as a
  30340. * `DocumentSnapshot`. Since query results contain only existing documents, the
  30341. * `exists` property will always be true and `data()` will never return
  30342. * 'undefined'.
  30343. */
  30344. class QueryDocumentSnapshot extends DocumentSnapshot {
  30345. /**
  30346. * Retrieves all fields in the document as an `Object`.
  30347. *
  30348. * By default, `serverTimestamp()` values that have not yet been
  30349. * set to their final value will be returned as `null`. You can override
  30350. * this by passing an options object.
  30351. *
  30352. * @override
  30353. * @param options - An options object to configure how data is retrieved from
  30354. * the snapshot (for example the desired behavior for server timestamps that
  30355. * have not yet been set to their final value).
  30356. * @returns An `Object` containing all fields in the document.
  30357. */
  30358. data(options = {}) {
  30359. return super.data(options);
  30360. }
  30361. }
  30362. /**
  30363. * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects
  30364. * representing the results of a query. The documents can be accessed as an
  30365. * array via the `docs` property or enumerated using the `forEach` method. The
  30366. * number of documents can be determined via the `empty` and `size`
  30367. * properties.
  30368. */
  30369. class QuerySnapshot {
  30370. /** @hideconstructor */
  30371. constructor(_firestore, _userDataWriter, query, _snapshot) {
  30372. this._firestore = _firestore;
  30373. this._userDataWriter = _userDataWriter;
  30374. this._snapshot = _snapshot;
  30375. this.metadata = new SnapshotMetadata(_snapshot.hasPendingWrites, _snapshot.fromCache);
  30376. this.query = query;
  30377. }
  30378. /** An array of all the documents in the `QuerySnapshot`. */
  30379. get docs() {
  30380. const result = [];
  30381. this.forEach(doc => result.push(doc));
  30382. return result;
  30383. }
  30384. /** The number of documents in the `QuerySnapshot`. */
  30385. get size() {
  30386. return this._snapshot.docs.size;
  30387. }
  30388. /** True if there are no documents in the `QuerySnapshot`. */
  30389. get empty() {
  30390. return this.size === 0;
  30391. }
  30392. /**
  30393. * Enumerates all of the documents in the `QuerySnapshot`.
  30394. *
  30395. * @param callback - A callback to be called with a `QueryDocumentSnapshot` for
  30396. * each document in the snapshot.
  30397. * @param thisArg - The `this` binding for the callback.
  30398. */
  30399. forEach(callback, thisArg) {
  30400. this._snapshot.docs.forEach(doc => {
  30401. callback.call(thisArg, new QueryDocumentSnapshot(this._firestore, this._userDataWriter, doc.key, doc, new SnapshotMetadata(this._snapshot.mutatedKeys.has(doc.key), this._snapshot.fromCache), this.query.converter));
  30402. });
  30403. }
  30404. /**
  30405. * Returns an array of the documents changes since the last snapshot. If this
  30406. * is the first snapshot, all documents will be in the list as 'added'
  30407. * changes.
  30408. *
  30409. * @param options - `SnapshotListenOptions` that control whether metadata-only
  30410. * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger
  30411. * snapshot events.
  30412. */
  30413. docChanges(options = {}) {
  30414. const includeMetadataChanges = !!options.includeMetadataChanges;
  30415. if (includeMetadataChanges && this._snapshot.excludesMetadataChanges) {
  30416. throw new FirestoreError(Code.INVALID_ARGUMENT, 'To include metadata changes with your document changes, you must ' +
  30417. 'also pass { includeMetadataChanges:true } to onSnapshot().');
  30418. }
  30419. if (!this._cachedChanges ||
  30420. this._cachedChangesIncludeMetadataChanges !== includeMetadataChanges) {
  30421. this._cachedChanges = changesFromSnapshot(this, includeMetadataChanges);
  30422. this._cachedChangesIncludeMetadataChanges = includeMetadataChanges;
  30423. }
  30424. return this._cachedChanges;
  30425. }
  30426. }
  30427. /** Calculates the array of `DocumentChange`s for a given `ViewSnapshot`. */
  30428. function changesFromSnapshot(querySnapshot, includeMetadataChanges) {
  30429. if (querySnapshot._snapshot.oldDocs.isEmpty()) {
  30430. let index = 0;
  30431. return querySnapshot._snapshot.docChanges.map(change => {
  30432. const doc = new QueryDocumentSnapshot(querySnapshot._firestore, querySnapshot._userDataWriter, change.doc.key, change.doc, new SnapshotMetadata(querySnapshot._snapshot.mutatedKeys.has(change.doc.key), querySnapshot._snapshot.fromCache), querySnapshot.query.converter);
  30433. change.doc;
  30434. return {
  30435. type: 'added',
  30436. doc,
  30437. oldIndex: -1,
  30438. newIndex: index++
  30439. };
  30440. });
  30441. }
  30442. else {
  30443. // A `DocumentSet` that is updated incrementally as changes are applied to use
  30444. // to lookup the index of a document.
  30445. let indexTracker = querySnapshot._snapshot.oldDocs;
  30446. return querySnapshot._snapshot.docChanges
  30447. .filter(change => includeMetadataChanges || change.type !== 3 /* ChangeType.Metadata */)
  30448. .map(change => {
  30449. const doc = new QueryDocumentSnapshot(querySnapshot._firestore, querySnapshot._userDataWriter, change.doc.key, change.doc, new SnapshotMetadata(querySnapshot._snapshot.mutatedKeys.has(change.doc.key), querySnapshot._snapshot.fromCache), querySnapshot.query.converter);
  30450. let oldIndex = -1;
  30451. let newIndex = -1;
  30452. if (change.type !== 0 /* ChangeType.Added */) {
  30453. oldIndex = indexTracker.indexOf(change.doc.key);
  30454. indexTracker = indexTracker.delete(change.doc.key);
  30455. }
  30456. if (change.type !== 1 /* ChangeType.Removed */) {
  30457. indexTracker = indexTracker.add(change.doc);
  30458. newIndex = indexTracker.indexOf(change.doc.key);
  30459. }
  30460. return {
  30461. type: resultChangeType(change.type),
  30462. doc,
  30463. oldIndex,
  30464. newIndex
  30465. };
  30466. });
  30467. }
  30468. }
  30469. function resultChangeType(type) {
  30470. switch (type) {
  30471. case 0 /* ChangeType.Added */:
  30472. return 'added';
  30473. case 2 /* ChangeType.Modified */:
  30474. case 3 /* ChangeType.Metadata */:
  30475. return 'modified';
  30476. case 1 /* ChangeType.Removed */:
  30477. return 'removed';
  30478. default:
  30479. return fail();
  30480. }
  30481. }
  30482. // TODO(firestoreexp): Add tests for snapshotEqual with different snapshot
  30483. // metadata
  30484. /**
  30485. * Returns true if the provided snapshots are equal.
  30486. *
  30487. * @param left - A snapshot to compare.
  30488. * @param right - A snapshot to compare.
  30489. * @returns true if the snapshots are equal.
  30490. */
  30491. function snapshotEqual(left, right) {
  30492. if (left instanceof DocumentSnapshot && right instanceof DocumentSnapshot) {
  30493. return (left._firestore === right._firestore &&
  30494. left._key.isEqual(right._key) &&
  30495. (left._document === null
  30496. ? right._document === null
  30497. : left._document.isEqual(right._document)) &&
  30498. left._converter === right._converter);
  30499. }
  30500. else if (left instanceof QuerySnapshot && right instanceof QuerySnapshot) {
  30501. return (left._firestore === right._firestore &&
  30502. queryEqual(left.query, right.query) &&
  30503. left.metadata.isEqual(right.metadata) &&
  30504. left._snapshot.isEqual(right._snapshot));
  30505. }
  30506. return false;
  30507. }
  30508. /**
  30509. * @license
  30510. * Copyright 2020 Google LLC
  30511. *
  30512. * Licensed under the Apache License, Version 2.0 (the "License");
  30513. * you may not use this file except in compliance with the License.
  30514. * You may obtain a copy of the License at
  30515. *
  30516. * http://www.apache.org/licenses/LICENSE-2.0
  30517. *
  30518. * Unless required by applicable law or agreed to in writing, software
  30519. * distributed under the License is distributed on an "AS IS" BASIS,
  30520. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30521. * See the License for the specific language governing permissions and
  30522. * limitations under the License.
  30523. */
  30524. /**
  30525. * Reads the document referred to by this `DocumentReference`.
  30526. *
  30527. * Note: `getDoc()` attempts to provide up-to-date data when possible by waiting
  30528. * for data from the server, but it may return cached data or fail if you are
  30529. * offline and the server cannot be reached. To specify this behavior, invoke
  30530. * {@link getDocFromCache} or {@link getDocFromServer}.
  30531. *
  30532. * @param reference - The reference of the document to fetch.
  30533. * @returns A Promise resolved with a `DocumentSnapshot` containing the
  30534. * current document contents.
  30535. */
  30536. function getDoc(reference) {
  30537. reference = cast(reference, DocumentReference);
  30538. const firestore = cast(reference.firestore, Firestore);
  30539. const client = ensureFirestoreConfigured(firestore);
  30540. return firestoreClientGetDocumentViaSnapshotListener(client, reference._key).then(snapshot => convertToDocSnapshot(firestore, reference, snapshot));
  30541. }
  30542. class ExpUserDataWriter extends AbstractUserDataWriter {
  30543. constructor(firestore) {
  30544. super();
  30545. this.firestore = firestore;
  30546. }
  30547. convertBytes(bytes) {
  30548. return new Bytes(bytes);
  30549. }
  30550. convertReference(name) {
  30551. const key = this.convertDocumentKey(name, this.firestore._databaseId);
  30552. return new DocumentReference(this.firestore, /* converter= */ null, key);
  30553. }
  30554. }
  30555. /**
  30556. * Reads the document referred to by this `DocumentReference` from cache.
  30557. * Returns an error if the document is not currently cached.
  30558. *
  30559. * @returns A `Promise` resolved with a `DocumentSnapshot` containing the
  30560. * current document contents.
  30561. */
  30562. function getDocFromCache(reference) {
  30563. reference = cast(reference, DocumentReference);
  30564. const firestore = cast(reference.firestore, Firestore);
  30565. const client = ensureFirestoreConfigured(firestore);
  30566. const userDataWriter = new ExpUserDataWriter(firestore);
  30567. return firestoreClientGetDocumentFromLocalCache(client, reference._key).then(doc => new DocumentSnapshot(firestore, userDataWriter, reference._key, doc, new SnapshotMetadata(doc !== null && doc.hasLocalMutations,
  30568. /* fromCache= */ true), reference.converter));
  30569. }
  30570. /**
  30571. * Reads the document referred to by this `DocumentReference` from the server.
  30572. * Returns an error if the network is not available.
  30573. *
  30574. * @returns A `Promise` resolved with a `DocumentSnapshot` containing the
  30575. * current document contents.
  30576. */
  30577. function getDocFromServer(reference) {
  30578. reference = cast(reference, DocumentReference);
  30579. const firestore = cast(reference.firestore, Firestore);
  30580. const client = ensureFirestoreConfigured(firestore);
  30581. return firestoreClientGetDocumentViaSnapshotListener(client, reference._key, {
  30582. source: 'server'
  30583. }).then(snapshot => convertToDocSnapshot(firestore, reference, snapshot));
  30584. }
  30585. /**
  30586. * Executes the query and returns the results as a `QuerySnapshot`.
  30587. *
  30588. * Note: `getDocs()` attempts to provide up-to-date data when possible by
  30589. * waiting for data from the server, but it may return cached data or fail if
  30590. * you are offline and the server cannot be reached. To specify this behavior,
  30591. * invoke {@link getDocsFromCache} or {@link getDocsFromServer}.
  30592. *
  30593. * @returns A `Promise` that will be resolved with the results of the query.
  30594. */
  30595. function getDocs(query) {
  30596. query = cast(query, Query);
  30597. const firestore = cast(query.firestore, Firestore);
  30598. const client = ensureFirestoreConfigured(firestore);
  30599. const userDataWriter = new ExpUserDataWriter(firestore);
  30600. validateHasExplicitOrderByForLimitToLast(query._query);
  30601. return firestoreClientGetDocumentsViaSnapshotListener(client, query._query).then(snapshot => new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  30602. }
  30603. /**
  30604. * Executes the query and returns the results as a `QuerySnapshot` from cache.
  30605. * Returns an empty result set if no documents matching the query are currently
  30606. * cached.
  30607. *
  30608. * @returns A `Promise` that will be resolved with the results of the query.
  30609. */
  30610. function getDocsFromCache(query) {
  30611. query = cast(query, Query);
  30612. const firestore = cast(query.firestore, Firestore);
  30613. const client = ensureFirestoreConfigured(firestore);
  30614. const userDataWriter = new ExpUserDataWriter(firestore);
  30615. return firestoreClientGetDocumentsFromLocalCache(client, query._query).then(snapshot => new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  30616. }
  30617. /**
  30618. * Executes the query and returns the results as a `QuerySnapshot` from the
  30619. * server. Returns an error if the network is not available.
  30620. *
  30621. * @returns A `Promise` that will be resolved with the results of the query.
  30622. */
  30623. function getDocsFromServer(query) {
  30624. query = cast(query, Query);
  30625. const firestore = cast(query.firestore, Firestore);
  30626. const client = ensureFirestoreConfigured(firestore);
  30627. const userDataWriter = new ExpUserDataWriter(firestore);
  30628. return firestoreClientGetDocumentsViaSnapshotListener(client, query._query, {
  30629. source: 'server'
  30630. }).then(snapshot => new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  30631. }
  30632. function setDoc(reference, data, options) {
  30633. reference = cast(reference, DocumentReference);
  30634. const firestore = cast(reference.firestore, Firestore);
  30635. const convertedValue = applyFirestoreDataConverter(reference.converter, data, options);
  30636. const dataReader = newUserDataReader(firestore);
  30637. const parsed = parseSetData(dataReader, 'setDoc', reference._key, convertedValue, reference.converter !== null, options);
  30638. const mutation = parsed.toMutation(reference._key, Precondition.none());
  30639. return executeWrite(firestore, [mutation]);
  30640. }
  30641. function updateDoc(reference, fieldOrUpdateData, value, ...moreFieldsAndValues) {
  30642. reference = cast(reference, DocumentReference);
  30643. const firestore = cast(reference.firestore, Firestore);
  30644. const dataReader = newUserDataReader(firestore);
  30645. // For Compat types, we have to "extract" the underlying types before
  30646. // performing validation.
  30647. fieldOrUpdateData = util.getModularInstance(fieldOrUpdateData);
  30648. let parsed;
  30649. if (typeof fieldOrUpdateData === 'string' ||
  30650. fieldOrUpdateData instanceof FieldPath) {
  30651. parsed = parseUpdateVarargs(dataReader, 'updateDoc', reference._key, fieldOrUpdateData, value, moreFieldsAndValues);
  30652. }
  30653. else {
  30654. parsed = parseUpdateData(dataReader, 'updateDoc', reference._key, fieldOrUpdateData);
  30655. }
  30656. const mutation = parsed.toMutation(reference._key, Precondition.exists(true));
  30657. return executeWrite(firestore, [mutation]);
  30658. }
  30659. /**
  30660. * Deletes the document referred to by the specified `DocumentReference`.
  30661. *
  30662. * @param reference - A reference to the document to delete.
  30663. * @returns A Promise resolved once the document has been successfully
  30664. * deleted from the backend (note that it won't resolve while you're offline).
  30665. */
  30666. function deleteDoc(reference) {
  30667. const firestore = cast(reference.firestore, Firestore);
  30668. const mutations = [new DeleteMutation(reference._key, Precondition.none())];
  30669. return executeWrite(firestore, mutations);
  30670. }
  30671. /**
  30672. * Add a new document to specified `CollectionReference` with the given data,
  30673. * assigning it a document ID automatically.
  30674. *
  30675. * @param reference - A reference to the collection to add this document to.
  30676. * @param data - An Object containing the data for the new document.
  30677. * @returns A `Promise` resolved with a `DocumentReference` pointing to the
  30678. * newly created document after it has been written to the backend (Note that it
  30679. * won't resolve while you're offline).
  30680. */
  30681. function addDoc(reference, data) {
  30682. const firestore = cast(reference.firestore, Firestore);
  30683. const docRef = doc(reference);
  30684. const convertedValue = applyFirestoreDataConverter(reference.converter, data);
  30685. const dataReader = newUserDataReader(reference.firestore);
  30686. const parsed = parseSetData(dataReader, 'addDoc', docRef._key, convertedValue, reference.converter !== null, {});
  30687. const mutation = parsed.toMutation(docRef._key, Precondition.exists(false));
  30688. return executeWrite(firestore, [mutation]).then(() => docRef);
  30689. }
  30690. function onSnapshot(reference, ...args) {
  30691. var _a, _b, _c;
  30692. reference = util.getModularInstance(reference);
  30693. let options = {
  30694. includeMetadataChanges: false
  30695. };
  30696. let currArg = 0;
  30697. if (typeof args[currArg] === 'object' && !isPartialObserver(args[currArg])) {
  30698. options = args[currArg];
  30699. currArg++;
  30700. }
  30701. const internalOptions = {
  30702. includeMetadataChanges: options.includeMetadataChanges
  30703. };
  30704. if (isPartialObserver(args[currArg])) {
  30705. const userObserver = args[currArg];
  30706. args[currArg] = (_a = userObserver.next) === null || _a === void 0 ? void 0 : _a.bind(userObserver);
  30707. args[currArg + 1] = (_b = userObserver.error) === null || _b === void 0 ? void 0 : _b.bind(userObserver);
  30708. args[currArg + 2] = (_c = userObserver.complete) === null || _c === void 0 ? void 0 : _c.bind(userObserver);
  30709. }
  30710. let observer;
  30711. let firestore;
  30712. let internalQuery;
  30713. if (reference instanceof DocumentReference) {
  30714. firestore = cast(reference.firestore, Firestore);
  30715. internalQuery = newQueryForPath(reference._key.path);
  30716. observer = {
  30717. next: snapshot => {
  30718. if (args[currArg]) {
  30719. args[currArg](convertToDocSnapshot(firestore, reference, snapshot));
  30720. }
  30721. },
  30722. error: args[currArg + 1],
  30723. complete: args[currArg + 2]
  30724. };
  30725. }
  30726. else {
  30727. const query = cast(reference, Query);
  30728. firestore = cast(query.firestore, Firestore);
  30729. internalQuery = query._query;
  30730. const userDataWriter = new ExpUserDataWriter(firestore);
  30731. observer = {
  30732. next: snapshot => {
  30733. if (args[currArg]) {
  30734. args[currArg](new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  30735. }
  30736. },
  30737. error: args[currArg + 1],
  30738. complete: args[currArg + 2]
  30739. };
  30740. validateHasExplicitOrderByForLimitToLast(reference._query);
  30741. }
  30742. const client = ensureFirestoreConfigured(firestore);
  30743. return firestoreClientListen(client, internalQuery, internalOptions, observer);
  30744. }
  30745. function onSnapshotsInSync(firestore, arg) {
  30746. firestore = cast(firestore, Firestore);
  30747. const client = ensureFirestoreConfigured(firestore);
  30748. const observer = isPartialObserver(arg)
  30749. ? arg
  30750. : {
  30751. next: arg
  30752. };
  30753. return firestoreClientAddSnapshotsInSyncListener(client, observer);
  30754. }
  30755. /**
  30756. * Locally writes `mutations` on the async queue.
  30757. * @internal
  30758. */
  30759. function executeWrite(firestore, mutations) {
  30760. const client = ensureFirestoreConfigured(firestore);
  30761. return firestoreClientWrite(client, mutations);
  30762. }
  30763. /**
  30764. * Converts a {@link ViewSnapshot} that contains the single document specified by `ref`
  30765. * to a {@link DocumentSnapshot}.
  30766. */
  30767. function convertToDocSnapshot(firestore, ref, snapshot) {
  30768. const doc = snapshot.docs.get(ref._key);
  30769. const userDataWriter = new ExpUserDataWriter(firestore);
  30770. return new DocumentSnapshot(firestore, userDataWriter, ref._key, doc, new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache), ref.converter);
  30771. }
  30772. /**
  30773. * @license
  30774. * Copyright 2022 Google LLC
  30775. *
  30776. * Licensed under the Apache License, Version 2.0 (the "License");
  30777. * you may not use this file except in compliance with the License.
  30778. * You may obtain a copy of the License at
  30779. *
  30780. * http://www.apache.org/licenses/LICENSE-2.0
  30781. *
  30782. * Unless required by applicable law or agreed to in writing, software
  30783. * distributed under the License is distributed on an "AS IS" BASIS,
  30784. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30785. * See the License for the specific language governing permissions and
  30786. * limitations under the License.
  30787. */
  30788. /**
  30789. * Calculates the number of documents in the result set of the given query,
  30790. * without actually downloading the documents.
  30791. *
  30792. * Using this function to count the documents is efficient because only the
  30793. * final count, not the documents' data, is downloaded. This function can even
  30794. * count the documents if the result set would be prohibitively large to
  30795. * download entirely (e.g. thousands of documents).
  30796. *
  30797. * The result received from the server is presented, unaltered, without
  30798. * considering any local state. That is, documents in the local cache are not
  30799. * taken into consideration, neither are local modifications not yet
  30800. * synchronized with the server. Previously-downloaded results, if any, are not
  30801. * used: every request using this source necessarily involves a round trip to
  30802. * the server.
  30803. *
  30804. * @param query - The query whose result set size to calculate.
  30805. * @returns A Promise that will be resolved with the count; the count can be
  30806. * retrieved from `snapshot.data().count`, where `snapshot` is the
  30807. * `AggregateQuerySnapshot` to which the returned Promise resolves.
  30808. */
  30809. function getCountFromServer(query) {
  30810. const countQuerySpec = {
  30811. count: count()
  30812. };
  30813. return getAggregateFromServer(query, countQuerySpec);
  30814. }
  30815. /**
  30816. * Calculates the specified aggregations over the documents in the result
  30817. * set of the given query, without actually downloading the documents.
  30818. *
  30819. * Using this function to perform aggregations is efficient because only the
  30820. * final aggregation values, not the documents' data, is downloaded. This
  30821. * function can even perform aggregations of the documents if the result set
  30822. * would be prohibitively large to download entirely (e.g. thousands of documents).
  30823. *
  30824. * The result received from the server is presented, unaltered, without
  30825. * considering any local state. That is, documents in the local cache are not
  30826. * taken into consideration, neither are local modifications not yet
  30827. * synchronized with the server. Previously-downloaded results, if any, are not
  30828. * used: every request using this source necessarily involves a round trip to
  30829. * the server.
  30830. *
  30831. * @param query The query whose result set to aggregate over.
  30832. * @param aggregateSpec An `AggregateSpec` object that specifies the aggregates
  30833. * to perform over the result set. The AggregateSpec specifies aliases for each
  30834. * aggregate, which can be used to retrieve the aggregate result.
  30835. * @example
  30836. * ```typescript
  30837. * const aggregateSnapshot = await getAggregateFromServer(query, {
  30838. * countOfDocs: count(),
  30839. * totalHours: sum('hours'),
  30840. * averageScore: average('score')
  30841. * });
  30842. *
  30843. * const countOfDocs: number = aggregateSnapshot.data().countOfDocs;
  30844. * const totalHours: number = aggregateSnapshot.data().totalHours;
  30845. * const averageScore: number | null = aggregateSnapshot.data().averageScore;
  30846. * ```
  30847. * @internal TODO (sum/avg) remove when public
  30848. */
  30849. function getAggregateFromServer(query, aggregateSpec) {
  30850. const firestore = cast(query.firestore, Firestore);
  30851. const client = ensureFirestoreConfigured(firestore);
  30852. const internalAggregates = mapToArray(aggregateSpec, (aggregate, alias) => {
  30853. return new AggregateImpl(alias, aggregate._aggregateType, aggregate._internalFieldPath);
  30854. });
  30855. // Run the aggregation and convert the results
  30856. return firestoreClientRunAggregateQuery(client, query._query, internalAggregates).then(aggregateResult => convertToAggregateQuerySnapshot(firestore, query, aggregateResult));
  30857. }
  30858. /**
  30859. * Converts the core aggregration result to an `AggregateQuerySnapshot`
  30860. * that can be returned to the consumer.
  30861. * @param query
  30862. * @param aggregateResult Core aggregation result
  30863. * @internal
  30864. */
  30865. function convertToAggregateQuerySnapshot(firestore, query, aggregateResult) {
  30866. const userDataWriter = new ExpUserDataWriter(firestore);
  30867. const querySnapshot = new AggregateQuerySnapshot(query, userDataWriter, aggregateResult);
  30868. return querySnapshot;
  30869. }
  30870. /**
  30871. * @license
  30872. * Copyright 2023 Google LLC
  30873. *
  30874. * Licensed under the Apache License, Version 2.0 (the "License");
  30875. * you may not use this file except in compliance with the License.
  30876. * You may obtain a copy of the License at
  30877. *
  30878. * http://www.apache.org/licenses/LICENSE-2.0
  30879. *
  30880. * Unless required by applicable law or agreed to in writing, software
  30881. * distributed under the License is distributed on an "AS IS" BASIS,
  30882. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30883. * See the License for the specific language governing permissions and
  30884. * limitations under the License.
  30885. */
  30886. class MemoryLocalCacheImpl {
  30887. constructor(settings) {
  30888. this.kind = 'memory';
  30889. this._onlineComponentProvider = new OnlineComponentProvider();
  30890. if (settings === null || settings === void 0 ? void 0 : settings.garbageCollector) {
  30891. this._offlineComponentProvider =
  30892. settings.garbageCollector._offlineComponentProvider;
  30893. }
  30894. else {
  30895. this._offlineComponentProvider = new MemoryOfflineComponentProvider();
  30896. }
  30897. }
  30898. toJSON() {
  30899. return { kind: this.kind };
  30900. }
  30901. }
  30902. class PersistentLocalCacheImpl {
  30903. constructor(settings) {
  30904. this.kind = 'persistent';
  30905. let tabManager;
  30906. if (settings === null || settings === void 0 ? void 0 : settings.tabManager) {
  30907. settings.tabManager._initialize(settings);
  30908. tabManager = settings.tabManager;
  30909. }
  30910. else {
  30911. tabManager = persistentSingleTabManager(undefined);
  30912. tabManager._initialize(settings);
  30913. }
  30914. this._onlineComponentProvider = tabManager._onlineComponentProvider;
  30915. this._offlineComponentProvider = tabManager._offlineComponentProvider;
  30916. }
  30917. toJSON() {
  30918. return { kind: this.kind };
  30919. }
  30920. }
  30921. class MemoryEagerGabageCollectorImpl {
  30922. constructor() {
  30923. this.kind = 'memoryEager';
  30924. this._offlineComponentProvider = new MemoryOfflineComponentProvider();
  30925. }
  30926. toJSON() {
  30927. return { kind: this.kind };
  30928. }
  30929. }
  30930. class MemoryLruGabageCollectorImpl {
  30931. constructor(cacheSize) {
  30932. this.kind = 'memoryLru';
  30933. this._offlineComponentProvider = new LruGcMemoryOfflineComponentProvider(cacheSize);
  30934. }
  30935. toJSON() {
  30936. return { kind: this.kind };
  30937. }
  30938. }
  30939. /**
  30940. * Creates an instance of `MemoryEagerGarbageCollector`. This is also the
  30941. * default garbage collector unless it is explicitly specified otherwise.
  30942. */
  30943. function memoryEagerGarbageCollector() {
  30944. return new MemoryEagerGabageCollectorImpl();
  30945. }
  30946. /**
  30947. * Creates an instance of `MemoryLruGarbageCollector`.
  30948. *
  30949. * A target size can be specified as part of the setting parameter. The
  30950. * collector will start deleting documents once the cache size exceeds
  30951. * the given size. The default cache size is 40MB (40 * 1024 * 1024 bytes).
  30952. */
  30953. function memoryLruGarbageCollector(settings) {
  30954. return new MemoryLruGabageCollectorImpl(settings === null || settings === void 0 ? void 0 : settings.cacheSizeBytes);
  30955. }
  30956. /**
  30957. * Creates an instance of `MemoryLocalCache`. The instance can be set to
  30958. * `FirestoreSettings.cache` to tell the SDK which cache layer to use.
  30959. */
  30960. function memoryLocalCache(settings) {
  30961. return new MemoryLocalCacheImpl(settings);
  30962. }
  30963. /**
  30964. * Creates an instance of `PersistentLocalCache`. The instance can be set to
  30965. * `FirestoreSettings.cache` to tell the SDK which cache layer to use.
  30966. *
  30967. * Persistent cache cannot be used in a Node.js environment.
  30968. */
  30969. function persistentLocalCache(settings) {
  30970. return new PersistentLocalCacheImpl(settings);
  30971. }
  30972. class SingleTabManagerImpl {
  30973. constructor(forceOwnership) {
  30974. this.forceOwnership = forceOwnership;
  30975. this.kind = 'persistentSingleTab';
  30976. }
  30977. toJSON() {
  30978. return { kind: this.kind };
  30979. }
  30980. /**
  30981. * @internal
  30982. */
  30983. _initialize(settings) {
  30984. this._onlineComponentProvider = new OnlineComponentProvider();
  30985. this._offlineComponentProvider = new IndexedDbOfflineComponentProvider(this._onlineComponentProvider, settings === null || settings === void 0 ? void 0 : settings.cacheSizeBytes, this.forceOwnership);
  30986. }
  30987. }
  30988. class MultiTabManagerImpl {
  30989. constructor() {
  30990. this.kind = 'PersistentMultipleTab';
  30991. }
  30992. toJSON() {
  30993. return { kind: this.kind };
  30994. }
  30995. /**
  30996. * @internal
  30997. */
  30998. _initialize(settings) {
  30999. this._onlineComponentProvider = new OnlineComponentProvider();
  31000. this._offlineComponentProvider = new MultiTabOfflineComponentProvider(this._onlineComponentProvider, settings === null || settings === void 0 ? void 0 : settings.cacheSizeBytes);
  31001. }
  31002. }
  31003. /**
  31004. * Creates an instance of `PersistentSingleTabManager`.
  31005. *
  31006. * @param settings Configures the created tab manager.
  31007. */
  31008. function persistentSingleTabManager(settings) {
  31009. return new SingleTabManagerImpl(settings === null || settings === void 0 ? void 0 : settings.forceOwnership);
  31010. }
  31011. /**
  31012. * Creates an instance of `PersistentMultipleTabManager`.
  31013. */
  31014. function persistentMultipleTabManager() {
  31015. return new MultiTabManagerImpl();
  31016. }
  31017. /**
  31018. * @license
  31019. * Copyright 2022 Google LLC
  31020. *
  31021. * Licensed under the Apache License, Version 2.0 (the "License");
  31022. * you may not use this file except in compliance with the License.
  31023. * You may obtain a copy of the License at
  31024. *
  31025. * http://www.apache.org/licenses/LICENSE-2.0
  31026. *
  31027. * Unless required by applicable law or agreed to in writing, software
  31028. * distributed under the License is distributed on an "AS IS" BASIS,
  31029. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31030. * See the License for the specific language governing permissions and
  31031. * limitations under the License.
  31032. */
  31033. const DEFAULT_TRANSACTION_OPTIONS = {
  31034. maxAttempts: 5
  31035. };
  31036. function validateTransactionOptions(options) {
  31037. if (options.maxAttempts < 1) {
  31038. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Max attempts must be at least 1');
  31039. }
  31040. }
  31041. /**
  31042. * @license
  31043. * Copyright 2020 Google LLC
  31044. *
  31045. * Licensed under the Apache License, Version 2.0 (the "License");
  31046. * you may not use this file except in compliance with the License.
  31047. * You may obtain a copy of the License at
  31048. *
  31049. * http://www.apache.org/licenses/LICENSE-2.0
  31050. *
  31051. * Unless required by applicable law or agreed to in writing, software
  31052. * distributed under the License is distributed on an "AS IS" BASIS,
  31053. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31054. * See the License for the specific language governing permissions and
  31055. * limitations under the License.
  31056. */
  31057. /**
  31058. * A write batch, used to perform multiple writes as a single atomic unit.
  31059. *
  31060. * A `WriteBatch` object can be acquired by calling {@link writeBatch}. It
  31061. * provides methods for adding writes to the write batch. None of the writes
  31062. * will be committed (or visible locally) until {@link WriteBatch.commit} is
  31063. * called.
  31064. */
  31065. class WriteBatch {
  31066. /** @hideconstructor */
  31067. constructor(_firestore, _commitHandler) {
  31068. this._firestore = _firestore;
  31069. this._commitHandler = _commitHandler;
  31070. this._mutations = [];
  31071. this._committed = false;
  31072. this._dataReader = newUserDataReader(_firestore);
  31073. }
  31074. set(documentRef, data, options) {
  31075. this._verifyNotCommitted();
  31076. const ref = validateReference(documentRef, this._firestore);
  31077. const convertedValue = applyFirestoreDataConverter(ref.converter, data, options);
  31078. const parsed = parseSetData(this._dataReader, 'WriteBatch.set', ref._key, convertedValue, ref.converter !== null, options);
  31079. this._mutations.push(parsed.toMutation(ref._key, Precondition.none()));
  31080. return this;
  31081. }
  31082. update(documentRef, fieldOrUpdateData, value, ...moreFieldsAndValues) {
  31083. this._verifyNotCommitted();
  31084. const ref = validateReference(documentRef, this._firestore);
  31085. // For Compat types, we have to "extract" the underlying types before
  31086. // performing validation.
  31087. fieldOrUpdateData = util.getModularInstance(fieldOrUpdateData);
  31088. let parsed;
  31089. if (typeof fieldOrUpdateData === 'string' ||
  31090. fieldOrUpdateData instanceof FieldPath) {
  31091. parsed = parseUpdateVarargs(this._dataReader, 'WriteBatch.update', ref._key, fieldOrUpdateData, value, moreFieldsAndValues);
  31092. }
  31093. else {
  31094. parsed = parseUpdateData(this._dataReader, 'WriteBatch.update', ref._key, fieldOrUpdateData);
  31095. }
  31096. this._mutations.push(parsed.toMutation(ref._key, Precondition.exists(true)));
  31097. return this;
  31098. }
  31099. /**
  31100. * Deletes the document referred to by the provided {@link DocumentReference}.
  31101. *
  31102. * @param documentRef - A reference to the document to be deleted.
  31103. * @returns This `WriteBatch` instance. Used for chaining method calls.
  31104. */
  31105. delete(documentRef) {
  31106. this._verifyNotCommitted();
  31107. const ref = validateReference(documentRef, this._firestore);
  31108. this._mutations = this._mutations.concat(new DeleteMutation(ref._key, Precondition.none()));
  31109. return this;
  31110. }
  31111. /**
  31112. * Commits all of the writes in this write batch as a single atomic unit.
  31113. *
  31114. * The result of these writes will only be reflected in document reads that
  31115. * occur after the returned promise resolves. If the client is offline, the
  31116. * write fails. If you would like to see local modifications or buffer writes
  31117. * until the client is online, use the full Firestore SDK.
  31118. *
  31119. * @returns A `Promise` resolved once all of the writes in the batch have been
  31120. * successfully written to the backend as an atomic unit (note that it won't
  31121. * resolve while you're offline).
  31122. */
  31123. commit() {
  31124. this._verifyNotCommitted();
  31125. this._committed = true;
  31126. if (this._mutations.length > 0) {
  31127. return this._commitHandler(this._mutations);
  31128. }
  31129. return Promise.resolve();
  31130. }
  31131. _verifyNotCommitted() {
  31132. if (this._committed) {
  31133. throw new FirestoreError(Code.FAILED_PRECONDITION, 'A write batch can no longer be used after commit() ' +
  31134. 'has been called.');
  31135. }
  31136. }
  31137. }
  31138. function validateReference(documentRef, firestore) {
  31139. documentRef = util.getModularInstance(documentRef);
  31140. if (documentRef.firestore !== firestore) {
  31141. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Provided document reference is from a different Firestore instance.');
  31142. }
  31143. else {
  31144. return documentRef;
  31145. }
  31146. }
  31147. /**
  31148. * @license
  31149. * Copyright 2020 Google LLC
  31150. *
  31151. * Licensed under the Apache License, Version 2.0 (the "License");
  31152. * you may not use this file except in compliance with the License.
  31153. * You may obtain a copy of the License at
  31154. *
  31155. * http://www.apache.org/licenses/LICENSE-2.0
  31156. *
  31157. * Unless required by applicable law or agreed to in writing, software
  31158. * distributed under the License is distributed on an "AS IS" BASIS,
  31159. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31160. * See the License for the specific language governing permissions and
  31161. * limitations under the License.
  31162. */
  31163. // TODO(mrschmidt) Consider using `BaseTransaction` as the base class in the
  31164. // legacy SDK.
  31165. /**
  31166. * A reference to a transaction.
  31167. *
  31168. * The `Transaction` object passed to a transaction's `updateFunction` provides
  31169. * the methods to read and write data within the transaction context. See
  31170. * {@link runTransaction}.
  31171. */
  31172. class Transaction$1 {
  31173. /** @hideconstructor */
  31174. constructor(_firestore, _transaction) {
  31175. this._firestore = _firestore;
  31176. this._transaction = _transaction;
  31177. this._dataReader = newUserDataReader(_firestore);
  31178. }
  31179. /**
  31180. * Reads the document referenced by the provided {@link DocumentReference}.
  31181. *
  31182. * @param documentRef - A reference to the document to be read.
  31183. * @returns A `DocumentSnapshot` with the read data.
  31184. */
  31185. get(documentRef) {
  31186. const ref = validateReference(documentRef, this._firestore);
  31187. const userDataWriter = new LiteUserDataWriter(this._firestore);
  31188. return this._transaction.lookup([ref._key]).then(docs => {
  31189. if (!docs || docs.length !== 1) {
  31190. return fail();
  31191. }
  31192. const doc = docs[0];
  31193. if (doc.isFoundDocument()) {
  31194. return new DocumentSnapshot$1(this._firestore, userDataWriter, doc.key, doc, ref.converter);
  31195. }
  31196. else if (doc.isNoDocument()) {
  31197. return new DocumentSnapshot$1(this._firestore, userDataWriter, ref._key, null, ref.converter);
  31198. }
  31199. else {
  31200. throw fail();
  31201. }
  31202. });
  31203. }
  31204. set(documentRef, value, options) {
  31205. const ref = validateReference(documentRef, this._firestore);
  31206. const convertedValue = applyFirestoreDataConverter(ref.converter, value, options);
  31207. const parsed = parseSetData(this._dataReader, 'Transaction.set', ref._key, convertedValue, ref.converter !== null, options);
  31208. this._transaction.set(ref._key, parsed);
  31209. return this;
  31210. }
  31211. update(documentRef, fieldOrUpdateData, value, ...moreFieldsAndValues) {
  31212. const ref = validateReference(documentRef, this._firestore);
  31213. // For Compat types, we have to "extract" the underlying types before
  31214. // performing validation.
  31215. fieldOrUpdateData = util.getModularInstance(fieldOrUpdateData);
  31216. let parsed;
  31217. if (typeof fieldOrUpdateData === 'string' ||
  31218. fieldOrUpdateData instanceof FieldPath) {
  31219. parsed = parseUpdateVarargs(this._dataReader, 'Transaction.update', ref._key, fieldOrUpdateData, value, moreFieldsAndValues);
  31220. }
  31221. else {
  31222. parsed = parseUpdateData(this._dataReader, 'Transaction.update', ref._key, fieldOrUpdateData);
  31223. }
  31224. this._transaction.update(ref._key, parsed);
  31225. return this;
  31226. }
  31227. /**
  31228. * Deletes the document referred to by the provided {@link DocumentReference}.
  31229. *
  31230. * @param documentRef - A reference to the document to be deleted.
  31231. * @returns This `Transaction` instance. Used for chaining method calls.
  31232. */
  31233. delete(documentRef) {
  31234. const ref = validateReference(documentRef, this._firestore);
  31235. this._transaction.delete(ref._key);
  31236. return this;
  31237. }
  31238. }
  31239. /**
  31240. * @license
  31241. * Copyright 2020 Google LLC
  31242. *
  31243. * Licensed under the Apache License, Version 2.0 (the "License");
  31244. * you may not use this file except in compliance with the License.
  31245. * You may obtain a copy of the License at
  31246. *
  31247. * http://www.apache.org/licenses/LICENSE-2.0
  31248. *
  31249. * Unless required by applicable law or agreed to in writing, software
  31250. * distributed under the License is distributed on an "AS IS" BASIS,
  31251. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31252. * See the License for the specific language governing permissions and
  31253. * limitations under the License.
  31254. */
  31255. /**
  31256. * A reference to a transaction.
  31257. *
  31258. * The `Transaction` object passed to a transaction's `updateFunction` provides
  31259. * the methods to read and write data within the transaction context. See
  31260. * {@link runTransaction}.
  31261. */
  31262. class Transaction extends Transaction$1 {
  31263. // This class implements the same logic as the Transaction API in the Lite SDK
  31264. // but is subclassed in order to return its own DocumentSnapshot types.
  31265. /** @hideconstructor */
  31266. constructor(_firestore, _transaction) {
  31267. super(_firestore, _transaction);
  31268. this._firestore = _firestore;
  31269. }
  31270. /**
  31271. * Reads the document referenced by the provided {@link DocumentReference}.
  31272. *
  31273. * @param documentRef - A reference to the document to be read.
  31274. * @returns A `DocumentSnapshot` with the read data.
  31275. */
  31276. get(documentRef) {
  31277. const ref = validateReference(documentRef, this._firestore);
  31278. const userDataWriter = new ExpUserDataWriter(this._firestore);
  31279. return super
  31280. .get(documentRef)
  31281. .then(liteDocumentSnapshot => new DocumentSnapshot(this._firestore, userDataWriter, ref._key, liteDocumentSnapshot._document, new SnapshotMetadata(
  31282. /* hasPendingWrites= */ false,
  31283. /* fromCache= */ false), ref.converter));
  31284. }
  31285. }
  31286. /**
  31287. * Executes the given `updateFunction` and then attempts to commit the changes
  31288. * applied within the transaction. If any document read within the transaction
  31289. * has changed, Cloud Firestore retries the `updateFunction`. If it fails to
  31290. * commit after 5 attempts, the transaction fails.
  31291. *
  31292. * The maximum number of writes allowed in a single transaction is 500.
  31293. *
  31294. * @param firestore - A reference to the Firestore database to run this
  31295. * transaction against.
  31296. * @param updateFunction - The function to execute within the transaction
  31297. * context.
  31298. * @param options - An options object to configure maximum number of attempts to
  31299. * commit.
  31300. * @returns If the transaction completed successfully or was explicitly aborted
  31301. * (the `updateFunction` returned a failed promise), the promise returned by the
  31302. * `updateFunction `is returned here. Otherwise, if the transaction failed, a
  31303. * rejected promise with the corresponding failure error is returned.
  31304. */
  31305. function runTransaction(firestore, updateFunction, options) {
  31306. firestore = cast(firestore, Firestore);
  31307. const optionsWithDefaults = Object.assign(Object.assign({}, DEFAULT_TRANSACTION_OPTIONS), options);
  31308. validateTransactionOptions(optionsWithDefaults);
  31309. const client = ensureFirestoreConfigured(firestore);
  31310. return firestoreClientTransaction(client, internalTransaction => updateFunction(new Transaction(firestore, internalTransaction)), optionsWithDefaults);
  31311. }
  31312. /**
  31313. * @license
  31314. * Copyright 2020 Google LLC
  31315. *
  31316. * Licensed under the Apache License, Version 2.0 (the "License");
  31317. * you may not use this file except in compliance with the License.
  31318. * You may obtain a copy of the License at
  31319. *
  31320. * http://www.apache.org/licenses/LICENSE-2.0
  31321. *
  31322. * Unless required by applicable law or agreed to in writing, software
  31323. * distributed under the License is distributed on an "AS IS" BASIS,
  31324. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31325. * See the License for the specific language governing permissions and
  31326. * limitations under the License.
  31327. */
  31328. /**
  31329. * Returns a sentinel for use with {@link @firebase/firestore/lite#(updateDoc:1)} or
  31330. * {@link @firebase/firestore/lite#(setDoc:1)} with `{merge: true}` to mark a field for deletion.
  31331. */
  31332. function deleteField() {
  31333. return new DeleteFieldValueImpl('deleteField');
  31334. }
  31335. /**
  31336. * Returns a sentinel used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link @firebase/firestore/lite#(updateDoc:1)} to
  31337. * include a server-generated timestamp in the written data.
  31338. */
  31339. function serverTimestamp() {
  31340. return new ServerTimestampFieldValueImpl('serverTimestamp');
  31341. }
  31342. /**
  31343. * Returns a special value that can be used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link
  31344. * @firebase/firestore/lite#(updateDoc:1)} that tells the server to union the given elements with any array
  31345. * value that already exists on the server. Each specified element that doesn't
  31346. * already exist in the array will be added to the end. If the field being
  31347. * modified is not already an array it will be overwritten with an array
  31348. * containing exactly the specified elements.
  31349. *
  31350. * @param elements - The elements to union into the array.
  31351. * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
  31352. * `updateDoc()`.
  31353. */
  31354. function arrayUnion(...elements) {
  31355. // NOTE: We don't actually parse the data until it's used in set() or
  31356. // update() since we'd need the Firestore instance to do this.
  31357. return new ArrayUnionFieldValueImpl('arrayUnion', elements);
  31358. }
  31359. /**
  31360. * Returns a special value that can be used with {@link (setDoc:1)} or {@link
  31361. * updateDoc:1} that tells the server to remove the given elements from any
  31362. * array value that already exists on the server. All instances of each element
  31363. * specified will be removed from the array. If the field being modified is not
  31364. * already an array it will be overwritten with an empty array.
  31365. *
  31366. * @param elements - The elements to remove from the array.
  31367. * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
  31368. * `updateDoc()`
  31369. */
  31370. function arrayRemove(...elements) {
  31371. // NOTE: We don't actually parse the data until it's used in set() or
  31372. // update() since we'd need the Firestore instance to do this.
  31373. return new ArrayRemoveFieldValueImpl('arrayRemove', elements);
  31374. }
  31375. /**
  31376. * Returns a special value that can be used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link
  31377. * @firebase/firestore/lite#(updateDoc:1)} that tells the server to increment the field's current value by
  31378. * the given value.
  31379. *
  31380. * If either the operand or the current field value uses floating point
  31381. * precision, all arithmetic follows IEEE 754 semantics. If both values are
  31382. * integers, values outside of JavaScript's safe number range
  31383. * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject to
  31384. * precision loss. Furthermore, once processed by the Firestore backend, all
  31385. * integer operations are capped between -2^63 and 2^63-1.
  31386. *
  31387. * If the current field value is not of type `number`, or if the field does not
  31388. * yet exist, the transformation sets the field to the given value.
  31389. *
  31390. * @param n - The value to increment by.
  31391. * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
  31392. * `updateDoc()`
  31393. */
  31394. function increment(n) {
  31395. return new NumericIncrementFieldValueImpl('increment', n);
  31396. }
  31397. /**
  31398. * @license
  31399. * Copyright 2020 Google LLC
  31400. *
  31401. * Licensed under the Apache License, Version 2.0 (the "License");
  31402. * you may not use this file except in compliance with the License.
  31403. * You may obtain a copy of the License at
  31404. *
  31405. * http://www.apache.org/licenses/LICENSE-2.0
  31406. *
  31407. * Unless required by applicable law or agreed to in writing, software
  31408. * distributed under the License is distributed on an "AS IS" BASIS,
  31409. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31410. * See the License for the specific language governing permissions and
  31411. * limitations under the License.
  31412. */
  31413. /**
  31414. * Creates a write batch, used for performing multiple writes as a single
  31415. * atomic operation. The maximum number of writes allowed in a single {@link WriteBatch}
  31416. * is 500.
  31417. *
  31418. * Unlike transactions, write batches are persisted offline and therefore are
  31419. * preferable when you don't need to condition your writes on read data.
  31420. *
  31421. * @returns A {@link WriteBatch} that can be used to atomically execute multiple
  31422. * writes.
  31423. */
  31424. function writeBatch(firestore) {
  31425. firestore = cast(firestore, Firestore);
  31426. ensureFirestoreConfigured(firestore);
  31427. return new WriteBatch(firestore, mutations => executeWrite(firestore, mutations));
  31428. }
  31429. /**
  31430. * @license
  31431. * Copyright 2021 Google LLC
  31432. *
  31433. * Licensed under the Apache License, Version 2.0 (the "License");
  31434. * you may not use this file except in compliance with the License.
  31435. * You may obtain a copy of the License at
  31436. *
  31437. * http://www.apache.org/licenses/LICENSE-2.0
  31438. *
  31439. * Unless required by applicable law or agreed to in writing, software
  31440. * distributed under the License is distributed on an "AS IS" BASIS,
  31441. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31442. * See the License for the specific language governing permissions and
  31443. * limitations under the License.
  31444. */
  31445. function setIndexConfiguration(firestore, jsonOrConfiguration) {
  31446. var _a;
  31447. firestore = cast(firestore, Firestore);
  31448. const client = ensureFirestoreConfigured(firestore);
  31449. if (!client._uninitializedComponentsProvider ||
  31450. ((_a = client._uninitializedComponentsProvider) === null || _a === void 0 ? void 0 : _a._offlineKind) === 'memory') {
  31451. // PORTING NOTE: We don't return an error if the user has not enabled
  31452. // persistence since `enableIndexeddbPersistence()` can fail on the Web.
  31453. logWarn('Cannot enable indexes when persistence is disabled');
  31454. return Promise.resolve();
  31455. }
  31456. const parsedIndexes = parseIndexes(jsonOrConfiguration);
  31457. return firestoreClientSetIndexConfiguration(client, parsedIndexes);
  31458. }
  31459. function parseIndexes(jsonOrConfiguration) {
  31460. const indexConfiguration = typeof jsonOrConfiguration === 'string'
  31461. ? tryParseJson(jsonOrConfiguration)
  31462. : jsonOrConfiguration;
  31463. const parsedIndexes = [];
  31464. if (Array.isArray(indexConfiguration.indexes)) {
  31465. for (const index of indexConfiguration.indexes) {
  31466. const collectionGroup = tryGetString(index, 'collectionGroup');
  31467. const segments = [];
  31468. if (Array.isArray(index.fields)) {
  31469. for (const field of index.fields) {
  31470. const fieldPathString = tryGetString(field, 'fieldPath');
  31471. const fieldPath = fieldPathFromDotSeparatedString('setIndexConfiguration', fieldPathString);
  31472. if (field.arrayConfig === 'CONTAINS') {
  31473. segments.push(new IndexSegment(fieldPath, 2 /* IndexKind.CONTAINS */));
  31474. }
  31475. else if (field.order === 'ASCENDING') {
  31476. segments.push(new IndexSegment(fieldPath, 0 /* IndexKind.ASCENDING */));
  31477. }
  31478. else if (field.order === 'DESCENDING') {
  31479. segments.push(new IndexSegment(fieldPath, 1 /* IndexKind.DESCENDING */));
  31480. }
  31481. }
  31482. }
  31483. parsedIndexes.push(new FieldIndex(FieldIndex.UNKNOWN_ID, collectionGroup, segments, IndexState.empty()));
  31484. }
  31485. }
  31486. return parsedIndexes;
  31487. }
  31488. function tryParseJson(json) {
  31489. try {
  31490. return JSON.parse(json);
  31491. }
  31492. catch (e) {
  31493. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Failed to parse JSON: ' + (e === null || e === void 0 ? void 0 : e.message));
  31494. }
  31495. }
  31496. function tryGetString(data, property) {
  31497. if (typeof data[property] !== 'string') {
  31498. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Missing string value for: ' + property);
  31499. }
  31500. return data[property];
  31501. }
  31502. /**
  31503. * @license
  31504. * Copyright 2021 Google LLC
  31505. *
  31506. * Licensed under the Apache License, Version 2.0 (the "License");
  31507. * you may not use this file except in compliance with the License.
  31508. * You may obtain a copy of the License at
  31509. *
  31510. * http://www.apache.org/licenses/LICENSE-2.0
  31511. *
  31512. * Unless required by applicable law or agreed to in writing, software
  31513. * distributed under the License is distributed on an "AS IS" BASIS,
  31514. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31515. * See the License for the specific language governing permissions and
  31516. * limitations under the License.
  31517. */
  31518. registerFirestore('node');
  31519. exports.AbstractUserDataWriter = AbstractUserDataWriter;
  31520. exports.AggregateField = AggregateField;
  31521. exports.AggregateQuerySnapshot = AggregateQuerySnapshot;
  31522. exports.Bytes = Bytes;
  31523. exports.CACHE_SIZE_UNLIMITED = CACHE_SIZE_UNLIMITED;
  31524. exports.CollectionReference = CollectionReference;
  31525. exports.DocumentReference = DocumentReference;
  31526. exports.DocumentSnapshot = DocumentSnapshot;
  31527. exports.FieldPath = FieldPath;
  31528. exports.FieldValue = FieldValue;
  31529. exports.Firestore = Firestore;
  31530. exports.FirestoreError = FirestoreError;
  31531. exports.GeoPoint = GeoPoint;
  31532. exports.LoadBundleTask = LoadBundleTask;
  31533. exports.Query = Query;
  31534. exports.QueryCompositeFilterConstraint = QueryCompositeFilterConstraint;
  31535. exports.QueryConstraint = QueryConstraint;
  31536. exports.QueryDocumentSnapshot = QueryDocumentSnapshot;
  31537. exports.QueryEndAtConstraint = QueryEndAtConstraint;
  31538. exports.QueryFieldFilterConstraint = QueryFieldFilterConstraint;
  31539. exports.QueryLimitConstraint = QueryLimitConstraint;
  31540. exports.QueryOrderByConstraint = QueryOrderByConstraint;
  31541. exports.QuerySnapshot = QuerySnapshot;
  31542. exports.QueryStartAtConstraint = QueryStartAtConstraint;
  31543. exports.SnapshotMetadata = SnapshotMetadata;
  31544. exports.Timestamp = Timestamp;
  31545. exports.Transaction = Transaction;
  31546. exports.WriteBatch = WriteBatch;
  31547. exports._DatabaseId = DatabaseId;
  31548. exports._DocumentKey = DocumentKey;
  31549. exports._EmptyAppCheckTokenProvider = EmptyAppCheckTokenProvider;
  31550. exports._EmptyAuthCredentialsProvider = EmptyAuthCredentialsProvider;
  31551. exports._FieldPath = FieldPath$1;
  31552. exports._TestingHooks = TestingHooks;
  31553. exports._cast = cast;
  31554. exports._debugAssert = debugAssert;
  31555. exports._isBase64Available = isBase64Available;
  31556. exports._logWarn = logWarn;
  31557. exports._validateIsNotUsedTogether = validateIsNotUsedTogether;
  31558. exports.addDoc = addDoc;
  31559. exports.aggregateFieldEqual = aggregateFieldEqual;
  31560. exports.aggregateQuerySnapshotEqual = aggregateQuerySnapshotEqual;
  31561. exports.and = and;
  31562. exports.arrayRemove = arrayRemove;
  31563. exports.arrayUnion = arrayUnion;
  31564. exports.average = average;
  31565. exports.clearIndexedDbPersistence = clearIndexedDbPersistence;
  31566. exports.collection = collection;
  31567. exports.collectionGroup = collectionGroup;
  31568. exports.connectFirestoreEmulator = connectFirestoreEmulator;
  31569. exports.count = count;
  31570. exports.deleteDoc = deleteDoc;
  31571. exports.deleteField = deleteField;
  31572. exports.disableNetwork = disableNetwork;
  31573. exports.doc = doc;
  31574. exports.documentId = documentId;
  31575. exports.enableIndexedDbPersistence = enableIndexedDbPersistence;
  31576. exports.enableMultiTabIndexedDbPersistence = enableMultiTabIndexedDbPersistence;
  31577. exports.enableNetwork = enableNetwork;
  31578. exports.endAt = endAt;
  31579. exports.endBefore = endBefore;
  31580. exports.ensureFirestoreConfigured = ensureFirestoreConfigured;
  31581. exports.executeWrite = executeWrite;
  31582. exports.getAggregateFromServer = getAggregateFromServer;
  31583. exports.getCountFromServer = getCountFromServer;
  31584. exports.getDoc = getDoc;
  31585. exports.getDocFromCache = getDocFromCache;
  31586. exports.getDocFromServer = getDocFromServer;
  31587. exports.getDocs = getDocs;
  31588. exports.getDocsFromCache = getDocsFromCache;
  31589. exports.getDocsFromServer = getDocsFromServer;
  31590. exports.getFirestore = getFirestore;
  31591. exports.increment = increment;
  31592. exports.initializeFirestore = initializeFirestore;
  31593. exports.limit = limit;
  31594. exports.limitToLast = limitToLast;
  31595. exports.loadBundle = loadBundle;
  31596. exports.memoryEagerGarbageCollector = memoryEagerGarbageCollector;
  31597. exports.memoryLocalCache = memoryLocalCache;
  31598. exports.memoryLruGarbageCollector = memoryLruGarbageCollector;
  31599. exports.namedQuery = namedQuery;
  31600. exports.onSnapshot = onSnapshot;
  31601. exports.onSnapshotsInSync = onSnapshotsInSync;
  31602. exports.or = or;
  31603. exports.orderBy = orderBy;
  31604. exports.persistentLocalCache = persistentLocalCache;
  31605. exports.persistentMultipleTabManager = persistentMultipleTabManager;
  31606. exports.persistentSingleTabManager = persistentSingleTabManager;
  31607. exports.query = query;
  31608. exports.queryEqual = queryEqual;
  31609. exports.refEqual = refEqual;
  31610. exports.runTransaction = runTransaction;
  31611. exports.serverTimestamp = serverTimestamp;
  31612. exports.setDoc = setDoc;
  31613. exports.setIndexConfiguration = setIndexConfiguration;
  31614. exports.setLogLevel = setLogLevel;
  31615. exports.snapshotEqual = snapshotEqual;
  31616. exports.startAfter = startAfter;
  31617. exports.startAt = startAt;
  31618. exports.sum = sum;
  31619. exports.terminate = terminate;
  31620. exports.updateDoc = updateDoc;
  31621. exports.waitForPendingWrites = waitForPendingWrites;
  31622. exports.where = where;
  31623. exports.writeBatch = writeBatch;
  31624. //# sourceMappingURL=index.node.cjs.js.map