Нема описа
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.node.cjs.js 1.1MB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673
  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 grpc = require('@grpc/grpc-js');
  10. var protoLoader = require('@grpc/proto-loader');
  11. function _interopNamespace(e) {
  12. if (e && e.__esModule) return e;
  13. var n = Object.create(null);
  14. if (e) {
  15. Object.keys(e).forEach(function (k) {
  16. if (k !== 'default') {
  17. var d = Object.getOwnPropertyDescriptor(e, k);
  18. Object.defineProperty(n, k, d.get ? d : {
  19. enumerable: true,
  20. get: function () { return e[k]; }
  21. });
  22. }
  23. });
  24. }
  25. n["default"] = e;
  26. return Object.freeze(n);
  27. }
  28. var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
  29. var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
  30. const name = "@firebase/firestore";
  31. const version$1 = "3.8.1";
  32. /**
  33. * @license
  34. * Copyright 2017 Google LLC
  35. *
  36. * Licensed under the Apache License, Version 2.0 (the "License");
  37. * you may not use this file except in compliance with the License.
  38. * You may obtain a copy of the License at
  39. *
  40. * http://www.apache.org/licenses/LICENSE-2.0
  41. *
  42. * Unless required by applicable law or agreed to in writing, software
  43. * distributed under the License is distributed on an "AS IS" BASIS,
  44. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  45. * See the License for the specific language governing permissions and
  46. * limitations under the License.
  47. */
  48. /**
  49. * Simple wrapper around a nullable UID. Mostly exists to make code more
  50. * readable.
  51. */
  52. class User {
  53. constructor(uid) {
  54. this.uid = uid;
  55. }
  56. isAuthenticated() {
  57. return this.uid != null;
  58. }
  59. /**
  60. * Returns a key representing this user, suitable for inclusion in a
  61. * dictionary.
  62. */
  63. toKey() {
  64. if (this.isAuthenticated()) {
  65. return 'uid:' + this.uid;
  66. }
  67. else {
  68. return 'anonymous-user';
  69. }
  70. }
  71. isEqual(otherUser) {
  72. return otherUser.uid === this.uid;
  73. }
  74. }
  75. /** A user with a null UID. */
  76. User.UNAUTHENTICATED = new User(null);
  77. // TODO(mikelehen): Look into getting a proper uid-equivalent for
  78. // non-FirebaseAuth providers.
  79. User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
  80. User.FIRST_PARTY = new User('first-party-uid');
  81. User.MOCK_USER = new User('mock-user');
  82. const version = "9.16.0";
  83. /**
  84. * @license
  85. * Copyright 2017 Google LLC
  86. *
  87. * Licensed under the Apache License, Version 2.0 (the "License");
  88. * you may not use this file except in compliance with the License.
  89. * You may obtain a copy of the License at
  90. *
  91. * http://www.apache.org/licenses/LICENSE-2.0
  92. *
  93. * Unless required by applicable law or agreed to in writing, software
  94. * distributed under the License is distributed on an "AS IS" BASIS,
  95. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  96. * See the License for the specific language governing permissions and
  97. * limitations under the License.
  98. */
  99. let SDK_VERSION = version;
  100. function setSDKVersion(version) {
  101. SDK_VERSION = version;
  102. }
  103. /**
  104. * @license
  105. * Copyright 2020 Google LLC
  106. *
  107. * Licensed under the Apache License, Version 2.0 (the "License");
  108. * you may not use this file except in compliance with the License.
  109. * You may obtain a copy of the License at
  110. *
  111. * http://www.apache.org/licenses/LICENSE-2.0
  112. *
  113. * Unless required by applicable law or agreed to in writing, software
  114. * distributed under the License is distributed on an "AS IS" BASIS,
  115. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  116. * See the License for the specific language governing permissions and
  117. * limitations under the License.
  118. */
  119. /** Formats an object as a JSON string, suitable for logging. */
  120. function formatJSON(value) {
  121. // util.inspect() results in much more readable output than JSON.stringify()
  122. return util$1.inspect(value, { depth: 100 });
  123. }
  124. /**
  125. * @license
  126. * Copyright 2017 Google LLC
  127. *
  128. * Licensed under the Apache License, Version 2.0 (the "License");
  129. * you may not use this file except in compliance with the License.
  130. * You may obtain a copy of the License at
  131. *
  132. * http://www.apache.org/licenses/LICENSE-2.0
  133. *
  134. * Unless required by applicable law or agreed to in writing, software
  135. * distributed under the License is distributed on an "AS IS" BASIS,
  136. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  137. * See the License for the specific language governing permissions and
  138. * limitations under the License.
  139. */
  140. const logClient = new logger.Logger('@firebase/firestore');
  141. // Helper methods are needed because variables can't be exported as read/write
  142. function getLogLevel() {
  143. return logClient.logLevel;
  144. }
  145. /**
  146. * Sets the verbosity of Cloud Firestore logs (debug, error, or silent).
  147. *
  148. * @param logLevel - The verbosity you set for activity and error logging. Can
  149. * be any of the following values:
  150. *
  151. * <ul>
  152. * <li>`debug` for the most verbose logging level, primarily for
  153. * debugging.</li>
  154. * <li>`error` to log errors only.</li>
  155. * <li><code>`silent` to turn off logging.</li>
  156. * </ul>
  157. */
  158. function setLogLevel(logLevel) {
  159. logClient.setLogLevel(logLevel);
  160. }
  161. function logDebug(msg, ...obj) {
  162. if (logClient.logLevel <= logger.LogLevel.DEBUG) {
  163. const args = obj.map(argToString);
  164. logClient.debug(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
  165. }
  166. }
  167. function logError(msg, ...obj) {
  168. if (logClient.logLevel <= logger.LogLevel.ERROR) {
  169. const args = obj.map(argToString);
  170. logClient.error(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
  171. }
  172. }
  173. /**
  174. * @internal
  175. */
  176. function logWarn(msg, ...obj) {
  177. if (logClient.logLevel <= logger.LogLevel.WARN) {
  178. const args = obj.map(argToString);
  179. logClient.warn(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
  180. }
  181. }
  182. /**
  183. * Converts an additional log parameter to a string representation.
  184. */
  185. function argToString(obj) {
  186. if (typeof obj === 'string') {
  187. return obj;
  188. }
  189. else {
  190. try {
  191. return formatJSON(obj);
  192. }
  193. catch (e) {
  194. // Converting to JSON failed, just log the object directly
  195. return obj;
  196. }
  197. }
  198. }
  199. /**
  200. * @license
  201. * Copyright 2017 Google LLC
  202. *
  203. * Licensed under the Apache License, Version 2.0 (the "License");
  204. * you may not use this file except in compliance with the License.
  205. * You may obtain a copy of the License at
  206. *
  207. * http://www.apache.org/licenses/LICENSE-2.0
  208. *
  209. * Unless required by applicable law or agreed to in writing, software
  210. * distributed under the License is distributed on an "AS IS" BASIS,
  211. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  212. * See the License for the specific language governing permissions and
  213. * limitations under the License.
  214. */
  215. /**
  216. * Unconditionally fails, throwing an Error with the given message.
  217. * Messages are stripped in production builds.
  218. *
  219. * Returns `never` and can be used in expressions:
  220. * @example
  221. * let futureVar = fail('not implemented yet');
  222. */
  223. function fail(failure = 'Unexpected state') {
  224. // Log the failure in addition to throw an exception, just in case the
  225. // exception is swallowed.
  226. const message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ` + failure;
  227. logError(message);
  228. // NOTE: We don't use FirestoreError here because these are internal failures
  229. // that cannot be handled by the user. (Also it would create a circular
  230. // dependency between the error and assert modules which doesn't work.)
  231. throw new Error(message);
  232. }
  233. /**
  234. * Fails if the given assertion condition is false, throwing an Error with the
  235. * given message if it did.
  236. *
  237. * Messages are stripped in production builds.
  238. */
  239. function hardAssert(assertion, message) {
  240. if (!assertion) {
  241. fail();
  242. }
  243. }
  244. /**
  245. * Fails if the given assertion condition is false, throwing an Error with the
  246. * given message if it did.
  247. *
  248. * The code of callsites invoking this function are stripped out in production
  249. * builds. Any side-effects of code within the debugAssert() invocation will not
  250. * happen in this case.
  251. *
  252. * @internal
  253. */
  254. function debugAssert(assertion, message) {
  255. if (!assertion) {
  256. fail();
  257. }
  258. }
  259. /**
  260. * Casts `obj` to `T`. In non-production builds, verifies that `obj` is an
  261. * instance of `T` before casting.
  262. */
  263. function debugCast(obj,
  264. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  265. constructor) {
  266. return obj;
  267. }
  268. /**
  269. * @license
  270. * Copyright 2017 Google LLC
  271. *
  272. * Licensed under the Apache License, Version 2.0 (the "License");
  273. * you may not use this file except in compliance with the License.
  274. * You may obtain a copy of the License at
  275. *
  276. * http://www.apache.org/licenses/LICENSE-2.0
  277. *
  278. * Unless required by applicable law or agreed to in writing, software
  279. * distributed under the License is distributed on an "AS IS" BASIS,
  280. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  281. * See the License for the specific language governing permissions and
  282. * limitations under the License.
  283. */
  284. const Code = {
  285. // Causes are copied from:
  286. // https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h
  287. /** Not an error; returned on success. */
  288. OK: 'ok',
  289. /** The operation was cancelled (typically by the caller). */
  290. CANCELLED: 'cancelled',
  291. /** Unknown error or an error from a different error domain. */
  292. UNKNOWN: 'unknown',
  293. /**
  294. * Client specified an invalid argument. Note that this differs from
  295. * FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
  296. * problematic regardless of the state of the system (e.g., a malformed file
  297. * name).
  298. */
  299. INVALID_ARGUMENT: 'invalid-argument',
  300. /**
  301. * Deadline expired before operation could complete. For operations that
  302. * change the state of the system, this error may be returned even if the
  303. * operation has completed successfully. For example, a successful response
  304. * from a server could have been delayed long enough for the deadline to
  305. * expire.
  306. */
  307. DEADLINE_EXCEEDED: 'deadline-exceeded',
  308. /** Some requested entity (e.g., file or directory) was not found. */
  309. NOT_FOUND: 'not-found',
  310. /**
  311. * Some entity that we attempted to create (e.g., file or directory) already
  312. * exists.
  313. */
  314. ALREADY_EXISTS: 'already-exists',
  315. /**
  316. * The caller does not have permission to execute the specified operation.
  317. * PERMISSION_DENIED must not be used for rejections caused by exhausting
  318. * some resource (use RESOURCE_EXHAUSTED instead for those errors).
  319. * PERMISSION_DENIED must not be used if the caller can not be identified
  320. * (use UNAUTHENTICATED instead for those errors).
  321. */
  322. PERMISSION_DENIED: 'permission-denied',
  323. /**
  324. * The request does not have valid authentication credentials for the
  325. * operation.
  326. */
  327. UNAUTHENTICATED: 'unauthenticated',
  328. /**
  329. * Some resource has been exhausted, perhaps a per-user quota, or perhaps the
  330. * entire file system is out of space.
  331. */
  332. RESOURCE_EXHAUSTED: 'resource-exhausted',
  333. /**
  334. * Operation was rejected because the system is not in a state required for
  335. * the operation's execution. For example, directory to be deleted may be
  336. * non-empty, an rmdir operation is applied to a non-directory, etc.
  337. *
  338. * A litmus test that may help a service implementor in deciding
  339. * between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
  340. * (a) Use UNAVAILABLE if the client can retry just the failing call.
  341. * (b) Use ABORTED if the client should retry at a higher-level
  342. * (e.g., restarting a read-modify-write sequence).
  343. * (c) Use FAILED_PRECONDITION if the client should not retry until
  344. * the system state has been explicitly fixed. E.g., if an "rmdir"
  345. * fails because the directory is non-empty, FAILED_PRECONDITION
  346. * should be returned since the client should not retry unless
  347. * they have first fixed up the directory by deleting files from it.
  348. * (d) Use FAILED_PRECONDITION if the client performs conditional
  349. * REST Get/Update/Delete on a resource and the resource on the
  350. * server does not match the condition. E.g., conflicting
  351. * read-modify-write on the same resource.
  352. */
  353. FAILED_PRECONDITION: 'failed-precondition',
  354. /**
  355. * The operation was aborted, typically due to a concurrency issue like
  356. * sequencer check failures, transaction aborts, etc.
  357. *
  358. * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
  359. * and UNAVAILABLE.
  360. */
  361. ABORTED: 'aborted',
  362. /**
  363. * Operation was attempted past the valid range. E.g., seeking or reading
  364. * past end of file.
  365. *
  366. * Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed
  367. * if the system state changes. For example, a 32-bit file system will
  368. * generate INVALID_ARGUMENT if asked to read at an offset that is not in the
  369. * range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from
  370. * an offset past the current file size.
  371. *
  372. * There is a fair bit of overlap between FAILED_PRECONDITION and
  373. * OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
  374. * when it applies so that callers who are iterating through a space can
  375. * easily look for an OUT_OF_RANGE error to detect when they are done.
  376. */
  377. OUT_OF_RANGE: 'out-of-range',
  378. /** Operation is not implemented or not supported/enabled in this service. */
  379. UNIMPLEMENTED: 'unimplemented',
  380. /**
  381. * Internal errors. Means some invariants expected by underlying System has
  382. * been broken. If you see one of these errors, Something is very broken.
  383. */
  384. INTERNAL: 'internal',
  385. /**
  386. * The service is currently unavailable. This is a most likely a transient
  387. * condition and may be corrected by retrying with a backoff.
  388. *
  389. * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
  390. * and UNAVAILABLE.
  391. */
  392. UNAVAILABLE: 'unavailable',
  393. /** Unrecoverable data loss or corruption. */
  394. DATA_LOSS: 'data-loss'
  395. };
  396. /** An error returned by a Firestore operation. */
  397. class FirestoreError extends util.FirebaseError {
  398. /** @hideconstructor */
  399. constructor(
  400. /**
  401. * The backend error code associated with this error.
  402. */
  403. code,
  404. /**
  405. * A custom error description.
  406. */
  407. message) {
  408. super(code, message);
  409. this.code = code;
  410. this.message = message;
  411. // HACK: We write a toString property directly because Error is not a real
  412. // class and so inheritance does not work correctly. We could alternatively
  413. // do the same "back-door inheritance" trick that FirebaseError does.
  414. this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`;
  415. }
  416. }
  417. /**
  418. * @license
  419. * Copyright 2017 Google LLC
  420. *
  421. * Licensed under the Apache License, Version 2.0 (the "License");
  422. * you may not use this file except in compliance with the License.
  423. * You may obtain a copy of the License at
  424. *
  425. * http://www.apache.org/licenses/LICENSE-2.0
  426. *
  427. * Unless required by applicable law or agreed to in writing, software
  428. * distributed under the License is distributed on an "AS IS" BASIS,
  429. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  430. * See the License for the specific language governing permissions and
  431. * limitations under the License.
  432. */
  433. class Deferred {
  434. constructor() {
  435. this.promise = new Promise((resolve, reject) => {
  436. this.resolve = resolve;
  437. this.reject = reject;
  438. });
  439. }
  440. }
  441. /**
  442. * @license
  443. * Copyright 2017 Google LLC
  444. *
  445. * Licensed under the Apache License, Version 2.0 (the "License");
  446. * you may not use this file except in compliance with the License.
  447. * You may obtain a copy of the License at
  448. *
  449. * http://www.apache.org/licenses/LICENSE-2.0
  450. *
  451. * Unless required by applicable law or agreed to in writing, software
  452. * distributed under the License is distributed on an "AS IS" BASIS,
  453. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  454. * See the License for the specific language governing permissions and
  455. * limitations under the License.
  456. */
  457. class OAuthToken {
  458. constructor(value, user) {
  459. this.user = user;
  460. this.type = 'OAuth';
  461. this.headers = new Map();
  462. this.headers.set('Authorization', `Bearer ${value}`);
  463. }
  464. }
  465. /**
  466. * A CredentialsProvider that always yields an empty token.
  467. * @internal
  468. */
  469. class EmptyAuthCredentialsProvider {
  470. getToken() {
  471. return Promise.resolve(null);
  472. }
  473. invalidateToken() { }
  474. start(asyncQueue, changeListener) {
  475. // Fire with initial user.
  476. asyncQueue.enqueueRetryable(() => changeListener(User.UNAUTHENTICATED));
  477. }
  478. shutdown() { }
  479. }
  480. /**
  481. * A CredentialsProvider that always returns a constant token. Used for
  482. * emulator token mocking.
  483. */
  484. class EmulatorAuthCredentialsProvider {
  485. constructor(token) {
  486. this.token = token;
  487. /**
  488. * Stores the listener registered with setChangeListener()
  489. * This isn't actually necessary since the UID never changes, but we use this
  490. * to verify the listen contract is adhered to in tests.
  491. */
  492. this.changeListener = null;
  493. }
  494. getToken() {
  495. return Promise.resolve(this.token);
  496. }
  497. invalidateToken() { }
  498. start(asyncQueue, changeListener) {
  499. this.changeListener = changeListener;
  500. // Fire with initial user.
  501. asyncQueue.enqueueRetryable(() => changeListener(this.token.user));
  502. }
  503. shutdown() {
  504. this.changeListener = null;
  505. }
  506. }
  507. class FirebaseAuthCredentialsProvider {
  508. constructor(authProvider) {
  509. this.authProvider = authProvider;
  510. /** Tracks the current User. */
  511. this.currentUser = User.UNAUTHENTICATED;
  512. /**
  513. * Counter used to detect if the token changed while a getToken request was
  514. * outstanding.
  515. */
  516. this.tokenCounter = 0;
  517. this.forceRefresh = false;
  518. this.auth = null;
  519. }
  520. start(asyncQueue, changeListener) {
  521. let lastTokenId = this.tokenCounter;
  522. // A change listener that prevents double-firing for the same token change.
  523. const guardedChangeListener = user => {
  524. if (this.tokenCounter !== lastTokenId) {
  525. lastTokenId = this.tokenCounter;
  526. return changeListener(user);
  527. }
  528. else {
  529. return Promise.resolve();
  530. }
  531. };
  532. // A promise that can be waited on to block on the next token change.
  533. // This promise is re-created after each change.
  534. let nextToken = new Deferred();
  535. this.tokenListener = () => {
  536. this.tokenCounter++;
  537. this.currentUser = this.getUser();
  538. nextToken.resolve();
  539. nextToken = new Deferred();
  540. asyncQueue.enqueueRetryable(() => guardedChangeListener(this.currentUser));
  541. };
  542. const awaitNextToken = () => {
  543. const currentTokenAttempt = nextToken;
  544. asyncQueue.enqueueRetryable(async () => {
  545. await currentTokenAttempt.promise;
  546. await guardedChangeListener(this.currentUser);
  547. });
  548. };
  549. const registerAuth = (auth) => {
  550. logDebug('FirebaseAuthCredentialsProvider', 'Auth detected');
  551. this.auth = auth;
  552. this.auth.addAuthTokenListener(this.tokenListener);
  553. awaitNextToken();
  554. };
  555. this.authProvider.onInit(auth => registerAuth(auth));
  556. // Our users can initialize Auth right after Firestore, so we give it
  557. // a chance to register itself with the component framework before we
  558. // determine whether to start up in unauthenticated mode.
  559. setTimeout(() => {
  560. if (!this.auth) {
  561. const auth = this.authProvider.getImmediate({ optional: true });
  562. if (auth) {
  563. registerAuth(auth);
  564. }
  565. else {
  566. // If auth is still not available, proceed with `null` user
  567. logDebug('FirebaseAuthCredentialsProvider', 'Auth not yet detected');
  568. nextToken.resolve();
  569. nextToken = new Deferred();
  570. }
  571. }
  572. }, 0);
  573. awaitNextToken();
  574. }
  575. getToken() {
  576. // Take note of the current value of the tokenCounter so that this method
  577. // can fail (with an ABORTED error) if there is a token change while the
  578. // request is outstanding.
  579. const initialTokenCounter = this.tokenCounter;
  580. const forceRefresh = this.forceRefresh;
  581. this.forceRefresh = false;
  582. if (!this.auth) {
  583. return Promise.resolve(null);
  584. }
  585. return this.auth.getToken(forceRefresh).then(tokenData => {
  586. // Cancel the request since the token changed while the request was
  587. // outstanding so the response is potentially for a previous user (which
  588. // user, we can't be sure).
  589. if (this.tokenCounter !== initialTokenCounter) {
  590. logDebug('FirebaseAuthCredentialsProvider', 'getToken aborted due to token change.');
  591. return this.getToken();
  592. }
  593. else {
  594. if (tokenData) {
  595. hardAssert(typeof tokenData.accessToken === 'string');
  596. return new OAuthToken(tokenData.accessToken, this.currentUser);
  597. }
  598. else {
  599. return null;
  600. }
  601. }
  602. });
  603. }
  604. invalidateToken() {
  605. this.forceRefresh = true;
  606. }
  607. shutdown() {
  608. if (this.auth) {
  609. this.auth.removeAuthTokenListener(this.tokenListener);
  610. }
  611. }
  612. // Auth.getUid() can return null even with a user logged in. It is because
  613. // getUid() is synchronous, but the auth code populating Uid is asynchronous.
  614. // This method should only be called in the AuthTokenListener callback
  615. // to guarantee to get the actual user.
  616. getUser() {
  617. const currentUid = this.auth && this.auth.getUid();
  618. hardAssert(currentUid === null || typeof currentUid === 'string');
  619. return new User(currentUid);
  620. }
  621. }
  622. /*
  623. * FirstPartyToken provides a fresh token each time its value
  624. * is requested, because if the token is too old, requests will be rejected.
  625. * Technically this may no longer be necessary since the SDK should gracefully
  626. * recover from unauthenticated errors (see b/33147818 for context), but it's
  627. * safer to keep the implementation as-is.
  628. */
  629. class FirstPartyToken {
  630. constructor(gapi, sessionIndex, iamToken, authTokenFactory) {
  631. this.gapi = gapi;
  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. /** Gets an authorization token, using a provided factory function, or falling back to First Party GAPI. */
  640. getAuthToken() {
  641. if (this.authTokenFactory) {
  642. return this.authTokenFactory();
  643. }
  644. else {
  645. // Make sure this really is a Gapi client.
  646. hardAssert(!!(typeof this.gapi === 'object' &&
  647. this.gapi !== null &&
  648. this.gapi['auth'] &&
  649. this.gapi['auth']['getAuthHeaderValueForFirstParty']));
  650. return this.gapi['auth']['getAuthHeaderValueForFirstParty']([]);
  651. }
  652. }
  653. get headers() {
  654. this._headers.set('X-Goog-AuthUser', this.sessionIndex);
  655. // Use array notation to prevent minification
  656. const authHeaderTokenValue = this.getAuthToken();
  657. if (authHeaderTokenValue) {
  658. this._headers.set('Authorization', authHeaderTokenValue);
  659. }
  660. if (this.iamToken) {
  661. this._headers.set('X-Goog-Iam-Authorization-Token', this.iamToken);
  662. }
  663. return this._headers;
  664. }
  665. }
  666. /*
  667. * Provides user credentials required for the Firestore JavaScript SDK
  668. * to authenticate the user, using technique that is only available
  669. * to applications hosted by Google.
  670. */
  671. class FirstPartyAuthCredentialsProvider {
  672. constructor(gapi, sessionIndex, iamToken, authTokenFactory) {
  673. this.gapi = gapi;
  674. this.sessionIndex = sessionIndex;
  675. this.iamToken = iamToken;
  676. this.authTokenFactory = authTokenFactory;
  677. }
  678. getToken() {
  679. return Promise.resolve(new FirstPartyToken(this.gapi, this.sessionIndex, this.iamToken, this.authTokenFactory));
  680. }
  681. start(asyncQueue, changeListener) {
  682. // Fire with initial uid.
  683. asyncQueue.enqueueRetryable(() => changeListener(User.FIRST_PARTY));
  684. }
  685. shutdown() { }
  686. invalidateToken() { }
  687. }
  688. class AppCheckToken {
  689. constructor(value) {
  690. this.value = value;
  691. this.type = 'AppCheck';
  692. this.headers = new Map();
  693. if (value && value.length > 0) {
  694. this.headers.set('x-firebase-appcheck', this.value);
  695. }
  696. }
  697. }
  698. class FirebaseAppCheckTokenProvider {
  699. constructor(appCheckProvider) {
  700. this.appCheckProvider = appCheckProvider;
  701. this.forceRefresh = false;
  702. this.appCheck = null;
  703. this.latestAppCheckToken = null;
  704. }
  705. start(asyncQueue, changeListener) {
  706. const onTokenChanged = tokenResult => {
  707. if (tokenResult.error != null) {
  708. logDebug('FirebaseAppCheckTokenProvider', `Error getting App Check token; using placeholder token instead. Error: ${tokenResult.error.message}`);
  709. }
  710. const tokenUpdated = tokenResult.token !== this.latestAppCheckToken;
  711. this.latestAppCheckToken = tokenResult.token;
  712. logDebug('FirebaseAppCheckTokenProvider', `Received ${tokenUpdated ? 'new' : 'existing'} token.`);
  713. return tokenUpdated
  714. ? changeListener(tokenResult.token)
  715. : Promise.resolve();
  716. };
  717. this.tokenListener = (tokenResult) => {
  718. asyncQueue.enqueueRetryable(() => onTokenChanged(tokenResult));
  719. };
  720. const registerAppCheck = (appCheck) => {
  721. logDebug('FirebaseAppCheckTokenProvider', 'AppCheck detected');
  722. this.appCheck = appCheck;
  723. this.appCheck.addTokenListener(this.tokenListener);
  724. };
  725. this.appCheckProvider.onInit(appCheck => registerAppCheck(appCheck));
  726. // Our users can initialize AppCheck after Firestore, so we give it
  727. // a chance to register itself with the component framework.
  728. setTimeout(() => {
  729. if (!this.appCheck) {
  730. const appCheck = this.appCheckProvider.getImmediate({ optional: true });
  731. if (appCheck) {
  732. registerAppCheck(appCheck);
  733. }
  734. else {
  735. // If AppCheck is still not available, proceed without it.
  736. logDebug('FirebaseAppCheckTokenProvider', 'AppCheck not yet detected');
  737. }
  738. }
  739. }, 0);
  740. }
  741. getToken() {
  742. const forceRefresh = this.forceRefresh;
  743. this.forceRefresh = false;
  744. if (!this.appCheck) {
  745. return Promise.resolve(null);
  746. }
  747. return this.appCheck.getToken(forceRefresh).then(tokenResult => {
  748. if (tokenResult) {
  749. hardAssert(typeof tokenResult.token === 'string');
  750. this.latestAppCheckToken = tokenResult.token;
  751. return new AppCheckToken(tokenResult.token);
  752. }
  753. else {
  754. return null;
  755. }
  756. });
  757. }
  758. invalidateToken() {
  759. this.forceRefresh = true;
  760. }
  761. shutdown() {
  762. if (this.appCheck) {
  763. this.appCheck.removeTokenListener(this.tokenListener);
  764. }
  765. }
  766. }
  767. /**
  768. * An AppCheck token provider that always yields an empty token.
  769. * @internal
  770. */
  771. class EmptyAppCheckTokenProvider {
  772. getToken() {
  773. return Promise.resolve(new AppCheckToken(''));
  774. }
  775. invalidateToken() { }
  776. start(asyncQueue, changeListener) { }
  777. shutdown() { }
  778. }
  779. /**
  780. * Builds a CredentialsProvider depending on the type of
  781. * the credentials passed in.
  782. */
  783. function makeAuthCredentialsProvider(credentials) {
  784. if (!credentials) {
  785. return new EmptyAuthCredentialsProvider();
  786. }
  787. switch (credentials['type']) {
  788. case 'gapi':
  789. const client = credentials['client'];
  790. return new FirstPartyAuthCredentialsProvider(client, credentials['sessionIndex'] || '0', credentials['iamToken'] || null, credentials['authTokenFactory'] || null);
  791. case 'provider':
  792. return credentials['client'];
  793. default:
  794. throw new FirestoreError(Code.INVALID_ARGUMENT, 'makeAuthCredentialsProvider failed due to invalid credential type');
  795. }
  796. }
  797. /**
  798. * @license
  799. * Copyright 2020 Google LLC
  800. *
  801. * Licensed under the Apache License, Version 2.0 (the "License");
  802. * you may not use this file except in compliance with the License.
  803. * You may obtain a copy of the License at
  804. *
  805. * http://www.apache.org/licenses/LICENSE-2.0
  806. *
  807. * Unless required by applicable law or agreed to in writing, software
  808. * distributed under the License is distributed on an "AS IS" BASIS,
  809. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  810. * See the License for the specific language governing permissions and
  811. * limitations under the License.
  812. */
  813. /**
  814. * Generates `nBytes` of random bytes.
  815. *
  816. * If `nBytes < 0` , an error will be thrown.
  817. */
  818. function randomBytes(nBytes) {
  819. return crypto.randomBytes(nBytes);
  820. }
  821. /**
  822. * @license
  823. * Copyright 2017 Google LLC
  824. *
  825. * Licensed under the Apache License, Version 2.0 (the "License");
  826. * you may not use this file except in compliance with the License.
  827. * You may obtain a copy of the License at
  828. *
  829. * http://www.apache.org/licenses/LICENSE-2.0
  830. *
  831. * Unless required by applicable law or agreed to in writing, software
  832. * distributed under the License is distributed on an "AS IS" BASIS,
  833. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  834. * See the License for the specific language governing permissions and
  835. * limitations under the License.
  836. */
  837. class AutoId {
  838. static newId() {
  839. // Alphanumeric characters
  840. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  841. // The largest byte value that is a multiple of `char.length`.
  842. const maxMultiple = Math.floor(256 / chars.length) * chars.length;
  843. let autoId = '';
  844. const targetLength = 20;
  845. while (autoId.length < targetLength) {
  846. const bytes = randomBytes(40);
  847. for (let i = 0; i < bytes.length; ++i) {
  848. // Only accept values that are [0, maxMultiple), this ensures they can
  849. // be evenly mapped to indices of `chars` via a modulo operation.
  850. if (autoId.length < targetLength && bytes[i] < maxMultiple) {
  851. autoId += chars.charAt(bytes[i] % chars.length);
  852. }
  853. }
  854. }
  855. return autoId;
  856. }
  857. }
  858. function primitiveComparator(left, right) {
  859. if (left < right) {
  860. return -1;
  861. }
  862. if (left > right) {
  863. return 1;
  864. }
  865. return 0;
  866. }
  867. /** Helper to compare arrays using isEqual(). */
  868. function arrayEquals(left, right, comparator) {
  869. if (left.length !== right.length) {
  870. return false;
  871. }
  872. return left.every((value, index) => comparator(value, right[index]));
  873. }
  874. /**
  875. * Returns the immediate lexicographically-following string. This is useful to
  876. * construct an inclusive range for indexeddb iterators.
  877. */
  878. function immediateSuccessor(s) {
  879. // Return the input string, with an additional NUL byte appended.
  880. return s + '\0';
  881. }
  882. /**
  883. * @license
  884. * Copyright 2017 Google LLC
  885. *
  886. * Licensed under the Apache License, Version 2.0 (the "License");
  887. * you may not use this file except in compliance with the License.
  888. * You may obtain a copy of the License at
  889. *
  890. * http://www.apache.org/licenses/LICENSE-2.0
  891. *
  892. * Unless required by applicable law or agreed to in writing, software
  893. * distributed under the License is distributed on an "AS IS" BASIS,
  894. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  895. * See the License for the specific language governing permissions and
  896. * limitations under the License.
  897. */
  898. // The earliest date supported by Firestore timestamps (0001-01-01T00:00:00Z).
  899. const MIN_SECONDS = -62135596800;
  900. // Number of nanoseconds in a millisecond.
  901. const MS_TO_NANOS = 1e6;
  902. /**
  903. * A `Timestamp` represents a point in time independent of any time zone or
  904. * calendar, represented as seconds and fractions of seconds at nanosecond
  905. * resolution in UTC Epoch time.
  906. *
  907. * It is encoded using the Proleptic Gregorian Calendar which extends the
  908. * Gregorian calendar backwards to year one. It is encoded assuming all minutes
  909. * are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
  910. * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
  911. * 9999-12-31T23:59:59.999999999Z.
  912. *
  913. * For examples and further specifications, refer to the
  914. * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}.
  915. */
  916. class Timestamp {
  917. /**
  918. * Creates a new timestamp.
  919. *
  920. * @param seconds - The number of seconds of UTC time since Unix epoch
  921. * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
  922. * 9999-12-31T23:59:59Z inclusive.
  923. * @param nanoseconds - The non-negative fractions of a second at nanosecond
  924. * resolution. Negative second values with fractions must still have
  925. * non-negative nanoseconds values that count forward in time. Must be
  926. * from 0 to 999,999,999 inclusive.
  927. */
  928. constructor(
  929. /**
  930. * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
  931. */
  932. seconds,
  933. /**
  934. * The fractions of a second at nanosecond resolution.*
  935. */
  936. nanoseconds) {
  937. this.seconds = seconds;
  938. this.nanoseconds = nanoseconds;
  939. if (nanoseconds < 0) {
  940. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
  941. }
  942. if (nanoseconds >= 1e9) {
  943. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
  944. }
  945. if (seconds < MIN_SECONDS) {
  946. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
  947. }
  948. // This will break in the year 10,000.
  949. if (seconds >= 253402300800) {
  950. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
  951. }
  952. }
  953. /**
  954. * Creates a new timestamp with the current date, with millisecond precision.
  955. *
  956. * @returns a new timestamp representing the current date.
  957. */
  958. static now() {
  959. return Timestamp.fromMillis(Date.now());
  960. }
  961. /**
  962. * Creates a new timestamp from the given date.
  963. *
  964. * @param date - The date to initialize the `Timestamp` from.
  965. * @returns A new `Timestamp` representing the same point in time as the given
  966. * date.
  967. */
  968. static fromDate(date) {
  969. return Timestamp.fromMillis(date.getTime());
  970. }
  971. /**
  972. * Creates a new timestamp from the given number of milliseconds.
  973. *
  974. * @param milliseconds - Number of milliseconds since Unix epoch
  975. * 1970-01-01T00:00:00Z.
  976. * @returns A new `Timestamp` representing the same point in time as the given
  977. * number of milliseconds.
  978. */
  979. static fromMillis(milliseconds) {
  980. const seconds = Math.floor(milliseconds / 1000);
  981. const nanos = Math.floor((milliseconds - seconds * 1000) * MS_TO_NANOS);
  982. return new Timestamp(seconds, nanos);
  983. }
  984. /**
  985. * Converts a `Timestamp` to a JavaScript `Date` object. This conversion
  986. * causes a loss of precision since `Date` objects only support millisecond
  987. * precision.
  988. *
  989. * @returns JavaScript `Date` object representing the same point in time as
  990. * this `Timestamp`, with millisecond precision.
  991. */
  992. toDate() {
  993. return new Date(this.toMillis());
  994. }
  995. /**
  996. * Converts a `Timestamp` to a numeric timestamp (in milliseconds since
  997. * epoch). This operation causes a loss of precision.
  998. *
  999. * @returns The point in time corresponding to this timestamp, represented as
  1000. * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
  1001. */
  1002. toMillis() {
  1003. return this.seconds * 1000 + this.nanoseconds / MS_TO_NANOS;
  1004. }
  1005. _compareTo(other) {
  1006. if (this.seconds === other.seconds) {
  1007. return primitiveComparator(this.nanoseconds, other.nanoseconds);
  1008. }
  1009. return primitiveComparator(this.seconds, other.seconds);
  1010. }
  1011. /**
  1012. * Returns true if this `Timestamp` is equal to the provided one.
  1013. *
  1014. * @param other - The `Timestamp` to compare against.
  1015. * @returns true if this `Timestamp` is equal to the provided one.
  1016. */
  1017. isEqual(other) {
  1018. return (other.seconds === this.seconds && other.nanoseconds === this.nanoseconds);
  1019. }
  1020. /** Returns a textual representation of this `Timestamp`. */
  1021. toString() {
  1022. return ('Timestamp(seconds=' +
  1023. this.seconds +
  1024. ', nanoseconds=' +
  1025. this.nanoseconds +
  1026. ')');
  1027. }
  1028. /** Returns a JSON-serializable representation of this `Timestamp`. */
  1029. toJSON() {
  1030. return { seconds: this.seconds, nanoseconds: this.nanoseconds };
  1031. }
  1032. /**
  1033. * Converts this object to a primitive string, which allows `Timestamp` objects
  1034. * to be compared using the `>`, `<=`, `>=` and `>` operators.
  1035. */
  1036. valueOf() {
  1037. // This method returns a string of the form <seconds>.<nanoseconds> where
  1038. // <seconds> is translated to have a non-negative value and both <seconds>
  1039. // and <nanoseconds> are left-padded with zeroes to be a consistent length.
  1040. // Strings with this format then have a lexiographical ordering that matches
  1041. // the expected ordering. The <seconds> translation is done to avoid having
  1042. // a leading negative sign (i.e. a leading '-' character) in its string
  1043. // representation, which would affect its lexiographical ordering.
  1044. const adjustedSeconds = this.seconds - MIN_SECONDS;
  1045. // Note: Up to 12 decimal digits are required to represent all valid
  1046. // 'seconds' values.
  1047. const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
  1048. const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
  1049. return formattedSeconds + '.' + formattedNanoseconds;
  1050. }
  1051. }
  1052. /**
  1053. * @license
  1054. * Copyright 2017 Google LLC
  1055. *
  1056. * Licensed under the Apache License, Version 2.0 (the "License");
  1057. * you may not use this file except in compliance with the License.
  1058. * You may obtain a copy of the License at
  1059. *
  1060. * http://www.apache.org/licenses/LICENSE-2.0
  1061. *
  1062. * Unless required by applicable law or agreed to in writing, software
  1063. * distributed under the License is distributed on an "AS IS" BASIS,
  1064. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1065. * See the License for the specific language governing permissions and
  1066. * limitations under the License.
  1067. */
  1068. /**
  1069. * A version of a document in Firestore. This corresponds to the version
  1070. * timestamp, such as update_time or read_time.
  1071. */
  1072. class SnapshotVersion {
  1073. constructor(timestamp) {
  1074. this.timestamp = timestamp;
  1075. }
  1076. static fromTimestamp(value) {
  1077. return new SnapshotVersion(value);
  1078. }
  1079. static min() {
  1080. return new SnapshotVersion(new Timestamp(0, 0));
  1081. }
  1082. static max() {
  1083. return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
  1084. }
  1085. compareTo(other) {
  1086. return this.timestamp._compareTo(other.timestamp);
  1087. }
  1088. isEqual(other) {
  1089. return this.timestamp.isEqual(other.timestamp);
  1090. }
  1091. /** Returns a number representation of the version for use in spec tests. */
  1092. toMicroseconds() {
  1093. // Convert to microseconds.
  1094. return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;
  1095. }
  1096. toString() {
  1097. return 'SnapshotVersion(' + this.timestamp.toString() + ')';
  1098. }
  1099. toTimestamp() {
  1100. return this.timestamp;
  1101. }
  1102. }
  1103. /**
  1104. * @license
  1105. * Copyright 2017 Google LLC
  1106. *
  1107. * Licensed under the Apache License, Version 2.0 (the "License");
  1108. * you may not use this file except in compliance with the License.
  1109. * You may obtain a copy of the License at
  1110. *
  1111. * http://www.apache.org/licenses/LICENSE-2.0
  1112. *
  1113. * Unless required by applicable law or agreed to in writing, software
  1114. * distributed under the License is distributed on an "AS IS" BASIS,
  1115. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1116. * See the License for the specific language governing permissions and
  1117. * limitations under the License.
  1118. */
  1119. const DOCUMENT_KEY_NAME = '__name__';
  1120. /**
  1121. * Path represents an ordered sequence of string segments.
  1122. */
  1123. class BasePath {
  1124. constructor(segments, offset, length) {
  1125. if (offset === undefined) {
  1126. offset = 0;
  1127. }
  1128. else if (offset > segments.length) {
  1129. fail();
  1130. }
  1131. if (length === undefined) {
  1132. length = segments.length - offset;
  1133. }
  1134. else if (length > segments.length - offset) {
  1135. fail();
  1136. }
  1137. this.segments = segments;
  1138. this.offset = offset;
  1139. this.len = length;
  1140. }
  1141. get length() {
  1142. return this.len;
  1143. }
  1144. isEqual(other) {
  1145. return BasePath.comparator(this, other) === 0;
  1146. }
  1147. child(nameOrPath) {
  1148. const segments = this.segments.slice(this.offset, this.limit());
  1149. if (nameOrPath instanceof BasePath) {
  1150. nameOrPath.forEach(segment => {
  1151. segments.push(segment);
  1152. });
  1153. }
  1154. else {
  1155. segments.push(nameOrPath);
  1156. }
  1157. return this.construct(segments);
  1158. }
  1159. /** The index of one past the last segment of the path. */
  1160. limit() {
  1161. return this.offset + this.length;
  1162. }
  1163. popFirst(size) {
  1164. size = size === undefined ? 1 : size;
  1165. return this.construct(this.segments, this.offset + size, this.length - size);
  1166. }
  1167. popLast() {
  1168. return this.construct(this.segments, this.offset, this.length - 1);
  1169. }
  1170. firstSegment() {
  1171. return this.segments[this.offset];
  1172. }
  1173. lastSegment() {
  1174. return this.get(this.length - 1);
  1175. }
  1176. get(index) {
  1177. return this.segments[this.offset + index];
  1178. }
  1179. isEmpty() {
  1180. return this.length === 0;
  1181. }
  1182. isPrefixOf(other) {
  1183. if (other.length < this.length) {
  1184. return false;
  1185. }
  1186. for (let i = 0; i < this.length; i++) {
  1187. if (this.get(i) !== other.get(i)) {
  1188. return false;
  1189. }
  1190. }
  1191. return true;
  1192. }
  1193. isImmediateParentOf(potentialChild) {
  1194. if (this.length + 1 !== potentialChild.length) {
  1195. return false;
  1196. }
  1197. for (let i = 0; i < this.length; i++) {
  1198. if (this.get(i) !== potentialChild.get(i)) {
  1199. return false;
  1200. }
  1201. }
  1202. return true;
  1203. }
  1204. forEach(fn) {
  1205. for (let i = this.offset, end = this.limit(); i < end; i++) {
  1206. fn(this.segments[i]);
  1207. }
  1208. }
  1209. toArray() {
  1210. return this.segments.slice(this.offset, this.limit());
  1211. }
  1212. static comparator(p1, p2) {
  1213. const len = Math.min(p1.length, p2.length);
  1214. for (let i = 0; i < len; i++) {
  1215. const left = p1.get(i);
  1216. const right = p2.get(i);
  1217. if (left < right) {
  1218. return -1;
  1219. }
  1220. if (left > right) {
  1221. return 1;
  1222. }
  1223. }
  1224. if (p1.length < p2.length) {
  1225. return -1;
  1226. }
  1227. if (p1.length > p2.length) {
  1228. return 1;
  1229. }
  1230. return 0;
  1231. }
  1232. }
  1233. /**
  1234. * A slash-separated path for navigating resources (documents and collections)
  1235. * within Firestore.
  1236. *
  1237. * @internal
  1238. */
  1239. class ResourcePath extends BasePath {
  1240. construct(segments, offset, length) {
  1241. return new ResourcePath(segments, offset, length);
  1242. }
  1243. canonicalString() {
  1244. // NOTE: The client is ignorant of any path segments containing escape
  1245. // sequences (e.g. __id123__) and just passes them through raw (they exist
  1246. // for legacy reasons and should not be used frequently).
  1247. return this.toArray().join('/');
  1248. }
  1249. toString() {
  1250. return this.canonicalString();
  1251. }
  1252. /**
  1253. * Creates a resource path from the given slash-delimited string. If multiple
  1254. * arguments are provided, all components are combined. Leading and trailing
  1255. * slashes from all components are ignored.
  1256. */
  1257. static fromString(...pathComponents) {
  1258. // NOTE: The client is ignorant of any path segments containing escape
  1259. // sequences (e.g. __id123__) and just passes them through raw (they exist
  1260. // for legacy reasons and should not be used frequently).
  1261. const segments = [];
  1262. for (const path of pathComponents) {
  1263. if (path.indexOf('//') >= 0) {
  1264. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid segment (${path}). Paths must not contain // in them.`);
  1265. }
  1266. // Strip leading and traling slashed.
  1267. segments.push(...path.split('/').filter(segment => segment.length > 0));
  1268. }
  1269. return new ResourcePath(segments);
  1270. }
  1271. static emptyPath() {
  1272. return new ResourcePath([]);
  1273. }
  1274. }
  1275. const identifierRegExp = /^[_a-zA-Z][_a-zA-Z0-9]*$/;
  1276. /**
  1277. * A dot-separated path for navigating sub-objects within a document.
  1278. * @internal
  1279. */
  1280. class FieldPath$1 extends BasePath {
  1281. construct(segments, offset, length) {
  1282. return new FieldPath$1(segments, offset, length);
  1283. }
  1284. /**
  1285. * Returns true if the string could be used as a segment in a field path
  1286. * without escaping.
  1287. */
  1288. static isValidIdentifier(segment) {
  1289. return identifierRegExp.test(segment);
  1290. }
  1291. canonicalString() {
  1292. return this.toArray()
  1293. .map(str => {
  1294. str = str.replace(/\\/g, '\\\\').replace(/`/g, '\\`');
  1295. if (!FieldPath$1.isValidIdentifier(str)) {
  1296. str = '`' + str + '`';
  1297. }
  1298. return str;
  1299. })
  1300. .join('.');
  1301. }
  1302. toString() {
  1303. return this.canonicalString();
  1304. }
  1305. /**
  1306. * Returns true if this field references the key of a document.
  1307. */
  1308. isKeyField() {
  1309. return this.length === 1 && this.get(0) === DOCUMENT_KEY_NAME;
  1310. }
  1311. /**
  1312. * The field designating the key of a document.
  1313. */
  1314. static keyField() {
  1315. return new FieldPath$1([DOCUMENT_KEY_NAME]);
  1316. }
  1317. /**
  1318. * Parses a field string from the given server-formatted string.
  1319. *
  1320. * - Splitting the empty string is not allowed (for now at least).
  1321. * - Empty segments within the string (e.g. if there are two consecutive
  1322. * separators) are not allowed.
  1323. *
  1324. * TODO(b/37244157): we should make this more strict. Right now, it allows
  1325. * non-identifier path components, even if they aren't escaped.
  1326. */
  1327. static fromServerFormat(path) {
  1328. const segments = [];
  1329. let current = '';
  1330. let i = 0;
  1331. const addCurrentSegment = () => {
  1332. if (current.length === 0) {
  1333. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid field path (${path}). Paths must not be empty, begin ` +
  1334. `with '.', end with '.', or contain '..'`);
  1335. }
  1336. segments.push(current);
  1337. current = '';
  1338. };
  1339. let inBackticks = false;
  1340. while (i < path.length) {
  1341. const c = path[i];
  1342. if (c === '\\') {
  1343. if (i + 1 === path.length) {
  1344. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has trailing escape character: ' + path);
  1345. }
  1346. const next = path[i + 1];
  1347. if (!(next === '\\' || next === '.' || next === '`')) {
  1348. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has invalid escape sequence: ' + path);
  1349. }
  1350. current += next;
  1351. i += 2;
  1352. }
  1353. else if (c === '`') {
  1354. inBackticks = !inBackticks;
  1355. i++;
  1356. }
  1357. else if (c === '.' && !inBackticks) {
  1358. addCurrentSegment();
  1359. i++;
  1360. }
  1361. else {
  1362. current += c;
  1363. i++;
  1364. }
  1365. }
  1366. addCurrentSegment();
  1367. if (inBackticks) {
  1368. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Unterminated ` in path: ' + path);
  1369. }
  1370. return new FieldPath$1(segments);
  1371. }
  1372. static emptyPath() {
  1373. return new FieldPath$1([]);
  1374. }
  1375. }
  1376. /**
  1377. * @license
  1378. * Copyright 2017 Google LLC
  1379. *
  1380. * Licensed under the Apache License, Version 2.0 (the "License");
  1381. * you may not use this file except in compliance with the License.
  1382. * You may obtain a copy of the License at
  1383. *
  1384. * http://www.apache.org/licenses/LICENSE-2.0
  1385. *
  1386. * Unless required by applicable law or agreed to in writing, software
  1387. * distributed under the License is distributed on an "AS IS" BASIS,
  1388. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1389. * See the License for the specific language governing permissions and
  1390. * limitations under the License.
  1391. */
  1392. /**
  1393. * @internal
  1394. */
  1395. class DocumentKey {
  1396. constructor(path) {
  1397. this.path = path;
  1398. }
  1399. static fromPath(path) {
  1400. return new DocumentKey(ResourcePath.fromString(path));
  1401. }
  1402. static fromName(name) {
  1403. return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
  1404. }
  1405. static empty() {
  1406. return new DocumentKey(ResourcePath.emptyPath());
  1407. }
  1408. get collectionGroup() {
  1409. return this.path.popLast().lastSegment();
  1410. }
  1411. /** Returns true if the document is in the specified collectionId. */
  1412. hasCollectionId(collectionId) {
  1413. return (this.path.length >= 2 &&
  1414. this.path.get(this.path.length - 2) === collectionId);
  1415. }
  1416. /** Returns the collection group (i.e. the name of the parent collection) for this key. */
  1417. getCollectionGroup() {
  1418. return this.path.get(this.path.length - 2);
  1419. }
  1420. /** Returns the fully qualified path to the parent collection. */
  1421. getCollectionPath() {
  1422. return this.path.popLast();
  1423. }
  1424. isEqual(other) {
  1425. return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
  1426. }
  1427. toString() {
  1428. return this.path.toString();
  1429. }
  1430. static comparator(k1, k2) {
  1431. return ResourcePath.comparator(k1.path, k2.path);
  1432. }
  1433. static isDocumentKey(path) {
  1434. return path.length % 2 === 0;
  1435. }
  1436. /**
  1437. * Creates and returns a new document key with the given segments.
  1438. *
  1439. * @param segments - The segments of the path to the document
  1440. * @returns A new instance of DocumentKey
  1441. */
  1442. static fromSegments(segments) {
  1443. return new DocumentKey(new ResourcePath(segments.slice()));
  1444. }
  1445. }
  1446. /**
  1447. * @license
  1448. * Copyright 2021 Google LLC
  1449. *
  1450. * Licensed under the Apache License, Version 2.0 (the "License");
  1451. * you may not use this file except in compliance with the License.
  1452. * You may obtain a copy of the License at
  1453. *
  1454. * http://www.apache.org/licenses/LICENSE-2.0
  1455. *
  1456. * Unless required by applicable law or agreed to in writing, software
  1457. * distributed under the License is distributed on an "AS IS" BASIS,
  1458. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1459. * See the License for the specific language governing permissions and
  1460. * limitations under the License.
  1461. */
  1462. /**
  1463. * The initial mutation batch id for each index. Gets updated during index
  1464. * backfill.
  1465. */
  1466. const INITIAL_LARGEST_BATCH_ID = -1;
  1467. /**
  1468. * The initial sequence number for each index. Gets updated during index
  1469. * backfill.
  1470. */
  1471. const INITIAL_SEQUENCE_NUMBER = 0;
  1472. /**
  1473. * An index definition for field indexes in Firestore.
  1474. *
  1475. * Every index is associated with a collection. The definition contains a list
  1476. * of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
  1477. * `CONTAINS` for ArrayContains/ArrayContainsAny queries).
  1478. *
  1479. * Unlike the backend, the SDK does not differentiate between collection or
  1480. * collection group-scoped indices. Every index can be used for both single
  1481. * collection and collection group queries.
  1482. */
  1483. class FieldIndex {
  1484. constructor(
  1485. /**
  1486. * The index ID. Returns -1 if the index ID is not available (e.g. the index
  1487. * has not yet been persisted).
  1488. */
  1489. indexId,
  1490. /** The collection ID this index applies to. */
  1491. collectionGroup,
  1492. /** The field segments for this index. */
  1493. fields,
  1494. /** Shows how up-to-date the index is for the current user. */
  1495. indexState) {
  1496. this.indexId = indexId;
  1497. this.collectionGroup = collectionGroup;
  1498. this.fields = fields;
  1499. this.indexState = indexState;
  1500. }
  1501. }
  1502. /** An ID for an index that has not yet been added to persistence. */
  1503. FieldIndex.UNKNOWN_ID = -1;
  1504. /** Returns the ArrayContains/ArrayContainsAny segment for this index. */
  1505. function fieldIndexGetArraySegment(fieldIndex) {
  1506. return fieldIndex.fields.find(s => s.kind === 2 /* IndexKind.CONTAINS */);
  1507. }
  1508. /** Returns all directional (ascending/descending) segments for this index. */
  1509. function fieldIndexGetDirectionalSegments(fieldIndex) {
  1510. return fieldIndex.fields.filter(s => s.kind !== 2 /* IndexKind.CONTAINS */);
  1511. }
  1512. /**
  1513. * Returns the order of the document key component for the given index.
  1514. *
  1515. * PORTING NOTE: This is only used in the Web IndexedDb implementation.
  1516. */
  1517. function fieldIndexGetKeyOrder(fieldIndex) {
  1518. const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
  1519. return directionalSegments.length === 0
  1520. ? 0 /* IndexKind.ASCENDING */
  1521. : directionalSegments[directionalSegments.length - 1].kind;
  1522. }
  1523. /**
  1524. * Compares indexes by collection group and segments. Ignores update time and
  1525. * index ID.
  1526. */
  1527. function fieldIndexSemanticComparator(left, right) {
  1528. let cmp = primitiveComparator(left.collectionGroup, right.collectionGroup);
  1529. if (cmp !== 0) {
  1530. return cmp;
  1531. }
  1532. for (let i = 0; i < Math.min(left.fields.length, right.fields.length); ++i) {
  1533. cmp = indexSegmentComparator(left.fields[i], right.fields[i]);
  1534. if (cmp !== 0) {
  1535. return cmp;
  1536. }
  1537. }
  1538. return primitiveComparator(left.fields.length, right.fields.length);
  1539. }
  1540. /** Returns a debug representation of the field index */
  1541. function fieldIndexToString(fieldIndex) {
  1542. return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
  1543. }
  1544. /** An index component consisting of field path and index type. */
  1545. class IndexSegment {
  1546. constructor(
  1547. /** The field path of the component. */
  1548. fieldPath,
  1549. /** The fields sorting order. */
  1550. kind) {
  1551. this.fieldPath = fieldPath;
  1552. this.kind = kind;
  1553. }
  1554. }
  1555. function indexSegmentComparator(left, right) {
  1556. const cmp = FieldPath$1.comparator(left.fieldPath, right.fieldPath);
  1557. if (cmp !== 0) {
  1558. return cmp;
  1559. }
  1560. return primitiveComparator(left.kind, right.kind);
  1561. }
  1562. /**
  1563. * Stores the "high water mark" that indicates how updated the Index is for the
  1564. * current user.
  1565. */
  1566. class IndexState {
  1567. constructor(
  1568. /**
  1569. * Indicates when the index was last updated (relative to other indexes).
  1570. */
  1571. sequenceNumber,
  1572. /** The the latest indexed read time, document and batch id. */
  1573. offset) {
  1574. this.sequenceNumber = sequenceNumber;
  1575. this.offset = offset;
  1576. }
  1577. /** The state of an index that has not yet been backfilled. */
  1578. static empty() {
  1579. return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
  1580. }
  1581. }
  1582. /**
  1583. * Creates an offset that matches all documents with a read time higher than
  1584. * `readTime`.
  1585. */
  1586. function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
  1587. // We want to create an offset that matches all documents with a read time
  1588. // greater than the provided read time. To do so, we technically need to
  1589. // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
  1590. // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
  1591. // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
  1592. // all valid document IDs.
  1593. const successorSeconds = readTime.toTimestamp().seconds;
  1594. const successorNanos = readTime.toTimestamp().nanoseconds + 1;
  1595. const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
  1596. ? new Timestamp(successorSeconds + 1, 0)
  1597. : new Timestamp(successorSeconds, successorNanos));
  1598. return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
  1599. }
  1600. /** Creates a new offset based on the provided document. */
  1601. function newIndexOffsetFromDocument(document) {
  1602. return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
  1603. }
  1604. /**
  1605. * Stores the latest read time, document and batch ID that were processed for an
  1606. * index.
  1607. */
  1608. class IndexOffset {
  1609. constructor(
  1610. /**
  1611. * The latest read time version that has been indexed by Firestore for this
  1612. * field index.
  1613. */
  1614. readTime,
  1615. /**
  1616. * The key of the last document that was indexed for this query. Use
  1617. * `DocumentKey.empty()` if no document has been indexed.
  1618. */
  1619. documentKey,
  1620. /*
  1621. * The largest mutation batch id that's been processed by Firestore.
  1622. */
  1623. largestBatchId) {
  1624. this.readTime = readTime;
  1625. this.documentKey = documentKey;
  1626. this.largestBatchId = largestBatchId;
  1627. }
  1628. /** Returns an offset that sorts before all regular offsets. */
  1629. static min() {
  1630. return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
  1631. }
  1632. /** Returns an offset that sorts after all regular offsets. */
  1633. static max() {
  1634. return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
  1635. }
  1636. }
  1637. function indexOffsetComparator(left, right) {
  1638. let cmp = left.readTime.compareTo(right.readTime);
  1639. if (cmp !== 0) {
  1640. return cmp;
  1641. }
  1642. cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
  1643. if (cmp !== 0) {
  1644. return cmp;
  1645. }
  1646. return primitiveComparator(left.largestBatchId, right.largestBatchId);
  1647. }
  1648. /**
  1649. * @license
  1650. * Copyright 2020 Google LLC
  1651. *
  1652. * Licensed under the Apache License, Version 2.0 (the "License");
  1653. * you may not use this file except in compliance with the License.
  1654. * You may obtain a copy of the License at
  1655. *
  1656. * http://www.apache.org/licenses/LICENSE-2.0
  1657. *
  1658. * Unless required by applicable law or agreed to in writing, software
  1659. * distributed under the License is distributed on an "AS IS" BASIS,
  1660. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1661. * See the License for the specific language governing permissions and
  1662. * limitations under the License.
  1663. */
  1664. const PRIMARY_LEASE_LOST_ERROR_MSG = 'The current tab is not in the required state to perform this operation. ' +
  1665. 'It might be necessary to refresh the browser tab.';
  1666. /**
  1667. * A base class representing a persistence transaction, encapsulating both the
  1668. * transaction's sequence numbers as well as a list of onCommitted listeners.
  1669. *
  1670. * When you call Persistence.runTransaction(), it will create a transaction and
  1671. * pass it to your callback. You then pass it to any method that operates
  1672. * on persistence.
  1673. */
  1674. class PersistenceTransaction {
  1675. constructor() {
  1676. this.onCommittedListeners = [];
  1677. }
  1678. addOnCommittedListener(listener) {
  1679. this.onCommittedListeners.push(listener);
  1680. }
  1681. raiseOnCommittedEvent() {
  1682. this.onCommittedListeners.forEach(listener => listener());
  1683. }
  1684. }
  1685. /**
  1686. * @license
  1687. * Copyright 2017 Google LLC
  1688. *
  1689. * Licensed under the Apache License, Version 2.0 (the "License");
  1690. * you may not use this file except in compliance with the License.
  1691. * You may obtain a copy of the License at
  1692. *
  1693. * http://www.apache.org/licenses/LICENSE-2.0
  1694. *
  1695. * Unless required by applicable law or agreed to in writing, software
  1696. * distributed under the License is distributed on an "AS IS" BASIS,
  1697. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1698. * See the License for the specific language governing permissions and
  1699. * limitations under the License.
  1700. */
  1701. /**
  1702. * Verifies the error thrown by a LocalStore operation. If a LocalStore
  1703. * operation fails because the primary lease has been taken by another client,
  1704. * we ignore the error (the persistence layer will immediately call
  1705. * `applyPrimaryLease` to propagate the primary state change). All other errors
  1706. * are re-thrown.
  1707. *
  1708. * @param err - An error returned by a LocalStore operation.
  1709. * @returns A Promise that resolves after we recovered, or the original error.
  1710. */
  1711. async function ignoreIfPrimaryLeaseLoss(err) {
  1712. if (err.code === Code.FAILED_PRECONDITION &&
  1713. err.message === PRIMARY_LEASE_LOST_ERROR_MSG) {
  1714. logDebug('LocalStore', 'Unexpectedly lost primary lease');
  1715. }
  1716. else {
  1717. throw err;
  1718. }
  1719. }
  1720. /**
  1721. * @license
  1722. * Copyright 2017 Google LLC
  1723. *
  1724. * Licensed under the Apache License, Version 2.0 (the "License");
  1725. * you may not use this file except in compliance with the License.
  1726. * You may obtain a copy of the License at
  1727. *
  1728. * http://www.apache.org/licenses/LICENSE-2.0
  1729. *
  1730. * Unless required by applicable law or agreed to in writing, software
  1731. * distributed under the License is distributed on an "AS IS" BASIS,
  1732. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1733. * See the License for the specific language governing permissions and
  1734. * limitations under the License.
  1735. */
  1736. /**
  1737. * PersistencePromise is essentially a re-implementation of Promise except
  1738. * it has a .next() method instead of .then() and .next() and .catch() callbacks
  1739. * are executed synchronously when a PersistencePromise resolves rather than
  1740. * asynchronously (Promise implementations use setImmediate() or similar).
  1741. *
  1742. * This is necessary to interoperate with IndexedDB which will automatically
  1743. * commit transactions if control is returned to the event loop without
  1744. * synchronously initiating another operation on the transaction.
  1745. *
  1746. * NOTE: .then() and .catch() only allow a single consumer, unlike normal
  1747. * Promises.
  1748. */
  1749. class PersistencePromise {
  1750. constructor(callback) {
  1751. // NOTE: next/catchCallback will always point to our own wrapper functions,
  1752. // not the user's raw next() or catch() callbacks.
  1753. this.nextCallback = null;
  1754. this.catchCallback = null;
  1755. // When the operation resolves, we'll set result or error and mark isDone.
  1756. this.result = undefined;
  1757. this.error = undefined;
  1758. this.isDone = false;
  1759. // Set to true when .then() or .catch() are called and prevents additional
  1760. // chaining.
  1761. this.callbackAttached = false;
  1762. callback(value => {
  1763. this.isDone = true;
  1764. this.result = value;
  1765. if (this.nextCallback) {
  1766. // value should be defined unless T is Void, but we can't express
  1767. // that in the type system.
  1768. this.nextCallback(value);
  1769. }
  1770. }, error => {
  1771. this.isDone = true;
  1772. this.error = error;
  1773. if (this.catchCallback) {
  1774. this.catchCallback(error);
  1775. }
  1776. });
  1777. }
  1778. catch(fn) {
  1779. return this.next(undefined, fn);
  1780. }
  1781. next(nextFn, catchFn) {
  1782. if (this.callbackAttached) {
  1783. fail();
  1784. }
  1785. this.callbackAttached = true;
  1786. if (this.isDone) {
  1787. if (!this.error) {
  1788. return this.wrapSuccess(nextFn, this.result);
  1789. }
  1790. else {
  1791. return this.wrapFailure(catchFn, this.error);
  1792. }
  1793. }
  1794. else {
  1795. return new PersistencePromise((resolve, reject) => {
  1796. this.nextCallback = (value) => {
  1797. this.wrapSuccess(nextFn, value).next(resolve, reject);
  1798. };
  1799. this.catchCallback = (error) => {
  1800. this.wrapFailure(catchFn, error).next(resolve, reject);
  1801. };
  1802. });
  1803. }
  1804. }
  1805. toPromise() {
  1806. return new Promise((resolve, reject) => {
  1807. this.next(resolve, reject);
  1808. });
  1809. }
  1810. wrapUserFunction(fn) {
  1811. try {
  1812. const result = fn();
  1813. if (result instanceof PersistencePromise) {
  1814. return result;
  1815. }
  1816. else {
  1817. return PersistencePromise.resolve(result);
  1818. }
  1819. }
  1820. catch (e) {
  1821. return PersistencePromise.reject(e);
  1822. }
  1823. }
  1824. wrapSuccess(nextFn, value) {
  1825. if (nextFn) {
  1826. return this.wrapUserFunction(() => nextFn(value));
  1827. }
  1828. else {
  1829. // If there's no nextFn, then R must be the same as T
  1830. return PersistencePromise.resolve(value);
  1831. }
  1832. }
  1833. wrapFailure(catchFn, error) {
  1834. if (catchFn) {
  1835. return this.wrapUserFunction(() => catchFn(error));
  1836. }
  1837. else {
  1838. return PersistencePromise.reject(error);
  1839. }
  1840. }
  1841. static resolve(result) {
  1842. return new PersistencePromise((resolve, reject) => {
  1843. resolve(result);
  1844. });
  1845. }
  1846. static reject(error) {
  1847. return new PersistencePromise((resolve, reject) => {
  1848. reject(error);
  1849. });
  1850. }
  1851. static waitFor(
  1852. // Accept all Promise types in waitFor().
  1853. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  1854. all) {
  1855. return new PersistencePromise((resolve, reject) => {
  1856. let expectedCount = 0;
  1857. let resolvedCount = 0;
  1858. let done = false;
  1859. all.forEach(element => {
  1860. ++expectedCount;
  1861. element.next(() => {
  1862. ++resolvedCount;
  1863. if (done && resolvedCount === expectedCount) {
  1864. resolve();
  1865. }
  1866. }, err => reject(err));
  1867. });
  1868. done = true;
  1869. if (resolvedCount === expectedCount) {
  1870. resolve();
  1871. }
  1872. });
  1873. }
  1874. /**
  1875. * Given an array of predicate functions that asynchronously evaluate to a
  1876. * boolean, implements a short-circuiting `or` between the results. Predicates
  1877. * will be evaluated until one of them returns `true`, then stop. The final
  1878. * result will be whether any of them returned `true`.
  1879. */
  1880. static or(predicates) {
  1881. let p = PersistencePromise.resolve(false);
  1882. for (const predicate of predicates) {
  1883. p = p.next(isTrue => {
  1884. if (isTrue) {
  1885. return PersistencePromise.resolve(isTrue);
  1886. }
  1887. else {
  1888. return predicate();
  1889. }
  1890. });
  1891. }
  1892. return p;
  1893. }
  1894. static forEach(collection, f) {
  1895. const promises = [];
  1896. collection.forEach((r, s) => {
  1897. promises.push(f.call(this, r, s));
  1898. });
  1899. return this.waitFor(promises);
  1900. }
  1901. /**
  1902. * Concurrently map all array elements through asynchronous function.
  1903. */
  1904. static mapArray(array, f) {
  1905. return new PersistencePromise((resolve, reject) => {
  1906. const expectedCount = array.length;
  1907. const results = new Array(expectedCount);
  1908. let resolvedCount = 0;
  1909. for (let i = 0; i < expectedCount; i++) {
  1910. const current = i;
  1911. f(array[current]).next(result => {
  1912. results[current] = result;
  1913. ++resolvedCount;
  1914. if (resolvedCount === expectedCount) {
  1915. resolve(results);
  1916. }
  1917. }, err => reject(err));
  1918. }
  1919. });
  1920. }
  1921. /**
  1922. * An alternative to recursive PersistencePromise calls, that avoids
  1923. * potential memory problems from unbounded chains of promises.
  1924. *
  1925. * The `action` will be called repeatedly while `condition` is true.
  1926. */
  1927. static doWhile(condition, action) {
  1928. return new PersistencePromise((resolve, reject) => {
  1929. const process = () => {
  1930. if (condition() === true) {
  1931. action().next(() => {
  1932. process();
  1933. }, reject);
  1934. }
  1935. else {
  1936. resolve();
  1937. }
  1938. };
  1939. process();
  1940. });
  1941. }
  1942. }
  1943. /**
  1944. * @license
  1945. * Copyright 2017 Google LLC
  1946. *
  1947. * Licensed under the Apache License, Version 2.0 (the "License");
  1948. * you may not use this file except in compliance with the License.
  1949. * You may obtain a copy of the License at
  1950. *
  1951. * http://www.apache.org/licenses/LICENSE-2.0
  1952. *
  1953. * Unless required by applicable law or agreed to in writing, software
  1954. * distributed under the License is distributed on an "AS IS" BASIS,
  1955. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1956. * See the License for the specific language governing permissions and
  1957. * limitations under the License.
  1958. */
  1959. // References to `window` are guarded by SimpleDb.isAvailable()
  1960. /* eslint-disable no-restricted-globals */
  1961. const LOG_TAG$i = 'SimpleDb';
  1962. /**
  1963. * The maximum number of retry attempts for an IndexedDb transaction that fails
  1964. * with a DOMException.
  1965. */
  1966. const TRANSACTION_RETRY_COUNT = 3;
  1967. /**
  1968. * Wraps an IDBTransaction and exposes a store() method to get a handle to a
  1969. * specific object store.
  1970. */
  1971. class SimpleDbTransaction {
  1972. constructor(action, transaction) {
  1973. this.action = action;
  1974. this.transaction = transaction;
  1975. this.aborted = false;
  1976. /**
  1977. * A `Promise` that resolves with the result of the IndexedDb transaction.
  1978. */
  1979. this.completionDeferred = new Deferred();
  1980. this.transaction.oncomplete = () => {
  1981. this.completionDeferred.resolve();
  1982. };
  1983. this.transaction.onabort = () => {
  1984. if (transaction.error) {
  1985. this.completionDeferred.reject(new IndexedDbTransactionError(action, transaction.error));
  1986. }
  1987. else {
  1988. this.completionDeferred.resolve();
  1989. }
  1990. };
  1991. this.transaction.onerror = (event) => {
  1992. const error = checkForAndReportiOSError(event.target.error);
  1993. this.completionDeferred.reject(new IndexedDbTransactionError(action, error));
  1994. };
  1995. }
  1996. static open(db, action, mode, objectStoreNames) {
  1997. try {
  1998. return new SimpleDbTransaction(action, db.transaction(objectStoreNames, mode));
  1999. }
  2000. catch (e) {
  2001. throw new IndexedDbTransactionError(action, e);
  2002. }
  2003. }
  2004. get completionPromise() {
  2005. return this.completionDeferred.promise;
  2006. }
  2007. abort(error) {
  2008. if (error) {
  2009. this.completionDeferred.reject(error);
  2010. }
  2011. if (!this.aborted) {
  2012. logDebug(LOG_TAG$i, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
  2013. this.aborted = true;
  2014. this.transaction.abort();
  2015. }
  2016. }
  2017. maybeCommit() {
  2018. // If the browser supports V3 IndexedDB, we invoke commit() explicitly to
  2019. // speed up index DB processing if the event loop remains blocks.
  2020. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  2021. const maybeV3IndexedDb = this.transaction;
  2022. if (!this.aborted && typeof maybeV3IndexedDb.commit === 'function') {
  2023. maybeV3IndexedDb.commit();
  2024. }
  2025. }
  2026. /**
  2027. * Returns a SimpleDbStore<KeyType, ValueType> for the specified store. All
  2028. * operations performed on the SimpleDbStore happen within the context of this
  2029. * transaction and it cannot be used anymore once the transaction is
  2030. * completed.
  2031. *
  2032. * Note that we can't actually enforce that the KeyType and ValueType are
  2033. * correct, but they allow type safety through the rest of the consuming code.
  2034. */
  2035. store(storeName) {
  2036. const store = this.transaction.objectStore(storeName);
  2037. return new SimpleDbStore(store);
  2038. }
  2039. }
  2040. /**
  2041. * Provides a wrapper around IndexedDb with a simplified interface that uses
  2042. * Promise-like return values to chain operations. Real promises cannot be used
  2043. * since .then() continuations are executed asynchronously (e.g. via
  2044. * .setImmediate), which would cause IndexedDB to end the transaction.
  2045. * See PersistencePromise for more details.
  2046. */
  2047. class SimpleDb {
  2048. /*
  2049. * Creates a new SimpleDb wrapper for IndexedDb database `name`.
  2050. *
  2051. * Note that `version` must not be a downgrade. IndexedDB does not support
  2052. * downgrading the schema version. We currently do not support any way to do
  2053. * versioning outside of IndexedDB's versioning mechanism, as only
  2054. * version-upgrade transactions are allowed to do things like create
  2055. * objectstores.
  2056. */
  2057. constructor(name, version, schemaConverter) {
  2058. this.name = name;
  2059. this.version = version;
  2060. this.schemaConverter = schemaConverter;
  2061. const iOSVersion = SimpleDb.getIOSVersion(util.getUA());
  2062. // NOTE: According to https://bugs.webkit.org/show_bug.cgi?id=197050, the
  2063. // bug we're checking for should exist in iOS >= 12.2 and < 13, but for
  2064. // whatever reason it's much harder to hit after 12.2 so we only proactively
  2065. // log on 12.2.
  2066. if (iOSVersion === 12.2) {
  2067. logError('Firestore persistence suffers from a bug in iOS 12.2 ' +
  2068. 'Safari that may cause your app to stop working. See ' +
  2069. 'https://stackoverflow.com/q/56496296/110915 for details ' +
  2070. 'and a potential workaround.');
  2071. }
  2072. }
  2073. /** Deletes the specified database. */
  2074. static delete(name) {
  2075. logDebug(LOG_TAG$i, 'Removing database:', name);
  2076. return wrapRequest(window.indexedDB.deleteDatabase(name)).toPromise();
  2077. }
  2078. /** Returns true if IndexedDB is available in the current environment. */
  2079. static isAvailable() {
  2080. if (!util.isIndexedDBAvailable()) {
  2081. return false;
  2082. }
  2083. if (SimpleDb.isMockPersistence()) {
  2084. return true;
  2085. }
  2086. // We extensively use indexed array values and compound keys,
  2087. // which IE and Edge do not support. However, they still have indexedDB
  2088. // defined on the window, so we need to check for them here and make sure
  2089. // to return that persistence is not enabled for those browsers.
  2090. // For tracking support of this feature, see here:
  2091. // https://developer.microsoft.com/en-us/microsoft-edge/platform/status/indexeddbarraysandmultientrysupport/
  2092. // Check the UA string to find out the browser.
  2093. const ua = util.getUA();
  2094. // IE 10
  2095. // ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';
  2096. // IE 11
  2097. // ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
  2098. // Edge
  2099. // ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML,
  2100. // like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';
  2101. // iOS Safari: Disable for users running iOS version < 10.
  2102. const iOSVersion = SimpleDb.getIOSVersion(ua);
  2103. const isUnsupportedIOS = 0 < iOSVersion && iOSVersion < 10;
  2104. // Android browser: Disable for userse running version < 4.5.
  2105. const androidVersion = SimpleDb.getAndroidVersion(ua);
  2106. const isUnsupportedAndroid = 0 < androidVersion && androidVersion < 4.5;
  2107. if (ua.indexOf('MSIE ') > 0 ||
  2108. ua.indexOf('Trident/') > 0 ||
  2109. ua.indexOf('Edge/') > 0 ||
  2110. isUnsupportedIOS ||
  2111. isUnsupportedAndroid) {
  2112. return false;
  2113. }
  2114. else {
  2115. return true;
  2116. }
  2117. }
  2118. /**
  2119. * Returns true if the backing IndexedDB store is the Node IndexedDBShim
  2120. * (see https://github.com/axemclion/IndexedDBShim).
  2121. */
  2122. static isMockPersistence() {
  2123. var _a;
  2124. return (typeof process !== 'undefined' &&
  2125. ((_a = process.env) === null || _a === void 0 ? void 0 : _a.USE_MOCK_PERSISTENCE) === 'YES');
  2126. }
  2127. /** Helper to get a typed SimpleDbStore from a transaction. */
  2128. static getStore(txn, store) {
  2129. return txn.store(store);
  2130. }
  2131. // visible for testing
  2132. /** Parse User Agent to determine iOS version. Returns -1 if not found. */
  2133. static getIOSVersion(ua) {
  2134. const iOSVersionRegex = ua.match(/i(?:phone|pad|pod) os ([\d_]+)/i);
  2135. const version = iOSVersionRegex
  2136. ? iOSVersionRegex[1].split('_').slice(0, 2).join('.')
  2137. : '-1';
  2138. return Number(version);
  2139. }
  2140. // visible for testing
  2141. /** Parse User Agent to determine Android version. Returns -1 if not found. */
  2142. static getAndroidVersion(ua) {
  2143. const androidVersionRegex = ua.match(/Android ([\d.]+)/i);
  2144. const version = androidVersionRegex
  2145. ? androidVersionRegex[1].split('.').slice(0, 2).join('.')
  2146. : '-1';
  2147. return Number(version);
  2148. }
  2149. /**
  2150. * Opens the specified database, creating or upgrading it if necessary.
  2151. */
  2152. async ensureDb(action) {
  2153. if (!this.db) {
  2154. logDebug(LOG_TAG$i, 'Opening database:', this.name);
  2155. this.db = await new Promise((resolve, reject) => {
  2156. // TODO(mikelehen): Investigate browser compatibility.
  2157. // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
  2158. // suggests IE9 and older WebKit browsers handle upgrade
  2159. // differently. They expect setVersion, as described here:
  2160. // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion
  2161. const request = indexedDB.open(this.name, this.version);
  2162. request.onsuccess = (event) => {
  2163. const db = event.target.result;
  2164. resolve(db);
  2165. };
  2166. request.onblocked = () => {
  2167. reject(new IndexedDbTransactionError(action, 'Cannot upgrade IndexedDB schema while another tab is open. ' +
  2168. 'Close all tabs that access Firestore and reload this page to proceed.'));
  2169. };
  2170. request.onerror = (event) => {
  2171. const error = event.target.error;
  2172. if (error.name === 'VersionError') {
  2173. reject(new FirestoreError(Code.FAILED_PRECONDITION, 'A newer version of the Firestore SDK was previously used and so the persisted ' +
  2174. 'data is not compatible with the version of the SDK you are now using. The SDK ' +
  2175. 'will operate with persistence disabled. If you need persistence, please ' +
  2176. 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' +
  2177. 'data for your app to start fresh.'));
  2178. }
  2179. else if (error.name === 'InvalidStateError') {
  2180. reject(new FirestoreError(Code.FAILED_PRECONDITION, 'Unable to open an IndexedDB connection. This could be due to running in a ' +
  2181. 'private browsing session on a browser whose private browsing sessions do not ' +
  2182. 'support IndexedDB: ' +
  2183. error));
  2184. }
  2185. else {
  2186. reject(new IndexedDbTransactionError(action, error));
  2187. }
  2188. };
  2189. request.onupgradeneeded = (event) => {
  2190. logDebug(LOG_TAG$i, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
  2191. const db = event.target.result;
  2192. this.schemaConverter
  2193. .createOrUpgrade(db, request.transaction, event.oldVersion, this.version)
  2194. .next(() => {
  2195. logDebug(LOG_TAG$i, 'Database upgrade to version ' + this.version + ' complete');
  2196. });
  2197. };
  2198. });
  2199. }
  2200. if (this.versionchangelistener) {
  2201. this.db.onversionchange = event => this.versionchangelistener(event);
  2202. }
  2203. return this.db;
  2204. }
  2205. setVersionChangeListener(versionChangeListener) {
  2206. this.versionchangelistener = versionChangeListener;
  2207. if (this.db) {
  2208. this.db.onversionchange = (event) => {
  2209. return versionChangeListener(event);
  2210. };
  2211. }
  2212. }
  2213. async runTransaction(action, mode, objectStores, transactionFn) {
  2214. const readonly = mode === 'readonly';
  2215. let attemptNumber = 0;
  2216. while (true) {
  2217. ++attemptNumber;
  2218. try {
  2219. this.db = await this.ensureDb(action);
  2220. const transaction = SimpleDbTransaction.open(this.db, action, readonly ? 'readonly' : 'readwrite', objectStores);
  2221. const transactionFnResult = transactionFn(transaction)
  2222. .next(result => {
  2223. transaction.maybeCommit();
  2224. return result;
  2225. })
  2226. .catch(error => {
  2227. // Abort the transaction if there was an error.
  2228. transaction.abort(error);
  2229. // We cannot actually recover, and calling `abort()` will cause the transaction's
  2230. // completion promise to be rejected. This in turn means that we won't use
  2231. // `transactionFnResult` below. We return a rejection here so that we don't add the
  2232. // possibility of returning `void` to the type of `transactionFnResult`.
  2233. return PersistencePromise.reject(error);
  2234. })
  2235. .toPromise();
  2236. // As noted above, errors are propagated by aborting the transaction. So
  2237. // we swallow any error here to avoid the browser logging it as unhandled.
  2238. transactionFnResult.catch(() => { });
  2239. // Wait for the transaction to complete (i.e. IndexedDb's onsuccess event to
  2240. // fire), but still return the original transactionFnResult back to the
  2241. // caller.
  2242. await transaction.completionPromise;
  2243. return transactionFnResult;
  2244. }
  2245. catch (e) {
  2246. const error = e;
  2247. // TODO(schmidt-sebastian): We could probably be smarter about this and
  2248. // not retry exceptions that are likely unrecoverable (such as quota
  2249. // exceeded errors).
  2250. // Note: We cannot use an instanceof check for FirestoreException, since the
  2251. // exception is wrapped in a generic error by our async/await handling.
  2252. const retryable = error.name !== 'FirebaseError' &&
  2253. attemptNumber < TRANSACTION_RETRY_COUNT;
  2254. logDebug(LOG_TAG$i, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
  2255. this.close();
  2256. if (!retryable) {
  2257. return Promise.reject(error);
  2258. }
  2259. }
  2260. }
  2261. }
  2262. close() {
  2263. if (this.db) {
  2264. this.db.close();
  2265. }
  2266. this.db = undefined;
  2267. }
  2268. }
  2269. /**
  2270. * A controller for iterating over a key range or index. It allows an iterate
  2271. * callback to delete the currently-referenced object, or jump to a new key
  2272. * within the key range or index.
  2273. */
  2274. class IterationController {
  2275. constructor(dbCursor) {
  2276. this.dbCursor = dbCursor;
  2277. this.shouldStop = false;
  2278. this.nextKey = null;
  2279. }
  2280. get isDone() {
  2281. return this.shouldStop;
  2282. }
  2283. get skipToKey() {
  2284. return this.nextKey;
  2285. }
  2286. set cursor(value) {
  2287. this.dbCursor = value;
  2288. }
  2289. /**
  2290. * This function can be called to stop iteration at any point.
  2291. */
  2292. done() {
  2293. this.shouldStop = true;
  2294. }
  2295. /**
  2296. * This function can be called to skip to that next key, which could be
  2297. * an index or a primary key.
  2298. */
  2299. skip(key) {
  2300. this.nextKey = key;
  2301. }
  2302. /**
  2303. * Delete the current cursor value from the object store.
  2304. *
  2305. * NOTE: You CANNOT do this with a keysOnly query.
  2306. */
  2307. delete() {
  2308. return wrapRequest(this.dbCursor.delete());
  2309. }
  2310. }
  2311. /** An error that wraps exceptions that thrown during IndexedDB execution. */
  2312. class IndexedDbTransactionError extends FirestoreError {
  2313. constructor(actionName, cause) {
  2314. super(Code.UNAVAILABLE, `IndexedDB transaction '${actionName}' failed: ${cause}`);
  2315. this.name = 'IndexedDbTransactionError';
  2316. }
  2317. }
  2318. /** Verifies whether `e` is an IndexedDbTransactionError. */
  2319. function isIndexedDbTransactionError(e) {
  2320. // Use name equality, as instanceof checks on errors don't work with errors
  2321. // that wrap other errors.
  2322. return e.name === 'IndexedDbTransactionError';
  2323. }
  2324. /**
  2325. * A wrapper around an IDBObjectStore providing an API that:
  2326. *
  2327. * 1) Has generic KeyType / ValueType parameters to provide strongly-typed
  2328. * methods for acting against the object store.
  2329. * 2) Deals with IndexedDB's onsuccess / onerror event callbacks, making every
  2330. * method return a PersistencePromise instead.
  2331. * 3) Provides a higher-level API to avoid needing to do excessive wrapping of
  2332. * intermediate IndexedDB types (IDBCursorWithValue, etc.)
  2333. */
  2334. class SimpleDbStore {
  2335. constructor(store) {
  2336. this.store = store;
  2337. }
  2338. put(keyOrValue, value) {
  2339. let request;
  2340. if (value !== undefined) {
  2341. logDebug(LOG_TAG$i, 'PUT', this.store.name, keyOrValue, value);
  2342. request = this.store.put(value, keyOrValue);
  2343. }
  2344. else {
  2345. logDebug(LOG_TAG$i, 'PUT', this.store.name, '<auto-key>', keyOrValue);
  2346. request = this.store.put(keyOrValue);
  2347. }
  2348. return wrapRequest(request);
  2349. }
  2350. /**
  2351. * Adds a new value into an Object Store and returns the new key. Similar to
  2352. * IndexedDb's `add()`, this method will fail on primary key collisions.
  2353. *
  2354. * @param value - The object to write.
  2355. * @returns The key of the value to add.
  2356. */
  2357. add(value) {
  2358. logDebug(LOG_TAG$i, 'ADD', this.store.name, value, value);
  2359. const request = this.store.add(value);
  2360. return wrapRequest(request);
  2361. }
  2362. /**
  2363. * Gets the object with the specified key from the specified store, or null
  2364. * if no object exists with the specified key.
  2365. *
  2366. * @key The key of the object to get.
  2367. * @returns The object with the specified key or null if no object exists.
  2368. */
  2369. get(key) {
  2370. const request = this.store.get(key);
  2371. // We're doing an unsafe cast to ValueType.
  2372. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  2373. return wrapRequest(request).next(result => {
  2374. // Normalize nonexistence to null.
  2375. if (result === undefined) {
  2376. result = null;
  2377. }
  2378. logDebug(LOG_TAG$i, 'GET', this.store.name, key, result);
  2379. return result;
  2380. });
  2381. }
  2382. delete(key) {
  2383. logDebug(LOG_TAG$i, 'DELETE', this.store.name, key);
  2384. const request = this.store.delete(key);
  2385. return wrapRequest(request);
  2386. }
  2387. /**
  2388. * If we ever need more of the count variants, we can add overloads. For now,
  2389. * all we need is to count everything in a store.
  2390. *
  2391. * Returns the number of rows in the store.
  2392. */
  2393. count() {
  2394. logDebug(LOG_TAG$i, 'COUNT', this.store.name);
  2395. const request = this.store.count();
  2396. return wrapRequest(request);
  2397. }
  2398. loadAll(indexOrRange, range) {
  2399. const iterateOptions = this.options(indexOrRange, range);
  2400. // Use `getAll()` if the browser supports IndexedDB v3, as it is roughly
  2401. // 20% faster. Unfortunately, getAll() does not support custom indices.
  2402. if (!iterateOptions.index && typeof this.store.getAll === 'function') {
  2403. const request = this.store.getAll(iterateOptions.range);
  2404. return new PersistencePromise((resolve, reject) => {
  2405. request.onerror = (event) => {
  2406. reject(event.target.error);
  2407. };
  2408. request.onsuccess = (event) => {
  2409. resolve(event.target.result);
  2410. };
  2411. });
  2412. }
  2413. else {
  2414. const cursor = this.cursor(iterateOptions);
  2415. const results = [];
  2416. return this.iterateCursor(cursor, (key, value) => {
  2417. results.push(value);
  2418. }).next(() => {
  2419. return results;
  2420. });
  2421. }
  2422. }
  2423. /**
  2424. * Loads the first `count` elements from the provided index range. Loads all
  2425. * elements if no limit is provided.
  2426. */
  2427. loadFirst(range, count) {
  2428. const request = this.store.getAll(range, count === null ? undefined : count);
  2429. return new PersistencePromise((resolve, reject) => {
  2430. request.onerror = (event) => {
  2431. reject(event.target.error);
  2432. };
  2433. request.onsuccess = (event) => {
  2434. resolve(event.target.result);
  2435. };
  2436. });
  2437. }
  2438. deleteAll(indexOrRange, range) {
  2439. logDebug(LOG_TAG$i, 'DELETE ALL', this.store.name);
  2440. const options = this.options(indexOrRange, range);
  2441. options.keysOnly = false;
  2442. const cursor = this.cursor(options);
  2443. return this.iterateCursor(cursor, (key, value, control) => {
  2444. // NOTE: Calling delete() on a cursor is documented as more efficient than
  2445. // calling delete() on an object store with a single key
  2446. // (https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/delete),
  2447. // however, this requires us *not* to use a keysOnly cursor
  2448. // (https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/delete). We
  2449. // may want to compare the performance of each method.
  2450. return control.delete();
  2451. });
  2452. }
  2453. iterate(optionsOrCallback, callback) {
  2454. let options;
  2455. if (!callback) {
  2456. options = {};
  2457. callback = optionsOrCallback;
  2458. }
  2459. else {
  2460. options = optionsOrCallback;
  2461. }
  2462. const cursor = this.cursor(options);
  2463. return this.iterateCursor(cursor, callback);
  2464. }
  2465. /**
  2466. * Iterates over a store, but waits for the given callback to complete for
  2467. * each entry before iterating the next entry. This allows the callback to do
  2468. * asynchronous work to determine if this iteration should continue.
  2469. *
  2470. * The provided callback should return `true` to continue iteration, and
  2471. * `false` otherwise.
  2472. */
  2473. iterateSerial(callback) {
  2474. const cursorRequest = this.cursor({});
  2475. return new PersistencePromise((resolve, reject) => {
  2476. cursorRequest.onerror = (event) => {
  2477. const error = checkForAndReportiOSError(event.target.error);
  2478. reject(error);
  2479. };
  2480. cursorRequest.onsuccess = (event) => {
  2481. const cursor = event.target.result;
  2482. if (!cursor) {
  2483. resolve();
  2484. return;
  2485. }
  2486. callback(cursor.primaryKey, cursor.value).next(shouldContinue => {
  2487. if (shouldContinue) {
  2488. cursor.continue();
  2489. }
  2490. else {
  2491. resolve();
  2492. }
  2493. });
  2494. };
  2495. });
  2496. }
  2497. iterateCursor(cursorRequest, fn) {
  2498. const results = [];
  2499. return new PersistencePromise((resolve, reject) => {
  2500. cursorRequest.onerror = (event) => {
  2501. reject(event.target.error);
  2502. };
  2503. cursorRequest.onsuccess = (event) => {
  2504. const cursor = event.target.result;
  2505. if (!cursor) {
  2506. resolve();
  2507. return;
  2508. }
  2509. const controller = new IterationController(cursor);
  2510. const userResult = fn(cursor.primaryKey, cursor.value, controller);
  2511. if (userResult instanceof PersistencePromise) {
  2512. const userPromise = userResult.catch(err => {
  2513. controller.done();
  2514. return PersistencePromise.reject(err);
  2515. });
  2516. results.push(userPromise);
  2517. }
  2518. if (controller.isDone) {
  2519. resolve();
  2520. }
  2521. else if (controller.skipToKey === null) {
  2522. cursor.continue();
  2523. }
  2524. else {
  2525. cursor.continue(controller.skipToKey);
  2526. }
  2527. };
  2528. }).next(() => PersistencePromise.waitFor(results));
  2529. }
  2530. options(indexOrRange, range) {
  2531. let indexName = undefined;
  2532. if (indexOrRange !== undefined) {
  2533. if (typeof indexOrRange === 'string') {
  2534. indexName = indexOrRange;
  2535. }
  2536. else {
  2537. range = indexOrRange;
  2538. }
  2539. }
  2540. return { index: indexName, range };
  2541. }
  2542. cursor(options) {
  2543. let direction = 'next';
  2544. if (options.reverse) {
  2545. direction = 'prev';
  2546. }
  2547. if (options.index) {
  2548. const index = this.store.index(options.index);
  2549. if (options.keysOnly) {
  2550. return index.openKeyCursor(options.range, direction);
  2551. }
  2552. else {
  2553. return index.openCursor(options.range, direction);
  2554. }
  2555. }
  2556. else {
  2557. return this.store.openCursor(options.range, direction);
  2558. }
  2559. }
  2560. }
  2561. /**
  2562. * Wraps an IDBRequest in a PersistencePromise, using the onsuccess / onerror
  2563. * handlers to resolve / reject the PersistencePromise as appropriate.
  2564. */
  2565. function wrapRequest(request) {
  2566. return new PersistencePromise((resolve, reject) => {
  2567. request.onsuccess = (event) => {
  2568. const result = event.target.result;
  2569. resolve(result);
  2570. };
  2571. request.onerror = (event) => {
  2572. const error = checkForAndReportiOSError(event.target.error);
  2573. reject(error);
  2574. };
  2575. });
  2576. }
  2577. // Guard so we only report the error once.
  2578. let reportedIOSError = false;
  2579. function checkForAndReportiOSError(error) {
  2580. const iOSVersion = SimpleDb.getIOSVersion(util.getUA());
  2581. if (iOSVersion >= 12.2 && iOSVersion < 13) {
  2582. const IOS_ERROR = 'An internal error was encountered in the Indexed Database server';
  2583. if (error.message.indexOf(IOS_ERROR) >= 0) {
  2584. // Wrap error in a more descriptive one.
  2585. const newError = new FirestoreError('internal', `IOS_INDEXEDDB_BUG1: IndexedDb has thrown '${IOS_ERROR}'. This is likely ` +
  2586. `due to an unavoidable bug in iOS. See https://stackoverflow.com/q/56496296/110915 ` +
  2587. `for details and a potential workaround.`);
  2588. if (!reportedIOSError) {
  2589. reportedIOSError = true;
  2590. // Throw a global exception outside of this promise chain, for the user to
  2591. // potentially catch.
  2592. setTimeout(() => {
  2593. throw newError;
  2594. }, 0);
  2595. }
  2596. return newError;
  2597. }
  2598. }
  2599. return error;
  2600. }
  2601. const LOG_TAG$h = 'IndexBackiller';
  2602. /** How long we wait to try running index backfill after SDK initialization. */
  2603. const INITIAL_BACKFILL_DELAY_MS = 15 * 1000;
  2604. /** Minimum amount of time between backfill checks, after the first one. */
  2605. const REGULAR_BACKFILL_DELAY_MS = 60 * 1000;
  2606. /** The maximum number of documents to process each time backfill() is called. */
  2607. const MAX_DOCUMENTS_TO_PROCESS = 50;
  2608. /** This class is responsible for the scheduling of Index Backfiller. */
  2609. class IndexBackfillerScheduler {
  2610. constructor(asyncQueue, backfiller) {
  2611. this.asyncQueue = asyncQueue;
  2612. this.backfiller = backfiller;
  2613. this.task = null;
  2614. }
  2615. start() {
  2616. this.schedule(INITIAL_BACKFILL_DELAY_MS);
  2617. }
  2618. stop() {
  2619. if (this.task) {
  2620. this.task.cancel();
  2621. this.task = null;
  2622. }
  2623. }
  2624. get started() {
  2625. return this.task !== null;
  2626. }
  2627. schedule(delay) {
  2628. logDebug(LOG_TAG$h, `Scheduled in ${delay}ms`);
  2629. this.task = this.asyncQueue.enqueueAfterDelay("index_backfill" /* TimerId.IndexBackfill */, delay, async () => {
  2630. this.task = null;
  2631. try {
  2632. const documentsProcessed = await this.backfiller.backfill();
  2633. logDebug(LOG_TAG$h, `Documents written: ${documentsProcessed}`);
  2634. }
  2635. catch (e) {
  2636. if (isIndexedDbTransactionError(e)) {
  2637. logDebug(LOG_TAG$h, 'Ignoring IndexedDB error during index backfill: ', e);
  2638. }
  2639. else {
  2640. await ignoreIfPrimaryLeaseLoss(e);
  2641. }
  2642. }
  2643. await this.schedule(REGULAR_BACKFILL_DELAY_MS);
  2644. });
  2645. }
  2646. }
  2647. /** Implements the steps for backfilling indexes. */
  2648. class IndexBackfiller {
  2649. constructor(
  2650. /**
  2651. * LocalStore provides access to IndexManager and LocalDocumentView.
  2652. * These properties will update when the user changes. Consequently,
  2653. * making a local copy of IndexManager and LocalDocumentView will require
  2654. * updates over time. The simpler solution is to rely on LocalStore to have
  2655. * an up-to-date references to IndexManager and LocalDocumentStore.
  2656. */
  2657. localStore, persistence) {
  2658. this.localStore = localStore;
  2659. this.persistence = persistence;
  2660. }
  2661. async backfill(maxDocumentsToProcess = MAX_DOCUMENTS_TO_PROCESS) {
  2662. return this.persistence.runTransaction('Backfill Indexes', 'readwrite-primary', txn => this.writeIndexEntries(txn, maxDocumentsToProcess));
  2663. }
  2664. /** Writes index entries until the cap is reached. Returns the number of documents processed. */
  2665. writeIndexEntries(transation, maxDocumentsToProcess) {
  2666. const processedCollectionGroups = new Set();
  2667. let documentsRemaining = maxDocumentsToProcess;
  2668. let continueLoop = true;
  2669. return PersistencePromise.doWhile(() => continueLoop === true && documentsRemaining > 0, () => {
  2670. return this.localStore.indexManager
  2671. .getNextCollectionGroupToUpdate(transation)
  2672. .next((collectionGroup) => {
  2673. if (collectionGroup === null ||
  2674. processedCollectionGroups.has(collectionGroup)) {
  2675. continueLoop = false;
  2676. }
  2677. else {
  2678. logDebug(LOG_TAG$h, `Processing collection: ${collectionGroup}`);
  2679. return this.writeEntriesForCollectionGroup(transation, collectionGroup, documentsRemaining).next(documentsProcessed => {
  2680. documentsRemaining -= documentsProcessed;
  2681. processedCollectionGroups.add(collectionGroup);
  2682. });
  2683. }
  2684. });
  2685. }).next(() => maxDocumentsToProcess - documentsRemaining);
  2686. }
  2687. /**
  2688. * Writes entries for the provided collection group. Returns the number of documents processed.
  2689. */
  2690. writeEntriesForCollectionGroup(transaction, collectionGroup, documentsRemainingUnderCap) {
  2691. // Use the earliest offset of all field indexes to query the local cache.
  2692. return this.localStore.indexManager
  2693. .getMinOffsetFromCollectionGroup(transaction, collectionGroup)
  2694. .next(existingOffset => this.localStore.localDocuments
  2695. .getNextDocuments(transaction, collectionGroup, existingOffset, documentsRemainingUnderCap)
  2696. .next(nextBatch => {
  2697. const docs = nextBatch.changes;
  2698. return this.localStore.indexManager
  2699. .updateIndexEntries(transaction, docs)
  2700. .next(() => this.getNewOffset(existingOffset, nextBatch))
  2701. .next(newOffset => {
  2702. logDebug(LOG_TAG$h, `Updating offset: ${newOffset}`);
  2703. return this.localStore.indexManager.updateCollectionGroup(transaction, collectionGroup, newOffset);
  2704. })
  2705. .next(() => docs.size);
  2706. }));
  2707. }
  2708. /** Returns the next offset based on the provided documents. */
  2709. getNewOffset(existingOffset, lookupResult) {
  2710. let maxOffset = existingOffset;
  2711. lookupResult.changes.forEach((key, document) => {
  2712. const newOffset = newIndexOffsetFromDocument(document);
  2713. if (indexOffsetComparator(newOffset, maxOffset) > 0) {
  2714. maxOffset = newOffset;
  2715. }
  2716. });
  2717. return new IndexOffset(maxOffset.readTime, maxOffset.documentKey, Math.max(lookupResult.batchId, existingOffset.largestBatchId));
  2718. }
  2719. }
  2720. /**
  2721. * @license
  2722. * Copyright 2018 Google LLC
  2723. *
  2724. * Licensed under the Apache License, Version 2.0 (the "License");
  2725. * you may not use this file except in compliance with the License.
  2726. * You may obtain a copy of the License at
  2727. *
  2728. * http://www.apache.org/licenses/LICENSE-2.0
  2729. *
  2730. * Unless required by applicable law or agreed to in writing, software
  2731. * distributed under the License is distributed on an "AS IS" BASIS,
  2732. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2733. * See the License for the specific language governing permissions and
  2734. * limitations under the License.
  2735. */
  2736. /**
  2737. * `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to
  2738. * exceed. All subsequent calls to next will return increasing values. If provided with a
  2739. * `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as
  2740. * well as write out sequence numbers that it produces via `next()`.
  2741. */
  2742. class ListenSequence {
  2743. constructor(previousValue, sequenceNumberSyncer) {
  2744. this.previousValue = previousValue;
  2745. if (sequenceNumberSyncer) {
  2746. sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber => this.setPreviousValue(sequenceNumber);
  2747. this.writeNewSequenceNumber = sequenceNumber => sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);
  2748. }
  2749. }
  2750. setPreviousValue(externalPreviousValue) {
  2751. this.previousValue = Math.max(externalPreviousValue, this.previousValue);
  2752. return this.previousValue;
  2753. }
  2754. next() {
  2755. const nextValue = ++this.previousValue;
  2756. if (this.writeNewSequenceNumber) {
  2757. this.writeNewSequenceNumber(nextValue);
  2758. }
  2759. return nextValue;
  2760. }
  2761. }
  2762. ListenSequence.INVALID = -1;
  2763. /**
  2764. * @license
  2765. * Copyright 2017 Google LLC
  2766. *
  2767. * Licensed under the Apache License, Version 2.0 (the "License");
  2768. * you may not use this file except in compliance with the License.
  2769. * You may obtain a copy of the License at
  2770. *
  2771. * http://www.apache.org/licenses/LICENSE-2.0
  2772. *
  2773. * Unless required by applicable law or agreed to in writing, software
  2774. * distributed under the License is distributed on an "AS IS" BASIS,
  2775. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2776. * See the License for the specific language governing permissions and
  2777. * limitations under the License.
  2778. */
  2779. const escapeChar = '\u0001';
  2780. const encodedSeparatorChar = '\u0001';
  2781. const encodedNul = '\u0010';
  2782. const encodedEscape = '\u0011';
  2783. /**
  2784. * Encodes a resource path into a IndexedDb-compatible string form.
  2785. */
  2786. function encodeResourcePath(path) {
  2787. let result = '';
  2788. for (let i = 0; i < path.length; i++) {
  2789. if (result.length > 0) {
  2790. result = encodeSeparator(result);
  2791. }
  2792. result = encodeSegment(path.get(i), result);
  2793. }
  2794. return encodeSeparator(result);
  2795. }
  2796. /** Encodes a single segment of a resource path into the given result */
  2797. function encodeSegment(segment, resultBuf) {
  2798. let result = resultBuf;
  2799. const length = segment.length;
  2800. for (let i = 0; i < length; i++) {
  2801. const c = segment.charAt(i);
  2802. switch (c) {
  2803. case '\0':
  2804. result += escapeChar + encodedNul;
  2805. break;
  2806. case escapeChar:
  2807. result += escapeChar + encodedEscape;
  2808. break;
  2809. default:
  2810. result += c;
  2811. }
  2812. }
  2813. return result;
  2814. }
  2815. /** Encodes a path separator into the given result */
  2816. function encodeSeparator(result) {
  2817. return result + escapeChar + encodedSeparatorChar;
  2818. }
  2819. /**
  2820. * Decodes the given IndexedDb-compatible string form of a resource path into
  2821. * a ResourcePath instance. Note that this method is not suitable for use with
  2822. * decoding resource names from the server; those are One Platform format
  2823. * strings.
  2824. */
  2825. function decodeResourcePath(path) {
  2826. // Event the empty path must encode as a path of at least length 2. A path
  2827. // with exactly 2 must be the empty path.
  2828. const length = path.length;
  2829. hardAssert(length >= 2);
  2830. if (length === 2) {
  2831. hardAssert(path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar);
  2832. return ResourcePath.emptyPath();
  2833. }
  2834. // Escape characters cannot exist past the second-to-last position in the
  2835. // source value.
  2836. const lastReasonableEscapeIndex = length - 2;
  2837. const segments = [];
  2838. let segmentBuilder = '';
  2839. for (let start = 0; start < length;) {
  2840. // The last two characters of a valid encoded path must be a separator, so
  2841. // there must be an end to this segment.
  2842. const end = path.indexOf(escapeChar, start);
  2843. if (end < 0 || end > lastReasonableEscapeIndex) {
  2844. fail();
  2845. }
  2846. const next = path.charAt(end + 1);
  2847. switch (next) {
  2848. case encodedSeparatorChar:
  2849. const currentPiece = path.substring(start, end);
  2850. let segment;
  2851. if (segmentBuilder.length === 0) {
  2852. // Avoid copying for the common case of a segment that excludes \0
  2853. // and \001
  2854. segment = currentPiece;
  2855. }
  2856. else {
  2857. segmentBuilder += currentPiece;
  2858. segment = segmentBuilder;
  2859. segmentBuilder = '';
  2860. }
  2861. segments.push(segment);
  2862. break;
  2863. case encodedNul:
  2864. segmentBuilder += path.substring(start, end);
  2865. segmentBuilder += '\0';
  2866. break;
  2867. case encodedEscape:
  2868. // The escape character can be used in the output to encode itself.
  2869. segmentBuilder += path.substring(start, end + 1);
  2870. break;
  2871. default:
  2872. fail();
  2873. }
  2874. start = end + 2;
  2875. }
  2876. return new ResourcePath(segments);
  2877. }
  2878. /**
  2879. * @license
  2880. * Copyright 2022 Google LLC
  2881. *
  2882. * Licensed under the Apache License, Version 2.0 (the "License");
  2883. * you may not use this file except in compliance with the License.
  2884. * You may obtain a copy of the License at
  2885. *
  2886. * http://www.apache.org/licenses/LICENSE-2.0
  2887. *
  2888. * Unless required by applicable law or agreed to in writing, software
  2889. * distributed under the License is distributed on an "AS IS" BASIS,
  2890. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2891. * See the License for the specific language governing permissions and
  2892. * limitations under the License.
  2893. */
  2894. const DbRemoteDocumentStore$1 = 'remoteDocuments';
  2895. /**
  2896. * @license
  2897. * Copyright 2022 Google LLC
  2898. *
  2899. * Licensed under the Apache License, Version 2.0 (the "License");
  2900. * you may not use this file except in compliance with the License.
  2901. * You may obtain a copy of the License at
  2902. *
  2903. * http://www.apache.org/licenses/LICENSE-2.0
  2904. *
  2905. * Unless required by applicable law or agreed to in writing, software
  2906. * distributed under the License is distributed on an "AS IS" BASIS,
  2907. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2908. * See the License for the specific language governing permissions and
  2909. * limitations under the License.
  2910. */
  2911. /**
  2912. * Name of the IndexedDb object store.
  2913. *
  2914. * Note that the name 'owner' is chosen to ensure backwards compatibility with
  2915. * older clients that only supported single locked access to the persistence
  2916. * layer.
  2917. */
  2918. const DbPrimaryClientStore = 'owner';
  2919. /**
  2920. * The key string used for the single object that exists in the
  2921. * DbPrimaryClient store.
  2922. */
  2923. const DbPrimaryClientKey = 'owner';
  2924. /** Name of the IndexedDb object store. */
  2925. const DbMutationQueueStore = 'mutationQueues';
  2926. /** Keys are automatically assigned via the userId property. */
  2927. const DbMutationQueueKeyPath = 'userId';
  2928. /** Name of the IndexedDb object store. */
  2929. const DbMutationBatchStore = 'mutations';
  2930. /** Keys are automatically assigned via the userId, batchId properties. */
  2931. const DbMutationBatchKeyPath = 'batchId';
  2932. /** The index name for lookup of mutations by user. */
  2933. const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
  2934. /** The user mutations index is keyed by [userId, batchId] pairs. */
  2935. const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
  2936. /**
  2937. * Creates a [userId] key for use in the DbDocumentMutations index to iterate
  2938. * over all of a user's document mutations.
  2939. */
  2940. function newDbDocumentMutationPrefixForUser(userId) {
  2941. return [userId];
  2942. }
  2943. /**
  2944. * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
  2945. * index to iterate over all at document mutations for a given path or lower.
  2946. */
  2947. function newDbDocumentMutationPrefixForPath(userId, path) {
  2948. return [userId, encodeResourcePath(path)];
  2949. }
  2950. /**
  2951. * Creates a full index key of [userId, encodedPath, batchId] for inserting
  2952. * and deleting into the DbDocumentMutations index.
  2953. */
  2954. function newDbDocumentMutationKey(userId, path, batchId) {
  2955. return [userId, encodeResourcePath(path), batchId];
  2956. }
  2957. /**
  2958. * Because we store all the useful information for this store in the key,
  2959. * there is no useful information to store as the value. The raw (unencoded)
  2960. * path cannot be stored because IndexedDb doesn't store prototype
  2961. * information.
  2962. */
  2963. const DbDocumentMutationPlaceholder = {};
  2964. const DbDocumentMutationStore = 'documentMutations';
  2965. const DbRemoteDocumentStore = 'remoteDocumentsV14';
  2966. /**
  2967. * The primary key of the remote documents store, which allows for efficient
  2968. * access by collection path and read time.
  2969. */
  2970. const DbRemoteDocumentKeyPath = [
  2971. 'prefixPath',
  2972. 'collectionGroup',
  2973. 'readTime',
  2974. 'documentId'
  2975. ];
  2976. /** An index that provides access to documents by key. */
  2977. const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
  2978. const DbRemoteDocumentDocumentKeyIndexPath = [
  2979. 'prefixPath',
  2980. 'collectionGroup',
  2981. 'documentId'
  2982. ];
  2983. /**
  2984. * An index that provides access to documents by collection group and read
  2985. * time.
  2986. *
  2987. * This index is used by the index backfiller.
  2988. */
  2989. const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
  2990. const DbRemoteDocumentCollectionGroupIndexPath = [
  2991. 'collectionGroup',
  2992. 'readTime',
  2993. 'prefixPath',
  2994. 'documentId'
  2995. ];
  2996. const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
  2997. const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
  2998. const DbTargetStore = 'targets';
  2999. /** Keys are automatically assigned via the targetId property. */
  3000. const DbTargetKeyPath = 'targetId';
  3001. /** The name of the queryTargets index. */
  3002. const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
  3003. /**
  3004. * The index of all canonicalIds to the targets that they match. This is not
  3005. * a unique mapping because canonicalId does not promise a unique name for all
  3006. * possible queries, so we append the targetId to make the mapping unique.
  3007. */
  3008. const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
  3009. /** Name of the IndexedDb object store. */
  3010. const DbTargetDocumentStore = 'targetDocuments';
  3011. /** Keys are automatically assigned via the targetId, path properties. */
  3012. const DbTargetDocumentKeyPath = ['targetId', 'path'];
  3013. /** The index name for the reverse index. */
  3014. const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
  3015. /** We also need to create the reverse index for these properties. */
  3016. const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
  3017. /**
  3018. * The key string used for the single object that exists in the
  3019. * DbTargetGlobal store.
  3020. */
  3021. const DbTargetGlobalKey = 'targetGlobalKey';
  3022. const DbTargetGlobalStore = 'targetGlobal';
  3023. /** Name of the IndexedDb object store. */
  3024. const DbCollectionParentStore = 'collectionParents';
  3025. /** Keys are automatically assigned via the collectionId, parent properties. */
  3026. const DbCollectionParentKeyPath = ['collectionId', 'parent'];
  3027. /** Name of the IndexedDb object store. */
  3028. const DbClientMetadataStore = 'clientMetadata';
  3029. /** Keys are automatically assigned via the clientId properties. */
  3030. const DbClientMetadataKeyPath = 'clientId';
  3031. /** Name of the IndexedDb object store. */
  3032. const DbBundleStore = 'bundles';
  3033. const DbBundleKeyPath = 'bundleId';
  3034. /** Name of the IndexedDb object store. */
  3035. const DbNamedQueryStore = 'namedQueries';
  3036. const DbNamedQueryKeyPath = 'name';
  3037. /** Name of the IndexedDb object store. */
  3038. const DbIndexConfigurationStore = 'indexConfiguration';
  3039. const DbIndexConfigurationKeyPath = 'indexId';
  3040. /**
  3041. * An index that provides access to the index configurations by collection
  3042. * group.
  3043. *
  3044. * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
  3045. * not possible here as the Web client supports concurrent access to
  3046. * persistence via multi-tab.
  3047. */
  3048. const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
  3049. const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
  3050. /** Name of the IndexedDb object store. */
  3051. const DbIndexStateStore = 'indexState';
  3052. const DbIndexStateKeyPath = ['indexId', 'uid'];
  3053. /**
  3054. * An index that provides access to documents in a collection sorted by last
  3055. * update time. Used by the backfiller.
  3056. *
  3057. * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
  3058. * not possible here as the Web client supports concurrent access to
  3059. * persistence via multi-tab.
  3060. */
  3061. const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
  3062. const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
  3063. /** Name of the IndexedDb object store. */
  3064. const DbIndexEntryStore = 'indexEntries';
  3065. const DbIndexEntryKeyPath = [
  3066. 'indexId',
  3067. 'uid',
  3068. 'arrayValue',
  3069. 'directionalValue',
  3070. 'orderedDocumentKey',
  3071. 'documentKey'
  3072. ];
  3073. const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
  3074. const DbIndexEntryDocumentKeyIndexPath = [
  3075. 'indexId',
  3076. 'uid',
  3077. 'orderedDocumentKey'
  3078. ];
  3079. /** Name of the IndexedDb object store. */
  3080. const DbDocumentOverlayStore = 'documentOverlays';
  3081. const DbDocumentOverlayKeyPath = [
  3082. 'userId',
  3083. 'collectionPath',
  3084. 'documentId'
  3085. ];
  3086. const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
  3087. const DbDocumentOverlayCollectionPathOverlayIndexPath = [
  3088. 'userId',
  3089. 'collectionPath',
  3090. 'largestBatchId'
  3091. ];
  3092. const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
  3093. const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
  3094. 'userId',
  3095. 'collectionGroup',
  3096. 'largestBatchId'
  3097. ];
  3098. // Visible for testing
  3099. const V1_STORES = [
  3100. DbMutationQueueStore,
  3101. DbMutationBatchStore,
  3102. DbDocumentMutationStore,
  3103. DbRemoteDocumentStore$1,
  3104. DbTargetStore,
  3105. DbPrimaryClientStore,
  3106. DbTargetGlobalStore,
  3107. DbTargetDocumentStore
  3108. ];
  3109. // Visible for testing
  3110. const V3_STORES = V1_STORES;
  3111. // Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
  3112. const V4_STORES = [...V3_STORES, DbClientMetadataStore];
  3113. const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
  3114. const V8_STORES = [...V6_STORES, DbCollectionParentStore];
  3115. const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
  3116. const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
  3117. const V13_STORES = [
  3118. DbMutationQueueStore,
  3119. DbMutationBatchStore,
  3120. DbDocumentMutationStore,
  3121. DbRemoteDocumentStore,
  3122. DbTargetStore,
  3123. DbPrimaryClientStore,
  3124. DbTargetGlobalStore,
  3125. DbTargetDocumentStore,
  3126. DbClientMetadataStore,
  3127. DbRemoteDocumentGlobalStore,
  3128. DbCollectionParentStore,
  3129. DbBundleStore,
  3130. DbNamedQueryStore,
  3131. DbDocumentOverlayStore
  3132. ];
  3133. const V14_STORES = V13_STORES;
  3134. const V15_STORES = [
  3135. ...V14_STORES,
  3136. DbIndexConfigurationStore,
  3137. DbIndexStateStore,
  3138. DbIndexEntryStore
  3139. ];
  3140. /** Returns the object stores for the provided schema. */
  3141. function getObjectStores(schemaVersion) {
  3142. if (schemaVersion === 15) {
  3143. return V15_STORES;
  3144. }
  3145. else if (schemaVersion === 14) {
  3146. return V14_STORES;
  3147. }
  3148. else if (schemaVersion === 13) {
  3149. return V13_STORES;
  3150. }
  3151. else if (schemaVersion === 12) {
  3152. return V12_STORES;
  3153. }
  3154. else if (schemaVersion === 11) {
  3155. return V11_STORES;
  3156. }
  3157. else {
  3158. fail();
  3159. }
  3160. }
  3161. /**
  3162. * @license
  3163. * Copyright 2020 Google LLC
  3164. *
  3165. * Licensed under the Apache License, Version 2.0 (the "License");
  3166. * you may not use this file except in compliance with the License.
  3167. * You may obtain a copy of the License at
  3168. *
  3169. * http://www.apache.org/licenses/LICENSE-2.0
  3170. *
  3171. * Unless required by applicable law or agreed to in writing, software
  3172. * distributed under the License is distributed on an "AS IS" BASIS,
  3173. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3174. * See the License for the specific language governing permissions and
  3175. * limitations under the License.
  3176. */
  3177. class IndexedDbTransaction extends PersistenceTransaction {
  3178. constructor(simpleDbTransaction, currentSequenceNumber) {
  3179. super();
  3180. this.simpleDbTransaction = simpleDbTransaction;
  3181. this.currentSequenceNumber = currentSequenceNumber;
  3182. }
  3183. }
  3184. function getStore(txn, store) {
  3185. const indexedDbTransaction = debugCast(txn);
  3186. return SimpleDb.getStore(indexedDbTransaction.simpleDbTransaction, store);
  3187. }
  3188. /**
  3189. * @license
  3190. * Copyright 2017 Google LLC
  3191. *
  3192. * Licensed under the Apache License, Version 2.0 (the "License");
  3193. * you may not use this file except in compliance with the License.
  3194. * You may obtain a copy of the License at
  3195. *
  3196. * http://www.apache.org/licenses/LICENSE-2.0
  3197. *
  3198. * Unless required by applicable law or agreed to in writing, software
  3199. * distributed under the License is distributed on an "AS IS" BASIS,
  3200. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3201. * See the License for the specific language governing permissions and
  3202. * limitations under the License.
  3203. */
  3204. function objectSize(obj) {
  3205. let count = 0;
  3206. for (const key in obj) {
  3207. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3208. count++;
  3209. }
  3210. }
  3211. return count;
  3212. }
  3213. function forEach(obj, fn) {
  3214. for (const key in obj) {
  3215. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3216. fn(key, obj[key]);
  3217. }
  3218. }
  3219. }
  3220. function isEmpty(obj) {
  3221. for (const key in obj) {
  3222. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3223. return false;
  3224. }
  3225. }
  3226. return true;
  3227. }
  3228. /**
  3229. * @license
  3230. * Copyright 2017 Google LLC
  3231. *
  3232. * Licensed under the Apache License, Version 2.0 (the "License");
  3233. * you may not use this file except in compliance with the License.
  3234. * You may obtain a copy of the License at
  3235. *
  3236. * http://www.apache.org/licenses/LICENSE-2.0
  3237. *
  3238. * Unless required by applicable law or agreed to in writing, software
  3239. * distributed under the License is distributed on an "AS IS" BASIS,
  3240. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3241. * See the License for the specific language governing permissions and
  3242. * limitations under the License.
  3243. */
  3244. // An immutable sorted map implementation, based on a Left-leaning Red-Black
  3245. // tree.
  3246. class SortedMap {
  3247. constructor(comparator, root) {
  3248. this.comparator = comparator;
  3249. this.root = root ? root : LLRBNode.EMPTY;
  3250. }
  3251. // Returns a copy of the map, with the specified key/value added or replaced.
  3252. insert(key, value) {
  3253. return new SortedMap(this.comparator, this.root
  3254. .insert(key, value, this.comparator)
  3255. .copy(null, null, LLRBNode.BLACK, null, null));
  3256. }
  3257. // Returns a copy of the map, with the specified key removed.
  3258. remove(key) {
  3259. return new SortedMap(this.comparator, this.root
  3260. .remove(key, this.comparator)
  3261. .copy(null, null, LLRBNode.BLACK, null, null));
  3262. }
  3263. // Returns the value of the node with the given key, or null.
  3264. get(key) {
  3265. let node = this.root;
  3266. while (!node.isEmpty()) {
  3267. const cmp = this.comparator(key, node.key);
  3268. if (cmp === 0) {
  3269. return node.value;
  3270. }
  3271. else if (cmp < 0) {
  3272. node = node.left;
  3273. }
  3274. else if (cmp > 0) {
  3275. node = node.right;
  3276. }
  3277. }
  3278. return null;
  3279. }
  3280. // Returns the index of the element in this sorted map, or -1 if it doesn't
  3281. // exist.
  3282. indexOf(key) {
  3283. // Number of nodes that were pruned when descending right
  3284. let prunedNodes = 0;
  3285. let node = this.root;
  3286. while (!node.isEmpty()) {
  3287. const cmp = this.comparator(key, node.key);
  3288. if (cmp === 0) {
  3289. return prunedNodes + node.left.size;
  3290. }
  3291. else if (cmp < 0) {
  3292. node = node.left;
  3293. }
  3294. else {
  3295. // Count all nodes left of the node plus the node itself
  3296. prunedNodes += node.left.size + 1;
  3297. node = node.right;
  3298. }
  3299. }
  3300. // Node not found
  3301. return -1;
  3302. }
  3303. isEmpty() {
  3304. return this.root.isEmpty();
  3305. }
  3306. // Returns the total number of nodes in the map.
  3307. get size() {
  3308. return this.root.size;
  3309. }
  3310. // Returns the minimum key in the map.
  3311. minKey() {
  3312. return this.root.minKey();
  3313. }
  3314. // Returns the maximum key in the map.
  3315. maxKey() {
  3316. return this.root.maxKey();
  3317. }
  3318. // Traverses the map in key order and calls the specified action function
  3319. // for each key/value pair. If action returns true, traversal is aborted.
  3320. // Returns the first truthy value returned by action, or the last falsey
  3321. // value returned by action.
  3322. inorderTraversal(action) {
  3323. return this.root.inorderTraversal(action);
  3324. }
  3325. forEach(fn) {
  3326. this.inorderTraversal((k, v) => {
  3327. fn(k, v);
  3328. return false;
  3329. });
  3330. }
  3331. toString() {
  3332. const descriptions = [];
  3333. this.inorderTraversal((k, v) => {
  3334. descriptions.push(`${k}:${v}`);
  3335. return false;
  3336. });
  3337. return `{${descriptions.join(', ')}}`;
  3338. }
  3339. // Traverses the map in reverse key order and calls the specified action
  3340. // function for each key/value pair. If action returns true, traversal is
  3341. // aborted.
  3342. // Returns the first truthy value returned by action, or the last falsey
  3343. // value returned by action.
  3344. reverseTraversal(action) {
  3345. return this.root.reverseTraversal(action);
  3346. }
  3347. // Returns an iterator over the SortedMap.
  3348. getIterator() {
  3349. return new SortedMapIterator(this.root, null, this.comparator, false);
  3350. }
  3351. getIteratorFrom(key) {
  3352. return new SortedMapIterator(this.root, key, this.comparator, false);
  3353. }
  3354. getReverseIterator() {
  3355. return new SortedMapIterator(this.root, null, this.comparator, true);
  3356. }
  3357. getReverseIteratorFrom(key) {
  3358. return new SortedMapIterator(this.root, key, this.comparator, true);
  3359. }
  3360. } // end SortedMap
  3361. // An iterator over an LLRBNode.
  3362. class SortedMapIterator {
  3363. constructor(node, startKey, comparator, isReverse) {
  3364. this.isReverse = isReverse;
  3365. this.nodeStack = [];
  3366. let cmp = 1;
  3367. while (!node.isEmpty()) {
  3368. cmp = startKey ? comparator(node.key, startKey) : 1;
  3369. // flip the comparison if we're going in reverse
  3370. if (startKey && isReverse) {
  3371. cmp *= -1;
  3372. }
  3373. if (cmp < 0) {
  3374. // This node is less than our start key. ignore it
  3375. if (this.isReverse) {
  3376. node = node.left;
  3377. }
  3378. else {
  3379. node = node.right;
  3380. }
  3381. }
  3382. else if (cmp === 0) {
  3383. // This node is exactly equal to our start key. Push it on the stack,
  3384. // but stop iterating;
  3385. this.nodeStack.push(node);
  3386. break;
  3387. }
  3388. else {
  3389. // This node is greater than our start key, add it to the stack and move
  3390. // to the next one
  3391. this.nodeStack.push(node);
  3392. if (this.isReverse) {
  3393. node = node.right;
  3394. }
  3395. else {
  3396. node = node.left;
  3397. }
  3398. }
  3399. }
  3400. }
  3401. getNext() {
  3402. let node = this.nodeStack.pop();
  3403. const result = { key: node.key, value: node.value };
  3404. if (this.isReverse) {
  3405. node = node.left;
  3406. while (!node.isEmpty()) {
  3407. this.nodeStack.push(node);
  3408. node = node.right;
  3409. }
  3410. }
  3411. else {
  3412. node = node.right;
  3413. while (!node.isEmpty()) {
  3414. this.nodeStack.push(node);
  3415. node = node.left;
  3416. }
  3417. }
  3418. return result;
  3419. }
  3420. hasNext() {
  3421. return this.nodeStack.length > 0;
  3422. }
  3423. peek() {
  3424. if (this.nodeStack.length === 0) {
  3425. return null;
  3426. }
  3427. const node = this.nodeStack[this.nodeStack.length - 1];
  3428. return { key: node.key, value: node.value };
  3429. }
  3430. } // end SortedMapIterator
  3431. // Represents a node in a Left-leaning Red-Black tree.
  3432. class LLRBNode {
  3433. constructor(key, value, color, left, right) {
  3434. this.key = key;
  3435. this.value = value;
  3436. this.color = color != null ? color : LLRBNode.RED;
  3437. this.left = left != null ? left : LLRBNode.EMPTY;
  3438. this.right = right != null ? right : LLRBNode.EMPTY;
  3439. this.size = this.left.size + 1 + this.right.size;
  3440. }
  3441. // Returns a copy of the current node, optionally replacing pieces of it.
  3442. copy(key, value, color, left, right) {
  3443. 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);
  3444. }
  3445. isEmpty() {
  3446. return false;
  3447. }
  3448. // Traverses the tree in key order and calls the specified action function
  3449. // for each node. If action returns true, traversal is aborted.
  3450. // Returns the first truthy value returned by action, or the last falsey
  3451. // value returned by action.
  3452. inorderTraversal(action) {
  3453. return (this.left.inorderTraversal(action) ||
  3454. action(this.key, this.value) ||
  3455. this.right.inorderTraversal(action));
  3456. }
  3457. // Traverses the tree in reverse key order and calls the specified action
  3458. // function for each node. If action returns true, traversal is aborted.
  3459. // Returns the first truthy value returned by action, or the last falsey
  3460. // value returned by action.
  3461. reverseTraversal(action) {
  3462. return (this.right.reverseTraversal(action) ||
  3463. action(this.key, this.value) ||
  3464. this.left.reverseTraversal(action));
  3465. }
  3466. // Returns the minimum node in the tree.
  3467. min() {
  3468. if (this.left.isEmpty()) {
  3469. return this;
  3470. }
  3471. else {
  3472. return this.left.min();
  3473. }
  3474. }
  3475. // Returns the maximum key in the tree.
  3476. minKey() {
  3477. return this.min().key;
  3478. }
  3479. // Returns the maximum key in the tree.
  3480. maxKey() {
  3481. if (this.right.isEmpty()) {
  3482. return this.key;
  3483. }
  3484. else {
  3485. return this.right.maxKey();
  3486. }
  3487. }
  3488. // Returns new tree, with the key/value added.
  3489. insert(key, value, comparator) {
  3490. let n = this;
  3491. const cmp = comparator(key, n.key);
  3492. if (cmp < 0) {
  3493. n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
  3494. }
  3495. else if (cmp === 0) {
  3496. n = n.copy(null, value, null, null, null);
  3497. }
  3498. else {
  3499. n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
  3500. }
  3501. return n.fixUp();
  3502. }
  3503. removeMin() {
  3504. if (this.left.isEmpty()) {
  3505. return LLRBNode.EMPTY;
  3506. }
  3507. let n = this;
  3508. if (!n.left.isRed() && !n.left.left.isRed()) {
  3509. n = n.moveRedLeft();
  3510. }
  3511. n = n.copy(null, null, null, n.left.removeMin(), null);
  3512. return n.fixUp();
  3513. }
  3514. // Returns new tree, with the specified item removed.
  3515. remove(key, comparator) {
  3516. let smallest;
  3517. let n = this;
  3518. if (comparator(key, n.key) < 0) {
  3519. if (!n.left.isEmpty() && !n.left.isRed() && !n.left.left.isRed()) {
  3520. n = n.moveRedLeft();
  3521. }
  3522. n = n.copy(null, null, null, n.left.remove(key, comparator), null);
  3523. }
  3524. else {
  3525. if (n.left.isRed()) {
  3526. n = n.rotateRight();
  3527. }
  3528. if (!n.right.isEmpty() && !n.right.isRed() && !n.right.left.isRed()) {
  3529. n = n.moveRedRight();
  3530. }
  3531. if (comparator(key, n.key) === 0) {
  3532. if (n.right.isEmpty()) {
  3533. return LLRBNode.EMPTY;
  3534. }
  3535. else {
  3536. smallest = n.right.min();
  3537. n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin());
  3538. }
  3539. }
  3540. n = n.copy(null, null, null, null, n.right.remove(key, comparator));
  3541. }
  3542. return n.fixUp();
  3543. }
  3544. isRed() {
  3545. return this.color;
  3546. }
  3547. // Returns new tree after performing any needed rotations.
  3548. fixUp() {
  3549. let n = this;
  3550. if (n.right.isRed() && !n.left.isRed()) {
  3551. n = n.rotateLeft();
  3552. }
  3553. if (n.left.isRed() && n.left.left.isRed()) {
  3554. n = n.rotateRight();
  3555. }
  3556. if (n.left.isRed() && n.right.isRed()) {
  3557. n = n.colorFlip();
  3558. }
  3559. return n;
  3560. }
  3561. moveRedLeft() {
  3562. let n = this.colorFlip();
  3563. if (n.right.left.isRed()) {
  3564. n = n.copy(null, null, null, null, n.right.rotateRight());
  3565. n = n.rotateLeft();
  3566. n = n.colorFlip();
  3567. }
  3568. return n;
  3569. }
  3570. moveRedRight() {
  3571. let n = this.colorFlip();
  3572. if (n.left.left.isRed()) {
  3573. n = n.rotateRight();
  3574. n = n.colorFlip();
  3575. }
  3576. return n;
  3577. }
  3578. rotateLeft() {
  3579. const nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
  3580. return this.right.copy(null, null, this.color, nl, null);
  3581. }
  3582. rotateRight() {
  3583. const nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
  3584. return this.left.copy(null, null, this.color, null, nr);
  3585. }
  3586. colorFlip() {
  3587. const left = this.left.copy(null, null, !this.left.color, null, null);
  3588. const right = this.right.copy(null, null, !this.right.color, null, null);
  3589. return this.copy(null, null, !this.color, left, right);
  3590. }
  3591. // For testing.
  3592. checkMaxDepth() {
  3593. const blackDepth = this.check();
  3594. if (Math.pow(2.0, blackDepth) <= this.size + 1) {
  3595. return true;
  3596. }
  3597. else {
  3598. return false;
  3599. }
  3600. }
  3601. // In a balanced RB tree, the black-depth (number of black nodes) from root to
  3602. // leaves is equal on both sides. This function verifies that or asserts.
  3603. check() {
  3604. if (this.isRed() && this.left.isRed()) {
  3605. throw fail();
  3606. }
  3607. if (this.right.isRed()) {
  3608. throw fail();
  3609. }
  3610. const blackDepth = this.left.check();
  3611. if (blackDepth !== this.right.check()) {
  3612. throw fail();
  3613. }
  3614. else {
  3615. return blackDepth + (this.isRed() ? 0 : 1);
  3616. }
  3617. }
  3618. } // end LLRBNode
  3619. // Empty node is shared between all LLRB trees.
  3620. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  3621. LLRBNode.EMPTY = null;
  3622. LLRBNode.RED = true;
  3623. LLRBNode.BLACK = false;
  3624. // Represents an empty node (a leaf node in the Red-Black Tree).
  3625. class LLRBEmptyNode {
  3626. constructor() {
  3627. this.size = 0;
  3628. }
  3629. get key() {
  3630. throw fail();
  3631. }
  3632. get value() {
  3633. throw fail();
  3634. }
  3635. get color() {
  3636. throw fail();
  3637. }
  3638. get left() {
  3639. throw fail();
  3640. }
  3641. get right() {
  3642. throw fail();
  3643. }
  3644. // Returns a copy of the current node.
  3645. copy(key, value, color, left, right) {
  3646. return this;
  3647. }
  3648. // Returns a copy of the tree, with the specified key/value added.
  3649. insert(key, value, comparator) {
  3650. return new LLRBNode(key, value);
  3651. }
  3652. // Returns a copy of the tree, with the specified key removed.
  3653. remove(key, comparator) {
  3654. return this;
  3655. }
  3656. isEmpty() {
  3657. return true;
  3658. }
  3659. inorderTraversal(action) {
  3660. return false;
  3661. }
  3662. reverseTraversal(action) {
  3663. return false;
  3664. }
  3665. minKey() {
  3666. return null;
  3667. }
  3668. maxKey() {
  3669. return null;
  3670. }
  3671. isRed() {
  3672. return false;
  3673. }
  3674. // For testing.
  3675. checkMaxDepth() {
  3676. return true;
  3677. }
  3678. check() {
  3679. return 0;
  3680. }
  3681. } // end LLRBEmptyNode
  3682. LLRBNode.EMPTY = new LLRBEmptyNode();
  3683. /**
  3684. * @license
  3685. * Copyright 2017 Google LLC
  3686. *
  3687. * Licensed under the Apache License, Version 2.0 (the "License");
  3688. * you may not use this file except in compliance with the License.
  3689. * You may obtain a copy of the License at
  3690. *
  3691. * http://www.apache.org/licenses/LICENSE-2.0
  3692. *
  3693. * Unless required by applicable law or agreed to in writing, software
  3694. * distributed under the License is distributed on an "AS IS" BASIS,
  3695. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3696. * See the License for the specific language governing permissions and
  3697. * limitations under the License.
  3698. */
  3699. /**
  3700. * SortedSet is an immutable (copy-on-write) collection that holds elements
  3701. * in order specified by the provided comparator.
  3702. *
  3703. * NOTE: if provided comparator returns 0 for two elements, we consider them to
  3704. * be equal!
  3705. */
  3706. class SortedSet {
  3707. constructor(comparator) {
  3708. this.comparator = comparator;
  3709. this.data = new SortedMap(this.comparator);
  3710. }
  3711. has(elem) {
  3712. return this.data.get(elem) !== null;
  3713. }
  3714. first() {
  3715. return this.data.minKey();
  3716. }
  3717. last() {
  3718. return this.data.maxKey();
  3719. }
  3720. get size() {
  3721. return this.data.size;
  3722. }
  3723. indexOf(elem) {
  3724. return this.data.indexOf(elem);
  3725. }
  3726. /** Iterates elements in order defined by "comparator" */
  3727. forEach(cb) {
  3728. this.data.inorderTraversal((k, v) => {
  3729. cb(k);
  3730. return false;
  3731. });
  3732. }
  3733. /** Iterates over `elem`s such that: range[0] &lt;= elem &lt; range[1]. */
  3734. forEachInRange(range, cb) {
  3735. const iter = this.data.getIteratorFrom(range[0]);
  3736. while (iter.hasNext()) {
  3737. const elem = iter.getNext();
  3738. if (this.comparator(elem.key, range[1]) >= 0) {
  3739. return;
  3740. }
  3741. cb(elem.key);
  3742. }
  3743. }
  3744. /**
  3745. * Iterates over `elem`s such that: start &lt;= elem until false is returned.
  3746. */
  3747. forEachWhile(cb, start) {
  3748. let iter;
  3749. if (start !== undefined) {
  3750. iter = this.data.getIteratorFrom(start);
  3751. }
  3752. else {
  3753. iter = this.data.getIterator();
  3754. }
  3755. while (iter.hasNext()) {
  3756. const elem = iter.getNext();
  3757. const result = cb(elem.key);
  3758. if (!result) {
  3759. return;
  3760. }
  3761. }
  3762. }
  3763. /** Finds the least element greater than or equal to `elem`. */
  3764. firstAfterOrEqual(elem) {
  3765. const iter = this.data.getIteratorFrom(elem);
  3766. return iter.hasNext() ? iter.getNext().key : null;
  3767. }
  3768. getIterator() {
  3769. return new SortedSetIterator(this.data.getIterator());
  3770. }
  3771. getIteratorFrom(key) {
  3772. return new SortedSetIterator(this.data.getIteratorFrom(key));
  3773. }
  3774. /** Inserts or updates an element */
  3775. add(elem) {
  3776. return this.copy(this.data.remove(elem).insert(elem, true));
  3777. }
  3778. /** Deletes an element */
  3779. delete(elem) {
  3780. if (!this.has(elem)) {
  3781. return this;
  3782. }
  3783. return this.copy(this.data.remove(elem));
  3784. }
  3785. isEmpty() {
  3786. return this.data.isEmpty();
  3787. }
  3788. unionWith(other) {
  3789. let result = this;
  3790. // Make sure `result` always refers to the larger one of the two sets.
  3791. if (result.size < other.size) {
  3792. result = other;
  3793. other = this;
  3794. }
  3795. other.forEach(elem => {
  3796. result = result.add(elem);
  3797. });
  3798. return result;
  3799. }
  3800. isEqual(other) {
  3801. if (!(other instanceof SortedSet)) {
  3802. return false;
  3803. }
  3804. if (this.size !== other.size) {
  3805. return false;
  3806. }
  3807. const thisIt = this.data.getIterator();
  3808. const otherIt = other.data.getIterator();
  3809. while (thisIt.hasNext()) {
  3810. const thisElem = thisIt.getNext().key;
  3811. const otherElem = otherIt.getNext().key;
  3812. if (this.comparator(thisElem, otherElem) !== 0) {
  3813. return false;
  3814. }
  3815. }
  3816. return true;
  3817. }
  3818. toArray() {
  3819. const res = [];
  3820. this.forEach(targetId => {
  3821. res.push(targetId);
  3822. });
  3823. return res;
  3824. }
  3825. toString() {
  3826. const result = [];
  3827. this.forEach(elem => result.push(elem));
  3828. return 'SortedSet(' + result.toString() + ')';
  3829. }
  3830. copy(data) {
  3831. const result = new SortedSet(this.comparator);
  3832. result.data = data;
  3833. return result;
  3834. }
  3835. }
  3836. class SortedSetIterator {
  3837. constructor(iter) {
  3838. this.iter = iter;
  3839. }
  3840. getNext() {
  3841. return this.iter.getNext().key;
  3842. }
  3843. hasNext() {
  3844. return this.iter.hasNext();
  3845. }
  3846. }
  3847. /**
  3848. * Compares two sorted sets for equality using their natural ordering. The
  3849. * method computes the intersection and invokes `onAdd` for every element that
  3850. * is in `after` but not `before`. `onRemove` is invoked for every element in
  3851. * `before` but missing from `after`.
  3852. *
  3853. * The method creates a copy of both `before` and `after` and runs in O(n log
  3854. * n), where n is the size of the two lists.
  3855. *
  3856. * @param before - The elements that exist in the original set.
  3857. * @param after - The elements to diff against the original set.
  3858. * @param comparator - The comparator for the elements in before and after.
  3859. * @param onAdd - A function to invoke for every element that is part of `
  3860. * after` but not `before`.
  3861. * @param onRemove - A function to invoke for every element that is part of
  3862. * `before` but not `after`.
  3863. */
  3864. function diffSortedSets(before, after, comparator, onAdd, onRemove) {
  3865. const beforeIt = before.getIterator();
  3866. const afterIt = after.getIterator();
  3867. let beforeValue = advanceIterator(beforeIt);
  3868. let afterValue = advanceIterator(afterIt);
  3869. // Walk through the two sets at the same time, using the ordering defined by
  3870. // `comparator`.
  3871. while (beforeValue || afterValue) {
  3872. let added = false;
  3873. let removed = false;
  3874. if (beforeValue && afterValue) {
  3875. const cmp = comparator(beforeValue, afterValue);
  3876. if (cmp < 0) {
  3877. // The element was removed if the next element in our ordered
  3878. // walkthrough is only in `before`.
  3879. removed = true;
  3880. }
  3881. else if (cmp > 0) {
  3882. // The element was added if the next element in our ordered walkthrough
  3883. // is only in `after`.
  3884. added = true;
  3885. }
  3886. }
  3887. else if (beforeValue != null) {
  3888. removed = true;
  3889. }
  3890. else {
  3891. added = true;
  3892. }
  3893. if (added) {
  3894. onAdd(afterValue);
  3895. afterValue = advanceIterator(afterIt);
  3896. }
  3897. else if (removed) {
  3898. onRemove(beforeValue);
  3899. beforeValue = advanceIterator(beforeIt);
  3900. }
  3901. else {
  3902. beforeValue = advanceIterator(beforeIt);
  3903. afterValue = advanceIterator(afterIt);
  3904. }
  3905. }
  3906. }
  3907. /**
  3908. * Returns the next element from the iterator or `undefined` if none available.
  3909. */
  3910. function advanceIterator(it) {
  3911. return it.hasNext() ? it.getNext() : undefined;
  3912. }
  3913. /**
  3914. * @license
  3915. * Copyright 2020 Google LLC
  3916. *
  3917. * Licensed under the Apache License, Version 2.0 (the "License");
  3918. * you may not use this file except in compliance with the License.
  3919. * You may obtain a copy of the License at
  3920. *
  3921. * http://www.apache.org/licenses/LICENSE-2.0
  3922. *
  3923. * Unless required by applicable law or agreed to in writing, software
  3924. * distributed under the License is distributed on an "AS IS" BASIS,
  3925. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3926. * See the License for the specific language governing permissions and
  3927. * limitations under the License.
  3928. */
  3929. /**
  3930. * Provides a set of fields that can be used to partially patch a document.
  3931. * FieldMask is used in conjunction with ObjectValue.
  3932. * Examples:
  3933. * foo - Overwrites foo entirely with the provided value. If foo is not
  3934. * present in the companion ObjectValue, the field is deleted.
  3935. * foo.bar - Overwrites only the field bar of the object foo.
  3936. * If foo is not an object, foo is replaced with an object
  3937. * containing foo
  3938. */
  3939. class FieldMask {
  3940. constructor(fields) {
  3941. this.fields = fields;
  3942. // TODO(dimond): validation of FieldMask
  3943. // Sort the field mask to support `FieldMask.isEqual()` and assert below.
  3944. fields.sort(FieldPath$1.comparator);
  3945. }
  3946. static empty() {
  3947. return new FieldMask([]);
  3948. }
  3949. /**
  3950. * Returns a new FieldMask object that is the result of adding all the given
  3951. * fields paths to this field mask.
  3952. */
  3953. unionWith(extraFields) {
  3954. let mergedMaskSet = new SortedSet(FieldPath$1.comparator);
  3955. for (const fieldPath of this.fields) {
  3956. mergedMaskSet = mergedMaskSet.add(fieldPath);
  3957. }
  3958. for (const fieldPath of extraFields) {
  3959. mergedMaskSet = mergedMaskSet.add(fieldPath);
  3960. }
  3961. return new FieldMask(mergedMaskSet.toArray());
  3962. }
  3963. /**
  3964. * Verifies that `fieldPath` is included by at least one field in this field
  3965. * mask.
  3966. *
  3967. * This is an O(n) operation, where `n` is the size of the field mask.
  3968. */
  3969. covers(fieldPath) {
  3970. for (const fieldMaskPath of this.fields) {
  3971. if (fieldMaskPath.isPrefixOf(fieldPath)) {
  3972. return true;
  3973. }
  3974. }
  3975. return false;
  3976. }
  3977. isEqual(other) {
  3978. return arrayEquals(this.fields, other.fields, (l, r) => l.isEqual(r));
  3979. }
  3980. }
  3981. /**
  3982. * @license
  3983. * Copyright 2020 Google LLC
  3984. *
  3985. * Licensed under the Apache License, Version 2.0 (the "License");
  3986. * you may not use this file except in compliance with the License.
  3987. * You may obtain a copy of the License at
  3988. *
  3989. * http://www.apache.org/licenses/LICENSE-2.0
  3990. *
  3991. * Unless required by applicable law or agreed to in writing, software
  3992. * distributed under the License is distributed on an "AS IS" BASIS,
  3993. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3994. * See the License for the specific language governing permissions and
  3995. * limitations under the License.
  3996. */
  3997. /** Converts a Base64 encoded string to a binary string. */
  3998. function decodeBase64(encoded) {
  3999. // Note: We used to validate the base64 string here via a regular expression.
  4000. // This was removed to improve the performance of indexing.
  4001. return Buffer.from(encoded, 'base64').toString('binary');
  4002. }
  4003. /** Converts a binary string to a Base64 encoded string. */
  4004. function encodeBase64(raw) {
  4005. return Buffer.from(raw, 'binary').toString('base64');
  4006. }
  4007. /** True if and only if the Base64 conversion functions are available. */
  4008. function isBase64Available() {
  4009. return true;
  4010. }
  4011. /**
  4012. * @license
  4013. * Copyright 2020 Google LLC
  4014. *
  4015. * Licensed under the Apache License, Version 2.0 (the "License");
  4016. * you may not use this file except in compliance with the License.
  4017. * You may obtain a copy of the License at
  4018. *
  4019. * http://www.apache.org/licenses/LICENSE-2.0
  4020. *
  4021. * Unless required by applicable law or agreed to in writing, software
  4022. * distributed under the License is distributed on an "AS IS" BASIS,
  4023. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4024. * See the License for the specific language governing permissions and
  4025. * limitations under the License.
  4026. */
  4027. /**
  4028. * Immutable class that represents a "proto" byte string.
  4029. *
  4030. * Proto byte strings can either be Base64-encoded strings or Uint8Arrays when
  4031. * sent on the wire. This class abstracts away this differentiation by holding
  4032. * the proto byte string in a common class that must be converted into a string
  4033. * before being sent as a proto.
  4034. * @internal
  4035. */
  4036. class ByteString {
  4037. constructor(binaryString) {
  4038. this.binaryString = binaryString;
  4039. }
  4040. static fromBase64String(base64) {
  4041. const binaryString = decodeBase64(base64);
  4042. return new ByteString(binaryString);
  4043. }
  4044. static fromUint8Array(array) {
  4045. // TODO(indexing); Remove the copy of the byte string here as this method
  4046. // is frequently called during indexing.
  4047. const binaryString = binaryStringFromUint8Array(array);
  4048. return new ByteString(binaryString);
  4049. }
  4050. [Symbol.iterator]() {
  4051. let i = 0;
  4052. return {
  4053. next: () => {
  4054. if (i < this.binaryString.length) {
  4055. return { value: this.binaryString.charCodeAt(i++), done: false };
  4056. }
  4057. else {
  4058. return { value: undefined, done: true };
  4059. }
  4060. }
  4061. };
  4062. }
  4063. toBase64() {
  4064. return encodeBase64(this.binaryString);
  4065. }
  4066. toUint8Array() {
  4067. return uint8ArrayFromBinaryString(this.binaryString);
  4068. }
  4069. approximateByteSize() {
  4070. return this.binaryString.length * 2;
  4071. }
  4072. compareTo(other) {
  4073. return primitiveComparator(this.binaryString, other.binaryString);
  4074. }
  4075. isEqual(other) {
  4076. return this.binaryString === other.binaryString;
  4077. }
  4078. }
  4079. ByteString.EMPTY_BYTE_STRING = new ByteString('');
  4080. /**
  4081. * Helper function to convert an Uint8array to a binary string.
  4082. */
  4083. function binaryStringFromUint8Array(array) {
  4084. let binaryString = '';
  4085. for (let i = 0; i < array.length; ++i) {
  4086. binaryString += String.fromCharCode(array[i]);
  4087. }
  4088. return binaryString;
  4089. }
  4090. /**
  4091. * Helper function to convert a binary string to an Uint8Array.
  4092. */
  4093. function uint8ArrayFromBinaryString(binaryString) {
  4094. const buffer = new Uint8Array(binaryString.length);
  4095. for (let i = 0; i < binaryString.length; i++) {
  4096. buffer[i] = binaryString.charCodeAt(i);
  4097. }
  4098. return buffer;
  4099. }
  4100. /**
  4101. * @license
  4102. * Copyright 2020 Google LLC
  4103. *
  4104. * Licensed under the Apache License, Version 2.0 (the "License");
  4105. * you may not use this file except in compliance with the License.
  4106. * You may obtain a copy of the License at
  4107. *
  4108. * http://www.apache.org/licenses/LICENSE-2.0
  4109. *
  4110. * Unless required by applicable law or agreed to in writing, software
  4111. * distributed under the License is distributed on an "AS IS" BASIS,
  4112. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4113. * See the License for the specific language governing permissions and
  4114. * limitations under the License.
  4115. */
  4116. // A RegExp matching ISO 8601 UTC timestamps with optional fraction.
  4117. const ISO_TIMESTAMP_REG_EXP = new RegExp(/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.(\d+))?Z$/);
  4118. /**
  4119. * Converts the possible Proto values for a timestamp value into a "seconds and
  4120. * nanos" representation.
  4121. */
  4122. function normalizeTimestamp(date) {
  4123. hardAssert(!!date);
  4124. // The json interface (for the browser) will return an iso timestamp string,
  4125. // while the proto js library (for node) will return a
  4126. // google.protobuf.Timestamp instance.
  4127. if (typeof date === 'string') {
  4128. // The date string can have higher precision (nanos) than the Date class
  4129. // (millis), so we do some custom parsing here.
  4130. // Parse the nanos right out of the string.
  4131. let nanos = 0;
  4132. const fraction = ISO_TIMESTAMP_REG_EXP.exec(date);
  4133. hardAssert(!!fraction);
  4134. if (fraction[1]) {
  4135. // Pad the fraction out to 9 digits (nanos).
  4136. let nanoStr = fraction[1];
  4137. nanoStr = (nanoStr + '000000000').substr(0, 9);
  4138. nanos = Number(nanoStr);
  4139. }
  4140. // Parse the date to get the seconds.
  4141. const parsedDate = new Date(date);
  4142. const seconds = Math.floor(parsedDate.getTime() / 1000);
  4143. return { seconds, nanos };
  4144. }
  4145. else {
  4146. // TODO(b/37282237): Use strings for Proto3 timestamps
  4147. // assert(!this.options.useProto3Json,
  4148. // 'The timestamp instance format requires Proto JS.');
  4149. const seconds = normalizeNumber(date.seconds);
  4150. const nanos = normalizeNumber(date.nanos);
  4151. return { seconds, nanos };
  4152. }
  4153. }
  4154. /**
  4155. * Converts the possible Proto types for numbers into a JavaScript number.
  4156. * Returns 0 if the value is not numeric.
  4157. */
  4158. function normalizeNumber(value) {
  4159. // TODO(bjornick): Handle int64 greater than 53 bits.
  4160. if (typeof value === 'number') {
  4161. return value;
  4162. }
  4163. else if (typeof value === 'string') {
  4164. return Number(value);
  4165. }
  4166. else {
  4167. return 0;
  4168. }
  4169. }
  4170. /** Converts the possible Proto types for Blobs into a ByteString. */
  4171. function normalizeByteString(blob) {
  4172. if (typeof blob === 'string') {
  4173. return ByteString.fromBase64String(blob);
  4174. }
  4175. else {
  4176. return ByteString.fromUint8Array(blob);
  4177. }
  4178. }
  4179. /**
  4180. * @license
  4181. * Copyright 2020 Google LLC
  4182. *
  4183. * Licensed under the Apache License, Version 2.0 (the "License");
  4184. * you may not use this file except in compliance with the License.
  4185. * You may obtain a copy of the License at
  4186. *
  4187. * http://www.apache.org/licenses/LICENSE-2.0
  4188. *
  4189. * Unless required by applicable law or agreed to in writing, software
  4190. * distributed under the License is distributed on an "AS IS" BASIS,
  4191. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4192. * See the License for the specific language governing permissions and
  4193. * limitations under the License.
  4194. */
  4195. /**
  4196. * Represents a locally-applied ServerTimestamp.
  4197. *
  4198. * Server Timestamps are backed by MapValues that contain an internal field
  4199. * `__type__` with a value of `server_timestamp`. The previous value and local
  4200. * write time are stored in its `__previous_value__` and `__local_write_time__`
  4201. * fields respectively.
  4202. *
  4203. * Notes:
  4204. * - ServerTimestampValue instances are created as the result of applying a
  4205. * transform. They can only exist in the local view of a document. Therefore
  4206. * they do not need to be parsed or serialized.
  4207. * - When evaluated locally (e.g. for snapshot.data()), they by default
  4208. * evaluate to `null`. This behavior can be configured by passing custom
  4209. * FieldValueOptions to value().
  4210. * - With respect to other ServerTimestampValues, they sort by their
  4211. * localWriteTime.
  4212. */
  4213. const SERVER_TIMESTAMP_SENTINEL = 'server_timestamp';
  4214. const TYPE_KEY = '__type__';
  4215. const PREVIOUS_VALUE_KEY = '__previous_value__';
  4216. const LOCAL_WRITE_TIME_KEY = '__local_write_time__';
  4217. function isServerTimestamp(value) {
  4218. var _a, _b;
  4219. 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;
  4220. return type === SERVER_TIMESTAMP_SENTINEL;
  4221. }
  4222. /**
  4223. * Creates a new ServerTimestamp proto value (using the internal format).
  4224. */
  4225. function serverTimestamp$1(localWriteTime, previousValue) {
  4226. const mapValue = {
  4227. fields: {
  4228. [TYPE_KEY]: {
  4229. stringValue: SERVER_TIMESTAMP_SENTINEL
  4230. },
  4231. [LOCAL_WRITE_TIME_KEY]: {
  4232. timestampValue: {
  4233. seconds: localWriteTime.seconds,
  4234. nanos: localWriteTime.nanoseconds
  4235. }
  4236. }
  4237. }
  4238. };
  4239. if (previousValue) {
  4240. mapValue.fields[PREVIOUS_VALUE_KEY] = previousValue;
  4241. }
  4242. return { mapValue };
  4243. }
  4244. /**
  4245. * Returns the value of the field before this ServerTimestamp was set.
  4246. *
  4247. * Preserving the previous values allows the user to display the last resoled
  4248. * value until the backend responds with the timestamp.
  4249. */
  4250. function getPreviousValue(value) {
  4251. const previousValue = value.mapValue.fields[PREVIOUS_VALUE_KEY];
  4252. if (isServerTimestamp(previousValue)) {
  4253. return getPreviousValue(previousValue);
  4254. }
  4255. return previousValue;
  4256. }
  4257. /**
  4258. * Returns the local time at which this timestamp was first set.
  4259. */
  4260. function getLocalWriteTime(value) {
  4261. const localWriteTime = normalizeTimestamp(value.mapValue.fields[LOCAL_WRITE_TIME_KEY].timestampValue);
  4262. return new Timestamp(localWriteTime.seconds, localWriteTime.nanos);
  4263. }
  4264. /**
  4265. * @license
  4266. * Copyright 2017 Google LLC
  4267. *
  4268. * Licensed under the Apache License, Version 2.0 (the "License");
  4269. * you may not use this file except in compliance with the License.
  4270. * You may obtain a copy of the License at
  4271. *
  4272. * http://www.apache.org/licenses/LICENSE-2.0
  4273. *
  4274. * Unless required by applicable law or agreed to in writing, software
  4275. * distributed under the License is distributed on an "AS IS" BASIS,
  4276. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4277. * See the License for the specific language governing permissions and
  4278. * limitations under the License.
  4279. */
  4280. class DatabaseInfo {
  4281. /**
  4282. * Constructs a DatabaseInfo using the provided host, databaseId and
  4283. * persistenceKey.
  4284. *
  4285. * @param databaseId - The database to use.
  4286. * @param appId - The Firebase App Id.
  4287. * @param persistenceKey - A unique identifier for this Firestore's local
  4288. * storage (used in conjunction with the databaseId).
  4289. * @param host - The Firestore backend host to connect to.
  4290. * @param ssl - Whether to use SSL when connecting.
  4291. * @param forceLongPolling - Whether to use the forceLongPolling option
  4292. * when using WebChannel as the network transport.
  4293. * @param autoDetectLongPolling - Whether to use the detectBufferingProxy
  4294. * option when using WebChannel as the network transport.
  4295. * @param useFetchStreams Whether to use the Fetch API instead of
  4296. * XMLHTTPRequest
  4297. */
  4298. constructor(databaseId, appId, persistenceKey, host, ssl, forceLongPolling, autoDetectLongPolling, useFetchStreams) {
  4299. this.databaseId = databaseId;
  4300. this.appId = appId;
  4301. this.persistenceKey = persistenceKey;
  4302. this.host = host;
  4303. this.ssl = ssl;
  4304. this.forceLongPolling = forceLongPolling;
  4305. this.autoDetectLongPolling = autoDetectLongPolling;
  4306. this.useFetchStreams = useFetchStreams;
  4307. }
  4308. }
  4309. /** The default database name for a project. */
  4310. const DEFAULT_DATABASE_NAME = '(default)';
  4311. /**
  4312. * Represents the database ID a Firestore client is associated with.
  4313. * @internal
  4314. */
  4315. class DatabaseId {
  4316. constructor(projectId, database) {
  4317. this.projectId = projectId;
  4318. this.database = database ? database : DEFAULT_DATABASE_NAME;
  4319. }
  4320. static empty() {
  4321. return new DatabaseId('', '');
  4322. }
  4323. get isDefaultDatabase() {
  4324. return this.database === DEFAULT_DATABASE_NAME;
  4325. }
  4326. isEqual(other) {
  4327. return (other instanceof DatabaseId &&
  4328. other.projectId === this.projectId &&
  4329. other.database === this.database);
  4330. }
  4331. }
  4332. function databaseIdFromApp(app, database) {
  4333. if (!Object.prototype.hasOwnProperty.apply(app.options, ['projectId'])) {
  4334. throw new FirestoreError(Code.INVALID_ARGUMENT, '"projectId" not provided in firebase.initializeApp.');
  4335. }
  4336. return new DatabaseId(app.options.projectId, database);
  4337. }
  4338. /**
  4339. * @license
  4340. * Copyright 2017 Google LLC
  4341. *
  4342. * Licensed under the Apache License, Version 2.0 (the "License");
  4343. * you may not use this file except in compliance with the License.
  4344. * You may obtain a copy of the License at
  4345. *
  4346. * http://www.apache.org/licenses/LICENSE-2.0
  4347. *
  4348. * Unless required by applicable law or agreed to in writing, software
  4349. * distributed under the License is distributed on an "AS IS" BASIS,
  4350. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4351. * See the License for the specific language governing permissions and
  4352. * limitations under the License.
  4353. */
  4354. /** Sentinel value that sorts before any Mutation Batch ID. */
  4355. const BATCHID_UNKNOWN = -1;
  4356. /**
  4357. * Returns whether a variable is either undefined or null.
  4358. */
  4359. function isNullOrUndefined(value) {
  4360. return value === null || value === undefined;
  4361. }
  4362. /** Returns whether the value represents -0. */
  4363. function isNegativeZero(value) {
  4364. // Detect if the value is -0.0. Based on polyfill from
  4365. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
  4366. return value === 0 && 1 / value === 1 / -0;
  4367. }
  4368. /**
  4369. * Returns whether a value is an integer and in the safe integer range
  4370. * @param value - The value to test for being an integer and in the safe range
  4371. */
  4372. function isSafeInteger(value) {
  4373. return (typeof value === 'number' &&
  4374. Number.isInteger(value) &&
  4375. !isNegativeZero(value) &&
  4376. value <= Number.MAX_SAFE_INTEGER &&
  4377. value >= Number.MIN_SAFE_INTEGER);
  4378. }
  4379. /**
  4380. * @license
  4381. * Copyright 2020 Google LLC
  4382. *
  4383. * Licensed under the Apache License, Version 2.0 (the "License");
  4384. * you may not use this file except in compliance with the License.
  4385. * You may obtain a copy of the License at
  4386. *
  4387. * http://www.apache.org/licenses/LICENSE-2.0
  4388. *
  4389. * Unless required by applicable law or agreed to in writing, software
  4390. * distributed under the License is distributed on an "AS IS" BASIS,
  4391. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4392. * See the License for the specific language governing permissions and
  4393. * limitations under the License.
  4394. */
  4395. const MAX_VALUE_TYPE = '__max__';
  4396. const MAX_VALUE = {
  4397. mapValue: {
  4398. fields: {
  4399. '__type__': { stringValue: MAX_VALUE_TYPE }
  4400. }
  4401. }
  4402. };
  4403. const MIN_VALUE = {
  4404. nullValue: 'NULL_VALUE'
  4405. };
  4406. /** Extracts the backend's type order for the provided value. */
  4407. function typeOrder(value) {
  4408. if ('nullValue' in value) {
  4409. return 0 /* TypeOrder.NullValue */;
  4410. }
  4411. else if ('booleanValue' in value) {
  4412. return 1 /* TypeOrder.BooleanValue */;
  4413. }
  4414. else if ('integerValue' in value || 'doubleValue' in value) {
  4415. return 2 /* TypeOrder.NumberValue */;
  4416. }
  4417. else if ('timestampValue' in value) {
  4418. return 3 /* TypeOrder.TimestampValue */;
  4419. }
  4420. else if ('stringValue' in value) {
  4421. return 5 /* TypeOrder.StringValue */;
  4422. }
  4423. else if ('bytesValue' in value) {
  4424. return 6 /* TypeOrder.BlobValue */;
  4425. }
  4426. else if ('referenceValue' in value) {
  4427. return 7 /* TypeOrder.RefValue */;
  4428. }
  4429. else if ('geoPointValue' in value) {
  4430. return 8 /* TypeOrder.GeoPointValue */;
  4431. }
  4432. else if ('arrayValue' in value) {
  4433. return 9 /* TypeOrder.ArrayValue */;
  4434. }
  4435. else if ('mapValue' in value) {
  4436. if (isServerTimestamp(value)) {
  4437. return 4 /* TypeOrder.ServerTimestampValue */;
  4438. }
  4439. else if (isMaxValue(value)) {
  4440. return 9007199254740991 /* TypeOrder.MaxValue */;
  4441. }
  4442. return 10 /* TypeOrder.ObjectValue */;
  4443. }
  4444. else {
  4445. return fail();
  4446. }
  4447. }
  4448. /** Tests `left` and `right` for equality based on the backend semantics. */
  4449. function valueEquals(left, right) {
  4450. if (left === right) {
  4451. return true;
  4452. }
  4453. const leftType = typeOrder(left);
  4454. const rightType = typeOrder(right);
  4455. if (leftType !== rightType) {
  4456. return false;
  4457. }
  4458. switch (leftType) {
  4459. case 0 /* TypeOrder.NullValue */:
  4460. return true;
  4461. case 1 /* TypeOrder.BooleanValue */:
  4462. return left.booleanValue === right.booleanValue;
  4463. case 4 /* TypeOrder.ServerTimestampValue */:
  4464. return getLocalWriteTime(left).isEqual(getLocalWriteTime(right));
  4465. case 3 /* TypeOrder.TimestampValue */:
  4466. return timestampEquals(left, right);
  4467. case 5 /* TypeOrder.StringValue */:
  4468. return left.stringValue === right.stringValue;
  4469. case 6 /* TypeOrder.BlobValue */:
  4470. return blobEquals(left, right);
  4471. case 7 /* TypeOrder.RefValue */:
  4472. return left.referenceValue === right.referenceValue;
  4473. case 8 /* TypeOrder.GeoPointValue */:
  4474. return geoPointEquals(left, right);
  4475. case 2 /* TypeOrder.NumberValue */:
  4476. return numberEquals(left, right);
  4477. case 9 /* TypeOrder.ArrayValue */:
  4478. return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
  4479. case 10 /* TypeOrder.ObjectValue */:
  4480. return objectEquals(left, right);
  4481. case 9007199254740991 /* TypeOrder.MaxValue */:
  4482. return true;
  4483. default:
  4484. return fail();
  4485. }
  4486. }
  4487. function timestampEquals(left, right) {
  4488. if (typeof left.timestampValue === 'string' &&
  4489. typeof right.timestampValue === 'string' &&
  4490. left.timestampValue.length === right.timestampValue.length) {
  4491. // Use string equality for ISO 8601 timestamps
  4492. return left.timestampValue === right.timestampValue;
  4493. }
  4494. const leftTimestamp = normalizeTimestamp(left.timestampValue);
  4495. const rightTimestamp = normalizeTimestamp(right.timestampValue);
  4496. return (leftTimestamp.seconds === rightTimestamp.seconds &&
  4497. leftTimestamp.nanos === rightTimestamp.nanos);
  4498. }
  4499. function geoPointEquals(left, right) {
  4500. return (normalizeNumber(left.geoPointValue.latitude) ===
  4501. normalizeNumber(right.geoPointValue.latitude) &&
  4502. normalizeNumber(left.geoPointValue.longitude) ===
  4503. normalizeNumber(right.geoPointValue.longitude));
  4504. }
  4505. function blobEquals(left, right) {
  4506. return normalizeByteString(left.bytesValue).isEqual(normalizeByteString(right.bytesValue));
  4507. }
  4508. function numberEquals(left, right) {
  4509. if ('integerValue' in left && 'integerValue' in right) {
  4510. return (normalizeNumber(left.integerValue) === normalizeNumber(right.integerValue));
  4511. }
  4512. else if ('doubleValue' in left && 'doubleValue' in right) {
  4513. const n1 = normalizeNumber(left.doubleValue);
  4514. const n2 = normalizeNumber(right.doubleValue);
  4515. if (n1 === n2) {
  4516. return isNegativeZero(n1) === isNegativeZero(n2);
  4517. }
  4518. else {
  4519. return isNaN(n1) && isNaN(n2);
  4520. }
  4521. }
  4522. return false;
  4523. }
  4524. function objectEquals(left, right) {
  4525. const leftMap = left.mapValue.fields || {};
  4526. const rightMap = right.mapValue.fields || {};
  4527. if (objectSize(leftMap) !== objectSize(rightMap)) {
  4528. return false;
  4529. }
  4530. for (const key in leftMap) {
  4531. if (leftMap.hasOwnProperty(key)) {
  4532. if (rightMap[key] === undefined ||
  4533. !valueEquals(leftMap[key], rightMap[key])) {
  4534. return false;
  4535. }
  4536. }
  4537. }
  4538. return true;
  4539. }
  4540. /** Returns true if the ArrayValue contains the specified element. */
  4541. function arrayValueContains(haystack, needle) {
  4542. return ((haystack.values || []).find(v => valueEquals(v, needle)) !== undefined);
  4543. }
  4544. function valueCompare(left, right) {
  4545. if (left === right) {
  4546. return 0;
  4547. }
  4548. const leftType = typeOrder(left);
  4549. const rightType = typeOrder(right);
  4550. if (leftType !== rightType) {
  4551. return primitiveComparator(leftType, rightType);
  4552. }
  4553. switch (leftType) {
  4554. case 0 /* TypeOrder.NullValue */:
  4555. case 9007199254740991 /* TypeOrder.MaxValue */:
  4556. return 0;
  4557. case 1 /* TypeOrder.BooleanValue */:
  4558. return primitiveComparator(left.booleanValue, right.booleanValue);
  4559. case 2 /* TypeOrder.NumberValue */:
  4560. return compareNumbers(left, right);
  4561. case 3 /* TypeOrder.TimestampValue */:
  4562. return compareTimestamps(left.timestampValue, right.timestampValue);
  4563. case 4 /* TypeOrder.ServerTimestampValue */:
  4564. return compareTimestamps(getLocalWriteTime(left), getLocalWriteTime(right));
  4565. case 5 /* TypeOrder.StringValue */:
  4566. return primitiveComparator(left.stringValue, right.stringValue);
  4567. case 6 /* TypeOrder.BlobValue */:
  4568. return compareBlobs(left.bytesValue, right.bytesValue);
  4569. case 7 /* TypeOrder.RefValue */:
  4570. return compareReferences(left.referenceValue, right.referenceValue);
  4571. case 8 /* TypeOrder.GeoPointValue */:
  4572. return compareGeoPoints(left.geoPointValue, right.geoPointValue);
  4573. case 9 /* TypeOrder.ArrayValue */:
  4574. return compareArrays(left.arrayValue, right.arrayValue);
  4575. case 10 /* TypeOrder.ObjectValue */:
  4576. return compareMaps(left.mapValue, right.mapValue);
  4577. default:
  4578. throw fail();
  4579. }
  4580. }
  4581. function compareNumbers(left, right) {
  4582. const leftNumber = normalizeNumber(left.integerValue || left.doubleValue);
  4583. const rightNumber = normalizeNumber(right.integerValue || right.doubleValue);
  4584. if (leftNumber < rightNumber) {
  4585. return -1;
  4586. }
  4587. else if (leftNumber > rightNumber) {
  4588. return 1;
  4589. }
  4590. else if (leftNumber === rightNumber) {
  4591. return 0;
  4592. }
  4593. else {
  4594. // one or both are NaN.
  4595. if (isNaN(leftNumber)) {
  4596. return isNaN(rightNumber) ? 0 : -1;
  4597. }
  4598. else {
  4599. return 1;
  4600. }
  4601. }
  4602. }
  4603. function compareTimestamps(left, right) {
  4604. if (typeof left === 'string' &&
  4605. typeof right === 'string' &&
  4606. left.length === right.length) {
  4607. return primitiveComparator(left, right);
  4608. }
  4609. const leftTimestamp = normalizeTimestamp(left);
  4610. const rightTimestamp = normalizeTimestamp(right);
  4611. const comparison = primitiveComparator(leftTimestamp.seconds, rightTimestamp.seconds);
  4612. if (comparison !== 0) {
  4613. return comparison;
  4614. }
  4615. return primitiveComparator(leftTimestamp.nanos, rightTimestamp.nanos);
  4616. }
  4617. function compareReferences(leftPath, rightPath) {
  4618. const leftSegments = leftPath.split('/');
  4619. const rightSegments = rightPath.split('/');
  4620. for (let i = 0; i < leftSegments.length && i < rightSegments.length; i++) {
  4621. const comparison = primitiveComparator(leftSegments[i], rightSegments[i]);
  4622. if (comparison !== 0) {
  4623. return comparison;
  4624. }
  4625. }
  4626. return primitiveComparator(leftSegments.length, rightSegments.length);
  4627. }
  4628. function compareGeoPoints(left, right) {
  4629. const comparison = primitiveComparator(normalizeNumber(left.latitude), normalizeNumber(right.latitude));
  4630. if (comparison !== 0) {
  4631. return comparison;
  4632. }
  4633. return primitiveComparator(normalizeNumber(left.longitude), normalizeNumber(right.longitude));
  4634. }
  4635. function compareBlobs(left, right) {
  4636. const leftBytes = normalizeByteString(left);
  4637. const rightBytes = normalizeByteString(right);
  4638. return leftBytes.compareTo(rightBytes);
  4639. }
  4640. function compareArrays(left, right) {
  4641. const leftArray = left.values || [];
  4642. const rightArray = right.values || [];
  4643. for (let i = 0; i < leftArray.length && i < rightArray.length; ++i) {
  4644. const compare = valueCompare(leftArray[i], rightArray[i]);
  4645. if (compare) {
  4646. return compare;
  4647. }
  4648. }
  4649. return primitiveComparator(leftArray.length, rightArray.length);
  4650. }
  4651. function compareMaps(left, right) {
  4652. if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) {
  4653. return 0;
  4654. }
  4655. else if (left === MAX_VALUE.mapValue) {
  4656. return 1;
  4657. }
  4658. else if (right === MAX_VALUE.mapValue) {
  4659. return -1;
  4660. }
  4661. const leftMap = left.fields || {};
  4662. const leftKeys = Object.keys(leftMap);
  4663. const rightMap = right.fields || {};
  4664. const rightKeys = Object.keys(rightMap);
  4665. // Even though MapValues are likely sorted correctly based on their insertion
  4666. // order (e.g. when received from the backend), local modifications can bring
  4667. // elements out of order. We need to re-sort the elements to ensure that
  4668. // canonical IDs are independent of insertion order.
  4669. leftKeys.sort();
  4670. rightKeys.sort();
  4671. for (let i = 0; i < leftKeys.length && i < rightKeys.length; ++i) {
  4672. const keyCompare = primitiveComparator(leftKeys[i], rightKeys[i]);
  4673. if (keyCompare !== 0) {
  4674. return keyCompare;
  4675. }
  4676. const compare = valueCompare(leftMap[leftKeys[i]], rightMap[rightKeys[i]]);
  4677. if (compare !== 0) {
  4678. return compare;
  4679. }
  4680. }
  4681. return primitiveComparator(leftKeys.length, rightKeys.length);
  4682. }
  4683. /**
  4684. * Generates the canonical ID for the provided field value (as used in Target
  4685. * serialization).
  4686. */
  4687. function canonicalId(value) {
  4688. return canonifyValue(value);
  4689. }
  4690. function canonifyValue(value) {
  4691. if ('nullValue' in value) {
  4692. return 'null';
  4693. }
  4694. else if ('booleanValue' in value) {
  4695. return '' + value.booleanValue;
  4696. }
  4697. else if ('integerValue' in value) {
  4698. return '' + value.integerValue;
  4699. }
  4700. else if ('doubleValue' in value) {
  4701. return '' + value.doubleValue;
  4702. }
  4703. else if ('timestampValue' in value) {
  4704. return canonifyTimestamp(value.timestampValue);
  4705. }
  4706. else if ('stringValue' in value) {
  4707. return value.stringValue;
  4708. }
  4709. else if ('bytesValue' in value) {
  4710. return canonifyByteString(value.bytesValue);
  4711. }
  4712. else if ('referenceValue' in value) {
  4713. return canonifyReference(value.referenceValue);
  4714. }
  4715. else if ('geoPointValue' in value) {
  4716. return canonifyGeoPoint(value.geoPointValue);
  4717. }
  4718. else if ('arrayValue' in value) {
  4719. return canonifyArray(value.arrayValue);
  4720. }
  4721. else if ('mapValue' in value) {
  4722. return canonifyMap(value.mapValue);
  4723. }
  4724. else {
  4725. return fail();
  4726. }
  4727. }
  4728. function canonifyByteString(byteString) {
  4729. return normalizeByteString(byteString).toBase64();
  4730. }
  4731. function canonifyTimestamp(timestamp) {
  4732. const normalizedTimestamp = normalizeTimestamp(timestamp);
  4733. return `time(${normalizedTimestamp.seconds},${normalizedTimestamp.nanos})`;
  4734. }
  4735. function canonifyGeoPoint(geoPoint) {
  4736. return `geo(${geoPoint.latitude},${geoPoint.longitude})`;
  4737. }
  4738. function canonifyReference(referenceValue) {
  4739. return DocumentKey.fromName(referenceValue).toString();
  4740. }
  4741. function canonifyMap(mapValue) {
  4742. // Iteration order in JavaScript is not guaranteed. To ensure that we generate
  4743. // matching canonical IDs for identical maps, we need to sort the keys.
  4744. const sortedKeys = Object.keys(mapValue.fields || {}).sort();
  4745. let result = '{';
  4746. let first = true;
  4747. for (const key of sortedKeys) {
  4748. if (!first) {
  4749. result += ',';
  4750. }
  4751. else {
  4752. first = false;
  4753. }
  4754. result += `${key}:${canonifyValue(mapValue.fields[key])}`;
  4755. }
  4756. return result + '}';
  4757. }
  4758. function canonifyArray(arrayValue) {
  4759. let result = '[';
  4760. let first = true;
  4761. for (const value of arrayValue.values || []) {
  4762. if (!first) {
  4763. result += ',';
  4764. }
  4765. else {
  4766. first = false;
  4767. }
  4768. result += canonifyValue(value);
  4769. }
  4770. return result + ']';
  4771. }
  4772. /** Returns a reference value for the provided database and key. */
  4773. function refValue(databaseId, key) {
  4774. return {
  4775. referenceValue: `projects/${databaseId.projectId}/databases/${databaseId.database}/documents/${key.path.canonicalString()}`
  4776. };
  4777. }
  4778. /** Returns true if `value` is an IntegerValue . */
  4779. function isInteger(value) {
  4780. return !!value && 'integerValue' in value;
  4781. }
  4782. /** Returns true if `value` is a DoubleValue. */
  4783. function isDouble(value) {
  4784. return !!value && 'doubleValue' in value;
  4785. }
  4786. /** Returns true if `value` is either an IntegerValue or a DoubleValue. */
  4787. function isNumber(value) {
  4788. return isInteger(value) || isDouble(value);
  4789. }
  4790. /** Returns true if `value` is an ArrayValue. */
  4791. function isArray(value) {
  4792. return !!value && 'arrayValue' in value;
  4793. }
  4794. /** Returns true if `value` is a NullValue. */
  4795. function isNullValue(value) {
  4796. return !!value && 'nullValue' in value;
  4797. }
  4798. /** Returns true if `value` is NaN. */
  4799. function isNanValue(value) {
  4800. return !!value && 'doubleValue' in value && isNaN(Number(value.doubleValue));
  4801. }
  4802. /** Returns true if `value` is a MapValue. */
  4803. function isMapValue(value) {
  4804. return !!value && 'mapValue' in value;
  4805. }
  4806. /** Creates a deep copy of `source`. */
  4807. function deepClone(source) {
  4808. if (source.geoPointValue) {
  4809. return { geoPointValue: Object.assign({}, source.geoPointValue) };
  4810. }
  4811. else if (source.timestampValue &&
  4812. typeof source.timestampValue === 'object') {
  4813. return { timestampValue: Object.assign({}, source.timestampValue) };
  4814. }
  4815. else if (source.mapValue) {
  4816. const target = { mapValue: { fields: {} } };
  4817. forEach(source.mapValue.fields, (key, val) => (target.mapValue.fields[key] = deepClone(val)));
  4818. return target;
  4819. }
  4820. else if (source.arrayValue) {
  4821. const target = { arrayValue: { values: [] } };
  4822. for (let i = 0; i < (source.arrayValue.values || []).length; ++i) {
  4823. target.arrayValue.values[i] = deepClone(source.arrayValue.values[i]);
  4824. }
  4825. return target;
  4826. }
  4827. else {
  4828. return Object.assign({}, source);
  4829. }
  4830. }
  4831. /** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
  4832. function isMaxValue(value) {
  4833. return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
  4834. MAX_VALUE_TYPE);
  4835. }
  4836. /** Returns the lowest value for the given value type (inclusive). */
  4837. function valuesGetLowerBound(value) {
  4838. if ('nullValue' in value) {
  4839. return MIN_VALUE;
  4840. }
  4841. else if ('booleanValue' in value) {
  4842. return { booleanValue: false };
  4843. }
  4844. else if ('integerValue' in value || 'doubleValue' in value) {
  4845. return { doubleValue: NaN };
  4846. }
  4847. else if ('timestampValue' in value) {
  4848. return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
  4849. }
  4850. else if ('stringValue' in value) {
  4851. return { stringValue: '' };
  4852. }
  4853. else if ('bytesValue' in value) {
  4854. return { bytesValue: '' };
  4855. }
  4856. else if ('referenceValue' in value) {
  4857. return refValue(DatabaseId.empty(), DocumentKey.empty());
  4858. }
  4859. else if ('geoPointValue' in value) {
  4860. return { geoPointValue: { latitude: -90, longitude: -180 } };
  4861. }
  4862. else if ('arrayValue' in value) {
  4863. return { arrayValue: {} };
  4864. }
  4865. else if ('mapValue' in value) {
  4866. return { mapValue: {} };
  4867. }
  4868. else {
  4869. return fail();
  4870. }
  4871. }
  4872. /** Returns the largest value for the given value type (exclusive). */
  4873. function valuesGetUpperBound(value) {
  4874. if ('nullValue' in value) {
  4875. return { booleanValue: false };
  4876. }
  4877. else if ('booleanValue' in value) {
  4878. return { doubleValue: NaN };
  4879. }
  4880. else if ('integerValue' in value || 'doubleValue' in value) {
  4881. return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
  4882. }
  4883. else if ('timestampValue' in value) {
  4884. return { stringValue: '' };
  4885. }
  4886. else if ('stringValue' in value) {
  4887. return { bytesValue: '' };
  4888. }
  4889. else if ('bytesValue' in value) {
  4890. return refValue(DatabaseId.empty(), DocumentKey.empty());
  4891. }
  4892. else if ('referenceValue' in value) {
  4893. return { geoPointValue: { latitude: -90, longitude: -180 } };
  4894. }
  4895. else if ('geoPointValue' in value) {
  4896. return { arrayValue: {} };
  4897. }
  4898. else if ('arrayValue' in value) {
  4899. return { mapValue: {} };
  4900. }
  4901. else if ('mapValue' in value) {
  4902. return MAX_VALUE;
  4903. }
  4904. else {
  4905. return fail();
  4906. }
  4907. }
  4908. function lowerBoundCompare(left, right) {
  4909. const cmp = valueCompare(left.value, right.value);
  4910. if (cmp !== 0) {
  4911. return cmp;
  4912. }
  4913. if (left.inclusive && !right.inclusive) {
  4914. return -1;
  4915. }
  4916. else if (!left.inclusive && right.inclusive) {
  4917. return 1;
  4918. }
  4919. return 0;
  4920. }
  4921. function upperBoundCompare(left, right) {
  4922. const cmp = valueCompare(left.value, right.value);
  4923. if (cmp !== 0) {
  4924. return cmp;
  4925. }
  4926. if (left.inclusive && !right.inclusive) {
  4927. return 1;
  4928. }
  4929. else if (!left.inclusive && right.inclusive) {
  4930. return -1;
  4931. }
  4932. return 0;
  4933. }
  4934. /**
  4935. * @license
  4936. * Copyright 2017 Google LLC
  4937. *
  4938. * Licensed under the Apache License, Version 2.0 (the "License");
  4939. * you may not use this file except in compliance with the License.
  4940. * You may obtain a copy of the License at
  4941. *
  4942. * http://www.apache.org/licenses/LICENSE-2.0
  4943. *
  4944. * Unless required by applicable law or agreed to in writing, software
  4945. * distributed under the License is distributed on an "AS IS" BASIS,
  4946. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4947. * See the License for the specific language governing permissions and
  4948. * limitations under the License.
  4949. */
  4950. /**
  4951. * An ObjectValue represents a MapValue in the Firestore Proto and offers the
  4952. * ability to add and remove fields (via the ObjectValueBuilder).
  4953. */
  4954. class ObjectValue {
  4955. constructor(value) {
  4956. this.value = value;
  4957. }
  4958. static empty() {
  4959. return new ObjectValue({ mapValue: {} });
  4960. }
  4961. /**
  4962. * Returns the value at the given path or null.
  4963. *
  4964. * @param path - the path to search
  4965. * @returns The value at the path or null if the path is not set.
  4966. */
  4967. field(path) {
  4968. if (path.isEmpty()) {
  4969. return this.value;
  4970. }
  4971. else {
  4972. let currentLevel = this.value;
  4973. for (let i = 0; i < path.length - 1; ++i) {
  4974. currentLevel = (currentLevel.mapValue.fields || {})[path.get(i)];
  4975. if (!isMapValue(currentLevel)) {
  4976. return null;
  4977. }
  4978. }
  4979. currentLevel = (currentLevel.mapValue.fields || {})[path.lastSegment()];
  4980. return currentLevel || null;
  4981. }
  4982. }
  4983. /**
  4984. * Sets the field to the provided value.
  4985. *
  4986. * @param path - The field path to set.
  4987. * @param value - The value to set.
  4988. */
  4989. set(path, value) {
  4990. const fieldsMap = this.getFieldsMap(path.popLast());
  4991. fieldsMap[path.lastSegment()] = deepClone(value);
  4992. }
  4993. /**
  4994. * Sets the provided fields to the provided values.
  4995. *
  4996. * @param data - A map of fields to values (or null for deletes).
  4997. */
  4998. setAll(data) {
  4999. let parent = FieldPath$1.emptyPath();
  5000. let upserts = {};
  5001. let deletes = [];
  5002. data.forEach((value, path) => {
  5003. if (!parent.isImmediateParentOf(path)) {
  5004. // Insert the accumulated changes at this parent location
  5005. const fieldsMap = this.getFieldsMap(parent);
  5006. this.applyChanges(fieldsMap, upserts, deletes);
  5007. upserts = {};
  5008. deletes = [];
  5009. parent = path.popLast();
  5010. }
  5011. if (value) {
  5012. upserts[path.lastSegment()] = deepClone(value);
  5013. }
  5014. else {
  5015. deletes.push(path.lastSegment());
  5016. }
  5017. });
  5018. const fieldsMap = this.getFieldsMap(parent);
  5019. this.applyChanges(fieldsMap, upserts, deletes);
  5020. }
  5021. /**
  5022. * Removes the field at the specified path. If there is no field at the
  5023. * specified path, nothing is changed.
  5024. *
  5025. * @param path - The field path to remove.
  5026. */
  5027. delete(path) {
  5028. const nestedValue = this.field(path.popLast());
  5029. if (isMapValue(nestedValue) && nestedValue.mapValue.fields) {
  5030. delete nestedValue.mapValue.fields[path.lastSegment()];
  5031. }
  5032. }
  5033. isEqual(other) {
  5034. return valueEquals(this.value, other.value);
  5035. }
  5036. /**
  5037. * Returns the map that contains the leaf element of `path`. If the parent
  5038. * entry does not yet exist, or if it is not a map, a new map will be created.
  5039. */
  5040. getFieldsMap(path) {
  5041. let current = this.value;
  5042. if (!current.mapValue.fields) {
  5043. current.mapValue = { fields: {} };
  5044. }
  5045. for (let i = 0; i < path.length; ++i) {
  5046. let next = current.mapValue.fields[path.get(i)];
  5047. if (!isMapValue(next) || !next.mapValue.fields) {
  5048. next = { mapValue: { fields: {} } };
  5049. current.mapValue.fields[path.get(i)] = next;
  5050. }
  5051. current = next;
  5052. }
  5053. return current.mapValue.fields;
  5054. }
  5055. /**
  5056. * Modifies `fieldsMap` by adding, replacing or deleting the specified
  5057. * entries.
  5058. */
  5059. applyChanges(fieldsMap, inserts, deletes) {
  5060. forEach(inserts, (key, val) => (fieldsMap[key] = val));
  5061. for (const field of deletes) {
  5062. delete fieldsMap[field];
  5063. }
  5064. }
  5065. clone() {
  5066. return new ObjectValue(deepClone(this.value));
  5067. }
  5068. }
  5069. /**
  5070. * Returns a FieldMask built from all fields in a MapValue.
  5071. */
  5072. function extractFieldMask(value) {
  5073. const fields = [];
  5074. forEach(value.fields, (key, value) => {
  5075. const currentPath = new FieldPath$1([key]);
  5076. if (isMapValue(value)) {
  5077. const nestedMask = extractFieldMask(value.mapValue);
  5078. const nestedFields = nestedMask.fields;
  5079. if (nestedFields.length === 0) {
  5080. // Preserve the empty map by adding it to the FieldMask.
  5081. fields.push(currentPath);
  5082. }
  5083. else {
  5084. // For nested and non-empty ObjectValues, add the FieldPath of the
  5085. // leaf nodes.
  5086. for (const nestedPath of nestedFields) {
  5087. fields.push(currentPath.child(nestedPath));
  5088. }
  5089. }
  5090. }
  5091. else {
  5092. // For nested and non-empty ObjectValues, add the FieldPath of the leaf
  5093. // nodes.
  5094. fields.push(currentPath);
  5095. }
  5096. });
  5097. return new FieldMask(fields);
  5098. }
  5099. /**
  5100. * @license
  5101. * Copyright 2017 Google LLC
  5102. *
  5103. * Licensed under the Apache License, Version 2.0 (the "License");
  5104. * you may not use this file except in compliance with the License.
  5105. * You may obtain a copy of the License at
  5106. *
  5107. * http://www.apache.org/licenses/LICENSE-2.0
  5108. *
  5109. * Unless required by applicable law or agreed to in writing, software
  5110. * distributed under the License is distributed on an "AS IS" BASIS,
  5111. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5112. * See the License for the specific language governing permissions and
  5113. * limitations under the License.
  5114. */
  5115. /**
  5116. * Represents a document in Firestore with a key, version, data and whether it
  5117. * has local mutations applied to it.
  5118. *
  5119. * Documents can transition between states via `convertToFoundDocument()`,
  5120. * `convertToNoDocument()` and `convertToUnknownDocument()`. If a document does
  5121. * not transition to one of these states even after all mutations have been
  5122. * applied, `isValidDocument()` returns false and the document should be removed
  5123. * from all views.
  5124. */
  5125. class MutableDocument {
  5126. constructor(key, documentType, version, readTime, createTime, data, documentState) {
  5127. this.key = key;
  5128. this.documentType = documentType;
  5129. this.version = version;
  5130. this.readTime = readTime;
  5131. this.createTime = createTime;
  5132. this.data = data;
  5133. this.documentState = documentState;
  5134. }
  5135. /**
  5136. * Creates a document with no known version or data, but which can serve as
  5137. * base document for mutations.
  5138. */
  5139. static newInvalidDocument(documentKey) {
  5140. return new MutableDocument(documentKey, 0 /* DocumentType.INVALID */,
  5141. /* version */ SnapshotVersion.min(),
  5142. /* readTime */ SnapshotVersion.min(),
  5143. /* createTime */ SnapshotVersion.min(), ObjectValue.empty(), 0 /* DocumentState.SYNCED */);
  5144. }
  5145. /**
  5146. * Creates a new document that is known to exist with the given data at the
  5147. * given version.
  5148. */
  5149. static newFoundDocument(documentKey, version, createTime, value) {
  5150. return new MutableDocument(documentKey, 1 /* DocumentType.FOUND_DOCUMENT */,
  5151. /* version */ version,
  5152. /* readTime */ SnapshotVersion.min(),
  5153. /* createTime */ createTime, value, 0 /* DocumentState.SYNCED */);
  5154. }
  5155. /** Creates a new document that is known to not exist at the given version. */
  5156. static newNoDocument(documentKey, version) {
  5157. return new MutableDocument(documentKey, 2 /* DocumentType.NO_DOCUMENT */,
  5158. /* version */ version,
  5159. /* readTime */ SnapshotVersion.min(),
  5160. /* createTime */ SnapshotVersion.min(), ObjectValue.empty(), 0 /* DocumentState.SYNCED */);
  5161. }
  5162. /**
  5163. * Creates a new document that is known to exist at the given version but
  5164. * whose data is not known (e.g. a document that was updated without a known
  5165. * base document).
  5166. */
  5167. static newUnknownDocument(documentKey, version) {
  5168. return new MutableDocument(documentKey, 3 /* DocumentType.UNKNOWN_DOCUMENT */,
  5169. /* version */ version,
  5170. /* readTime */ SnapshotVersion.min(),
  5171. /* createTime */ SnapshotVersion.min(), ObjectValue.empty(), 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */);
  5172. }
  5173. /**
  5174. * Changes the document type to indicate that it exists and that its version
  5175. * and data are known.
  5176. */
  5177. convertToFoundDocument(version, value) {
  5178. // If a document is switching state from being an invalid or deleted
  5179. // document to a valid (FOUND_DOCUMENT) document, either due to receiving an
  5180. // update from Watch or due to applying a local set mutation on top
  5181. // of a deleted document, our best guess about its createTime would be the
  5182. // version at which the document transitioned to a FOUND_DOCUMENT.
  5183. if (this.createTime.isEqual(SnapshotVersion.min()) &&
  5184. (this.documentType === 2 /* DocumentType.NO_DOCUMENT */ ||
  5185. this.documentType === 0 /* DocumentType.INVALID */)) {
  5186. this.createTime = version;
  5187. }
  5188. this.version = version;
  5189. this.documentType = 1 /* DocumentType.FOUND_DOCUMENT */;
  5190. this.data = value;
  5191. this.documentState = 0 /* DocumentState.SYNCED */;
  5192. return this;
  5193. }
  5194. /**
  5195. * Changes the document type to indicate that it doesn't exist at the given
  5196. * version.
  5197. */
  5198. convertToNoDocument(version) {
  5199. this.version = version;
  5200. this.documentType = 2 /* DocumentType.NO_DOCUMENT */;
  5201. this.data = ObjectValue.empty();
  5202. this.documentState = 0 /* DocumentState.SYNCED */;
  5203. return this;
  5204. }
  5205. /**
  5206. * Changes the document type to indicate that it exists at a given version but
  5207. * that its data is not known (e.g. a document that was updated without a known
  5208. * base document).
  5209. */
  5210. convertToUnknownDocument(version) {
  5211. this.version = version;
  5212. this.documentType = 3 /* DocumentType.UNKNOWN_DOCUMENT */;
  5213. this.data = ObjectValue.empty();
  5214. this.documentState = 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */;
  5215. return this;
  5216. }
  5217. setHasCommittedMutations() {
  5218. this.documentState = 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */;
  5219. return this;
  5220. }
  5221. setHasLocalMutations() {
  5222. this.documentState = 1 /* DocumentState.HAS_LOCAL_MUTATIONS */;
  5223. this.version = SnapshotVersion.min();
  5224. return this;
  5225. }
  5226. setReadTime(readTime) {
  5227. this.readTime = readTime;
  5228. return this;
  5229. }
  5230. get hasLocalMutations() {
  5231. return this.documentState === 1 /* DocumentState.HAS_LOCAL_MUTATIONS */;
  5232. }
  5233. get hasCommittedMutations() {
  5234. return this.documentState === 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */;
  5235. }
  5236. get hasPendingWrites() {
  5237. return this.hasLocalMutations || this.hasCommittedMutations;
  5238. }
  5239. isValidDocument() {
  5240. return this.documentType !== 0 /* DocumentType.INVALID */;
  5241. }
  5242. isFoundDocument() {
  5243. return this.documentType === 1 /* DocumentType.FOUND_DOCUMENT */;
  5244. }
  5245. isNoDocument() {
  5246. return this.documentType === 2 /* DocumentType.NO_DOCUMENT */;
  5247. }
  5248. isUnknownDocument() {
  5249. return this.documentType === 3 /* DocumentType.UNKNOWN_DOCUMENT */;
  5250. }
  5251. isEqual(other) {
  5252. return (other instanceof MutableDocument &&
  5253. this.key.isEqual(other.key) &&
  5254. this.version.isEqual(other.version) &&
  5255. this.documentType === other.documentType &&
  5256. this.documentState === other.documentState &&
  5257. this.data.isEqual(other.data));
  5258. }
  5259. mutableCopy() {
  5260. return new MutableDocument(this.key, this.documentType, this.version, this.readTime, this.createTime, this.data.clone(), this.documentState);
  5261. }
  5262. toString() {
  5263. return (`Document(${this.key}, ${this.version}, ${JSON.stringify(this.data.value)}, ` +
  5264. `{createTime: ${this.createTime}}), ` +
  5265. `{documentType: ${this.documentType}}), ` +
  5266. `{documentState: ${this.documentState}})`);
  5267. }
  5268. }
  5269. /**
  5270. * Compares the value for field `field` in the provided documents. Throws if
  5271. * the field does not exist in both documents.
  5272. */
  5273. function compareDocumentsByField(field, d1, d2) {
  5274. const v1 = d1.data.field(field);
  5275. const v2 = d2.data.field(field);
  5276. if (v1 !== null && v2 !== null) {
  5277. return valueCompare(v1, v2);
  5278. }
  5279. else {
  5280. return fail();
  5281. }
  5282. }
  5283. /**
  5284. * @license
  5285. * Copyright 2022 Google LLC
  5286. *
  5287. * Licensed under the Apache License, Version 2.0 (the "License");
  5288. * you may not use this file except in compliance with the License.
  5289. * You may obtain a copy of the License at
  5290. *
  5291. * http://www.apache.org/licenses/LICENSE-2.0
  5292. *
  5293. * Unless required by applicable law or agreed to in writing, software
  5294. * distributed under the License is distributed on an "AS IS" BASIS,
  5295. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5296. * See the License for the specific language governing permissions and
  5297. * limitations under the License.
  5298. */
  5299. /**
  5300. * Represents a bound of a query.
  5301. *
  5302. * The bound is specified with the given components representing a position and
  5303. * whether it's just before or just after the position (relative to whatever the
  5304. * query order is).
  5305. *
  5306. * The position represents a logical index position for a query. It's a prefix
  5307. * of values for the (potentially implicit) order by clauses of a query.
  5308. *
  5309. * Bound provides a function to determine whether a document comes before or
  5310. * after a bound. This is influenced by whether the position is just before or
  5311. * just after the provided values.
  5312. */
  5313. class Bound {
  5314. constructor(position, inclusive) {
  5315. this.position = position;
  5316. this.inclusive = inclusive;
  5317. }
  5318. }
  5319. function boundCompareToDocument(bound, orderBy, doc) {
  5320. let comparison = 0;
  5321. for (let i = 0; i < bound.position.length; i++) {
  5322. const orderByComponent = orderBy[i];
  5323. const component = bound.position[i];
  5324. if (orderByComponent.field.isKeyField()) {
  5325. comparison = DocumentKey.comparator(DocumentKey.fromName(component.referenceValue), doc.key);
  5326. }
  5327. else {
  5328. const docValue = doc.data.field(orderByComponent.field);
  5329. comparison = valueCompare(component, docValue);
  5330. }
  5331. if (orderByComponent.dir === "desc" /* Direction.DESCENDING */) {
  5332. comparison = comparison * -1;
  5333. }
  5334. if (comparison !== 0) {
  5335. break;
  5336. }
  5337. }
  5338. return comparison;
  5339. }
  5340. /**
  5341. * Returns true if a document sorts after a bound using the provided sort
  5342. * order.
  5343. */
  5344. function boundSortsAfterDocument(bound, orderBy, doc) {
  5345. const comparison = boundCompareToDocument(bound, orderBy, doc);
  5346. return bound.inclusive ? comparison >= 0 : comparison > 0;
  5347. }
  5348. /**
  5349. * Returns true if a document sorts before a bound using the provided sort
  5350. * order.
  5351. */
  5352. function boundSortsBeforeDocument(bound, orderBy, doc) {
  5353. const comparison = boundCompareToDocument(bound, orderBy, doc);
  5354. return bound.inclusive ? comparison <= 0 : comparison < 0;
  5355. }
  5356. function boundEquals(left, right) {
  5357. if (left === null) {
  5358. return right === null;
  5359. }
  5360. else if (right === null) {
  5361. return false;
  5362. }
  5363. if (left.inclusive !== right.inclusive ||
  5364. left.position.length !== right.position.length) {
  5365. return false;
  5366. }
  5367. for (let i = 0; i < left.position.length; i++) {
  5368. const leftPosition = left.position[i];
  5369. const rightPosition = right.position[i];
  5370. if (!valueEquals(leftPosition, rightPosition)) {
  5371. return false;
  5372. }
  5373. }
  5374. return true;
  5375. }
  5376. /**
  5377. * @license
  5378. * Copyright 2022 Google LLC
  5379. *
  5380. * Licensed under the Apache License, Version 2.0 (the "License");
  5381. * you may not use this file except in compliance with the License.
  5382. * You may obtain a copy of the License at
  5383. *
  5384. * http://www.apache.org/licenses/LICENSE-2.0
  5385. *
  5386. * Unless required by applicable law or agreed to in writing, software
  5387. * distributed under the License is distributed on an "AS IS" BASIS,
  5388. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5389. * See the License for the specific language governing permissions and
  5390. * limitations under the License.
  5391. */
  5392. class Filter {
  5393. }
  5394. class FieldFilter extends Filter {
  5395. constructor(field, op, value) {
  5396. super();
  5397. this.field = field;
  5398. this.op = op;
  5399. this.value = value;
  5400. }
  5401. /**
  5402. * Creates a filter based on the provided arguments.
  5403. */
  5404. static create(field, op, value) {
  5405. if (field.isKeyField()) {
  5406. if (op === "in" /* Operator.IN */ || op === "not-in" /* Operator.NOT_IN */) {
  5407. return this.createKeyFieldInFilter(field, op, value);
  5408. }
  5409. else {
  5410. return new KeyFieldFilter(field, op, value);
  5411. }
  5412. }
  5413. else if (op === "array-contains" /* Operator.ARRAY_CONTAINS */) {
  5414. return new ArrayContainsFilter(field, value);
  5415. }
  5416. else if (op === "in" /* Operator.IN */) {
  5417. return new InFilter(field, value);
  5418. }
  5419. else if (op === "not-in" /* Operator.NOT_IN */) {
  5420. return new NotInFilter(field, value);
  5421. }
  5422. else if (op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  5423. return new ArrayContainsAnyFilter(field, value);
  5424. }
  5425. else {
  5426. return new FieldFilter(field, op, value);
  5427. }
  5428. }
  5429. static createKeyFieldInFilter(field, op, value) {
  5430. return op === "in" /* Operator.IN */
  5431. ? new KeyFieldInFilter(field, value)
  5432. : new KeyFieldNotInFilter(field, value);
  5433. }
  5434. matches(doc) {
  5435. const other = doc.data.field(this.field);
  5436. // Types do not have to match in NOT_EQUAL filters.
  5437. if (this.op === "!=" /* Operator.NOT_EQUAL */) {
  5438. return (other !== null &&
  5439. this.matchesComparison(valueCompare(other, this.value)));
  5440. }
  5441. // Only compare types with matching backend order (such as double and int).
  5442. return (other !== null &&
  5443. typeOrder(this.value) === typeOrder(other) &&
  5444. this.matchesComparison(valueCompare(other, this.value)));
  5445. }
  5446. matchesComparison(comparison) {
  5447. switch (this.op) {
  5448. case "<" /* Operator.LESS_THAN */:
  5449. return comparison < 0;
  5450. case "<=" /* Operator.LESS_THAN_OR_EQUAL */:
  5451. return comparison <= 0;
  5452. case "==" /* Operator.EQUAL */:
  5453. return comparison === 0;
  5454. case "!=" /* Operator.NOT_EQUAL */:
  5455. return comparison !== 0;
  5456. case ">" /* Operator.GREATER_THAN */:
  5457. return comparison > 0;
  5458. case ">=" /* Operator.GREATER_THAN_OR_EQUAL */:
  5459. return comparison >= 0;
  5460. default:
  5461. return fail();
  5462. }
  5463. }
  5464. isInequality() {
  5465. return ([
  5466. "<" /* Operator.LESS_THAN */,
  5467. "<=" /* Operator.LESS_THAN_OR_EQUAL */,
  5468. ">" /* Operator.GREATER_THAN */,
  5469. ">=" /* Operator.GREATER_THAN_OR_EQUAL */,
  5470. "!=" /* Operator.NOT_EQUAL */,
  5471. "not-in" /* Operator.NOT_IN */
  5472. ].indexOf(this.op) >= 0);
  5473. }
  5474. getFlattenedFilters() {
  5475. return [this];
  5476. }
  5477. getFilters() {
  5478. return [this];
  5479. }
  5480. getFirstInequalityField() {
  5481. if (this.isInequality()) {
  5482. return this.field;
  5483. }
  5484. return null;
  5485. }
  5486. }
  5487. class CompositeFilter extends Filter {
  5488. constructor(filters, op) {
  5489. super();
  5490. this.filters = filters;
  5491. this.op = op;
  5492. this.memoizedFlattenedFilters = null;
  5493. }
  5494. /**
  5495. * Creates a filter based on the provided arguments.
  5496. */
  5497. static create(filters, op) {
  5498. return new CompositeFilter(filters, op);
  5499. }
  5500. matches(doc) {
  5501. if (compositeFilterIsConjunction(this)) {
  5502. // For conjunctions, all filters must match, so return false if any filter doesn't match.
  5503. return this.filters.find(filter => !filter.matches(doc)) === undefined;
  5504. }
  5505. else {
  5506. // For disjunctions, at least one filter should match.
  5507. return this.filters.find(filter => filter.matches(doc)) !== undefined;
  5508. }
  5509. }
  5510. getFlattenedFilters() {
  5511. if (this.memoizedFlattenedFilters !== null) {
  5512. return this.memoizedFlattenedFilters;
  5513. }
  5514. this.memoizedFlattenedFilters = this.filters.reduce((result, subfilter) => {
  5515. return result.concat(subfilter.getFlattenedFilters());
  5516. }, []);
  5517. return this.memoizedFlattenedFilters;
  5518. }
  5519. // Returns a mutable copy of `this.filters`
  5520. getFilters() {
  5521. return Object.assign([], this.filters);
  5522. }
  5523. getFirstInequalityField() {
  5524. const found = this.findFirstMatchingFilter(filter => filter.isInequality());
  5525. if (found !== null) {
  5526. return found.field;
  5527. }
  5528. return null;
  5529. }
  5530. // Performs a depth-first search to find and return the first FieldFilter in the composite filter
  5531. // that satisfies the predicate. Returns `null` if none of the FieldFilters satisfy the
  5532. // predicate.
  5533. findFirstMatchingFilter(predicate) {
  5534. for (const fieldFilter of this.getFlattenedFilters()) {
  5535. if (predicate(fieldFilter)) {
  5536. return fieldFilter;
  5537. }
  5538. }
  5539. return null;
  5540. }
  5541. }
  5542. function compositeFilterIsConjunction(compositeFilter) {
  5543. return compositeFilter.op === "and" /* CompositeOperator.AND */;
  5544. }
  5545. function compositeFilterIsDisjunction(compositeFilter) {
  5546. return compositeFilter.op === "or" /* CompositeOperator.OR */;
  5547. }
  5548. /**
  5549. * Returns true if this filter is a conjunction of field filters only. Returns false otherwise.
  5550. */
  5551. function compositeFilterIsFlatConjunction(compositeFilter) {
  5552. return (compositeFilterIsFlat(compositeFilter) &&
  5553. compositeFilterIsConjunction(compositeFilter));
  5554. }
  5555. /**
  5556. * Returns true if this filter does not contain any composite filters. Returns false otherwise.
  5557. */
  5558. function compositeFilterIsFlat(compositeFilter) {
  5559. for (const filter of compositeFilter.filters) {
  5560. if (filter instanceof CompositeFilter) {
  5561. return false;
  5562. }
  5563. }
  5564. return true;
  5565. }
  5566. function canonifyFilter(filter) {
  5567. if (filter instanceof FieldFilter) {
  5568. // TODO(b/29183165): Technically, this won't be unique if two values have
  5569. // the same description, such as the int 3 and the string "3". So we should
  5570. // add the types in here somehow, too.
  5571. return (filter.field.canonicalString() +
  5572. filter.op.toString() +
  5573. canonicalId(filter.value));
  5574. }
  5575. else if (compositeFilterIsFlatConjunction(filter)) {
  5576. // Older SDK versions use an implicit AND operation between their filters.
  5577. // In the new SDK versions, the developer may use an explicit AND filter.
  5578. // To stay consistent with the old usages, we add a special case to ensure
  5579. // the canonical ID for these two are the same. For example:
  5580. // `col.whereEquals("a", 1).whereEquals("b", 2)` should have the same
  5581. // canonical ID as `col.where(and(equals("a",1), equals("b",2)))`.
  5582. return filter.filters.map(filter => canonifyFilter(filter)).join(',');
  5583. }
  5584. else {
  5585. // filter instanceof CompositeFilter
  5586. const canonicalIdsString = filter.filters
  5587. .map(filter => canonifyFilter(filter))
  5588. .join(',');
  5589. return `${filter.op}(${canonicalIdsString})`;
  5590. }
  5591. }
  5592. function filterEquals(f1, f2) {
  5593. if (f1 instanceof FieldFilter) {
  5594. return fieldFilterEquals(f1, f2);
  5595. }
  5596. else if (f1 instanceof CompositeFilter) {
  5597. return compositeFilterEquals(f1, f2);
  5598. }
  5599. else {
  5600. fail();
  5601. }
  5602. }
  5603. function fieldFilterEquals(f1, f2) {
  5604. return (f2 instanceof FieldFilter &&
  5605. f1.op === f2.op &&
  5606. f1.field.isEqual(f2.field) &&
  5607. valueEquals(f1.value, f2.value));
  5608. }
  5609. function compositeFilterEquals(f1, f2) {
  5610. if (f2 instanceof CompositeFilter &&
  5611. f1.op === f2.op &&
  5612. f1.filters.length === f2.filters.length) {
  5613. const subFiltersMatch = f1.filters.reduce((result, f1Filter, index) => result && filterEquals(f1Filter, f2.filters[index]), true);
  5614. return subFiltersMatch;
  5615. }
  5616. return false;
  5617. }
  5618. /**
  5619. * Returns a new composite filter that contains all filter from
  5620. * `compositeFilter` plus all the given filters in `otherFilters`.
  5621. */
  5622. function compositeFilterWithAddedFilters(compositeFilter, otherFilters) {
  5623. const mergedFilters = compositeFilter.filters.concat(otherFilters);
  5624. return CompositeFilter.create(mergedFilters, compositeFilter.op);
  5625. }
  5626. /** Returns a debug description for `filter`. */
  5627. function stringifyFilter(filter) {
  5628. if (filter instanceof FieldFilter) {
  5629. return stringifyFieldFilter(filter);
  5630. }
  5631. else if (filter instanceof CompositeFilter) {
  5632. return stringifyCompositeFilter(filter);
  5633. }
  5634. else {
  5635. return 'Filter';
  5636. }
  5637. }
  5638. function stringifyCompositeFilter(filter) {
  5639. return (filter.op.toString() +
  5640. ` {` +
  5641. filter.getFilters().map(stringifyFilter).join(' ,') +
  5642. '}');
  5643. }
  5644. function stringifyFieldFilter(filter) {
  5645. return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(filter.value)}`;
  5646. }
  5647. /** Filter that matches on key fields (i.e. '__name__'). */
  5648. class KeyFieldFilter extends FieldFilter {
  5649. constructor(field, op, value) {
  5650. super(field, op, value);
  5651. this.key = DocumentKey.fromName(value.referenceValue);
  5652. }
  5653. matches(doc) {
  5654. const comparison = DocumentKey.comparator(doc.key, this.key);
  5655. return this.matchesComparison(comparison);
  5656. }
  5657. }
  5658. /** Filter that matches on key fields within an array. */
  5659. class KeyFieldInFilter extends FieldFilter {
  5660. constructor(field, value) {
  5661. super(field, "in" /* Operator.IN */, value);
  5662. this.keys = extractDocumentKeysFromArrayValue("in" /* Operator.IN */, value);
  5663. }
  5664. matches(doc) {
  5665. return this.keys.some(key => key.isEqual(doc.key));
  5666. }
  5667. }
  5668. /** Filter that matches on key fields not present within an array. */
  5669. class KeyFieldNotInFilter extends FieldFilter {
  5670. constructor(field, value) {
  5671. super(field, "not-in" /* Operator.NOT_IN */, value);
  5672. this.keys = extractDocumentKeysFromArrayValue("not-in" /* Operator.NOT_IN */, value);
  5673. }
  5674. matches(doc) {
  5675. return !this.keys.some(key => key.isEqual(doc.key));
  5676. }
  5677. }
  5678. function extractDocumentKeysFromArrayValue(op, value) {
  5679. var _a;
  5680. return (((_a = value.arrayValue) === null || _a === void 0 ? void 0 : _a.values) || []).map(v => {
  5681. return DocumentKey.fromName(v.referenceValue);
  5682. });
  5683. }
  5684. /** A Filter that implements the array-contains operator. */
  5685. class ArrayContainsFilter extends FieldFilter {
  5686. constructor(field, value) {
  5687. super(field, "array-contains" /* Operator.ARRAY_CONTAINS */, value);
  5688. }
  5689. matches(doc) {
  5690. const other = doc.data.field(this.field);
  5691. return isArray(other) && arrayValueContains(other.arrayValue, this.value);
  5692. }
  5693. }
  5694. /** A Filter that implements the IN operator. */
  5695. class InFilter extends FieldFilter {
  5696. constructor(field, value) {
  5697. super(field, "in" /* Operator.IN */, value);
  5698. }
  5699. matches(doc) {
  5700. const other = doc.data.field(this.field);
  5701. return other !== null && arrayValueContains(this.value.arrayValue, other);
  5702. }
  5703. }
  5704. /** A Filter that implements the not-in operator. */
  5705. class NotInFilter extends FieldFilter {
  5706. constructor(field, value) {
  5707. super(field, "not-in" /* Operator.NOT_IN */, value);
  5708. }
  5709. matches(doc) {
  5710. if (arrayValueContains(this.value.arrayValue, { nullValue: 'NULL_VALUE' })) {
  5711. return false;
  5712. }
  5713. const other = doc.data.field(this.field);
  5714. return other !== null && !arrayValueContains(this.value.arrayValue, other);
  5715. }
  5716. }
  5717. /** A Filter that implements the array-contains-any operator. */
  5718. class ArrayContainsAnyFilter extends FieldFilter {
  5719. constructor(field, value) {
  5720. super(field, "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */, value);
  5721. }
  5722. matches(doc) {
  5723. const other = doc.data.field(this.field);
  5724. if (!isArray(other) || !other.arrayValue.values) {
  5725. return false;
  5726. }
  5727. return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
  5728. }
  5729. }
  5730. /**
  5731. * @license
  5732. * Copyright 2022 Google LLC
  5733. *
  5734. * Licensed under the Apache License, Version 2.0 (the "License");
  5735. * you may not use this file except in compliance with the License.
  5736. * You may obtain a copy of the License at
  5737. *
  5738. * http://www.apache.org/licenses/LICENSE-2.0
  5739. *
  5740. * Unless required by applicable law or agreed to in writing, software
  5741. * distributed under the License is distributed on an "AS IS" BASIS,
  5742. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5743. * See the License for the specific language governing permissions and
  5744. * limitations under the License.
  5745. */
  5746. /**
  5747. * An ordering on a field, in some Direction. Direction defaults to ASCENDING.
  5748. */
  5749. class OrderBy {
  5750. constructor(field, dir = "asc" /* Direction.ASCENDING */) {
  5751. this.field = field;
  5752. this.dir = dir;
  5753. }
  5754. }
  5755. function canonifyOrderBy(orderBy) {
  5756. // TODO(b/29183165): Make this collision robust.
  5757. return orderBy.field.canonicalString() + orderBy.dir;
  5758. }
  5759. function stringifyOrderBy(orderBy) {
  5760. return `${orderBy.field.canonicalString()} (${orderBy.dir})`;
  5761. }
  5762. function orderByEquals(left, right) {
  5763. return left.dir === right.dir && left.field.isEqual(right.field);
  5764. }
  5765. /**
  5766. * @license
  5767. * Copyright 2019 Google LLC
  5768. *
  5769. * Licensed under the Apache License, Version 2.0 (the "License");
  5770. * you may not use this file except in compliance with the License.
  5771. * You may obtain a copy of the License at
  5772. *
  5773. * http://www.apache.org/licenses/LICENSE-2.0
  5774. *
  5775. * Unless required by applicable law or agreed to in writing, software
  5776. * distributed under the License is distributed on an "AS IS" BASIS,
  5777. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5778. * See the License for the specific language governing permissions and
  5779. * limitations under the License.
  5780. */
  5781. // Visible for testing
  5782. class TargetImpl {
  5783. constructor(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
  5784. this.path = path;
  5785. this.collectionGroup = collectionGroup;
  5786. this.orderBy = orderBy;
  5787. this.filters = filters;
  5788. this.limit = limit;
  5789. this.startAt = startAt;
  5790. this.endAt = endAt;
  5791. this.memoizedCanonicalId = null;
  5792. }
  5793. }
  5794. /**
  5795. * Initializes a Target with a path and optional additional query constraints.
  5796. * Path must currently be empty if this is a collection group query.
  5797. *
  5798. * NOTE: you should always construct `Target` from `Query.toTarget` instead of
  5799. * using this factory method, because `Query` provides an implicit `orderBy`
  5800. * property.
  5801. */
  5802. function newTarget(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
  5803. return new TargetImpl(path, collectionGroup, orderBy, filters, limit, startAt, endAt);
  5804. }
  5805. function canonifyTarget(target) {
  5806. const targetImpl = debugCast(target);
  5807. if (targetImpl.memoizedCanonicalId === null) {
  5808. let str = targetImpl.path.canonicalString();
  5809. if (targetImpl.collectionGroup !== null) {
  5810. str += '|cg:' + targetImpl.collectionGroup;
  5811. }
  5812. str += '|f:';
  5813. str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
  5814. str += '|ob:';
  5815. str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
  5816. if (!isNullOrUndefined(targetImpl.limit)) {
  5817. str += '|l:';
  5818. str += targetImpl.limit;
  5819. }
  5820. if (targetImpl.startAt) {
  5821. str += '|lb:';
  5822. str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
  5823. str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
  5824. }
  5825. if (targetImpl.endAt) {
  5826. str += '|ub:';
  5827. str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
  5828. str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
  5829. }
  5830. targetImpl.memoizedCanonicalId = str;
  5831. }
  5832. return targetImpl.memoizedCanonicalId;
  5833. }
  5834. function stringifyTarget(target) {
  5835. let str = target.path.canonicalString();
  5836. if (target.collectionGroup !== null) {
  5837. str += ' collectionGroup=' + target.collectionGroup;
  5838. }
  5839. if (target.filters.length > 0) {
  5840. str += `, filters: [${target.filters
  5841. .map(f => stringifyFilter(f))
  5842. .join(', ')}]`;
  5843. }
  5844. if (!isNullOrUndefined(target.limit)) {
  5845. str += ', limit: ' + target.limit;
  5846. }
  5847. if (target.orderBy.length > 0) {
  5848. str += `, orderBy: [${target.orderBy
  5849. .map(o => stringifyOrderBy(o))
  5850. .join(', ')}]`;
  5851. }
  5852. if (target.startAt) {
  5853. str += ', startAt: ';
  5854. str += target.startAt.inclusive ? 'b:' : 'a:';
  5855. str += target.startAt.position.map(p => canonicalId(p)).join(',');
  5856. }
  5857. if (target.endAt) {
  5858. str += ', endAt: ';
  5859. str += target.endAt.inclusive ? 'a:' : 'b:';
  5860. str += target.endAt.position.map(p => canonicalId(p)).join(',');
  5861. }
  5862. return `Target(${str})`;
  5863. }
  5864. function targetEquals(left, right) {
  5865. if (left.limit !== right.limit) {
  5866. return false;
  5867. }
  5868. if (left.orderBy.length !== right.orderBy.length) {
  5869. return false;
  5870. }
  5871. for (let i = 0; i < left.orderBy.length; i++) {
  5872. if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {
  5873. return false;
  5874. }
  5875. }
  5876. if (left.filters.length !== right.filters.length) {
  5877. return false;
  5878. }
  5879. for (let i = 0; i < left.filters.length; i++) {
  5880. if (!filterEquals(left.filters[i], right.filters[i])) {
  5881. return false;
  5882. }
  5883. }
  5884. if (left.collectionGroup !== right.collectionGroup) {
  5885. return false;
  5886. }
  5887. if (!left.path.isEqual(right.path)) {
  5888. return false;
  5889. }
  5890. if (!boundEquals(left.startAt, right.startAt)) {
  5891. return false;
  5892. }
  5893. return boundEquals(left.endAt, right.endAt);
  5894. }
  5895. function targetIsDocumentTarget(target) {
  5896. return (DocumentKey.isDocumentKey(target.path) &&
  5897. target.collectionGroup === null &&
  5898. target.filters.length === 0);
  5899. }
  5900. /** Returns the field filters that target the given field path. */
  5901. function targetGetFieldFiltersForPath(target, path) {
  5902. return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
  5903. }
  5904. /**
  5905. * Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
  5906. * filters. Returns `null` if there are no such filters.
  5907. */
  5908. function targetGetArrayValues(target, fieldIndex) {
  5909. const segment = fieldIndexGetArraySegment(fieldIndex);
  5910. if (segment === undefined) {
  5911. return null;
  5912. }
  5913. for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
  5914. switch (fieldFilter.op) {
  5915. case "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */:
  5916. return fieldFilter.value.arrayValue.values || [];
  5917. case "array-contains" /* Operator.ARRAY_CONTAINS */:
  5918. return [fieldFilter.value];
  5919. // Remaining filters are not array filters.
  5920. }
  5921. }
  5922. return null;
  5923. }
  5924. /**
  5925. * Returns the list of values that are used in != or NOT_IN filters. Returns
  5926. * `null` if there are no such filters.
  5927. */
  5928. function targetGetNotInValues(target, fieldIndex) {
  5929. const values = new Map();
  5930. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  5931. for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
  5932. switch (fieldFilter.op) {
  5933. case "==" /* Operator.EQUAL */:
  5934. case "in" /* Operator.IN */:
  5935. // Encode equality prefix, which is encoded in the index value before
  5936. // the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
  5937. // `value != 'ab'`).
  5938. values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
  5939. break;
  5940. case "not-in" /* Operator.NOT_IN */:
  5941. case "!=" /* Operator.NOT_EQUAL */:
  5942. // NotIn/NotEqual is always a suffix. There cannot be any remaining
  5943. // segments and hence we can return early here.
  5944. values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
  5945. return Array.from(values.values());
  5946. // Remaining filters cannot be used as notIn bounds.
  5947. }
  5948. }
  5949. }
  5950. return null;
  5951. }
  5952. /**
  5953. * Returns a lower bound of field values that can be used as a starting point to
  5954. * scan the index defined by `fieldIndex`. Returns `MIN_VALUE` if no lower bound
  5955. * exists.
  5956. */
  5957. function targetGetLowerBound(target, fieldIndex) {
  5958. const values = [];
  5959. let inclusive = true;
  5960. // For each segment, retrieve a lower bound if there is a suitable filter or
  5961. // startAt.
  5962. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  5963. const segmentBound = segment.kind === 0 /* IndexKind.ASCENDING */
  5964. ? targetGetAscendingBound(target, segment.fieldPath, target.startAt)
  5965. : targetGetDescendingBound(target, segment.fieldPath, target.startAt);
  5966. values.push(segmentBound.value);
  5967. inclusive && (inclusive = segmentBound.inclusive);
  5968. }
  5969. return new Bound(values, inclusive);
  5970. }
  5971. /**
  5972. * Returns an upper bound of field values that can be used as an ending point
  5973. * when scanning the index defined by `fieldIndex`. Returns `MAX_VALUE` if no
  5974. * upper bound exists.
  5975. */
  5976. function targetGetUpperBound(target, fieldIndex) {
  5977. const values = [];
  5978. let inclusive = true;
  5979. // For each segment, retrieve an upper bound if there is a suitable filter or
  5980. // endAt.
  5981. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  5982. const segmentBound = segment.kind === 0 /* IndexKind.ASCENDING */
  5983. ? targetGetDescendingBound(target, segment.fieldPath, target.endAt)
  5984. : targetGetAscendingBound(target, segment.fieldPath, target.endAt);
  5985. values.push(segmentBound.value);
  5986. inclusive && (inclusive = segmentBound.inclusive);
  5987. }
  5988. return new Bound(values, inclusive);
  5989. }
  5990. /**
  5991. * Returns the value to use as the lower bound for ascending index segment at
  5992. * the provided `fieldPath` (or the upper bound for an descending segment).
  5993. */
  5994. function targetGetAscendingBound(target, fieldPath, bound) {
  5995. let value = MIN_VALUE;
  5996. let inclusive = true;
  5997. // Process all filters to find a value for the current field segment
  5998. for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
  5999. let filterValue = MIN_VALUE;
  6000. let filterInclusive = true;
  6001. switch (fieldFilter.op) {
  6002. case "<" /* Operator.LESS_THAN */:
  6003. case "<=" /* Operator.LESS_THAN_OR_EQUAL */:
  6004. filterValue = valuesGetLowerBound(fieldFilter.value);
  6005. break;
  6006. case "==" /* Operator.EQUAL */:
  6007. case "in" /* Operator.IN */:
  6008. case ">=" /* Operator.GREATER_THAN_OR_EQUAL */:
  6009. filterValue = fieldFilter.value;
  6010. break;
  6011. case ">" /* Operator.GREATER_THAN */:
  6012. filterValue = fieldFilter.value;
  6013. filterInclusive = false;
  6014. break;
  6015. case "!=" /* Operator.NOT_EQUAL */:
  6016. case "not-in" /* Operator.NOT_IN */:
  6017. filterValue = MIN_VALUE;
  6018. break;
  6019. // Remaining filters cannot be used as lower bounds.
  6020. }
  6021. if (lowerBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) < 0) {
  6022. value = filterValue;
  6023. inclusive = filterInclusive;
  6024. }
  6025. }
  6026. // If there is an additional bound, compare the values against the existing
  6027. // range to see if we can narrow the scope.
  6028. if (bound !== null) {
  6029. for (let i = 0; i < target.orderBy.length; ++i) {
  6030. const orderBy = target.orderBy[i];
  6031. if (orderBy.field.isEqual(fieldPath)) {
  6032. const cursorValue = bound.position[i];
  6033. if (lowerBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) < 0) {
  6034. value = cursorValue;
  6035. inclusive = bound.inclusive;
  6036. }
  6037. break;
  6038. }
  6039. }
  6040. }
  6041. return { value, inclusive };
  6042. }
  6043. /**
  6044. * Returns the value to use as the upper bound for ascending index segment at
  6045. * the provided `fieldPath` (or the lower bound for a descending segment).
  6046. */
  6047. function targetGetDescendingBound(target, fieldPath, bound) {
  6048. let value = MAX_VALUE;
  6049. let inclusive = true;
  6050. // Process all filters to find a value for the current field segment
  6051. for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
  6052. let filterValue = MAX_VALUE;
  6053. let filterInclusive = true;
  6054. switch (fieldFilter.op) {
  6055. case ">=" /* Operator.GREATER_THAN_OR_EQUAL */:
  6056. case ">" /* Operator.GREATER_THAN */:
  6057. filterValue = valuesGetUpperBound(fieldFilter.value);
  6058. filterInclusive = false;
  6059. break;
  6060. case "==" /* Operator.EQUAL */:
  6061. case "in" /* Operator.IN */:
  6062. case "<=" /* Operator.LESS_THAN_OR_EQUAL */:
  6063. filterValue = fieldFilter.value;
  6064. break;
  6065. case "<" /* Operator.LESS_THAN */:
  6066. filterValue = fieldFilter.value;
  6067. filterInclusive = false;
  6068. break;
  6069. case "!=" /* Operator.NOT_EQUAL */:
  6070. case "not-in" /* Operator.NOT_IN */:
  6071. filterValue = MAX_VALUE;
  6072. break;
  6073. // Remaining filters cannot be used as upper bounds.
  6074. }
  6075. if (upperBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) > 0) {
  6076. value = filterValue;
  6077. inclusive = filterInclusive;
  6078. }
  6079. }
  6080. // If there is an additional bound, compare the values against the existing
  6081. // range to see if we can narrow the scope.
  6082. if (bound !== null) {
  6083. for (let i = 0; i < target.orderBy.length; ++i) {
  6084. const orderBy = target.orderBy[i];
  6085. if (orderBy.field.isEqual(fieldPath)) {
  6086. const cursorValue = bound.position[i];
  6087. if (upperBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) > 0) {
  6088. value = cursorValue;
  6089. inclusive = bound.inclusive;
  6090. }
  6091. break;
  6092. }
  6093. }
  6094. }
  6095. return { value, inclusive };
  6096. }
  6097. /** Returns the number of segments of a perfect index for this target. */
  6098. function targetGetSegmentCount(target) {
  6099. let fields = new SortedSet(FieldPath$1.comparator);
  6100. let hasArraySegment = false;
  6101. for (const filter of target.filters) {
  6102. for (const subFilter of filter.getFlattenedFilters()) {
  6103. // __name__ is not an explicit segment of any index, so we don't need to
  6104. // count it.
  6105. if (subFilter.field.isKeyField()) {
  6106. continue;
  6107. }
  6108. // ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filters must be counted separately.
  6109. // For instance, it is possible to have an index for "a ARRAY a ASC". Even
  6110. // though these are on the same field, they should be counted as two
  6111. // separate segments in an index.
  6112. if (subFilter.op === "array-contains" /* Operator.ARRAY_CONTAINS */ ||
  6113. subFilter.op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  6114. hasArraySegment = true;
  6115. }
  6116. else {
  6117. fields = fields.add(subFilter.field);
  6118. }
  6119. }
  6120. }
  6121. for (const orderBy of target.orderBy) {
  6122. // __name__ is not an explicit segment of any index, so we don't need to
  6123. // count it.
  6124. if (!orderBy.field.isKeyField()) {
  6125. fields = fields.add(orderBy.field);
  6126. }
  6127. }
  6128. return fields.size + (hasArraySegment ? 1 : 0);
  6129. }
  6130. function targetHasLimit(target) {
  6131. return target.limit !== null;
  6132. }
  6133. /**
  6134. * @license
  6135. * Copyright 2017 Google LLC
  6136. *
  6137. * Licensed under the Apache License, Version 2.0 (the "License");
  6138. * you may not use this file except in compliance with the License.
  6139. * You may obtain a copy of the License at
  6140. *
  6141. * http://www.apache.org/licenses/LICENSE-2.0
  6142. *
  6143. * Unless required by applicable law or agreed to in writing, software
  6144. * distributed under the License is distributed on an "AS IS" BASIS,
  6145. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6146. * See the License for the specific language governing permissions and
  6147. * limitations under the License.
  6148. */
  6149. /**
  6150. * Query encapsulates all the query attributes we support in the SDK. It can
  6151. * be run against the LocalStore, as well as be converted to a `Target` to
  6152. * query the RemoteStore results.
  6153. *
  6154. * Visible for testing.
  6155. */
  6156. class QueryImpl {
  6157. /**
  6158. * Initializes a Query with a path and optional additional query constraints.
  6159. * Path must currently be empty if this is a collection group query.
  6160. */
  6161. constructor(path, collectionGroup = null, explicitOrderBy = [], filters = [], limit = null, limitType = "F" /* LimitType.First */, startAt = null, endAt = null) {
  6162. this.path = path;
  6163. this.collectionGroup = collectionGroup;
  6164. this.explicitOrderBy = explicitOrderBy;
  6165. this.filters = filters;
  6166. this.limit = limit;
  6167. this.limitType = limitType;
  6168. this.startAt = startAt;
  6169. this.endAt = endAt;
  6170. this.memoizedOrderBy = null;
  6171. // The corresponding `Target` of this `Query` instance.
  6172. this.memoizedTarget = null;
  6173. if (this.startAt) ;
  6174. if (this.endAt) ;
  6175. }
  6176. }
  6177. /** Creates a new Query instance with the options provided. */
  6178. function newQuery(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt) {
  6179. return new QueryImpl(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt);
  6180. }
  6181. /** Creates a new Query for a query that matches all documents at `path` */
  6182. function newQueryForPath(path) {
  6183. return new QueryImpl(path);
  6184. }
  6185. /**
  6186. * Helper to convert a collection group query into a collection query at a
  6187. * specific path. This is used when executing collection group queries, since
  6188. * we have to split the query into a set of collection queries at multiple
  6189. * paths.
  6190. */
  6191. function asCollectionQueryAtPath(query, path) {
  6192. return new QueryImpl(path,
  6193. /*collectionGroup=*/ null, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
  6194. }
  6195. /**
  6196. * Returns true if this query does not specify any query constraints that
  6197. * could remove results.
  6198. */
  6199. function queryMatchesAllDocuments(query) {
  6200. return (query.filters.length === 0 &&
  6201. query.limit === null &&
  6202. query.startAt == null &&
  6203. query.endAt == null &&
  6204. (query.explicitOrderBy.length === 0 ||
  6205. (query.explicitOrderBy.length === 1 &&
  6206. query.explicitOrderBy[0].field.isKeyField())));
  6207. }
  6208. function getFirstOrderByField(query) {
  6209. return query.explicitOrderBy.length > 0
  6210. ? query.explicitOrderBy[0].field
  6211. : null;
  6212. }
  6213. function getInequalityFilterField(query) {
  6214. for (const filter of query.filters) {
  6215. const result = filter.getFirstInequalityField();
  6216. if (result !== null) {
  6217. return result;
  6218. }
  6219. }
  6220. return null;
  6221. }
  6222. /**
  6223. * Creates a new Query for a collection group query that matches all documents
  6224. * within the provided collection group.
  6225. */
  6226. function newQueryForCollectionGroup(collectionId) {
  6227. return new QueryImpl(ResourcePath.emptyPath(), collectionId);
  6228. }
  6229. /**
  6230. * Returns whether the query matches a single document by path (rather than a
  6231. * collection).
  6232. */
  6233. function isDocumentQuery$1(query) {
  6234. return (DocumentKey.isDocumentKey(query.path) &&
  6235. query.collectionGroup === null &&
  6236. query.filters.length === 0);
  6237. }
  6238. /**
  6239. * Returns whether the query matches a collection group rather than a specific
  6240. * collection.
  6241. */
  6242. function isCollectionGroupQuery(query) {
  6243. return query.collectionGroup !== null;
  6244. }
  6245. /**
  6246. * Returns the implicit order by constraint that is used to execute the Query,
  6247. * which can be different from the order by constraints the user provided (e.g.
  6248. * the SDK and backend always orders by `__name__`).
  6249. */
  6250. function queryOrderBy(query) {
  6251. const queryImpl = debugCast(query);
  6252. if (queryImpl.memoizedOrderBy === null) {
  6253. queryImpl.memoizedOrderBy = [];
  6254. const inequalityField = getInequalityFilterField(queryImpl);
  6255. const firstOrderByField = getFirstOrderByField(queryImpl);
  6256. if (inequalityField !== null && firstOrderByField === null) {
  6257. // In order to implicitly add key ordering, we must also add the
  6258. // inequality filter field for it to be a valid query.
  6259. // Note that the default inequality field and key ordering is ascending.
  6260. if (!inequalityField.isKeyField()) {
  6261. queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
  6262. }
  6263. queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), "asc" /* Direction.ASCENDING */));
  6264. }
  6265. else {
  6266. let foundKeyOrdering = false;
  6267. for (const orderBy of queryImpl.explicitOrderBy) {
  6268. queryImpl.memoizedOrderBy.push(orderBy);
  6269. if (orderBy.field.isKeyField()) {
  6270. foundKeyOrdering = true;
  6271. }
  6272. }
  6273. if (!foundKeyOrdering) {
  6274. // The order of the implicit key ordering always matches the last
  6275. // explicit order by
  6276. const lastDirection = queryImpl.explicitOrderBy.length > 0
  6277. ? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
  6278. .dir
  6279. : "asc" /* Direction.ASCENDING */;
  6280. queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), lastDirection));
  6281. }
  6282. }
  6283. }
  6284. return queryImpl.memoizedOrderBy;
  6285. }
  6286. /**
  6287. * Converts this `Query` instance to it's corresponding `Target` representation.
  6288. */
  6289. function queryToTarget(query) {
  6290. const queryImpl = debugCast(query);
  6291. if (!queryImpl.memoizedTarget) {
  6292. if (queryImpl.limitType === "F" /* LimitType.First */) {
  6293. queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, queryOrderBy(queryImpl), queryImpl.filters, queryImpl.limit, queryImpl.startAt, queryImpl.endAt);
  6294. }
  6295. else {
  6296. // Flip the orderBy directions since we want the last results
  6297. const orderBys = [];
  6298. for (const orderBy of queryOrderBy(queryImpl)) {
  6299. const dir = orderBy.dir === "desc" /* Direction.DESCENDING */
  6300. ? "asc" /* Direction.ASCENDING */
  6301. : "desc" /* Direction.DESCENDING */;
  6302. orderBys.push(new OrderBy(orderBy.field, dir));
  6303. }
  6304. // We need to swap the cursors to match the now-flipped query ordering.
  6305. const startAt = queryImpl.endAt
  6306. ? new Bound(queryImpl.endAt.position, queryImpl.endAt.inclusive)
  6307. : null;
  6308. const endAt = queryImpl.startAt
  6309. ? new Bound(queryImpl.startAt.position, queryImpl.startAt.inclusive)
  6310. : null;
  6311. // Now return as a LimitType.First query.
  6312. queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
  6313. }
  6314. }
  6315. return queryImpl.memoizedTarget;
  6316. }
  6317. function queryWithAddedFilter(query, filter) {
  6318. filter.getFirstInequalityField();
  6319. getInequalityFilterField(query);
  6320. const newFilters = query.filters.concat([filter]);
  6321. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), newFilters, query.limit, query.limitType, query.startAt, query.endAt);
  6322. }
  6323. function queryWithAddedOrderBy(query, orderBy) {
  6324. // TODO(dimond): validate that orderBy does not list the same key twice.
  6325. const newOrderBy = query.explicitOrderBy.concat([orderBy]);
  6326. return new QueryImpl(query.path, query.collectionGroup, newOrderBy, query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
  6327. }
  6328. function queryWithLimit(query, limit, limitType) {
  6329. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), limit, limitType, query.startAt, query.endAt);
  6330. }
  6331. function queryWithStartAt(query, bound) {
  6332. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, bound, query.endAt);
  6333. }
  6334. function queryWithEndAt(query, bound) {
  6335. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, bound);
  6336. }
  6337. function queryEquals(left, right) {
  6338. return (targetEquals(queryToTarget(left), queryToTarget(right)) &&
  6339. left.limitType === right.limitType);
  6340. }
  6341. // TODO(b/29183165): This is used to get a unique string from a query to, for
  6342. // example, use as a dictionary key, but the implementation is subject to
  6343. // collisions. Make it collision-free.
  6344. function canonifyQuery(query) {
  6345. return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;
  6346. }
  6347. function stringifyQuery(query) {
  6348. return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${query.limitType})`;
  6349. }
  6350. /** Returns whether `doc` matches the constraints of `query`. */
  6351. function queryMatches(query, doc) {
  6352. return (doc.isFoundDocument() &&
  6353. queryMatchesPathAndCollectionGroup(query, doc) &&
  6354. queryMatchesOrderBy(query, doc) &&
  6355. queryMatchesFilters(query, doc) &&
  6356. queryMatchesBounds(query, doc));
  6357. }
  6358. function queryMatchesPathAndCollectionGroup(query, doc) {
  6359. const docPath = doc.key.path;
  6360. if (query.collectionGroup !== null) {
  6361. // NOTE: this.path is currently always empty since we don't expose Collection
  6362. // Group queries rooted at a document path yet.
  6363. return (doc.key.hasCollectionId(query.collectionGroup) &&
  6364. query.path.isPrefixOf(docPath));
  6365. }
  6366. else if (DocumentKey.isDocumentKey(query.path)) {
  6367. // exact match for document queries
  6368. return query.path.isEqual(docPath);
  6369. }
  6370. else {
  6371. // shallow ancestor queries by default
  6372. return query.path.isImmediateParentOf(docPath);
  6373. }
  6374. }
  6375. /**
  6376. * A document must have a value for every ordering clause in order to show up
  6377. * in the results.
  6378. */
  6379. function queryMatchesOrderBy(query, doc) {
  6380. // We must use `queryOrderBy()` to get the list of all orderBys (both implicit and explicit).
  6381. // Note that for OR queries, orderBy applies to all disjunction terms and implicit orderBys must
  6382. // be taken into account. For example, the query "a > 1 || b==1" has an implicit "orderBy a" due
  6383. // to the inequality, and is evaluated as "a > 1 orderBy a || b==1 orderBy a".
  6384. // A document with content of {b:1} matches the filters, but does not match the orderBy because
  6385. // it's missing the field 'a'.
  6386. for (const orderBy of queryOrderBy(query)) {
  6387. // order by key always matches
  6388. if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
  6389. return false;
  6390. }
  6391. }
  6392. return true;
  6393. }
  6394. function queryMatchesFilters(query, doc) {
  6395. for (const filter of query.filters) {
  6396. if (!filter.matches(doc)) {
  6397. return false;
  6398. }
  6399. }
  6400. return true;
  6401. }
  6402. /** Makes sure a document is within the bounds, if provided. */
  6403. function queryMatchesBounds(query, doc) {
  6404. if (query.startAt &&
  6405. !boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
  6406. return false;
  6407. }
  6408. if (query.endAt &&
  6409. !boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
  6410. return false;
  6411. }
  6412. return true;
  6413. }
  6414. /**
  6415. * Returns the collection group that this query targets.
  6416. *
  6417. * PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
  6418. * synchronization for query results.
  6419. */
  6420. function queryCollectionGroup(query) {
  6421. return (query.collectionGroup ||
  6422. (query.path.length % 2 === 1
  6423. ? query.path.lastSegment()
  6424. : query.path.get(query.path.length - 2)));
  6425. }
  6426. /**
  6427. * Returns a new comparator function that can be used to compare two documents
  6428. * based on the Query's ordering constraint.
  6429. */
  6430. function newQueryComparator(query) {
  6431. return (d1, d2) => {
  6432. let comparedOnKeyField = false;
  6433. for (const orderBy of queryOrderBy(query)) {
  6434. const comp = compareDocs(orderBy, d1, d2);
  6435. if (comp !== 0) {
  6436. return comp;
  6437. }
  6438. comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();
  6439. }
  6440. return 0;
  6441. };
  6442. }
  6443. function compareDocs(orderBy, d1, d2) {
  6444. const comparison = orderBy.field.isKeyField()
  6445. ? DocumentKey.comparator(d1.key, d2.key)
  6446. : compareDocumentsByField(orderBy.field, d1, d2);
  6447. switch (orderBy.dir) {
  6448. case "asc" /* Direction.ASCENDING */:
  6449. return comparison;
  6450. case "desc" /* Direction.DESCENDING */:
  6451. return -1 * comparison;
  6452. default:
  6453. return fail();
  6454. }
  6455. }
  6456. /**
  6457. * @license
  6458. * Copyright 2017 Google LLC
  6459. *
  6460. * Licensed under the Apache License, Version 2.0 (the "License");
  6461. * you may not use this file except in compliance with the License.
  6462. * You may obtain a copy of the License at
  6463. *
  6464. * http://www.apache.org/licenses/LICENSE-2.0
  6465. *
  6466. * Unless required by applicable law or agreed to in writing, software
  6467. * distributed under the License is distributed on an "AS IS" BASIS,
  6468. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6469. * See the License for the specific language governing permissions and
  6470. * limitations under the License.
  6471. */
  6472. /**
  6473. * A map implementation that uses objects as keys. Objects must have an
  6474. * associated equals function and must be immutable. Entries in the map are
  6475. * stored together with the key being produced from the mapKeyFn. This map
  6476. * automatically handles collisions of keys.
  6477. */
  6478. class ObjectMap {
  6479. constructor(mapKeyFn, equalsFn) {
  6480. this.mapKeyFn = mapKeyFn;
  6481. this.equalsFn = equalsFn;
  6482. /**
  6483. * The inner map for a key/value pair. Due to the possibility of collisions we
  6484. * keep a list of entries that we do a linear search through to find an actual
  6485. * match. Note that collisions should be rare, so we still expect near
  6486. * constant time lookups in practice.
  6487. */
  6488. this.inner = {};
  6489. /** The number of entries stored in the map */
  6490. this.innerSize = 0;
  6491. }
  6492. /** Get a value for this key, or undefined if it does not exist. */
  6493. get(key) {
  6494. const id = this.mapKeyFn(key);
  6495. const matches = this.inner[id];
  6496. if (matches === undefined) {
  6497. return undefined;
  6498. }
  6499. for (const [otherKey, value] of matches) {
  6500. if (this.equalsFn(otherKey, key)) {
  6501. return value;
  6502. }
  6503. }
  6504. return undefined;
  6505. }
  6506. has(key) {
  6507. return this.get(key) !== undefined;
  6508. }
  6509. /** Put this key and value in the map. */
  6510. set(key, value) {
  6511. const id = this.mapKeyFn(key);
  6512. const matches = this.inner[id];
  6513. if (matches === undefined) {
  6514. this.inner[id] = [[key, value]];
  6515. this.innerSize++;
  6516. return;
  6517. }
  6518. for (let i = 0; i < matches.length; i++) {
  6519. if (this.equalsFn(matches[i][0], key)) {
  6520. // This is updating an existing entry and does not increase `innerSize`.
  6521. matches[i] = [key, value];
  6522. return;
  6523. }
  6524. }
  6525. matches.push([key, value]);
  6526. this.innerSize++;
  6527. }
  6528. /**
  6529. * Remove this key from the map. Returns a boolean if anything was deleted.
  6530. */
  6531. delete(key) {
  6532. const id = this.mapKeyFn(key);
  6533. const matches = this.inner[id];
  6534. if (matches === undefined) {
  6535. return false;
  6536. }
  6537. for (let i = 0; i < matches.length; i++) {
  6538. if (this.equalsFn(matches[i][0], key)) {
  6539. if (matches.length === 1) {
  6540. delete this.inner[id];
  6541. }
  6542. else {
  6543. matches.splice(i, 1);
  6544. }
  6545. this.innerSize--;
  6546. return true;
  6547. }
  6548. }
  6549. return false;
  6550. }
  6551. forEach(fn) {
  6552. forEach(this.inner, (_, entries) => {
  6553. for (const [k, v] of entries) {
  6554. fn(k, v);
  6555. }
  6556. });
  6557. }
  6558. isEmpty() {
  6559. return isEmpty(this.inner);
  6560. }
  6561. size() {
  6562. return this.innerSize;
  6563. }
  6564. }
  6565. /**
  6566. * @license
  6567. * Copyright 2017 Google LLC
  6568. *
  6569. * Licensed under the Apache License, Version 2.0 (the "License");
  6570. * you may not use this file except in compliance with the License.
  6571. * You may obtain a copy of the License at
  6572. *
  6573. * http://www.apache.org/licenses/LICENSE-2.0
  6574. *
  6575. * Unless required by applicable law or agreed to in writing, software
  6576. * distributed under the License is distributed on an "AS IS" BASIS,
  6577. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6578. * See the License for the specific language governing permissions and
  6579. * limitations under the License.
  6580. */
  6581. const EMPTY_MUTABLE_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
  6582. function mutableDocumentMap() {
  6583. return EMPTY_MUTABLE_DOCUMENT_MAP;
  6584. }
  6585. const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
  6586. function documentMap(...docs) {
  6587. let map = EMPTY_DOCUMENT_MAP;
  6588. for (const doc of docs) {
  6589. map = map.insert(doc.key, doc);
  6590. }
  6591. return map;
  6592. }
  6593. function newOverlayedDocumentMap() {
  6594. return newDocumentKeyMap();
  6595. }
  6596. function convertOverlayedDocumentMapToDocumentMap(collection) {
  6597. let documents = EMPTY_DOCUMENT_MAP;
  6598. collection.forEach((k, v) => (documents = documents.insert(k, v.overlayedDocument)));
  6599. return documents;
  6600. }
  6601. function newOverlayMap() {
  6602. return newDocumentKeyMap();
  6603. }
  6604. function newMutationMap() {
  6605. return newDocumentKeyMap();
  6606. }
  6607. function newDocumentKeyMap() {
  6608. return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
  6609. }
  6610. const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
  6611. function documentVersionMap() {
  6612. return EMPTY_DOCUMENT_VERSION_MAP;
  6613. }
  6614. const EMPTY_DOCUMENT_KEY_SET = new SortedSet(DocumentKey.comparator);
  6615. function documentKeySet(...keys) {
  6616. let set = EMPTY_DOCUMENT_KEY_SET;
  6617. for (const key of keys) {
  6618. set = set.add(key);
  6619. }
  6620. return set;
  6621. }
  6622. const EMPTY_TARGET_ID_SET = new SortedSet(primitiveComparator);
  6623. function targetIdSet() {
  6624. return EMPTY_TARGET_ID_SET;
  6625. }
  6626. /**
  6627. * @license
  6628. * Copyright 2020 Google LLC
  6629. *
  6630. * Licensed under the Apache License, Version 2.0 (the "License");
  6631. * you may not use this file except in compliance with the License.
  6632. * You may obtain a copy of the License at
  6633. *
  6634. * http://www.apache.org/licenses/LICENSE-2.0
  6635. *
  6636. * Unless required by applicable law or agreed to in writing, software
  6637. * distributed under the License is distributed on an "AS IS" BASIS,
  6638. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6639. * See the License for the specific language governing permissions and
  6640. * limitations under the License.
  6641. */
  6642. /**
  6643. * Returns an DoubleValue for `value` that is encoded based the serializer's
  6644. * `useProto3Json` setting.
  6645. */
  6646. function toDouble(serializer, value) {
  6647. if (serializer.useProto3Json) {
  6648. if (isNaN(value)) {
  6649. return { doubleValue: 'NaN' };
  6650. }
  6651. else if (value === Infinity) {
  6652. return { doubleValue: 'Infinity' };
  6653. }
  6654. else if (value === -Infinity) {
  6655. return { doubleValue: '-Infinity' };
  6656. }
  6657. }
  6658. return { doubleValue: isNegativeZero(value) ? '-0' : value };
  6659. }
  6660. /**
  6661. * Returns an IntegerValue for `value`.
  6662. */
  6663. function toInteger(value) {
  6664. return { integerValue: '' + value };
  6665. }
  6666. /**
  6667. * Returns a value for a number that's appropriate to put into a proto.
  6668. * The return value is an IntegerValue if it can safely represent the value,
  6669. * otherwise a DoubleValue is returned.
  6670. */
  6671. function toNumber(serializer, value) {
  6672. return isSafeInteger(value) ? toInteger(value) : toDouble(serializer, value);
  6673. }
  6674. /**
  6675. * @license
  6676. * Copyright 2018 Google LLC
  6677. *
  6678. * Licensed under the Apache License, Version 2.0 (the "License");
  6679. * you may not use this file except in compliance with the License.
  6680. * You may obtain a copy of the License at
  6681. *
  6682. * http://www.apache.org/licenses/LICENSE-2.0
  6683. *
  6684. * Unless required by applicable law or agreed to in writing, software
  6685. * distributed under the License is distributed on an "AS IS" BASIS,
  6686. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6687. * See the License for the specific language governing permissions and
  6688. * limitations under the License.
  6689. */
  6690. /** Used to represent a field transform on a mutation. */
  6691. class TransformOperation {
  6692. constructor() {
  6693. // Make sure that the structural type of `TransformOperation` is unique.
  6694. // See https://github.com/microsoft/TypeScript/issues/5451
  6695. this._ = undefined;
  6696. }
  6697. }
  6698. /**
  6699. * Computes the local transform result against the provided `previousValue`,
  6700. * optionally using the provided localWriteTime.
  6701. */
  6702. function applyTransformOperationToLocalView(transform, previousValue, localWriteTime) {
  6703. if (transform instanceof ServerTimestampTransform) {
  6704. return serverTimestamp$1(localWriteTime, previousValue);
  6705. }
  6706. else if (transform instanceof ArrayUnionTransformOperation) {
  6707. return applyArrayUnionTransformOperation(transform, previousValue);
  6708. }
  6709. else if (transform instanceof ArrayRemoveTransformOperation) {
  6710. return applyArrayRemoveTransformOperation(transform, previousValue);
  6711. }
  6712. else {
  6713. return applyNumericIncrementTransformOperationToLocalView(transform, previousValue);
  6714. }
  6715. }
  6716. /**
  6717. * Computes a final transform result after the transform has been acknowledged
  6718. * by the server, potentially using the server-provided transformResult.
  6719. */
  6720. function applyTransformOperationToRemoteDocument(transform, previousValue, transformResult) {
  6721. // The server just sends null as the transform result for array operations,
  6722. // so we have to calculate a result the same as we do for local
  6723. // applications.
  6724. if (transform instanceof ArrayUnionTransformOperation) {
  6725. return applyArrayUnionTransformOperation(transform, previousValue);
  6726. }
  6727. else if (transform instanceof ArrayRemoveTransformOperation) {
  6728. return applyArrayRemoveTransformOperation(transform, previousValue);
  6729. }
  6730. return transformResult;
  6731. }
  6732. /**
  6733. * If this transform operation is not idempotent, returns the base value to
  6734. * persist for this transform. If a base value is returned, the transform
  6735. * operation is always applied to this base value, even if document has
  6736. * already been updated.
  6737. *
  6738. * Base values provide consistent behavior for non-idempotent transforms and
  6739. * allow us to return the same latency-compensated value even if the backend
  6740. * has already applied the transform operation. The base value is null for
  6741. * idempotent transforms, as they can be re-played even if the backend has
  6742. * already applied them.
  6743. *
  6744. * @returns a base value to store along with the mutation, or null for
  6745. * idempotent transforms.
  6746. */
  6747. function computeTransformOperationBaseValue(transform, previousValue) {
  6748. if (transform instanceof NumericIncrementTransformOperation) {
  6749. return isNumber(previousValue) ? previousValue : { integerValue: 0 };
  6750. }
  6751. return null;
  6752. }
  6753. function transformOperationEquals(left, right) {
  6754. if (left instanceof ArrayUnionTransformOperation &&
  6755. right instanceof ArrayUnionTransformOperation) {
  6756. return arrayEquals(left.elements, right.elements, valueEquals);
  6757. }
  6758. else if (left instanceof ArrayRemoveTransformOperation &&
  6759. right instanceof ArrayRemoveTransformOperation) {
  6760. return arrayEquals(left.elements, right.elements, valueEquals);
  6761. }
  6762. else if (left instanceof NumericIncrementTransformOperation &&
  6763. right instanceof NumericIncrementTransformOperation) {
  6764. return valueEquals(left.operand, right.operand);
  6765. }
  6766. return (left instanceof ServerTimestampTransform &&
  6767. right instanceof ServerTimestampTransform);
  6768. }
  6769. /** Transforms a value into a server-generated timestamp. */
  6770. class ServerTimestampTransform extends TransformOperation {
  6771. }
  6772. /** Transforms an array value via a union operation. */
  6773. class ArrayUnionTransformOperation extends TransformOperation {
  6774. constructor(elements) {
  6775. super();
  6776. this.elements = elements;
  6777. }
  6778. }
  6779. function applyArrayUnionTransformOperation(transform, previousValue) {
  6780. const values = coercedFieldValuesArray(previousValue);
  6781. for (const toUnion of transform.elements) {
  6782. if (!values.some(element => valueEquals(element, toUnion))) {
  6783. values.push(toUnion);
  6784. }
  6785. }
  6786. return { arrayValue: { values } };
  6787. }
  6788. /** Transforms an array value via a remove operation. */
  6789. class ArrayRemoveTransformOperation extends TransformOperation {
  6790. constructor(elements) {
  6791. super();
  6792. this.elements = elements;
  6793. }
  6794. }
  6795. function applyArrayRemoveTransformOperation(transform, previousValue) {
  6796. let values = coercedFieldValuesArray(previousValue);
  6797. for (const toRemove of transform.elements) {
  6798. values = values.filter(element => !valueEquals(element, toRemove));
  6799. }
  6800. return { arrayValue: { values } };
  6801. }
  6802. /**
  6803. * Implements the backend semantics for locally computed NUMERIC_ADD (increment)
  6804. * transforms. Converts all field values to integers or doubles, but unlike the
  6805. * backend does not cap integer values at 2^63. Instead, JavaScript number
  6806. * arithmetic is used and precision loss can occur for values greater than 2^53.
  6807. */
  6808. class NumericIncrementTransformOperation extends TransformOperation {
  6809. constructor(serializer, operand) {
  6810. super();
  6811. this.serializer = serializer;
  6812. this.operand = operand;
  6813. }
  6814. }
  6815. function applyNumericIncrementTransformOperationToLocalView(transform, previousValue) {
  6816. // PORTING NOTE: Since JavaScript's integer arithmetic is limited to 53 bit
  6817. // precision and resolves overflows by reducing precision, we do not
  6818. // manually cap overflows at 2^63.
  6819. const baseValue = computeTransformOperationBaseValue(transform, previousValue);
  6820. const sum = asNumber(baseValue) + asNumber(transform.operand);
  6821. if (isInteger(baseValue) && isInteger(transform.operand)) {
  6822. return toInteger(sum);
  6823. }
  6824. else {
  6825. return toDouble(transform.serializer, sum);
  6826. }
  6827. }
  6828. function asNumber(value) {
  6829. return normalizeNumber(value.integerValue || value.doubleValue);
  6830. }
  6831. function coercedFieldValuesArray(value) {
  6832. return isArray(value) && value.arrayValue.values
  6833. ? value.arrayValue.values.slice()
  6834. : [];
  6835. }
  6836. /**
  6837. * @license
  6838. * Copyright 2017 Google LLC
  6839. *
  6840. * Licensed under the Apache License, Version 2.0 (the "License");
  6841. * you may not use this file except in compliance with the License.
  6842. * You may obtain a copy of the License at
  6843. *
  6844. * http://www.apache.org/licenses/LICENSE-2.0
  6845. *
  6846. * Unless required by applicable law or agreed to in writing, software
  6847. * distributed under the License is distributed on an "AS IS" BASIS,
  6848. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6849. * See the License for the specific language governing permissions and
  6850. * limitations under the License.
  6851. */
  6852. /** A field path and the TransformOperation to perform upon it. */
  6853. class FieldTransform {
  6854. constructor(field, transform) {
  6855. this.field = field;
  6856. this.transform = transform;
  6857. }
  6858. }
  6859. function fieldTransformEquals(left, right) {
  6860. return (left.field.isEqual(right.field) &&
  6861. transformOperationEquals(left.transform, right.transform));
  6862. }
  6863. function fieldTransformsAreEqual(left, right) {
  6864. if (left === undefined && right === undefined) {
  6865. return true;
  6866. }
  6867. if (left && right) {
  6868. return arrayEquals(left, right, (l, r) => fieldTransformEquals(l, r));
  6869. }
  6870. return false;
  6871. }
  6872. /** The result of successfully applying a mutation to the backend. */
  6873. class MutationResult {
  6874. constructor(
  6875. /**
  6876. * The version at which the mutation was committed:
  6877. *
  6878. * - For most operations, this is the updateTime in the WriteResult.
  6879. * - For deletes, the commitTime of the WriteResponse (because deletes are
  6880. * not stored and have no updateTime).
  6881. *
  6882. * Note that these versions can be different: No-op writes will not change
  6883. * the updateTime even though the commitTime advances.
  6884. */
  6885. version,
  6886. /**
  6887. * The resulting fields returned from the backend after a mutation
  6888. * containing field transforms has been committed. Contains one FieldValue
  6889. * for each FieldTransform that was in the mutation.
  6890. *
  6891. * Will be empty if the mutation did not contain any field transforms.
  6892. */
  6893. transformResults) {
  6894. this.version = version;
  6895. this.transformResults = transformResults;
  6896. }
  6897. }
  6898. /**
  6899. * Encodes a precondition for a mutation. This follows the model that the
  6900. * backend accepts with the special case of an explicit "empty" precondition
  6901. * (meaning no precondition).
  6902. */
  6903. class Precondition {
  6904. constructor(updateTime, exists) {
  6905. this.updateTime = updateTime;
  6906. this.exists = exists;
  6907. }
  6908. /** Creates a new empty Precondition. */
  6909. static none() {
  6910. return new Precondition();
  6911. }
  6912. /** Creates a new Precondition with an exists flag. */
  6913. static exists(exists) {
  6914. return new Precondition(undefined, exists);
  6915. }
  6916. /** Creates a new Precondition based on a version a document exists at. */
  6917. static updateTime(version) {
  6918. return new Precondition(version);
  6919. }
  6920. /** Returns whether this Precondition is empty. */
  6921. get isNone() {
  6922. return this.updateTime === undefined && this.exists === undefined;
  6923. }
  6924. isEqual(other) {
  6925. return (this.exists === other.exists &&
  6926. (this.updateTime
  6927. ? !!other.updateTime && this.updateTime.isEqual(other.updateTime)
  6928. : !other.updateTime));
  6929. }
  6930. }
  6931. /** Returns true if the preconditions is valid for the given document. */
  6932. function preconditionIsValidForDocument(precondition, document) {
  6933. if (precondition.updateTime !== undefined) {
  6934. return (document.isFoundDocument() &&
  6935. document.version.isEqual(precondition.updateTime));
  6936. }
  6937. else if (precondition.exists !== undefined) {
  6938. return precondition.exists === document.isFoundDocument();
  6939. }
  6940. else {
  6941. return true;
  6942. }
  6943. }
  6944. /**
  6945. * A mutation describes a self-contained change to a document. Mutations can
  6946. * create, replace, delete, and update subsets of documents.
  6947. *
  6948. * Mutations not only act on the value of the document but also its version.
  6949. *
  6950. * For local mutations (mutations that haven't been committed yet), we preserve
  6951. * the existing version for Set and Patch mutations. For Delete mutations, we
  6952. * reset the version to 0.
  6953. *
  6954. * Here's the expected transition table.
  6955. *
  6956. * MUTATION APPLIED TO RESULTS IN
  6957. *
  6958. * SetMutation Document(v3) Document(v3)
  6959. * SetMutation NoDocument(v3) Document(v0)
  6960. * SetMutation InvalidDocument(v0) Document(v0)
  6961. * PatchMutation Document(v3) Document(v3)
  6962. * PatchMutation NoDocument(v3) NoDocument(v3)
  6963. * PatchMutation InvalidDocument(v0) UnknownDocument(v3)
  6964. * DeleteMutation Document(v3) NoDocument(v0)
  6965. * DeleteMutation NoDocument(v3) NoDocument(v0)
  6966. * DeleteMutation InvalidDocument(v0) NoDocument(v0)
  6967. *
  6968. * For acknowledged mutations, we use the updateTime of the WriteResponse as
  6969. * the resulting version for Set and Patch mutations. As deletes have no
  6970. * explicit update time, we use the commitTime of the WriteResponse for
  6971. * Delete mutations.
  6972. *
  6973. * If a mutation is acknowledged by the backend but fails the precondition check
  6974. * locally, we transition to an `UnknownDocument` and rely on Watch to send us
  6975. * the updated version.
  6976. *
  6977. * Field transforms are used only with Patch and Set Mutations. We use the
  6978. * `updateTransforms` message to store transforms, rather than the `transforms`s
  6979. * messages.
  6980. *
  6981. * ## Subclassing Notes
  6982. *
  6983. * Every type of mutation needs to implement its own applyToRemoteDocument() and
  6984. * applyToLocalView() to implement the actual behavior of applying the mutation
  6985. * to some source document (see `setMutationApplyToRemoteDocument()` for an
  6986. * example).
  6987. */
  6988. class Mutation {
  6989. }
  6990. /**
  6991. * A utility method to calculate a `Mutation` representing the overlay from the
  6992. * final state of the document, and a `FieldMask` representing the fields that
  6993. * are mutated by the local mutations.
  6994. */
  6995. function calculateOverlayMutation(doc, mask) {
  6996. if (!doc.hasLocalMutations || (mask && mask.fields.length === 0)) {
  6997. return null;
  6998. }
  6999. // mask is null when sets or deletes are applied to the current document.
  7000. if (mask === null) {
  7001. if (doc.isNoDocument()) {
  7002. return new DeleteMutation(doc.key, Precondition.none());
  7003. }
  7004. else {
  7005. return new SetMutation(doc.key, doc.data, Precondition.none());
  7006. }
  7007. }
  7008. else {
  7009. const docValue = doc.data;
  7010. const patchValue = ObjectValue.empty();
  7011. let maskSet = new SortedSet(FieldPath$1.comparator);
  7012. for (let path of mask.fields) {
  7013. if (!maskSet.has(path)) {
  7014. let value = docValue.field(path);
  7015. // If we are deleting a nested field, we take the immediate parent as
  7016. // the mask used to construct the resulting mutation.
  7017. // Justification: Nested fields can create parent fields implicitly. If
  7018. // only a leaf entry is deleted in later mutations, the parent field
  7019. // should still remain, but we may have lost this information.
  7020. // Consider mutation (foo.bar 1), then mutation (foo.bar delete()).
  7021. // This leaves the final result (foo, {}). Despite the fact that `doc`
  7022. // has the correct result, `foo` is not in `mask`, and the resulting
  7023. // mutation would miss `foo`.
  7024. if (value === null && path.length > 1) {
  7025. path = path.popLast();
  7026. value = docValue.field(path);
  7027. }
  7028. if (value === null) {
  7029. patchValue.delete(path);
  7030. }
  7031. else {
  7032. patchValue.set(path, value);
  7033. }
  7034. maskSet = maskSet.add(path);
  7035. }
  7036. }
  7037. return new PatchMutation(doc.key, patchValue, new FieldMask(maskSet.toArray()), Precondition.none());
  7038. }
  7039. }
  7040. /**
  7041. * Applies this mutation to the given document for the purposes of computing a
  7042. * new remote document. If the input document doesn't match the expected state
  7043. * (e.g. it is invalid or outdated), the document type may transition to
  7044. * unknown.
  7045. *
  7046. * @param mutation - The mutation to apply.
  7047. * @param document - The document to mutate. The input document can be an
  7048. * invalid document if the client has no knowledge of the pre-mutation state
  7049. * of the document.
  7050. * @param mutationResult - The result of applying the mutation from the backend.
  7051. */
  7052. function mutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7053. if (mutation instanceof SetMutation) {
  7054. setMutationApplyToRemoteDocument(mutation, document, mutationResult);
  7055. }
  7056. else if (mutation instanceof PatchMutation) {
  7057. patchMutationApplyToRemoteDocument(mutation, document, mutationResult);
  7058. }
  7059. else {
  7060. deleteMutationApplyToRemoteDocument(mutation, document, mutationResult);
  7061. }
  7062. }
  7063. /**
  7064. * Applies this mutation to the given document for the purposes of computing
  7065. * the new local view of a document. If the input document doesn't match the
  7066. * expected state, the document is not modified.
  7067. *
  7068. * @param mutation - The mutation to apply.
  7069. * @param document - The document to mutate. The input document can be an
  7070. * invalid document if the client has no knowledge of the pre-mutation state
  7071. * of the document.
  7072. * @param previousMask - The fields that have been updated before applying this mutation.
  7073. * @param localWriteTime - A timestamp indicating the local write time of the
  7074. * batch this mutation is a part of.
  7075. * @returns A `FieldMask` representing the fields that are changed by applying this mutation.
  7076. */
  7077. function mutationApplyToLocalView(mutation, document, previousMask, localWriteTime) {
  7078. if (mutation instanceof SetMutation) {
  7079. return setMutationApplyToLocalView(mutation, document, previousMask, localWriteTime);
  7080. }
  7081. else if (mutation instanceof PatchMutation) {
  7082. return patchMutationApplyToLocalView(mutation, document, previousMask, localWriteTime);
  7083. }
  7084. else {
  7085. return deleteMutationApplyToLocalView(mutation, document, previousMask);
  7086. }
  7087. }
  7088. /**
  7089. * If this mutation is not idempotent, returns the base value to persist with
  7090. * this mutation. If a base value is returned, the mutation is always applied
  7091. * to this base value, even if document has already been updated.
  7092. *
  7093. * The base value is a sparse object that consists of only the document
  7094. * fields for which this mutation contains a non-idempotent transformation
  7095. * (e.g. a numeric increment). The provided value guarantees consistent
  7096. * behavior for non-idempotent transforms and allow us to return the same
  7097. * latency-compensated value even if the backend has already applied the
  7098. * mutation. The base value is null for idempotent mutations, as they can be
  7099. * re-played even if the backend has already applied them.
  7100. *
  7101. * @returns a base value to store along with the mutation, or null for
  7102. * idempotent mutations.
  7103. */
  7104. function mutationExtractBaseValue(mutation, document) {
  7105. let baseObject = null;
  7106. for (const fieldTransform of mutation.fieldTransforms) {
  7107. const existingValue = document.data.field(fieldTransform.field);
  7108. const coercedValue = computeTransformOperationBaseValue(fieldTransform.transform, existingValue || null);
  7109. if (coercedValue != null) {
  7110. if (baseObject === null) {
  7111. baseObject = ObjectValue.empty();
  7112. }
  7113. baseObject.set(fieldTransform.field, coercedValue);
  7114. }
  7115. }
  7116. return baseObject ? baseObject : null;
  7117. }
  7118. function mutationEquals(left, right) {
  7119. if (left.type !== right.type) {
  7120. return false;
  7121. }
  7122. if (!left.key.isEqual(right.key)) {
  7123. return false;
  7124. }
  7125. if (!left.precondition.isEqual(right.precondition)) {
  7126. return false;
  7127. }
  7128. if (!fieldTransformsAreEqual(left.fieldTransforms, right.fieldTransforms)) {
  7129. return false;
  7130. }
  7131. if (left.type === 0 /* MutationType.Set */) {
  7132. return left.value.isEqual(right.value);
  7133. }
  7134. if (left.type === 1 /* MutationType.Patch */) {
  7135. return (left.data.isEqual(right.data) &&
  7136. left.fieldMask.isEqual(right.fieldMask));
  7137. }
  7138. return true;
  7139. }
  7140. /**
  7141. * A mutation that creates or replaces the document at the given key with the
  7142. * object value contents.
  7143. */
  7144. class SetMutation extends Mutation {
  7145. constructor(key, value, precondition, fieldTransforms = []) {
  7146. super();
  7147. this.key = key;
  7148. this.value = value;
  7149. this.precondition = precondition;
  7150. this.fieldTransforms = fieldTransforms;
  7151. this.type = 0 /* MutationType.Set */;
  7152. }
  7153. getFieldMask() {
  7154. return null;
  7155. }
  7156. }
  7157. function setMutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7158. // Unlike setMutationApplyToLocalView, if we're applying a mutation to a
  7159. // remote document the server has accepted the mutation so the precondition
  7160. // must have held.
  7161. const newData = mutation.value.clone();
  7162. const transformResults = serverTransformResults(mutation.fieldTransforms, document, mutationResult.transformResults);
  7163. newData.setAll(transformResults);
  7164. document
  7165. .convertToFoundDocument(mutationResult.version, newData)
  7166. .setHasCommittedMutations();
  7167. }
  7168. function setMutationApplyToLocalView(mutation, document, previousMask, localWriteTime) {
  7169. if (!preconditionIsValidForDocument(mutation.precondition, document)) {
  7170. // The mutation failed to apply (e.g. a document ID created with add()
  7171. // caused a name collision).
  7172. return previousMask;
  7173. }
  7174. const newData = mutation.value.clone();
  7175. const transformResults = localTransformResults(mutation.fieldTransforms, localWriteTime, document);
  7176. newData.setAll(transformResults);
  7177. document
  7178. .convertToFoundDocument(document.version, newData)
  7179. .setHasLocalMutations();
  7180. return null; // SetMutation overwrites all fields.
  7181. }
  7182. /**
  7183. * A mutation that modifies fields of the document at the given key with the
  7184. * given values. The values are applied through a field mask:
  7185. *
  7186. * * When a field is in both the mask and the values, the corresponding field
  7187. * is updated.
  7188. * * When a field is in neither the mask nor the values, the corresponding
  7189. * field is unmodified.
  7190. * * When a field is in the mask but not in the values, the corresponding field
  7191. * is deleted.
  7192. * * When a field is not in the mask but is in the values, the values map is
  7193. * ignored.
  7194. */
  7195. class PatchMutation extends Mutation {
  7196. constructor(key, data, fieldMask, precondition, fieldTransforms = []) {
  7197. super();
  7198. this.key = key;
  7199. this.data = data;
  7200. this.fieldMask = fieldMask;
  7201. this.precondition = precondition;
  7202. this.fieldTransforms = fieldTransforms;
  7203. this.type = 1 /* MutationType.Patch */;
  7204. }
  7205. getFieldMask() {
  7206. return this.fieldMask;
  7207. }
  7208. }
  7209. function patchMutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7210. if (!preconditionIsValidForDocument(mutation.precondition, document)) {
  7211. // Since the mutation was not rejected, we know that the precondition
  7212. // matched on the backend. We therefore must not have the expected version
  7213. // of the document in our cache and convert to an UnknownDocument with a
  7214. // known updateTime.
  7215. document.convertToUnknownDocument(mutationResult.version);
  7216. return;
  7217. }
  7218. const transformResults = serverTransformResults(mutation.fieldTransforms, document, mutationResult.transformResults);
  7219. const newData = document.data;
  7220. newData.setAll(getPatch(mutation));
  7221. newData.setAll(transformResults);
  7222. document
  7223. .convertToFoundDocument(mutationResult.version, newData)
  7224. .setHasCommittedMutations();
  7225. }
  7226. function patchMutationApplyToLocalView(mutation, document, previousMask, localWriteTime) {
  7227. if (!preconditionIsValidForDocument(mutation.precondition, document)) {
  7228. return previousMask;
  7229. }
  7230. const transformResults = localTransformResults(mutation.fieldTransforms, localWriteTime, document);
  7231. const newData = document.data;
  7232. newData.setAll(getPatch(mutation));
  7233. newData.setAll(transformResults);
  7234. document
  7235. .convertToFoundDocument(document.version, newData)
  7236. .setHasLocalMutations();
  7237. if (previousMask === null) {
  7238. return null;
  7239. }
  7240. return previousMask
  7241. .unionWith(mutation.fieldMask.fields)
  7242. .unionWith(mutation.fieldTransforms.map(transform => transform.field));
  7243. }
  7244. /**
  7245. * Returns a FieldPath/Value map with the content of the PatchMutation.
  7246. */
  7247. function getPatch(mutation) {
  7248. const result = new Map();
  7249. mutation.fieldMask.fields.forEach(fieldPath => {
  7250. if (!fieldPath.isEmpty()) {
  7251. const newValue = mutation.data.field(fieldPath);
  7252. result.set(fieldPath, newValue);
  7253. }
  7254. });
  7255. return result;
  7256. }
  7257. /**
  7258. * Creates a list of "transform results" (a transform result is a field value
  7259. * representing the result of applying a transform) for use after a mutation
  7260. * containing transforms has been acknowledged by the server.
  7261. *
  7262. * @param fieldTransforms - The field transforms to apply the result to.
  7263. * @param mutableDocument - The current state of the document after applying all
  7264. * previous mutations.
  7265. * @param serverTransformResults - The transform results received by the server.
  7266. * @returns The transform results list.
  7267. */
  7268. function serverTransformResults(fieldTransforms, mutableDocument, serverTransformResults) {
  7269. const transformResults = new Map();
  7270. hardAssert(fieldTransforms.length === serverTransformResults.length);
  7271. for (let i = 0; i < serverTransformResults.length; i++) {
  7272. const fieldTransform = fieldTransforms[i];
  7273. const transform = fieldTransform.transform;
  7274. const previousValue = mutableDocument.data.field(fieldTransform.field);
  7275. transformResults.set(fieldTransform.field, applyTransformOperationToRemoteDocument(transform, previousValue, serverTransformResults[i]));
  7276. }
  7277. return transformResults;
  7278. }
  7279. /**
  7280. * Creates a list of "transform results" (a transform result is a field value
  7281. * representing the result of applying a transform) for use when applying a
  7282. * transform locally.
  7283. *
  7284. * @param fieldTransforms - The field transforms to apply the result to.
  7285. * @param localWriteTime - The local time of the mutation (used to
  7286. * generate ServerTimestampValues).
  7287. * @param mutableDocument - The document to apply transforms on.
  7288. * @returns The transform results list.
  7289. */
  7290. function localTransformResults(fieldTransforms, localWriteTime, mutableDocument) {
  7291. const transformResults = new Map();
  7292. for (const fieldTransform of fieldTransforms) {
  7293. const transform = fieldTransform.transform;
  7294. const previousValue = mutableDocument.data.field(fieldTransform.field);
  7295. transformResults.set(fieldTransform.field, applyTransformOperationToLocalView(transform, previousValue, localWriteTime));
  7296. }
  7297. return transformResults;
  7298. }
  7299. /** A mutation that deletes the document at the given key. */
  7300. class DeleteMutation extends Mutation {
  7301. constructor(key, precondition) {
  7302. super();
  7303. this.key = key;
  7304. this.precondition = precondition;
  7305. this.type = 2 /* MutationType.Delete */;
  7306. this.fieldTransforms = [];
  7307. }
  7308. getFieldMask() {
  7309. return null;
  7310. }
  7311. }
  7312. function deleteMutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7313. // Unlike applyToLocalView, if we're applying a mutation to a remote
  7314. // document the server has accepted the mutation so the precondition must
  7315. // have held.
  7316. document
  7317. .convertToNoDocument(mutationResult.version)
  7318. .setHasCommittedMutations();
  7319. }
  7320. function deleteMutationApplyToLocalView(mutation, document, previousMask) {
  7321. if (preconditionIsValidForDocument(mutation.precondition, document)) {
  7322. document.convertToNoDocument(document.version).setHasLocalMutations();
  7323. return null;
  7324. }
  7325. return previousMask;
  7326. }
  7327. /**
  7328. * A mutation that verifies the existence of the document at the given key with
  7329. * the provided precondition.
  7330. *
  7331. * The `verify` operation is only used in Transactions, and this class serves
  7332. * primarily to facilitate serialization into protos.
  7333. */
  7334. class VerifyMutation extends Mutation {
  7335. constructor(key, precondition) {
  7336. super();
  7337. this.key = key;
  7338. this.precondition = precondition;
  7339. this.type = 3 /* MutationType.Verify */;
  7340. this.fieldTransforms = [];
  7341. }
  7342. getFieldMask() {
  7343. return null;
  7344. }
  7345. }
  7346. /**
  7347. * @license
  7348. * Copyright 2017 Google LLC
  7349. *
  7350. * Licensed under the Apache License, Version 2.0 (the "License");
  7351. * you may not use this file except in compliance with the License.
  7352. * You may obtain a copy of the License at
  7353. *
  7354. * http://www.apache.org/licenses/LICENSE-2.0
  7355. *
  7356. * Unless required by applicable law or agreed to in writing, software
  7357. * distributed under the License is distributed on an "AS IS" BASIS,
  7358. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7359. * See the License for the specific language governing permissions and
  7360. * limitations under the License.
  7361. */
  7362. /**
  7363. * A batch of mutations that will be sent as one unit to the backend.
  7364. */
  7365. class MutationBatch {
  7366. /**
  7367. * @param batchId - The unique ID of this mutation batch.
  7368. * @param localWriteTime - The original write time of this mutation.
  7369. * @param baseMutations - Mutations that are used to populate the base
  7370. * values when this mutation is applied locally. This can be used to locally
  7371. * overwrite values that are persisted in the remote document cache. Base
  7372. * mutations are never sent to the backend.
  7373. * @param mutations - The user-provided mutations in this mutation batch.
  7374. * User-provided mutations are applied both locally and remotely on the
  7375. * backend.
  7376. */
  7377. constructor(batchId, localWriteTime, baseMutations, mutations) {
  7378. this.batchId = batchId;
  7379. this.localWriteTime = localWriteTime;
  7380. this.baseMutations = baseMutations;
  7381. this.mutations = mutations;
  7382. }
  7383. /**
  7384. * Applies all the mutations in this MutationBatch to the specified document
  7385. * to compute the state of the remote document
  7386. *
  7387. * @param document - The document to apply mutations to.
  7388. * @param batchResult - The result of applying the MutationBatch to the
  7389. * backend.
  7390. */
  7391. applyToRemoteDocument(document, batchResult) {
  7392. const mutationResults = batchResult.mutationResults;
  7393. for (let i = 0; i < this.mutations.length; i++) {
  7394. const mutation = this.mutations[i];
  7395. if (mutation.key.isEqual(document.key)) {
  7396. const mutationResult = mutationResults[i];
  7397. mutationApplyToRemoteDocument(mutation, document, mutationResult);
  7398. }
  7399. }
  7400. }
  7401. /**
  7402. * Computes the local view of a document given all the mutations in this
  7403. * batch.
  7404. *
  7405. * @param document - The document to apply mutations to.
  7406. * @param mutatedFields - Fields that have been updated before applying this mutation batch.
  7407. * @returns A `FieldMask` representing all the fields that are mutated.
  7408. */
  7409. applyToLocalView(document, mutatedFields) {
  7410. // First, apply the base state. This allows us to apply non-idempotent
  7411. // transform against a consistent set of values.
  7412. for (const mutation of this.baseMutations) {
  7413. if (mutation.key.isEqual(document.key)) {
  7414. mutatedFields = mutationApplyToLocalView(mutation, document, mutatedFields, this.localWriteTime);
  7415. }
  7416. }
  7417. // Second, apply all user-provided mutations.
  7418. for (const mutation of this.mutations) {
  7419. if (mutation.key.isEqual(document.key)) {
  7420. mutatedFields = mutationApplyToLocalView(mutation, document, mutatedFields, this.localWriteTime);
  7421. }
  7422. }
  7423. return mutatedFields;
  7424. }
  7425. /**
  7426. * Computes the local view for all provided documents given the mutations in
  7427. * this batch. Returns a `DocumentKey` to `Mutation` map which can be used to
  7428. * replace all the mutation applications.
  7429. */
  7430. applyToLocalDocumentSet(documentMap, documentsWithoutRemoteVersion) {
  7431. // TODO(mrschmidt): This implementation is O(n^2). If we apply the mutations
  7432. // directly (as done in `applyToLocalView()`), we can reduce the complexity
  7433. // to O(n).
  7434. const overlays = newMutationMap();
  7435. this.mutations.forEach(m => {
  7436. const overlayedDocument = documentMap.get(m.key);
  7437. // TODO(mutabledocuments): This method should take a MutableDocumentMap
  7438. // and we should remove this cast.
  7439. const mutableDocument = overlayedDocument.overlayedDocument;
  7440. let mutatedFields = this.applyToLocalView(mutableDocument, overlayedDocument.mutatedFields);
  7441. // Set mutatedFields to null if the document is only from local mutations.
  7442. // This creates a Set or Delete mutation, instead of trying to create a
  7443. // patch mutation as the overlay.
  7444. mutatedFields = documentsWithoutRemoteVersion.has(m.key)
  7445. ? null
  7446. : mutatedFields;
  7447. const overlay = calculateOverlayMutation(mutableDocument, mutatedFields);
  7448. if (overlay !== null) {
  7449. overlays.set(m.key, overlay);
  7450. }
  7451. if (!mutableDocument.isValidDocument()) {
  7452. mutableDocument.convertToNoDocument(SnapshotVersion.min());
  7453. }
  7454. });
  7455. return overlays;
  7456. }
  7457. keys() {
  7458. return this.mutations.reduce((keys, m) => keys.add(m.key), documentKeySet());
  7459. }
  7460. isEqual(other) {
  7461. return (this.batchId === other.batchId &&
  7462. arrayEquals(this.mutations, other.mutations, (l, r) => mutationEquals(l, r)) &&
  7463. arrayEquals(this.baseMutations, other.baseMutations, (l, r) => mutationEquals(l, r)));
  7464. }
  7465. }
  7466. /** The result of applying a mutation batch to the backend. */
  7467. class MutationBatchResult {
  7468. constructor(batch, commitVersion, mutationResults,
  7469. /**
  7470. * A pre-computed mapping from each mutated document to the resulting
  7471. * version.
  7472. */
  7473. docVersions) {
  7474. this.batch = batch;
  7475. this.commitVersion = commitVersion;
  7476. this.mutationResults = mutationResults;
  7477. this.docVersions = docVersions;
  7478. }
  7479. /**
  7480. * Creates a new MutationBatchResult for the given batch and results. There
  7481. * must be one result for each mutation in the batch. This static factory
  7482. * caches a document=&gt;version mapping (docVersions).
  7483. */
  7484. static from(batch, commitVersion, results) {
  7485. hardAssert(batch.mutations.length === results.length);
  7486. let versionMap = documentVersionMap();
  7487. const mutations = batch.mutations;
  7488. for (let i = 0; i < mutations.length; i++) {
  7489. versionMap = versionMap.insert(mutations[i].key, results[i].version);
  7490. }
  7491. return new MutationBatchResult(batch, commitVersion, results, versionMap);
  7492. }
  7493. }
  7494. /**
  7495. * @license
  7496. * Copyright 2022 Google LLC
  7497. *
  7498. * Licensed under the Apache License, Version 2.0 (the "License");
  7499. * you may not use this file except in compliance with the License.
  7500. * You may obtain a copy of the License at
  7501. *
  7502. * http://www.apache.org/licenses/LICENSE-2.0
  7503. *
  7504. * Unless required by applicable law or agreed to in writing, software
  7505. * distributed under the License is distributed on an "AS IS" BASIS,
  7506. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7507. * See the License for the specific language governing permissions and
  7508. * limitations under the License.
  7509. */
  7510. /**
  7511. * Representation of an overlay computed by Firestore.
  7512. *
  7513. * Holds information about a mutation and the largest batch id in Firestore when
  7514. * the mutation was created.
  7515. */
  7516. class Overlay {
  7517. constructor(largestBatchId, mutation) {
  7518. this.largestBatchId = largestBatchId;
  7519. this.mutation = mutation;
  7520. }
  7521. getKey() {
  7522. return this.mutation.key;
  7523. }
  7524. isEqual(other) {
  7525. return other !== null && this.mutation === other.mutation;
  7526. }
  7527. toString() {
  7528. return `Overlay{
  7529. largestBatchId: ${this.largestBatchId},
  7530. mutation: ${this.mutation.toString()}
  7531. }`;
  7532. }
  7533. }
  7534. /**
  7535. * @license
  7536. * Copyright 2017 Google LLC
  7537. *
  7538. * Licensed under the Apache License, Version 2.0 (the "License");
  7539. * you may not use this file except in compliance with the License.
  7540. * You may obtain a copy of the License at
  7541. *
  7542. * http://www.apache.org/licenses/LICENSE-2.0
  7543. *
  7544. * Unless required by applicable law or agreed to in writing, software
  7545. * distributed under the License is distributed on an "AS IS" BASIS,
  7546. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7547. * See the License for the specific language governing permissions and
  7548. * limitations under the License.
  7549. */
  7550. class ExistenceFilter {
  7551. // TODO(b/33078163): just use simplest form of existence filter for now
  7552. constructor(count) {
  7553. this.count = count;
  7554. }
  7555. }
  7556. /**
  7557. * @license
  7558. * Copyright 2017 Google LLC
  7559. *
  7560. * Licensed under the Apache License, Version 2.0 (the "License");
  7561. * you may not use this file except in compliance with the License.
  7562. * You may obtain a copy of the License at
  7563. *
  7564. * http://www.apache.org/licenses/LICENSE-2.0
  7565. *
  7566. * Unless required by applicable law or agreed to in writing, software
  7567. * distributed under the License is distributed on an "AS IS" BASIS,
  7568. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7569. * See the License for the specific language governing permissions and
  7570. * limitations under the License.
  7571. */
  7572. /**
  7573. * Error Codes describing the different ways GRPC can fail. These are copied
  7574. * directly from GRPC's sources here:
  7575. *
  7576. * https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h
  7577. *
  7578. * Important! The names of these identifiers matter because the string forms
  7579. * are used for reverse lookups from the webchannel stream. Do NOT change the
  7580. * names of these identifiers or change this into a const enum.
  7581. */
  7582. var RpcCode;
  7583. (function (RpcCode) {
  7584. RpcCode[RpcCode["OK"] = 0] = "OK";
  7585. RpcCode[RpcCode["CANCELLED"] = 1] = "CANCELLED";
  7586. RpcCode[RpcCode["UNKNOWN"] = 2] = "UNKNOWN";
  7587. RpcCode[RpcCode["INVALID_ARGUMENT"] = 3] = "INVALID_ARGUMENT";
  7588. RpcCode[RpcCode["DEADLINE_EXCEEDED"] = 4] = "DEADLINE_EXCEEDED";
  7589. RpcCode[RpcCode["NOT_FOUND"] = 5] = "NOT_FOUND";
  7590. RpcCode[RpcCode["ALREADY_EXISTS"] = 6] = "ALREADY_EXISTS";
  7591. RpcCode[RpcCode["PERMISSION_DENIED"] = 7] = "PERMISSION_DENIED";
  7592. RpcCode[RpcCode["UNAUTHENTICATED"] = 16] = "UNAUTHENTICATED";
  7593. RpcCode[RpcCode["RESOURCE_EXHAUSTED"] = 8] = "RESOURCE_EXHAUSTED";
  7594. RpcCode[RpcCode["FAILED_PRECONDITION"] = 9] = "FAILED_PRECONDITION";
  7595. RpcCode[RpcCode["ABORTED"] = 10] = "ABORTED";
  7596. RpcCode[RpcCode["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE";
  7597. RpcCode[RpcCode["UNIMPLEMENTED"] = 12] = "UNIMPLEMENTED";
  7598. RpcCode[RpcCode["INTERNAL"] = 13] = "INTERNAL";
  7599. RpcCode[RpcCode["UNAVAILABLE"] = 14] = "UNAVAILABLE";
  7600. RpcCode[RpcCode["DATA_LOSS"] = 15] = "DATA_LOSS";
  7601. })(RpcCode || (RpcCode = {}));
  7602. /**
  7603. * Determines whether an error code represents a permanent error when received
  7604. * in response to a non-write operation.
  7605. *
  7606. * See isPermanentWriteError for classifying write errors.
  7607. */
  7608. function isPermanentError(code) {
  7609. switch (code) {
  7610. case Code.OK:
  7611. return fail();
  7612. case Code.CANCELLED:
  7613. case Code.UNKNOWN:
  7614. case Code.DEADLINE_EXCEEDED:
  7615. case Code.RESOURCE_EXHAUSTED:
  7616. case Code.INTERNAL:
  7617. case Code.UNAVAILABLE:
  7618. // Unauthenticated means something went wrong with our token and we need
  7619. // to retry with new credentials which will happen automatically.
  7620. case Code.UNAUTHENTICATED:
  7621. return false;
  7622. case Code.INVALID_ARGUMENT:
  7623. case Code.NOT_FOUND:
  7624. case Code.ALREADY_EXISTS:
  7625. case Code.PERMISSION_DENIED:
  7626. case Code.FAILED_PRECONDITION:
  7627. // Aborted might be retried in some scenarios, but that is dependant on
  7628. // the context and should handled individually by the calling code.
  7629. // See https://cloud.google.com/apis/design/errors.
  7630. case Code.ABORTED:
  7631. case Code.OUT_OF_RANGE:
  7632. case Code.UNIMPLEMENTED:
  7633. case Code.DATA_LOSS:
  7634. return true;
  7635. default:
  7636. return fail();
  7637. }
  7638. }
  7639. /**
  7640. * Determines whether an error code represents a permanent error when received
  7641. * in response to a write operation.
  7642. *
  7643. * Write operations must be handled specially because as of b/119437764, ABORTED
  7644. * errors on the write stream should be retried too (even though ABORTED errors
  7645. * are not generally retryable).
  7646. *
  7647. * Note that during the initial handshake on the write stream an ABORTED error
  7648. * signals that we should discard our stream token (i.e. it is permanent). This
  7649. * means a handshake error should be classified with isPermanentError, above.
  7650. */
  7651. function isPermanentWriteError(code) {
  7652. return isPermanentError(code) && code !== Code.ABORTED;
  7653. }
  7654. /**
  7655. * Maps an error Code from GRPC status code number, like 0, 1, or 14. These
  7656. * are not the same as HTTP status codes.
  7657. *
  7658. * @returns The Code equivalent to the given GRPC status code. Fails if there
  7659. * is no match.
  7660. */
  7661. function mapCodeFromRpcCode(code) {
  7662. if (code === undefined) {
  7663. // This shouldn't normally happen, but in certain error cases (like trying
  7664. // to send invalid proto messages) we may get an error with no GRPC code.
  7665. logError('GRPC error has no .code');
  7666. return Code.UNKNOWN;
  7667. }
  7668. switch (code) {
  7669. case RpcCode.OK:
  7670. return Code.OK;
  7671. case RpcCode.CANCELLED:
  7672. return Code.CANCELLED;
  7673. case RpcCode.UNKNOWN:
  7674. return Code.UNKNOWN;
  7675. case RpcCode.DEADLINE_EXCEEDED:
  7676. return Code.DEADLINE_EXCEEDED;
  7677. case RpcCode.RESOURCE_EXHAUSTED:
  7678. return Code.RESOURCE_EXHAUSTED;
  7679. case RpcCode.INTERNAL:
  7680. return Code.INTERNAL;
  7681. case RpcCode.UNAVAILABLE:
  7682. return Code.UNAVAILABLE;
  7683. case RpcCode.UNAUTHENTICATED:
  7684. return Code.UNAUTHENTICATED;
  7685. case RpcCode.INVALID_ARGUMENT:
  7686. return Code.INVALID_ARGUMENT;
  7687. case RpcCode.NOT_FOUND:
  7688. return Code.NOT_FOUND;
  7689. case RpcCode.ALREADY_EXISTS:
  7690. return Code.ALREADY_EXISTS;
  7691. case RpcCode.PERMISSION_DENIED:
  7692. return Code.PERMISSION_DENIED;
  7693. case RpcCode.FAILED_PRECONDITION:
  7694. return Code.FAILED_PRECONDITION;
  7695. case RpcCode.ABORTED:
  7696. return Code.ABORTED;
  7697. case RpcCode.OUT_OF_RANGE:
  7698. return Code.OUT_OF_RANGE;
  7699. case RpcCode.UNIMPLEMENTED:
  7700. return Code.UNIMPLEMENTED;
  7701. case RpcCode.DATA_LOSS:
  7702. return Code.DATA_LOSS;
  7703. default:
  7704. return fail();
  7705. }
  7706. }
  7707. /**
  7708. * @license
  7709. * Copyright 2017 Google LLC
  7710. *
  7711. * Licensed under the Apache License, Version 2.0 (the "License");
  7712. * you may not use this file except in compliance with the License.
  7713. * You may obtain a copy of the License at
  7714. *
  7715. * http://www.apache.org/licenses/LICENSE-2.0
  7716. *
  7717. * Unless required by applicable law or agreed to in writing, software
  7718. * distributed under the License is distributed on an "AS IS" BASIS,
  7719. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7720. * See the License for the specific language governing permissions and
  7721. * limitations under the License.
  7722. */
  7723. /**
  7724. * An event from the RemoteStore. It is split into targetChanges (changes to the
  7725. * state or the set of documents in our watched targets) and documentUpdates
  7726. * (changes to the actual documents).
  7727. */
  7728. class RemoteEvent {
  7729. constructor(
  7730. /**
  7731. * The snapshot version this event brings us up to, or MIN if not set.
  7732. */
  7733. snapshotVersion,
  7734. /**
  7735. * A map from target to changes to the target. See TargetChange.
  7736. */
  7737. targetChanges,
  7738. /**
  7739. * A set of targets that is known to be inconsistent. Listens for these
  7740. * targets should be re-established without resume tokens.
  7741. */
  7742. targetMismatches,
  7743. /**
  7744. * A set of which documents have changed or been deleted, along with the
  7745. * doc's new values (if not deleted).
  7746. */
  7747. documentUpdates,
  7748. /**
  7749. * A set of which document updates are due only to limbo resolution targets.
  7750. */
  7751. resolvedLimboDocuments) {
  7752. this.snapshotVersion = snapshotVersion;
  7753. this.targetChanges = targetChanges;
  7754. this.targetMismatches = targetMismatches;
  7755. this.documentUpdates = documentUpdates;
  7756. this.resolvedLimboDocuments = resolvedLimboDocuments;
  7757. }
  7758. /**
  7759. * HACK: Views require RemoteEvents in order to determine whether the view is
  7760. * CURRENT, but secondary tabs don't receive remote events. So this method is
  7761. * used to create a synthesized RemoteEvent that can be used to apply a
  7762. * CURRENT status change to a View, for queries executed in a different tab.
  7763. */
  7764. // PORTING NOTE: Multi-tab only
  7765. static createSynthesizedRemoteEventForCurrentChange(targetId, current, resumeToken) {
  7766. const targetChanges = new Map();
  7767. targetChanges.set(targetId, TargetChange.createSynthesizedTargetChangeForCurrentChange(targetId, current, resumeToken));
  7768. return new RemoteEvent(SnapshotVersion.min(), targetChanges, targetIdSet(), mutableDocumentMap(), documentKeySet());
  7769. }
  7770. }
  7771. /**
  7772. * A TargetChange specifies the set of changes for a specific target as part of
  7773. * a RemoteEvent. These changes track which documents are added, modified or
  7774. * removed, as well as the target's resume token and whether the target is
  7775. * marked CURRENT.
  7776. * The actual changes *to* documents are not part of the TargetChange since
  7777. * documents may be part of multiple targets.
  7778. */
  7779. class TargetChange {
  7780. constructor(
  7781. /**
  7782. * An opaque, server-assigned token that allows watching a query to be resumed
  7783. * after disconnecting without retransmitting all the data that matches the
  7784. * query. The resume token essentially identifies a point in time from which
  7785. * the server should resume sending results.
  7786. */
  7787. resumeToken,
  7788. /**
  7789. * The "current" (synced) status of this target. Note that "current"
  7790. * has special meaning in the RPC protocol that implies that a target is
  7791. * both up-to-date and consistent with the rest of the watch stream.
  7792. */
  7793. current,
  7794. /**
  7795. * The set of documents that were newly assigned to this target as part of
  7796. * this remote event.
  7797. */
  7798. addedDocuments,
  7799. /**
  7800. * The set of documents that were already assigned to this target but received
  7801. * an update during this remote event.
  7802. */
  7803. modifiedDocuments,
  7804. /**
  7805. * The set of documents that were removed from this target as part of this
  7806. * remote event.
  7807. */
  7808. removedDocuments) {
  7809. this.resumeToken = resumeToken;
  7810. this.current = current;
  7811. this.addedDocuments = addedDocuments;
  7812. this.modifiedDocuments = modifiedDocuments;
  7813. this.removedDocuments = removedDocuments;
  7814. }
  7815. /**
  7816. * This method is used to create a synthesized TargetChanges that can be used to
  7817. * apply a CURRENT status change to a View (for queries executed in a different
  7818. * tab) or for new queries (to raise snapshots with correct CURRENT status).
  7819. */
  7820. static createSynthesizedTargetChangeForCurrentChange(targetId, current, resumeToken) {
  7821. return new TargetChange(resumeToken, current, documentKeySet(), documentKeySet(), documentKeySet());
  7822. }
  7823. }
  7824. /**
  7825. * @license
  7826. * Copyright 2017 Google LLC
  7827. *
  7828. * Licensed under the Apache License, Version 2.0 (the "License");
  7829. * you may not use this file except in compliance with the License.
  7830. * You may obtain a copy of the License at
  7831. *
  7832. * http://www.apache.org/licenses/LICENSE-2.0
  7833. *
  7834. * Unless required by applicable law or agreed to in writing, software
  7835. * distributed under the License is distributed on an "AS IS" BASIS,
  7836. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7837. * See the License for the specific language governing permissions and
  7838. * limitations under the License.
  7839. */
  7840. /**
  7841. * Represents a changed document and a list of target ids to which this change
  7842. * applies.
  7843. *
  7844. * If document has been deleted NoDocument will be provided.
  7845. */
  7846. class DocumentWatchChange {
  7847. constructor(
  7848. /** The new document applies to all of these targets. */
  7849. updatedTargetIds,
  7850. /** The new document is removed from all of these targets. */
  7851. removedTargetIds,
  7852. /** The key of the document for this change. */
  7853. key,
  7854. /**
  7855. * The new document or NoDocument if it was deleted. Is null if the
  7856. * document went out of view without the server sending a new document.
  7857. */
  7858. newDoc) {
  7859. this.updatedTargetIds = updatedTargetIds;
  7860. this.removedTargetIds = removedTargetIds;
  7861. this.key = key;
  7862. this.newDoc = newDoc;
  7863. }
  7864. }
  7865. class ExistenceFilterChange {
  7866. constructor(targetId, existenceFilter) {
  7867. this.targetId = targetId;
  7868. this.existenceFilter = existenceFilter;
  7869. }
  7870. }
  7871. class WatchTargetChange {
  7872. constructor(
  7873. /** What kind of change occurred to the watch target. */
  7874. state,
  7875. /** The target IDs that were added/removed/set. */
  7876. targetIds,
  7877. /**
  7878. * An opaque, server-assigned token that allows watching a target to be
  7879. * resumed after disconnecting without retransmitting all the data that
  7880. * matches the target. The resume token essentially identifies a point in
  7881. * time from which the server should resume sending results.
  7882. */
  7883. resumeToken = ByteString.EMPTY_BYTE_STRING,
  7884. /** An RPC error indicating why the watch failed. */
  7885. cause = null) {
  7886. this.state = state;
  7887. this.targetIds = targetIds;
  7888. this.resumeToken = resumeToken;
  7889. this.cause = cause;
  7890. }
  7891. }
  7892. /** Tracks the internal state of a Watch target. */
  7893. class TargetState {
  7894. constructor() {
  7895. /**
  7896. * The number of pending responses (adds or removes) that we are waiting on.
  7897. * We only consider targets active that have no pending responses.
  7898. */
  7899. this.pendingResponses = 0;
  7900. /**
  7901. * Keeps track of the document changes since the last raised snapshot.
  7902. *
  7903. * These changes are continuously updated as we receive document updates and
  7904. * always reflect the current set of changes against the last issued snapshot.
  7905. */
  7906. this.documentChanges = snapshotChangesMap();
  7907. /** See public getters for explanations of these fields. */
  7908. this._resumeToken = ByteString.EMPTY_BYTE_STRING;
  7909. this._current = false;
  7910. /**
  7911. * Whether this target state should be included in the next snapshot. We
  7912. * initialize to true so that newly-added targets are included in the next
  7913. * RemoteEvent.
  7914. */
  7915. this._hasPendingChanges = true;
  7916. }
  7917. /**
  7918. * Whether this target has been marked 'current'.
  7919. *
  7920. * 'Current' has special meaning in the RPC protocol: It implies that the
  7921. * Watch backend has sent us all changes up to the point at which the target
  7922. * was added and that the target is consistent with the rest of the watch
  7923. * stream.
  7924. */
  7925. get current() {
  7926. return this._current;
  7927. }
  7928. /** The last resume token sent to us for this target. */
  7929. get resumeToken() {
  7930. return this._resumeToken;
  7931. }
  7932. /** Whether this target has pending target adds or target removes. */
  7933. get isPending() {
  7934. return this.pendingResponses !== 0;
  7935. }
  7936. /** Whether we have modified any state that should trigger a snapshot. */
  7937. get hasPendingChanges() {
  7938. return this._hasPendingChanges;
  7939. }
  7940. /**
  7941. * Applies the resume token to the TargetChange, but only when it has a new
  7942. * value. Empty resumeTokens are discarded.
  7943. */
  7944. updateResumeToken(resumeToken) {
  7945. if (resumeToken.approximateByteSize() > 0) {
  7946. this._hasPendingChanges = true;
  7947. this._resumeToken = resumeToken;
  7948. }
  7949. }
  7950. /**
  7951. * Creates a target change from the current set of changes.
  7952. *
  7953. * To reset the document changes after raising this snapshot, call
  7954. * `clearPendingChanges()`.
  7955. */
  7956. toTargetChange() {
  7957. let addedDocuments = documentKeySet();
  7958. let modifiedDocuments = documentKeySet();
  7959. let removedDocuments = documentKeySet();
  7960. this.documentChanges.forEach((key, changeType) => {
  7961. switch (changeType) {
  7962. case 0 /* ChangeType.Added */:
  7963. addedDocuments = addedDocuments.add(key);
  7964. break;
  7965. case 2 /* ChangeType.Modified */:
  7966. modifiedDocuments = modifiedDocuments.add(key);
  7967. break;
  7968. case 1 /* ChangeType.Removed */:
  7969. removedDocuments = removedDocuments.add(key);
  7970. break;
  7971. default:
  7972. fail();
  7973. }
  7974. });
  7975. return new TargetChange(this._resumeToken, this._current, addedDocuments, modifiedDocuments, removedDocuments);
  7976. }
  7977. /**
  7978. * Resets the document changes and sets `hasPendingChanges` to false.
  7979. */
  7980. clearPendingChanges() {
  7981. this._hasPendingChanges = false;
  7982. this.documentChanges = snapshotChangesMap();
  7983. }
  7984. addDocumentChange(key, changeType) {
  7985. this._hasPendingChanges = true;
  7986. this.documentChanges = this.documentChanges.insert(key, changeType);
  7987. }
  7988. removeDocumentChange(key) {
  7989. this._hasPendingChanges = true;
  7990. this.documentChanges = this.documentChanges.remove(key);
  7991. }
  7992. recordPendingTargetRequest() {
  7993. this.pendingResponses += 1;
  7994. }
  7995. recordTargetResponse() {
  7996. this.pendingResponses -= 1;
  7997. }
  7998. markCurrent() {
  7999. this._hasPendingChanges = true;
  8000. this._current = true;
  8001. }
  8002. }
  8003. const LOG_TAG$g = 'WatchChangeAggregator';
  8004. /**
  8005. * A helper class to accumulate watch changes into a RemoteEvent.
  8006. */
  8007. class WatchChangeAggregator {
  8008. constructor(metadataProvider) {
  8009. this.metadataProvider = metadataProvider;
  8010. /** The internal state of all tracked targets. */
  8011. this.targetStates = new Map();
  8012. /** Keeps track of the documents to update since the last raised snapshot. */
  8013. this.pendingDocumentUpdates = mutableDocumentMap();
  8014. /** A mapping of document keys to their set of target IDs. */
  8015. this.pendingDocumentTargetMapping = documentTargetMap();
  8016. /**
  8017. * A list of targets with existence filter mismatches. These targets are
  8018. * known to be inconsistent and their listens needs to be re-established by
  8019. * RemoteStore.
  8020. */
  8021. this.pendingTargetResets = new SortedSet(primitiveComparator);
  8022. }
  8023. /**
  8024. * Processes and adds the DocumentWatchChange to the current set of changes.
  8025. */
  8026. handleDocumentChange(docChange) {
  8027. for (const targetId of docChange.updatedTargetIds) {
  8028. if (docChange.newDoc && docChange.newDoc.isFoundDocument()) {
  8029. this.addDocumentToTarget(targetId, docChange.newDoc);
  8030. }
  8031. else {
  8032. this.removeDocumentFromTarget(targetId, docChange.key, docChange.newDoc);
  8033. }
  8034. }
  8035. for (const targetId of docChange.removedTargetIds) {
  8036. this.removeDocumentFromTarget(targetId, docChange.key, docChange.newDoc);
  8037. }
  8038. }
  8039. /** Processes and adds the WatchTargetChange to the current set of changes. */
  8040. handleTargetChange(targetChange) {
  8041. this.forEachTarget(targetChange, targetId => {
  8042. const targetState = this.ensureTargetState(targetId);
  8043. switch (targetChange.state) {
  8044. case 0 /* WatchTargetChangeState.NoChange */:
  8045. if (this.isActiveTarget(targetId)) {
  8046. targetState.updateResumeToken(targetChange.resumeToken);
  8047. }
  8048. break;
  8049. case 1 /* WatchTargetChangeState.Added */:
  8050. // We need to decrement the number of pending acks needed from watch
  8051. // for this targetId.
  8052. targetState.recordTargetResponse();
  8053. if (!targetState.isPending) {
  8054. // We have a freshly added target, so we need to reset any state
  8055. // that we had previously. This can happen e.g. when remove and add
  8056. // back a target for existence filter mismatches.
  8057. targetState.clearPendingChanges();
  8058. }
  8059. targetState.updateResumeToken(targetChange.resumeToken);
  8060. break;
  8061. case 2 /* WatchTargetChangeState.Removed */:
  8062. // We need to keep track of removed targets to we can post-filter and
  8063. // remove any target changes.
  8064. // We need to decrement the number of pending acks needed from watch
  8065. // for this targetId.
  8066. targetState.recordTargetResponse();
  8067. if (!targetState.isPending) {
  8068. this.removeTarget(targetId);
  8069. }
  8070. break;
  8071. case 3 /* WatchTargetChangeState.Current */:
  8072. if (this.isActiveTarget(targetId)) {
  8073. targetState.markCurrent();
  8074. targetState.updateResumeToken(targetChange.resumeToken);
  8075. }
  8076. break;
  8077. case 4 /* WatchTargetChangeState.Reset */:
  8078. if (this.isActiveTarget(targetId)) {
  8079. // Reset the target and synthesizes removes for all existing
  8080. // documents. The backend will re-add any documents that still
  8081. // match the target before it sends the next global snapshot.
  8082. this.resetTarget(targetId);
  8083. targetState.updateResumeToken(targetChange.resumeToken);
  8084. }
  8085. break;
  8086. default:
  8087. fail();
  8088. }
  8089. });
  8090. }
  8091. /**
  8092. * Iterates over all targetIds that the watch change applies to: either the
  8093. * targetIds explicitly listed in the change or the targetIds of all currently
  8094. * active targets.
  8095. */
  8096. forEachTarget(targetChange, fn) {
  8097. if (targetChange.targetIds.length > 0) {
  8098. targetChange.targetIds.forEach(fn);
  8099. }
  8100. else {
  8101. this.targetStates.forEach((_, targetId) => {
  8102. if (this.isActiveTarget(targetId)) {
  8103. fn(targetId);
  8104. }
  8105. });
  8106. }
  8107. }
  8108. /**
  8109. * Handles existence filters and synthesizes deletes for filter mismatches.
  8110. * Targets that are invalidated by filter mismatches are added to
  8111. * `pendingTargetResets`.
  8112. */
  8113. handleExistenceFilter(watchChange) {
  8114. const targetId = watchChange.targetId;
  8115. const expectedCount = watchChange.existenceFilter.count;
  8116. const targetData = this.targetDataForActiveTarget(targetId);
  8117. if (targetData) {
  8118. const target = targetData.target;
  8119. if (targetIsDocumentTarget(target)) {
  8120. if (expectedCount === 0) {
  8121. // The existence filter told us the document does not exist. We deduce
  8122. // that this document does not exist and apply a deleted document to
  8123. // our updates. Without applying this deleted document there might be
  8124. // another query that will raise this document as part of a snapshot
  8125. // until it is resolved, essentially exposing inconsistency between
  8126. // queries.
  8127. const key = new DocumentKey(target.path);
  8128. this.removeDocumentFromTarget(targetId, key, MutableDocument.newNoDocument(key, SnapshotVersion.min()));
  8129. }
  8130. else {
  8131. hardAssert(expectedCount === 1);
  8132. }
  8133. }
  8134. else {
  8135. const currentSize = this.getCurrentDocumentCountForTarget(targetId);
  8136. if (currentSize !== expectedCount) {
  8137. // Existence filter mismatch: We reset the mapping and raise a new
  8138. // snapshot with `isFromCache:true`.
  8139. this.resetTarget(targetId);
  8140. this.pendingTargetResets = this.pendingTargetResets.add(targetId);
  8141. }
  8142. }
  8143. }
  8144. }
  8145. /**
  8146. * Converts the currently accumulated state into a remote event at the
  8147. * provided snapshot version. Resets the accumulated changes before returning.
  8148. */
  8149. createRemoteEvent(snapshotVersion) {
  8150. const targetChanges = new Map();
  8151. this.targetStates.forEach((targetState, targetId) => {
  8152. const targetData = this.targetDataForActiveTarget(targetId);
  8153. if (targetData) {
  8154. if (targetState.current && targetIsDocumentTarget(targetData.target)) {
  8155. // Document queries for document that don't exist can produce an empty
  8156. // result set. To update our local cache, we synthesize a document
  8157. // delete if we have not previously received the document. This
  8158. // resolves the limbo state of the document, removing it from
  8159. // limboDocumentRefs.
  8160. //
  8161. // TODO(dimond): Ideally we would have an explicit lookup target
  8162. // instead resulting in an explicit delete message and we could
  8163. // remove this special logic.
  8164. const key = new DocumentKey(targetData.target.path);
  8165. if (this.pendingDocumentUpdates.get(key) === null &&
  8166. !this.targetContainsDocument(targetId, key)) {
  8167. this.removeDocumentFromTarget(targetId, key, MutableDocument.newNoDocument(key, snapshotVersion));
  8168. }
  8169. }
  8170. if (targetState.hasPendingChanges) {
  8171. targetChanges.set(targetId, targetState.toTargetChange());
  8172. targetState.clearPendingChanges();
  8173. }
  8174. }
  8175. });
  8176. let resolvedLimboDocuments = documentKeySet();
  8177. // We extract the set of limbo-only document updates as the GC logic
  8178. // special-cases documents that do not appear in the target cache.
  8179. //
  8180. // TODO(gsoltis): Expand on this comment once GC is available in the JS
  8181. // client.
  8182. this.pendingDocumentTargetMapping.forEach((key, targets) => {
  8183. let isOnlyLimboTarget = true;
  8184. targets.forEachWhile(targetId => {
  8185. const targetData = this.targetDataForActiveTarget(targetId);
  8186. if (targetData &&
  8187. targetData.purpose !== 2 /* TargetPurpose.LimboResolution */) {
  8188. isOnlyLimboTarget = false;
  8189. return false;
  8190. }
  8191. return true;
  8192. });
  8193. if (isOnlyLimboTarget) {
  8194. resolvedLimboDocuments = resolvedLimboDocuments.add(key);
  8195. }
  8196. });
  8197. this.pendingDocumentUpdates.forEach((_, doc) => doc.setReadTime(snapshotVersion));
  8198. const remoteEvent = new RemoteEvent(snapshotVersion, targetChanges, this.pendingTargetResets, this.pendingDocumentUpdates, resolvedLimboDocuments);
  8199. this.pendingDocumentUpdates = mutableDocumentMap();
  8200. this.pendingDocumentTargetMapping = documentTargetMap();
  8201. this.pendingTargetResets = new SortedSet(primitiveComparator);
  8202. return remoteEvent;
  8203. }
  8204. /**
  8205. * Adds the provided document to the internal list of document updates and
  8206. * its document key to the given target's mapping.
  8207. */
  8208. // Visible for testing.
  8209. addDocumentToTarget(targetId, document) {
  8210. if (!this.isActiveTarget(targetId)) {
  8211. return;
  8212. }
  8213. const changeType = this.targetContainsDocument(targetId, document.key)
  8214. ? 2 /* ChangeType.Modified */
  8215. : 0 /* ChangeType.Added */;
  8216. const targetState = this.ensureTargetState(targetId);
  8217. targetState.addDocumentChange(document.key, changeType);
  8218. this.pendingDocumentUpdates = this.pendingDocumentUpdates.insert(document.key, document);
  8219. this.pendingDocumentTargetMapping =
  8220. this.pendingDocumentTargetMapping.insert(document.key, this.ensureDocumentTargetMapping(document.key).add(targetId));
  8221. }
  8222. /**
  8223. * Removes the provided document from the target mapping. If the
  8224. * document no longer matches the target, but the document's state is still
  8225. * known (e.g. we know that the document was deleted or we received the change
  8226. * that caused the filter mismatch), the new document can be provided
  8227. * to update the remote document cache.
  8228. */
  8229. // Visible for testing.
  8230. removeDocumentFromTarget(targetId, key, updatedDocument) {
  8231. if (!this.isActiveTarget(targetId)) {
  8232. return;
  8233. }
  8234. const targetState = this.ensureTargetState(targetId);
  8235. if (this.targetContainsDocument(targetId, key)) {
  8236. targetState.addDocumentChange(key, 1 /* ChangeType.Removed */);
  8237. }
  8238. else {
  8239. // The document may have entered and left the target before we raised a
  8240. // snapshot, so we can just ignore the change.
  8241. targetState.removeDocumentChange(key);
  8242. }
  8243. this.pendingDocumentTargetMapping =
  8244. this.pendingDocumentTargetMapping.insert(key, this.ensureDocumentTargetMapping(key).delete(targetId));
  8245. if (updatedDocument) {
  8246. this.pendingDocumentUpdates = this.pendingDocumentUpdates.insert(key, updatedDocument);
  8247. }
  8248. }
  8249. removeTarget(targetId) {
  8250. this.targetStates.delete(targetId);
  8251. }
  8252. /**
  8253. * Returns the current count of documents in the target. This includes both
  8254. * the number of documents that the LocalStore considers to be part of the
  8255. * target as well as any accumulated changes.
  8256. */
  8257. getCurrentDocumentCountForTarget(targetId) {
  8258. const targetState = this.ensureTargetState(targetId);
  8259. const targetChange = targetState.toTargetChange();
  8260. return (this.metadataProvider.getRemoteKeysForTarget(targetId).size +
  8261. targetChange.addedDocuments.size -
  8262. targetChange.removedDocuments.size);
  8263. }
  8264. /**
  8265. * Increment the number of acks needed from watch before we can consider the
  8266. * server to be 'in-sync' with the client's active targets.
  8267. */
  8268. recordPendingTargetRequest(targetId) {
  8269. // For each request we get we need to record we need a response for it.
  8270. const targetState = this.ensureTargetState(targetId);
  8271. targetState.recordPendingTargetRequest();
  8272. }
  8273. ensureTargetState(targetId) {
  8274. let result = this.targetStates.get(targetId);
  8275. if (!result) {
  8276. result = new TargetState();
  8277. this.targetStates.set(targetId, result);
  8278. }
  8279. return result;
  8280. }
  8281. ensureDocumentTargetMapping(key) {
  8282. let targetMapping = this.pendingDocumentTargetMapping.get(key);
  8283. if (!targetMapping) {
  8284. targetMapping = new SortedSet(primitiveComparator);
  8285. this.pendingDocumentTargetMapping =
  8286. this.pendingDocumentTargetMapping.insert(key, targetMapping);
  8287. }
  8288. return targetMapping;
  8289. }
  8290. /**
  8291. * Verifies that the user is still interested in this target (by calling
  8292. * `getTargetDataForTarget()`) and that we are not waiting for pending ADDs
  8293. * from watch.
  8294. */
  8295. isActiveTarget(targetId) {
  8296. const targetActive = this.targetDataForActiveTarget(targetId) !== null;
  8297. if (!targetActive) {
  8298. logDebug(LOG_TAG$g, 'Detected inactive target', targetId);
  8299. }
  8300. return targetActive;
  8301. }
  8302. /**
  8303. * Returns the TargetData for an active target (i.e. a target that the user
  8304. * is still interested in that has no outstanding target change requests).
  8305. */
  8306. targetDataForActiveTarget(targetId) {
  8307. const targetState = this.targetStates.get(targetId);
  8308. return targetState && targetState.isPending
  8309. ? null
  8310. : this.metadataProvider.getTargetDataForTarget(targetId);
  8311. }
  8312. /**
  8313. * Resets the state of a Watch target to its initial state (e.g. sets
  8314. * 'current' to false, clears the resume token and removes its target mapping
  8315. * from all documents).
  8316. */
  8317. resetTarget(targetId) {
  8318. this.targetStates.set(targetId, new TargetState());
  8319. // Trigger removal for any documents currently mapped to this target.
  8320. // These removals will be part of the initial snapshot if Watch does not
  8321. // resend these documents.
  8322. const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);
  8323. existingKeys.forEach(key => {
  8324. this.removeDocumentFromTarget(targetId, key, /*updatedDocument=*/ null);
  8325. });
  8326. }
  8327. /**
  8328. * Returns whether the LocalStore considers the document to be part of the
  8329. * specified target.
  8330. */
  8331. targetContainsDocument(targetId, key) {
  8332. const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);
  8333. return existingKeys.has(key);
  8334. }
  8335. }
  8336. function documentTargetMap() {
  8337. return new SortedMap(DocumentKey.comparator);
  8338. }
  8339. function snapshotChangesMap() {
  8340. return new SortedMap(DocumentKey.comparator);
  8341. }
  8342. /**
  8343. * @license
  8344. * Copyright 2017 Google LLC
  8345. *
  8346. * Licensed under the Apache License, Version 2.0 (the "License");
  8347. * you may not use this file except in compliance with the License.
  8348. * You may obtain a copy of the License at
  8349. *
  8350. * http://www.apache.org/licenses/LICENSE-2.0
  8351. *
  8352. * Unless required by applicable law or agreed to in writing, software
  8353. * distributed under the License is distributed on an "AS IS" BASIS,
  8354. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8355. * See the License for the specific language governing permissions and
  8356. * limitations under the License.
  8357. */
  8358. const DIRECTIONS = (() => {
  8359. const dirs = {};
  8360. dirs["asc" /* Direction.ASCENDING */] = 'ASCENDING';
  8361. dirs["desc" /* Direction.DESCENDING */] = 'DESCENDING';
  8362. return dirs;
  8363. })();
  8364. const OPERATORS = (() => {
  8365. const ops = {};
  8366. ops["<" /* Operator.LESS_THAN */] = 'LESS_THAN';
  8367. ops["<=" /* Operator.LESS_THAN_OR_EQUAL */] = 'LESS_THAN_OR_EQUAL';
  8368. ops[">" /* Operator.GREATER_THAN */] = 'GREATER_THAN';
  8369. ops[">=" /* Operator.GREATER_THAN_OR_EQUAL */] = 'GREATER_THAN_OR_EQUAL';
  8370. ops["==" /* Operator.EQUAL */] = 'EQUAL';
  8371. ops["!=" /* Operator.NOT_EQUAL */] = 'NOT_EQUAL';
  8372. ops["array-contains" /* Operator.ARRAY_CONTAINS */] = 'ARRAY_CONTAINS';
  8373. ops["in" /* Operator.IN */] = 'IN';
  8374. ops["not-in" /* Operator.NOT_IN */] = 'NOT_IN';
  8375. ops["array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */] = 'ARRAY_CONTAINS_ANY';
  8376. return ops;
  8377. })();
  8378. const COMPOSITE_OPERATORS = (() => {
  8379. const ops = {};
  8380. ops["and" /* CompositeOperator.AND */] = 'AND';
  8381. ops["or" /* CompositeOperator.OR */] = 'OR';
  8382. return ops;
  8383. })();
  8384. function assertPresent(value, description) {
  8385. }
  8386. /**
  8387. * This class generates JsonObject values for the Datastore API suitable for
  8388. * sending to either GRPC stub methods or via the JSON/HTTP REST API.
  8389. *
  8390. * The serializer supports both Protobuf.js and Proto3 JSON formats. By
  8391. * setting `useProto3Json` to true, the serializer will use the Proto3 JSON
  8392. * format.
  8393. *
  8394. * For a description of the Proto3 JSON format check
  8395. * https://developers.google.com/protocol-buffers/docs/proto3#json
  8396. *
  8397. * TODO(klimt): We can remove the databaseId argument if we keep the full
  8398. * resource name in documents.
  8399. */
  8400. class JsonProtoSerializer {
  8401. constructor(databaseId, useProto3Json) {
  8402. this.databaseId = databaseId;
  8403. this.useProto3Json = useProto3Json;
  8404. }
  8405. }
  8406. function fromRpcStatus(status) {
  8407. const code = status.code === undefined ? Code.UNKNOWN : mapCodeFromRpcCode(status.code);
  8408. return new FirestoreError(code, status.message || '');
  8409. }
  8410. /**
  8411. * Returns a value for a number (or null) that's appropriate to put into
  8412. * a google.protobuf.Int32Value proto.
  8413. * DO NOT USE THIS FOR ANYTHING ELSE.
  8414. * This method cheats. It's typed as returning "number" because that's what
  8415. * our generated proto interfaces say Int32Value must be. But GRPC actually
  8416. * expects a { value: <number> } struct.
  8417. */
  8418. function toInt32Proto(serializer, val) {
  8419. if (serializer.useProto3Json || isNullOrUndefined(val)) {
  8420. return val;
  8421. }
  8422. else {
  8423. return { value: val };
  8424. }
  8425. }
  8426. /**
  8427. * Returns a number (or null) from a google.protobuf.Int32Value proto.
  8428. */
  8429. function fromInt32Proto(val) {
  8430. let result;
  8431. if (typeof val === 'object') {
  8432. result = val.value;
  8433. }
  8434. else {
  8435. result = val;
  8436. }
  8437. return isNullOrUndefined(result) ? null : result;
  8438. }
  8439. /**
  8440. * Returns a value for a Date that's appropriate to put into a proto.
  8441. */
  8442. function toTimestamp(serializer, timestamp) {
  8443. if (serializer.useProto3Json) {
  8444. // Serialize to ISO-8601 date format, but with full nano resolution.
  8445. // Since JS Date has only millis, let's only use it for the seconds and
  8446. // then manually add the fractions to the end.
  8447. const jsDateStr = new Date(timestamp.seconds * 1000).toISOString();
  8448. // Remove .xxx frac part and Z in the end.
  8449. const strUntilSeconds = jsDateStr.replace(/\.\d*/, '').replace('Z', '');
  8450. // Pad the fraction out to 9 digits (nanos).
  8451. const nanoStr = ('000000000' + timestamp.nanoseconds).slice(-9);
  8452. return `${strUntilSeconds}.${nanoStr}Z`;
  8453. }
  8454. else {
  8455. return {
  8456. seconds: '' + timestamp.seconds,
  8457. nanos: timestamp.nanoseconds
  8458. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  8459. };
  8460. }
  8461. }
  8462. function fromTimestamp(date) {
  8463. const timestamp = normalizeTimestamp(date);
  8464. return new Timestamp(timestamp.seconds, timestamp.nanos);
  8465. }
  8466. /**
  8467. * Returns a value for bytes that's appropriate to put in a proto.
  8468. *
  8469. * Visible for testing.
  8470. */
  8471. function toBytes(serializer, bytes) {
  8472. if (serializer.useProto3Json) {
  8473. return bytes.toBase64();
  8474. }
  8475. else {
  8476. return bytes.toUint8Array();
  8477. }
  8478. }
  8479. /**
  8480. * Returns a ByteString based on the proto string value.
  8481. */
  8482. function fromBytes(serializer, value) {
  8483. if (serializer.useProto3Json) {
  8484. hardAssert(value === undefined || typeof value === 'string');
  8485. return ByteString.fromBase64String(value ? value : '');
  8486. }
  8487. else {
  8488. hardAssert(value === undefined || value instanceof Uint8Array);
  8489. return ByteString.fromUint8Array(value ? value : new Uint8Array());
  8490. }
  8491. }
  8492. function toVersion(serializer, version) {
  8493. return toTimestamp(serializer, version.toTimestamp());
  8494. }
  8495. function fromVersion(version) {
  8496. hardAssert(!!version);
  8497. return SnapshotVersion.fromTimestamp(fromTimestamp(version));
  8498. }
  8499. function toResourceName(databaseId, path) {
  8500. return fullyQualifiedPrefixPath(databaseId)
  8501. .child('documents')
  8502. .child(path)
  8503. .canonicalString();
  8504. }
  8505. function fromResourceName(name) {
  8506. const resource = ResourcePath.fromString(name);
  8507. hardAssert(isValidResourceName(resource));
  8508. return resource;
  8509. }
  8510. function toName(serializer, key) {
  8511. return toResourceName(serializer.databaseId, key.path);
  8512. }
  8513. function fromName(serializer, name) {
  8514. const resource = fromResourceName(name);
  8515. if (resource.get(1) !== serializer.databaseId.projectId) {
  8516. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Tried to deserialize key from different project: ' +
  8517. resource.get(1) +
  8518. ' vs ' +
  8519. serializer.databaseId.projectId);
  8520. }
  8521. if (resource.get(3) !== serializer.databaseId.database) {
  8522. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Tried to deserialize key from different database: ' +
  8523. resource.get(3) +
  8524. ' vs ' +
  8525. serializer.databaseId.database);
  8526. }
  8527. return new DocumentKey(extractLocalPathFromResourceName(resource));
  8528. }
  8529. function toQueryPath(serializer, path) {
  8530. return toResourceName(serializer.databaseId, path);
  8531. }
  8532. function fromQueryPath(name) {
  8533. const resourceName = fromResourceName(name);
  8534. // In v1beta1 queries for collections at the root did not have a trailing
  8535. // "/documents". In v1 all resource paths contain "/documents". Preserve the
  8536. // ability to read the v1beta1 form for compatibility with queries persisted
  8537. // in the local target cache.
  8538. if (resourceName.length === 4) {
  8539. return ResourcePath.emptyPath();
  8540. }
  8541. return extractLocalPathFromResourceName(resourceName);
  8542. }
  8543. function getEncodedDatabaseId(serializer) {
  8544. const path = new ResourcePath([
  8545. 'projects',
  8546. serializer.databaseId.projectId,
  8547. 'databases',
  8548. serializer.databaseId.database
  8549. ]);
  8550. return path.canonicalString();
  8551. }
  8552. function fullyQualifiedPrefixPath(databaseId) {
  8553. return new ResourcePath([
  8554. 'projects',
  8555. databaseId.projectId,
  8556. 'databases',
  8557. databaseId.database
  8558. ]);
  8559. }
  8560. function extractLocalPathFromResourceName(resourceName) {
  8561. hardAssert(resourceName.length > 4 && resourceName.get(4) === 'documents');
  8562. return resourceName.popFirst(5);
  8563. }
  8564. /** Creates a Document proto from key and fields (but no create/update time) */
  8565. function toMutationDocument(serializer, key, fields) {
  8566. return {
  8567. name: toName(serializer, key),
  8568. fields: fields.value.mapValue.fields
  8569. };
  8570. }
  8571. function toDocument(serializer, document) {
  8572. return {
  8573. name: toName(serializer, document.key),
  8574. fields: document.data.value.mapValue.fields,
  8575. updateTime: toTimestamp(serializer, document.version.toTimestamp()),
  8576. createTime: toTimestamp(serializer, document.createTime.toTimestamp())
  8577. };
  8578. }
  8579. function fromDocument(serializer, document, hasCommittedMutations) {
  8580. const key = fromName(serializer, document.name);
  8581. const version = fromVersion(document.updateTime);
  8582. // If we read a document from persistence that is missing createTime, it's due
  8583. // to older SDK versions not storing this information. In such cases, we'll
  8584. // set the createTime to zero. This can be removed in the long term.
  8585. const createTime = document.createTime
  8586. ? fromVersion(document.createTime)
  8587. : SnapshotVersion.min();
  8588. const data = new ObjectValue({ mapValue: { fields: document.fields } });
  8589. const result = MutableDocument.newFoundDocument(key, version, createTime, data);
  8590. if (hasCommittedMutations) {
  8591. result.setHasCommittedMutations();
  8592. }
  8593. return hasCommittedMutations ? result.setHasCommittedMutations() : result;
  8594. }
  8595. function fromFound(serializer, doc) {
  8596. hardAssert(!!doc.found);
  8597. assertPresent(doc.found.name);
  8598. assertPresent(doc.found.updateTime);
  8599. const key = fromName(serializer, doc.found.name);
  8600. const version = fromVersion(doc.found.updateTime);
  8601. const createTime = doc.found.createTime
  8602. ? fromVersion(doc.found.createTime)
  8603. : SnapshotVersion.min();
  8604. const data = new ObjectValue({ mapValue: { fields: doc.found.fields } });
  8605. return MutableDocument.newFoundDocument(key, version, createTime, data);
  8606. }
  8607. function fromMissing(serializer, result) {
  8608. hardAssert(!!result.missing);
  8609. hardAssert(!!result.readTime);
  8610. const key = fromName(serializer, result.missing);
  8611. const version = fromVersion(result.readTime);
  8612. return MutableDocument.newNoDocument(key, version);
  8613. }
  8614. function fromBatchGetDocumentsResponse(serializer, result) {
  8615. if ('found' in result) {
  8616. return fromFound(serializer, result);
  8617. }
  8618. else if ('missing' in result) {
  8619. return fromMissing(serializer, result);
  8620. }
  8621. return fail();
  8622. }
  8623. function fromWatchChange(serializer, change) {
  8624. let watchChange;
  8625. if ('targetChange' in change) {
  8626. assertPresent(change.targetChange);
  8627. // proto3 default value is unset in JSON (undefined), so use 'NO_CHANGE'
  8628. // if unset
  8629. const state = fromWatchTargetChangeState(change.targetChange.targetChangeType || 'NO_CHANGE');
  8630. const targetIds = change.targetChange.targetIds || [];
  8631. const resumeToken = fromBytes(serializer, change.targetChange.resumeToken);
  8632. const causeProto = change.targetChange.cause;
  8633. const cause = causeProto && fromRpcStatus(causeProto);
  8634. watchChange = new WatchTargetChange(state, targetIds, resumeToken, cause || null);
  8635. }
  8636. else if ('documentChange' in change) {
  8637. assertPresent(change.documentChange);
  8638. const entityChange = change.documentChange;
  8639. assertPresent(entityChange.document);
  8640. assertPresent(entityChange.document.name);
  8641. assertPresent(entityChange.document.updateTime);
  8642. const key = fromName(serializer, entityChange.document.name);
  8643. const version = fromVersion(entityChange.document.updateTime);
  8644. const createTime = entityChange.document.createTime
  8645. ? fromVersion(entityChange.document.createTime)
  8646. : SnapshotVersion.min();
  8647. const data = new ObjectValue({
  8648. mapValue: { fields: entityChange.document.fields }
  8649. });
  8650. const doc = MutableDocument.newFoundDocument(key, version, createTime, data);
  8651. const updatedTargetIds = entityChange.targetIds || [];
  8652. const removedTargetIds = entityChange.removedTargetIds || [];
  8653. watchChange = new DocumentWatchChange(updatedTargetIds, removedTargetIds, doc.key, doc);
  8654. }
  8655. else if ('documentDelete' in change) {
  8656. assertPresent(change.documentDelete);
  8657. const docDelete = change.documentDelete;
  8658. assertPresent(docDelete.document);
  8659. const key = fromName(serializer, docDelete.document);
  8660. const version = docDelete.readTime
  8661. ? fromVersion(docDelete.readTime)
  8662. : SnapshotVersion.min();
  8663. const doc = MutableDocument.newNoDocument(key, version);
  8664. const removedTargetIds = docDelete.removedTargetIds || [];
  8665. watchChange = new DocumentWatchChange([], removedTargetIds, doc.key, doc);
  8666. }
  8667. else if ('documentRemove' in change) {
  8668. assertPresent(change.documentRemove);
  8669. const docRemove = change.documentRemove;
  8670. assertPresent(docRemove.document);
  8671. const key = fromName(serializer, docRemove.document);
  8672. const removedTargetIds = docRemove.removedTargetIds || [];
  8673. watchChange = new DocumentWatchChange([], removedTargetIds, key, null);
  8674. }
  8675. else if ('filter' in change) {
  8676. // TODO(dimond): implement existence filter parsing with strategy.
  8677. assertPresent(change.filter);
  8678. const filter = change.filter;
  8679. assertPresent(filter.targetId);
  8680. const count = filter.count || 0;
  8681. const existenceFilter = new ExistenceFilter(count);
  8682. const targetId = filter.targetId;
  8683. watchChange = new ExistenceFilterChange(targetId, existenceFilter);
  8684. }
  8685. else {
  8686. return fail();
  8687. }
  8688. return watchChange;
  8689. }
  8690. function fromWatchTargetChangeState(state) {
  8691. if (state === 'NO_CHANGE') {
  8692. return 0 /* WatchTargetChangeState.NoChange */;
  8693. }
  8694. else if (state === 'ADD') {
  8695. return 1 /* WatchTargetChangeState.Added */;
  8696. }
  8697. else if (state === 'REMOVE') {
  8698. return 2 /* WatchTargetChangeState.Removed */;
  8699. }
  8700. else if (state === 'CURRENT') {
  8701. return 3 /* WatchTargetChangeState.Current */;
  8702. }
  8703. else if (state === 'RESET') {
  8704. return 4 /* WatchTargetChangeState.Reset */;
  8705. }
  8706. else {
  8707. return fail();
  8708. }
  8709. }
  8710. function versionFromListenResponse(change) {
  8711. // We have only reached a consistent snapshot for the entire stream if there
  8712. // is a read_time set and it applies to all targets (i.e. the list of
  8713. // targets is empty). The backend is guaranteed to send such responses.
  8714. if (!('targetChange' in change)) {
  8715. return SnapshotVersion.min();
  8716. }
  8717. const targetChange = change.targetChange;
  8718. if (targetChange.targetIds && targetChange.targetIds.length) {
  8719. return SnapshotVersion.min();
  8720. }
  8721. if (!targetChange.readTime) {
  8722. return SnapshotVersion.min();
  8723. }
  8724. return fromVersion(targetChange.readTime);
  8725. }
  8726. function toMutation(serializer, mutation) {
  8727. let result;
  8728. if (mutation instanceof SetMutation) {
  8729. result = {
  8730. update: toMutationDocument(serializer, mutation.key, mutation.value)
  8731. };
  8732. }
  8733. else if (mutation instanceof DeleteMutation) {
  8734. result = { delete: toName(serializer, mutation.key) };
  8735. }
  8736. else if (mutation instanceof PatchMutation) {
  8737. result = {
  8738. update: toMutationDocument(serializer, mutation.key, mutation.data),
  8739. updateMask: toDocumentMask(mutation.fieldMask)
  8740. };
  8741. }
  8742. else if (mutation instanceof VerifyMutation) {
  8743. result = {
  8744. verify: toName(serializer, mutation.key)
  8745. };
  8746. }
  8747. else {
  8748. return fail();
  8749. }
  8750. if (mutation.fieldTransforms.length > 0) {
  8751. result.updateTransforms = mutation.fieldTransforms.map(transform => toFieldTransform(serializer, transform));
  8752. }
  8753. if (!mutation.precondition.isNone) {
  8754. result.currentDocument = toPrecondition(serializer, mutation.precondition);
  8755. }
  8756. return result;
  8757. }
  8758. function fromMutation(serializer, proto) {
  8759. const precondition = proto.currentDocument
  8760. ? fromPrecondition(proto.currentDocument)
  8761. : Precondition.none();
  8762. const fieldTransforms = proto.updateTransforms
  8763. ? proto.updateTransforms.map(transform => fromFieldTransform(serializer, transform))
  8764. : [];
  8765. if (proto.update) {
  8766. assertPresent(proto.update.name);
  8767. const key = fromName(serializer, proto.update.name);
  8768. const value = new ObjectValue({
  8769. mapValue: { fields: proto.update.fields }
  8770. });
  8771. if (proto.updateMask) {
  8772. const fieldMask = fromDocumentMask(proto.updateMask);
  8773. return new PatchMutation(key, value, fieldMask, precondition, fieldTransforms);
  8774. }
  8775. else {
  8776. return new SetMutation(key, value, precondition, fieldTransforms);
  8777. }
  8778. }
  8779. else if (proto.delete) {
  8780. const key = fromName(serializer, proto.delete);
  8781. return new DeleteMutation(key, precondition);
  8782. }
  8783. else if (proto.verify) {
  8784. const key = fromName(serializer, proto.verify);
  8785. return new VerifyMutation(key, precondition);
  8786. }
  8787. else {
  8788. return fail();
  8789. }
  8790. }
  8791. function toPrecondition(serializer, precondition) {
  8792. if (precondition.updateTime !== undefined) {
  8793. return {
  8794. updateTime: toVersion(serializer, precondition.updateTime)
  8795. };
  8796. }
  8797. else if (precondition.exists !== undefined) {
  8798. return { exists: precondition.exists };
  8799. }
  8800. else {
  8801. return fail();
  8802. }
  8803. }
  8804. function fromPrecondition(precondition) {
  8805. if (precondition.updateTime !== undefined) {
  8806. return Precondition.updateTime(fromVersion(precondition.updateTime));
  8807. }
  8808. else if (precondition.exists !== undefined) {
  8809. return Precondition.exists(precondition.exists);
  8810. }
  8811. else {
  8812. return Precondition.none();
  8813. }
  8814. }
  8815. function fromWriteResult(proto, commitTime) {
  8816. // NOTE: Deletes don't have an updateTime.
  8817. let version = proto.updateTime
  8818. ? fromVersion(proto.updateTime)
  8819. : fromVersion(commitTime);
  8820. if (version.isEqual(SnapshotVersion.min())) {
  8821. // The Firestore Emulator currently returns an update time of 0 for
  8822. // deletes of non-existing documents (rather than null). This breaks the
  8823. // test "get deleted doc while offline with source=cache" as NoDocuments
  8824. // with version 0 are filtered by IndexedDb's RemoteDocumentCache.
  8825. // TODO(#2149): Remove this when Emulator is fixed
  8826. version = fromVersion(commitTime);
  8827. }
  8828. return new MutationResult(version, proto.transformResults || []);
  8829. }
  8830. function fromWriteResults(protos, commitTime) {
  8831. if (protos && protos.length > 0) {
  8832. hardAssert(commitTime !== undefined);
  8833. return protos.map(proto => fromWriteResult(proto, commitTime));
  8834. }
  8835. else {
  8836. return [];
  8837. }
  8838. }
  8839. function toFieldTransform(serializer, fieldTransform) {
  8840. const transform = fieldTransform.transform;
  8841. if (transform instanceof ServerTimestampTransform) {
  8842. return {
  8843. fieldPath: fieldTransform.field.canonicalString(),
  8844. setToServerValue: 'REQUEST_TIME'
  8845. };
  8846. }
  8847. else if (transform instanceof ArrayUnionTransformOperation) {
  8848. return {
  8849. fieldPath: fieldTransform.field.canonicalString(),
  8850. appendMissingElements: {
  8851. values: transform.elements
  8852. }
  8853. };
  8854. }
  8855. else if (transform instanceof ArrayRemoveTransformOperation) {
  8856. return {
  8857. fieldPath: fieldTransform.field.canonicalString(),
  8858. removeAllFromArray: {
  8859. values: transform.elements
  8860. }
  8861. };
  8862. }
  8863. else if (transform instanceof NumericIncrementTransformOperation) {
  8864. return {
  8865. fieldPath: fieldTransform.field.canonicalString(),
  8866. increment: transform.operand
  8867. };
  8868. }
  8869. else {
  8870. throw fail();
  8871. }
  8872. }
  8873. function fromFieldTransform(serializer, proto) {
  8874. let transform = null;
  8875. if ('setToServerValue' in proto) {
  8876. hardAssert(proto.setToServerValue === 'REQUEST_TIME');
  8877. transform = new ServerTimestampTransform();
  8878. }
  8879. else if ('appendMissingElements' in proto) {
  8880. const values = proto.appendMissingElements.values || [];
  8881. transform = new ArrayUnionTransformOperation(values);
  8882. }
  8883. else if ('removeAllFromArray' in proto) {
  8884. const values = proto.removeAllFromArray.values || [];
  8885. transform = new ArrayRemoveTransformOperation(values);
  8886. }
  8887. else if ('increment' in proto) {
  8888. transform = new NumericIncrementTransformOperation(serializer, proto.increment);
  8889. }
  8890. else {
  8891. fail();
  8892. }
  8893. const fieldPath = FieldPath$1.fromServerFormat(proto.fieldPath);
  8894. return new FieldTransform(fieldPath, transform);
  8895. }
  8896. function toDocumentsTarget(serializer, target) {
  8897. return { documents: [toQueryPath(serializer, target.path)] };
  8898. }
  8899. function fromDocumentsTarget(documentsTarget) {
  8900. const count = documentsTarget.documents.length;
  8901. hardAssert(count === 1);
  8902. const name = documentsTarget.documents[0];
  8903. return queryToTarget(newQueryForPath(fromQueryPath(name)));
  8904. }
  8905. function toQueryTarget(serializer, target) {
  8906. // Dissect the path into parent, collectionId, and optional key filter.
  8907. const result = { structuredQuery: {} };
  8908. const path = target.path;
  8909. if (target.collectionGroup !== null) {
  8910. result.parent = toQueryPath(serializer, path);
  8911. result.structuredQuery.from = [
  8912. {
  8913. collectionId: target.collectionGroup,
  8914. allDescendants: true
  8915. }
  8916. ];
  8917. }
  8918. else {
  8919. result.parent = toQueryPath(serializer, path.popLast());
  8920. result.structuredQuery.from = [{ collectionId: path.lastSegment() }];
  8921. }
  8922. const where = toFilters(target.filters);
  8923. if (where) {
  8924. result.structuredQuery.where = where;
  8925. }
  8926. const orderBy = toOrder(target.orderBy);
  8927. if (orderBy) {
  8928. result.structuredQuery.orderBy = orderBy;
  8929. }
  8930. const limit = toInt32Proto(serializer, target.limit);
  8931. if (limit !== null) {
  8932. result.structuredQuery.limit = limit;
  8933. }
  8934. if (target.startAt) {
  8935. result.structuredQuery.startAt = toStartAtCursor(target.startAt);
  8936. }
  8937. if (target.endAt) {
  8938. result.structuredQuery.endAt = toEndAtCursor(target.endAt);
  8939. }
  8940. return result;
  8941. }
  8942. function toRunAggregationQueryRequest(serializer, target) {
  8943. const queryTarget = toQueryTarget(serializer, target);
  8944. return {
  8945. structuredAggregationQuery: {
  8946. aggregations: [
  8947. {
  8948. count: {},
  8949. alias: 'count_alias'
  8950. }
  8951. ],
  8952. structuredQuery: queryTarget.structuredQuery
  8953. },
  8954. parent: queryTarget.parent
  8955. };
  8956. }
  8957. function convertQueryTargetToQuery(target) {
  8958. let path = fromQueryPath(target.parent);
  8959. const query = target.structuredQuery;
  8960. const fromCount = query.from ? query.from.length : 0;
  8961. let collectionGroup = null;
  8962. if (fromCount > 0) {
  8963. hardAssert(fromCount === 1);
  8964. const from = query.from[0];
  8965. if (from.allDescendants) {
  8966. collectionGroup = from.collectionId;
  8967. }
  8968. else {
  8969. path = path.child(from.collectionId);
  8970. }
  8971. }
  8972. let filterBy = [];
  8973. if (query.where) {
  8974. filterBy = fromFilters(query.where);
  8975. }
  8976. let orderBy = [];
  8977. if (query.orderBy) {
  8978. orderBy = fromOrder(query.orderBy);
  8979. }
  8980. let limit = null;
  8981. if (query.limit) {
  8982. limit = fromInt32Proto(query.limit);
  8983. }
  8984. let startAt = null;
  8985. if (query.startAt) {
  8986. startAt = fromStartAtCursor(query.startAt);
  8987. }
  8988. let endAt = null;
  8989. if (query.endAt) {
  8990. endAt = fromEndAtCursor(query.endAt);
  8991. }
  8992. return newQuery(path, collectionGroup, orderBy, filterBy, limit, "F" /* LimitType.First */, startAt, endAt);
  8993. }
  8994. function fromQueryTarget(target) {
  8995. return queryToTarget(convertQueryTargetToQuery(target));
  8996. }
  8997. function toListenRequestLabels(serializer, targetData) {
  8998. const value = toLabel(serializer, targetData.purpose);
  8999. if (value == null) {
  9000. return null;
  9001. }
  9002. else {
  9003. return {
  9004. 'goog-listen-tags': value
  9005. };
  9006. }
  9007. }
  9008. function toLabel(serializer, purpose) {
  9009. switch (purpose) {
  9010. case 0 /* TargetPurpose.Listen */:
  9011. return null;
  9012. case 1 /* TargetPurpose.ExistenceFilterMismatch */:
  9013. return 'existence-filter-mismatch';
  9014. case 2 /* TargetPurpose.LimboResolution */:
  9015. return 'limbo-document';
  9016. default:
  9017. return fail();
  9018. }
  9019. }
  9020. function toTarget(serializer, targetData) {
  9021. let result;
  9022. const target = targetData.target;
  9023. if (targetIsDocumentTarget(target)) {
  9024. result = { documents: toDocumentsTarget(serializer, target) };
  9025. }
  9026. else {
  9027. result = { query: toQueryTarget(serializer, target) };
  9028. }
  9029. result.targetId = targetData.targetId;
  9030. if (targetData.resumeToken.approximateByteSize() > 0) {
  9031. result.resumeToken = toBytes(serializer, targetData.resumeToken);
  9032. }
  9033. else if (targetData.snapshotVersion.compareTo(SnapshotVersion.min()) > 0) {
  9034. // TODO(wuandy): Consider removing above check because it is most likely true.
  9035. // Right now, many tests depend on this behaviour though (leaving min() out
  9036. // of serialization).
  9037. result.readTime = toTimestamp(serializer, targetData.snapshotVersion.toTimestamp());
  9038. }
  9039. return result;
  9040. }
  9041. function toFilters(filters) {
  9042. if (filters.length === 0) {
  9043. return;
  9044. }
  9045. return toFilter(CompositeFilter.create(filters, "and" /* CompositeOperator.AND */));
  9046. }
  9047. function fromFilters(filter) {
  9048. const result = fromFilter(filter);
  9049. if (result instanceof CompositeFilter &&
  9050. compositeFilterIsFlatConjunction(result)) {
  9051. return result.getFilters();
  9052. }
  9053. return [result];
  9054. }
  9055. function fromFilter(filter) {
  9056. if (filter.unaryFilter !== undefined) {
  9057. return fromUnaryFilter(filter);
  9058. }
  9059. else if (filter.fieldFilter !== undefined) {
  9060. return fromFieldFilter(filter);
  9061. }
  9062. else if (filter.compositeFilter !== undefined) {
  9063. return fromCompositeFilter(filter);
  9064. }
  9065. else {
  9066. return fail();
  9067. }
  9068. }
  9069. function toOrder(orderBys) {
  9070. if (orderBys.length === 0) {
  9071. return;
  9072. }
  9073. return orderBys.map(order => toPropertyOrder(order));
  9074. }
  9075. function fromOrder(orderBys) {
  9076. return orderBys.map(order => fromPropertyOrder(order));
  9077. }
  9078. function toStartAtCursor(cursor) {
  9079. return {
  9080. before: cursor.inclusive,
  9081. values: cursor.position
  9082. };
  9083. }
  9084. function toEndAtCursor(cursor) {
  9085. return {
  9086. before: !cursor.inclusive,
  9087. values: cursor.position
  9088. };
  9089. }
  9090. function fromStartAtCursor(cursor) {
  9091. const inclusive = !!cursor.before;
  9092. const position = cursor.values || [];
  9093. return new Bound(position, inclusive);
  9094. }
  9095. function fromEndAtCursor(cursor) {
  9096. const inclusive = !cursor.before;
  9097. const position = cursor.values || [];
  9098. return new Bound(position, inclusive);
  9099. }
  9100. // visible for testing
  9101. function toDirection(dir) {
  9102. return DIRECTIONS[dir];
  9103. }
  9104. // visible for testing
  9105. function fromDirection(dir) {
  9106. switch (dir) {
  9107. case 'ASCENDING':
  9108. return "asc" /* Direction.ASCENDING */;
  9109. case 'DESCENDING':
  9110. return "desc" /* Direction.DESCENDING */;
  9111. default:
  9112. return undefined;
  9113. }
  9114. }
  9115. // visible for testing
  9116. function toOperatorName(op) {
  9117. return OPERATORS[op];
  9118. }
  9119. function toCompositeOperatorName(op) {
  9120. return COMPOSITE_OPERATORS[op];
  9121. }
  9122. function fromOperatorName(op) {
  9123. switch (op) {
  9124. case 'EQUAL':
  9125. return "==" /* Operator.EQUAL */;
  9126. case 'NOT_EQUAL':
  9127. return "!=" /* Operator.NOT_EQUAL */;
  9128. case 'GREATER_THAN':
  9129. return ">" /* Operator.GREATER_THAN */;
  9130. case 'GREATER_THAN_OR_EQUAL':
  9131. return ">=" /* Operator.GREATER_THAN_OR_EQUAL */;
  9132. case 'LESS_THAN':
  9133. return "<" /* Operator.LESS_THAN */;
  9134. case 'LESS_THAN_OR_EQUAL':
  9135. return "<=" /* Operator.LESS_THAN_OR_EQUAL */;
  9136. case 'ARRAY_CONTAINS':
  9137. return "array-contains" /* Operator.ARRAY_CONTAINS */;
  9138. case 'IN':
  9139. return "in" /* Operator.IN */;
  9140. case 'NOT_IN':
  9141. return "not-in" /* Operator.NOT_IN */;
  9142. case 'ARRAY_CONTAINS_ANY':
  9143. return "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */;
  9144. case 'OPERATOR_UNSPECIFIED':
  9145. return fail();
  9146. default:
  9147. return fail();
  9148. }
  9149. }
  9150. function fromCompositeOperatorName(op) {
  9151. switch (op) {
  9152. case 'AND':
  9153. return "and" /* CompositeOperator.AND */;
  9154. case 'OR':
  9155. return "or" /* CompositeOperator.OR */;
  9156. default:
  9157. return fail();
  9158. }
  9159. }
  9160. function toFieldPathReference(path) {
  9161. return { fieldPath: path.canonicalString() };
  9162. }
  9163. function fromFieldPathReference(fieldReference) {
  9164. return FieldPath$1.fromServerFormat(fieldReference.fieldPath);
  9165. }
  9166. // visible for testing
  9167. function toPropertyOrder(orderBy) {
  9168. return {
  9169. field: toFieldPathReference(orderBy.field),
  9170. direction: toDirection(orderBy.dir)
  9171. };
  9172. }
  9173. function fromPropertyOrder(orderBy) {
  9174. return new OrderBy(fromFieldPathReference(orderBy.field), fromDirection(orderBy.direction));
  9175. }
  9176. // visible for testing
  9177. function toFilter(filter) {
  9178. if (filter instanceof FieldFilter) {
  9179. return toUnaryOrFieldFilter(filter);
  9180. }
  9181. else if (filter instanceof CompositeFilter) {
  9182. return toCompositeFilter(filter);
  9183. }
  9184. else {
  9185. return fail();
  9186. }
  9187. }
  9188. function toCompositeFilter(filter) {
  9189. const protos = filter.getFilters().map(filter => toFilter(filter));
  9190. if (protos.length === 1) {
  9191. return protos[0];
  9192. }
  9193. return {
  9194. compositeFilter: {
  9195. op: toCompositeOperatorName(filter.op),
  9196. filters: protos
  9197. }
  9198. };
  9199. }
  9200. function toUnaryOrFieldFilter(filter) {
  9201. if (filter.op === "==" /* Operator.EQUAL */) {
  9202. if (isNanValue(filter.value)) {
  9203. return {
  9204. unaryFilter: {
  9205. field: toFieldPathReference(filter.field),
  9206. op: 'IS_NAN'
  9207. }
  9208. };
  9209. }
  9210. else if (isNullValue(filter.value)) {
  9211. return {
  9212. unaryFilter: {
  9213. field: toFieldPathReference(filter.field),
  9214. op: 'IS_NULL'
  9215. }
  9216. };
  9217. }
  9218. }
  9219. else if (filter.op === "!=" /* Operator.NOT_EQUAL */) {
  9220. if (isNanValue(filter.value)) {
  9221. return {
  9222. unaryFilter: {
  9223. field: toFieldPathReference(filter.field),
  9224. op: 'IS_NOT_NAN'
  9225. }
  9226. };
  9227. }
  9228. else if (isNullValue(filter.value)) {
  9229. return {
  9230. unaryFilter: {
  9231. field: toFieldPathReference(filter.field),
  9232. op: 'IS_NOT_NULL'
  9233. }
  9234. };
  9235. }
  9236. }
  9237. return {
  9238. fieldFilter: {
  9239. field: toFieldPathReference(filter.field),
  9240. op: toOperatorName(filter.op),
  9241. value: filter.value
  9242. }
  9243. };
  9244. }
  9245. function fromUnaryFilter(filter) {
  9246. switch (filter.unaryFilter.op) {
  9247. case 'IS_NAN':
  9248. const nanField = fromFieldPathReference(filter.unaryFilter.field);
  9249. return FieldFilter.create(nanField, "==" /* Operator.EQUAL */, {
  9250. doubleValue: NaN
  9251. });
  9252. case 'IS_NULL':
  9253. const nullField = fromFieldPathReference(filter.unaryFilter.field);
  9254. return FieldFilter.create(nullField, "==" /* Operator.EQUAL */, {
  9255. nullValue: 'NULL_VALUE'
  9256. });
  9257. case 'IS_NOT_NAN':
  9258. const notNanField = fromFieldPathReference(filter.unaryFilter.field);
  9259. return FieldFilter.create(notNanField, "!=" /* Operator.NOT_EQUAL */, {
  9260. doubleValue: NaN
  9261. });
  9262. case 'IS_NOT_NULL':
  9263. const notNullField = fromFieldPathReference(filter.unaryFilter.field);
  9264. return FieldFilter.create(notNullField, "!=" /* Operator.NOT_EQUAL */, {
  9265. nullValue: 'NULL_VALUE'
  9266. });
  9267. case 'OPERATOR_UNSPECIFIED':
  9268. return fail();
  9269. default:
  9270. return fail();
  9271. }
  9272. }
  9273. function fromFieldFilter(filter) {
  9274. return FieldFilter.create(fromFieldPathReference(filter.fieldFilter.field), fromOperatorName(filter.fieldFilter.op), filter.fieldFilter.value);
  9275. }
  9276. function fromCompositeFilter(filter) {
  9277. return CompositeFilter.create(filter.compositeFilter.filters.map(filter => fromFilter(filter)), fromCompositeOperatorName(filter.compositeFilter.op));
  9278. }
  9279. function toDocumentMask(fieldMask) {
  9280. const canonicalFields = [];
  9281. fieldMask.fields.forEach(field => canonicalFields.push(field.canonicalString()));
  9282. return {
  9283. fieldPaths: canonicalFields
  9284. };
  9285. }
  9286. function fromDocumentMask(proto) {
  9287. const paths = proto.fieldPaths || [];
  9288. return new FieldMask(paths.map(path => FieldPath$1.fromServerFormat(path)));
  9289. }
  9290. function isValidResourceName(path) {
  9291. // Resource names have at least 4 components (project ID, database ID)
  9292. return (path.length >= 4 &&
  9293. path.get(0) === 'projects' &&
  9294. path.get(2) === 'databases');
  9295. }
  9296. /**
  9297. * @license
  9298. * Copyright 2017 Google LLC
  9299. *
  9300. * Licensed under the Apache License, Version 2.0 (the "License");
  9301. * you may not use this file except in compliance with the License.
  9302. * You may obtain a copy of the License at
  9303. *
  9304. * http://www.apache.org/licenses/LICENSE-2.0
  9305. *
  9306. * Unless required by applicable law or agreed to in writing, software
  9307. * distributed under the License is distributed on an "AS IS" BASIS,
  9308. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9309. * See the License for the specific language governing permissions and
  9310. * limitations under the License.
  9311. */
  9312. /**
  9313. * An immutable set of metadata that the local store tracks for each target.
  9314. */
  9315. class TargetData {
  9316. constructor(
  9317. /** The target being listened to. */
  9318. target,
  9319. /**
  9320. * The target ID to which the target corresponds; Assigned by the
  9321. * LocalStore for user listens and by the SyncEngine for limbo watches.
  9322. */
  9323. targetId,
  9324. /** The purpose of the target. */
  9325. purpose,
  9326. /**
  9327. * The sequence number of the last transaction during which this target data
  9328. * was modified.
  9329. */
  9330. sequenceNumber,
  9331. /** The latest snapshot version seen for this target. */
  9332. snapshotVersion = SnapshotVersion.min(),
  9333. /**
  9334. * The maximum snapshot version at which the associated view
  9335. * contained no limbo documents.
  9336. */
  9337. lastLimboFreeSnapshotVersion = SnapshotVersion.min(),
  9338. /**
  9339. * An opaque, server-assigned token that allows watching a target to be
  9340. * resumed after disconnecting without retransmitting all the data that
  9341. * matches the target. The resume token essentially identifies a point in
  9342. * time from which the server should resume sending results.
  9343. */
  9344. resumeToken = ByteString.EMPTY_BYTE_STRING) {
  9345. this.target = target;
  9346. this.targetId = targetId;
  9347. this.purpose = purpose;
  9348. this.sequenceNumber = sequenceNumber;
  9349. this.snapshotVersion = snapshotVersion;
  9350. this.lastLimboFreeSnapshotVersion = lastLimboFreeSnapshotVersion;
  9351. this.resumeToken = resumeToken;
  9352. }
  9353. /** Creates a new target data instance with an updated sequence number. */
  9354. withSequenceNumber(sequenceNumber) {
  9355. return new TargetData(this.target, this.targetId, this.purpose, sequenceNumber, this.snapshotVersion, this.lastLimboFreeSnapshotVersion, this.resumeToken);
  9356. }
  9357. /**
  9358. * Creates a new target data instance with an updated resume token and
  9359. * snapshot version.
  9360. */
  9361. withResumeToken(resumeToken, snapshotVersion) {
  9362. return new TargetData(this.target, this.targetId, this.purpose, this.sequenceNumber, snapshotVersion, this.lastLimboFreeSnapshotVersion, resumeToken);
  9363. }
  9364. /**
  9365. * Creates a new target data instance with an updated last limbo free
  9366. * snapshot version number.
  9367. */
  9368. withLastLimboFreeSnapshotVersion(lastLimboFreeSnapshotVersion) {
  9369. return new TargetData(this.target, this.targetId, this.purpose, this.sequenceNumber, this.snapshotVersion, lastLimboFreeSnapshotVersion, this.resumeToken);
  9370. }
  9371. }
  9372. /**
  9373. * @license
  9374. * Copyright 2017 Google LLC
  9375. *
  9376. * Licensed under the Apache License, Version 2.0 (the "License");
  9377. * you may not use this file except in compliance with the License.
  9378. * You may obtain a copy of the License at
  9379. *
  9380. * http://www.apache.org/licenses/LICENSE-2.0
  9381. *
  9382. * Unless required by applicable law or agreed to in writing, software
  9383. * distributed under the License is distributed on an "AS IS" BASIS,
  9384. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9385. * See the License for the specific language governing permissions and
  9386. * limitations under the License.
  9387. */
  9388. /** Serializer for values stored in the LocalStore. */
  9389. class LocalSerializer {
  9390. constructor(remoteSerializer) {
  9391. this.remoteSerializer = remoteSerializer;
  9392. }
  9393. }
  9394. /** Decodes a remote document from storage locally to a Document. */
  9395. function fromDbRemoteDocument(localSerializer, remoteDoc) {
  9396. let doc;
  9397. if (remoteDoc.document) {
  9398. doc = fromDocument(localSerializer.remoteSerializer, remoteDoc.document, !!remoteDoc.hasCommittedMutations);
  9399. }
  9400. else if (remoteDoc.noDocument) {
  9401. const key = DocumentKey.fromSegments(remoteDoc.noDocument.path);
  9402. const version = fromDbTimestamp(remoteDoc.noDocument.readTime);
  9403. doc = MutableDocument.newNoDocument(key, version);
  9404. if (remoteDoc.hasCommittedMutations) {
  9405. doc.setHasCommittedMutations();
  9406. }
  9407. }
  9408. else if (remoteDoc.unknownDocument) {
  9409. const key = DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
  9410. const version = fromDbTimestamp(remoteDoc.unknownDocument.version);
  9411. doc = MutableDocument.newUnknownDocument(key, version);
  9412. }
  9413. else {
  9414. return fail();
  9415. }
  9416. if (remoteDoc.readTime) {
  9417. doc.setReadTime(fromDbTimestampKey(remoteDoc.readTime));
  9418. }
  9419. return doc;
  9420. }
  9421. /** Encodes a document for storage locally. */
  9422. function toDbRemoteDocument(localSerializer, document) {
  9423. const key = document.key;
  9424. const remoteDoc = {
  9425. prefixPath: key.getCollectionPath().popLast().toArray(),
  9426. collectionGroup: key.collectionGroup,
  9427. documentId: key.path.lastSegment(),
  9428. readTime: toDbTimestampKey(document.readTime),
  9429. hasCommittedMutations: document.hasCommittedMutations
  9430. };
  9431. if (document.isFoundDocument()) {
  9432. remoteDoc.document = toDocument(localSerializer.remoteSerializer, document);
  9433. }
  9434. else if (document.isNoDocument()) {
  9435. remoteDoc.noDocument = {
  9436. path: key.path.toArray(),
  9437. readTime: toDbTimestamp(document.version)
  9438. };
  9439. }
  9440. else if (document.isUnknownDocument()) {
  9441. remoteDoc.unknownDocument = {
  9442. path: key.path.toArray(),
  9443. version: toDbTimestamp(document.version)
  9444. };
  9445. }
  9446. else {
  9447. return fail();
  9448. }
  9449. return remoteDoc;
  9450. }
  9451. function toDbTimestampKey(snapshotVersion) {
  9452. const timestamp = snapshotVersion.toTimestamp();
  9453. return [timestamp.seconds, timestamp.nanoseconds];
  9454. }
  9455. function fromDbTimestampKey(dbTimestampKey) {
  9456. const timestamp = new Timestamp(dbTimestampKey[0], dbTimestampKey[1]);
  9457. return SnapshotVersion.fromTimestamp(timestamp);
  9458. }
  9459. function toDbTimestamp(snapshotVersion) {
  9460. const timestamp = snapshotVersion.toTimestamp();
  9461. return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
  9462. }
  9463. function fromDbTimestamp(dbTimestamp) {
  9464. const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
  9465. return SnapshotVersion.fromTimestamp(timestamp);
  9466. }
  9467. /** Encodes a batch of mutations into a DbMutationBatch for local storage. */
  9468. function toDbMutationBatch(localSerializer, userId, batch) {
  9469. const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
  9470. const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
  9471. return {
  9472. userId,
  9473. batchId: batch.batchId,
  9474. localWriteTimeMs: batch.localWriteTime.toMillis(),
  9475. baseMutations: serializedBaseMutations,
  9476. mutations: serializedMutations
  9477. };
  9478. }
  9479. /** Decodes a DbMutationBatch into a MutationBatch */
  9480. function fromDbMutationBatch(localSerializer, dbBatch) {
  9481. const baseMutations = (dbBatch.baseMutations || []).map(m => fromMutation(localSerializer.remoteSerializer, m));
  9482. // Squash old transform mutations into existing patch or set mutations.
  9483. // The replacement of representing `transforms` with `update_transforms`
  9484. // on the SDK means that old `transform` mutations stored in IndexedDB need
  9485. // to be updated to `update_transforms`.
  9486. // TODO(b/174608374): Remove this code once we perform a schema migration.
  9487. for (let i = 0; i < dbBatch.mutations.length - 1; ++i) {
  9488. const currentMutation = dbBatch.mutations[i];
  9489. const hasTransform = i + 1 < dbBatch.mutations.length &&
  9490. dbBatch.mutations[i + 1].transform !== undefined;
  9491. if (hasTransform) {
  9492. const transformMutation = dbBatch.mutations[i + 1];
  9493. currentMutation.updateTransforms =
  9494. transformMutation.transform.fieldTransforms;
  9495. dbBatch.mutations.splice(i + 1, 1);
  9496. ++i;
  9497. }
  9498. }
  9499. const mutations = dbBatch.mutations.map(m => fromMutation(localSerializer.remoteSerializer, m));
  9500. const timestamp = Timestamp.fromMillis(dbBatch.localWriteTimeMs);
  9501. return new MutationBatch(dbBatch.batchId, timestamp, baseMutations, mutations);
  9502. }
  9503. /** Decodes a DbTarget into TargetData */
  9504. function fromDbTarget(dbTarget) {
  9505. const version = fromDbTimestamp(dbTarget.readTime);
  9506. const lastLimboFreeSnapshotVersion = dbTarget.lastLimboFreeSnapshotVersion !== undefined
  9507. ? fromDbTimestamp(dbTarget.lastLimboFreeSnapshotVersion)
  9508. : SnapshotVersion.min();
  9509. let target;
  9510. if (isDocumentQuery(dbTarget.query)) {
  9511. target = fromDocumentsTarget(dbTarget.query);
  9512. }
  9513. else {
  9514. target = fromQueryTarget(dbTarget.query);
  9515. }
  9516. return new TargetData(target, dbTarget.targetId, 0 /* TargetPurpose.Listen */, dbTarget.lastListenSequenceNumber, version, lastLimboFreeSnapshotVersion, ByteString.fromBase64String(dbTarget.resumeToken));
  9517. }
  9518. /** Encodes TargetData into a DbTarget for storage locally. */
  9519. function toDbTarget(localSerializer, targetData) {
  9520. const dbTimestamp = toDbTimestamp(targetData.snapshotVersion);
  9521. const dbLastLimboFreeTimestamp = toDbTimestamp(targetData.lastLimboFreeSnapshotVersion);
  9522. let queryProto;
  9523. if (targetIsDocumentTarget(targetData.target)) {
  9524. queryProto = toDocumentsTarget(localSerializer.remoteSerializer, targetData.target);
  9525. }
  9526. else {
  9527. queryProto = toQueryTarget(localSerializer.remoteSerializer, targetData.target);
  9528. }
  9529. // We can't store the resumeToken as a ByteString in IndexedDb, so we
  9530. // convert it to a base64 string for storage.
  9531. const resumeToken = targetData.resumeToken.toBase64();
  9532. // lastListenSequenceNumber is always 0 until we do real GC.
  9533. return {
  9534. targetId: targetData.targetId,
  9535. canonicalId: canonifyTarget(targetData.target),
  9536. readTime: dbTimestamp,
  9537. resumeToken,
  9538. lastListenSequenceNumber: targetData.sequenceNumber,
  9539. lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
  9540. query: queryProto
  9541. };
  9542. }
  9543. /**
  9544. * A helper function for figuring out what kind of query has been stored.
  9545. */
  9546. function isDocumentQuery(dbQuery) {
  9547. return dbQuery.documents !== undefined;
  9548. }
  9549. /** Encodes a DbBundle to a BundleMetadata object. */
  9550. function fromDbBundle(dbBundle) {
  9551. return {
  9552. id: dbBundle.bundleId,
  9553. createTime: fromDbTimestamp(dbBundle.createTime),
  9554. version: dbBundle.version
  9555. };
  9556. }
  9557. /** Encodes a BundleMetadata to a DbBundle. */
  9558. function toDbBundle(metadata) {
  9559. return {
  9560. bundleId: metadata.id,
  9561. createTime: toDbTimestamp(fromVersion(metadata.createTime)),
  9562. version: metadata.version
  9563. };
  9564. }
  9565. /** Encodes a DbNamedQuery to a NamedQuery. */
  9566. function fromDbNamedQuery(dbNamedQuery) {
  9567. return {
  9568. name: dbNamedQuery.name,
  9569. query: fromBundledQuery(dbNamedQuery.bundledQuery),
  9570. readTime: fromDbTimestamp(dbNamedQuery.readTime)
  9571. };
  9572. }
  9573. /** Encodes a NamedQuery from a bundle proto to a DbNamedQuery. */
  9574. function toDbNamedQuery(query) {
  9575. return {
  9576. name: query.name,
  9577. readTime: toDbTimestamp(fromVersion(query.readTime)),
  9578. bundledQuery: query.bundledQuery
  9579. };
  9580. }
  9581. /**
  9582. * Encodes a `BundledQuery` from bundle proto to a Query object.
  9583. *
  9584. * This reconstructs the original query used to build the bundle being loaded,
  9585. * including features exists only in SDKs (for example: limit-to-last).
  9586. */
  9587. function fromBundledQuery(bundledQuery) {
  9588. const query = convertQueryTargetToQuery({
  9589. parent: bundledQuery.parent,
  9590. structuredQuery: bundledQuery.structuredQuery
  9591. });
  9592. if (bundledQuery.limitType === 'LAST') {
  9593. return queryWithLimit(query, query.limit, "L" /* LimitType.Last */);
  9594. }
  9595. return query;
  9596. }
  9597. /** Encodes a NamedQuery proto object to a NamedQuery model object. */
  9598. function fromProtoNamedQuery(namedQuery) {
  9599. return {
  9600. name: namedQuery.name,
  9601. query: fromBundledQuery(namedQuery.bundledQuery),
  9602. readTime: fromVersion(namedQuery.readTime)
  9603. };
  9604. }
  9605. /** Decodes a BundleMetadata proto into a BundleMetadata object. */
  9606. function fromBundleMetadata(metadata) {
  9607. return {
  9608. id: metadata.id,
  9609. version: metadata.version,
  9610. createTime: fromVersion(metadata.createTime)
  9611. };
  9612. }
  9613. /** Encodes a DbDocumentOverlay object to an Overlay model object. */
  9614. function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
  9615. return new Overlay(dbDocumentOverlay.largestBatchId, fromMutation(localSerializer.remoteSerializer, dbDocumentOverlay.overlayMutation));
  9616. }
  9617. /** Decodes an Overlay model object into a DbDocumentOverlay object. */
  9618. function toDbDocumentOverlay(localSerializer, userId, overlay) {
  9619. const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
  9620. return {
  9621. userId,
  9622. collectionPath,
  9623. documentId,
  9624. collectionGroup: overlay.mutation.key.getCollectionGroup(),
  9625. largestBatchId: overlay.largestBatchId,
  9626. overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
  9627. };
  9628. }
  9629. /**
  9630. * Returns the DbDocumentOverlayKey corresponding to the given user and
  9631. * document key.
  9632. */
  9633. function toDbDocumentOverlayKey(userId, docKey) {
  9634. const docId = docKey.path.lastSegment();
  9635. const collectionPath = encodeResourcePath(docKey.path.popLast());
  9636. return [userId, collectionPath, docId];
  9637. }
  9638. function toDbIndexConfiguration(index) {
  9639. return {
  9640. indexId: index.indexId,
  9641. collectionGroup: index.collectionGroup,
  9642. fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
  9643. };
  9644. }
  9645. function fromDbIndexConfiguration(index, state) {
  9646. const decodedState = state
  9647. ? new IndexState(state.sequenceNumber, new IndexOffset(fromDbTimestamp(state.readTime), new DocumentKey(decodeResourcePath(state.documentKey)), state.largestBatchId))
  9648. : IndexState.empty();
  9649. const decodedSegments = index.fields.map(([fieldPath, kind]) => new IndexSegment(FieldPath$1.fromServerFormat(fieldPath), kind));
  9650. return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
  9651. }
  9652. function toDbIndexState(indexId, user, sequenceNumber, offset) {
  9653. return {
  9654. indexId,
  9655. uid: user.uid || '',
  9656. sequenceNumber,
  9657. readTime: toDbTimestamp(offset.readTime),
  9658. documentKey: encodeResourcePath(offset.documentKey.path),
  9659. largestBatchId: offset.largestBatchId
  9660. };
  9661. }
  9662. /**
  9663. * @license
  9664. * Copyright 2020 Google LLC
  9665. *
  9666. * Licensed under the Apache License, Version 2.0 (the "License");
  9667. * you may not use this file except in compliance with the License.
  9668. * You may obtain a copy of the License at
  9669. *
  9670. * http://www.apache.org/licenses/LICENSE-2.0
  9671. *
  9672. * Unless required by applicable law or agreed to in writing, software
  9673. * distributed under the License is distributed on an "AS IS" BASIS,
  9674. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9675. * See the License for the specific language governing permissions and
  9676. * limitations under the License.
  9677. */
  9678. class IndexedDbBundleCache {
  9679. getBundleMetadata(transaction, bundleId) {
  9680. return bundlesStore(transaction)
  9681. .get(bundleId)
  9682. .next(bundle => {
  9683. if (bundle) {
  9684. return fromDbBundle(bundle);
  9685. }
  9686. return undefined;
  9687. });
  9688. }
  9689. saveBundleMetadata(transaction, bundleMetadata) {
  9690. return bundlesStore(transaction).put(toDbBundle(bundleMetadata));
  9691. }
  9692. getNamedQuery(transaction, queryName) {
  9693. return namedQueriesStore(transaction)
  9694. .get(queryName)
  9695. .next(query => {
  9696. if (query) {
  9697. return fromDbNamedQuery(query);
  9698. }
  9699. return undefined;
  9700. });
  9701. }
  9702. saveNamedQuery(transaction, query) {
  9703. return namedQueriesStore(transaction).put(toDbNamedQuery(query));
  9704. }
  9705. }
  9706. /**
  9707. * Helper to get a typed SimpleDbStore for the bundles object store.
  9708. */
  9709. function bundlesStore(txn) {
  9710. return getStore(txn, DbBundleStore);
  9711. }
  9712. /**
  9713. * Helper to get a typed SimpleDbStore for the namedQueries object store.
  9714. */
  9715. function namedQueriesStore(txn) {
  9716. return getStore(txn, DbNamedQueryStore);
  9717. }
  9718. /**
  9719. * @license
  9720. * Copyright 2022 Google LLC
  9721. *
  9722. * Licensed under the Apache License, Version 2.0 (the "License");
  9723. * you may not use this file except in compliance with the License.
  9724. * You may obtain a copy of the License at
  9725. *
  9726. * http://www.apache.org/licenses/LICENSE-2.0
  9727. *
  9728. * Unless required by applicable law or agreed to in writing, software
  9729. * distributed under the License is distributed on an "AS IS" BASIS,
  9730. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9731. * See the License for the specific language governing permissions and
  9732. * limitations under the License.
  9733. */
  9734. /**
  9735. * Implementation of DocumentOverlayCache using IndexedDb.
  9736. */
  9737. class IndexedDbDocumentOverlayCache {
  9738. /**
  9739. * @param serializer - The document serializer.
  9740. * @param userId - The userId for which we are accessing overlays.
  9741. */
  9742. constructor(serializer, userId) {
  9743. this.serializer = serializer;
  9744. this.userId = userId;
  9745. }
  9746. static forUser(serializer, user) {
  9747. const userId = user.uid || '';
  9748. return new IndexedDbDocumentOverlayCache(serializer, userId);
  9749. }
  9750. getOverlay(transaction, key) {
  9751. return documentOverlayStore(transaction)
  9752. .get(toDbDocumentOverlayKey(this.userId, key))
  9753. .next(dbOverlay => {
  9754. if (dbOverlay) {
  9755. return fromDbDocumentOverlay(this.serializer, dbOverlay);
  9756. }
  9757. return null;
  9758. });
  9759. }
  9760. getOverlays(transaction, keys) {
  9761. const result = newOverlayMap();
  9762. return PersistencePromise.forEach(keys, (key) => {
  9763. return this.getOverlay(transaction, key).next(overlay => {
  9764. if (overlay !== null) {
  9765. result.set(key, overlay);
  9766. }
  9767. });
  9768. }).next(() => result);
  9769. }
  9770. saveOverlays(transaction, largestBatchId, overlays) {
  9771. const promises = [];
  9772. overlays.forEach((_, mutation) => {
  9773. const overlay = new Overlay(largestBatchId, mutation);
  9774. promises.push(this.saveOverlay(transaction, overlay));
  9775. });
  9776. return PersistencePromise.waitFor(promises);
  9777. }
  9778. removeOverlaysForBatchId(transaction, documentKeys, batchId) {
  9779. const collectionPaths = new Set();
  9780. // Get the set of unique collection paths.
  9781. documentKeys.forEach(key => collectionPaths.add(encodeResourcePath(key.getCollectionPath())));
  9782. const promises = [];
  9783. collectionPaths.forEach(collectionPath => {
  9784. const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
  9785. /*lowerOpen=*/ false,
  9786. /*upperOpen=*/ true);
  9787. promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
  9788. });
  9789. return PersistencePromise.waitFor(promises);
  9790. }
  9791. getOverlaysForCollection(transaction, collection, sinceBatchId) {
  9792. const result = newOverlayMap();
  9793. const collectionPath = encodeResourcePath(collection);
  9794. // We want batch IDs larger than `sinceBatchId`, and so the lower bound
  9795. // is not inclusive.
  9796. const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
  9797. /*lowerOpen=*/ true);
  9798. return documentOverlayStore(transaction)
  9799. .loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
  9800. .next(dbOverlays => {
  9801. for (const dbOverlay of dbOverlays) {
  9802. const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
  9803. result.set(overlay.getKey(), overlay);
  9804. }
  9805. return result;
  9806. });
  9807. }
  9808. getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
  9809. const result = newOverlayMap();
  9810. let currentBatchId = undefined;
  9811. // We want batch IDs larger than `sinceBatchId`, and so the lower bound
  9812. // is not inclusive.
  9813. const range = IDBKeyRange.bound([this.userId, collectionGroup, sinceBatchId], [this.userId, collectionGroup, Number.POSITIVE_INFINITY],
  9814. /*lowerOpen=*/ true);
  9815. return documentOverlayStore(transaction)
  9816. .iterate({
  9817. index: DbDocumentOverlayCollectionGroupOverlayIndex,
  9818. range
  9819. }, (_, dbOverlay, control) => {
  9820. // We do not want to return partial batch overlays, even if the size
  9821. // of the result set exceeds the given `count` argument. Therefore, we
  9822. // continue to aggregate results even after the result size exceeds
  9823. // `count` if there are more overlays from the `currentBatchId`.
  9824. const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
  9825. if (result.size() < count ||
  9826. overlay.largestBatchId === currentBatchId) {
  9827. result.set(overlay.getKey(), overlay);
  9828. currentBatchId = overlay.largestBatchId;
  9829. }
  9830. else {
  9831. control.done();
  9832. }
  9833. })
  9834. .next(() => result);
  9835. }
  9836. saveOverlay(transaction, overlay) {
  9837. return documentOverlayStore(transaction).put(toDbDocumentOverlay(this.serializer, this.userId, overlay));
  9838. }
  9839. }
  9840. /**
  9841. * Helper to get a typed SimpleDbStore for the document overlay object store.
  9842. */
  9843. function documentOverlayStore(txn) {
  9844. return getStore(txn, DbDocumentOverlayStore);
  9845. }
  9846. /**
  9847. * @license
  9848. * Copyright 2021 Google LLC
  9849. *
  9850. * Licensed under the Apache License, Version 2.0 (the "License");
  9851. * you may not use this file except in compliance with the License.
  9852. * You may obtain a copy of the License at
  9853. *
  9854. * http://www.apache.org/licenses/LICENSE-2.0
  9855. *
  9856. * Unless required by applicable law or agreed to in writing, software
  9857. * distributed under the License is distributed on an "AS IS" BASIS,
  9858. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9859. * See the License for the specific language governing permissions and
  9860. * limitations under the License.
  9861. */
  9862. // Note: This code is copied from the backend. Code that is not used by
  9863. // Firestore was removed.
  9864. const INDEX_TYPE_NULL = 5;
  9865. const INDEX_TYPE_BOOLEAN = 10;
  9866. const INDEX_TYPE_NAN = 13;
  9867. const INDEX_TYPE_NUMBER = 15;
  9868. const INDEX_TYPE_TIMESTAMP = 20;
  9869. const INDEX_TYPE_STRING = 25;
  9870. const INDEX_TYPE_BLOB = 30;
  9871. const INDEX_TYPE_REFERENCE = 37;
  9872. const INDEX_TYPE_GEOPOINT = 45;
  9873. const INDEX_TYPE_ARRAY = 50;
  9874. const INDEX_TYPE_MAP = 55;
  9875. const INDEX_TYPE_REFERENCE_SEGMENT = 60;
  9876. // A terminator that indicates that a truncatable value was not truncated.
  9877. // This must be smaller than all other type labels.
  9878. const NOT_TRUNCATED = 2;
  9879. /** Firestore index value writer. */
  9880. class FirestoreIndexValueWriter {
  9881. constructor() { }
  9882. // The write methods below short-circuit writing terminators for values
  9883. // containing a (terminating) truncated value.
  9884. //
  9885. // As an example, consider the resulting encoding for:
  9886. //
  9887. // ["bar", [2, "foo"]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TERM, TERM, TERM)
  9888. // ["bar", [2, truncated("foo")]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TRUNC)
  9889. // ["bar", truncated(["foo"])] -> (STRING, "bar", TERM, ARRAY. STRING, "foo", TERM, TRUNC)
  9890. /** Writes an index value. */
  9891. writeIndexValue(value, encoder) {
  9892. this.writeIndexValueAux(value, encoder);
  9893. // Write separator to split index values
  9894. // (see go/firestore-storage-format#encodings).
  9895. encoder.writeInfinity();
  9896. }
  9897. writeIndexValueAux(indexValue, encoder) {
  9898. if ('nullValue' in indexValue) {
  9899. this.writeValueTypeLabel(encoder, INDEX_TYPE_NULL);
  9900. }
  9901. else if ('booleanValue' in indexValue) {
  9902. this.writeValueTypeLabel(encoder, INDEX_TYPE_BOOLEAN);
  9903. encoder.writeNumber(indexValue.booleanValue ? 1 : 0);
  9904. }
  9905. else if ('integerValue' in indexValue) {
  9906. this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
  9907. encoder.writeNumber(normalizeNumber(indexValue.integerValue));
  9908. }
  9909. else if ('doubleValue' in indexValue) {
  9910. const n = normalizeNumber(indexValue.doubleValue);
  9911. if (isNaN(n)) {
  9912. this.writeValueTypeLabel(encoder, INDEX_TYPE_NAN);
  9913. }
  9914. else {
  9915. this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
  9916. if (isNegativeZero(n)) {
  9917. // -0.0, 0 and 0.0 are all considered the same
  9918. encoder.writeNumber(0.0);
  9919. }
  9920. else {
  9921. encoder.writeNumber(n);
  9922. }
  9923. }
  9924. }
  9925. else if ('timestampValue' in indexValue) {
  9926. const timestamp = indexValue.timestampValue;
  9927. this.writeValueTypeLabel(encoder, INDEX_TYPE_TIMESTAMP);
  9928. if (typeof timestamp === 'string') {
  9929. encoder.writeString(timestamp);
  9930. }
  9931. else {
  9932. encoder.writeString(`${timestamp.seconds || ''}`);
  9933. encoder.writeNumber(timestamp.nanos || 0);
  9934. }
  9935. }
  9936. else if ('stringValue' in indexValue) {
  9937. this.writeIndexString(indexValue.stringValue, encoder);
  9938. this.writeTruncationMarker(encoder);
  9939. }
  9940. else if ('bytesValue' in indexValue) {
  9941. this.writeValueTypeLabel(encoder, INDEX_TYPE_BLOB);
  9942. encoder.writeBytes(normalizeByteString(indexValue.bytesValue));
  9943. this.writeTruncationMarker(encoder);
  9944. }
  9945. else if ('referenceValue' in indexValue) {
  9946. this.writeIndexEntityRef(indexValue.referenceValue, encoder);
  9947. }
  9948. else if ('geoPointValue' in indexValue) {
  9949. const geoPoint = indexValue.geoPointValue;
  9950. this.writeValueTypeLabel(encoder, INDEX_TYPE_GEOPOINT);
  9951. encoder.writeNumber(geoPoint.latitude || 0);
  9952. encoder.writeNumber(geoPoint.longitude || 0);
  9953. }
  9954. else if ('mapValue' in indexValue) {
  9955. if (isMaxValue(indexValue)) {
  9956. this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER);
  9957. }
  9958. else {
  9959. this.writeIndexMap(indexValue.mapValue, encoder);
  9960. this.writeTruncationMarker(encoder);
  9961. }
  9962. }
  9963. else if ('arrayValue' in indexValue) {
  9964. this.writeIndexArray(indexValue.arrayValue, encoder);
  9965. this.writeTruncationMarker(encoder);
  9966. }
  9967. else {
  9968. fail();
  9969. }
  9970. }
  9971. writeIndexString(stringIndexValue, encoder) {
  9972. this.writeValueTypeLabel(encoder, INDEX_TYPE_STRING);
  9973. this.writeUnlabeledIndexString(stringIndexValue, encoder);
  9974. }
  9975. writeUnlabeledIndexString(stringIndexValue, encoder) {
  9976. encoder.writeString(stringIndexValue);
  9977. }
  9978. writeIndexMap(mapIndexValue, encoder) {
  9979. const map = mapIndexValue.fields || {};
  9980. this.writeValueTypeLabel(encoder, INDEX_TYPE_MAP);
  9981. for (const key of Object.keys(map)) {
  9982. this.writeIndexString(key, encoder);
  9983. this.writeIndexValueAux(map[key], encoder);
  9984. }
  9985. }
  9986. writeIndexArray(arrayIndexValue, encoder) {
  9987. const values = arrayIndexValue.values || [];
  9988. this.writeValueTypeLabel(encoder, INDEX_TYPE_ARRAY);
  9989. for (const element of values) {
  9990. this.writeIndexValueAux(element, encoder);
  9991. }
  9992. }
  9993. writeIndexEntityRef(referenceValue, encoder) {
  9994. this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE);
  9995. const path = DocumentKey.fromName(referenceValue).path;
  9996. path.forEach(segment => {
  9997. this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE_SEGMENT);
  9998. this.writeUnlabeledIndexString(segment, encoder);
  9999. });
  10000. }
  10001. writeValueTypeLabel(encoder, typeOrder) {
  10002. encoder.writeNumber(typeOrder);
  10003. }
  10004. writeTruncationMarker(encoder) {
  10005. // While the SDK does not implement truncation, the truncation marker is
  10006. // used to terminate all variable length values (which are strings, bytes,
  10007. // references, arrays and maps).
  10008. encoder.writeNumber(NOT_TRUNCATED);
  10009. }
  10010. }
  10011. FirestoreIndexValueWriter.INSTANCE = new FirestoreIndexValueWriter();
  10012. /**
  10013. * @license
  10014. * Copyright 2021 Google LLC
  10015. *
  10016. * Licensed under the Apache License, Version 2.0 (the "License");
  10017. * you may not use this file except in compliance with the License.
  10018. * You may obtain a copy of the License at
  10019. *
  10020. * http://www.apache.org/licenses/LICENSE-2.0
  10021. *
  10022. * Unless required by applicable law | agreed to in writing, software
  10023. * distributed under the License is distributed on an "AS IS" BASIS,
  10024. * WITHOUT WARRANTIES | CONDITIONS OF ANY KIND, either express | implied.
  10025. * See the License for the specific language governing permissions and
  10026. * limitations under the License.
  10027. */
  10028. /** These constants are taken from the backend. */
  10029. const MIN_SURROGATE = '\uD800';
  10030. const MAX_SURROGATE = '\uDBFF';
  10031. const ESCAPE1 = 0x00;
  10032. const NULL_BYTE = 0xff; // Combined with ESCAPE1
  10033. const SEPARATOR = 0x01; // Combined with ESCAPE1
  10034. const ESCAPE2 = 0xff;
  10035. const INFINITY = 0xff; // Combined with ESCAPE2
  10036. const FF_BYTE = 0x00; // Combined with ESCAPE2
  10037. const LONG_SIZE = 64;
  10038. const BYTE_SIZE = 8;
  10039. /**
  10040. * The default size of the buffer. This is arbitrary, but likely larger than
  10041. * most index values so that less copies of the underlying buffer will be made.
  10042. * For large values, a single copy will made to double the buffer length.
  10043. */
  10044. const DEFAULT_BUFFER_SIZE = 1024;
  10045. /** Converts a JavaScript number to a byte array (using big endian encoding). */
  10046. function doubleToLongBits(value) {
  10047. const dv = new DataView(new ArrayBuffer(8));
  10048. dv.setFloat64(0, value, /* littleEndian= */ false);
  10049. return new Uint8Array(dv.buffer);
  10050. }
  10051. /**
  10052. * Counts the number of zeros in a byte.
  10053. *
  10054. * Visible for testing.
  10055. */
  10056. function numberOfLeadingZerosInByte(x) {
  10057. if (x === 0) {
  10058. return 8;
  10059. }
  10060. let zeros = 0;
  10061. if (x >> 4 === 0) {
  10062. // Test if the first four bits are zero.
  10063. zeros += 4;
  10064. x = x << 4;
  10065. }
  10066. if (x >> 6 === 0) {
  10067. // Test if the first two (or next two) bits are zero.
  10068. zeros += 2;
  10069. x = x << 2;
  10070. }
  10071. if (x >> 7 === 0) {
  10072. // Test if the remaining bit is zero.
  10073. zeros += 1;
  10074. }
  10075. return zeros;
  10076. }
  10077. /** Counts the number of leading zeros in the given byte array. */
  10078. function numberOfLeadingZeros(bytes) {
  10079. let leadingZeros = 0;
  10080. for (let i = 0; i < 8; ++i) {
  10081. const zeros = numberOfLeadingZerosInByte(bytes[i] & 0xff);
  10082. leadingZeros += zeros;
  10083. if (zeros !== 8) {
  10084. break;
  10085. }
  10086. }
  10087. return leadingZeros;
  10088. }
  10089. /**
  10090. * Returns the number of bytes required to store "value". Leading zero bytes
  10091. * are skipped.
  10092. */
  10093. function unsignedNumLength(value) {
  10094. // This is just the number of bytes for the unsigned representation of the number.
  10095. const numBits = LONG_SIZE - numberOfLeadingZeros(value);
  10096. return Math.ceil(numBits / BYTE_SIZE);
  10097. }
  10098. /**
  10099. * OrderedCodeWriter is a minimal-allocation implementation of the writing
  10100. * behavior defined by the backend.
  10101. *
  10102. * The code is ported from its Java counterpart.
  10103. */
  10104. class OrderedCodeWriter {
  10105. constructor() {
  10106. this.buffer = new Uint8Array(DEFAULT_BUFFER_SIZE);
  10107. this.position = 0;
  10108. }
  10109. writeBytesAscending(value) {
  10110. const it = value[Symbol.iterator]();
  10111. let byte = it.next();
  10112. while (!byte.done) {
  10113. this.writeByteAscending(byte.value);
  10114. byte = it.next();
  10115. }
  10116. this.writeSeparatorAscending();
  10117. }
  10118. writeBytesDescending(value) {
  10119. const it = value[Symbol.iterator]();
  10120. let byte = it.next();
  10121. while (!byte.done) {
  10122. this.writeByteDescending(byte.value);
  10123. byte = it.next();
  10124. }
  10125. this.writeSeparatorDescending();
  10126. }
  10127. /** Writes utf8 bytes into this byte sequence, ascending. */
  10128. writeUtf8Ascending(sequence) {
  10129. for (const c of sequence) {
  10130. const charCode = c.charCodeAt(0);
  10131. if (charCode < 0x80) {
  10132. this.writeByteAscending(charCode);
  10133. }
  10134. else if (charCode < 0x800) {
  10135. this.writeByteAscending((0x0f << 6) | (charCode >>> 6));
  10136. this.writeByteAscending(0x80 | (0x3f & charCode));
  10137. }
  10138. else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
  10139. this.writeByteAscending((0x0f << 5) | (charCode >>> 12));
  10140. this.writeByteAscending(0x80 | (0x3f & (charCode >>> 6)));
  10141. this.writeByteAscending(0x80 | (0x3f & charCode));
  10142. }
  10143. else {
  10144. const codePoint = c.codePointAt(0);
  10145. this.writeByteAscending((0x0f << 4) | (codePoint >>> 18));
  10146. this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 12)));
  10147. this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 6)));
  10148. this.writeByteAscending(0x80 | (0x3f & codePoint));
  10149. }
  10150. }
  10151. this.writeSeparatorAscending();
  10152. }
  10153. /** Writes utf8 bytes into this byte sequence, descending */
  10154. writeUtf8Descending(sequence) {
  10155. for (const c of sequence) {
  10156. const charCode = c.charCodeAt(0);
  10157. if (charCode < 0x80) {
  10158. this.writeByteDescending(charCode);
  10159. }
  10160. else if (charCode < 0x800) {
  10161. this.writeByteDescending((0x0f << 6) | (charCode >>> 6));
  10162. this.writeByteDescending(0x80 | (0x3f & charCode));
  10163. }
  10164. else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
  10165. this.writeByteDescending((0x0f << 5) | (charCode >>> 12));
  10166. this.writeByteDescending(0x80 | (0x3f & (charCode >>> 6)));
  10167. this.writeByteDescending(0x80 | (0x3f & charCode));
  10168. }
  10169. else {
  10170. const codePoint = c.codePointAt(0);
  10171. this.writeByteDescending((0x0f << 4) | (codePoint >>> 18));
  10172. this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 12)));
  10173. this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 6)));
  10174. this.writeByteDescending(0x80 | (0x3f & codePoint));
  10175. }
  10176. }
  10177. this.writeSeparatorDescending();
  10178. }
  10179. writeNumberAscending(val) {
  10180. // Values are encoded with a single byte length prefix, followed by the
  10181. // actual value in big-endian format with leading 0 bytes dropped.
  10182. const value = this.toOrderedBits(val);
  10183. const len = unsignedNumLength(value);
  10184. this.ensureAvailable(1 + len);
  10185. this.buffer[this.position++] = len & 0xff; // Write the length
  10186. for (let i = value.length - len; i < value.length; ++i) {
  10187. this.buffer[this.position++] = value[i] & 0xff;
  10188. }
  10189. }
  10190. writeNumberDescending(val) {
  10191. // Values are encoded with a single byte length prefix, followed by the
  10192. // inverted value in big-endian format with leading 0 bytes dropped.
  10193. const value = this.toOrderedBits(val);
  10194. const len = unsignedNumLength(value);
  10195. this.ensureAvailable(1 + len);
  10196. this.buffer[this.position++] = ~(len & 0xff); // Write the length
  10197. for (let i = value.length - len; i < value.length; ++i) {
  10198. this.buffer[this.position++] = ~(value[i] & 0xff);
  10199. }
  10200. }
  10201. /**
  10202. * Writes the "infinity" byte sequence that sorts after all other byte
  10203. * sequences written in ascending order.
  10204. */
  10205. writeInfinityAscending() {
  10206. this.writeEscapedByteAscending(ESCAPE2);
  10207. this.writeEscapedByteAscending(INFINITY);
  10208. }
  10209. /**
  10210. * Writes the "infinity" byte sequence that sorts before all other byte
  10211. * sequences written in descending order.
  10212. */
  10213. writeInfinityDescending() {
  10214. this.writeEscapedByteDescending(ESCAPE2);
  10215. this.writeEscapedByteDescending(INFINITY);
  10216. }
  10217. /**
  10218. * Resets the buffer such that it is the same as when it was newly
  10219. * constructed.
  10220. */
  10221. reset() {
  10222. this.position = 0;
  10223. }
  10224. seed(encodedBytes) {
  10225. this.ensureAvailable(encodedBytes.length);
  10226. this.buffer.set(encodedBytes, this.position);
  10227. this.position += encodedBytes.length;
  10228. }
  10229. /** Makes a copy of the encoded bytes in this buffer. */
  10230. encodedBytes() {
  10231. return this.buffer.slice(0, this.position);
  10232. }
  10233. /**
  10234. * Encodes `val` into an encoding so that the order matches the IEEE 754
  10235. * floating-point comparison results with the following exceptions:
  10236. * -0.0 < 0.0
  10237. * all non-NaN < NaN
  10238. * NaN = NaN
  10239. */
  10240. toOrderedBits(val) {
  10241. const value = doubleToLongBits(val);
  10242. // Check if the first bit is set. We use a bit mask since value[0] is
  10243. // encoded as a number from 0 to 255.
  10244. const isNegative = (value[0] & 0x80) !== 0;
  10245. // Revert the two complement to get natural ordering
  10246. value[0] ^= isNegative ? 0xff : 0x80;
  10247. for (let i = 1; i < value.length; ++i) {
  10248. value[i] ^= isNegative ? 0xff : 0x00;
  10249. }
  10250. return value;
  10251. }
  10252. /** Writes a single byte ascending to the buffer. */
  10253. writeByteAscending(b) {
  10254. const masked = b & 0xff;
  10255. if (masked === ESCAPE1) {
  10256. this.writeEscapedByteAscending(ESCAPE1);
  10257. this.writeEscapedByteAscending(NULL_BYTE);
  10258. }
  10259. else if (masked === ESCAPE2) {
  10260. this.writeEscapedByteAscending(ESCAPE2);
  10261. this.writeEscapedByteAscending(FF_BYTE);
  10262. }
  10263. else {
  10264. this.writeEscapedByteAscending(masked);
  10265. }
  10266. }
  10267. /** Writes a single byte descending to the buffer. */
  10268. writeByteDescending(b) {
  10269. const masked = b & 0xff;
  10270. if (masked === ESCAPE1) {
  10271. this.writeEscapedByteDescending(ESCAPE1);
  10272. this.writeEscapedByteDescending(NULL_BYTE);
  10273. }
  10274. else if (masked === ESCAPE2) {
  10275. this.writeEscapedByteDescending(ESCAPE2);
  10276. this.writeEscapedByteDescending(FF_BYTE);
  10277. }
  10278. else {
  10279. this.writeEscapedByteDescending(b);
  10280. }
  10281. }
  10282. writeSeparatorAscending() {
  10283. this.writeEscapedByteAscending(ESCAPE1);
  10284. this.writeEscapedByteAscending(SEPARATOR);
  10285. }
  10286. writeSeparatorDescending() {
  10287. this.writeEscapedByteDescending(ESCAPE1);
  10288. this.writeEscapedByteDescending(SEPARATOR);
  10289. }
  10290. writeEscapedByteAscending(b) {
  10291. this.ensureAvailable(1);
  10292. this.buffer[this.position++] = b;
  10293. }
  10294. writeEscapedByteDescending(b) {
  10295. this.ensureAvailable(1);
  10296. this.buffer[this.position++] = ~b;
  10297. }
  10298. ensureAvailable(bytes) {
  10299. const minCapacity = bytes + this.position;
  10300. if (minCapacity <= this.buffer.length) {
  10301. return;
  10302. }
  10303. // Try doubling.
  10304. let newLength = this.buffer.length * 2;
  10305. // Still not big enough? Just allocate the right size.
  10306. if (newLength < minCapacity) {
  10307. newLength = minCapacity;
  10308. }
  10309. // Create the new buffer.
  10310. const newBuffer = new Uint8Array(newLength);
  10311. newBuffer.set(this.buffer); // copy old data
  10312. this.buffer = newBuffer;
  10313. }
  10314. }
  10315. class AscendingIndexByteEncoder {
  10316. constructor(orderedCode) {
  10317. this.orderedCode = orderedCode;
  10318. }
  10319. writeBytes(value) {
  10320. this.orderedCode.writeBytesAscending(value);
  10321. }
  10322. writeString(value) {
  10323. this.orderedCode.writeUtf8Ascending(value);
  10324. }
  10325. writeNumber(value) {
  10326. this.orderedCode.writeNumberAscending(value);
  10327. }
  10328. writeInfinity() {
  10329. this.orderedCode.writeInfinityAscending();
  10330. }
  10331. }
  10332. class DescendingIndexByteEncoder {
  10333. constructor(orderedCode) {
  10334. this.orderedCode = orderedCode;
  10335. }
  10336. writeBytes(value) {
  10337. this.orderedCode.writeBytesDescending(value);
  10338. }
  10339. writeString(value) {
  10340. this.orderedCode.writeUtf8Descending(value);
  10341. }
  10342. writeNumber(value) {
  10343. this.orderedCode.writeNumberDescending(value);
  10344. }
  10345. writeInfinity() {
  10346. this.orderedCode.writeInfinityDescending();
  10347. }
  10348. }
  10349. /**
  10350. * Implements `DirectionalIndexByteEncoder` using `OrderedCodeWriter` for the
  10351. * actual encoding.
  10352. */
  10353. class IndexByteEncoder {
  10354. constructor() {
  10355. this.orderedCode = new OrderedCodeWriter();
  10356. this.ascending = new AscendingIndexByteEncoder(this.orderedCode);
  10357. this.descending = new DescendingIndexByteEncoder(this.orderedCode);
  10358. }
  10359. seed(encodedBytes) {
  10360. this.orderedCode.seed(encodedBytes);
  10361. }
  10362. forKind(kind) {
  10363. return kind === 0 /* IndexKind.ASCENDING */ ? this.ascending : this.descending;
  10364. }
  10365. encodedBytes() {
  10366. return this.orderedCode.encodedBytes();
  10367. }
  10368. reset() {
  10369. this.orderedCode.reset();
  10370. }
  10371. }
  10372. /**
  10373. * @license
  10374. * Copyright 2022 Google LLC
  10375. *
  10376. * Licensed under the Apache License, Version 2.0 (the "License");
  10377. * you may not use this file except in compliance with the License.
  10378. * You may obtain a copy of the License at
  10379. *
  10380. * http://www.apache.org/licenses/LICENSE-2.0
  10381. *
  10382. * Unless required by applicable law or agreed to in writing, software
  10383. * distributed under the License is distributed on an "AS IS" BASIS,
  10384. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10385. * See the License for the specific language governing permissions and
  10386. * limitations under the License.
  10387. */
  10388. /** Represents an index entry saved by the SDK in persisted storage. */
  10389. class IndexEntry {
  10390. constructor(indexId, documentKey, arrayValue, directionalValue) {
  10391. this.indexId = indexId;
  10392. this.documentKey = documentKey;
  10393. this.arrayValue = arrayValue;
  10394. this.directionalValue = directionalValue;
  10395. }
  10396. /**
  10397. * Returns an IndexEntry entry that sorts immediately after the current
  10398. * directional value.
  10399. */
  10400. successor() {
  10401. const currentLength = this.directionalValue.length;
  10402. const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
  10403. ? currentLength + 1
  10404. : currentLength;
  10405. const successor = new Uint8Array(newLength);
  10406. successor.set(this.directionalValue, 0);
  10407. if (newLength !== currentLength) {
  10408. successor.set([0], this.directionalValue.length);
  10409. }
  10410. else {
  10411. ++successor[successor.length - 1];
  10412. }
  10413. return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
  10414. }
  10415. }
  10416. function indexEntryComparator(left, right) {
  10417. let cmp = left.indexId - right.indexId;
  10418. if (cmp !== 0) {
  10419. return cmp;
  10420. }
  10421. cmp = compareByteArrays(left.arrayValue, right.arrayValue);
  10422. if (cmp !== 0) {
  10423. return cmp;
  10424. }
  10425. cmp = compareByteArrays(left.directionalValue, right.directionalValue);
  10426. if (cmp !== 0) {
  10427. return cmp;
  10428. }
  10429. return DocumentKey.comparator(left.documentKey, right.documentKey);
  10430. }
  10431. function compareByteArrays(left, right) {
  10432. for (let i = 0; i < left.length && i < right.length; ++i) {
  10433. const compare = left[i] - right[i];
  10434. if (compare !== 0) {
  10435. return compare;
  10436. }
  10437. }
  10438. return left.length - right.length;
  10439. }
  10440. /**
  10441. * @license
  10442. * Copyright 2022 Google LLC
  10443. *
  10444. * Licensed under the Apache License, Version 2.0 (the "License");
  10445. * you may not use this file except in compliance with the License.
  10446. * You may obtain a copy of the License at
  10447. *
  10448. * http://www.apache.org/licenses/LICENSE-2.0
  10449. *
  10450. * Unless required by applicable law or agreed to in writing, software
  10451. * distributed under the License is distributed on an "AS IS" BASIS,
  10452. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10453. * See the License for the specific language governing permissions and
  10454. * limitations under the License.
  10455. */
  10456. /**
  10457. * A light query planner for Firestore.
  10458. *
  10459. * This class matches a `FieldIndex` against a Firestore Query `Target`. It
  10460. * determines whether a given index can be used to serve the specified target.
  10461. *
  10462. * The following table showcases some possible index configurations:
  10463. *
  10464. * Query | Index
  10465. * -----------------------------------------------------------------------------
  10466. * where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
  10467. * where('a', '==', 'a').where('b', '==', 'b') | a ASC
  10468. * where('a', '==', 'a').where('b', '==', 'b') | b DESC
  10469. * where('a', '>=', 'a').orderBy('a') | a ASC
  10470. * where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
  10471. * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
  10472. * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
  10473. * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
  10474. * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
  10475. */
  10476. class TargetIndexMatcher {
  10477. constructor(target) {
  10478. this.collectionId =
  10479. target.collectionGroup != null
  10480. ? target.collectionGroup
  10481. : target.path.lastSegment();
  10482. this.orderBys = target.orderBy;
  10483. this.equalityFilters = [];
  10484. for (const filter of target.filters) {
  10485. const fieldFilter = filter;
  10486. if (fieldFilter.isInequality()) {
  10487. this.inequalityFilter = fieldFilter;
  10488. }
  10489. else {
  10490. this.equalityFilters.push(fieldFilter);
  10491. }
  10492. }
  10493. }
  10494. /**
  10495. * Returns whether the index can be used to serve the TargetIndexMatcher's
  10496. * target.
  10497. *
  10498. * An index is considered capable of serving the target when:
  10499. * - The target uses all index segments for its filters and orderBy clauses.
  10500. * The target can have additional filter and orderBy clauses, but not
  10501. * fewer.
  10502. * - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
  10503. * have a corresponding `CONTAINS` segment.
  10504. * - All directional index segments can be mapped to the target as a series of
  10505. * equality filters, a single inequality filter and a series of orderBy
  10506. * clauses.
  10507. * - The segments that represent the equality filters may appear out of order.
  10508. * - The optional segment for the inequality filter must appear after all
  10509. * equality segments.
  10510. * - The segments that represent that orderBy clause of the target must appear
  10511. * in order after all equality and inequality segments. Single orderBy
  10512. * clauses cannot be skipped, but a continuous orderBy suffix may be
  10513. * omitted.
  10514. */
  10515. servedByIndex(index) {
  10516. hardAssert(index.collectionGroup === this.collectionId);
  10517. // If there is an array element, find a matching filter.
  10518. const arraySegment = fieldIndexGetArraySegment(index);
  10519. if (arraySegment !== undefined &&
  10520. !this.hasMatchingEqualityFilter(arraySegment)) {
  10521. return false;
  10522. }
  10523. const segments = fieldIndexGetDirectionalSegments(index);
  10524. let segmentIndex = 0;
  10525. let orderBysIndex = 0;
  10526. // Process all equalities first. Equalities can appear out of order.
  10527. for (; segmentIndex < segments.length; ++segmentIndex) {
  10528. // We attempt to greedily match all segments to equality filters. If a
  10529. // filter matches an index segment, we can mark the segment as used.
  10530. // Since it is not possible to use the same field path in both an equality
  10531. // and inequality/oderBy clause, we do not have to consider the possibility
  10532. // that a matching equality segment should instead be used to map to an
  10533. // inequality filter or orderBy clause.
  10534. if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
  10535. // If we cannot find a matching filter, we need to verify whether the
  10536. // remaining segments map to the target's inequality and its orderBy
  10537. // clauses.
  10538. break;
  10539. }
  10540. }
  10541. // If we already have processed all segments, all segments are used to serve
  10542. // the equality filters and we do not need to map any segments to the
  10543. // target's inequality and orderBy clauses.
  10544. if (segmentIndex === segments.length) {
  10545. return true;
  10546. }
  10547. // If there is an inequality filter, the next segment must match both the
  10548. // filter and the first orderBy clause.
  10549. if (this.inequalityFilter !== undefined) {
  10550. const segment = segments[segmentIndex];
  10551. if (!this.matchesFilter(this.inequalityFilter, segment) ||
  10552. !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
  10553. return false;
  10554. }
  10555. ++segmentIndex;
  10556. }
  10557. // All remaining segments need to represent the prefix of the target's
  10558. // orderBy.
  10559. for (; segmentIndex < segments.length; ++segmentIndex) {
  10560. const segment = segments[segmentIndex];
  10561. if (orderBysIndex >= this.orderBys.length ||
  10562. !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
  10563. return false;
  10564. }
  10565. }
  10566. return true;
  10567. }
  10568. hasMatchingEqualityFilter(segment) {
  10569. for (const filter of this.equalityFilters) {
  10570. if (this.matchesFilter(filter, segment)) {
  10571. return true;
  10572. }
  10573. }
  10574. return false;
  10575. }
  10576. matchesFilter(filter, segment) {
  10577. if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
  10578. return false;
  10579. }
  10580. const isArrayOperator = filter.op === "array-contains" /* Operator.ARRAY_CONTAINS */ ||
  10581. filter.op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */;
  10582. return (segment.kind === 2 /* IndexKind.CONTAINS */) === isArrayOperator;
  10583. }
  10584. matchesOrderBy(orderBy, segment) {
  10585. if (!orderBy.field.isEqual(segment.fieldPath)) {
  10586. return false;
  10587. }
  10588. return ((segment.kind === 0 /* IndexKind.ASCENDING */ &&
  10589. orderBy.dir === "asc" /* Direction.ASCENDING */) ||
  10590. (segment.kind === 1 /* IndexKind.DESCENDING */ &&
  10591. orderBy.dir === "desc" /* Direction.DESCENDING */));
  10592. }
  10593. }
  10594. /**
  10595. * @license
  10596. * Copyright 2022 Google LLC
  10597. *
  10598. * Licensed under the Apache License, Version 2.0 (the "License");
  10599. * you may not use this file except in compliance with the License.
  10600. * You may obtain a copy of the License at
  10601. *
  10602. * http://www.apache.org/licenses/LICENSE-2.0
  10603. *
  10604. * Unless required by applicable law or agreed to in writing, software
  10605. * distributed under the License is distributed on an "AS IS" BASIS,
  10606. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10607. * See the License for the specific language governing permissions and
  10608. * limitations under the License.
  10609. */
  10610. /**
  10611. * Provides utility functions that help with boolean logic transformations needed for handling
  10612. * complex filters used in queries.
  10613. */
  10614. /**
  10615. * The `in` filter is only a syntactic sugar over a disjunction of equalities. For instance: `a in
  10616. * [1,2,3]` is in fact `a==1 || a==2 || a==3`. This method expands any `in` filter in the given
  10617. * input into a disjunction of equality filters and returns the expanded filter.
  10618. */
  10619. function computeInExpansion(filter) {
  10620. var _a, _b;
  10621. hardAssert(filter instanceof FieldFilter || filter instanceof CompositeFilter);
  10622. if (filter instanceof FieldFilter) {
  10623. if (filter instanceof InFilter) {
  10624. 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))) || [];
  10625. return CompositeFilter.create(expandedFilters, "or" /* CompositeOperator.OR */);
  10626. }
  10627. else {
  10628. // We have reached other kinds of field filters.
  10629. return filter;
  10630. }
  10631. }
  10632. // We have a composite filter.
  10633. const expandedFilters = filter.filters.map(subfilter => computeInExpansion(subfilter));
  10634. return CompositeFilter.create(expandedFilters, filter.op);
  10635. }
  10636. /**
  10637. * Given a composite filter, returns the list of terms in its disjunctive normal form.
  10638. *
  10639. * <p>Each element in the return value is one term of the resulting DNF. For instance: For the
  10640. * input: (A || B) && C, the DNF form is: (A && C) || (B && C), and the return value is a list
  10641. * with two elements: a composite filter that performs (A && C), and a composite filter that
  10642. * performs (B && C).
  10643. *
  10644. * @param filter the composite filter to calculate DNF transform for.
  10645. * @return the terms in the DNF transform.
  10646. */
  10647. function getDnfTerms(filter) {
  10648. if (filter.getFilters().length === 0) {
  10649. return [];
  10650. }
  10651. const result = computeDistributedNormalForm(computeInExpansion(filter));
  10652. hardAssert(isDisjunctiveNormalForm(result));
  10653. if (isSingleFieldFilter(result) || isFlatConjunction(result)) {
  10654. return [result];
  10655. }
  10656. return result.getFilters();
  10657. }
  10658. /** Returns true if the given filter is a single field filter. e.g. (a == 10). */
  10659. function isSingleFieldFilter(filter) {
  10660. return filter instanceof FieldFilter;
  10661. }
  10662. /**
  10663. * Returns true if the given filter is the conjunction of one or more field filters. e.g. (a == 10
  10664. * && b == 20)
  10665. */
  10666. function isFlatConjunction(filter) {
  10667. return (filter instanceof CompositeFilter &&
  10668. compositeFilterIsFlatConjunction(filter));
  10669. }
  10670. /**
  10671. * Returns whether or not the given filter is in disjunctive normal form (DNF).
  10672. *
  10673. * <p>In boolean logic, a disjunctive normal form (DNF) is a canonical normal form of a logical
  10674. * formula consisting of a disjunction of conjunctions; it can also be described as an OR of ANDs.
  10675. *
  10676. * <p>For more info, visit: https://en.wikipedia.org/wiki/Disjunctive_normal_form
  10677. */
  10678. function isDisjunctiveNormalForm(filter) {
  10679. return (isSingleFieldFilter(filter) ||
  10680. isFlatConjunction(filter) ||
  10681. isDisjunctionOfFieldFiltersAndFlatConjunctions(filter));
  10682. }
  10683. /**
  10684. * Returns true if the given filter is the disjunction of one or more "flat conjunctions" and
  10685. * field filters. e.g. (a == 10) || (b==20 && c==30)
  10686. */
  10687. function isDisjunctionOfFieldFiltersAndFlatConjunctions(filter) {
  10688. if (filter instanceof CompositeFilter) {
  10689. if (compositeFilterIsDisjunction(filter)) {
  10690. for (const subFilter of filter.getFilters()) {
  10691. if (!isSingleFieldFilter(subFilter) && !isFlatConjunction(subFilter)) {
  10692. return false;
  10693. }
  10694. }
  10695. return true;
  10696. }
  10697. }
  10698. return false;
  10699. }
  10700. function computeDistributedNormalForm(filter) {
  10701. hardAssert(filter instanceof FieldFilter || filter instanceof CompositeFilter);
  10702. if (filter instanceof FieldFilter) {
  10703. return filter;
  10704. }
  10705. if (filter.filters.length === 1) {
  10706. return computeDistributedNormalForm(filter.filters[0]);
  10707. }
  10708. // Compute DNF for each of the subfilters first
  10709. const result = filter.filters.map(subfilter => computeDistributedNormalForm(subfilter));
  10710. let newFilter = CompositeFilter.create(result, filter.op);
  10711. newFilter = applyAssociation(newFilter);
  10712. if (isDisjunctiveNormalForm(newFilter)) {
  10713. return newFilter;
  10714. }
  10715. hardAssert(newFilter instanceof CompositeFilter);
  10716. hardAssert(compositeFilterIsConjunction(newFilter));
  10717. hardAssert(newFilter.filters.length > 1);
  10718. return newFilter.filters.reduce((runningResult, filter) => applyDistribution(runningResult, filter));
  10719. }
  10720. function applyDistribution(lhs, rhs) {
  10721. hardAssert(lhs instanceof FieldFilter || lhs instanceof CompositeFilter);
  10722. hardAssert(rhs instanceof FieldFilter || rhs instanceof CompositeFilter);
  10723. let result;
  10724. if (lhs instanceof FieldFilter) {
  10725. if (rhs instanceof FieldFilter) {
  10726. // FieldFilter FieldFilter
  10727. result = applyDistributionFieldFilters(lhs, rhs);
  10728. }
  10729. else {
  10730. // FieldFilter CompositeFilter
  10731. result = applyDistributionFieldAndCompositeFilters(lhs, rhs);
  10732. }
  10733. }
  10734. else {
  10735. if (rhs instanceof FieldFilter) {
  10736. // CompositeFilter FieldFilter
  10737. result = applyDistributionFieldAndCompositeFilters(rhs, lhs);
  10738. }
  10739. else {
  10740. // CompositeFilter CompositeFilter
  10741. result = applyDistributionCompositeFilters(lhs, rhs);
  10742. }
  10743. }
  10744. return applyAssociation(result);
  10745. }
  10746. function applyDistributionFieldFilters(lhs, rhs) {
  10747. // Conjunction distribution for two field filters is the conjunction of them.
  10748. return CompositeFilter.create([lhs, rhs], "and" /* CompositeOperator.AND */);
  10749. }
  10750. function applyDistributionCompositeFilters(lhs, rhs) {
  10751. hardAssert(lhs.filters.length > 0 && rhs.filters.length > 0);
  10752. // There are four cases:
  10753. // (A & B) & (C & D) --> (A & B & C & D)
  10754. // (A & B) & (C | D) --> (A & B & C) | (A & B & D)
  10755. // (A | B) & (C & D) --> (C & D & A) | (C & D & B)
  10756. // (A | B) & (C | D) --> (A & C) | (A & D) | (B & C) | (B & D)
  10757. // Case 1 is a merge.
  10758. if (compositeFilterIsConjunction(lhs) && compositeFilterIsConjunction(rhs)) {
  10759. return compositeFilterWithAddedFilters(lhs, rhs.getFilters());
  10760. }
  10761. // Case 2,3,4 all have at least one side (lhs or rhs) that is a disjunction. In all three cases
  10762. // we should take each element of the disjunction and distribute it over the other side, and
  10763. // return the disjunction of the distribution results.
  10764. const disjunctionSide = compositeFilterIsDisjunction(lhs) ? lhs : rhs;
  10765. const otherSide = compositeFilterIsDisjunction(lhs) ? rhs : lhs;
  10766. const results = disjunctionSide.filters.map(subfilter => applyDistribution(subfilter, otherSide));
  10767. return CompositeFilter.create(results, "or" /* CompositeOperator.OR */);
  10768. }
  10769. function applyDistributionFieldAndCompositeFilters(fieldFilter, compositeFilter) {
  10770. // There are two cases:
  10771. // A & (B & C) --> (A & B & C)
  10772. // A & (B | C) --> (A & B) | (A & C)
  10773. if (compositeFilterIsConjunction(compositeFilter)) {
  10774. // Case 1
  10775. return compositeFilterWithAddedFilters(compositeFilter, fieldFilter.getFilters());
  10776. }
  10777. else {
  10778. // Case 2
  10779. const newFilters = compositeFilter.filters.map(subfilter => applyDistribution(fieldFilter, subfilter));
  10780. return CompositeFilter.create(newFilters, "or" /* CompositeOperator.OR */);
  10781. }
  10782. }
  10783. /**
  10784. * Applies the associativity property to the given filter and returns the resulting filter.
  10785. *
  10786. * <ul>
  10787. * <li>A | (B | C) == (A | B) | C == (A | B | C)
  10788. * <li>A & (B & C) == (A & B) & C == (A & B & C)
  10789. * </ul>
  10790. *
  10791. * <p>For more info, visit: https://en.wikipedia.org/wiki/Associative_property#Propositional_logic
  10792. */
  10793. function applyAssociation(filter) {
  10794. hardAssert(filter instanceof FieldFilter || filter instanceof CompositeFilter);
  10795. if (filter instanceof FieldFilter) {
  10796. return filter;
  10797. }
  10798. const filters = filter.getFilters();
  10799. // If the composite filter only contains 1 filter, apply associativity to it.
  10800. if (filters.length === 1) {
  10801. return applyAssociation(filters[0]);
  10802. }
  10803. // Associativity applied to a flat composite filter results is itself.
  10804. if (compositeFilterIsFlat(filter)) {
  10805. return filter;
  10806. }
  10807. // First apply associativity to all subfilters. This will in turn recursively apply
  10808. // associativity to all nested composite filters and field filters.
  10809. const updatedFilters = filters.map(subfilter => applyAssociation(subfilter));
  10810. // For composite subfilters that perform the same kind of logical operation as `compositeFilter`
  10811. // take out their filters and add them to `compositeFilter`. For example:
  10812. // compositeFilter = (A | (B | C | D))
  10813. // compositeSubfilter = (B | C | D)
  10814. // Result: (A | B | C | D)
  10815. // Note that the `compositeSubfilter` has been eliminated, and its filters (B, C, D) have been
  10816. // added to the top-level "compositeFilter".
  10817. const newSubfilters = [];
  10818. updatedFilters.forEach(subfilter => {
  10819. if (subfilter instanceof FieldFilter) {
  10820. newSubfilters.push(subfilter);
  10821. }
  10822. else if (subfilter instanceof CompositeFilter) {
  10823. if (subfilter.op === filter.op) {
  10824. // compositeFilter: (A | (B | C))
  10825. // compositeSubfilter: (B | C)
  10826. // Result: (A | B | C)
  10827. newSubfilters.push(...subfilter.filters);
  10828. }
  10829. else {
  10830. // compositeFilter: (A | (B & C))
  10831. // compositeSubfilter: (B & C)
  10832. // Result: (A | (B & C))
  10833. newSubfilters.push(subfilter);
  10834. }
  10835. }
  10836. });
  10837. if (newSubfilters.length === 1) {
  10838. return newSubfilters[0];
  10839. }
  10840. return CompositeFilter.create(newSubfilters, filter.op);
  10841. }
  10842. /**
  10843. * @license
  10844. * Copyright 2019 Google LLC
  10845. *
  10846. * Licensed under the Apache License, Version 2.0 (the "License");
  10847. * you may not use this file except in compliance with the License.
  10848. * You may obtain a copy of the License at
  10849. *
  10850. * http://www.apache.org/licenses/LICENSE-2.0
  10851. *
  10852. * Unless required by applicable law or agreed to in writing, software
  10853. * distributed under the License is distributed on an "AS IS" BASIS,
  10854. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10855. * See the License for the specific language governing permissions and
  10856. * limitations under the License.
  10857. */
  10858. /**
  10859. * An in-memory implementation of IndexManager.
  10860. */
  10861. class MemoryIndexManager {
  10862. constructor() {
  10863. this.collectionParentIndex = new MemoryCollectionParentIndex();
  10864. }
  10865. addToCollectionParentIndex(transaction, collectionPath) {
  10866. this.collectionParentIndex.add(collectionPath);
  10867. return PersistencePromise.resolve();
  10868. }
  10869. getCollectionParents(transaction, collectionId) {
  10870. return PersistencePromise.resolve(this.collectionParentIndex.getEntries(collectionId));
  10871. }
  10872. addFieldIndex(transaction, index) {
  10873. // Field indices are not supported with memory persistence.
  10874. return PersistencePromise.resolve();
  10875. }
  10876. deleteFieldIndex(transaction, index) {
  10877. // Field indices are not supported with memory persistence.
  10878. return PersistencePromise.resolve();
  10879. }
  10880. getDocumentsMatchingTarget(transaction, target) {
  10881. // Field indices are not supported with memory persistence.
  10882. return PersistencePromise.resolve(null);
  10883. }
  10884. getIndexType(transaction, target) {
  10885. // Field indices are not supported with memory persistence.
  10886. return PersistencePromise.resolve(0 /* IndexType.NONE */);
  10887. }
  10888. getFieldIndexes(transaction, collectionGroup) {
  10889. // Field indices are not supported with memory persistence.
  10890. return PersistencePromise.resolve([]);
  10891. }
  10892. getNextCollectionGroupToUpdate(transaction) {
  10893. // Field indices are not supported with memory persistence.
  10894. return PersistencePromise.resolve(null);
  10895. }
  10896. getMinOffset(transaction, target) {
  10897. return PersistencePromise.resolve(IndexOffset.min());
  10898. }
  10899. getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
  10900. return PersistencePromise.resolve(IndexOffset.min());
  10901. }
  10902. updateCollectionGroup(transaction, collectionGroup, offset) {
  10903. // Field indices are not supported with memory persistence.
  10904. return PersistencePromise.resolve();
  10905. }
  10906. updateIndexEntries(transaction, documents) {
  10907. // Field indices are not supported with memory persistence.
  10908. return PersistencePromise.resolve();
  10909. }
  10910. }
  10911. /**
  10912. * Internal implementation of the collection-parent index exposed by MemoryIndexManager.
  10913. * Also used for in-memory caching by IndexedDbIndexManager and initial index population
  10914. * in indexeddb_schema.ts
  10915. */
  10916. class MemoryCollectionParentIndex {
  10917. constructor() {
  10918. this.index = {};
  10919. }
  10920. // Returns false if the entry already existed.
  10921. add(collectionPath) {
  10922. const collectionId = collectionPath.lastSegment();
  10923. const parentPath = collectionPath.popLast();
  10924. const existingParents = this.index[collectionId] ||
  10925. new SortedSet(ResourcePath.comparator);
  10926. const added = !existingParents.has(parentPath);
  10927. this.index[collectionId] = existingParents.add(parentPath);
  10928. return added;
  10929. }
  10930. has(collectionPath) {
  10931. const collectionId = collectionPath.lastSegment();
  10932. const parentPath = collectionPath.popLast();
  10933. const existingParents = this.index[collectionId];
  10934. return existingParents && existingParents.has(parentPath);
  10935. }
  10936. getEntries(collectionId) {
  10937. const parentPaths = this.index[collectionId] ||
  10938. new SortedSet(ResourcePath.comparator);
  10939. return parentPaths.toArray();
  10940. }
  10941. }
  10942. /**
  10943. * @license
  10944. * Copyright 2019 Google LLC
  10945. *
  10946. * Licensed under the Apache License, Version 2.0 (the "License");
  10947. * you may not use this file except in compliance with the License.
  10948. * You may obtain a copy of the License at
  10949. *
  10950. * http://www.apache.org/licenses/LICENSE-2.0
  10951. *
  10952. * Unless required by applicable law or agreed to in writing, software
  10953. * distributed under the License is distributed on an "AS IS" BASIS,
  10954. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10955. * See the License for the specific language governing permissions and
  10956. * limitations under the License.
  10957. */
  10958. const LOG_TAG$f = 'IndexedDbIndexManager';
  10959. const EMPTY_VALUE = new Uint8Array(0);
  10960. /**
  10961. * A persisted implementation of IndexManager.
  10962. *
  10963. * PORTING NOTE: Unlike iOS and Android, the Web SDK does not memoize index
  10964. * data as it supports multi-tab access.
  10965. */
  10966. class IndexedDbIndexManager {
  10967. constructor(user, databaseId) {
  10968. this.user = user;
  10969. this.databaseId = databaseId;
  10970. /**
  10971. * An in-memory copy of the index entries we've already written since the SDK
  10972. * launched. Used to avoid re-writing the same entry repeatedly.
  10973. *
  10974. * This is *NOT* a complete cache of what's in persistence and so can never be
  10975. * used to satisfy reads.
  10976. */
  10977. this.collectionParentsCache = new MemoryCollectionParentIndex();
  10978. /**
  10979. * Maps from a target to its equivalent list of sub-targets. Each sub-target
  10980. * contains only one term from the target's disjunctive normal form (DNF).
  10981. */
  10982. this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
  10983. this.uid = user.uid || '';
  10984. }
  10985. /**
  10986. * Adds a new entry to the collection parent index.
  10987. *
  10988. * Repeated calls for the same collectionPath should be avoided within a
  10989. * transaction as IndexedDbIndexManager only caches writes once a transaction
  10990. * has been committed.
  10991. */
  10992. addToCollectionParentIndex(transaction, collectionPath) {
  10993. if (!this.collectionParentsCache.has(collectionPath)) {
  10994. const collectionId = collectionPath.lastSegment();
  10995. const parentPath = collectionPath.popLast();
  10996. transaction.addOnCommittedListener(() => {
  10997. // Add the collection to the in memory cache only if the transaction was
  10998. // successfully committed.
  10999. this.collectionParentsCache.add(collectionPath);
  11000. });
  11001. const collectionParent = {
  11002. collectionId,
  11003. parent: encodeResourcePath(parentPath)
  11004. };
  11005. return collectionParentsStore(transaction).put(collectionParent);
  11006. }
  11007. return PersistencePromise.resolve();
  11008. }
  11009. getCollectionParents(transaction, collectionId) {
  11010. const parentPaths = [];
  11011. const range = IDBKeyRange.bound([collectionId, ''], [immediateSuccessor(collectionId), ''],
  11012. /*lowerOpen=*/ false,
  11013. /*upperOpen=*/ true);
  11014. return collectionParentsStore(transaction)
  11015. .loadAll(range)
  11016. .next(entries => {
  11017. for (const entry of entries) {
  11018. // This collectionId guard shouldn't be necessary (and isn't as long
  11019. // as we're running in a real browser), but there's a bug in
  11020. // indexeddbshim that breaks our range in our tests running in node:
  11021. // https://github.com/axemclion/IndexedDBShim/issues/334
  11022. if (entry.collectionId !== collectionId) {
  11023. break;
  11024. }
  11025. parentPaths.push(decodeResourcePath(entry.parent));
  11026. }
  11027. return parentPaths;
  11028. });
  11029. }
  11030. addFieldIndex(transaction, index) {
  11031. // TODO(indexing): Verify that the auto-incrementing index ID works in
  11032. // Safari & Firefox.
  11033. const indexes = indexConfigurationStore(transaction);
  11034. const dbIndex = toDbIndexConfiguration(index);
  11035. delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
  11036. const result = indexes.add(dbIndex);
  11037. if (index.indexState) {
  11038. const states = indexStateStore(transaction);
  11039. return result.next(indexId => {
  11040. states.put(toDbIndexState(indexId, this.user, index.indexState.sequenceNumber, index.indexState.offset));
  11041. });
  11042. }
  11043. else {
  11044. return result.next();
  11045. }
  11046. }
  11047. deleteFieldIndex(transaction, index) {
  11048. const indexes = indexConfigurationStore(transaction);
  11049. const states = indexStateStore(transaction);
  11050. const entries = indexEntriesStore(transaction);
  11051. return indexes
  11052. .delete(index.indexId)
  11053. .next(() => states.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
  11054. /*lowerOpen=*/ false,
  11055. /*upperOpen=*/ true)))
  11056. .next(() => entries.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
  11057. /*lowerOpen=*/ false,
  11058. /*upperOpen=*/ true)));
  11059. }
  11060. getDocumentsMatchingTarget(transaction, target) {
  11061. const indexEntries = indexEntriesStore(transaction);
  11062. let canServeTarget = true;
  11063. const indexes = new Map();
  11064. return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
  11065. return this.getFieldIndex(transaction, subTarget).next(index => {
  11066. canServeTarget && (canServeTarget = !!index);
  11067. indexes.set(subTarget, index);
  11068. });
  11069. }).next(() => {
  11070. if (!canServeTarget) {
  11071. return PersistencePromise.resolve(null);
  11072. }
  11073. else {
  11074. let existingKeys = documentKeySet();
  11075. const result = [];
  11076. return PersistencePromise.forEach(indexes, (index, subTarget) => {
  11077. logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
  11078. const arrayValues = targetGetArrayValues(subTarget, index);
  11079. const notInValues = targetGetNotInValues(subTarget, index);
  11080. const lowerBound = targetGetLowerBound(subTarget, index);
  11081. const upperBound = targetGetUpperBound(subTarget, index);
  11082. const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
  11083. const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
  11084. const notInEncoded = this.encodeValues(index, subTarget, notInValues);
  11085. const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, lowerBound.inclusive, upperBoundEncoded, upperBound.inclusive, notInEncoded);
  11086. return PersistencePromise.forEach(indexRanges, (indexRange) => {
  11087. return indexEntries
  11088. .loadFirst(indexRange, target.limit)
  11089. .next(entries => {
  11090. entries.forEach(entry => {
  11091. const documentKey = DocumentKey.fromSegments(entry.documentKey);
  11092. if (!existingKeys.has(documentKey)) {
  11093. existingKeys = existingKeys.add(documentKey);
  11094. result.push(documentKey);
  11095. }
  11096. });
  11097. });
  11098. });
  11099. }).next(() => result);
  11100. }
  11101. });
  11102. }
  11103. getSubTargets(target) {
  11104. let subTargets = this.targetToDnfSubTargets.get(target);
  11105. if (subTargets) {
  11106. return subTargets;
  11107. }
  11108. if (target.filters.length === 0) {
  11109. subTargets = [target];
  11110. }
  11111. else {
  11112. // There is an implicit AND operation between all the filters stored in the target
  11113. const dnf = getDnfTerms(CompositeFilter.create(target.filters, "and" /* CompositeOperator.AND */));
  11114. subTargets = dnf.map(term => newTarget(target.path, target.collectionGroup, target.orderBy, term.getFilters(), target.limit, target.startAt, target.endAt));
  11115. }
  11116. this.targetToDnfSubTargets.set(target, subTargets);
  11117. return subTargets;
  11118. }
  11119. /**
  11120. * Constructs a key range query on `DbIndexEntryStore` that unions all
  11121. * bounds.
  11122. */
  11123. generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
  11124. // The number of total index scans we union together. This is similar to a
  11125. // distributed normal form, but adapted for array values. We create a single
  11126. // index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
  11127. // combined with the values from the query bounds.
  11128. const totalScans = (arrayValues != null ? arrayValues.length : 1) *
  11129. Math.max(lowerBounds.length, upperBounds.length);
  11130. const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
  11131. const indexRanges = [];
  11132. for (let i = 0; i < totalScans; ++i) {
  11133. const arrayValue = arrayValues
  11134. ? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
  11135. : EMPTY_VALUE;
  11136. const lowerBound = this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive);
  11137. const upperBound = this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive);
  11138. const notInBound = notInValues.map(notIn => this.generateLowerBound(indexId, arrayValue, notIn,
  11139. /* inclusive= */ true));
  11140. indexRanges.push(...this.createRange(lowerBound, upperBound, notInBound));
  11141. }
  11142. return indexRanges;
  11143. }
  11144. /** Generates the lower bound for `arrayValue` and `directionalValue`. */
  11145. generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
  11146. const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
  11147. return inclusive ? entry : entry.successor();
  11148. }
  11149. /** Generates the upper bound for `arrayValue` and `directionalValue`. */
  11150. generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
  11151. const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
  11152. return inclusive ? entry.successor() : entry;
  11153. }
  11154. getFieldIndex(transaction, target) {
  11155. const targetIndexMatcher = new TargetIndexMatcher(target);
  11156. const collectionGroup = target.collectionGroup != null
  11157. ? target.collectionGroup
  11158. : target.path.lastSegment();
  11159. return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
  11160. // Return the index with the most number of segments.
  11161. let index = null;
  11162. for (const candidate of indexes) {
  11163. const matches = targetIndexMatcher.servedByIndex(candidate);
  11164. if (matches &&
  11165. (!index || candidate.fields.length > index.fields.length)) {
  11166. index = candidate;
  11167. }
  11168. }
  11169. return index;
  11170. });
  11171. }
  11172. getIndexType(transaction, target) {
  11173. let indexType = 2 /* IndexType.FULL */;
  11174. const subTargets = this.getSubTargets(target);
  11175. return PersistencePromise.forEach(subTargets, (target) => {
  11176. return this.getFieldIndex(transaction, target).next(index => {
  11177. if (!index) {
  11178. indexType = 0 /* IndexType.NONE */;
  11179. }
  11180. else if (indexType !== 0 /* IndexType.NONE */ &&
  11181. index.fields.length < targetGetSegmentCount(target)) {
  11182. indexType = 1 /* IndexType.PARTIAL */;
  11183. }
  11184. });
  11185. }).next(() => {
  11186. // OR queries have more than one sub-target (one sub-target per DNF term). We currently consider
  11187. // OR queries that have a `limit` to have a partial index. For such queries we perform sorting
  11188. // and apply the limit in memory as a post-processing step.
  11189. if (targetHasLimit(target) &&
  11190. subTargets.length > 1 &&
  11191. indexType === 2 /* IndexType.FULL */) {
  11192. return 1 /* IndexType.PARTIAL */;
  11193. }
  11194. return indexType;
  11195. });
  11196. }
  11197. /**
  11198. * Returns the byte encoded form of the directional values in the field index.
  11199. * Returns `null` if the document does not have all fields specified in the
  11200. * index.
  11201. */
  11202. encodeDirectionalElements(fieldIndex, document) {
  11203. const encoder = new IndexByteEncoder();
  11204. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  11205. const field = document.data.field(segment.fieldPath);
  11206. if (field == null) {
  11207. return null;
  11208. }
  11209. const directionalEncoder = encoder.forKind(segment.kind);
  11210. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
  11211. }
  11212. return encoder.encodedBytes();
  11213. }
  11214. /** Encodes a single value to the ascending index format. */
  11215. encodeSingleElement(value) {
  11216. const encoder = new IndexByteEncoder();
  11217. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* IndexKind.ASCENDING */));
  11218. return encoder.encodedBytes();
  11219. }
  11220. /**
  11221. * Returns an encoded form of the document key that sorts based on the key
  11222. * ordering of the field index.
  11223. */
  11224. encodeDirectionalKey(fieldIndex, documentKey) {
  11225. const encoder = new IndexByteEncoder();
  11226. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(refValue(this.databaseId, documentKey), encoder.forKind(fieldIndexGetKeyOrder(fieldIndex)));
  11227. return encoder.encodedBytes();
  11228. }
  11229. /**
  11230. * Encodes the given field values according to the specification in `target`.
  11231. * For IN queries, a list of possible values is returned.
  11232. */
  11233. encodeValues(fieldIndex, target, values) {
  11234. if (values === null) {
  11235. return [];
  11236. }
  11237. let encoders = [];
  11238. encoders.push(new IndexByteEncoder());
  11239. let valueIdx = 0;
  11240. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  11241. const value = values[valueIdx++];
  11242. for (const encoder of encoders) {
  11243. if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
  11244. encoders = this.expandIndexValues(encoders, segment, value);
  11245. }
  11246. else {
  11247. const directionalEncoder = encoder.forKind(segment.kind);
  11248. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
  11249. }
  11250. }
  11251. }
  11252. return this.getEncodedBytes(encoders);
  11253. }
  11254. /**
  11255. * Encodes the given bounds according to the specification in `target`. For IN
  11256. * queries, a list of possible values is returned.
  11257. */
  11258. encodeBound(fieldIndex, target, bound) {
  11259. return this.encodeValues(fieldIndex, target, bound.position);
  11260. }
  11261. /** Returns the byte representation for the provided encoders. */
  11262. getEncodedBytes(encoders) {
  11263. const result = [];
  11264. for (let i = 0; i < encoders.length; ++i) {
  11265. result[i] = encoders[i].encodedBytes();
  11266. }
  11267. return result;
  11268. }
  11269. /**
  11270. * Creates a separate encoder for each element of an array.
  11271. *
  11272. * The method appends each value to all existing encoders (e.g. filter("a",
  11273. * "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
  11274. * list of new encoders is returned.
  11275. */
  11276. expandIndexValues(encoders, segment, value) {
  11277. const prefixes = [...encoders];
  11278. const results = [];
  11279. for (const arrayElement of value.arrayValue.values || []) {
  11280. for (const prefix of prefixes) {
  11281. const clonedEncoder = new IndexByteEncoder();
  11282. clonedEncoder.seed(prefix.encodedBytes());
  11283. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
  11284. results.push(clonedEncoder);
  11285. }
  11286. }
  11287. return results;
  11288. }
  11289. isInFilter(target, fieldPath) {
  11290. return !!target.filters.find(f => f instanceof FieldFilter &&
  11291. f.field.isEqual(fieldPath) &&
  11292. (f.op === "in" /* Operator.IN */ || f.op === "not-in" /* Operator.NOT_IN */));
  11293. }
  11294. getFieldIndexes(transaction, collectionGroup) {
  11295. const indexes = indexConfigurationStore(transaction);
  11296. const states = indexStateStore(transaction);
  11297. return (collectionGroup
  11298. ? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
  11299. : indexes.loadAll()).next(indexConfigs => {
  11300. const result = [];
  11301. return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
  11302. return states
  11303. .get([indexConfig.indexId, this.uid])
  11304. .next(indexState => {
  11305. result.push(fromDbIndexConfiguration(indexConfig, indexState));
  11306. });
  11307. }).next(() => result);
  11308. });
  11309. }
  11310. getNextCollectionGroupToUpdate(transaction) {
  11311. return this.getFieldIndexes(transaction).next(indexes => {
  11312. if (indexes.length === 0) {
  11313. return null;
  11314. }
  11315. indexes.sort((l, r) => {
  11316. const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
  11317. return cmp !== 0
  11318. ? cmp
  11319. : primitiveComparator(l.collectionGroup, r.collectionGroup);
  11320. });
  11321. return indexes[0].collectionGroup;
  11322. });
  11323. }
  11324. updateCollectionGroup(transaction, collectionGroup, offset) {
  11325. const indexes = indexConfigurationStore(transaction);
  11326. const states = indexStateStore(transaction);
  11327. return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
  11328. .loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
  11329. .next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
  11330. }
  11331. updateIndexEntries(transaction, documents) {
  11332. // Porting Note: `getFieldIndexes()` on Web does not cache index lookups as
  11333. // it could be used across different IndexedDB transactions. As any cached
  11334. // data might be invalidated by other multi-tab clients, we can only trust
  11335. // data within a single IndexedDB transaction. We therefore add a cache
  11336. // here.
  11337. const memoizedIndexes = new Map();
  11338. return PersistencePromise.forEach(documents, (key, doc) => {
  11339. const memoizedCollectionIndexes = memoizedIndexes.get(key.collectionGroup);
  11340. const fieldIndexes = memoizedCollectionIndexes
  11341. ? PersistencePromise.resolve(memoizedCollectionIndexes)
  11342. : this.getFieldIndexes(transaction, key.collectionGroup);
  11343. return fieldIndexes.next(fieldIndexes => {
  11344. memoizedIndexes.set(key.collectionGroup, fieldIndexes);
  11345. return PersistencePromise.forEach(fieldIndexes, (fieldIndex) => {
  11346. return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
  11347. const newEntries = this.computeIndexEntries(doc, fieldIndex);
  11348. if (!existingEntries.isEqual(newEntries)) {
  11349. return this.updateEntries(transaction, doc, fieldIndex, existingEntries, newEntries);
  11350. }
  11351. return PersistencePromise.resolve();
  11352. });
  11353. });
  11354. });
  11355. });
  11356. }
  11357. addIndexEntry(transaction, document, fieldIndex, indexEntry) {
  11358. const indexEntries = indexEntriesStore(transaction);
  11359. return indexEntries.put({
  11360. indexId: indexEntry.indexId,
  11361. uid: this.uid,
  11362. arrayValue: indexEntry.arrayValue,
  11363. directionalValue: indexEntry.directionalValue,
  11364. orderedDocumentKey: this.encodeDirectionalKey(fieldIndex, document.key),
  11365. documentKey: document.key.path.toArray()
  11366. });
  11367. }
  11368. deleteIndexEntry(transaction, document, fieldIndex, indexEntry) {
  11369. const indexEntries = indexEntriesStore(transaction);
  11370. return indexEntries.delete([
  11371. indexEntry.indexId,
  11372. this.uid,
  11373. indexEntry.arrayValue,
  11374. indexEntry.directionalValue,
  11375. this.encodeDirectionalKey(fieldIndex, document.key),
  11376. document.key.path.toArray()
  11377. ]);
  11378. }
  11379. getExistingIndexEntries(transaction, documentKey, fieldIndex) {
  11380. const indexEntries = indexEntriesStore(transaction);
  11381. let results = new SortedSet(indexEntryComparator);
  11382. return indexEntries
  11383. .iterate({
  11384. index: DbIndexEntryDocumentKeyIndex,
  11385. range: IDBKeyRange.only([
  11386. fieldIndex.indexId,
  11387. this.uid,
  11388. this.encodeDirectionalKey(fieldIndex, documentKey)
  11389. ])
  11390. }, (_, entry) => {
  11391. results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
  11392. })
  11393. .next(() => results);
  11394. }
  11395. /** Creates the index entries for the given document. */
  11396. computeIndexEntries(document, fieldIndex) {
  11397. let results = new SortedSet(indexEntryComparator);
  11398. const directionalValue = this.encodeDirectionalElements(fieldIndex, document);
  11399. if (directionalValue == null) {
  11400. return results;
  11401. }
  11402. const arraySegment = fieldIndexGetArraySegment(fieldIndex);
  11403. if (arraySegment != null) {
  11404. const value = document.data.field(arraySegment.fieldPath);
  11405. if (isArray(value)) {
  11406. for (const arrayValue of value.arrayValue.values || []) {
  11407. results = results.add(new IndexEntry(fieldIndex.indexId, document.key, this.encodeSingleElement(arrayValue), directionalValue));
  11408. }
  11409. }
  11410. }
  11411. else {
  11412. results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
  11413. }
  11414. return results;
  11415. }
  11416. /**
  11417. * Updates the index entries for the provided document by deleting entries
  11418. * that are no longer referenced in `newEntries` and adding all newly added
  11419. * entries.
  11420. */
  11421. updateEntries(transaction, document, fieldIndex, existingEntries, newEntries) {
  11422. logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
  11423. const promises = [];
  11424. diffSortedSets(existingEntries, newEntries, indexEntryComparator,
  11425. /* onAdd= */ entry => {
  11426. promises.push(this.addIndexEntry(transaction, document, fieldIndex, entry));
  11427. },
  11428. /* onRemove= */ entry => {
  11429. promises.push(this.deleteIndexEntry(transaction, document, fieldIndex, entry));
  11430. });
  11431. return PersistencePromise.waitFor(promises);
  11432. }
  11433. getNextSequenceNumber(transaction) {
  11434. let nextSequenceNumber = 1;
  11435. const states = indexStateStore(transaction);
  11436. return states
  11437. .iterate({
  11438. index: DbIndexStateSequenceNumberIndex,
  11439. reverse: true,
  11440. range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
  11441. }, (_, state, controller) => {
  11442. controller.done();
  11443. nextSequenceNumber = state.sequenceNumber + 1;
  11444. })
  11445. .next(() => nextSequenceNumber);
  11446. }
  11447. /**
  11448. * Returns a new set of IDB ranges that splits the existing range and excludes
  11449. * any values that match the `notInValue` from these ranges. As an example,
  11450. * '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
  11451. */
  11452. createRange(lower, upper, notInValues) {
  11453. // The notIn values need to be sorted and unique so that we can return a
  11454. // sorted set of non-overlapping ranges.
  11455. notInValues = notInValues
  11456. .sort((l, r) => indexEntryComparator(l, r))
  11457. .filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
  11458. const bounds = [];
  11459. bounds.push(lower);
  11460. for (const notInValue of notInValues) {
  11461. const cmpToLower = indexEntryComparator(notInValue, lower);
  11462. const cmpToUpper = indexEntryComparator(notInValue, upper);
  11463. if (cmpToLower === 0) {
  11464. // `notInValue` is the lower bound. We therefore need to raise the bound
  11465. // to the next value.
  11466. bounds[0] = lower.successor();
  11467. }
  11468. else if (cmpToLower > 0 && cmpToUpper < 0) {
  11469. // `notInValue` is in the middle of the range
  11470. bounds.push(notInValue);
  11471. bounds.push(notInValue.successor());
  11472. }
  11473. else if (cmpToUpper > 0) {
  11474. // `notInValue` (and all following values) are out of the range
  11475. break;
  11476. }
  11477. }
  11478. bounds.push(upper);
  11479. const ranges = [];
  11480. for (let i = 0; i < bounds.length; i += 2) {
  11481. // If we encounter two bounds that will create an unmatchable key range,
  11482. // then we return an empty set of key ranges.
  11483. if (this.isRangeMatchable(bounds[i], bounds[i + 1])) {
  11484. return [];
  11485. }
  11486. const lowerBound = [
  11487. bounds[i].indexId,
  11488. this.uid,
  11489. bounds[i].arrayValue,
  11490. bounds[i].directionalValue,
  11491. EMPTY_VALUE,
  11492. []
  11493. ];
  11494. const upperBound = [
  11495. bounds[i + 1].indexId,
  11496. this.uid,
  11497. bounds[i + 1].arrayValue,
  11498. bounds[i + 1].directionalValue,
  11499. EMPTY_VALUE,
  11500. []
  11501. ];
  11502. ranges.push(IDBKeyRange.bound(lowerBound, upperBound));
  11503. }
  11504. return ranges;
  11505. }
  11506. isRangeMatchable(lowerBound, upperBound) {
  11507. // If lower bound is greater than the upper bound, then the key
  11508. // range can never be matched.
  11509. return indexEntryComparator(lowerBound, upperBound) > 0;
  11510. }
  11511. getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
  11512. return this.getFieldIndexes(transaction, collectionGroup).next(getMinOffsetFromFieldIndexes);
  11513. }
  11514. getMinOffset(transaction, target) {
  11515. return PersistencePromise.mapArray(this.getSubTargets(target), (subTarget) => this.getFieldIndex(transaction, subTarget).next(index => index ? index : fail())).next(getMinOffsetFromFieldIndexes);
  11516. }
  11517. }
  11518. /**
  11519. * Helper to get a typed SimpleDbStore for the collectionParents
  11520. * document store.
  11521. */
  11522. function collectionParentsStore(txn) {
  11523. return getStore(txn, DbCollectionParentStore);
  11524. }
  11525. /**
  11526. * Helper to get a typed SimpleDbStore for the index entry object store.
  11527. */
  11528. function indexEntriesStore(txn) {
  11529. return getStore(txn, DbIndexEntryStore);
  11530. }
  11531. /**
  11532. * Helper to get a typed SimpleDbStore for the index configuration object store.
  11533. */
  11534. function indexConfigurationStore(txn) {
  11535. return getStore(txn, DbIndexConfigurationStore);
  11536. }
  11537. /**
  11538. * Helper to get a typed SimpleDbStore for the index state object store.
  11539. */
  11540. function indexStateStore(txn) {
  11541. return getStore(txn, DbIndexStateStore);
  11542. }
  11543. function getMinOffsetFromFieldIndexes(fieldIndexes) {
  11544. hardAssert(fieldIndexes.length !== 0);
  11545. let minOffset = fieldIndexes[0].indexState.offset;
  11546. let maxBatchId = minOffset.largestBatchId;
  11547. for (let i = 1; i < fieldIndexes.length; i++) {
  11548. const newOffset = fieldIndexes[i].indexState.offset;
  11549. if (indexOffsetComparator(newOffset, minOffset) < 0) {
  11550. minOffset = newOffset;
  11551. }
  11552. if (maxBatchId < newOffset.largestBatchId) {
  11553. maxBatchId = newOffset.largestBatchId;
  11554. }
  11555. }
  11556. return new IndexOffset(minOffset.readTime, minOffset.documentKey, maxBatchId);
  11557. }
  11558. /**
  11559. * @license
  11560. * Copyright 2020 Google LLC
  11561. *
  11562. * Licensed under the Apache License, Version 2.0 (the "License");
  11563. * you may not use this file except in compliance with the License.
  11564. * You may obtain a copy of the License at
  11565. *
  11566. * http://www.apache.org/licenses/LICENSE-2.0
  11567. *
  11568. * Unless required by applicable law or agreed to in writing, software
  11569. * distributed under the License is distributed on an "AS IS" BASIS,
  11570. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11571. * See the License for the specific language governing permissions and
  11572. * limitations under the License.
  11573. */
  11574. /**
  11575. * Delete a mutation batch and the associated document mutations.
  11576. * @returns A PersistencePromise of the document mutations that were removed.
  11577. */
  11578. function removeMutationBatch(txn, userId, batch) {
  11579. const mutationStore = txn.store(DbMutationBatchStore);
  11580. const indexTxn = txn.store(DbDocumentMutationStore);
  11581. const promises = [];
  11582. const range = IDBKeyRange.only(batch.batchId);
  11583. let numDeleted = 0;
  11584. const removePromise = mutationStore.iterate({ range }, (key, value, control) => {
  11585. numDeleted++;
  11586. return control.delete();
  11587. });
  11588. promises.push(removePromise.next(() => {
  11589. hardAssert(numDeleted === 1);
  11590. }));
  11591. const removedDocuments = [];
  11592. for (const mutation of batch.mutations) {
  11593. const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
  11594. promises.push(indexTxn.delete(indexKey));
  11595. removedDocuments.push(mutation.key);
  11596. }
  11597. return PersistencePromise.waitFor(promises).next(() => removedDocuments);
  11598. }
  11599. /**
  11600. * Returns an approximate size for the given document.
  11601. */
  11602. function dbDocumentSize(doc) {
  11603. if (!doc) {
  11604. return 0;
  11605. }
  11606. let value;
  11607. if (doc.document) {
  11608. value = doc.document;
  11609. }
  11610. else if (doc.unknownDocument) {
  11611. value = doc.unknownDocument;
  11612. }
  11613. else if (doc.noDocument) {
  11614. value = doc.noDocument;
  11615. }
  11616. else {
  11617. throw fail();
  11618. }
  11619. return JSON.stringify(value).length;
  11620. }
  11621. /**
  11622. * @license
  11623. * Copyright 2017 Google LLC
  11624. *
  11625. * Licensed under the Apache License, Version 2.0 (the "License");
  11626. * you may not use this file except in compliance with the License.
  11627. * You may obtain a copy of the License at
  11628. *
  11629. * http://www.apache.org/licenses/LICENSE-2.0
  11630. *
  11631. * Unless required by applicable law or agreed to in writing, software
  11632. * distributed under the License is distributed on an "AS IS" BASIS,
  11633. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11634. * See the License for the specific language governing permissions and
  11635. * limitations under the License.
  11636. */
  11637. /** A mutation queue for a specific user, backed by IndexedDB. */
  11638. class IndexedDbMutationQueue {
  11639. constructor(
  11640. /**
  11641. * The normalized userId (e.g. null UID => "" userId) used to store /
  11642. * retrieve mutations.
  11643. */
  11644. userId, serializer, indexManager, referenceDelegate) {
  11645. this.userId = userId;
  11646. this.serializer = serializer;
  11647. this.indexManager = indexManager;
  11648. this.referenceDelegate = referenceDelegate;
  11649. /**
  11650. * Caches the document keys for pending mutation batches. If the mutation
  11651. * has been removed from IndexedDb, the cached value may continue to
  11652. * be used to retrieve the batch's document keys. To remove a cached value
  11653. * locally, `removeCachedMutationKeys()` should be invoked either directly
  11654. * or through `removeMutationBatches()`.
  11655. *
  11656. * With multi-tab, when the primary client acknowledges or rejects a mutation,
  11657. * this cache is used by secondary clients to invalidate the local
  11658. * view of the documents that were previously affected by the mutation.
  11659. */
  11660. // PORTING NOTE: Multi-tab only.
  11661. this.documentKeysByBatchId = {};
  11662. }
  11663. /**
  11664. * Creates a new mutation queue for the given user.
  11665. * @param user - The user for which to create a mutation queue.
  11666. * @param serializer - The serializer to use when persisting to IndexedDb.
  11667. */
  11668. static forUser(user, serializer, indexManager, referenceDelegate) {
  11669. // TODO(mcg): Figure out what constraints there are on userIDs
  11670. // In particular, are there any reserved characters? are empty ids allowed?
  11671. // For the moment store these together in the same mutations table assuming
  11672. // that empty userIDs aren't allowed.
  11673. hardAssert(user.uid !== '');
  11674. const userId = user.isAuthenticated() ? user.uid : '';
  11675. return new IndexedDbMutationQueue(userId, serializer, indexManager, referenceDelegate);
  11676. }
  11677. checkEmpty(transaction) {
  11678. let empty = true;
  11679. const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
  11680. return mutationsStore(transaction)
  11681. .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
  11682. empty = false;
  11683. control.done();
  11684. })
  11685. .next(() => empty);
  11686. }
  11687. addMutationBatch(transaction, localWriteTime, baseMutations, mutations) {
  11688. const documentStore = documentMutationsStore(transaction);
  11689. const mutationStore = mutationsStore(transaction);
  11690. // The IndexedDb implementation in Chrome (and Firefox) does not handle
  11691. // compound indices that include auto-generated keys correctly. To ensure
  11692. // that the index entry is added correctly in all browsers, we perform two
  11693. // writes: The first write is used to retrieve the next auto-generated Batch
  11694. // ID, and the second write populates the index and stores the actual
  11695. // mutation batch.
  11696. // See: https://bugs.chromium.org/p/chromium/issues/detail?id=701972
  11697. // We write an empty object to obtain key
  11698. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  11699. return mutationStore.add({}).next(batchId => {
  11700. hardAssert(typeof batchId === 'number');
  11701. const batch = new MutationBatch(batchId, localWriteTime, baseMutations, mutations);
  11702. const dbBatch = toDbMutationBatch(this.serializer, this.userId, batch);
  11703. const promises = [];
  11704. let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
  11705. for (const mutation of mutations) {
  11706. const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
  11707. collectionParents = collectionParents.add(mutation.key.path.popLast());
  11708. promises.push(mutationStore.put(dbBatch));
  11709. promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
  11710. }
  11711. collectionParents.forEach(parent => {
  11712. promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
  11713. });
  11714. transaction.addOnCommittedListener(() => {
  11715. this.documentKeysByBatchId[batchId] = batch.keys();
  11716. });
  11717. return PersistencePromise.waitFor(promises).next(() => batch);
  11718. });
  11719. }
  11720. lookupMutationBatch(transaction, batchId) {
  11721. return mutationsStore(transaction)
  11722. .get(batchId)
  11723. .next(dbBatch => {
  11724. if (dbBatch) {
  11725. hardAssert(dbBatch.userId === this.userId);
  11726. return fromDbMutationBatch(this.serializer, dbBatch);
  11727. }
  11728. return null;
  11729. });
  11730. }
  11731. /**
  11732. * Returns the document keys for the mutation batch with the given batchId.
  11733. * For primary clients, this method returns `null` after
  11734. * `removeMutationBatches()` has been called. Secondary clients return a
  11735. * cached result until `removeCachedMutationKeys()` is invoked.
  11736. */
  11737. // PORTING NOTE: Multi-tab only.
  11738. lookupMutationKeys(transaction, batchId) {
  11739. if (this.documentKeysByBatchId[batchId]) {
  11740. return PersistencePromise.resolve(this.documentKeysByBatchId[batchId]);
  11741. }
  11742. else {
  11743. return this.lookupMutationBatch(transaction, batchId).next(batch => {
  11744. if (batch) {
  11745. const keys = batch.keys();
  11746. this.documentKeysByBatchId[batchId] = keys;
  11747. return keys;
  11748. }
  11749. else {
  11750. return null;
  11751. }
  11752. });
  11753. }
  11754. }
  11755. getNextMutationBatchAfterBatchId(transaction, batchId) {
  11756. const nextBatchId = batchId + 1;
  11757. const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
  11758. let foundBatch = null;
  11759. return mutationsStore(transaction)
  11760. .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
  11761. if (dbBatch.userId === this.userId) {
  11762. hardAssert(dbBatch.batchId >= nextBatchId);
  11763. foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
  11764. }
  11765. control.done();
  11766. })
  11767. .next(() => foundBatch);
  11768. }
  11769. getHighestUnacknowledgedBatchId(transaction) {
  11770. const range = IDBKeyRange.upperBound([
  11771. this.userId,
  11772. Number.POSITIVE_INFINITY
  11773. ]);
  11774. let batchId = BATCHID_UNKNOWN;
  11775. return mutationsStore(transaction)
  11776. .iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
  11777. batchId = dbBatch.batchId;
  11778. control.done();
  11779. })
  11780. .next(() => batchId);
  11781. }
  11782. getAllMutationBatches(transaction) {
  11783. const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
  11784. return mutationsStore(transaction)
  11785. .loadAll(DbMutationBatchUserMutationsIndex, range)
  11786. .next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
  11787. }
  11788. getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
  11789. // Scan the document-mutation index starting with a prefix starting with
  11790. // the given documentKey.
  11791. const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
  11792. const indexStart = IDBKeyRange.lowerBound(indexPrefix);
  11793. const results = [];
  11794. return documentMutationsStore(transaction)
  11795. .iterate({ range: indexStart }, (indexKey, _, control) => {
  11796. const [userID, encodedPath, batchId] = indexKey;
  11797. // Only consider rows matching exactly the specific key of
  11798. // interest. Note that because we order by path first, and we
  11799. // order terminators before path separators, we'll encounter all
  11800. // the index rows for documentKey contiguously. In particular, all
  11801. // the rows for documentKey will occur before any rows for
  11802. // documents nested in a subcollection beneath documentKey so we
  11803. // can stop as soon as we hit any such row.
  11804. const path = decodeResourcePath(encodedPath);
  11805. if (userID !== this.userId || !documentKey.path.isEqual(path)) {
  11806. control.done();
  11807. return;
  11808. }
  11809. // Look up the mutation batch in the store.
  11810. return mutationsStore(transaction)
  11811. .get(batchId)
  11812. .next(mutation => {
  11813. if (!mutation) {
  11814. throw fail();
  11815. }
  11816. hardAssert(mutation.userId === this.userId);
  11817. results.push(fromDbMutationBatch(this.serializer, mutation));
  11818. });
  11819. })
  11820. .next(() => results);
  11821. }
  11822. getAllMutationBatchesAffectingDocumentKeys(transaction, documentKeys) {
  11823. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  11824. const promises = [];
  11825. documentKeys.forEach(documentKey => {
  11826. const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
  11827. const range = IDBKeyRange.lowerBound(indexStart);
  11828. const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
  11829. const [userID, encodedPath, batchID] = indexKey;
  11830. // Only consider rows matching exactly the specific key of
  11831. // interest. Note that because we order by path first, and we
  11832. // order terminators before path separators, we'll encounter all
  11833. // the index rows for documentKey contiguously. In particular, all
  11834. // the rows for documentKey will occur before any rows for
  11835. // documents nested in a subcollection beneath documentKey so we
  11836. // can stop as soon as we hit any such row.
  11837. const path = decodeResourcePath(encodedPath);
  11838. if (userID !== this.userId || !documentKey.path.isEqual(path)) {
  11839. control.done();
  11840. return;
  11841. }
  11842. uniqueBatchIDs = uniqueBatchIDs.add(batchID);
  11843. });
  11844. promises.push(promise);
  11845. });
  11846. return PersistencePromise.waitFor(promises).next(() => this.lookupMutationBatches(transaction, uniqueBatchIDs));
  11847. }
  11848. getAllMutationBatchesAffectingQuery(transaction, query) {
  11849. const queryPath = query.path;
  11850. const immediateChildrenLength = queryPath.length + 1;
  11851. // TODO(mcg): Actually implement a single-collection query
  11852. //
  11853. // This is actually executing an ancestor query, traversing the whole
  11854. // subtree below the collection which can be horrifically inefficient for
  11855. // some structures. The right way to solve this is to implement the full
  11856. // value index, but that's not in the cards in the near future so this is
  11857. // the best we can do for the moment.
  11858. //
  11859. // Since we don't yet index the actual properties in the mutations, our
  11860. // current approach is to just return all mutation batches that affect
  11861. // documents in the collection being queried.
  11862. const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
  11863. const indexStart = IDBKeyRange.lowerBound(indexPrefix);
  11864. // Collect up unique batchIDs encountered during a scan of the index. Use a
  11865. // SortedSet to accumulate batch IDs so they can be traversed in order in a
  11866. // scan of the main table.
  11867. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  11868. return documentMutationsStore(transaction)
  11869. .iterate({ range: indexStart }, (indexKey, _, control) => {
  11870. const [userID, encodedPath, batchID] = indexKey;
  11871. const path = decodeResourcePath(encodedPath);
  11872. if (userID !== this.userId || !queryPath.isPrefixOf(path)) {
  11873. control.done();
  11874. return;
  11875. }
  11876. // Rows with document keys more than one segment longer than the
  11877. // query path can't be matches. For example, a query on 'rooms'
  11878. // can't match the document /rooms/abc/messages/xyx.
  11879. // TODO(mcg): we'll need a different scanner when we implement
  11880. // ancestor queries.
  11881. if (path.length !== immediateChildrenLength) {
  11882. return;
  11883. }
  11884. uniqueBatchIDs = uniqueBatchIDs.add(batchID);
  11885. })
  11886. .next(() => this.lookupMutationBatches(transaction, uniqueBatchIDs));
  11887. }
  11888. lookupMutationBatches(transaction, batchIDs) {
  11889. const results = [];
  11890. const promises = [];
  11891. // TODO(rockwood): Implement this using iterate.
  11892. batchIDs.forEach(batchId => {
  11893. promises.push(mutationsStore(transaction)
  11894. .get(batchId)
  11895. .next(mutation => {
  11896. if (mutation === null) {
  11897. throw fail();
  11898. }
  11899. hardAssert(mutation.userId === this.userId);
  11900. results.push(fromDbMutationBatch(this.serializer, mutation));
  11901. }));
  11902. });
  11903. return PersistencePromise.waitFor(promises).next(() => results);
  11904. }
  11905. removeMutationBatch(transaction, batch) {
  11906. return removeMutationBatch(transaction.simpleDbTransaction, this.userId, batch).next(removedDocuments => {
  11907. transaction.addOnCommittedListener(() => {
  11908. this.removeCachedMutationKeys(batch.batchId);
  11909. });
  11910. return PersistencePromise.forEach(removedDocuments, (key) => {
  11911. return this.referenceDelegate.markPotentiallyOrphaned(transaction, key);
  11912. });
  11913. });
  11914. }
  11915. /**
  11916. * Clears the cached keys for a mutation batch. This method should be
  11917. * called by secondary clients after they process mutation updates.
  11918. *
  11919. * Note that this method does not have to be called from primary clients as
  11920. * the corresponding cache entries are cleared when an acknowledged or
  11921. * rejected batch is removed from the mutation queue.
  11922. */
  11923. // PORTING NOTE: Multi-tab only
  11924. removeCachedMutationKeys(batchId) {
  11925. delete this.documentKeysByBatchId[batchId];
  11926. }
  11927. performConsistencyCheck(txn) {
  11928. return this.checkEmpty(txn).next(empty => {
  11929. if (!empty) {
  11930. return PersistencePromise.resolve();
  11931. }
  11932. // Verify that there are no entries in the documentMutations index if
  11933. // the queue is empty.
  11934. const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
  11935. const danglingMutationReferences = [];
  11936. return documentMutationsStore(txn)
  11937. .iterate({ range: startRange }, (key, _, control) => {
  11938. const userID = key[0];
  11939. if (userID !== this.userId) {
  11940. control.done();
  11941. return;
  11942. }
  11943. else {
  11944. const path = decodeResourcePath(key[1]);
  11945. danglingMutationReferences.push(path);
  11946. }
  11947. })
  11948. .next(() => {
  11949. hardAssert(danglingMutationReferences.length === 0);
  11950. });
  11951. });
  11952. }
  11953. containsKey(txn, key) {
  11954. return mutationQueueContainsKey(txn, this.userId, key);
  11955. }
  11956. // PORTING NOTE: Multi-tab only (state is held in memory in other clients).
  11957. /** Returns the mutation queue's metadata from IndexedDb. */
  11958. getMutationQueueMetadata(transaction) {
  11959. return mutationQueuesStore(transaction)
  11960. .get(this.userId)
  11961. .next((metadata) => {
  11962. return (metadata || {
  11963. userId: this.userId,
  11964. lastAcknowledgedBatchId: BATCHID_UNKNOWN,
  11965. lastStreamToken: ''
  11966. });
  11967. });
  11968. }
  11969. }
  11970. /**
  11971. * @returns true if the mutation queue for the given user contains a pending
  11972. * mutation for the given key.
  11973. */
  11974. function mutationQueueContainsKey(txn, userId, key) {
  11975. const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
  11976. const encodedPath = indexKey[1];
  11977. const startRange = IDBKeyRange.lowerBound(indexKey);
  11978. let containsKey = false;
  11979. return documentMutationsStore(txn)
  11980. .iterate({ range: startRange, keysOnly: true }, (key, value, control) => {
  11981. const [userID, keyPath, /*batchID*/ _] = key;
  11982. if (userID === userId && keyPath === encodedPath) {
  11983. containsKey = true;
  11984. }
  11985. control.done();
  11986. })
  11987. .next(() => containsKey);
  11988. }
  11989. /** Returns true if any mutation queue contains the given document. */
  11990. function mutationQueuesContainKey(txn, docKey) {
  11991. let found = false;
  11992. return mutationQueuesStore(txn)
  11993. .iterateSerial(userId => {
  11994. return mutationQueueContainsKey(txn, userId, docKey).next(containsKey => {
  11995. if (containsKey) {
  11996. found = true;
  11997. }
  11998. return PersistencePromise.resolve(!containsKey);
  11999. });
  12000. })
  12001. .next(() => found);
  12002. }
  12003. /**
  12004. * Helper to get a typed SimpleDbStore for the mutations object store.
  12005. */
  12006. function mutationsStore(txn) {
  12007. return getStore(txn, DbMutationBatchStore);
  12008. }
  12009. /**
  12010. * Helper to get a typed SimpleDbStore for the mutationQueues object store.
  12011. */
  12012. function documentMutationsStore(txn) {
  12013. return getStore(txn, DbDocumentMutationStore);
  12014. }
  12015. /**
  12016. * Helper to get a typed SimpleDbStore for the mutationQueues object store.
  12017. */
  12018. function mutationQueuesStore(txn) {
  12019. return getStore(txn, DbMutationQueueStore);
  12020. }
  12021. /**
  12022. * @license
  12023. * Copyright 2017 Google LLC
  12024. *
  12025. * Licensed under the Apache License, Version 2.0 (the "License");
  12026. * you may not use this file except in compliance with the License.
  12027. * You may obtain a copy of the License at
  12028. *
  12029. * http://www.apache.org/licenses/LICENSE-2.0
  12030. *
  12031. * Unless required by applicable law or agreed to in writing, software
  12032. * distributed under the License is distributed on an "AS IS" BASIS,
  12033. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12034. * See the License for the specific language governing permissions and
  12035. * limitations under the License.
  12036. */
  12037. /** Offset to ensure non-overlapping target ids. */
  12038. const OFFSET = 2;
  12039. /**
  12040. * Generates monotonically increasing target IDs for sending targets to the
  12041. * watch stream.
  12042. *
  12043. * The client constructs two generators, one for the target cache, and one for
  12044. * for the sync engine (to generate limbo documents targets). These
  12045. * generators produce non-overlapping IDs (by using even and odd IDs
  12046. * respectively).
  12047. *
  12048. * By separating the target ID space, the query cache can generate target IDs
  12049. * that persist across client restarts, while sync engine can independently
  12050. * generate in-memory target IDs that are transient and can be reused after a
  12051. * restart.
  12052. */
  12053. class TargetIdGenerator {
  12054. constructor(lastId) {
  12055. this.lastId = lastId;
  12056. }
  12057. next() {
  12058. this.lastId += OFFSET;
  12059. return this.lastId;
  12060. }
  12061. static forTargetCache() {
  12062. // The target cache generator must return '2' in its first call to `next()`
  12063. // as there is no differentiation in the protocol layer between an unset
  12064. // number and the number '0'. If we were to sent a target with target ID
  12065. // '0', the backend would consider it unset and replace it with its own ID.
  12066. return new TargetIdGenerator(2 - OFFSET);
  12067. }
  12068. static forSyncEngine() {
  12069. // Sync engine assigns target IDs for limbo document detection.
  12070. return new TargetIdGenerator(1 - OFFSET);
  12071. }
  12072. }
  12073. /**
  12074. * @license
  12075. * Copyright 2017 Google LLC
  12076. *
  12077. * Licensed under the Apache License, Version 2.0 (the "License");
  12078. * you may not use this file except in compliance with the License.
  12079. * You may obtain a copy of the License at
  12080. *
  12081. * http://www.apache.org/licenses/LICENSE-2.0
  12082. *
  12083. * Unless required by applicable law or agreed to in writing, software
  12084. * distributed under the License is distributed on an "AS IS" BASIS,
  12085. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12086. * See the License for the specific language governing permissions and
  12087. * limitations under the License.
  12088. */
  12089. class IndexedDbTargetCache {
  12090. constructor(referenceDelegate, serializer) {
  12091. this.referenceDelegate = referenceDelegate;
  12092. this.serializer = serializer;
  12093. }
  12094. // PORTING NOTE: We don't cache global metadata for the target cache, since
  12095. // some of it (in particular `highestTargetId`) can be modified by secondary
  12096. // tabs. We could perhaps be more granular (and e.g. still cache
  12097. // `lastRemoteSnapshotVersion` in memory) but for simplicity we currently go
  12098. // to IndexedDb whenever we need to read metadata. We can revisit if it turns
  12099. // out to have a meaningful performance impact.
  12100. allocateTargetId(transaction) {
  12101. return this.retrieveMetadata(transaction).next(metadata => {
  12102. const targetIdGenerator = new TargetIdGenerator(metadata.highestTargetId);
  12103. metadata.highestTargetId = targetIdGenerator.next();
  12104. return this.saveMetadata(transaction, metadata).next(() => metadata.highestTargetId);
  12105. });
  12106. }
  12107. getLastRemoteSnapshotVersion(transaction) {
  12108. return this.retrieveMetadata(transaction).next(metadata => {
  12109. return SnapshotVersion.fromTimestamp(new Timestamp(metadata.lastRemoteSnapshotVersion.seconds, metadata.lastRemoteSnapshotVersion.nanoseconds));
  12110. });
  12111. }
  12112. getHighestSequenceNumber(transaction) {
  12113. return this.retrieveMetadata(transaction).next(targetGlobal => targetGlobal.highestListenSequenceNumber);
  12114. }
  12115. setTargetsMetadata(transaction, highestListenSequenceNumber, lastRemoteSnapshotVersion) {
  12116. return this.retrieveMetadata(transaction).next(metadata => {
  12117. metadata.highestListenSequenceNumber = highestListenSequenceNumber;
  12118. if (lastRemoteSnapshotVersion) {
  12119. metadata.lastRemoteSnapshotVersion =
  12120. lastRemoteSnapshotVersion.toTimestamp();
  12121. }
  12122. if (highestListenSequenceNumber > metadata.highestListenSequenceNumber) {
  12123. metadata.highestListenSequenceNumber = highestListenSequenceNumber;
  12124. }
  12125. return this.saveMetadata(transaction, metadata);
  12126. });
  12127. }
  12128. addTargetData(transaction, targetData) {
  12129. return this.saveTargetData(transaction, targetData).next(() => {
  12130. return this.retrieveMetadata(transaction).next(metadata => {
  12131. metadata.targetCount += 1;
  12132. this.updateMetadataFromTargetData(targetData, metadata);
  12133. return this.saveMetadata(transaction, metadata);
  12134. });
  12135. });
  12136. }
  12137. updateTargetData(transaction, targetData) {
  12138. return this.saveTargetData(transaction, targetData);
  12139. }
  12140. removeTargetData(transaction, targetData) {
  12141. return this.removeMatchingKeysForTargetId(transaction, targetData.targetId)
  12142. .next(() => targetsStore(transaction).delete(targetData.targetId))
  12143. .next(() => this.retrieveMetadata(transaction))
  12144. .next(metadata => {
  12145. hardAssert(metadata.targetCount > 0);
  12146. metadata.targetCount -= 1;
  12147. return this.saveMetadata(transaction, metadata);
  12148. });
  12149. }
  12150. /**
  12151. * Drops any targets with sequence number less than or equal to the upper bound, excepting those
  12152. * present in `activeTargetIds`. Document associations for the removed targets are also removed.
  12153. * Returns the number of targets removed.
  12154. */
  12155. removeTargets(txn, upperBound, activeTargetIds) {
  12156. let count = 0;
  12157. const promises = [];
  12158. return targetsStore(txn)
  12159. .iterate((key, value) => {
  12160. const targetData = fromDbTarget(value);
  12161. if (targetData.sequenceNumber <= upperBound &&
  12162. activeTargetIds.get(targetData.targetId) === null) {
  12163. count++;
  12164. promises.push(this.removeTargetData(txn, targetData));
  12165. }
  12166. })
  12167. .next(() => PersistencePromise.waitFor(promises))
  12168. .next(() => count);
  12169. }
  12170. /**
  12171. * Call provided function with each `TargetData` that we have cached.
  12172. */
  12173. forEachTarget(txn, f) {
  12174. return targetsStore(txn).iterate((key, value) => {
  12175. const targetData = fromDbTarget(value);
  12176. f(targetData);
  12177. });
  12178. }
  12179. retrieveMetadata(transaction) {
  12180. return globalTargetStore(transaction)
  12181. .get(DbTargetGlobalKey)
  12182. .next(metadata => {
  12183. hardAssert(metadata !== null);
  12184. return metadata;
  12185. });
  12186. }
  12187. saveMetadata(transaction, metadata) {
  12188. return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
  12189. }
  12190. saveTargetData(transaction, targetData) {
  12191. return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
  12192. }
  12193. /**
  12194. * In-place updates the provided metadata to account for values in the given
  12195. * TargetData. Saving is done separately. Returns true if there were any
  12196. * changes to the metadata.
  12197. */
  12198. updateMetadataFromTargetData(targetData, metadata) {
  12199. let updated = false;
  12200. if (targetData.targetId > metadata.highestTargetId) {
  12201. metadata.highestTargetId = targetData.targetId;
  12202. updated = true;
  12203. }
  12204. if (targetData.sequenceNumber > metadata.highestListenSequenceNumber) {
  12205. metadata.highestListenSequenceNumber = targetData.sequenceNumber;
  12206. updated = true;
  12207. }
  12208. return updated;
  12209. }
  12210. getTargetCount(transaction) {
  12211. return this.retrieveMetadata(transaction).next(metadata => metadata.targetCount);
  12212. }
  12213. getTargetData(transaction, target) {
  12214. // Iterating by the canonicalId may yield more than one result because
  12215. // canonicalId values are not required to be unique per target. This query
  12216. // depends on the queryTargets index to be efficient.
  12217. const canonicalId = canonifyTarget(target);
  12218. const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
  12219. let result = null;
  12220. return targetsStore(transaction)
  12221. .iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
  12222. const found = fromDbTarget(value);
  12223. // After finding a potential match, check that the target is
  12224. // actually equal to the requested target.
  12225. if (targetEquals(target, found.target)) {
  12226. result = found;
  12227. control.done();
  12228. }
  12229. })
  12230. .next(() => result);
  12231. }
  12232. addMatchingKeys(txn, keys, targetId) {
  12233. // PORTING NOTE: The reverse index (documentsTargets) is maintained by
  12234. // IndexedDb.
  12235. const promises = [];
  12236. const store = documentTargetStore(txn);
  12237. keys.forEach(key => {
  12238. const path = encodeResourcePath(key.path);
  12239. promises.push(store.put({ targetId, path }));
  12240. promises.push(this.referenceDelegate.addReference(txn, targetId, key));
  12241. });
  12242. return PersistencePromise.waitFor(promises);
  12243. }
  12244. removeMatchingKeys(txn, keys, targetId) {
  12245. // PORTING NOTE: The reverse index (documentsTargets) is maintained by
  12246. // IndexedDb.
  12247. const store = documentTargetStore(txn);
  12248. return PersistencePromise.forEach(keys, (key) => {
  12249. const path = encodeResourcePath(key.path);
  12250. return PersistencePromise.waitFor([
  12251. store.delete([targetId, path]),
  12252. this.referenceDelegate.removeReference(txn, targetId, key)
  12253. ]);
  12254. });
  12255. }
  12256. removeMatchingKeysForTargetId(txn, targetId) {
  12257. const store = documentTargetStore(txn);
  12258. const range = IDBKeyRange.bound([targetId], [targetId + 1],
  12259. /*lowerOpen=*/ false,
  12260. /*upperOpen=*/ true);
  12261. return store.delete(range);
  12262. }
  12263. getMatchingKeysForTargetId(txn, targetId) {
  12264. const range = IDBKeyRange.bound([targetId], [targetId + 1],
  12265. /*lowerOpen=*/ false,
  12266. /*upperOpen=*/ true);
  12267. const store = documentTargetStore(txn);
  12268. let result = documentKeySet();
  12269. return store
  12270. .iterate({ range, keysOnly: true }, (key, _, control) => {
  12271. const path = decodeResourcePath(key[1]);
  12272. const docKey = new DocumentKey(path);
  12273. result = result.add(docKey);
  12274. })
  12275. .next(() => result);
  12276. }
  12277. containsKey(txn, key) {
  12278. const path = encodeResourcePath(key.path);
  12279. const range = IDBKeyRange.bound([path], [immediateSuccessor(path)],
  12280. /*lowerOpen=*/ false,
  12281. /*upperOpen=*/ true);
  12282. let count = 0;
  12283. return documentTargetStore(txn)
  12284. .iterate({
  12285. index: DbTargetDocumentDocumentTargetsIndex,
  12286. keysOnly: true,
  12287. range
  12288. }, ([targetId, path], _, control) => {
  12289. // Having a sentinel row for a document does not count as containing that document;
  12290. // For the target cache, containing the document means the document is part of some
  12291. // target.
  12292. if (targetId !== 0) {
  12293. count++;
  12294. control.done();
  12295. }
  12296. })
  12297. .next(() => count > 0);
  12298. }
  12299. /**
  12300. * Looks up a TargetData entry by target ID.
  12301. *
  12302. * @param targetId - The target ID of the TargetData entry to look up.
  12303. * @returns The cached TargetData entry, or null if the cache has no entry for
  12304. * the target.
  12305. */
  12306. // PORTING NOTE: Multi-tab only.
  12307. getTargetDataForTarget(transaction, targetId) {
  12308. return targetsStore(transaction)
  12309. .get(targetId)
  12310. .next(found => {
  12311. if (found) {
  12312. return fromDbTarget(found);
  12313. }
  12314. else {
  12315. return null;
  12316. }
  12317. });
  12318. }
  12319. }
  12320. /**
  12321. * Helper to get a typed SimpleDbStore for the queries object store.
  12322. */
  12323. function targetsStore(txn) {
  12324. return getStore(txn, DbTargetStore);
  12325. }
  12326. /**
  12327. * Helper to get a typed SimpleDbStore for the target globals object store.
  12328. */
  12329. function globalTargetStore(txn) {
  12330. return getStore(txn, DbTargetGlobalStore);
  12331. }
  12332. /**
  12333. * Helper to get a typed SimpleDbStore for the document target object store.
  12334. */
  12335. function documentTargetStore(txn) {
  12336. return getStore(txn, DbTargetDocumentStore);
  12337. }
  12338. /**
  12339. * @license
  12340. * Copyright 2018 Google LLC
  12341. *
  12342. * Licensed under the Apache License, Version 2.0 (the "License");
  12343. * you may not use this file except in compliance with the License.
  12344. * You may obtain a copy of the License at
  12345. *
  12346. * http://www.apache.org/licenses/LICENSE-2.0
  12347. *
  12348. * Unless required by applicable law or agreed to in writing, software
  12349. * distributed under the License is distributed on an "AS IS" BASIS,
  12350. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12351. * See the License for the specific language governing permissions and
  12352. * limitations under the License.
  12353. */
  12354. const GC_DID_NOT_RUN = {
  12355. didRun: false,
  12356. sequenceNumbersCollected: 0,
  12357. targetsRemoved: 0,
  12358. documentsRemoved: 0
  12359. };
  12360. const LRU_COLLECTION_DISABLED = -1;
  12361. const LRU_DEFAULT_CACHE_SIZE_BYTES = 40 * 1024 * 1024;
  12362. class LruParams {
  12363. constructor(
  12364. // When we attempt to collect, we will only do so if the cache size is greater than this
  12365. // threshold. Passing `COLLECTION_DISABLED` here will cause collection to always be skipped.
  12366. cacheSizeCollectionThreshold,
  12367. // The percentage of sequence numbers that we will attempt to collect
  12368. percentileToCollect,
  12369. // A cap on the total number of sequence numbers that will be collected. This prevents
  12370. // us from collecting a huge number of sequence numbers if the cache has grown very large.
  12371. maximumSequenceNumbersToCollect) {
  12372. this.cacheSizeCollectionThreshold = cacheSizeCollectionThreshold;
  12373. this.percentileToCollect = percentileToCollect;
  12374. this.maximumSequenceNumbersToCollect = maximumSequenceNumbersToCollect;
  12375. }
  12376. static withCacheSize(cacheSize) {
  12377. return new LruParams(cacheSize, LruParams.DEFAULT_COLLECTION_PERCENTILE, LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT);
  12378. }
  12379. }
  12380. LruParams.DEFAULT_COLLECTION_PERCENTILE = 10;
  12381. LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT = 1000;
  12382. LruParams.DEFAULT = new LruParams(LRU_DEFAULT_CACHE_SIZE_BYTES, LruParams.DEFAULT_COLLECTION_PERCENTILE, LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT);
  12383. LruParams.DISABLED = new LruParams(LRU_COLLECTION_DISABLED, 0, 0);
  12384. /**
  12385. * @license
  12386. * Copyright 2020 Google LLC
  12387. *
  12388. * Licensed under the Apache License, Version 2.0 (the "License");
  12389. * you may not use this file except in compliance with the License.
  12390. * You may obtain a copy of the License at
  12391. *
  12392. * http://www.apache.org/licenses/LICENSE-2.0
  12393. *
  12394. * Unless required by applicable law or agreed to in writing, software
  12395. * distributed under the License is distributed on an "AS IS" BASIS,
  12396. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12397. * See the License for the specific language governing permissions and
  12398. * limitations under the License.
  12399. */
  12400. const LOG_TAG$e = 'LruGarbageCollector';
  12401. const LRU_MINIMUM_CACHE_SIZE_BYTES = 1 * 1024 * 1024;
  12402. /** How long we wait to try running LRU GC after SDK initialization. */
  12403. const INITIAL_GC_DELAY_MS = 1 * 60 * 1000;
  12404. /** Minimum amount of time between GC checks, after the first one. */
  12405. const REGULAR_GC_DELAY_MS = 5 * 60 * 1000;
  12406. function bufferEntryComparator([aSequence, aIndex], [bSequence, bIndex]) {
  12407. const seqCmp = primitiveComparator(aSequence, bSequence);
  12408. if (seqCmp === 0) {
  12409. // This order doesn't matter, but we can bias against churn by sorting
  12410. // entries created earlier as less than newer entries.
  12411. return primitiveComparator(aIndex, bIndex);
  12412. }
  12413. else {
  12414. return seqCmp;
  12415. }
  12416. }
  12417. /**
  12418. * Used to calculate the nth sequence number. Keeps a rolling buffer of the
  12419. * lowest n values passed to `addElement`, and finally reports the largest of
  12420. * them in `maxValue`.
  12421. */
  12422. class RollingSequenceNumberBuffer {
  12423. constructor(maxElements) {
  12424. this.maxElements = maxElements;
  12425. this.buffer = new SortedSet(bufferEntryComparator);
  12426. this.previousIndex = 0;
  12427. }
  12428. nextIndex() {
  12429. return ++this.previousIndex;
  12430. }
  12431. addElement(sequenceNumber) {
  12432. const entry = [sequenceNumber, this.nextIndex()];
  12433. if (this.buffer.size < this.maxElements) {
  12434. this.buffer = this.buffer.add(entry);
  12435. }
  12436. else {
  12437. const highestValue = this.buffer.last();
  12438. if (bufferEntryComparator(entry, highestValue) < 0) {
  12439. this.buffer = this.buffer.delete(highestValue).add(entry);
  12440. }
  12441. }
  12442. }
  12443. get maxValue() {
  12444. // Guaranteed to be non-empty. If we decide we are not collecting any
  12445. // sequence numbers, nthSequenceNumber below short-circuits. If we have
  12446. // decided that we are collecting n sequence numbers, it's because n is some
  12447. // percentage of the existing sequence numbers. That means we should never
  12448. // be in a situation where we are collecting sequence numbers but don't
  12449. // actually have any.
  12450. return this.buffer.last()[0];
  12451. }
  12452. }
  12453. /**
  12454. * This class is responsible for the scheduling of LRU garbage collection. It handles checking
  12455. * whether or not GC is enabled, as well as which delay to use before the next run.
  12456. */
  12457. class LruScheduler {
  12458. constructor(garbageCollector, asyncQueue, localStore) {
  12459. this.garbageCollector = garbageCollector;
  12460. this.asyncQueue = asyncQueue;
  12461. this.localStore = localStore;
  12462. this.gcTask = null;
  12463. }
  12464. start() {
  12465. if (this.garbageCollector.params.cacheSizeCollectionThreshold !==
  12466. LRU_COLLECTION_DISABLED) {
  12467. this.scheduleGC(INITIAL_GC_DELAY_MS);
  12468. }
  12469. }
  12470. stop() {
  12471. if (this.gcTask) {
  12472. this.gcTask.cancel();
  12473. this.gcTask = null;
  12474. }
  12475. }
  12476. get started() {
  12477. return this.gcTask !== null;
  12478. }
  12479. scheduleGC(delay) {
  12480. logDebug(LOG_TAG$e, `Garbage collection scheduled in ${delay}ms`);
  12481. this.gcTask = this.asyncQueue.enqueueAfterDelay("lru_garbage_collection" /* TimerId.LruGarbageCollection */, delay, async () => {
  12482. this.gcTask = null;
  12483. try {
  12484. await this.localStore.collectGarbage(this.garbageCollector);
  12485. }
  12486. catch (e) {
  12487. if (isIndexedDbTransactionError(e)) {
  12488. logDebug(LOG_TAG$e, 'Ignoring IndexedDB error during garbage collection: ', e);
  12489. }
  12490. else {
  12491. await ignoreIfPrimaryLeaseLoss(e);
  12492. }
  12493. }
  12494. await this.scheduleGC(REGULAR_GC_DELAY_MS);
  12495. });
  12496. }
  12497. }
  12498. /** Implements the steps for LRU garbage collection. */
  12499. class LruGarbageCollectorImpl {
  12500. constructor(delegate, params) {
  12501. this.delegate = delegate;
  12502. this.params = params;
  12503. }
  12504. calculateTargetCount(txn, percentile) {
  12505. return this.delegate.getSequenceNumberCount(txn).next(targetCount => {
  12506. return Math.floor((percentile / 100.0) * targetCount);
  12507. });
  12508. }
  12509. nthSequenceNumber(txn, n) {
  12510. if (n === 0) {
  12511. return PersistencePromise.resolve(ListenSequence.INVALID);
  12512. }
  12513. const buffer = new RollingSequenceNumberBuffer(n);
  12514. return this.delegate
  12515. .forEachTarget(txn, target => buffer.addElement(target.sequenceNumber))
  12516. .next(() => {
  12517. return this.delegate.forEachOrphanedDocumentSequenceNumber(txn, sequenceNumber => buffer.addElement(sequenceNumber));
  12518. })
  12519. .next(() => buffer.maxValue);
  12520. }
  12521. removeTargets(txn, upperBound, activeTargetIds) {
  12522. return this.delegate.removeTargets(txn, upperBound, activeTargetIds);
  12523. }
  12524. removeOrphanedDocuments(txn, upperBound) {
  12525. return this.delegate.removeOrphanedDocuments(txn, upperBound);
  12526. }
  12527. collect(txn, activeTargetIds) {
  12528. if (this.params.cacheSizeCollectionThreshold === LRU_COLLECTION_DISABLED) {
  12529. logDebug('LruGarbageCollector', 'Garbage collection skipped; disabled');
  12530. return PersistencePromise.resolve(GC_DID_NOT_RUN);
  12531. }
  12532. return this.getCacheSize(txn).next(cacheSize => {
  12533. if (cacheSize < this.params.cacheSizeCollectionThreshold) {
  12534. logDebug('LruGarbageCollector', `Garbage collection skipped; Cache size ${cacheSize} ` +
  12535. `is lower than threshold ${this.params.cacheSizeCollectionThreshold}`);
  12536. return GC_DID_NOT_RUN;
  12537. }
  12538. else {
  12539. return this.runGarbageCollection(txn, activeTargetIds);
  12540. }
  12541. });
  12542. }
  12543. getCacheSize(txn) {
  12544. return this.delegate.getCacheSize(txn);
  12545. }
  12546. runGarbageCollection(txn, activeTargetIds) {
  12547. let upperBoundSequenceNumber;
  12548. let sequenceNumbersToCollect, targetsRemoved;
  12549. // Timestamps for various pieces of the process
  12550. let countedTargetsTs, foundUpperBoundTs, removedTargetsTs, removedDocumentsTs;
  12551. const startTs = Date.now();
  12552. return this.calculateTargetCount(txn, this.params.percentileToCollect)
  12553. .next(sequenceNumbers => {
  12554. // Cap at the configured max
  12555. if (sequenceNumbers > this.params.maximumSequenceNumbersToCollect) {
  12556. logDebug('LruGarbageCollector', 'Capping sequence numbers to collect down ' +
  12557. `to the maximum of ${this.params.maximumSequenceNumbersToCollect} ` +
  12558. `from ${sequenceNumbers}`);
  12559. sequenceNumbersToCollect =
  12560. this.params.maximumSequenceNumbersToCollect;
  12561. }
  12562. else {
  12563. sequenceNumbersToCollect = sequenceNumbers;
  12564. }
  12565. countedTargetsTs = Date.now();
  12566. return this.nthSequenceNumber(txn, sequenceNumbersToCollect);
  12567. })
  12568. .next(upperBound => {
  12569. upperBoundSequenceNumber = upperBound;
  12570. foundUpperBoundTs = Date.now();
  12571. return this.removeTargets(txn, upperBoundSequenceNumber, activeTargetIds);
  12572. })
  12573. .next(numTargetsRemoved => {
  12574. targetsRemoved = numTargetsRemoved;
  12575. removedTargetsTs = Date.now();
  12576. return this.removeOrphanedDocuments(txn, upperBoundSequenceNumber);
  12577. })
  12578. .next(documentsRemoved => {
  12579. removedDocumentsTs = Date.now();
  12580. if (getLogLevel() <= logger.LogLevel.DEBUG) {
  12581. const desc = 'LRU Garbage Collection\n' +
  12582. `\tCounted targets in ${countedTargetsTs - startTs}ms\n` +
  12583. `\tDetermined least recently used ${sequenceNumbersToCollect} in ` +
  12584. `${foundUpperBoundTs - countedTargetsTs}ms\n` +
  12585. `\tRemoved ${targetsRemoved} targets in ` +
  12586. `${removedTargetsTs - foundUpperBoundTs}ms\n` +
  12587. `\tRemoved ${documentsRemoved} documents in ` +
  12588. `${removedDocumentsTs - removedTargetsTs}ms\n` +
  12589. `Total Duration: ${removedDocumentsTs - startTs}ms`;
  12590. logDebug('LruGarbageCollector', desc);
  12591. }
  12592. return PersistencePromise.resolve({
  12593. didRun: true,
  12594. sequenceNumbersCollected: sequenceNumbersToCollect,
  12595. targetsRemoved,
  12596. documentsRemoved
  12597. });
  12598. });
  12599. }
  12600. }
  12601. function newLruGarbageCollector(delegate, params) {
  12602. return new LruGarbageCollectorImpl(delegate, params);
  12603. }
  12604. /**
  12605. * @license
  12606. * Copyright 2020 Google LLC
  12607. *
  12608. * Licensed under the Apache License, Version 2.0 (the "License");
  12609. * you may not use this file except in compliance with the License.
  12610. * You may obtain a copy of the License at
  12611. *
  12612. * http://www.apache.org/licenses/LICENSE-2.0
  12613. *
  12614. * Unless required by applicable law or agreed to in writing, software
  12615. * distributed under the License is distributed on an "AS IS" BASIS,
  12616. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12617. * See the License for the specific language governing permissions and
  12618. * limitations under the License.
  12619. */
  12620. /** Provides LRU functionality for IndexedDB persistence. */
  12621. class IndexedDbLruDelegateImpl {
  12622. constructor(db, params) {
  12623. this.db = db;
  12624. this.garbageCollector = newLruGarbageCollector(this, params);
  12625. }
  12626. getSequenceNumberCount(txn) {
  12627. const docCountPromise = this.orphanedDocumentCount(txn);
  12628. const targetCountPromise = this.db.getTargetCache().getTargetCount(txn);
  12629. return targetCountPromise.next(targetCount => docCountPromise.next(docCount => targetCount + docCount));
  12630. }
  12631. orphanedDocumentCount(txn) {
  12632. let orphanedCount = 0;
  12633. return this.forEachOrphanedDocumentSequenceNumber(txn, _ => {
  12634. orphanedCount++;
  12635. }).next(() => orphanedCount);
  12636. }
  12637. forEachTarget(txn, f) {
  12638. return this.db.getTargetCache().forEachTarget(txn, f);
  12639. }
  12640. forEachOrphanedDocumentSequenceNumber(txn, f) {
  12641. return this.forEachOrphanedDocument(txn, (docKey, sequenceNumber) => f(sequenceNumber));
  12642. }
  12643. addReference(txn, targetId, key) {
  12644. return writeSentinelKey(txn, key);
  12645. }
  12646. removeReference(txn, targetId, key) {
  12647. return writeSentinelKey(txn, key);
  12648. }
  12649. removeTargets(txn, upperBound, activeTargetIds) {
  12650. return this.db.getTargetCache().removeTargets(txn, upperBound, activeTargetIds);
  12651. }
  12652. markPotentiallyOrphaned(txn, key) {
  12653. return writeSentinelKey(txn, key);
  12654. }
  12655. /**
  12656. * Returns true if anything would prevent this document from being garbage
  12657. * collected, given that the document in question is not present in any
  12658. * targets and has a sequence number less than or equal to the upper bound for
  12659. * the collection run.
  12660. */
  12661. isPinned(txn, docKey) {
  12662. return mutationQueuesContainKey(txn, docKey);
  12663. }
  12664. removeOrphanedDocuments(txn, upperBound) {
  12665. const documentCache = this.db.getRemoteDocumentCache();
  12666. const changeBuffer = documentCache.newChangeBuffer();
  12667. const promises = [];
  12668. let documentCount = 0;
  12669. const iteration = this.forEachOrphanedDocument(txn, (docKey, sequenceNumber) => {
  12670. if (sequenceNumber <= upperBound) {
  12671. const p = this.isPinned(txn, docKey).next(isPinned => {
  12672. if (!isPinned) {
  12673. documentCount++;
  12674. // Our size accounting requires us to read all documents before
  12675. // removing them.
  12676. return changeBuffer.getEntry(txn, docKey).next(() => {
  12677. changeBuffer.removeEntry(docKey, SnapshotVersion.min());
  12678. return documentTargetStore(txn).delete(sentinelKey$1(docKey));
  12679. });
  12680. }
  12681. });
  12682. promises.push(p);
  12683. }
  12684. });
  12685. return iteration
  12686. .next(() => PersistencePromise.waitFor(promises))
  12687. .next(() => changeBuffer.apply(txn))
  12688. .next(() => documentCount);
  12689. }
  12690. removeTarget(txn, targetData) {
  12691. const updated = targetData.withSequenceNumber(txn.currentSequenceNumber);
  12692. return this.db.getTargetCache().updateTargetData(txn, updated);
  12693. }
  12694. updateLimboDocument(txn, key) {
  12695. return writeSentinelKey(txn, key);
  12696. }
  12697. /**
  12698. * Call provided function for each document in the cache that is 'orphaned'. Orphaned
  12699. * means not a part of any target, so the only entry in the target-document index for
  12700. * that document will be the sentinel row (targetId 0), which will also have the sequence
  12701. * number for the last time the document was accessed.
  12702. */
  12703. forEachOrphanedDocument(txn, f) {
  12704. const store = documentTargetStore(txn);
  12705. let nextToReport = ListenSequence.INVALID;
  12706. let nextPath;
  12707. return store
  12708. .iterate({
  12709. index: DbTargetDocumentDocumentTargetsIndex
  12710. }, ([targetId, docKey], { path, sequenceNumber }) => {
  12711. if (targetId === 0) {
  12712. // if nextToReport is valid, report it, this is a new key so the
  12713. // last one must not be a member of any targets.
  12714. if (nextToReport !== ListenSequence.INVALID) {
  12715. f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
  12716. }
  12717. // set nextToReport to be this sequence number. It's the next one we
  12718. // might report, if we don't find any targets for this document.
  12719. // Note that the sequence number must be defined when the targetId
  12720. // is 0.
  12721. nextToReport = sequenceNumber;
  12722. nextPath = path;
  12723. }
  12724. else {
  12725. // set nextToReport to be invalid, we know we don't need to report
  12726. // this one since we found a target for it.
  12727. nextToReport = ListenSequence.INVALID;
  12728. }
  12729. })
  12730. .next(() => {
  12731. // Since we report sequence numbers after getting to the next key, we
  12732. // need to check if the last key we iterated over was an orphaned
  12733. // document and report it.
  12734. if (nextToReport !== ListenSequence.INVALID) {
  12735. f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
  12736. }
  12737. });
  12738. }
  12739. getCacheSize(txn) {
  12740. return this.db.getRemoteDocumentCache().getSize(txn);
  12741. }
  12742. }
  12743. function sentinelKey$1(key) {
  12744. return [0, encodeResourcePath(key.path)];
  12745. }
  12746. /**
  12747. * @returns A value suitable for writing a sentinel row in the target-document
  12748. * store.
  12749. */
  12750. function sentinelRow(key, sequenceNumber) {
  12751. return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
  12752. }
  12753. function writeSentinelKey(txn, key) {
  12754. return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
  12755. }
  12756. /**
  12757. * @license
  12758. * Copyright 2017 Google LLC
  12759. *
  12760. * Licensed under the Apache License, Version 2.0 (the "License");
  12761. * you may not use this file except in compliance with the License.
  12762. * You may obtain a copy of the License at
  12763. *
  12764. * http://www.apache.org/licenses/LICENSE-2.0
  12765. *
  12766. * Unless required by applicable law or agreed to in writing, software
  12767. * distributed under the License is distributed on an "AS IS" BASIS,
  12768. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12769. * See the License for the specific language governing permissions and
  12770. * limitations under the License.
  12771. */
  12772. /**
  12773. * An in-memory buffer of entries to be written to a RemoteDocumentCache.
  12774. * It can be used to batch up a set of changes to be written to the cache, but
  12775. * additionally supports reading entries back with the `getEntry()` method,
  12776. * falling back to the underlying RemoteDocumentCache if no entry is
  12777. * buffered.
  12778. *
  12779. * Entries added to the cache *must* be read first. This is to facilitate
  12780. * calculating the size delta of the pending changes.
  12781. *
  12782. * PORTING NOTE: This class was implemented then removed from other platforms.
  12783. * If byte-counting ends up being needed on the other platforms, consider
  12784. * porting this class as part of that implementation work.
  12785. */
  12786. class RemoteDocumentChangeBuffer {
  12787. constructor() {
  12788. // A mapping of document key to the new cache entry that should be written.
  12789. this.changes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
  12790. this.changesApplied = false;
  12791. }
  12792. /**
  12793. * Buffers a `RemoteDocumentCache.addEntry()` call.
  12794. *
  12795. * You can only modify documents that have already been retrieved via
  12796. * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
  12797. */
  12798. addEntry(document) {
  12799. this.assertNotApplied();
  12800. this.changes.set(document.key, document);
  12801. }
  12802. /**
  12803. * Buffers a `RemoteDocumentCache.removeEntry()` call.
  12804. *
  12805. * You can only remove documents that have already been retrieved via
  12806. * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
  12807. */
  12808. removeEntry(key, readTime) {
  12809. this.assertNotApplied();
  12810. this.changes.set(key, MutableDocument.newInvalidDocument(key).setReadTime(readTime));
  12811. }
  12812. /**
  12813. * Looks up an entry in the cache. The buffered changes will first be checked,
  12814. * and if no buffered change applies, this will forward to
  12815. * `RemoteDocumentCache.getEntry()`.
  12816. *
  12817. * @param transaction - The transaction in which to perform any persistence
  12818. * operations.
  12819. * @param documentKey - The key of the entry to look up.
  12820. * @returns The cached document or an invalid document if we have nothing
  12821. * cached.
  12822. */
  12823. getEntry(transaction, documentKey) {
  12824. this.assertNotApplied();
  12825. const bufferedEntry = this.changes.get(documentKey);
  12826. if (bufferedEntry !== undefined) {
  12827. return PersistencePromise.resolve(bufferedEntry);
  12828. }
  12829. else {
  12830. return this.getFromCache(transaction, documentKey);
  12831. }
  12832. }
  12833. /**
  12834. * Looks up several entries in the cache, forwarding to
  12835. * `RemoteDocumentCache.getEntry()`.
  12836. *
  12837. * @param transaction - The transaction in which to perform any persistence
  12838. * operations.
  12839. * @param documentKeys - The keys of the entries to look up.
  12840. * @returns A map of cached documents, indexed by key. If an entry cannot be
  12841. * found, the corresponding key will be mapped to an invalid document.
  12842. */
  12843. getEntries(transaction, documentKeys) {
  12844. return this.getAllFromCache(transaction, documentKeys);
  12845. }
  12846. /**
  12847. * Applies buffered changes to the underlying RemoteDocumentCache, using
  12848. * the provided transaction.
  12849. */
  12850. apply(transaction) {
  12851. this.assertNotApplied();
  12852. this.changesApplied = true;
  12853. return this.applyChanges(transaction);
  12854. }
  12855. /** Helper to assert this.changes is not null */
  12856. assertNotApplied() {
  12857. }
  12858. }
  12859. /**
  12860. * @license
  12861. * Copyright 2017 Google LLC
  12862. *
  12863. * Licensed under the Apache License, Version 2.0 (the "License");
  12864. * you may not use this file except in compliance with the License.
  12865. * You may obtain a copy of the License at
  12866. *
  12867. * http://www.apache.org/licenses/LICENSE-2.0
  12868. *
  12869. * Unless required by applicable law or agreed to in writing, software
  12870. * distributed under the License is distributed on an "AS IS" BASIS,
  12871. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12872. * See the License for the specific language governing permissions and
  12873. * limitations under the License.
  12874. */
  12875. /**
  12876. * The RemoteDocumentCache for IndexedDb. To construct, invoke
  12877. * `newIndexedDbRemoteDocumentCache()`.
  12878. */
  12879. class IndexedDbRemoteDocumentCacheImpl {
  12880. constructor(serializer) {
  12881. this.serializer = serializer;
  12882. }
  12883. setIndexManager(indexManager) {
  12884. this.indexManager = indexManager;
  12885. }
  12886. /**
  12887. * Adds the supplied entries to the cache.
  12888. *
  12889. * All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer
  12890. * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
  12891. */
  12892. addEntry(transaction, key, doc) {
  12893. const documentStore = remoteDocumentsStore(transaction);
  12894. return documentStore.put(doc);
  12895. }
  12896. /**
  12897. * Removes a document from the cache.
  12898. *
  12899. * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
  12900. * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
  12901. */
  12902. removeEntry(transaction, documentKey, readTime) {
  12903. const store = remoteDocumentsStore(transaction);
  12904. return store.delete(dbReadTimeKey(documentKey, readTime));
  12905. }
  12906. /**
  12907. * Updates the current cache size.
  12908. *
  12909. * Callers to `addEntry()` and `removeEntry()` *must* call this afterwards to update the
  12910. * cache's metadata.
  12911. */
  12912. updateMetadata(transaction, sizeDelta) {
  12913. return this.getMetadata(transaction).next(metadata => {
  12914. metadata.byteSize += sizeDelta;
  12915. return this.setMetadata(transaction, metadata);
  12916. });
  12917. }
  12918. getEntry(transaction, documentKey) {
  12919. let doc = MutableDocument.newInvalidDocument(documentKey);
  12920. return remoteDocumentsStore(transaction)
  12921. .iterate({
  12922. index: DbRemoteDocumentDocumentKeyIndex,
  12923. range: IDBKeyRange.only(dbKey(documentKey))
  12924. }, (_, dbRemoteDoc) => {
  12925. doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
  12926. })
  12927. .next(() => doc);
  12928. }
  12929. /**
  12930. * Looks up an entry in the cache.
  12931. *
  12932. * @param documentKey - The key of the entry to look up.
  12933. * @returns The cached document entry and its size.
  12934. */
  12935. getSizedEntry(transaction, documentKey) {
  12936. let result = {
  12937. size: 0,
  12938. document: MutableDocument.newInvalidDocument(documentKey)
  12939. };
  12940. return remoteDocumentsStore(transaction)
  12941. .iterate({
  12942. index: DbRemoteDocumentDocumentKeyIndex,
  12943. range: IDBKeyRange.only(dbKey(documentKey))
  12944. }, (_, dbRemoteDoc) => {
  12945. result = {
  12946. document: this.maybeDecodeDocument(documentKey, dbRemoteDoc),
  12947. size: dbDocumentSize(dbRemoteDoc)
  12948. };
  12949. })
  12950. .next(() => result);
  12951. }
  12952. getEntries(transaction, documentKeys) {
  12953. let results = mutableDocumentMap();
  12954. return this.forEachDbEntry(transaction, documentKeys, (key, dbRemoteDoc) => {
  12955. const doc = this.maybeDecodeDocument(key, dbRemoteDoc);
  12956. results = results.insert(key, doc);
  12957. }).next(() => results);
  12958. }
  12959. /**
  12960. * Looks up several entries in the cache.
  12961. *
  12962. * @param documentKeys - The set of keys entries to look up.
  12963. * @returns A map of documents indexed by key and a map of sizes indexed by
  12964. * key (zero if the document does not exist).
  12965. */
  12966. getSizedEntries(transaction, documentKeys) {
  12967. let results = mutableDocumentMap();
  12968. let sizeMap = new SortedMap(DocumentKey.comparator);
  12969. return this.forEachDbEntry(transaction, documentKeys, (key, dbRemoteDoc) => {
  12970. const doc = this.maybeDecodeDocument(key, dbRemoteDoc);
  12971. results = results.insert(key, doc);
  12972. sizeMap = sizeMap.insert(key, dbDocumentSize(dbRemoteDoc));
  12973. }).next(() => {
  12974. return { documents: results, sizeMap };
  12975. });
  12976. }
  12977. forEachDbEntry(transaction, documentKeys, callback) {
  12978. if (documentKeys.isEmpty()) {
  12979. return PersistencePromise.resolve();
  12980. }
  12981. let sortedKeys = new SortedSet(dbKeyComparator);
  12982. documentKeys.forEach(e => (sortedKeys = sortedKeys.add(e)));
  12983. const range = IDBKeyRange.bound(dbKey(sortedKeys.first()), dbKey(sortedKeys.last()));
  12984. const keyIter = sortedKeys.getIterator();
  12985. let nextKey = keyIter.getNext();
  12986. return remoteDocumentsStore(transaction)
  12987. .iterate({ index: DbRemoteDocumentDocumentKeyIndex, range }, (_, dbRemoteDoc, control) => {
  12988. const potentialKey = DocumentKey.fromSegments([
  12989. ...dbRemoteDoc.prefixPath,
  12990. dbRemoteDoc.collectionGroup,
  12991. dbRemoteDoc.documentId
  12992. ]);
  12993. // Go through keys not found in cache.
  12994. while (nextKey && dbKeyComparator(nextKey, potentialKey) < 0) {
  12995. callback(nextKey, null);
  12996. nextKey = keyIter.getNext();
  12997. }
  12998. if (nextKey && nextKey.isEqual(potentialKey)) {
  12999. // Key found in cache.
  13000. callback(nextKey, dbRemoteDoc);
  13001. nextKey = keyIter.hasNext() ? keyIter.getNext() : null;
  13002. }
  13003. // Skip to the next key (if there is one).
  13004. if (nextKey) {
  13005. control.skip(dbKey(nextKey));
  13006. }
  13007. else {
  13008. control.done();
  13009. }
  13010. })
  13011. .next(() => {
  13012. // The rest of the keys are not in the cache. One case where `iterate`
  13013. // above won't go through them is when the cache is empty.
  13014. while (nextKey) {
  13015. callback(nextKey, null);
  13016. nextKey = keyIter.hasNext() ? keyIter.getNext() : null;
  13017. }
  13018. });
  13019. }
  13020. getAllFromCollection(transaction, collection, offset) {
  13021. const startKey = [
  13022. collection.popLast().toArray(),
  13023. collection.lastSegment(),
  13024. toDbTimestampKey(offset.readTime),
  13025. offset.documentKey.path.isEmpty()
  13026. ? ''
  13027. : offset.documentKey.path.lastSegment()
  13028. ];
  13029. const endKey = [
  13030. collection.popLast().toArray(),
  13031. collection.lastSegment(),
  13032. [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
  13033. ''
  13034. ];
  13035. return remoteDocumentsStore(transaction)
  13036. .loadAll(IDBKeyRange.bound(startKey, endKey, true))
  13037. .next(dbRemoteDocs => {
  13038. let results = mutableDocumentMap();
  13039. for (const dbRemoteDoc of dbRemoteDocs) {
  13040. const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
  13041. results = results.insert(document.key, document);
  13042. }
  13043. return results;
  13044. });
  13045. }
  13046. getAllFromCollectionGroup(transaction, collectionGroup, offset, limit) {
  13047. let results = mutableDocumentMap();
  13048. const startKey = dbCollectionGroupKey(collectionGroup, offset);
  13049. const endKey = dbCollectionGroupKey(collectionGroup, IndexOffset.max());
  13050. return remoteDocumentsStore(transaction)
  13051. .iterate({
  13052. index: DbRemoteDocumentCollectionGroupIndex,
  13053. range: IDBKeyRange.bound(startKey, endKey, true)
  13054. }, (_, dbRemoteDoc, control) => {
  13055. const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
  13056. results = results.insert(document.key, document);
  13057. if (results.size === limit) {
  13058. control.done();
  13059. }
  13060. })
  13061. .next(() => results);
  13062. }
  13063. newChangeBuffer(options) {
  13064. return new IndexedDbRemoteDocumentChangeBuffer(this, !!options && options.trackRemovals);
  13065. }
  13066. getSize(txn) {
  13067. return this.getMetadata(txn).next(metadata => metadata.byteSize);
  13068. }
  13069. getMetadata(txn) {
  13070. return documentGlobalStore(txn)
  13071. .get(DbRemoteDocumentGlobalKey)
  13072. .next(metadata => {
  13073. hardAssert(!!metadata);
  13074. return metadata;
  13075. });
  13076. }
  13077. setMetadata(txn, metadata) {
  13078. return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
  13079. }
  13080. /**
  13081. * Decodes `dbRemoteDoc` and returns the document (or an invalid document if
  13082. * the document corresponds to the format used for sentinel deletes).
  13083. */
  13084. maybeDecodeDocument(documentKey, dbRemoteDoc) {
  13085. if (dbRemoteDoc) {
  13086. const doc = fromDbRemoteDocument(this.serializer, dbRemoteDoc);
  13087. // Whether the document is a sentinel removal and should only be used in the
  13088. // `getNewDocumentChanges()`
  13089. const isSentinelRemoval = doc.isNoDocument() && doc.version.isEqual(SnapshotVersion.min());
  13090. if (!isSentinelRemoval) {
  13091. return doc;
  13092. }
  13093. }
  13094. return MutableDocument.newInvalidDocument(documentKey);
  13095. }
  13096. }
  13097. /** Creates a new IndexedDbRemoteDocumentCache. */
  13098. function newIndexedDbRemoteDocumentCache(serializer) {
  13099. return new IndexedDbRemoteDocumentCacheImpl(serializer);
  13100. }
  13101. /**
  13102. * Handles the details of adding and updating documents in the IndexedDbRemoteDocumentCache.
  13103. *
  13104. * Unlike the MemoryRemoteDocumentChangeBuffer, the IndexedDb implementation computes the size
  13105. * delta for all submitted changes. This avoids having to re-read all documents from IndexedDb
  13106. * when we apply the changes.
  13107. */
  13108. class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
  13109. /**
  13110. * @param documentCache - The IndexedDbRemoteDocumentCache to apply the changes to.
  13111. * @param trackRemovals - Whether to create sentinel deletes that can be tracked by
  13112. * `getNewDocumentChanges()`.
  13113. */
  13114. constructor(documentCache, trackRemovals) {
  13115. super();
  13116. this.documentCache = documentCache;
  13117. this.trackRemovals = trackRemovals;
  13118. // A map of document sizes and read times prior to applying the changes in
  13119. // this buffer.
  13120. this.documentStates = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
  13121. }
  13122. applyChanges(transaction) {
  13123. const promises = [];
  13124. let sizeDelta = 0;
  13125. let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
  13126. this.changes.forEach((key, documentChange) => {
  13127. const previousDoc = this.documentStates.get(key);
  13128. promises.push(this.documentCache.removeEntry(transaction, key, previousDoc.readTime));
  13129. if (documentChange.isValidDocument()) {
  13130. const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
  13131. collectionParents = collectionParents.add(key.path.popLast());
  13132. const size = dbDocumentSize(doc);
  13133. sizeDelta += size - previousDoc.size;
  13134. promises.push(this.documentCache.addEntry(transaction, key, doc));
  13135. }
  13136. else {
  13137. sizeDelta -= previousDoc.size;
  13138. if (this.trackRemovals) {
  13139. // In order to track removals, we store a "sentinel delete" in the
  13140. // RemoteDocumentCache. This entry is represented by a NoDocument
  13141. // with a version of 0 and ignored by `maybeDecodeDocument()` but
  13142. // preserved in `getNewDocumentChanges()`.
  13143. const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
  13144. promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
  13145. }
  13146. }
  13147. });
  13148. collectionParents.forEach(parent => {
  13149. promises.push(this.documentCache.indexManager.addToCollectionParentIndex(transaction, parent));
  13150. });
  13151. promises.push(this.documentCache.updateMetadata(transaction, sizeDelta));
  13152. return PersistencePromise.waitFor(promises);
  13153. }
  13154. getFromCache(transaction, documentKey) {
  13155. // Record the size of everything we load from the cache so we can compute a delta later.
  13156. return this.documentCache
  13157. .getSizedEntry(transaction, documentKey)
  13158. .next(getResult => {
  13159. this.documentStates.set(documentKey, {
  13160. size: getResult.size,
  13161. readTime: getResult.document.readTime
  13162. });
  13163. return getResult.document;
  13164. });
  13165. }
  13166. getAllFromCache(transaction, documentKeys) {
  13167. // Record the size of everything we load from the cache so we can compute
  13168. // a delta later.
  13169. return this.documentCache
  13170. .getSizedEntries(transaction, documentKeys)
  13171. .next(({ documents, sizeMap }) => {
  13172. // Note: `getAllFromCache` returns two maps instead of a single map from
  13173. // keys to `DocumentSizeEntry`s. This is to allow returning the
  13174. // `MutableDocumentMap` directly, without a conversion.
  13175. sizeMap.forEach((documentKey, size) => {
  13176. this.documentStates.set(documentKey, {
  13177. size,
  13178. readTime: documents.get(documentKey).readTime
  13179. });
  13180. });
  13181. return documents;
  13182. });
  13183. }
  13184. }
  13185. function documentGlobalStore(txn) {
  13186. return getStore(txn, DbRemoteDocumentGlobalStore);
  13187. }
  13188. /**
  13189. * Helper to get a typed SimpleDbStore for the remoteDocuments object store.
  13190. */
  13191. function remoteDocumentsStore(txn) {
  13192. return getStore(txn, DbRemoteDocumentStore);
  13193. }
  13194. /**
  13195. * Returns a key that can be used for document lookups on the
  13196. * `DbRemoteDocumentDocumentKeyIndex` index.
  13197. */
  13198. function dbKey(documentKey) {
  13199. const path = documentKey.path.toArray();
  13200. return [
  13201. /* prefix path */ path.slice(0, path.length - 2),
  13202. /* collection id */ path[path.length - 2],
  13203. /* document id */ path[path.length - 1]
  13204. ];
  13205. }
  13206. /**
  13207. * Returns a key that can be used for document lookups via the primary key of
  13208. * the DbRemoteDocument object store.
  13209. */
  13210. function dbReadTimeKey(documentKey, readTime) {
  13211. const path = documentKey.path.toArray();
  13212. return [
  13213. /* prefix path */ path.slice(0, path.length - 2),
  13214. /* collection id */ path[path.length - 2],
  13215. toDbTimestampKey(readTime),
  13216. /* document id */ path[path.length - 1]
  13217. ];
  13218. }
  13219. /**
  13220. * Returns a key that can be used for document lookups on the
  13221. * `DbRemoteDocumentDocumentCollectionGroupIndex` index.
  13222. */
  13223. function dbCollectionGroupKey(collectionGroup, offset) {
  13224. const path = offset.documentKey.path.toArray();
  13225. return [
  13226. /* collection id */ collectionGroup,
  13227. toDbTimestampKey(offset.readTime),
  13228. /* prefix path */ path.slice(0, path.length - 2),
  13229. /* document id */ path.length > 0 ? path[path.length - 1] : ''
  13230. ];
  13231. }
  13232. /**
  13233. * Comparator that compares document keys according to the primary key sorting
  13234. * used by the `DbRemoteDocumentDocument` store (by prefix path, collection id
  13235. * and then document ID).
  13236. *
  13237. * Visible for testing.
  13238. */
  13239. function dbKeyComparator(l, r) {
  13240. const left = l.path.toArray();
  13241. const right = r.path.toArray();
  13242. // The ordering is based on https://chromium.googlesource.com/chromium/blink/+/fe5c21fef94dae71c1c3344775b8d8a7f7e6d9ec/Source/modules/indexeddb/IDBKey.cpp#74
  13243. let cmp = 0;
  13244. for (let i = 0; i < left.length - 2 && i < right.length - 2; ++i) {
  13245. cmp = primitiveComparator(left[i], right[i]);
  13246. if (cmp) {
  13247. return cmp;
  13248. }
  13249. }
  13250. cmp = primitiveComparator(left.length, right.length);
  13251. if (cmp) {
  13252. return cmp;
  13253. }
  13254. cmp = primitiveComparator(left[left.length - 2], right[right.length - 2]);
  13255. if (cmp) {
  13256. return cmp;
  13257. }
  13258. return primitiveComparator(left[left.length - 1], right[right.length - 1]);
  13259. }
  13260. /**
  13261. * @license
  13262. * Copyright 2017 Google LLC
  13263. *
  13264. * Licensed under the Apache License, Version 2.0 (the "License");
  13265. * you may not use this file except in compliance with the License.
  13266. * You may obtain a copy of the License at
  13267. *
  13268. * http://www.apache.org/licenses/LICENSE-2.0
  13269. *
  13270. * Unless required by applicable law or agreed to in writing, software
  13271. * distributed under the License is distributed on an "AS IS" BASIS,
  13272. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13273. * See the License for the specific language governing permissions and
  13274. * limitations under the License.
  13275. */
  13276. /**
  13277. * Schema Version for the Web client:
  13278. * 1. Initial version including Mutation Queue, Query Cache, and Remote
  13279. * Document Cache
  13280. * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
  13281. * longer required because migration 3 unconditionally clears it.
  13282. * 3. Dropped and re-created Query Cache to deal with cache corruption related
  13283. * to limbo resolution. Addresses
  13284. * https://github.com/firebase/firebase-ios-sdk/issues/1548
  13285. * 4. Multi-Tab Support.
  13286. * 5. Removal of held write acks.
  13287. * 6. Create document global for tracking document cache size.
  13288. * 7. Ensure every cached document has a sentinel row with a sequence number.
  13289. * 8. Add collection-parent index for Collection Group queries.
  13290. * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
  13291. * an auto-incrementing ID. This is required for Index-Free queries.
  13292. * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
  13293. * 11. Add bundles and named_queries for bundle support.
  13294. * 12. Add document overlays.
  13295. * 13. Rewrite the keys of the remote document cache to allow for efficient
  13296. * document lookup via `getAll()`.
  13297. * 14. Add overlays.
  13298. * 15. Add indexing support.
  13299. */
  13300. const SCHEMA_VERSION = 15;
  13301. /**
  13302. * @license
  13303. * Copyright 2022 Google LLC
  13304. *
  13305. * Licensed under the Apache License, Version 2.0 (the "License");
  13306. * you may not use this file except in compliance with the License.
  13307. * You may obtain a copy of the License at
  13308. *
  13309. * http://www.apache.org/licenses/LICENSE-2.0
  13310. *
  13311. * Unless required by applicable law or agreed to in writing, software
  13312. * distributed under the License is distributed on an "AS IS" BASIS,
  13313. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13314. * See the License for the specific language governing permissions and
  13315. * limitations under the License.
  13316. */
  13317. /**
  13318. * Represents a local view (overlay) of a document, and the fields that are
  13319. * locally mutated.
  13320. */
  13321. class OverlayedDocument {
  13322. constructor(overlayedDocument,
  13323. /**
  13324. * The fields that are locally mutated by patch mutations.
  13325. *
  13326. * If the overlayed document is from set or delete mutations, this is `null`.
  13327. * If there is no overlay (mutation) for the document, this is an empty `FieldMask`.
  13328. */
  13329. mutatedFields) {
  13330. this.overlayedDocument = overlayedDocument;
  13331. this.mutatedFields = mutatedFields;
  13332. }
  13333. }
  13334. /**
  13335. * @license
  13336. * Copyright 2017 Google LLC
  13337. *
  13338. * Licensed under the Apache License, Version 2.0 (the "License");
  13339. * you may not use this file except in compliance with the License.
  13340. * You may obtain a copy of the License at
  13341. *
  13342. * http://www.apache.org/licenses/LICENSE-2.0
  13343. *
  13344. * Unless required by applicable law or agreed to in writing, software
  13345. * distributed under the License is distributed on an "AS IS" BASIS,
  13346. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13347. * See the License for the specific language governing permissions and
  13348. * limitations under the License.
  13349. */
  13350. /**
  13351. * A readonly view of the local state of all documents we're tracking (i.e. we
  13352. * have a cached version in remoteDocumentCache or local mutations for the
  13353. * document). The view is computed by applying the mutations in the
  13354. * MutationQueue to the RemoteDocumentCache.
  13355. */
  13356. class LocalDocumentsView {
  13357. constructor(remoteDocumentCache, mutationQueue, documentOverlayCache, indexManager) {
  13358. this.remoteDocumentCache = remoteDocumentCache;
  13359. this.mutationQueue = mutationQueue;
  13360. this.documentOverlayCache = documentOverlayCache;
  13361. this.indexManager = indexManager;
  13362. }
  13363. /**
  13364. * Get the local view of the document identified by `key`.
  13365. *
  13366. * @returns Local view of the document or null if we don't have any cached
  13367. * state for it.
  13368. */
  13369. getDocument(transaction, key) {
  13370. let overlay = null;
  13371. return this.documentOverlayCache
  13372. .getOverlay(transaction, key)
  13373. .next(value => {
  13374. overlay = value;
  13375. return this.remoteDocumentCache.getEntry(transaction, key);
  13376. })
  13377. .next(document => {
  13378. if (overlay !== null) {
  13379. mutationApplyToLocalView(overlay.mutation, document, FieldMask.empty(), Timestamp.now());
  13380. }
  13381. return document;
  13382. });
  13383. }
  13384. /**
  13385. * Gets the local view of the documents identified by `keys`.
  13386. *
  13387. * If we don't have cached state for a document in `keys`, a NoDocument will
  13388. * be stored for that key in the resulting set.
  13389. */
  13390. getDocuments(transaction, keys) {
  13391. return this.remoteDocumentCache
  13392. .getEntries(transaction, keys)
  13393. .next(docs => this.getLocalViewOfDocuments(transaction, docs, documentKeySet()).next(() => docs));
  13394. }
  13395. /**
  13396. * Similar to `getDocuments`, but creates the local view from the given
  13397. * `baseDocs` without retrieving documents from the local store.
  13398. *
  13399. * @param transaction - The transaction this operation is scoped to.
  13400. * @param docs - The documents to apply local mutations to get the local views.
  13401. * @param existenceStateChanged - The set of document keys whose existence state
  13402. * is changed. This is useful to determine if some documents overlay needs
  13403. * to be recalculated.
  13404. */
  13405. getLocalViewOfDocuments(transaction, docs, existenceStateChanged = documentKeySet()) {
  13406. const overlays = newOverlayMap();
  13407. return this.populateOverlays(transaction, overlays, docs).next(() => {
  13408. return this.computeViews(transaction, docs, overlays, existenceStateChanged).next(computeViewsResult => {
  13409. let result = documentMap();
  13410. computeViewsResult.forEach((documentKey, overlayedDocument) => {
  13411. result = result.insert(documentKey, overlayedDocument.overlayedDocument);
  13412. });
  13413. return result;
  13414. });
  13415. });
  13416. }
  13417. /**
  13418. * Gets the overlayed documents for the given document map, which will include
  13419. * the local view of those documents and a `FieldMask` indicating which fields
  13420. * are mutated locally, `null` if overlay is a Set or Delete mutation.
  13421. */
  13422. getOverlayedDocuments(transaction, docs) {
  13423. const overlays = newOverlayMap();
  13424. return this.populateOverlays(transaction, overlays, docs).next(() => this.computeViews(transaction, docs, overlays, documentKeySet()));
  13425. }
  13426. /**
  13427. * Fetches the overlays for {@code docs} and adds them to provided overlay map
  13428. * if the map does not already contain an entry for the given document key.
  13429. */
  13430. populateOverlays(transaction, overlays, docs) {
  13431. const missingOverlays = [];
  13432. docs.forEach(key => {
  13433. if (!overlays.has(key)) {
  13434. missingOverlays.push(key);
  13435. }
  13436. });
  13437. return this.documentOverlayCache
  13438. .getOverlays(transaction, missingOverlays)
  13439. .next(result => {
  13440. result.forEach((key, val) => {
  13441. overlays.set(key, val);
  13442. });
  13443. });
  13444. }
  13445. /**
  13446. * Computes the local view for the given documents.
  13447. *
  13448. * @param docs - The documents to compute views for. It also has the base
  13449. * version of the documents.
  13450. * @param overlays - The overlays that need to be applied to the given base
  13451. * version of the documents.
  13452. * @param existenceStateChanged - A set of documents whose existence states
  13453. * might have changed. This is used to determine if we need to re-calculate
  13454. * overlays from mutation queues.
  13455. * @return A map represents the local documents view.
  13456. */
  13457. computeViews(transaction, docs, overlays, existenceStateChanged) {
  13458. let recalculateDocuments = mutableDocumentMap();
  13459. const mutatedFields = newDocumentKeyMap();
  13460. const results = newOverlayedDocumentMap();
  13461. docs.forEach((_, doc) => {
  13462. const overlay = overlays.get(doc.key);
  13463. // Recalculate an overlay if the document's existence state changed due to
  13464. // a remote event *and* the overlay is a PatchMutation. This is because
  13465. // document existence state can change if some patch mutation's
  13466. // preconditions are met.
  13467. // NOTE: we recalculate when `overlay` is undefined as well, because there
  13468. // might be a patch mutation whose precondition does not match before the
  13469. // change (hence overlay is undefined), but would now match.
  13470. if (existenceStateChanged.has(doc.key) &&
  13471. (overlay === undefined || overlay.mutation instanceof PatchMutation)) {
  13472. recalculateDocuments = recalculateDocuments.insert(doc.key, doc);
  13473. }
  13474. else if (overlay !== undefined) {
  13475. mutatedFields.set(doc.key, overlay.mutation.getFieldMask());
  13476. mutationApplyToLocalView(overlay.mutation, doc, overlay.mutation.getFieldMask(), Timestamp.now());
  13477. }
  13478. else {
  13479. // no overlay exists
  13480. // Using EMPTY to indicate there is no overlay for the document.
  13481. mutatedFields.set(doc.key, FieldMask.empty());
  13482. }
  13483. });
  13484. return this.recalculateAndSaveOverlays(transaction, recalculateDocuments).next(recalculatedFields => {
  13485. recalculatedFields.forEach((documentKey, mask) => mutatedFields.set(documentKey, mask));
  13486. docs.forEach((documentKey, document) => {
  13487. var _a;
  13488. return results.set(documentKey, new OverlayedDocument(document, (_a = mutatedFields.get(documentKey)) !== null && _a !== void 0 ? _a : null));
  13489. });
  13490. return results;
  13491. });
  13492. }
  13493. recalculateAndSaveOverlays(transaction, docs) {
  13494. const masks = newDocumentKeyMap();
  13495. // A reverse lookup map from batch id to the documents within that batch.
  13496. let documentsByBatchId = new SortedMap((key1, key2) => key1 - key2);
  13497. let processed = documentKeySet();
  13498. return this.mutationQueue
  13499. .getAllMutationBatchesAffectingDocumentKeys(transaction, docs)
  13500. .next(batches => {
  13501. for (const batch of batches) {
  13502. batch.keys().forEach(key => {
  13503. const baseDoc = docs.get(key);
  13504. if (baseDoc === null) {
  13505. return;
  13506. }
  13507. let mask = masks.get(key) || FieldMask.empty();
  13508. mask = batch.applyToLocalView(baseDoc, mask);
  13509. masks.set(key, mask);
  13510. const newSet = (documentsByBatchId.get(batch.batchId) || documentKeySet()).add(key);
  13511. documentsByBatchId = documentsByBatchId.insert(batch.batchId, newSet);
  13512. });
  13513. }
  13514. })
  13515. .next(() => {
  13516. const promises = [];
  13517. // Iterate in descending order of batch IDs, and skip documents that are
  13518. // already saved.
  13519. const iter = documentsByBatchId.getReverseIterator();
  13520. while (iter.hasNext()) {
  13521. const entry = iter.getNext();
  13522. const batchId = entry.key;
  13523. const keys = entry.value;
  13524. const overlays = newMutationMap();
  13525. keys.forEach(key => {
  13526. if (!processed.has(key)) {
  13527. const overlayMutation = calculateOverlayMutation(docs.get(key), masks.get(key));
  13528. if (overlayMutation !== null) {
  13529. overlays.set(key, overlayMutation);
  13530. }
  13531. processed = processed.add(key);
  13532. }
  13533. });
  13534. promises.push(this.documentOverlayCache.saveOverlays(transaction, batchId, overlays));
  13535. }
  13536. return PersistencePromise.waitFor(promises);
  13537. })
  13538. .next(() => masks);
  13539. }
  13540. /**
  13541. * Recalculates overlays by reading the documents from remote document cache
  13542. * first, and saves them after they are calculated.
  13543. */
  13544. recalculateAndSaveOverlaysForDocumentKeys(transaction, documentKeys) {
  13545. return this.remoteDocumentCache
  13546. .getEntries(transaction, documentKeys)
  13547. .next(docs => this.recalculateAndSaveOverlays(transaction, docs));
  13548. }
  13549. /**
  13550. * Performs a query against the local view of all documents.
  13551. *
  13552. * @param transaction - The persistence transaction.
  13553. * @param query - The query to match documents against.
  13554. * @param offset - Read time and key to start scanning by (exclusive).
  13555. */
  13556. getDocumentsMatchingQuery(transaction, query, offset) {
  13557. if (isDocumentQuery$1(query)) {
  13558. return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
  13559. }
  13560. else if (isCollectionGroupQuery(query)) {
  13561. return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, offset);
  13562. }
  13563. else {
  13564. return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
  13565. }
  13566. }
  13567. /**
  13568. * Given a collection group, returns the next documents that follow the provided offset, along
  13569. * with an updated batch ID.
  13570. *
  13571. * <p>The documents returned by this method are ordered by remote version from the provided
  13572. * offset. If there are no more remote documents after the provided offset, documents with
  13573. * mutations in order of batch id from the offset are returned. Since all documents in a batch are
  13574. * returned together, the total number of documents returned can exceed {@code count}.
  13575. *
  13576. * @param transaction
  13577. * @param collectionGroup The collection group for the documents.
  13578. * @param offset The offset to index into.
  13579. * @param count The number of documents to return
  13580. * @return A LocalWriteResult with the documents that follow the provided offset and the last processed batch id.
  13581. */
  13582. getNextDocuments(transaction, collectionGroup, offset, count) {
  13583. return this.remoteDocumentCache
  13584. .getAllFromCollectionGroup(transaction, collectionGroup, offset, count)
  13585. .next((originalDocs) => {
  13586. const overlaysPromise = count - originalDocs.size > 0
  13587. ? this.documentOverlayCache.getOverlaysForCollectionGroup(transaction, collectionGroup, offset.largestBatchId, count - originalDocs.size)
  13588. : PersistencePromise.resolve(newOverlayMap());
  13589. // The callsite will use the largest batch ID together with the latest read time to create
  13590. // a new index offset. Since we only process batch IDs if all remote documents have been read,
  13591. // no overlay will increase the overall read time. This is why we only need to special case
  13592. // the batch id.
  13593. let largestBatchId = INITIAL_LARGEST_BATCH_ID;
  13594. let modifiedDocs = originalDocs;
  13595. return overlaysPromise.next(overlays => {
  13596. return PersistencePromise.forEach(overlays, (key, overlay) => {
  13597. if (largestBatchId < overlay.largestBatchId) {
  13598. largestBatchId = overlay.largestBatchId;
  13599. }
  13600. if (originalDocs.get(key)) {
  13601. return PersistencePromise.resolve();
  13602. }
  13603. return this.remoteDocumentCache
  13604. .getEntry(transaction, key)
  13605. .next(doc => {
  13606. modifiedDocs = modifiedDocs.insert(key, doc);
  13607. });
  13608. })
  13609. .next(() => this.populateOverlays(transaction, overlays, originalDocs))
  13610. .next(() => this.computeViews(transaction, modifiedDocs, overlays, documentKeySet()))
  13611. .next(localDocs => ({
  13612. batchId: largestBatchId,
  13613. changes: convertOverlayedDocumentMapToDocumentMap(localDocs)
  13614. }));
  13615. });
  13616. });
  13617. }
  13618. getDocumentsMatchingDocumentQuery(transaction, docPath) {
  13619. // Just do a simple document lookup.
  13620. return this.getDocument(transaction, new DocumentKey(docPath)).next(document => {
  13621. let result = documentMap();
  13622. if (document.isFoundDocument()) {
  13623. result = result.insert(document.key, document);
  13624. }
  13625. return result;
  13626. });
  13627. }
  13628. getDocumentsMatchingCollectionGroupQuery(transaction, query, offset) {
  13629. const collectionId = query.collectionGroup;
  13630. let results = documentMap();
  13631. return this.indexManager
  13632. .getCollectionParents(transaction, collectionId)
  13633. .next(parents => {
  13634. // Perform a collection query against each parent that contains the
  13635. // collectionId and aggregate the results.
  13636. return PersistencePromise.forEach(parents, (parent) => {
  13637. const collectionQuery = asCollectionQueryAtPath(query, parent.child(collectionId));
  13638. return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, offset).next(r => {
  13639. r.forEach((key, doc) => {
  13640. results = results.insert(key, doc);
  13641. });
  13642. });
  13643. }).next(() => results);
  13644. });
  13645. }
  13646. getDocumentsMatchingCollectionQuery(transaction, query, offset) {
  13647. // Query the remote documents and overlay mutations.
  13648. let remoteDocuments;
  13649. return this.remoteDocumentCache
  13650. .getAllFromCollection(transaction, query.path, offset)
  13651. .next(queryResults => {
  13652. remoteDocuments = queryResults;
  13653. return this.documentOverlayCache.getOverlaysForCollection(transaction, query.path, offset.largestBatchId);
  13654. })
  13655. .next(overlays => {
  13656. // As documents might match the query because of their overlay we need to
  13657. // include documents for all overlays in the initial document set.
  13658. overlays.forEach((_, overlay) => {
  13659. const key = overlay.getKey();
  13660. if (remoteDocuments.get(key) === null) {
  13661. remoteDocuments = remoteDocuments.insert(key, MutableDocument.newInvalidDocument(key));
  13662. }
  13663. });
  13664. // Apply the overlays and match against the query.
  13665. let results = documentMap();
  13666. remoteDocuments.forEach((key, document) => {
  13667. const overlay = overlays.get(key);
  13668. if (overlay !== undefined) {
  13669. mutationApplyToLocalView(overlay.mutation, document, FieldMask.empty(), Timestamp.now());
  13670. }
  13671. // Finally, insert the documents that still match the query
  13672. if (queryMatches(query, document)) {
  13673. results = results.insert(key, document);
  13674. }
  13675. });
  13676. return results;
  13677. });
  13678. }
  13679. }
  13680. /**
  13681. * @license
  13682. * Copyright 2020 Google LLC
  13683. *
  13684. * Licensed under the Apache License, Version 2.0 (the "License");
  13685. * you may not use this file except in compliance with the License.
  13686. * You may obtain a copy of the License at
  13687. *
  13688. * http://www.apache.org/licenses/LICENSE-2.0
  13689. *
  13690. * Unless required by applicable law or agreed to in writing, software
  13691. * distributed under the License is distributed on an "AS IS" BASIS,
  13692. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13693. * See the License for the specific language governing permissions and
  13694. * limitations under the License.
  13695. */
  13696. class MemoryBundleCache {
  13697. constructor(serializer) {
  13698. this.serializer = serializer;
  13699. this.bundles = new Map();
  13700. this.namedQueries = new Map();
  13701. }
  13702. getBundleMetadata(transaction, bundleId) {
  13703. return PersistencePromise.resolve(this.bundles.get(bundleId));
  13704. }
  13705. saveBundleMetadata(transaction, bundleMetadata) {
  13706. this.bundles.set(bundleMetadata.id, fromBundleMetadata(bundleMetadata));
  13707. return PersistencePromise.resolve();
  13708. }
  13709. getNamedQuery(transaction, queryName) {
  13710. return PersistencePromise.resolve(this.namedQueries.get(queryName));
  13711. }
  13712. saveNamedQuery(transaction, query) {
  13713. this.namedQueries.set(query.name, fromProtoNamedQuery(query));
  13714. return PersistencePromise.resolve();
  13715. }
  13716. }
  13717. /**
  13718. * @license
  13719. * Copyright 2022 Google LLC
  13720. *
  13721. * Licensed under the Apache License, Version 2.0 (the "License");
  13722. * you may not use this file except in compliance with the License.
  13723. * You may obtain a copy of the License at
  13724. *
  13725. * http://www.apache.org/licenses/LICENSE-2.0
  13726. *
  13727. * Unless required by applicable law or agreed to in writing, software
  13728. * distributed under the License is distributed on an "AS IS" BASIS,
  13729. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13730. * See the License for the specific language governing permissions and
  13731. * limitations under the License.
  13732. */
  13733. /**
  13734. * An in-memory implementation of DocumentOverlayCache.
  13735. */
  13736. class MemoryDocumentOverlayCache {
  13737. constructor() {
  13738. // A map sorted by DocumentKey, whose value is a pair of the largest batch id
  13739. // for the overlay and the overlay itself.
  13740. this.overlays = new SortedMap(DocumentKey.comparator);
  13741. this.overlayByBatchId = new Map();
  13742. }
  13743. getOverlay(transaction, key) {
  13744. return PersistencePromise.resolve(this.overlays.get(key));
  13745. }
  13746. getOverlays(transaction, keys) {
  13747. const result = newOverlayMap();
  13748. return PersistencePromise.forEach(keys, (key) => {
  13749. return this.getOverlay(transaction, key).next(overlay => {
  13750. if (overlay !== null) {
  13751. result.set(key, overlay);
  13752. }
  13753. });
  13754. }).next(() => result);
  13755. }
  13756. saveOverlays(transaction, largestBatchId, overlays) {
  13757. overlays.forEach((_, mutation) => {
  13758. this.saveOverlay(transaction, largestBatchId, mutation);
  13759. });
  13760. return PersistencePromise.resolve();
  13761. }
  13762. removeOverlaysForBatchId(transaction, documentKeys, batchId) {
  13763. const keys = this.overlayByBatchId.get(batchId);
  13764. if (keys !== undefined) {
  13765. keys.forEach(key => (this.overlays = this.overlays.remove(key)));
  13766. this.overlayByBatchId.delete(batchId);
  13767. }
  13768. return PersistencePromise.resolve();
  13769. }
  13770. getOverlaysForCollection(transaction, collection, sinceBatchId) {
  13771. const result = newOverlayMap();
  13772. const immediateChildrenPathLength = collection.length + 1;
  13773. const prefix = new DocumentKey(collection.child(''));
  13774. const iter = this.overlays.getIteratorFrom(prefix);
  13775. while (iter.hasNext()) {
  13776. const entry = iter.getNext();
  13777. const overlay = entry.value;
  13778. const key = overlay.getKey();
  13779. if (!collection.isPrefixOf(key.path)) {
  13780. break;
  13781. }
  13782. // Documents from sub-collections
  13783. if (key.path.length !== immediateChildrenPathLength) {
  13784. continue;
  13785. }
  13786. if (overlay.largestBatchId > sinceBatchId) {
  13787. result.set(overlay.getKey(), overlay);
  13788. }
  13789. }
  13790. return PersistencePromise.resolve(result);
  13791. }
  13792. getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
  13793. let batchIdToOverlays = new SortedMap((key1, key2) => key1 - key2);
  13794. const iter = this.overlays.getIterator();
  13795. while (iter.hasNext()) {
  13796. const entry = iter.getNext();
  13797. const overlay = entry.value;
  13798. const key = overlay.getKey();
  13799. if (key.getCollectionGroup() !== collectionGroup) {
  13800. continue;
  13801. }
  13802. if (overlay.largestBatchId > sinceBatchId) {
  13803. let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
  13804. if (overlaysForBatchId === null) {
  13805. overlaysForBatchId = newOverlayMap();
  13806. batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
  13807. }
  13808. overlaysForBatchId.set(overlay.getKey(), overlay);
  13809. }
  13810. }
  13811. const result = newOverlayMap();
  13812. const batchIter = batchIdToOverlays.getIterator();
  13813. while (batchIter.hasNext()) {
  13814. const entry = batchIter.getNext();
  13815. const overlays = entry.value;
  13816. overlays.forEach((key, overlay) => result.set(key, overlay));
  13817. if (result.size() >= count) {
  13818. break;
  13819. }
  13820. }
  13821. return PersistencePromise.resolve(result);
  13822. }
  13823. saveOverlay(transaction, largestBatchId, mutation) {
  13824. // Remove the association of the overlay to its batch id.
  13825. const existing = this.overlays.get(mutation.key);
  13826. if (existing !== null) {
  13827. const newSet = this.overlayByBatchId
  13828. .get(existing.largestBatchId)
  13829. .delete(mutation.key);
  13830. this.overlayByBatchId.set(existing.largestBatchId, newSet);
  13831. }
  13832. this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
  13833. // Create the association of this overlay to the given largestBatchId.
  13834. let batch = this.overlayByBatchId.get(largestBatchId);
  13835. if (batch === undefined) {
  13836. batch = documentKeySet();
  13837. this.overlayByBatchId.set(largestBatchId, batch);
  13838. }
  13839. this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
  13840. }
  13841. }
  13842. /**
  13843. * @license
  13844. * Copyright 2017 Google LLC
  13845. *
  13846. * Licensed under the Apache License, Version 2.0 (the "License");
  13847. * you may not use this file except in compliance with the License.
  13848. * You may obtain a copy of the License at
  13849. *
  13850. * http://www.apache.org/licenses/LICENSE-2.0
  13851. *
  13852. * Unless required by applicable law or agreed to in writing, software
  13853. * distributed under the License is distributed on an "AS IS" BASIS,
  13854. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13855. * See the License for the specific language governing permissions and
  13856. * limitations under the License.
  13857. */
  13858. /**
  13859. * A collection of references to a document from some kind of numbered entity
  13860. * (either a target ID or batch ID). As references are added to or removed from
  13861. * the set corresponding events are emitted to a registered garbage collector.
  13862. *
  13863. * Each reference is represented by a DocumentReference object. Each of them
  13864. * contains enough information to uniquely identify the reference. They are all
  13865. * stored primarily in a set sorted by key. A document is considered garbage if
  13866. * there's no references in that set (this can be efficiently checked thanks to
  13867. * sorting by key).
  13868. *
  13869. * ReferenceSet also keeps a secondary set that contains references sorted by
  13870. * IDs. This one is used to efficiently implement removal of all references by
  13871. * some target ID.
  13872. */
  13873. class ReferenceSet {
  13874. constructor() {
  13875. // A set of outstanding references to a document sorted by key.
  13876. this.refsByKey = new SortedSet(DocReference.compareByKey);
  13877. // A set of outstanding references to a document sorted by target id.
  13878. this.refsByTarget = new SortedSet(DocReference.compareByTargetId);
  13879. }
  13880. /** Returns true if the reference set contains no references. */
  13881. isEmpty() {
  13882. return this.refsByKey.isEmpty();
  13883. }
  13884. /** Adds a reference to the given document key for the given ID. */
  13885. addReference(key, id) {
  13886. const ref = new DocReference(key, id);
  13887. this.refsByKey = this.refsByKey.add(ref);
  13888. this.refsByTarget = this.refsByTarget.add(ref);
  13889. }
  13890. /** Add references to the given document keys for the given ID. */
  13891. addReferences(keys, id) {
  13892. keys.forEach(key => this.addReference(key, id));
  13893. }
  13894. /**
  13895. * Removes a reference to the given document key for the given
  13896. * ID.
  13897. */
  13898. removeReference(key, id) {
  13899. this.removeRef(new DocReference(key, id));
  13900. }
  13901. removeReferences(keys, id) {
  13902. keys.forEach(key => this.removeReference(key, id));
  13903. }
  13904. /**
  13905. * Clears all references with a given ID. Calls removeRef() for each key
  13906. * removed.
  13907. */
  13908. removeReferencesForId(id) {
  13909. const emptyKey = new DocumentKey(new ResourcePath([]));
  13910. const startRef = new DocReference(emptyKey, id);
  13911. const endRef = new DocReference(emptyKey, id + 1);
  13912. const keys = [];
  13913. this.refsByTarget.forEachInRange([startRef, endRef], ref => {
  13914. this.removeRef(ref);
  13915. keys.push(ref.key);
  13916. });
  13917. return keys;
  13918. }
  13919. removeAllReferences() {
  13920. this.refsByKey.forEach(ref => this.removeRef(ref));
  13921. }
  13922. removeRef(ref) {
  13923. this.refsByKey = this.refsByKey.delete(ref);
  13924. this.refsByTarget = this.refsByTarget.delete(ref);
  13925. }
  13926. referencesForId(id) {
  13927. const emptyKey = new DocumentKey(new ResourcePath([]));
  13928. const startRef = new DocReference(emptyKey, id);
  13929. const endRef = new DocReference(emptyKey, id + 1);
  13930. let keys = documentKeySet();
  13931. this.refsByTarget.forEachInRange([startRef, endRef], ref => {
  13932. keys = keys.add(ref.key);
  13933. });
  13934. return keys;
  13935. }
  13936. containsKey(key) {
  13937. const ref = new DocReference(key, 0);
  13938. const firstRef = this.refsByKey.firstAfterOrEqual(ref);
  13939. return firstRef !== null && key.isEqual(firstRef.key);
  13940. }
  13941. }
  13942. class DocReference {
  13943. constructor(key, targetOrBatchId) {
  13944. this.key = key;
  13945. this.targetOrBatchId = targetOrBatchId;
  13946. }
  13947. /** Compare by key then by ID */
  13948. static compareByKey(left, right) {
  13949. return (DocumentKey.comparator(left.key, right.key) ||
  13950. primitiveComparator(left.targetOrBatchId, right.targetOrBatchId));
  13951. }
  13952. /** Compare by ID then by key */
  13953. static compareByTargetId(left, right) {
  13954. return (primitiveComparator(left.targetOrBatchId, right.targetOrBatchId) ||
  13955. DocumentKey.comparator(left.key, right.key));
  13956. }
  13957. }
  13958. /**
  13959. * @license
  13960. * Copyright 2017 Google LLC
  13961. *
  13962. * Licensed under the Apache License, Version 2.0 (the "License");
  13963. * you may not use this file except in compliance with the License.
  13964. * You may obtain a copy of the License at
  13965. *
  13966. * http://www.apache.org/licenses/LICENSE-2.0
  13967. *
  13968. * Unless required by applicable law or agreed to in writing, software
  13969. * distributed under the License is distributed on an "AS IS" BASIS,
  13970. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13971. * See the License for the specific language governing permissions and
  13972. * limitations under the License.
  13973. */
  13974. class MemoryMutationQueue {
  13975. constructor(indexManager, referenceDelegate) {
  13976. this.indexManager = indexManager;
  13977. this.referenceDelegate = referenceDelegate;
  13978. /**
  13979. * The set of all mutations that have been sent but not yet been applied to
  13980. * the backend.
  13981. */
  13982. this.mutationQueue = [];
  13983. /** Next value to use when assigning sequential IDs to each mutation batch. */
  13984. this.nextBatchId = 1;
  13985. /** An ordered mapping between documents and the mutations batch IDs. */
  13986. this.batchesByDocumentKey = new SortedSet(DocReference.compareByKey);
  13987. }
  13988. checkEmpty(transaction) {
  13989. return PersistencePromise.resolve(this.mutationQueue.length === 0);
  13990. }
  13991. addMutationBatch(transaction, localWriteTime, baseMutations, mutations) {
  13992. const batchId = this.nextBatchId;
  13993. this.nextBatchId++;
  13994. if (this.mutationQueue.length > 0) {
  13995. this.mutationQueue[this.mutationQueue.length - 1];
  13996. }
  13997. const batch = new MutationBatch(batchId, localWriteTime, baseMutations, mutations);
  13998. this.mutationQueue.push(batch);
  13999. // Track references by document key and index collection parents.
  14000. for (const mutation of mutations) {
  14001. this.batchesByDocumentKey = this.batchesByDocumentKey.add(new DocReference(mutation.key, batchId));
  14002. this.indexManager.addToCollectionParentIndex(transaction, mutation.key.path.popLast());
  14003. }
  14004. return PersistencePromise.resolve(batch);
  14005. }
  14006. lookupMutationBatch(transaction, batchId) {
  14007. return PersistencePromise.resolve(this.findMutationBatch(batchId));
  14008. }
  14009. getNextMutationBatchAfterBatchId(transaction, batchId) {
  14010. const nextBatchId = batchId + 1;
  14011. // The requested batchId may still be out of range so normalize it to the
  14012. // start of the queue.
  14013. const rawIndex = this.indexOfBatchId(nextBatchId);
  14014. const index = rawIndex < 0 ? 0 : rawIndex;
  14015. return PersistencePromise.resolve(this.mutationQueue.length > index ? this.mutationQueue[index] : null);
  14016. }
  14017. getHighestUnacknowledgedBatchId() {
  14018. return PersistencePromise.resolve(this.mutationQueue.length === 0 ? BATCHID_UNKNOWN : this.nextBatchId - 1);
  14019. }
  14020. getAllMutationBatches(transaction) {
  14021. return PersistencePromise.resolve(this.mutationQueue.slice());
  14022. }
  14023. getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
  14024. const start = new DocReference(documentKey, 0);
  14025. const end = new DocReference(documentKey, Number.POSITIVE_INFINITY);
  14026. const result = [];
  14027. this.batchesByDocumentKey.forEachInRange([start, end], ref => {
  14028. const batch = this.findMutationBatch(ref.targetOrBatchId);
  14029. result.push(batch);
  14030. });
  14031. return PersistencePromise.resolve(result);
  14032. }
  14033. getAllMutationBatchesAffectingDocumentKeys(transaction, documentKeys) {
  14034. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  14035. documentKeys.forEach(documentKey => {
  14036. const start = new DocReference(documentKey, 0);
  14037. const end = new DocReference(documentKey, Number.POSITIVE_INFINITY);
  14038. this.batchesByDocumentKey.forEachInRange([start, end], ref => {
  14039. uniqueBatchIDs = uniqueBatchIDs.add(ref.targetOrBatchId);
  14040. });
  14041. });
  14042. return PersistencePromise.resolve(this.findMutationBatches(uniqueBatchIDs));
  14043. }
  14044. getAllMutationBatchesAffectingQuery(transaction, query) {
  14045. // Use the query path as a prefix for testing if a document matches the
  14046. // query.
  14047. const prefix = query.path;
  14048. const immediateChildrenPathLength = prefix.length + 1;
  14049. // Construct a document reference for actually scanning the index. Unlike
  14050. // the prefix the document key in this reference must have an even number of
  14051. // segments. The empty segment can be used a suffix of the query path
  14052. // because it precedes all other segments in an ordered traversal.
  14053. let startPath = prefix;
  14054. if (!DocumentKey.isDocumentKey(startPath)) {
  14055. startPath = startPath.child('');
  14056. }
  14057. const start = new DocReference(new DocumentKey(startPath), 0);
  14058. // Find unique batchIDs referenced by all documents potentially matching the
  14059. // query.
  14060. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  14061. this.batchesByDocumentKey.forEachWhile(ref => {
  14062. const rowKeyPath = ref.key.path;
  14063. if (!prefix.isPrefixOf(rowKeyPath)) {
  14064. return false;
  14065. }
  14066. else {
  14067. // Rows with document keys more than one segment longer than the query
  14068. // path can't be matches. For example, a query on 'rooms' can't match
  14069. // the document /rooms/abc/messages/xyx.
  14070. // TODO(mcg): we'll need a different scanner when we implement
  14071. // ancestor queries.
  14072. if (rowKeyPath.length === immediateChildrenPathLength) {
  14073. uniqueBatchIDs = uniqueBatchIDs.add(ref.targetOrBatchId);
  14074. }
  14075. return true;
  14076. }
  14077. }, start);
  14078. return PersistencePromise.resolve(this.findMutationBatches(uniqueBatchIDs));
  14079. }
  14080. findMutationBatches(batchIDs) {
  14081. // Construct an array of matching batches, sorted by batchID to ensure that
  14082. // multiple mutations affecting the same document key are applied in order.
  14083. const result = [];
  14084. batchIDs.forEach(batchId => {
  14085. const batch = this.findMutationBatch(batchId);
  14086. if (batch !== null) {
  14087. result.push(batch);
  14088. }
  14089. });
  14090. return result;
  14091. }
  14092. removeMutationBatch(transaction, batch) {
  14093. // Find the position of the first batch for removal.
  14094. const batchIndex = this.indexOfExistingBatchId(batch.batchId, 'removed');
  14095. hardAssert(batchIndex === 0);
  14096. this.mutationQueue.shift();
  14097. let references = this.batchesByDocumentKey;
  14098. return PersistencePromise.forEach(batch.mutations, (mutation) => {
  14099. const ref = new DocReference(mutation.key, batch.batchId);
  14100. references = references.delete(ref);
  14101. return this.referenceDelegate.markPotentiallyOrphaned(transaction, mutation.key);
  14102. }).next(() => {
  14103. this.batchesByDocumentKey = references;
  14104. });
  14105. }
  14106. removeCachedMutationKeys(batchId) {
  14107. // No-op since the memory mutation queue does not maintain a separate cache.
  14108. }
  14109. containsKey(txn, key) {
  14110. const ref = new DocReference(key, 0);
  14111. const firstRef = this.batchesByDocumentKey.firstAfterOrEqual(ref);
  14112. return PersistencePromise.resolve(key.isEqual(firstRef && firstRef.key));
  14113. }
  14114. performConsistencyCheck(txn) {
  14115. if (this.mutationQueue.length === 0) ;
  14116. return PersistencePromise.resolve();
  14117. }
  14118. /**
  14119. * Finds the index of the given batchId in the mutation queue and asserts that
  14120. * the resulting index is within the bounds of the queue.
  14121. *
  14122. * @param batchId - The batchId to search for
  14123. * @param action - A description of what the caller is doing, phrased in passive
  14124. * form (e.g. "acknowledged" in a routine that acknowledges batches).
  14125. */
  14126. indexOfExistingBatchId(batchId, action) {
  14127. const index = this.indexOfBatchId(batchId);
  14128. return index;
  14129. }
  14130. /**
  14131. * Finds the index of the given batchId in the mutation queue. This operation
  14132. * is O(1).
  14133. *
  14134. * @returns The computed index of the batch with the given batchId, based on
  14135. * the state of the queue. Note this index can be negative if the requested
  14136. * batchId has already been remvoed from the queue or past the end of the
  14137. * queue if the batchId is larger than the last added batch.
  14138. */
  14139. indexOfBatchId(batchId) {
  14140. if (this.mutationQueue.length === 0) {
  14141. // As an index this is past the end of the queue
  14142. return 0;
  14143. }
  14144. // Examine the front of the queue to figure out the difference between the
  14145. // batchId and indexes in the array. Note that since the queue is ordered
  14146. // by batchId, if the first batch has a larger batchId then the requested
  14147. // batchId doesn't exist in the queue.
  14148. const firstBatchId = this.mutationQueue[0].batchId;
  14149. return batchId - firstBatchId;
  14150. }
  14151. /**
  14152. * A version of lookupMutationBatch that doesn't return a promise, this makes
  14153. * other functions that uses this code easier to read and more efficent.
  14154. */
  14155. findMutationBatch(batchId) {
  14156. const index = this.indexOfBatchId(batchId);
  14157. if (index < 0 || index >= this.mutationQueue.length) {
  14158. return null;
  14159. }
  14160. const batch = this.mutationQueue[index];
  14161. return batch;
  14162. }
  14163. }
  14164. /**
  14165. * @license
  14166. * Copyright 2017 Google LLC
  14167. *
  14168. * Licensed under the Apache License, Version 2.0 (the "License");
  14169. * you may not use this file except in compliance with the License.
  14170. * You may obtain a copy of the License at
  14171. *
  14172. * http://www.apache.org/licenses/LICENSE-2.0
  14173. *
  14174. * Unless required by applicable law or agreed to in writing, software
  14175. * distributed under the License is distributed on an "AS IS" BASIS,
  14176. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14177. * See the License for the specific language governing permissions and
  14178. * limitations under the License.
  14179. */
  14180. function documentEntryMap() {
  14181. return new SortedMap(DocumentKey.comparator);
  14182. }
  14183. /**
  14184. * The memory-only RemoteDocumentCache for IndexedDb. To construct, invoke
  14185. * `newMemoryRemoteDocumentCache()`.
  14186. */
  14187. class MemoryRemoteDocumentCacheImpl {
  14188. /**
  14189. * @param sizer - Used to assess the size of a document. For eager GC, this is
  14190. * expected to just return 0 to avoid unnecessarily doing the work of
  14191. * calculating the size.
  14192. */
  14193. constructor(sizer) {
  14194. this.sizer = sizer;
  14195. /** Underlying cache of documents and their read times. */
  14196. this.docs = documentEntryMap();
  14197. /** Size of all cached documents. */
  14198. this.size = 0;
  14199. }
  14200. setIndexManager(indexManager) {
  14201. this.indexManager = indexManager;
  14202. }
  14203. /**
  14204. * Adds the supplied entry to the cache and updates the cache size as appropriate.
  14205. *
  14206. * All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer
  14207. * returned by `newChangeBuffer()`.
  14208. */
  14209. addEntry(transaction, doc) {
  14210. const key = doc.key;
  14211. const entry = this.docs.get(key);
  14212. const previousSize = entry ? entry.size : 0;
  14213. const currentSize = this.sizer(doc);
  14214. this.docs = this.docs.insert(key, {
  14215. document: doc.mutableCopy(),
  14216. size: currentSize
  14217. });
  14218. this.size += currentSize - previousSize;
  14219. return this.indexManager.addToCollectionParentIndex(transaction, key.path.popLast());
  14220. }
  14221. /**
  14222. * Removes the specified entry from the cache and updates the cache size as appropriate.
  14223. *
  14224. * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
  14225. * returned by `newChangeBuffer()`.
  14226. */
  14227. removeEntry(documentKey) {
  14228. const entry = this.docs.get(documentKey);
  14229. if (entry) {
  14230. this.docs = this.docs.remove(documentKey);
  14231. this.size -= entry.size;
  14232. }
  14233. }
  14234. getEntry(transaction, documentKey) {
  14235. const entry = this.docs.get(documentKey);
  14236. return PersistencePromise.resolve(entry
  14237. ? entry.document.mutableCopy()
  14238. : MutableDocument.newInvalidDocument(documentKey));
  14239. }
  14240. getEntries(transaction, documentKeys) {
  14241. let results = mutableDocumentMap();
  14242. documentKeys.forEach(documentKey => {
  14243. const entry = this.docs.get(documentKey);
  14244. results = results.insert(documentKey, entry
  14245. ? entry.document.mutableCopy()
  14246. : MutableDocument.newInvalidDocument(documentKey));
  14247. });
  14248. return PersistencePromise.resolve(results);
  14249. }
  14250. getAllFromCollection(transaction, collectionPath, offset) {
  14251. let results = mutableDocumentMap();
  14252. // Documents are ordered by key, so we can use a prefix scan to narrow down
  14253. // the documents we need to match the query against.
  14254. const prefix = new DocumentKey(collectionPath.child(''));
  14255. const iterator = this.docs.getIteratorFrom(prefix);
  14256. while (iterator.hasNext()) {
  14257. const { key, value: { document } } = iterator.getNext();
  14258. if (!collectionPath.isPrefixOf(key.path)) {
  14259. break;
  14260. }
  14261. if (key.path.length > collectionPath.length + 1) {
  14262. // Exclude entries from subcollections.
  14263. continue;
  14264. }
  14265. if (indexOffsetComparator(newIndexOffsetFromDocument(document), offset) <= 0) {
  14266. // The document sorts before the offset.
  14267. continue;
  14268. }
  14269. results = results.insert(document.key, document.mutableCopy());
  14270. }
  14271. return PersistencePromise.resolve(results);
  14272. }
  14273. getAllFromCollectionGroup(transaction, collectionGroup, offset, limti) {
  14274. // This method should only be called from the IndexBackfiller if persistence
  14275. // is enabled.
  14276. fail();
  14277. }
  14278. forEachDocumentKey(transaction, f) {
  14279. return PersistencePromise.forEach(this.docs, (key) => f(key));
  14280. }
  14281. newChangeBuffer(options) {
  14282. // `trackRemovals` is ignores since the MemoryRemoteDocumentCache keeps
  14283. // a separate changelog and does not need special handling for removals.
  14284. return new MemoryRemoteDocumentChangeBuffer(this);
  14285. }
  14286. getSize(txn) {
  14287. return PersistencePromise.resolve(this.size);
  14288. }
  14289. }
  14290. /**
  14291. * Creates a new memory-only RemoteDocumentCache.
  14292. *
  14293. * @param sizer - Used to assess the size of a document. For eager GC, this is
  14294. * expected to just return 0 to avoid unnecessarily doing the work of
  14295. * calculating the size.
  14296. */
  14297. function newMemoryRemoteDocumentCache(sizer) {
  14298. return new MemoryRemoteDocumentCacheImpl(sizer);
  14299. }
  14300. /**
  14301. * Handles the details of adding and updating documents in the MemoryRemoteDocumentCache.
  14302. */
  14303. class MemoryRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
  14304. constructor(documentCache) {
  14305. super();
  14306. this.documentCache = documentCache;
  14307. }
  14308. applyChanges(transaction) {
  14309. const promises = [];
  14310. this.changes.forEach((key, doc) => {
  14311. if (doc.isValidDocument()) {
  14312. promises.push(this.documentCache.addEntry(transaction, doc));
  14313. }
  14314. else {
  14315. this.documentCache.removeEntry(key);
  14316. }
  14317. });
  14318. return PersistencePromise.waitFor(promises);
  14319. }
  14320. getFromCache(transaction, documentKey) {
  14321. return this.documentCache.getEntry(transaction, documentKey);
  14322. }
  14323. getAllFromCache(transaction, documentKeys) {
  14324. return this.documentCache.getEntries(transaction, documentKeys);
  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. class MemoryTargetCache {
  14344. constructor(persistence) {
  14345. this.persistence = persistence;
  14346. /**
  14347. * Maps a target to the data about that target
  14348. */
  14349. this.targets = new ObjectMap(t => canonifyTarget(t), targetEquals);
  14350. /** The last received snapshot version. */
  14351. this.lastRemoteSnapshotVersion = SnapshotVersion.min();
  14352. /** The highest numbered target ID encountered. */
  14353. this.highestTargetId = 0;
  14354. /** The highest sequence number encountered. */
  14355. this.highestSequenceNumber = 0;
  14356. /**
  14357. * A ordered bidirectional mapping between documents and the remote target
  14358. * IDs.
  14359. */
  14360. this.references = new ReferenceSet();
  14361. this.targetCount = 0;
  14362. this.targetIdGenerator = TargetIdGenerator.forTargetCache();
  14363. }
  14364. forEachTarget(txn, f) {
  14365. this.targets.forEach((_, targetData) => f(targetData));
  14366. return PersistencePromise.resolve();
  14367. }
  14368. getLastRemoteSnapshotVersion(transaction) {
  14369. return PersistencePromise.resolve(this.lastRemoteSnapshotVersion);
  14370. }
  14371. getHighestSequenceNumber(transaction) {
  14372. return PersistencePromise.resolve(this.highestSequenceNumber);
  14373. }
  14374. allocateTargetId(transaction) {
  14375. this.highestTargetId = this.targetIdGenerator.next();
  14376. return PersistencePromise.resolve(this.highestTargetId);
  14377. }
  14378. setTargetsMetadata(transaction, highestListenSequenceNumber, lastRemoteSnapshotVersion) {
  14379. if (lastRemoteSnapshotVersion) {
  14380. this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;
  14381. }
  14382. if (highestListenSequenceNumber > this.highestSequenceNumber) {
  14383. this.highestSequenceNumber = highestListenSequenceNumber;
  14384. }
  14385. return PersistencePromise.resolve();
  14386. }
  14387. saveTargetData(targetData) {
  14388. this.targets.set(targetData.target, targetData);
  14389. const targetId = targetData.targetId;
  14390. if (targetId > this.highestTargetId) {
  14391. this.targetIdGenerator = new TargetIdGenerator(targetId);
  14392. this.highestTargetId = targetId;
  14393. }
  14394. if (targetData.sequenceNumber > this.highestSequenceNumber) {
  14395. this.highestSequenceNumber = targetData.sequenceNumber;
  14396. }
  14397. }
  14398. addTargetData(transaction, targetData) {
  14399. this.saveTargetData(targetData);
  14400. this.targetCount += 1;
  14401. return PersistencePromise.resolve();
  14402. }
  14403. updateTargetData(transaction, targetData) {
  14404. this.saveTargetData(targetData);
  14405. return PersistencePromise.resolve();
  14406. }
  14407. removeTargetData(transaction, targetData) {
  14408. this.targets.delete(targetData.target);
  14409. this.references.removeReferencesForId(targetData.targetId);
  14410. this.targetCount -= 1;
  14411. return PersistencePromise.resolve();
  14412. }
  14413. removeTargets(transaction, upperBound, activeTargetIds) {
  14414. let count = 0;
  14415. const removals = [];
  14416. this.targets.forEach((key, targetData) => {
  14417. if (targetData.sequenceNumber <= upperBound &&
  14418. activeTargetIds.get(targetData.targetId) === null) {
  14419. this.targets.delete(key);
  14420. removals.push(this.removeMatchingKeysForTargetId(transaction, targetData.targetId));
  14421. count++;
  14422. }
  14423. });
  14424. return PersistencePromise.waitFor(removals).next(() => count);
  14425. }
  14426. getTargetCount(transaction) {
  14427. return PersistencePromise.resolve(this.targetCount);
  14428. }
  14429. getTargetData(transaction, target) {
  14430. const targetData = this.targets.get(target) || null;
  14431. return PersistencePromise.resolve(targetData);
  14432. }
  14433. addMatchingKeys(txn, keys, targetId) {
  14434. this.references.addReferences(keys, targetId);
  14435. return PersistencePromise.resolve();
  14436. }
  14437. removeMatchingKeys(txn, keys, targetId) {
  14438. this.references.removeReferences(keys, targetId);
  14439. const referenceDelegate = this.persistence.referenceDelegate;
  14440. const promises = [];
  14441. if (referenceDelegate) {
  14442. keys.forEach(key => {
  14443. promises.push(referenceDelegate.markPotentiallyOrphaned(txn, key));
  14444. });
  14445. }
  14446. return PersistencePromise.waitFor(promises);
  14447. }
  14448. removeMatchingKeysForTargetId(txn, targetId) {
  14449. this.references.removeReferencesForId(targetId);
  14450. return PersistencePromise.resolve();
  14451. }
  14452. getMatchingKeysForTargetId(txn, targetId) {
  14453. const matchingKeys = this.references.referencesForId(targetId);
  14454. return PersistencePromise.resolve(matchingKeys);
  14455. }
  14456. containsKey(txn, key) {
  14457. return PersistencePromise.resolve(this.references.containsKey(key));
  14458. }
  14459. }
  14460. /**
  14461. * @license
  14462. * Copyright 2017 Google LLC
  14463. *
  14464. * Licensed under the Apache License, Version 2.0 (the "License");
  14465. * you may not use this file except in compliance with the License.
  14466. * You may obtain a copy of the License at
  14467. *
  14468. * http://www.apache.org/licenses/LICENSE-2.0
  14469. *
  14470. * Unless required by applicable law or agreed to in writing, software
  14471. * distributed under the License is distributed on an "AS IS" BASIS,
  14472. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14473. * See the License for the specific language governing permissions and
  14474. * limitations under the License.
  14475. */
  14476. const LOG_TAG$d = 'MemoryPersistence';
  14477. /**
  14478. * A memory-backed instance of Persistence. Data is stored only in RAM and
  14479. * not persisted across sessions.
  14480. */
  14481. class MemoryPersistence {
  14482. /**
  14483. * The constructor accepts a factory for creating a reference delegate. This
  14484. * allows both the delegate and this instance to have strong references to
  14485. * each other without having nullable fields that would then need to be
  14486. * checked or asserted on every access.
  14487. */
  14488. constructor(referenceDelegateFactory, serializer) {
  14489. this.mutationQueues = {};
  14490. this.overlays = {};
  14491. this.listenSequence = new ListenSequence(0);
  14492. this._started = false;
  14493. this._started = true;
  14494. this.referenceDelegate = referenceDelegateFactory(this);
  14495. this.targetCache = new MemoryTargetCache(this);
  14496. const sizer = (doc) => this.referenceDelegate.documentSize(doc);
  14497. this.indexManager = new MemoryIndexManager();
  14498. this.remoteDocumentCache = newMemoryRemoteDocumentCache(sizer);
  14499. this.serializer = new LocalSerializer(serializer);
  14500. this.bundleCache = new MemoryBundleCache(this.serializer);
  14501. }
  14502. start() {
  14503. return Promise.resolve();
  14504. }
  14505. shutdown() {
  14506. // No durable state to ensure is closed on shutdown.
  14507. this._started = false;
  14508. return Promise.resolve();
  14509. }
  14510. get started() {
  14511. return this._started;
  14512. }
  14513. setDatabaseDeletedListener() {
  14514. // No op.
  14515. }
  14516. setNetworkEnabled() {
  14517. // No op.
  14518. }
  14519. getIndexManager(user) {
  14520. // We do not currently support indices for memory persistence, so we can
  14521. // return the same shared instance of the memory index manager.
  14522. return this.indexManager;
  14523. }
  14524. getDocumentOverlayCache(user) {
  14525. let overlay = this.overlays[user.toKey()];
  14526. if (!overlay) {
  14527. overlay = new MemoryDocumentOverlayCache();
  14528. this.overlays[user.toKey()] = overlay;
  14529. }
  14530. return overlay;
  14531. }
  14532. getMutationQueue(user, indexManager) {
  14533. let queue = this.mutationQueues[user.toKey()];
  14534. if (!queue) {
  14535. queue = new MemoryMutationQueue(indexManager, this.referenceDelegate);
  14536. this.mutationQueues[user.toKey()] = queue;
  14537. }
  14538. return queue;
  14539. }
  14540. getTargetCache() {
  14541. return this.targetCache;
  14542. }
  14543. getRemoteDocumentCache() {
  14544. return this.remoteDocumentCache;
  14545. }
  14546. getBundleCache() {
  14547. return this.bundleCache;
  14548. }
  14549. runTransaction(action, mode, transactionOperation) {
  14550. logDebug(LOG_TAG$d, 'Starting transaction:', action);
  14551. const txn = new MemoryTransaction(this.listenSequence.next());
  14552. this.referenceDelegate.onTransactionStarted();
  14553. return transactionOperation(txn)
  14554. .next(result => {
  14555. return this.referenceDelegate
  14556. .onTransactionCommitted(txn)
  14557. .next(() => result);
  14558. })
  14559. .toPromise()
  14560. .then(result => {
  14561. txn.raiseOnCommittedEvent();
  14562. return result;
  14563. });
  14564. }
  14565. mutationQueuesContainKey(transaction, key) {
  14566. return PersistencePromise.or(Object.values(this.mutationQueues).map(queue => () => queue.containsKey(transaction, key)));
  14567. }
  14568. }
  14569. /**
  14570. * Memory persistence is not actually transactional, but future implementations
  14571. * may have transaction-scoped state.
  14572. */
  14573. class MemoryTransaction extends PersistenceTransaction {
  14574. constructor(currentSequenceNumber) {
  14575. super();
  14576. this.currentSequenceNumber = currentSequenceNumber;
  14577. }
  14578. }
  14579. class MemoryEagerDelegate {
  14580. constructor(persistence) {
  14581. this.persistence = persistence;
  14582. /** Tracks all documents that are active in Query views. */
  14583. this.localViewReferences = new ReferenceSet();
  14584. /** The list of documents that are potentially GCed after each transaction. */
  14585. this._orphanedDocuments = null;
  14586. }
  14587. static factory(persistence) {
  14588. return new MemoryEagerDelegate(persistence);
  14589. }
  14590. get orphanedDocuments() {
  14591. if (!this._orphanedDocuments) {
  14592. throw fail();
  14593. }
  14594. else {
  14595. return this._orphanedDocuments;
  14596. }
  14597. }
  14598. addReference(txn, targetId, key) {
  14599. this.localViewReferences.addReference(key, targetId);
  14600. this.orphanedDocuments.delete(key.toString());
  14601. return PersistencePromise.resolve();
  14602. }
  14603. removeReference(txn, targetId, key) {
  14604. this.localViewReferences.removeReference(key, targetId);
  14605. this.orphanedDocuments.add(key.toString());
  14606. return PersistencePromise.resolve();
  14607. }
  14608. markPotentiallyOrphaned(txn, key) {
  14609. this.orphanedDocuments.add(key.toString());
  14610. return PersistencePromise.resolve();
  14611. }
  14612. removeTarget(txn, targetData) {
  14613. const orphaned = this.localViewReferences.removeReferencesForId(targetData.targetId);
  14614. orphaned.forEach(key => this.orphanedDocuments.add(key.toString()));
  14615. const cache = this.persistence.getTargetCache();
  14616. return cache
  14617. .getMatchingKeysForTargetId(txn, targetData.targetId)
  14618. .next(keys => {
  14619. keys.forEach(key => this.orphanedDocuments.add(key.toString()));
  14620. })
  14621. .next(() => cache.removeTargetData(txn, targetData));
  14622. }
  14623. onTransactionStarted() {
  14624. this._orphanedDocuments = new Set();
  14625. }
  14626. onTransactionCommitted(txn) {
  14627. // Remove newly orphaned documents.
  14628. const cache = this.persistence.getRemoteDocumentCache();
  14629. const changeBuffer = cache.newChangeBuffer();
  14630. return PersistencePromise.forEach(this.orphanedDocuments, (path) => {
  14631. const key = DocumentKey.fromPath(path);
  14632. return this.isReferenced(txn, key).next(isReferenced => {
  14633. if (!isReferenced) {
  14634. changeBuffer.removeEntry(key, SnapshotVersion.min());
  14635. }
  14636. });
  14637. }).next(() => {
  14638. this._orphanedDocuments = null;
  14639. return changeBuffer.apply(txn);
  14640. });
  14641. }
  14642. updateLimboDocument(txn, key) {
  14643. return this.isReferenced(txn, key).next(isReferenced => {
  14644. if (isReferenced) {
  14645. this.orphanedDocuments.delete(key.toString());
  14646. }
  14647. else {
  14648. this.orphanedDocuments.add(key.toString());
  14649. }
  14650. });
  14651. }
  14652. documentSize(doc) {
  14653. // For eager GC, we don't care about the document size, there are no size thresholds.
  14654. return 0;
  14655. }
  14656. isReferenced(txn, key) {
  14657. return PersistencePromise.or([
  14658. () => PersistencePromise.resolve(this.localViewReferences.containsKey(key)),
  14659. () => this.persistence.getTargetCache().containsKey(txn, key),
  14660. () => this.persistence.mutationQueuesContainKey(txn, key)
  14661. ]);
  14662. }
  14663. }
  14664. /**
  14665. * @license
  14666. * Copyright 2020 Google LLC
  14667. *
  14668. * Licensed under the Apache License, Version 2.0 (the "License");
  14669. * you may not use this file except in compliance with the License.
  14670. * You may obtain a copy of the License at
  14671. *
  14672. * http://www.apache.org/licenses/LICENSE-2.0
  14673. *
  14674. * Unless required by applicable law or agreed to in writing, software
  14675. * distributed under the License is distributed on an "AS IS" BASIS,
  14676. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14677. * See the License for the specific language governing permissions and
  14678. * limitations under the License.
  14679. */
  14680. /** Performs database creation and schema upgrades. */
  14681. class SchemaConverter {
  14682. constructor(serializer) {
  14683. this.serializer = serializer;
  14684. }
  14685. /**
  14686. * Performs database creation and schema upgrades.
  14687. *
  14688. * Note that in production, this method is only ever used to upgrade the schema
  14689. * to SCHEMA_VERSION. Different values of toVersion are only used for testing
  14690. * and local feature development.
  14691. */
  14692. createOrUpgrade(db, txn, fromVersion, toVersion) {
  14693. const simpleDbTransaction = new SimpleDbTransaction('createOrUpgrade', txn);
  14694. if (fromVersion < 1 && toVersion >= 1) {
  14695. createPrimaryClientStore(db);
  14696. createMutationQueue(db);
  14697. createQueryCache(db);
  14698. createLegacyRemoteDocumentCache(db);
  14699. }
  14700. // Migration 2 to populate the targetGlobal object no longer needed since
  14701. // migration 3 unconditionally clears it.
  14702. let p = PersistencePromise.resolve();
  14703. if (fromVersion < 3 && toVersion >= 3) {
  14704. // Brand new clients don't need to drop and recreate--only clients that
  14705. // potentially have corrupt data.
  14706. if (fromVersion !== 0) {
  14707. dropQueryCache(db);
  14708. createQueryCache(db);
  14709. }
  14710. p = p.next(() => writeEmptyTargetGlobalEntry(simpleDbTransaction));
  14711. }
  14712. if (fromVersion < 4 && toVersion >= 4) {
  14713. if (fromVersion !== 0) {
  14714. // Schema version 3 uses auto-generated keys to generate globally unique
  14715. // mutation batch IDs (this was previously ensured internally by the
  14716. // client). To migrate to the new schema, we have to read all mutations
  14717. // and write them back out. We preserve the existing batch IDs to guarantee
  14718. // consistency with other object stores. Any further mutation batch IDs will
  14719. // be auto-generated.
  14720. p = p.next(() => upgradeMutationBatchSchemaAndMigrateData(db, simpleDbTransaction));
  14721. }
  14722. p = p.next(() => {
  14723. createClientMetadataStore(db);
  14724. });
  14725. }
  14726. if (fromVersion < 5 && toVersion >= 5) {
  14727. p = p.next(() => this.removeAcknowledgedMutations(simpleDbTransaction));
  14728. }
  14729. if (fromVersion < 6 && toVersion >= 6) {
  14730. p = p.next(() => {
  14731. createDocumentGlobalStore(db);
  14732. return this.addDocumentGlobal(simpleDbTransaction);
  14733. });
  14734. }
  14735. if (fromVersion < 7 && toVersion >= 7) {
  14736. p = p.next(() => this.ensureSequenceNumbers(simpleDbTransaction));
  14737. }
  14738. if (fromVersion < 8 && toVersion >= 8) {
  14739. p = p.next(() => this.createCollectionParentIndex(db, simpleDbTransaction));
  14740. }
  14741. if (fromVersion < 9 && toVersion >= 9) {
  14742. p = p.next(() => {
  14743. // Multi-Tab used to manage its own changelog, but this has been moved
  14744. // to the DbRemoteDocument object store itself. Since the previous change
  14745. // log only contained transient data, we can drop its object store.
  14746. dropRemoteDocumentChangesStore(db);
  14747. // Note: Schema version 9 used to create a read time index for the
  14748. // RemoteDocumentCache. This is now done with schema version 13.
  14749. });
  14750. }
  14751. if (fromVersion < 10 && toVersion >= 10) {
  14752. p = p.next(() => this.rewriteCanonicalIds(simpleDbTransaction));
  14753. }
  14754. if (fromVersion < 11 && toVersion >= 11) {
  14755. p = p.next(() => {
  14756. createBundlesStore(db);
  14757. createNamedQueriesStore(db);
  14758. });
  14759. }
  14760. if (fromVersion < 12 && toVersion >= 12) {
  14761. p = p.next(() => {
  14762. createDocumentOverlayStore(db);
  14763. });
  14764. }
  14765. if (fromVersion < 13 && toVersion >= 13) {
  14766. p = p
  14767. .next(() => createRemoteDocumentCache(db))
  14768. .next(() => this.rewriteRemoteDocumentCache(db, simpleDbTransaction))
  14769. .next(() => db.deleteObjectStore(DbRemoteDocumentStore$1));
  14770. }
  14771. if (fromVersion < 14 && toVersion >= 14) {
  14772. p = p.next(() => this.runOverlayMigration(db, simpleDbTransaction));
  14773. }
  14774. if (fromVersion < 15 && toVersion >= 15) {
  14775. p = p.next(() => createFieldIndex(db));
  14776. }
  14777. return p;
  14778. }
  14779. addDocumentGlobal(txn) {
  14780. let byteSize = 0;
  14781. return txn
  14782. .store(DbRemoteDocumentStore$1)
  14783. .iterate((_, doc) => {
  14784. byteSize += dbDocumentSize(doc);
  14785. })
  14786. .next(() => {
  14787. const metadata = { byteSize };
  14788. return txn
  14789. .store(DbRemoteDocumentGlobalStore)
  14790. .put(DbRemoteDocumentGlobalKey, metadata);
  14791. });
  14792. }
  14793. removeAcknowledgedMutations(txn) {
  14794. const queuesStore = txn.store(DbMutationQueueStore);
  14795. const mutationsStore = txn.store(DbMutationBatchStore);
  14796. return queuesStore.loadAll().next(queues => {
  14797. return PersistencePromise.forEach(queues, (queue) => {
  14798. const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
  14799. return mutationsStore
  14800. .loadAll(DbMutationBatchUserMutationsIndex, range)
  14801. .next(dbBatches => {
  14802. return PersistencePromise.forEach(dbBatches, (dbBatch) => {
  14803. hardAssert(dbBatch.userId === queue.userId);
  14804. const batch = fromDbMutationBatch(this.serializer, dbBatch);
  14805. return removeMutationBatch(txn, queue.userId, batch).next(() => { });
  14806. });
  14807. });
  14808. });
  14809. });
  14810. }
  14811. /**
  14812. * Ensures that every document in the remote document cache has a corresponding sentinel row
  14813. * with a sequence number. Missing rows are given the most recently used sequence number.
  14814. */
  14815. ensureSequenceNumbers(txn) {
  14816. const documentTargetStore = txn.store(DbTargetDocumentStore);
  14817. const documentsStore = txn.store(DbRemoteDocumentStore$1);
  14818. const globalTargetStore = txn.store(DbTargetGlobalStore);
  14819. return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
  14820. const writeSentinelKey = (path) => {
  14821. return documentTargetStore.put({
  14822. targetId: 0,
  14823. path: encodeResourcePath(path),
  14824. sequenceNumber: metadata.highestListenSequenceNumber
  14825. });
  14826. };
  14827. const promises = [];
  14828. return documentsStore
  14829. .iterate((key, doc) => {
  14830. const path = new ResourcePath(key);
  14831. const docSentinelKey = sentinelKey(path);
  14832. promises.push(documentTargetStore.get(docSentinelKey).next(maybeSentinel => {
  14833. if (!maybeSentinel) {
  14834. return writeSentinelKey(path);
  14835. }
  14836. else {
  14837. return PersistencePromise.resolve();
  14838. }
  14839. }));
  14840. })
  14841. .next(() => PersistencePromise.waitFor(promises));
  14842. });
  14843. }
  14844. createCollectionParentIndex(db, txn) {
  14845. // Create the index.
  14846. db.createObjectStore(DbCollectionParentStore, {
  14847. keyPath: DbCollectionParentKeyPath
  14848. });
  14849. const collectionParentsStore = txn.store(DbCollectionParentStore);
  14850. // Helper to add an index entry iff we haven't already written it.
  14851. const cache = new MemoryCollectionParentIndex();
  14852. const addEntry = (collectionPath) => {
  14853. if (cache.add(collectionPath)) {
  14854. const collectionId = collectionPath.lastSegment();
  14855. const parentPath = collectionPath.popLast();
  14856. return collectionParentsStore.put({
  14857. collectionId,
  14858. parent: encodeResourcePath(parentPath)
  14859. });
  14860. }
  14861. };
  14862. // Index existing remote documents.
  14863. return txn
  14864. .store(DbRemoteDocumentStore$1)
  14865. .iterate({ keysOnly: true }, (pathSegments, _) => {
  14866. const path = new ResourcePath(pathSegments);
  14867. return addEntry(path.popLast());
  14868. })
  14869. .next(() => {
  14870. // Index existing mutations.
  14871. return txn
  14872. .store(DbDocumentMutationStore)
  14873. .iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
  14874. const path = decodeResourcePath(encodedPath);
  14875. return addEntry(path.popLast());
  14876. });
  14877. });
  14878. }
  14879. rewriteCanonicalIds(txn) {
  14880. const targetStore = txn.store(DbTargetStore);
  14881. return targetStore.iterate((key, originalDbTarget) => {
  14882. const originalTargetData = fromDbTarget(originalDbTarget);
  14883. const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
  14884. return targetStore.put(updatedDbTarget);
  14885. });
  14886. }
  14887. rewriteRemoteDocumentCache(db, transaction) {
  14888. const legacyRemoteDocumentStore = transaction.store(DbRemoteDocumentStore$1);
  14889. const writes = [];
  14890. return legacyRemoteDocumentStore
  14891. .iterate((_, legacyDocument) => {
  14892. const remoteDocumentStore = transaction.store(DbRemoteDocumentStore);
  14893. const path = extractKey(legacyDocument).path.toArray();
  14894. const dbRemoteDocument = {
  14895. prefixPath: path.slice(0, path.length - 2),
  14896. collectionGroup: path[path.length - 2],
  14897. documentId: path[path.length - 1],
  14898. readTime: legacyDocument.readTime || [0, 0],
  14899. unknownDocument: legacyDocument.unknownDocument,
  14900. noDocument: legacyDocument.noDocument,
  14901. document: legacyDocument.document,
  14902. hasCommittedMutations: !!legacyDocument.hasCommittedMutations
  14903. };
  14904. writes.push(remoteDocumentStore.put(dbRemoteDocument));
  14905. })
  14906. .next(() => PersistencePromise.waitFor(writes));
  14907. }
  14908. runOverlayMigration(db, transaction) {
  14909. const mutationsStore = transaction.store(DbMutationBatchStore);
  14910. const remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
  14911. const memoryPersistence = new MemoryPersistence(MemoryEagerDelegate.factory, this.serializer.remoteSerializer);
  14912. return mutationsStore.loadAll().next(dbBatches => {
  14913. const userToDocumentSet = new Map();
  14914. dbBatches.forEach(dbBatch => {
  14915. var _a;
  14916. let documentSet = (_a = userToDocumentSet.get(dbBatch.userId)) !== null && _a !== void 0 ? _a : documentKeySet();
  14917. const batch = fromDbMutationBatch(this.serializer, dbBatch);
  14918. batch.keys().forEach(key => (documentSet = documentSet.add(key)));
  14919. userToDocumentSet.set(dbBatch.userId, documentSet);
  14920. });
  14921. return PersistencePromise.forEach(userToDocumentSet, (allDocumentKeysForUser, userId) => {
  14922. const user = new User(userId);
  14923. const documentOverlayCache = IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
  14924. // NOTE: The index manager and the reference delegate are
  14925. // irrelevant for the purpose of recalculating and saving
  14926. // overlays. We can therefore simply use the memory
  14927. // implementation.
  14928. const indexManager = memoryPersistence.getIndexManager(user);
  14929. const mutationQueue = IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, memoryPersistence.referenceDelegate);
  14930. const localDocumentsView = new LocalDocumentsView(remoteDocumentCache, mutationQueue, documentOverlayCache, indexManager);
  14931. return localDocumentsView
  14932. .recalculateAndSaveOverlaysForDocumentKeys(new IndexedDbTransaction(transaction, ListenSequence.INVALID), allDocumentKeysForUser)
  14933. .next();
  14934. });
  14935. });
  14936. }
  14937. }
  14938. function sentinelKey(path) {
  14939. return [0, encodeResourcePath(path)];
  14940. }
  14941. function createPrimaryClientStore(db) {
  14942. db.createObjectStore(DbPrimaryClientStore);
  14943. }
  14944. function createMutationQueue(db) {
  14945. db.createObjectStore(DbMutationQueueStore, {
  14946. keyPath: DbMutationQueueKeyPath
  14947. });
  14948. const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
  14949. keyPath: DbMutationBatchKeyPath,
  14950. autoIncrement: true
  14951. });
  14952. mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
  14953. db.createObjectStore(DbDocumentMutationStore);
  14954. }
  14955. /**
  14956. * Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
  14957. * and rewrites all data.
  14958. */
  14959. function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
  14960. const v1MutationsStore = txn.store(DbMutationBatchStore);
  14961. return v1MutationsStore.loadAll().next(existingMutations => {
  14962. db.deleteObjectStore(DbMutationBatchStore);
  14963. const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
  14964. keyPath: DbMutationBatchKeyPath,
  14965. autoIncrement: true
  14966. });
  14967. mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
  14968. const v3MutationsStore = txn.store(DbMutationBatchStore);
  14969. const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
  14970. return PersistencePromise.waitFor(writeAll);
  14971. });
  14972. }
  14973. function createLegacyRemoteDocumentCache(db) {
  14974. db.createObjectStore(DbRemoteDocumentStore$1);
  14975. }
  14976. function createRemoteDocumentCache(db) {
  14977. const remoteDocumentStore = db.createObjectStore(DbRemoteDocumentStore, {
  14978. keyPath: DbRemoteDocumentKeyPath
  14979. });
  14980. remoteDocumentStore.createIndex(DbRemoteDocumentDocumentKeyIndex, DbRemoteDocumentDocumentKeyIndexPath);
  14981. remoteDocumentStore.createIndex(DbRemoteDocumentCollectionGroupIndex, DbRemoteDocumentCollectionGroupIndexPath);
  14982. }
  14983. function createDocumentGlobalStore(db) {
  14984. db.createObjectStore(DbRemoteDocumentGlobalStore);
  14985. }
  14986. function createQueryCache(db) {
  14987. const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
  14988. keyPath: DbTargetDocumentKeyPath
  14989. });
  14990. targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
  14991. const targetStore = db.createObjectStore(DbTargetStore, {
  14992. keyPath: DbTargetKeyPath
  14993. });
  14994. // NOTE: This is unique only because the TargetId is the suffix.
  14995. targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
  14996. db.createObjectStore(DbTargetGlobalStore);
  14997. }
  14998. function dropQueryCache(db) {
  14999. db.deleteObjectStore(DbTargetDocumentStore);
  15000. db.deleteObjectStore(DbTargetStore);
  15001. db.deleteObjectStore(DbTargetGlobalStore);
  15002. }
  15003. function dropRemoteDocumentChangesStore(db) {
  15004. if (db.objectStoreNames.contains('remoteDocumentChanges')) {
  15005. db.deleteObjectStore('remoteDocumentChanges');
  15006. }
  15007. }
  15008. /**
  15009. * Creates the target global singleton row.
  15010. *
  15011. * @param txn - The version upgrade transaction for indexeddb
  15012. */
  15013. function writeEmptyTargetGlobalEntry(txn) {
  15014. const globalStore = txn.store(DbTargetGlobalStore);
  15015. const metadata = {
  15016. highestTargetId: 0,
  15017. highestListenSequenceNumber: 0,
  15018. lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
  15019. targetCount: 0
  15020. };
  15021. return globalStore.put(DbTargetGlobalKey, metadata);
  15022. }
  15023. function createClientMetadataStore(db) {
  15024. db.createObjectStore(DbClientMetadataStore, {
  15025. keyPath: DbClientMetadataKeyPath
  15026. });
  15027. }
  15028. function createBundlesStore(db) {
  15029. db.createObjectStore(DbBundleStore, {
  15030. keyPath: DbBundleKeyPath
  15031. });
  15032. }
  15033. function createNamedQueriesStore(db) {
  15034. db.createObjectStore(DbNamedQueryStore, {
  15035. keyPath: DbNamedQueryKeyPath
  15036. });
  15037. }
  15038. function createFieldIndex(db) {
  15039. const indexConfigurationStore = db.createObjectStore(DbIndexConfigurationStore, {
  15040. keyPath: DbIndexConfigurationKeyPath,
  15041. autoIncrement: true
  15042. });
  15043. indexConfigurationStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
  15044. const indexStateStore = db.createObjectStore(DbIndexStateStore, {
  15045. keyPath: DbIndexStateKeyPath
  15046. });
  15047. indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
  15048. const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
  15049. keyPath: DbIndexEntryKeyPath
  15050. });
  15051. indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
  15052. }
  15053. function createDocumentOverlayStore(db) {
  15054. const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
  15055. keyPath: DbDocumentOverlayKeyPath
  15056. });
  15057. documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
  15058. documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
  15059. }
  15060. function extractKey(remoteDoc) {
  15061. if (remoteDoc.document) {
  15062. return new DocumentKey(ResourcePath.fromString(remoteDoc.document.name).popFirst(5));
  15063. }
  15064. else if (remoteDoc.noDocument) {
  15065. return DocumentKey.fromSegments(remoteDoc.noDocument.path);
  15066. }
  15067. else if (remoteDoc.unknownDocument) {
  15068. return DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
  15069. }
  15070. else {
  15071. return fail();
  15072. }
  15073. }
  15074. /**
  15075. * @license
  15076. * Copyright 2017 Google LLC
  15077. *
  15078. * Licensed under the Apache License, Version 2.0 (the "License");
  15079. * you may not use this file except in compliance with the License.
  15080. * You may obtain a copy of the License at
  15081. *
  15082. * http://www.apache.org/licenses/LICENSE-2.0
  15083. *
  15084. * Unless required by applicable law or agreed to in writing, software
  15085. * distributed under the License is distributed on an "AS IS" BASIS,
  15086. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15087. * See the License for the specific language governing permissions and
  15088. * limitations under the License.
  15089. */
  15090. const LOG_TAG$c = 'IndexedDbPersistence';
  15091. /**
  15092. * Oldest acceptable age in milliseconds for client metadata before the client
  15093. * is considered inactive and its associated data is garbage collected.
  15094. */
  15095. const MAX_CLIENT_AGE_MS = 30 * 60 * 1000; // 30 minutes
  15096. /**
  15097. * Oldest acceptable metadata age for clients that may participate in the
  15098. * primary lease election. Clients that have not updated their client metadata
  15099. * within 5 seconds are not eligible to receive a primary lease.
  15100. */
  15101. const MAX_PRIMARY_ELIGIBLE_AGE_MS = 5000;
  15102. /**
  15103. * The interval at which clients will update their metadata, including
  15104. * refreshing their primary lease if held or potentially trying to acquire it if
  15105. * not held.
  15106. *
  15107. * Primary clients may opportunistically refresh their metadata earlier
  15108. * if they're already performing an IndexedDB operation.
  15109. */
  15110. const CLIENT_METADATA_REFRESH_INTERVAL_MS = 4000;
  15111. /** User-facing error when the primary lease is required but not available. */
  15112. const PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG = 'Failed to obtain exclusive access to the persistence layer. To allow ' +
  15113. 'shared access, multi-tab synchronization has to be enabled in all tabs. ' +
  15114. 'If you are using `experimentalForceOwningTab:true`, make sure that only ' +
  15115. 'one tab has persistence enabled at any given time.';
  15116. const UNSUPPORTED_PLATFORM_ERROR_MSG = 'This platform is either missing IndexedDB or is known to have ' +
  15117. 'an incomplete implementation. Offline persistence has been disabled.';
  15118. // The format of the LocalStorage key that stores zombied client is:
  15119. // firestore_zombie_<persistence_prefix>_<instance_key>
  15120. const ZOMBIED_CLIENTS_KEY_PREFIX = 'firestore_zombie';
  15121. /**
  15122. * The name of the main (and currently only) IndexedDB database. This name is
  15123. * appended to the prefix provided to the IndexedDbPersistence constructor.
  15124. */
  15125. const MAIN_DATABASE = 'main';
  15126. /**
  15127. * An IndexedDB-backed instance of Persistence. Data is stored persistently
  15128. * across sessions.
  15129. *
  15130. * On Web only, the Firestore SDKs support shared access to its persistence
  15131. * layer. This allows multiple browser tabs to read and write to IndexedDb and
  15132. * to synchronize state even without network connectivity. Shared access is
  15133. * currently optional and not enabled unless all clients invoke
  15134. * `enablePersistence()` with `{synchronizeTabs:true}`.
  15135. *
  15136. * In multi-tab mode, if multiple clients are active at the same time, the SDK
  15137. * will designate one client as the “primary client”. An effort is made to pick
  15138. * a visible, network-connected and active client, and this client is
  15139. * responsible for letting other clients know about its presence. The primary
  15140. * client writes a unique client-generated identifier (the client ID) to
  15141. * IndexedDb’s “owner” store every 4 seconds. If the primary client fails to
  15142. * update this entry, another client can acquire the lease and take over as
  15143. * primary.
  15144. *
  15145. * Some persistence operations in the SDK are designated as primary-client only
  15146. * operations. This includes the acknowledgment of mutations and all updates of
  15147. * remote documents. The effects of these operations are written to persistence
  15148. * and then broadcast to other tabs via LocalStorage (see
  15149. * `WebStorageSharedClientState`), which then refresh their state from
  15150. * persistence.
  15151. *
  15152. * Similarly, the primary client listens to notifications sent by secondary
  15153. * clients to discover persistence changes written by secondary clients, such as
  15154. * the addition of new mutations and query targets.
  15155. *
  15156. * If multi-tab is not enabled and another tab already obtained the primary
  15157. * lease, IndexedDbPersistence enters a failed state and all subsequent
  15158. * operations will automatically fail.
  15159. *
  15160. * Additionally, there is an optimization so that when a tab is closed, the
  15161. * primary lease is released immediately (this is especially important to make
  15162. * sure that a refreshed tab is able to immediately re-acquire the primary
  15163. * lease). Unfortunately, IndexedDB cannot be reliably used in window.unload
  15164. * since it is an asynchronous API. So in addition to attempting to give up the
  15165. * lease, the leaseholder writes its client ID to a "zombiedClient" entry in
  15166. * LocalStorage which acts as an indicator that another tab should go ahead and
  15167. * take the primary lease immediately regardless of the current lease timestamp.
  15168. *
  15169. * TODO(b/114226234): Remove `synchronizeTabs` section when multi-tab is no
  15170. * longer optional.
  15171. */
  15172. class IndexedDbPersistence {
  15173. constructor(
  15174. /**
  15175. * Whether to synchronize the in-memory state of multiple tabs and share
  15176. * access to local persistence.
  15177. */
  15178. allowTabSynchronization, persistenceKey, clientId, lruParams, queue, window, document, serializer, sequenceNumberSyncer,
  15179. /**
  15180. * If set to true, forcefully obtains database access. Existing tabs will
  15181. * no longer be able to access IndexedDB.
  15182. */
  15183. forceOwningTab, schemaVersion = SCHEMA_VERSION) {
  15184. this.allowTabSynchronization = allowTabSynchronization;
  15185. this.persistenceKey = persistenceKey;
  15186. this.clientId = clientId;
  15187. this.queue = queue;
  15188. this.window = window;
  15189. this.document = document;
  15190. this.sequenceNumberSyncer = sequenceNumberSyncer;
  15191. this.forceOwningTab = forceOwningTab;
  15192. this.schemaVersion = schemaVersion;
  15193. this.listenSequence = null;
  15194. this._started = false;
  15195. this.isPrimary = false;
  15196. this.networkEnabled = true;
  15197. /** Our window.unload handler, if registered. */
  15198. this.windowUnloadHandler = null;
  15199. this.inForeground = false;
  15200. /** Our 'visibilitychange' listener if registered. */
  15201. this.documentVisibilityHandler = null;
  15202. /** The client metadata refresh task. */
  15203. this.clientMetadataRefresher = null;
  15204. /** The last time we garbage collected the client metadata object store. */
  15205. this.lastGarbageCollectionTime = Number.NEGATIVE_INFINITY;
  15206. /** A listener to notify on primary state changes. */
  15207. this.primaryStateListener = _ => Promise.resolve();
  15208. if (!IndexedDbPersistence.isAvailable()) {
  15209. throw new FirestoreError(Code.UNIMPLEMENTED, UNSUPPORTED_PLATFORM_ERROR_MSG);
  15210. }
  15211. this.referenceDelegate = new IndexedDbLruDelegateImpl(this, lruParams);
  15212. this.dbName = persistenceKey + MAIN_DATABASE;
  15213. this.serializer = new LocalSerializer(serializer);
  15214. this.simpleDb = new SimpleDb(this.dbName, this.schemaVersion, new SchemaConverter(this.serializer));
  15215. this.targetCache = new IndexedDbTargetCache(this.referenceDelegate, this.serializer);
  15216. this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
  15217. this.bundleCache = new IndexedDbBundleCache();
  15218. if (this.window && this.window.localStorage) {
  15219. this.webStorage = this.window.localStorage;
  15220. }
  15221. else {
  15222. this.webStorage = null;
  15223. if (forceOwningTab === false) {
  15224. logError(LOG_TAG$c, 'LocalStorage is unavailable. As a result, persistence may not work ' +
  15225. 'reliably. In particular enablePersistence() could fail immediately ' +
  15226. 'after refreshing the page.');
  15227. }
  15228. }
  15229. }
  15230. /**
  15231. * Attempt to start IndexedDb persistence.
  15232. *
  15233. * @returns Whether persistence was enabled.
  15234. */
  15235. start() {
  15236. // NOTE: This is expected to fail sometimes (in the case of another tab
  15237. // already having the persistence lock), so it's the first thing we should
  15238. // do.
  15239. return this.updateClientMetadataAndTryBecomePrimary()
  15240. .then(() => {
  15241. if (!this.isPrimary && !this.allowTabSynchronization) {
  15242. // Fail `start()` if `synchronizeTabs` is disabled and we cannot
  15243. // obtain the primary lease.
  15244. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG);
  15245. }
  15246. this.attachVisibilityHandler();
  15247. this.attachWindowUnloadHook();
  15248. this.scheduleClientMetadataAndPrimaryLeaseRefreshes();
  15249. return this.runTransaction('getHighestListenSequenceNumber', 'readonly', txn => this.targetCache.getHighestSequenceNumber(txn));
  15250. })
  15251. .then(highestListenSequenceNumber => {
  15252. this.listenSequence = new ListenSequence(highestListenSequenceNumber, this.sequenceNumberSyncer);
  15253. })
  15254. .then(() => {
  15255. this._started = true;
  15256. })
  15257. .catch(reason => {
  15258. this.simpleDb && this.simpleDb.close();
  15259. return Promise.reject(reason);
  15260. });
  15261. }
  15262. /**
  15263. * Registers a listener that gets called when the primary state of the
  15264. * instance changes. Upon registering, this listener is invoked immediately
  15265. * with the current primary state.
  15266. *
  15267. * PORTING NOTE: This is only used for Web multi-tab.
  15268. */
  15269. setPrimaryStateListener(primaryStateListener) {
  15270. this.primaryStateListener = async (primaryState) => {
  15271. if (this.started) {
  15272. return primaryStateListener(primaryState);
  15273. }
  15274. };
  15275. return primaryStateListener(this.isPrimary);
  15276. }
  15277. /**
  15278. * Registers a listener that gets called when the database receives a
  15279. * version change event indicating that it has deleted.
  15280. *
  15281. * PORTING NOTE: This is only used for Web multi-tab.
  15282. */
  15283. setDatabaseDeletedListener(databaseDeletedListener) {
  15284. this.simpleDb.setVersionChangeListener(async (event) => {
  15285. // Check if an attempt is made to delete IndexedDB.
  15286. if (event.newVersion === null) {
  15287. await databaseDeletedListener();
  15288. }
  15289. });
  15290. }
  15291. /**
  15292. * Adjusts the current network state in the client's metadata, potentially
  15293. * affecting the primary lease.
  15294. *
  15295. * PORTING NOTE: This is only used for Web multi-tab.
  15296. */
  15297. setNetworkEnabled(networkEnabled) {
  15298. if (this.networkEnabled !== networkEnabled) {
  15299. this.networkEnabled = networkEnabled;
  15300. // Schedule a primary lease refresh for immediate execution. The eventual
  15301. // lease update will be propagated via `primaryStateListener`.
  15302. this.queue.enqueueAndForget(async () => {
  15303. if (this.started) {
  15304. await this.updateClientMetadataAndTryBecomePrimary();
  15305. }
  15306. });
  15307. }
  15308. }
  15309. /**
  15310. * Updates the client metadata in IndexedDb and attempts to either obtain or
  15311. * extend the primary lease for the local client. Asynchronously notifies the
  15312. * primary state listener if the client either newly obtained or released its
  15313. * primary lease.
  15314. */
  15315. updateClientMetadataAndTryBecomePrimary() {
  15316. return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
  15317. const metadataStore = clientMetadataStore(txn);
  15318. return metadataStore
  15319. .put({
  15320. clientId: this.clientId,
  15321. updateTimeMs: Date.now(),
  15322. networkEnabled: this.networkEnabled,
  15323. inForeground: this.inForeground
  15324. })
  15325. .next(() => {
  15326. if (this.isPrimary) {
  15327. return this.verifyPrimaryLease(txn).next(success => {
  15328. if (!success) {
  15329. this.isPrimary = false;
  15330. this.queue.enqueueRetryable(() => this.primaryStateListener(false));
  15331. }
  15332. });
  15333. }
  15334. })
  15335. .next(() => this.canActAsPrimary(txn))
  15336. .next(canActAsPrimary => {
  15337. if (this.isPrimary && !canActAsPrimary) {
  15338. return this.releasePrimaryLeaseIfHeld(txn).next(() => false);
  15339. }
  15340. else if (canActAsPrimary) {
  15341. return this.acquireOrExtendPrimaryLease(txn).next(() => true);
  15342. }
  15343. else {
  15344. return /* canActAsPrimary= */ false;
  15345. }
  15346. });
  15347. })
  15348. .catch(e => {
  15349. if (isIndexedDbTransactionError(e)) {
  15350. logDebug(LOG_TAG$c, 'Failed to extend owner lease: ', e);
  15351. // Proceed with the existing state. Any subsequent access to
  15352. // IndexedDB will verify the lease.
  15353. return this.isPrimary;
  15354. }
  15355. if (!this.allowTabSynchronization) {
  15356. throw e;
  15357. }
  15358. logDebug(LOG_TAG$c, 'Releasing owner lease after error during lease refresh', e);
  15359. return /* isPrimary= */ false;
  15360. })
  15361. .then(isPrimary => {
  15362. if (this.isPrimary !== isPrimary) {
  15363. this.queue.enqueueRetryable(() => this.primaryStateListener(isPrimary));
  15364. }
  15365. this.isPrimary = isPrimary;
  15366. });
  15367. }
  15368. verifyPrimaryLease(txn) {
  15369. const store = primaryClientStore(txn);
  15370. return store.get(DbPrimaryClientKey).next(primaryClient => {
  15371. return PersistencePromise.resolve(this.isLocalClient(primaryClient));
  15372. });
  15373. }
  15374. removeClientMetadata(txn) {
  15375. const metadataStore = clientMetadataStore(txn);
  15376. return metadataStore.delete(this.clientId);
  15377. }
  15378. /**
  15379. * If the garbage collection threshold has passed, prunes the
  15380. * RemoteDocumentChanges and the ClientMetadata store based on the last update
  15381. * time of all clients.
  15382. */
  15383. async maybeGarbageCollectMultiClientState() {
  15384. if (this.isPrimary &&
  15385. !this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
  15386. this.lastGarbageCollectionTime = Date.now();
  15387. const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
  15388. const metadataStore = getStore(txn, DbClientMetadataStore);
  15389. return metadataStore.loadAll().next(existingClients => {
  15390. const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
  15391. const inactive = existingClients.filter(client => active.indexOf(client) === -1);
  15392. // Delete metadata for clients that are no longer considered active.
  15393. return PersistencePromise.forEach(inactive, (inactiveClient) => metadataStore.delete(inactiveClient.clientId)).next(() => inactive);
  15394. });
  15395. }).catch(() => {
  15396. // Ignore primary lease violations or any other type of error. The next
  15397. // primary will run `maybeGarbageCollectMultiClientState()` again.
  15398. // We don't use `ignoreIfPrimaryLeaseLoss()` since we don't want to depend
  15399. // on LocalStore.
  15400. return [];
  15401. });
  15402. // Delete potential leftover entries that may continue to mark the
  15403. // inactive clients as zombied in LocalStorage.
  15404. // Ideally we'd delete the IndexedDb and LocalStorage zombie entries for
  15405. // the client atomically, but we can't. So we opt to delete the IndexedDb
  15406. // entries first to avoid potentially reviving a zombied client.
  15407. if (this.webStorage) {
  15408. for (const inactiveClient of inactiveClients) {
  15409. this.webStorage.removeItem(this.zombiedClientLocalStorageKey(inactiveClient.clientId));
  15410. }
  15411. }
  15412. }
  15413. }
  15414. /**
  15415. * Schedules a recurring timer to update the client metadata and to either
  15416. * extend or acquire the primary lease if the client is eligible.
  15417. */
  15418. scheduleClientMetadataAndPrimaryLeaseRefreshes() {
  15419. this.clientMetadataRefresher = this.queue.enqueueAfterDelay("client_metadata_refresh" /* TimerId.ClientMetadataRefresh */, CLIENT_METADATA_REFRESH_INTERVAL_MS, () => {
  15420. return this.updateClientMetadataAndTryBecomePrimary()
  15421. .then(() => this.maybeGarbageCollectMultiClientState())
  15422. .then(() => this.scheduleClientMetadataAndPrimaryLeaseRefreshes());
  15423. });
  15424. }
  15425. /** Checks whether `client` is the local client. */
  15426. isLocalClient(client) {
  15427. return client ? client.ownerId === this.clientId : false;
  15428. }
  15429. /**
  15430. * Evaluate the state of all active clients and determine whether the local
  15431. * client is or can act as the holder of the primary lease. Returns whether
  15432. * the client is eligible for the lease, but does not actually acquire it.
  15433. * May return 'false' even if there is no active leaseholder and another
  15434. * (foreground) client should become leaseholder instead.
  15435. */
  15436. canActAsPrimary(txn) {
  15437. if (this.forceOwningTab) {
  15438. return PersistencePromise.resolve(true);
  15439. }
  15440. const store = primaryClientStore(txn);
  15441. return store
  15442. .get(DbPrimaryClientKey)
  15443. .next(currentPrimary => {
  15444. const currentLeaseIsValid = currentPrimary !== null &&
  15445. this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
  15446. !this.isClientZombied(currentPrimary.ownerId);
  15447. // A client is eligible for the primary lease if:
  15448. // - its network is enabled and the client's tab is in the foreground.
  15449. // - its network is enabled and no other client's tab is in the
  15450. // foreground.
  15451. // - every clients network is disabled and the client's tab is in the
  15452. // foreground.
  15453. // - every clients network is disabled and no other client's tab is in
  15454. // the foreground.
  15455. // - the `forceOwningTab` setting was passed in.
  15456. if (currentLeaseIsValid) {
  15457. if (this.isLocalClient(currentPrimary) && this.networkEnabled) {
  15458. return true;
  15459. }
  15460. if (!this.isLocalClient(currentPrimary)) {
  15461. if (!currentPrimary.allowTabSynchronization) {
  15462. // Fail the `canActAsPrimary` check if the current leaseholder has
  15463. // not opted into multi-tab synchronization. If this happens at
  15464. // client startup, we reject the Promise returned by
  15465. // `enablePersistence()` and the user can continue to use Firestore
  15466. // with in-memory persistence.
  15467. // If this fails during a lease refresh, we will instead block the
  15468. // AsyncQueue from executing further operations. Note that this is
  15469. // acceptable since mixing & matching different `synchronizeTabs`
  15470. // settings is not supported.
  15471. //
  15472. // TODO(b/114226234): Remove this check when `synchronizeTabs` can
  15473. // no longer be turned off.
  15474. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG);
  15475. }
  15476. return false;
  15477. }
  15478. }
  15479. if (this.networkEnabled && this.inForeground) {
  15480. return true;
  15481. }
  15482. return clientMetadataStore(txn)
  15483. .loadAll()
  15484. .next(existingClients => {
  15485. // Process all existing clients and determine whether at least one of
  15486. // them is better suited to obtain the primary lease.
  15487. const preferredCandidate = this.filterActiveClients(existingClients, MAX_PRIMARY_ELIGIBLE_AGE_MS).find(otherClient => {
  15488. if (this.clientId !== otherClient.clientId) {
  15489. const otherClientHasBetterNetworkState = !this.networkEnabled && otherClient.networkEnabled;
  15490. const otherClientHasBetterVisibility = !this.inForeground && otherClient.inForeground;
  15491. const otherClientHasSameNetworkState = this.networkEnabled === otherClient.networkEnabled;
  15492. if (otherClientHasBetterNetworkState ||
  15493. (otherClientHasBetterVisibility &&
  15494. otherClientHasSameNetworkState)) {
  15495. return true;
  15496. }
  15497. }
  15498. return false;
  15499. });
  15500. return preferredCandidate === undefined;
  15501. });
  15502. })
  15503. .next(canActAsPrimary => {
  15504. if (this.isPrimary !== canActAsPrimary) {
  15505. logDebug(LOG_TAG$c, `Client ${canActAsPrimary ? 'is' : 'is not'} eligible for a primary lease.`);
  15506. }
  15507. return canActAsPrimary;
  15508. });
  15509. }
  15510. async shutdown() {
  15511. // The shutdown() operations are idempotent and can be called even when
  15512. // start() aborted (e.g. because it couldn't acquire the persistence lease).
  15513. this._started = false;
  15514. this.markClientZombied();
  15515. if (this.clientMetadataRefresher) {
  15516. this.clientMetadataRefresher.cancel();
  15517. this.clientMetadataRefresher = null;
  15518. }
  15519. this.detachVisibilityHandler();
  15520. this.detachWindowUnloadHook();
  15521. // Use `SimpleDb.runTransaction` directly to avoid failing if another tab
  15522. // has obtained the primary lease.
  15523. await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
  15524. const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
  15525. return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
  15526. });
  15527. this.simpleDb.close();
  15528. // Remove the entry marking the client as zombied from LocalStorage since
  15529. // we successfully deleted its metadata from IndexedDb.
  15530. this.removeClientZombiedEntry();
  15531. }
  15532. /**
  15533. * Returns clients that are not zombied and have an updateTime within the
  15534. * provided threshold.
  15535. */
  15536. filterActiveClients(clients, activityThresholdMs) {
  15537. return clients.filter(client => this.isWithinAge(client.updateTimeMs, activityThresholdMs) &&
  15538. !this.isClientZombied(client.clientId));
  15539. }
  15540. /**
  15541. * Returns the IDs of the clients that are currently active. If multi-tab
  15542. * is not supported, returns an array that only contains the local client's
  15543. * ID.
  15544. *
  15545. * PORTING NOTE: This is only used for Web multi-tab.
  15546. */
  15547. getActiveClients() {
  15548. return this.runTransaction('getActiveClients', 'readonly', txn => {
  15549. return clientMetadataStore(txn)
  15550. .loadAll()
  15551. .next(clients => this.filterActiveClients(clients, MAX_CLIENT_AGE_MS).map(clientMetadata => clientMetadata.clientId));
  15552. });
  15553. }
  15554. get started() {
  15555. return this._started;
  15556. }
  15557. getMutationQueue(user, indexManager) {
  15558. return IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, this.referenceDelegate);
  15559. }
  15560. getTargetCache() {
  15561. return this.targetCache;
  15562. }
  15563. getRemoteDocumentCache() {
  15564. return this.remoteDocumentCache;
  15565. }
  15566. getIndexManager(user) {
  15567. return new IndexedDbIndexManager(user, this.serializer.remoteSerializer.databaseId);
  15568. }
  15569. getDocumentOverlayCache(user) {
  15570. return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
  15571. }
  15572. getBundleCache() {
  15573. return this.bundleCache;
  15574. }
  15575. runTransaction(action, mode, transactionOperation) {
  15576. logDebug(LOG_TAG$c, 'Starting transaction:', action);
  15577. const simpleDbMode = mode === 'readonly' ? 'readonly' : 'readwrite';
  15578. const objectStores = getObjectStores(this.schemaVersion);
  15579. let persistenceTransaction;
  15580. // Do all transactions as readwrite against all object stores, since we
  15581. // are the only reader/writer.
  15582. return this.simpleDb
  15583. .runTransaction(action, simpleDbMode, objectStores, simpleDbTxn => {
  15584. persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, this.listenSequence
  15585. ? this.listenSequence.next()
  15586. : ListenSequence.INVALID);
  15587. if (mode === 'readwrite-primary') {
  15588. // While we merely verify that we have (or can acquire) the lease
  15589. // immediately, we wait to extend the primary lease until after
  15590. // executing transactionOperation(). This ensures that even if the
  15591. // transactionOperation takes a long time, we'll use a recent
  15592. // leaseTimestampMs in the extended (or newly acquired) lease.
  15593. return this.verifyPrimaryLease(persistenceTransaction)
  15594. .next(holdsPrimaryLease => {
  15595. if (holdsPrimaryLease) {
  15596. return /* holdsPrimaryLease= */ true;
  15597. }
  15598. return this.canActAsPrimary(persistenceTransaction);
  15599. })
  15600. .next(holdsPrimaryLease => {
  15601. if (!holdsPrimaryLease) {
  15602. logError(`Failed to obtain primary lease for action '${action}'.`);
  15603. this.isPrimary = false;
  15604. this.queue.enqueueRetryable(() => this.primaryStateListener(false));
  15605. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_LOST_ERROR_MSG);
  15606. }
  15607. return transactionOperation(persistenceTransaction);
  15608. })
  15609. .next(result => {
  15610. return this.acquireOrExtendPrimaryLease(persistenceTransaction).next(() => result);
  15611. });
  15612. }
  15613. else {
  15614. return this.verifyAllowTabSynchronization(persistenceTransaction).next(() => transactionOperation(persistenceTransaction));
  15615. }
  15616. })
  15617. .then(result => {
  15618. persistenceTransaction.raiseOnCommittedEvent();
  15619. return result;
  15620. });
  15621. }
  15622. /**
  15623. * Verifies that the current tab is the primary leaseholder or alternatively
  15624. * that the leaseholder has opted into multi-tab synchronization.
  15625. */
  15626. // TODO(b/114226234): Remove this check when `synchronizeTabs` can no longer
  15627. // be turned off.
  15628. verifyAllowTabSynchronization(txn) {
  15629. const store = primaryClientStore(txn);
  15630. return store.get(DbPrimaryClientKey).next(currentPrimary => {
  15631. const currentLeaseIsValid = currentPrimary !== null &&
  15632. this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
  15633. !this.isClientZombied(currentPrimary.ownerId);
  15634. if (currentLeaseIsValid && !this.isLocalClient(currentPrimary)) {
  15635. if (!this.forceOwningTab &&
  15636. (!this.allowTabSynchronization ||
  15637. !currentPrimary.allowTabSynchronization)) {
  15638. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG);
  15639. }
  15640. }
  15641. });
  15642. }
  15643. /**
  15644. * Obtains or extends the new primary lease for the local client. This
  15645. * method does not verify that the client is eligible for this lease.
  15646. */
  15647. acquireOrExtendPrimaryLease(txn) {
  15648. const newPrimary = {
  15649. ownerId: this.clientId,
  15650. allowTabSynchronization: this.allowTabSynchronization,
  15651. leaseTimestampMs: Date.now()
  15652. };
  15653. return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
  15654. }
  15655. static isAvailable() {
  15656. return SimpleDb.isAvailable();
  15657. }
  15658. /** Checks the primary lease and removes it if we are the current primary. */
  15659. releasePrimaryLeaseIfHeld(txn) {
  15660. const store = primaryClientStore(txn);
  15661. return store.get(DbPrimaryClientKey).next(primaryClient => {
  15662. if (this.isLocalClient(primaryClient)) {
  15663. logDebug(LOG_TAG$c, 'Releasing primary lease.');
  15664. return store.delete(DbPrimaryClientKey);
  15665. }
  15666. else {
  15667. return PersistencePromise.resolve();
  15668. }
  15669. });
  15670. }
  15671. /** Verifies that `updateTimeMs` is within `maxAgeMs`. */
  15672. isWithinAge(updateTimeMs, maxAgeMs) {
  15673. const now = Date.now();
  15674. const minAcceptable = now - maxAgeMs;
  15675. const maxAcceptable = now;
  15676. if (updateTimeMs < minAcceptable) {
  15677. return false;
  15678. }
  15679. else if (updateTimeMs > maxAcceptable) {
  15680. logError(`Detected an update time that is in the future: ${updateTimeMs} > ${maxAcceptable}`);
  15681. return false;
  15682. }
  15683. return true;
  15684. }
  15685. attachVisibilityHandler() {
  15686. if (this.document !== null &&
  15687. typeof this.document.addEventListener === 'function') {
  15688. this.documentVisibilityHandler = () => {
  15689. this.queue.enqueueAndForget(() => {
  15690. this.inForeground = this.document.visibilityState === 'visible';
  15691. return this.updateClientMetadataAndTryBecomePrimary();
  15692. });
  15693. };
  15694. this.document.addEventListener('visibilitychange', this.documentVisibilityHandler);
  15695. this.inForeground = this.document.visibilityState === 'visible';
  15696. }
  15697. }
  15698. detachVisibilityHandler() {
  15699. if (this.documentVisibilityHandler) {
  15700. this.document.removeEventListener('visibilitychange', this.documentVisibilityHandler);
  15701. this.documentVisibilityHandler = null;
  15702. }
  15703. }
  15704. /**
  15705. * Attaches a window.unload handler that will synchronously write our
  15706. * clientId to a "zombie client id" location in LocalStorage. This can be used
  15707. * by tabs trying to acquire the primary lease to determine that the lease
  15708. * is no longer valid even if the timestamp is recent. This is particularly
  15709. * important for the refresh case (so the tab correctly re-acquires the
  15710. * primary lease). LocalStorage is used for this rather than IndexedDb because
  15711. * it is a synchronous API and so can be used reliably from an unload
  15712. * handler.
  15713. */
  15714. attachWindowUnloadHook() {
  15715. var _a;
  15716. if (typeof ((_a = this.window) === null || _a === void 0 ? void 0 : _a.addEventListener) === 'function') {
  15717. this.windowUnloadHandler = () => {
  15718. // Note: In theory, this should be scheduled on the AsyncQueue since it
  15719. // accesses internal state. We execute this code directly during shutdown
  15720. // to make sure it gets a chance to run.
  15721. this.markClientZombied();
  15722. if (util.isSafari() && navigator.appVersion.match(/Version\/1[45]/)) {
  15723. // On Safari 14 and 15, we do not run any cleanup actions as it might
  15724. // trigger a bug that prevents Safari from re-opening IndexedDB during
  15725. // the next page load.
  15726. // See https://bugs.webkit.org/show_bug.cgi?id=226547
  15727. this.queue.enterRestrictedMode(/* purgeExistingTasks= */ true);
  15728. }
  15729. this.queue.enqueueAndForget(() => {
  15730. // Attempt graceful shutdown (including releasing our primary lease),
  15731. // but there's no guarantee it will complete.
  15732. return this.shutdown();
  15733. });
  15734. };
  15735. this.window.addEventListener('pagehide', this.windowUnloadHandler);
  15736. }
  15737. }
  15738. detachWindowUnloadHook() {
  15739. if (this.windowUnloadHandler) {
  15740. this.window.removeEventListener('pagehide', this.windowUnloadHandler);
  15741. this.windowUnloadHandler = null;
  15742. }
  15743. }
  15744. /**
  15745. * Returns whether a client is "zombied" based on its LocalStorage entry.
  15746. * Clients become zombied when their tab closes without running all of the
  15747. * cleanup logic in `shutdown()`.
  15748. */
  15749. isClientZombied(clientId) {
  15750. var _a;
  15751. try {
  15752. const isZombied = ((_a = this.webStorage) === null || _a === void 0 ? void 0 : _a.getItem(this.zombiedClientLocalStorageKey(clientId))) !== null;
  15753. logDebug(LOG_TAG$c, `Client '${clientId}' ${isZombied ? 'is' : 'is not'} zombied in LocalStorage`);
  15754. return isZombied;
  15755. }
  15756. catch (e) {
  15757. // Gracefully handle if LocalStorage isn't working.
  15758. logError(LOG_TAG$c, 'Failed to get zombied client id.', e);
  15759. return false;
  15760. }
  15761. }
  15762. /**
  15763. * Record client as zombied (a client that had its tab closed). Zombied
  15764. * clients are ignored during primary tab selection.
  15765. */
  15766. markClientZombied() {
  15767. if (!this.webStorage) {
  15768. return;
  15769. }
  15770. try {
  15771. this.webStorage.setItem(this.zombiedClientLocalStorageKey(this.clientId), String(Date.now()));
  15772. }
  15773. catch (e) {
  15774. // Gracefully handle if LocalStorage isn't available / working.
  15775. logError('Failed to set zombie client id.', e);
  15776. }
  15777. }
  15778. /** Removes the zombied client entry if it exists. */
  15779. removeClientZombiedEntry() {
  15780. if (!this.webStorage) {
  15781. return;
  15782. }
  15783. try {
  15784. this.webStorage.removeItem(this.zombiedClientLocalStorageKey(this.clientId));
  15785. }
  15786. catch (e) {
  15787. // Ignore
  15788. }
  15789. }
  15790. zombiedClientLocalStorageKey(clientId) {
  15791. return `${ZOMBIED_CLIENTS_KEY_PREFIX}_${this.persistenceKey}_${clientId}`;
  15792. }
  15793. }
  15794. /**
  15795. * Helper to get a typed SimpleDbStore for the primary client object store.
  15796. */
  15797. function primaryClientStore(txn) {
  15798. return getStore(txn, DbPrimaryClientStore);
  15799. }
  15800. /**
  15801. * Helper to get a typed SimpleDbStore for the client metadata object store.
  15802. */
  15803. function clientMetadataStore(txn) {
  15804. return getStore(txn, DbClientMetadataStore);
  15805. }
  15806. /**
  15807. * Generates a string used as a prefix when storing data in IndexedDB and
  15808. * LocalStorage.
  15809. */
  15810. function indexedDbStoragePrefix(databaseId, persistenceKey) {
  15811. // Use two different prefix formats:
  15812. //
  15813. // * firestore / persistenceKey / projectID . databaseID / ...
  15814. // * firestore / persistenceKey / projectID / ...
  15815. //
  15816. // projectIDs are DNS-compatible names and cannot contain dots
  15817. // so there's no danger of collisions.
  15818. let database = databaseId.projectId;
  15819. if (!databaseId.isDefaultDatabase) {
  15820. database += '.' + databaseId.database;
  15821. }
  15822. return 'firestore/' + persistenceKey + '/' + database + '/';
  15823. }
  15824. async function indexedDbClearPersistence(persistenceKey) {
  15825. if (!SimpleDb.isAvailable()) {
  15826. return Promise.resolve();
  15827. }
  15828. const dbName = persistenceKey + MAIN_DATABASE;
  15829. await SimpleDb.delete(dbName);
  15830. }
  15831. /**
  15832. * @license
  15833. * Copyright 2017 Google LLC
  15834. *
  15835. * Licensed under the Apache License, Version 2.0 (the "License");
  15836. * you may not use this file except in compliance with the License.
  15837. * You may obtain a copy of the License at
  15838. *
  15839. * http://www.apache.org/licenses/LICENSE-2.0
  15840. *
  15841. * Unless required by applicable law or agreed to in writing, software
  15842. * distributed under the License is distributed on an "AS IS" BASIS,
  15843. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15844. * See the License for the specific language governing permissions and
  15845. * limitations under the License.
  15846. */
  15847. /**
  15848. * Compares two array for equality using comparator. The method computes the
  15849. * intersection and invokes `onAdd` for every element that is in `after` but not
  15850. * `before`. `onRemove` is invoked for every element in `before` but missing
  15851. * from `after`.
  15852. *
  15853. * The method creates a copy of both `before` and `after` and runs in O(n log
  15854. * n), where n is the size of the two lists.
  15855. *
  15856. * @param before - The elements that exist in the original array.
  15857. * @param after - The elements to diff against the original array.
  15858. * @param comparator - The comparator for the elements in before and after.
  15859. * @param onAdd - A function to invoke for every element that is part of `
  15860. * after` but not `before`.
  15861. * @param onRemove - A function to invoke for every element that is part of
  15862. * `before` but not `after`.
  15863. */
  15864. function diffArrays(before, after, comparator, onAdd, onRemove) {
  15865. before = [...before];
  15866. after = [...after];
  15867. before.sort(comparator);
  15868. after.sort(comparator);
  15869. const bLen = before.length;
  15870. const aLen = after.length;
  15871. let a = 0;
  15872. let b = 0;
  15873. while (a < aLen && b < bLen) {
  15874. const cmp = comparator(before[b], after[a]);
  15875. if (cmp < 0) {
  15876. // The element was removed if the next element in our ordered
  15877. // walkthrough is only in `before`.
  15878. onRemove(before[b++]);
  15879. }
  15880. else if (cmp > 0) {
  15881. // The element was added if the next element in our ordered walkthrough
  15882. // is only in `after`.
  15883. onAdd(after[a++]);
  15884. }
  15885. else {
  15886. a++;
  15887. b++;
  15888. }
  15889. }
  15890. while (a < aLen) {
  15891. onAdd(after[a++]);
  15892. }
  15893. while (b < bLen) {
  15894. onRemove(before[b++]);
  15895. }
  15896. }
  15897. /**
  15898. * @license
  15899. * Copyright 2020 Google LLC
  15900. *
  15901. * Licensed under the Apache License, Version 2.0 (the "License");
  15902. * you may not use this file except in compliance with the License.
  15903. * You may obtain a copy of the License at
  15904. *
  15905. * http://www.apache.org/licenses/LICENSE-2.0
  15906. *
  15907. * Unless required by applicable law or agreed to in writing, software
  15908. * distributed under the License is distributed on an "AS IS" BASIS,
  15909. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15910. * See the License for the specific language governing permissions and
  15911. * limitations under the License.
  15912. */
  15913. const LOG_TAG$b = 'LocalStore';
  15914. /**
  15915. * The maximum time to leave a resume token buffered without writing it out.
  15916. * This value is arbitrary: it's long enough to avoid several writes
  15917. * (possibly indefinitely if updates come more frequently than this) but
  15918. * short enough that restarting after crashing will still have a pretty
  15919. * recent resume token.
  15920. */
  15921. const RESUME_TOKEN_MAX_AGE_MICROS = 5 * 60 * 1e6;
  15922. /**
  15923. * Implements `LocalStore` interface.
  15924. *
  15925. * Note: some field defined in this class might have public access level, but
  15926. * the class is not exported so they are only accessible from this module.
  15927. * This is useful to implement optional features (like bundles) in free
  15928. * functions, such that they are tree-shakeable.
  15929. */
  15930. class LocalStoreImpl {
  15931. constructor(
  15932. /** Manages our in-memory or durable persistence. */
  15933. persistence, queryEngine, initialUser, serializer) {
  15934. this.persistence = persistence;
  15935. this.queryEngine = queryEngine;
  15936. this.serializer = serializer;
  15937. /**
  15938. * Maps a targetID to data about its target.
  15939. *
  15940. * PORTING NOTE: We are using an immutable data structure on Web to make re-runs
  15941. * of `applyRemoteEvent()` idempotent.
  15942. */
  15943. this.targetDataByTarget = new SortedMap(primitiveComparator);
  15944. /** Maps a target to its targetID. */
  15945. // TODO(wuandy): Evaluate if TargetId can be part of Target.
  15946. this.targetIdByTarget = new ObjectMap(t => canonifyTarget(t), targetEquals);
  15947. /**
  15948. * A per collection group index of the last read time processed by
  15949. * `getNewDocumentChanges()`.
  15950. *
  15951. * PORTING NOTE: This is only used for multi-tab synchronization.
  15952. */
  15953. this.collectionGroupReadTime = new Map();
  15954. this.remoteDocuments = persistence.getRemoteDocumentCache();
  15955. this.targetCache = persistence.getTargetCache();
  15956. this.bundleCache = persistence.getBundleCache();
  15957. this.initializeUserComponents(initialUser);
  15958. }
  15959. initializeUserComponents(user) {
  15960. // TODO(indexing): Add spec tests that test these components change after a
  15961. // user change
  15962. this.documentOverlayCache = this.persistence.getDocumentOverlayCache(user);
  15963. this.indexManager = this.persistence.getIndexManager(user);
  15964. this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
  15965. this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.documentOverlayCache, this.indexManager);
  15966. this.remoteDocuments.setIndexManager(this.indexManager);
  15967. this.queryEngine.initialize(this.localDocuments, this.indexManager);
  15968. }
  15969. collectGarbage(garbageCollector) {
  15970. return this.persistence.runTransaction('Collect garbage', 'readwrite-primary', txn => garbageCollector.collect(txn, this.targetDataByTarget));
  15971. }
  15972. }
  15973. function newLocalStore(
  15974. /** Manages our in-memory or durable persistence. */
  15975. persistence, queryEngine, initialUser, serializer) {
  15976. return new LocalStoreImpl(persistence, queryEngine, initialUser, serializer);
  15977. }
  15978. /**
  15979. * Tells the LocalStore that the currently authenticated user has changed.
  15980. *
  15981. * In response the local store switches the mutation queue to the new user and
  15982. * returns any resulting document changes.
  15983. */
  15984. // PORTING NOTE: Android and iOS only return the documents affected by the
  15985. // change.
  15986. async function localStoreHandleUserChange(localStore, user) {
  15987. const localStoreImpl = debugCast(localStore);
  15988. const result = await localStoreImpl.persistence.runTransaction('Handle user change', 'readonly', txn => {
  15989. // Swap out the mutation queue, grabbing the pending mutation batches
  15990. // before and after.
  15991. let oldBatches;
  15992. return localStoreImpl.mutationQueue
  15993. .getAllMutationBatches(txn)
  15994. .next(promisedOldBatches => {
  15995. oldBatches = promisedOldBatches;
  15996. localStoreImpl.initializeUserComponents(user);
  15997. return localStoreImpl.mutationQueue.getAllMutationBatches(txn);
  15998. })
  15999. .next(newBatches => {
  16000. const removedBatchIds = [];
  16001. const addedBatchIds = [];
  16002. // Union the old/new changed keys.
  16003. let changedKeys = documentKeySet();
  16004. for (const batch of oldBatches) {
  16005. removedBatchIds.push(batch.batchId);
  16006. for (const mutation of batch.mutations) {
  16007. changedKeys = changedKeys.add(mutation.key);
  16008. }
  16009. }
  16010. for (const batch of newBatches) {
  16011. addedBatchIds.push(batch.batchId);
  16012. for (const mutation of batch.mutations) {
  16013. changedKeys = changedKeys.add(mutation.key);
  16014. }
  16015. }
  16016. // Return the set of all (potentially) changed documents and the list
  16017. // of mutation batch IDs that were affected by change.
  16018. return localStoreImpl.localDocuments
  16019. .getDocuments(txn, changedKeys)
  16020. .next(affectedDocuments => {
  16021. return {
  16022. affectedDocuments,
  16023. removedBatchIds,
  16024. addedBatchIds
  16025. };
  16026. });
  16027. });
  16028. });
  16029. return result;
  16030. }
  16031. /* Accepts locally generated Mutations and commit them to storage. */
  16032. function localStoreWriteLocally(localStore, mutations) {
  16033. const localStoreImpl = debugCast(localStore);
  16034. const localWriteTime = Timestamp.now();
  16035. const keys = mutations.reduce((keys, m) => keys.add(m.key), documentKeySet());
  16036. let overlayedDocuments;
  16037. let mutationBatch;
  16038. return localStoreImpl.persistence
  16039. .runTransaction('Locally write mutations', 'readwrite', txn => {
  16040. // Figure out which keys do not have a remote version in the cache, this
  16041. // is needed to create the right overlay mutation: if no remote version
  16042. // presents, we do not need to create overlays as patch mutations.
  16043. // TODO(Overlay): Is there a better way to determine this? Using the
  16044. // document version does not work because local mutations set them back
  16045. // to 0.
  16046. let remoteDocs = mutableDocumentMap();
  16047. let docsWithoutRemoteVersion = documentKeySet();
  16048. return localStoreImpl.remoteDocuments
  16049. .getEntries(txn, keys)
  16050. .next(docs => {
  16051. remoteDocs = docs;
  16052. remoteDocs.forEach((key, doc) => {
  16053. if (!doc.isValidDocument()) {
  16054. docsWithoutRemoteVersion = docsWithoutRemoteVersion.add(key);
  16055. }
  16056. });
  16057. })
  16058. .next(() => {
  16059. // Load and apply all existing mutations. This lets us compute the
  16060. // current base state for all non-idempotent transforms before applying
  16061. // any additional user-provided writes.
  16062. return localStoreImpl.localDocuments.getOverlayedDocuments(txn, remoteDocs);
  16063. })
  16064. .next((docs) => {
  16065. overlayedDocuments = docs;
  16066. // For non-idempotent mutations (such as `FieldValue.increment()`),
  16067. // we record the base state in a separate patch mutation. This is
  16068. // later used to guarantee consistent values and prevents flicker
  16069. // even if the backend sends us an update that already includes our
  16070. // transform.
  16071. const baseMutations = [];
  16072. for (const mutation of mutations) {
  16073. const baseValue = mutationExtractBaseValue(mutation, overlayedDocuments.get(mutation.key).overlayedDocument);
  16074. if (baseValue != null) {
  16075. // NOTE: The base state should only be applied if there's some
  16076. // existing document to override, so use a Precondition of
  16077. // exists=true
  16078. baseMutations.push(new PatchMutation(mutation.key, baseValue, extractFieldMask(baseValue.value.mapValue), Precondition.exists(true)));
  16079. }
  16080. }
  16081. return localStoreImpl.mutationQueue.addMutationBatch(txn, localWriteTime, baseMutations, mutations);
  16082. })
  16083. .next(batch => {
  16084. mutationBatch = batch;
  16085. const overlays = batch.applyToLocalDocumentSet(overlayedDocuments, docsWithoutRemoteVersion);
  16086. return localStoreImpl.documentOverlayCache.saveOverlays(txn, batch.batchId, overlays);
  16087. });
  16088. })
  16089. .then(() => ({
  16090. batchId: mutationBatch.batchId,
  16091. changes: convertOverlayedDocumentMapToDocumentMap(overlayedDocuments)
  16092. }));
  16093. }
  16094. /**
  16095. * Acknowledges the given batch.
  16096. *
  16097. * On the happy path when a batch is acknowledged, the local store will
  16098. *
  16099. * + remove the batch from the mutation queue;
  16100. * + apply the changes to the remote document cache;
  16101. * + recalculate the latency compensated view implied by those changes (there
  16102. * may be mutations in the queue that affect the documents but haven't been
  16103. * acknowledged yet); and
  16104. * + give the changed documents back the sync engine
  16105. *
  16106. * @returns The resulting (modified) documents.
  16107. */
  16108. function localStoreAcknowledgeBatch(localStore, batchResult) {
  16109. const localStoreImpl = debugCast(localStore);
  16110. return localStoreImpl.persistence.runTransaction('Acknowledge batch', 'readwrite-primary', txn => {
  16111. const affected = batchResult.batch.keys();
  16112. const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
  16113. trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
  16114. });
  16115. return applyWriteToRemoteDocuments(localStoreImpl, txn, batchResult, documentBuffer)
  16116. .next(() => documentBuffer.apply(txn))
  16117. .next(() => localStoreImpl.mutationQueue.performConsistencyCheck(txn))
  16118. .next(() => localStoreImpl.documentOverlayCache.removeOverlaysForBatchId(txn, affected, batchResult.batch.batchId))
  16119. .next(() => localStoreImpl.localDocuments.recalculateAndSaveOverlaysForDocumentKeys(txn, getKeysWithTransformResults(batchResult)))
  16120. .next(() => localStoreImpl.localDocuments.getDocuments(txn, affected));
  16121. });
  16122. }
  16123. function getKeysWithTransformResults(batchResult) {
  16124. let result = documentKeySet();
  16125. for (let i = 0; i < batchResult.mutationResults.length; ++i) {
  16126. const mutationResult = batchResult.mutationResults[i];
  16127. if (mutationResult.transformResults.length > 0) {
  16128. result = result.add(batchResult.batch.mutations[i].key);
  16129. }
  16130. }
  16131. return result;
  16132. }
  16133. /**
  16134. * Removes mutations from the MutationQueue for the specified batch;
  16135. * LocalDocuments will be recalculated.
  16136. *
  16137. * @returns The resulting modified documents.
  16138. */
  16139. function localStoreRejectBatch(localStore, batchId) {
  16140. const localStoreImpl = debugCast(localStore);
  16141. return localStoreImpl.persistence.runTransaction('Reject batch', 'readwrite-primary', txn => {
  16142. let affectedKeys;
  16143. return localStoreImpl.mutationQueue
  16144. .lookupMutationBatch(txn, batchId)
  16145. .next((batch) => {
  16146. hardAssert(batch !== null);
  16147. affectedKeys = batch.keys();
  16148. return localStoreImpl.mutationQueue.removeMutationBatch(txn, batch);
  16149. })
  16150. .next(() => localStoreImpl.mutationQueue.performConsistencyCheck(txn))
  16151. .next(() => localStoreImpl.documentOverlayCache.removeOverlaysForBatchId(txn, affectedKeys, batchId))
  16152. .next(() => localStoreImpl.localDocuments.recalculateAndSaveOverlaysForDocumentKeys(txn, affectedKeys))
  16153. .next(() => localStoreImpl.localDocuments.getDocuments(txn, affectedKeys));
  16154. });
  16155. }
  16156. /**
  16157. * Returns the largest (latest) batch id in mutation queue that is pending
  16158. * server response.
  16159. *
  16160. * Returns `BATCHID_UNKNOWN` if the queue is empty.
  16161. */
  16162. function localStoreGetHighestUnacknowledgedBatchId(localStore) {
  16163. const localStoreImpl = debugCast(localStore);
  16164. return localStoreImpl.persistence.runTransaction('Get highest unacknowledged batch id', 'readonly', txn => localStoreImpl.mutationQueue.getHighestUnacknowledgedBatchId(txn));
  16165. }
  16166. /**
  16167. * Returns the last consistent snapshot processed (used by the RemoteStore to
  16168. * determine whether to buffer incoming snapshots from the backend).
  16169. */
  16170. function localStoreGetLastRemoteSnapshotVersion(localStore) {
  16171. const localStoreImpl = debugCast(localStore);
  16172. return localStoreImpl.persistence.runTransaction('Get last remote snapshot version', 'readonly', txn => localStoreImpl.targetCache.getLastRemoteSnapshotVersion(txn));
  16173. }
  16174. /**
  16175. * Updates the "ground-state" (remote) documents. We assume that the remote
  16176. * event reflects any write batches that have been acknowledged or rejected
  16177. * (i.e. we do not re-apply local mutations to updates from this event).
  16178. *
  16179. * LocalDocuments are re-calculated if there are remaining mutations in the
  16180. * queue.
  16181. */
  16182. function localStoreApplyRemoteEventToLocalCache(localStore, remoteEvent) {
  16183. const localStoreImpl = debugCast(localStore);
  16184. const remoteVersion = remoteEvent.snapshotVersion;
  16185. let newTargetDataByTargetMap = localStoreImpl.targetDataByTarget;
  16186. return localStoreImpl.persistence
  16187. .runTransaction('Apply remote event', 'readwrite-primary', txn => {
  16188. const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
  16189. trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
  16190. });
  16191. // Reset newTargetDataByTargetMap in case this transaction gets re-run.
  16192. newTargetDataByTargetMap = localStoreImpl.targetDataByTarget;
  16193. const promises = [];
  16194. remoteEvent.targetChanges.forEach((change, targetId) => {
  16195. const oldTargetData = newTargetDataByTargetMap.get(targetId);
  16196. if (!oldTargetData) {
  16197. return;
  16198. }
  16199. // Only update the remote keys if the target is still active. This
  16200. // ensures that we can persist the updated target data along with
  16201. // the updated assignment.
  16202. promises.push(localStoreImpl.targetCache
  16203. .removeMatchingKeys(txn, change.removedDocuments, targetId)
  16204. .next(() => {
  16205. return localStoreImpl.targetCache.addMatchingKeys(txn, change.addedDocuments, targetId);
  16206. }));
  16207. let newTargetData = oldTargetData.withSequenceNumber(txn.currentSequenceNumber);
  16208. if (remoteEvent.targetMismatches.has(targetId)) {
  16209. newTargetData = newTargetData
  16210. .withResumeToken(ByteString.EMPTY_BYTE_STRING, SnapshotVersion.min())
  16211. .withLastLimboFreeSnapshotVersion(SnapshotVersion.min());
  16212. }
  16213. else if (change.resumeToken.approximateByteSize() > 0) {
  16214. newTargetData = newTargetData.withResumeToken(change.resumeToken, remoteVersion);
  16215. }
  16216. newTargetDataByTargetMap = newTargetDataByTargetMap.insert(targetId, newTargetData);
  16217. // Update the target data if there are target changes (or if
  16218. // sufficient time has passed since the last update).
  16219. if (shouldPersistTargetData(oldTargetData, newTargetData, change)) {
  16220. promises.push(localStoreImpl.targetCache.updateTargetData(txn, newTargetData));
  16221. }
  16222. });
  16223. let changedDocs = mutableDocumentMap();
  16224. let existenceChangedKeys = documentKeySet();
  16225. remoteEvent.documentUpdates.forEach(key => {
  16226. if (remoteEvent.resolvedLimboDocuments.has(key)) {
  16227. promises.push(localStoreImpl.persistence.referenceDelegate.updateLimboDocument(txn, key));
  16228. }
  16229. });
  16230. // Each loop iteration only affects its "own" doc, so it's safe to get all
  16231. // the remote documents in advance in a single call.
  16232. promises.push(populateDocumentChangeBuffer(txn, documentBuffer, remoteEvent.documentUpdates).next(result => {
  16233. changedDocs = result.changedDocuments;
  16234. existenceChangedKeys = result.existenceChangedKeys;
  16235. }));
  16236. // HACK: The only reason we allow a null snapshot version is so that we
  16237. // can synthesize remote events when we get permission denied errors while
  16238. // trying to resolve the state of a locally cached document that is in
  16239. // limbo.
  16240. if (!remoteVersion.isEqual(SnapshotVersion.min())) {
  16241. const updateRemoteVersion = localStoreImpl.targetCache
  16242. .getLastRemoteSnapshotVersion(txn)
  16243. .next(lastRemoteSnapshotVersion => {
  16244. return localStoreImpl.targetCache.setTargetsMetadata(txn, txn.currentSequenceNumber, remoteVersion);
  16245. });
  16246. promises.push(updateRemoteVersion);
  16247. }
  16248. return PersistencePromise.waitFor(promises)
  16249. .next(() => documentBuffer.apply(txn))
  16250. .next(() => localStoreImpl.localDocuments.getLocalViewOfDocuments(txn, changedDocs, existenceChangedKeys))
  16251. .next(() => changedDocs);
  16252. })
  16253. .then(changedDocs => {
  16254. localStoreImpl.targetDataByTarget = newTargetDataByTargetMap;
  16255. return changedDocs;
  16256. });
  16257. }
  16258. /**
  16259. * Populates document change buffer with documents from backend or a bundle.
  16260. * Returns the document changes resulting from applying those documents, and
  16261. * also a set of documents whose existence state are changed as a result.
  16262. *
  16263. * @param txn - Transaction to use to read existing documents from storage.
  16264. * @param documentBuffer - Document buffer to collect the resulted changes to be
  16265. * applied to storage.
  16266. * @param documents - Documents to be applied.
  16267. */
  16268. function populateDocumentChangeBuffer(txn, documentBuffer, documents) {
  16269. let updatedKeys = documentKeySet();
  16270. let existenceChangedKeys = documentKeySet();
  16271. documents.forEach(k => (updatedKeys = updatedKeys.add(k)));
  16272. return documentBuffer.getEntries(txn, updatedKeys).next(existingDocs => {
  16273. let changedDocuments = mutableDocumentMap();
  16274. documents.forEach((key, doc) => {
  16275. const existingDoc = existingDocs.get(key);
  16276. // Check if see if there is a existence state change for this document.
  16277. if (doc.isFoundDocument() !== existingDoc.isFoundDocument()) {
  16278. existenceChangedKeys = existenceChangedKeys.add(key);
  16279. }
  16280. // Note: The order of the steps below is important, since we want
  16281. // to ensure that rejected limbo resolutions (which fabricate
  16282. // NoDocuments with SnapshotVersion.min()) never add documents to
  16283. // cache.
  16284. if (doc.isNoDocument() && doc.version.isEqual(SnapshotVersion.min())) {
  16285. // NoDocuments with SnapshotVersion.min() are used in manufactured
  16286. // events. We remove these documents from cache since we lost
  16287. // access.
  16288. documentBuffer.removeEntry(key, doc.readTime);
  16289. changedDocuments = changedDocuments.insert(key, doc);
  16290. }
  16291. else if (!existingDoc.isValidDocument() ||
  16292. doc.version.compareTo(existingDoc.version) > 0 ||
  16293. (doc.version.compareTo(existingDoc.version) === 0 &&
  16294. existingDoc.hasPendingWrites)) {
  16295. documentBuffer.addEntry(doc);
  16296. changedDocuments = changedDocuments.insert(key, doc);
  16297. }
  16298. else {
  16299. logDebug(LOG_TAG$b, 'Ignoring outdated watch update for ', key, '. Current version:', existingDoc.version, ' Watch version:', doc.version);
  16300. }
  16301. });
  16302. return { changedDocuments, existenceChangedKeys };
  16303. });
  16304. }
  16305. /**
  16306. * Returns true if the newTargetData should be persisted during an update of
  16307. * an active target. TargetData should always be persisted when a target is
  16308. * being released and should not call this function.
  16309. *
  16310. * While the target is active, TargetData updates can be omitted when nothing
  16311. * about the target has changed except metadata like the resume token or
  16312. * snapshot version. Occasionally it's worth the extra write to prevent these
  16313. * values from getting too stale after a crash, but this doesn't have to be
  16314. * too frequent.
  16315. */
  16316. function shouldPersistTargetData(oldTargetData, newTargetData, change) {
  16317. // Always persist target data if we don't already have a resume token.
  16318. if (oldTargetData.resumeToken.approximateByteSize() === 0) {
  16319. return true;
  16320. }
  16321. // Don't allow resume token changes to be buffered indefinitely. This
  16322. // allows us to be reasonably up-to-date after a crash and avoids needing
  16323. // to loop over all active queries on shutdown. Especially in the browser
  16324. // we may not get time to do anything interesting while the current tab is
  16325. // closing.
  16326. const timeDelta = newTargetData.snapshotVersion.toMicroseconds() -
  16327. oldTargetData.snapshotVersion.toMicroseconds();
  16328. if (timeDelta >= RESUME_TOKEN_MAX_AGE_MICROS) {
  16329. return true;
  16330. }
  16331. // Otherwise if the only thing that has changed about a target is its resume
  16332. // token it's not worth persisting. Note that the RemoteStore keeps an
  16333. // in-memory view of the currently active targets which includes the current
  16334. // resume token, so stream failure or user changes will still use an
  16335. // up-to-date resume token regardless of what we do here.
  16336. const changes = change.addedDocuments.size +
  16337. change.modifiedDocuments.size +
  16338. change.removedDocuments.size;
  16339. return changes > 0;
  16340. }
  16341. /**
  16342. * Notifies local store of the changed views to locally pin documents.
  16343. */
  16344. async function localStoreNotifyLocalViewChanges(localStore, viewChanges) {
  16345. const localStoreImpl = debugCast(localStore);
  16346. try {
  16347. await localStoreImpl.persistence.runTransaction('notifyLocalViewChanges', 'readwrite', txn => {
  16348. return PersistencePromise.forEach(viewChanges, (viewChange) => {
  16349. 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)));
  16350. });
  16351. });
  16352. }
  16353. catch (e) {
  16354. if (isIndexedDbTransactionError(e)) {
  16355. // If `notifyLocalViewChanges` fails, we did not advance the sequence
  16356. // number for the documents that were included in this transaction.
  16357. // This might trigger them to be deleted earlier than they otherwise
  16358. // would have, but it should not invalidate the integrity of the data.
  16359. logDebug(LOG_TAG$b, 'Failed to update sequence numbers: ' + e);
  16360. }
  16361. else {
  16362. throw e;
  16363. }
  16364. }
  16365. for (const viewChange of viewChanges) {
  16366. const targetId = viewChange.targetId;
  16367. if (!viewChange.fromCache) {
  16368. const targetData = localStoreImpl.targetDataByTarget.get(targetId);
  16369. // Advance the last limbo free snapshot version
  16370. const lastLimboFreeSnapshotVersion = targetData.snapshotVersion;
  16371. const updatedTargetData = targetData.withLastLimboFreeSnapshotVersion(lastLimboFreeSnapshotVersion);
  16372. localStoreImpl.targetDataByTarget =
  16373. localStoreImpl.targetDataByTarget.insert(targetId, updatedTargetData);
  16374. }
  16375. }
  16376. }
  16377. /**
  16378. * Gets the mutation batch after the passed in batchId in the mutation queue
  16379. * or null if empty.
  16380. * @param afterBatchId - If provided, the batch to search after.
  16381. * @returns The next mutation or null if there wasn't one.
  16382. */
  16383. function localStoreGetNextMutationBatch(localStore, afterBatchId) {
  16384. const localStoreImpl = debugCast(localStore);
  16385. return localStoreImpl.persistence.runTransaction('Get next mutation batch', 'readonly', txn => {
  16386. if (afterBatchId === undefined) {
  16387. afterBatchId = BATCHID_UNKNOWN;
  16388. }
  16389. return localStoreImpl.mutationQueue.getNextMutationBatchAfterBatchId(txn, afterBatchId);
  16390. });
  16391. }
  16392. /**
  16393. * Reads the current value of a Document with a given key or null if not
  16394. * found - used for testing.
  16395. */
  16396. function localStoreReadDocument(localStore, key) {
  16397. const localStoreImpl = debugCast(localStore);
  16398. return localStoreImpl.persistence.runTransaction('read document', 'readonly', txn => localStoreImpl.localDocuments.getDocument(txn, key));
  16399. }
  16400. /**
  16401. * Assigns the given target an internal ID so that its results can be pinned so
  16402. * they don't get GC'd. A target must be allocated in the local store before
  16403. * the store can be used to manage its view.
  16404. *
  16405. * Allocating an already allocated `Target` will return the existing `TargetData`
  16406. * for that `Target`.
  16407. */
  16408. function localStoreAllocateTarget(localStore, target) {
  16409. const localStoreImpl = debugCast(localStore);
  16410. return localStoreImpl.persistence
  16411. .runTransaction('Allocate target', 'readwrite', txn => {
  16412. let targetData;
  16413. return localStoreImpl.targetCache
  16414. .getTargetData(txn, target)
  16415. .next((cached) => {
  16416. if (cached) {
  16417. // This target has been listened to previously, so reuse the
  16418. // previous targetID.
  16419. // TODO(mcg): freshen last accessed date?
  16420. targetData = cached;
  16421. return PersistencePromise.resolve(targetData);
  16422. }
  16423. else {
  16424. return localStoreImpl.targetCache
  16425. .allocateTargetId(txn)
  16426. .next(targetId => {
  16427. targetData = new TargetData(target, targetId, 0 /* TargetPurpose.Listen */, txn.currentSequenceNumber);
  16428. return localStoreImpl.targetCache
  16429. .addTargetData(txn, targetData)
  16430. .next(() => targetData);
  16431. });
  16432. }
  16433. });
  16434. })
  16435. .then(targetData => {
  16436. // If Multi-Tab is enabled, the existing target data may be newer than
  16437. // the in-memory data
  16438. const cachedTargetData = localStoreImpl.targetDataByTarget.get(targetData.targetId);
  16439. if (cachedTargetData === null ||
  16440. targetData.snapshotVersion.compareTo(cachedTargetData.snapshotVersion) >
  16441. 0) {
  16442. localStoreImpl.targetDataByTarget =
  16443. localStoreImpl.targetDataByTarget.insert(targetData.targetId, targetData);
  16444. localStoreImpl.targetIdByTarget.set(target, targetData.targetId);
  16445. }
  16446. return targetData;
  16447. });
  16448. }
  16449. /**
  16450. * Returns the TargetData as seen by the LocalStore, including updates that may
  16451. * have not yet been persisted to the TargetCache.
  16452. */
  16453. // Visible for testing.
  16454. function localStoreGetTargetData(localStore, transaction, target) {
  16455. const localStoreImpl = debugCast(localStore);
  16456. const targetId = localStoreImpl.targetIdByTarget.get(target);
  16457. if (targetId !== undefined) {
  16458. return PersistencePromise.resolve(localStoreImpl.targetDataByTarget.get(targetId));
  16459. }
  16460. else {
  16461. return localStoreImpl.targetCache.getTargetData(transaction, target);
  16462. }
  16463. }
  16464. /**
  16465. * Unpins all the documents associated with the given target. If
  16466. * `keepPersistedTargetData` is set to false and Eager GC enabled, the method
  16467. * directly removes the associated target data from the target cache.
  16468. *
  16469. * Releasing a non-existing `Target` is a no-op.
  16470. */
  16471. // PORTING NOTE: `keepPersistedTargetData` is multi-tab only.
  16472. async function localStoreReleaseTarget(localStore, targetId, keepPersistedTargetData) {
  16473. const localStoreImpl = debugCast(localStore);
  16474. const targetData = localStoreImpl.targetDataByTarget.get(targetId);
  16475. const mode = keepPersistedTargetData ? 'readwrite' : 'readwrite-primary';
  16476. try {
  16477. if (!keepPersistedTargetData) {
  16478. await localStoreImpl.persistence.runTransaction('Release target', mode, txn => {
  16479. return localStoreImpl.persistence.referenceDelegate.removeTarget(txn, targetData);
  16480. });
  16481. }
  16482. }
  16483. catch (e) {
  16484. if (isIndexedDbTransactionError(e)) {
  16485. // All `releaseTarget` does is record the final metadata state for the
  16486. // target, but we've been recording this periodically during target
  16487. // activity. If we lose this write this could cause a very slight
  16488. // difference in the order of target deletion during GC, but we
  16489. // don't define exact LRU semantics so this is acceptable.
  16490. logDebug(LOG_TAG$b, `Failed to update sequence numbers for target ${targetId}: ${e}`);
  16491. }
  16492. else {
  16493. throw e;
  16494. }
  16495. }
  16496. localStoreImpl.targetDataByTarget =
  16497. localStoreImpl.targetDataByTarget.remove(targetId);
  16498. localStoreImpl.targetIdByTarget.delete(targetData.target);
  16499. }
  16500. /**
  16501. * Runs the specified query against the local store and returns the results,
  16502. * potentially taking advantage of query data from previous executions (such
  16503. * as the set of remote keys).
  16504. *
  16505. * @param usePreviousResults - Whether results from previous executions can
  16506. * be used to optimize this query execution.
  16507. */
  16508. function localStoreExecuteQuery(localStore, query, usePreviousResults) {
  16509. const localStoreImpl = debugCast(localStore);
  16510. let lastLimboFreeSnapshotVersion = SnapshotVersion.min();
  16511. let remoteKeys = documentKeySet();
  16512. return localStoreImpl.persistence.runTransaction('Execute query', 'readonly', txn => {
  16513. return localStoreGetTargetData(localStoreImpl, txn, queryToTarget(query))
  16514. .next(targetData => {
  16515. if (targetData) {
  16516. lastLimboFreeSnapshotVersion =
  16517. targetData.lastLimboFreeSnapshotVersion;
  16518. return localStoreImpl.targetCache
  16519. .getMatchingKeysForTargetId(txn, targetData.targetId)
  16520. .next(result => {
  16521. remoteKeys = result;
  16522. });
  16523. }
  16524. })
  16525. .next(() => localStoreImpl.queryEngine.getDocumentsMatchingQuery(txn, query, usePreviousResults
  16526. ? lastLimboFreeSnapshotVersion
  16527. : SnapshotVersion.min(), usePreviousResults ? remoteKeys : documentKeySet()))
  16528. .next(documents => {
  16529. setMaxReadTime(localStoreImpl, queryCollectionGroup(query), documents);
  16530. return { documents, remoteKeys };
  16531. });
  16532. });
  16533. }
  16534. function applyWriteToRemoteDocuments(localStoreImpl, txn, batchResult, documentBuffer) {
  16535. const batch = batchResult.batch;
  16536. const docKeys = batch.keys();
  16537. let promiseChain = PersistencePromise.resolve();
  16538. docKeys.forEach(docKey => {
  16539. promiseChain = promiseChain
  16540. .next(() => documentBuffer.getEntry(txn, docKey))
  16541. .next(doc => {
  16542. const ackVersion = batchResult.docVersions.get(docKey);
  16543. hardAssert(ackVersion !== null);
  16544. if (doc.version.compareTo(ackVersion) < 0) {
  16545. batch.applyToRemoteDocument(doc, batchResult);
  16546. if (doc.isValidDocument()) {
  16547. // We use the commitVersion as the readTime rather than the
  16548. // document's updateTime since the updateTime is not advanced
  16549. // for updates that do not modify the underlying document.
  16550. doc.setReadTime(batchResult.commitVersion);
  16551. documentBuffer.addEntry(doc);
  16552. }
  16553. }
  16554. });
  16555. });
  16556. return promiseChain.next(() => localStoreImpl.mutationQueue.removeMutationBatch(txn, batch));
  16557. }
  16558. /** Returns the local view of the documents affected by a mutation batch. */
  16559. // PORTING NOTE: Multi-Tab only.
  16560. function localStoreLookupMutationDocuments(localStore, batchId) {
  16561. const localStoreImpl = debugCast(localStore);
  16562. const mutationQueueImpl = debugCast(localStoreImpl.mutationQueue);
  16563. return localStoreImpl.persistence.runTransaction('Lookup mutation documents', 'readonly', txn => {
  16564. return mutationQueueImpl.lookupMutationKeys(txn, batchId).next(keys => {
  16565. if (keys) {
  16566. return localStoreImpl.localDocuments.getDocuments(txn, keys);
  16567. }
  16568. else {
  16569. return PersistencePromise.resolve(null);
  16570. }
  16571. });
  16572. });
  16573. }
  16574. // PORTING NOTE: Multi-Tab only.
  16575. function localStoreRemoveCachedMutationBatchMetadata(localStore, batchId) {
  16576. const mutationQueueImpl = debugCast(debugCast(localStore, LocalStoreImpl).mutationQueue);
  16577. mutationQueueImpl.removeCachedMutationKeys(batchId);
  16578. }
  16579. // PORTING NOTE: Multi-Tab only.
  16580. function localStoreGetActiveClients(localStore) {
  16581. const persistenceImpl = debugCast(debugCast(localStore, LocalStoreImpl).persistence);
  16582. return persistenceImpl.getActiveClients();
  16583. }
  16584. // PORTING NOTE: Multi-Tab only.
  16585. function localStoreGetCachedTarget(localStore, targetId) {
  16586. const localStoreImpl = debugCast(localStore);
  16587. const targetCacheImpl = debugCast(localStoreImpl.targetCache);
  16588. const cachedTargetData = localStoreImpl.targetDataByTarget.get(targetId);
  16589. if (cachedTargetData) {
  16590. return Promise.resolve(cachedTargetData.target);
  16591. }
  16592. else {
  16593. return localStoreImpl.persistence.runTransaction('Get target data', 'readonly', txn => {
  16594. return targetCacheImpl
  16595. .getTargetDataForTarget(txn, targetId)
  16596. .next(targetData => (targetData ? targetData.target : null));
  16597. });
  16598. }
  16599. }
  16600. /**
  16601. * Returns the set of documents that have been updated since the last call.
  16602. * If this is the first call, returns the set of changes since client
  16603. * initialization. Further invocations will return document that have changed
  16604. * since the prior call.
  16605. */
  16606. // PORTING NOTE: Multi-Tab only.
  16607. function localStoreGetNewDocumentChanges(localStore, collectionGroup) {
  16608. const localStoreImpl = debugCast(localStore);
  16609. // Get the current maximum read time for the collection. This should always
  16610. // exist, but to reduce the chance for regressions we default to
  16611. // SnapshotVersion.Min()
  16612. // TODO(indexing): Consider removing the default value.
  16613. const readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
  16614. SnapshotVersion.min();
  16615. return localStoreImpl.persistence
  16616. .runTransaction('Get new document changes', 'readonly', txn => localStoreImpl.remoteDocuments.getAllFromCollectionGroup(txn, collectionGroup, newIndexOffsetSuccessorFromReadTime(readTime, INITIAL_LARGEST_BATCH_ID),
  16617. /* limit= */ Number.MAX_SAFE_INTEGER))
  16618. .then(changedDocs => {
  16619. setMaxReadTime(localStoreImpl, collectionGroup, changedDocs);
  16620. return changedDocs;
  16621. });
  16622. }
  16623. /** Sets the collection group's maximum read time from the given documents. */
  16624. // PORTING NOTE: Multi-Tab only.
  16625. function setMaxReadTime(localStoreImpl, collectionGroup, changedDocs) {
  16626. let readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
  16627. SnapshotVersion.min();
  16628. changedDocs.forEach((_, doc) => {
  16629. if (doc.readTime.compareTo(readTime) > 0) {
  16630. readTime = doc.readTime;
  16631. }
  16632. });
  16633. localStoreImpl.collectionGroupReadTime.set(collectionGroup, readTime);
  16634. }
  16635. /**
  16636. * Creates a new target using the given bundle name, which will be used to
  16637. * hold the keys of all documents from the bundle in query-document mappings.
  16638. * This ensures that the loaded documents do not get garbage collected
  16639. * right away.
  16640. */
  16641. function umbrellaTarget(bundleName) {
  16642. // It is OK that the path used for the query is not valid, because this will
  16643. // not be read and queried.
  16644. return queryToTarget(newQueryForPath(ResourcePath.fromString(`__bundle__/docs/${bundleName}`)));
  16645. }
  16646. /**
  16647. * Applies the documents from a bundle to the "ground-state" (remote)
  16648. * documents.
  16649. *
  16650. * LocalDocuments are re-calculated if there are remaining mutations in the
  16651. * queue.
  16652. */
  16653. async function localStoreApplyBundledDocuments(localStore, bundleConverter, documents, bundleName) {
  16654. const localStoreImpl = debugCast(localStore);
  16655. let documentKeys = documentKeySet();
  16656. let documentMap = mutableDocumentMap();
  16657. for (const bundleDoc of documents) {
  16658. const documentKey = bundleConverter.toDocumentKey(bundleDoc.metadata.name);
  16659. if (bundleDoc.document) {
  16660. documentKeys = documentKeys.add(documentKey);
  16661. }
  16662. const doc = bundleConverter.toMutableDocument(bundleDoc);
  16663. doc.setReadTime(bundleConverter.toSnapshotVersion(bundleDoc.metadata.readTime));
  16664. documentMap = documentMap.insert(documentKey, doc);
  16665. }
  16666. const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
  16667. trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
  16668. });
  16669. // Allocates a target to hold all document keys from the bundle, such that
  16670. // they will not get garbage collected right away.
  16671. const umbrellaTargetData = await localStoreAllocateTarget(localStoreImpl, umbrellaTarget(bundleName));
  16672. return localStoreImpl.persistence.runTransaction('Apply bundle documents', 'readwrite', txn => {
  16673. return populateDocumentChangeBuffer(txn, documentBuffer, documentMap)
  16674. .next(documentChangeResult => {
  16675. documentBuffer.apply(txn);
  16676. return documentChangeResult;
  16677. })
  16678. .next(documentChangeResult => {
  16679. return localStoreImpl.targetCache
  16680. .removeMatchingKeysForTargetId(txn, umbrellaTargetData.targetId)
  16681. .next(() => localStoreImpl.targetCache.addMatchingKeys(txn, documentKeys, umbrellaTargetData.targetId))
  16682. .next(() => localStoreImpl.localDocuments.getLocalViewOfDocuments(txn, documentChangeResult.changedDocuments, documentChangeResult.existenceChangedKeys))
  16683. .next(() => documentChangeResult.changedDocuments);
  16684. });
  16685. });
  16686. }
  16687. /**
  16688. * Returns a promise of a boolean to indicate if the given bundle has already
  16689. * been loaded and the create time is newer than the current loading bundle.
  16690. */
  16691. function localStoreHasNewerBundle(localStore, bundleMetadata) {
  16692. const localStoreImpl = debugCast(localStore);
  16693. const currentReadTime = fromVersion(bundleMetadata.createTime);
  16694. return localStoreImpl.persistence
  16695. .runTransaction('hasNewerBundle', 'readonly', transaction => {
  16696. return localStoreImpl.bundleCache.getBundleMetadata(transaction, bundleMetadata.id);
  16697. })
  16698. .then(cached => {
  16699. return !!cached && cached.createTime.compareTo(currentReadTime) >= 0;
  16700. });
  16701. }
  16702. /**
  16703. * Saves the given `BundleMetadata` to local persistence.
  16704. */
  16705. function localStoreSaveBundle(localStore, bundleMetadata) {
  16706. const localStoreImpl = debugCast(localStore);
  16707. return localStoreImpl.persistence.runTransaction('Save bundle', 'readwrite', transaction => {
  16708. return localStoreImpl.bundleCache.saveBundleMetadata(transaction, bundleMetadata);
  16709. });
  16710. }
  16711. /**
  16712. * Returns a promise of a `NamedQuery` associated with given query name. Promise
  16713. * resolves to undefined if no persisted data can be found.
  16714. */
  16715. function localStoreGetNamedQuery(localStore, queryName) {
  16716. const localStoreImpl = debugCast(localStore);
  16717. return localStoreImpl.persistence.runTransaction('Get named query', 'readonly', transaction => localStoreImpl.bundleCache.getNamedQuery(transaction, queryName));
  16718. }
  16719. /**
  16720. * Saves the given `NamedQuery` to local persistence.
  16721. */
  16722. async function localStoreSaveNamedQuery(localStore, query, documents = documentKeySet()) {
  16723. // Allocate a target for the named query such that it can be resumed
  16724. // from associated read time if users use it to listen.
  16725. // NOTE: this also means if no corresponding target exists, the new target
  16726. // will remain active and will not get collected, unless users happen to
  16727. // unlisten the query somehow.
  16728. const allocated = await localStoreAllocateTarget(localStore, queryToTarget(fromBundledQuery(query.bundledQuery)));
  16729. const localStoreImpl = debugCast(localStore);
  16730. return localStoreImpl.persistence.runTransaction('Save named query', 'readwrite', transaction => {
  16731. const readTime = fromVersion(query.readTime);
  16732. // Simply save the query itself if it is older than what the SDK already
  16733. // has.
  16734. if (allocated.snapshotVersion.compareTo(readTime) >= 0) {
  16735. return localStoreImpl.bundleCache.saveNamedQuery(transaction, query);
  16736. }
  16737. // Update existing target data because the query from the bundle is newer.
  16738. const newTargetData = allocated.withResumeToken(ByteString.EMPTY_BYTE_STRING, readTime);
  16739. localStoreImpl.targetDataByTarget =
  16740. localStoreImpl.targetDataByTarget.insert(newTargetData.targetId, newTargetData);
  16741. return localStoreImpl.targetCache
  16742. .updateTargetData(transaction, newTargetData)
  16743. .next(() => localStoreImpl.targetCache.removeMatchingKeysForTargetId(transaction, allocated.targetId))
  16744. .next(() => localStoreImpl.targetCache.addMatchingKeys(transaction, documents, allocated.targetId))
  16745. .next(() => localStoreImpl.bundleCache.saveNamedQuery(transaction, query));
  16746. });
  16747. }
  16748. async function localStoreConfigureFieldIndexes(localStore, newFieldIndexes) {
  16749. const localStoreImpl = debugCast(localStore);
  16750. const indexManager = localStoreImpl.indexManager;
  16751. const promises = [];
  16752. return localStoreImpl.persistence.runTransaction('Configure indexes', 'readwrite', transaction => indexManager
  16753. .getFieldIndexes(transaction)
  16754. .next(oldFieldIndexes => diffArrays(oldFieldIndexes, newFieldIndexes, fieldIndexSemanticComparator, fieldIndex => {
  16755. promises.push(indexManager.addFieldIndex(transaction, fieldIndex));
  16756. }, fieldIndex => {
  16757. promises.push(indexManager.deleteFieldIndex(transaction, fieldIndex));
  16758. }))
  16759. .next(() => PersistencePromise.waitFor(promises)));
  16760. }
  16761. /**
  16762. * @license
  16763. * Copyright 2019 Google LLC
  16764. *
  16765. * Licensed under the Apache License, Version 2.0 (the "License");
  16766. * you may not use this file except in compliance with the License.
  16767. * You may obtain a copy of the License at
  16768. *
  16769. * http://www.apache.org/licenses/LICENSE-2.0
  16770. *
  16771. * Unless required by applicable law or agreed to in writing, software
  16772. * distributed under the License is distributed on an "AS IS" BASIS,
  16773. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16774. * See the License for the specific language governing permissions and
  16775. * limitations under the License.
  16776. */
  16777. /**
  16778. * The Firestore query engine.
  16779. *
  16780. * Firestore queries can be executed in three modes. The Query Engine determines
  16781. * what mode to use based on what data is persisted. The mode only determines
  16782. * the runtime complexity of the query - the result set is equivalent across all
  16783. * implementations.
  16784. *
  16785. * The Query engine will use indexed-based execution if a user has configured
  16786. * any index that can be used to execute query (via `setIndexConfiguration()`).
  16787. * Otherwise, the engine will try to optimize the query by re-using a previously
  16788. * persisted query result. If that is not possible, the query will be executed
  16789. * via a full collection scan.
  16790. *
  16791. * Index-based execution is the default when available. The query engine
  16792. * supports partial indexed execution and merges the result from the index
  16793. * lookup with documents that have not yet been indexed. The index evaluation
  16794. * matches the backend's format and as such, the SDK can use indexing for all
  16795. * queries that the backend supports.
  16796. *
  16797. * If no index exists, the query engine tries to take advantage of the target
  16798. * document mapping in the TargetCache. These mappings exists for all queries
  16799. * that have been synced with the backend at least once and allow the query
  16800. * engine to only read documents that previously matched a query plus any
  16801. * documents that were edited after the query was last listened to.
  16802. *
  16803. * There are some cases when this optimization is not guaranteed to produce
  16804. * the same results as full collection scans. In these cases, query
  16805. * processing falls back to full scans. These cases are:
  16806. *
  16807. * - Limit queries where a document that matched the query previously no longer
  16808. * matches the query.
  16809. *
  16810. * - Limit queries where a document edit may cause the document to sort below
  16811. * another document that is in the local cache.
  16812. *
  16813. * - Queries that have never been CURRENT or free of limbo documents.
  16814. */
  16815. class QueryEngine {
  16816. constructor() {
  16817. this.initialized = false;
  16818. }
  16819. /** Sets the document view to query against. */
  16820. initialize(localDocuments, indexManager) {
  16821. this.localDocumentsView = localDocuments;
  16822. this.indexManager = indexManager;
  16823. this.initialized = true;
  16824. }
  16825. /** Returns all local documents matching the specified query. */
  16826. getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion, remoteKeys) {
  16827. return this.performQueryUsingIndex(transaction, query)
  16828. .next(result => result
  16829. ? result
  16830. : this.performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion))
  16831. .next(result => result ? result : this.executeFullCollectionScan(transaction, query));
  16832. }
  16833. /**
  16834. * Performs an indexed query that evaluates the query based on a collection's
  16835. * persisted index values. Returns `null` if an index is not available.
  16836. */
  16837. performQueryUsingIndex(transaction, query) {
  16838. if (queryMatchesAllDocuments(query)) {
  16839. // Queries that match all documents don't benefit from using
  16840. // key-based lookups. It is more efficient to scan all documents in a
  16841. // collection, rather than to perform individual lookups.
  16842. return PersistencePromise.resolve(null);
  16843. }
  16844. let target = queryToTarget(query);
  16845. return this.indexManager
  16846. .getIndexType(transaction, target)
  16847. .next(indexType => {
  16848. if (indexType === 0 /* IndexType.NONE */) {
  16849. // The target cannot be served from any index.
  16850. return null;
  16851. }
  16852. if (query.limit !== null && indexType === 1 /* IndexType.PARTIAL */) {
  16853. // We cannot apply a limit for targets that are served using a partial
  16854. // index. If a partial index will be used to serve the target, the
  16855. // query may return a superset of documents that match the target
  16856. // (e.g. if the index doesn't include all the target's filters), or
  16857. // may return the correct set of documents in the wrong order (e.g. if
  16858. // the index doesn't include a segment for one of the orderBys).
  16859. // Therefore, a limit should not be applied in such cases.
  16860. query = queryWithLimit(query, null, "F" /* LimitType.First */);
  16861. target = queryToTarget(query);
  16862. }
  16863. return this.indexManager
  16864. .getDocumentsMatchingTarget(transaction, target)
  16865. .next(keys => {
  16866. const sortedKeys = documentKeySet(...keys);
  16867. return this.localDocumentsView
  16868. .getDocuments(transaction, sortedKeys)
  16869. .next(indexedDocuments => {
  16870. return this.indexManager
  16871. .getMinOffset(transaction, target)
  16872. .next(offset => {
  16873. const previousResults = this.applyQuery(query, indexedDocuments);
  16874. if (this.needsRefill(query, previousResults, sortedKeys, offset.readTime)) {
  16875. // A limit query whose boundaries change due to local
  16876. // edits can be re-run against the cache by excluding the
  16877. // limit. This ensures that all documents that match the
  16878. // query's filters are included in the result set. The SDK
  16879. // can then apply the limit once all local edits are
  16880. // incorporated.
  16881. return this.performQueryUsingIndex(transaction, queryWithLimit(query, null, "F" /* LimitType.First */));
  16882. }
  16883. return this.appendRemainingResults(transaction, previousResults, query, offset);
  16884. });
  16885. });
  16886. });
  16887. });
  16888. }
  16889. /**
  16890. * Performs a query based on the target's persisted query mapping. Returns
  16891. * `null` if the mapping is not available or cannot be used.
  16892. */
  16893. performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion) {
  16894. if (queryMatchesAllDocuments(query)) {
  16895. // Queries that match all documents don't benefit from using
  16896. // key-based lookups. It is more efficient to scan all documents in a
  16897. // collection, rather than to perform individual lookups.
  16898. return this.executeFullCollectionScan(transaction, query);
  16899. }
  16900. // Queries that have never seen a snapshot without limbo free documents
  16901. // should also be run as a full collection scan.
  16902. if (lastLimboFreeSnapshotVersion.isEqual(SnapshotVersion.min())) {
  16903. return this.executeFullCollectionScan(transaction, query);
  16904. }
  16905. return this.localDocumentsView.getDocuments(transaction, remoteKeys).next(documents => {
  16906. const previousResults = this.applyQuery(query, documents);
  16907. if (this.needsRefill(query, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
  16908. return this.executeFullCollectionScan(transaction, query);
  16909. }
  16910. if (getLogLevel() <= logger.LogLevel.DEBUG) {
  16911. logDebug('QueryEngine', 'Re-using previous result from %s to execute query: %s', lastLimboFreeSnapshotVersion.toString(), stringifyQuery(query));
  16912. }
  16913. // Retrieve all results for documents that were updated since the last
  16914. // limbo-document free remote snapshot.
  16915. return this.appendRemainingResults(transaction, previousResults, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID));
  16916. });
  16917. }
  16918. /** Applies the query filter and sorting to the provided documents. */
  16919. applyQuery(query, documents) {
  16920. // Sort the documents and re-apply the query filter since previously
  16921. // matching documents do not necessarily still match the query.
  16922. let queryResults = new SortedSet(newQueryComparator(query));
  16923. documents.forEach((_, maybeDoc) => {
  16924. if (queryMatches(query, maybeDoc)) {
  16925. queryResults = queryResults.add(maybeDoc);
  16926. }
  16927. });
  16928. return queryResults;
  16929. }
  16930. /**
  16931. * Determines if a limit query needs to be refilled from cache, making it
  16932. * ineligible for index-free execution.
  16933. *
  16934. * @param query - The query.
  16935. * @param sortedPreviousResults - The documents that matched the query when it
  16936. * was last synchronized, sorted by the query's comparator.
  16937. * @param remoteKeys - The document keys that matched the query at the last
  16938. * snapshot.
  16939. * @param limboFreeSnapshotVersion - The version of the snapshot when the
  16940. * query was last synchronized.
  16941. */
  16942. needsRefill(query, sortedPreviousResults, remoteKeys, limboFreeSnapshotVersion) {
  16943. if (query.limit === null) {
  16944. // Queries without limits do not need to be refilled.
  16945. return false;
  16946. }
  16947. if (remoteKeys.size !== sortedPreviousResults.size) {
  16948. // The query needs to be refilled if a previously matching document no
  16949. // longer matches.
  16950. return true;
  16951. }
  16952. // Limit queries are not eligible for index-free query execution if there is
  16953. // a potential that an older document from cache now sorts before a document
  16954. // that was previously part of the limit. This, however, can only happen if
  16955. // the document at the edge of the limit goes out of limit.
  16956. // If a document that is not the limit boundary sorts differently,
  16957. // the boundary of the limit itself did not change and documents from cache
  16958. // will continue to be "rejected" by this boundary. Therefore, we can ignore
  16959. // any modifications that don't affect the last document.
  16960. const docAtLimitEdge = query.limitType === "F" /* LimitType.First */
  16961. ? sortedPreviousResults.last()
  16962. : sortedPreviousResults.first();
  16963. if (!docAtLimitEdge) {
  16964. // We don't need to refill the query if there were already no documents.
  16965. return false;
  16966. }
  16967. return (docAtLimitEdge.hasPendingWrites ||
  16968. docAtLimitEdge.version.compareTo(limboFreeSnapshotVersion) > 0);
  16969. }
  16970. executeFullCollectionScan(transaction, query) {
  16971. if (getLogLevel() <= logger.LogLevel.DEBUG) {
  16972. logDebug('QueryEngine', 'Using full collection scan to execute query:', stringifyQuery(query));
  16973. }
  16974. return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
  16975. }
  16976. /**
  16977. * Combines the results from an indexed execution with the remaining documents
  16978. * that have not yet been indexed.
  16979. */
  16980. appendRemainingResults(transaction, indexedResults, query, offset) {
  16981. // Retrieve all results for documents that were updated since the offset.
  16982. return this.localDocumentsView
  16983. .getDocumentsMatchingQuery(transaction, query, offset)
  16984. .next(remainingResults => {
  16985. // Merge with existing results
  16986. indexedResults.forEach(d => {
  16987. remainingResults = remainingResults.insert(d.key, d);
  16988. });
  16989. return remainingResults;
  16990. });
  16991. }
  16992. }
  16993. /**
  16994. * @license
  16995. * Copyright 2019 Google LLC
  16996. *
  16997. * Licensed under the Apache License, Version 2.0 (the "License");
  16998. * you may not use this file except in compliance with the License.
  16999. * You may obtain a copy of the License at
  17000. *
  17001. * http://www.apache.org/licenses/LICENSE-2.0
  17002. *
  17003. * Unless required by applicable law or agreed to in writing, software
  17004. * distributed under the License is distributed on an "AS IS" BASIS,
  17005. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17006. * See the License for the specific language governing permissions and
  17007. * limitations under the License.
  17008. */
  17009. // The format of the LocalStorage key that stores the client state is:
  17010. // firestore_clients_<persistence_prefix>_<instance_key>
  17011. const CLIENT_STATE_KEY_PREFIX = 'firestore_clients';
  17012. /** Assembles the key for a client state in WebStorage */
  17013. function createWebStorageClientStateKey(persistenceKey, clientId) {
  17014. return `${CLIENT_STATE_KEY_PREFIX}_${persistenceKey}_${clientId}`;
  17015. }
  17016. // The format of the WebStorage key that stores the mutation state is:
  17017. // firestore_mutations_<persistence_prefix>_<batch_id>
  17018. // (for unauthenticated users)
  17019. // or: firestore_mutations_<persistence_prefix>_<batch_id>_<user_uid>
  17020. //
  17021. // 'user_uid' is last to avoid needing to escape '_' characters that it might
  17022. // contain.
  17023. const MUTATION_BATCH_KEY_PREFIX = 'firestore_mutations';
  17024. /** Assembles the key for a mutation batch in WebStorage */
  17025. function createWebStorageMutationBatchKey(persistenceKey, user, batchId) {
  17026. let mutationKey = `${MUTATION_BATCH_KEY_PREFIX}_${persistenceKey}_${batchId}`;
  17027. if (user.isAuthenticated()) {
  17028. mutationKey += `_${user.uid}`;
  17029. }
  17030. return mutationKey;
  17031. }
  17032. // The format of the WebStorage key that stores a query target's metadata is:
  17033. // firestore_targets_<persistence_prefix>_<target_id>
  17034. const QUERY_TARGET_KEY_PREFIX = 'firestore_targets';
  17035. /** Assembles the key for a query state in WebStorage */
  17036. function createWebStorageQueryTargetMetadataKey(persistenceKey, targetId) {
  17037. return `${QUERY_TARGET_KEY_PREFIX}_${persistenceKey}_${targetId}`;
  17038. }
  17039. // The WebStorage prefix that stores the primary tab's online state. The
  17040. // format of the key is:
  17041. // firestore_online_state_<persistence_prefix>
  17042. const ONLINE_STATE_KEY_PREFIX = 'firestore_online_state';
  17043. /** Assembles the key for the online state of the primary tab. */
  17044. function createWebStorageOnlineStateKey(persistenceKey) {
  17045. return `${ONLINE_STATE_KEY_PREFIX}_${persistenceKey}`;
  17046. }
  17047. // The WebStorage prefix that plays as a event to indicate the remote documents
  17048. // might have changed due to some secondary tabs loading a bundle.
  17049. // format of the key is:
  17050. // firestore_bundle_loaded_v2_<persistenceKey>
  17051. // The version ending with "v2" stores the list of modified collection groups.
  17052. const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded_v2';
  17053. function createBundleLoadedKey(persistenceKey) {
  17054. return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
  17055. }
  17056. // The WebStorage key prefix for the key that stores the last sequence number allocated. The key
  17057. // looks like 'firestore_sequence_number_<persistence_prefix>'.
  17058. const SEQUENCE_NUMBER_KEY_PREFIX = 'firestore_sequence_number';
  17059. /** Assembles the key for the current sequence number. */
  17060. function createWebStorageSequenceNumberKey(persistenceKey) {
  17061. return `${SEQUENCE_NUMBER_KEY_PREFIX}_${persistenceKey}`;
  17062. }
  17063. /**
  17064. * @license
  17065. * Copyright 2018 Google LLC
  17066. *
  17067. * Licensed under the Apache License, Version 2.0 (the "License");
  17068. * you may not use this file except in compliance with the License.
  17069. * You may obtain a copy of the License at
  17070. *
  17071. * http://www.apache.org/licenses/LICENSE-2.0
  17072. *
  17073. * Unless required by applicable law or agreed to in writing, software
  17074. * distributed under the License is distributed on an "AS IS" BASIS,
  17075. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17076. * See the License for the specific language governing permissions and
  17077. * limitations under the License.
  17078. */
  17079. const LOG_TAG$a = 'SharedClientState';
  17080. /**
  17081. * Holds the state of a mutation batch, including its user ID, batch ID and
  17082. * whether the batch is 'pending', 'acknowledged' or 'rejected'.
  17083. */
  17084. // Visible for testing
  17085. class MutationMetadata {
  17086. constructor(user, batchId, state, error) {
  17087. this.user = user;
  17088. this.batchId = batchId;
  17089. this.state = state;
  17090. this.error = error;
  17091. }
  17092. /**
  17093. * Parses a MutationMetadata from its JSON representation in WebStorage.
  17094. * Logs a warning and returns null if the format of the data is not valid.
  17095. */
  17096. static fromWebStorageEntry(user, batchId, value) {
  17097. const mutationBatch = JSON.parse(value);
  17098. let validData = typeof mutationBatch === 'object' &&
  17099. ['pending', 'acknowledged', 'rejected'].indexOf(mutationBatch.state) !==
  17100. -1 &&
  17101. (mutationBatch.error === undefined ||
  17102. typeof mutationBatch.error === 'object');
  17103. let firestoreError = undefined;
  17104. if (validData && mutationBatch.error) {
  17105. validData =
  17106. typeof mutationBatch.error.message === 'string' &&
  17107. typeof mutationBatch.error.code === 'string';
  17108. if (validData) {
  17109. firestoreError = new FirestoreError(mutationBatch.error.code, mutationBatch.error.message);
  17110. }
  17111. }
  17112. if (validData) {
  17113. return new MutationMetadata(user, batchId, mutationBatch.state, firestoreError);
  17114. }
  17115. else {
  17116. logError(LOG_TAG$a, `Failed to parse mutation state for ID '${batchId}': ${value}`);
  17117. return null;
  17118. }
  17119. }
  17120. toWebStorageJSON() {
  17121. const batchMetadata = {
  17122. state: this.state,
  17123. updateTimeMs: Date.now() // Modify the existing value to trigger update.
  17124. };
  17125. if (this.error) {
  17126. batchMetadata.error = {
  17127. code: this.error.code,
  17128. message: this.error.message
  17129. };
  17130. }
  17131. return JSON.stringify(batchMetadata);
  17132. }
  17133. }
  17134. /**
  17135. * Holds the state of a query target, including its target ID and whether the
  17136. * target is 'not-current', 'current' or 'rejected'.
  17137. */
  17138. // Visible for testing
  17139. class QueryTargetMetadata {
  17140. constructor(targetId, state, error) {
  17141. this.targetId = targetId;
  17142. this.state = state;
  17143. this.error = error;
  17144. }
  17145. /**
  17146. * Parses a QueryTargetMetadata from its JSON representation in WebStorage.
  17147. * Logs a warning and returns null if the format of the data is not valid.
  17148. */
  17149. static fromWebStorageEntry(targetId, value) {
  17150. const targetState = JSON.parse(value);
  17151. let validData = typeof targetState === 'object' &&
  17152. ['not-current', 'current', 'rejected'].indexOf(targetState.state) !==
  17153. -1 &&
  17154. (targetState.error === undefined ||
  17155. typeof targetState.error === 'object');
  17156. let firestoreError = undefined;
  17157. if (validData && targetState.error) {
  17158. validData =
  17159. typeof targetState.error.message === 'string' &&
  17160. typeof targetState.error.code === 'string';
  17161. if (validData) {
  17162. firestoreError = new FirestoreError(targetState.error.code, targetState.error.message);
  17163. }
  17164. }
  17165. if (validData) {
  17166. return new QueryTargetMetadata(targetId, targetState.state, firestoreError);
  17167. }
  17168. else {
  17169. logError(LOG_TAG$a, `Failed to parse target state for ID '${targetId}': ${value}`);
  17170. return null;
  17171. }
  17172. }
  17173. toWebStorageJSON() {
  17174. const targetState = {
  17175. state: this.state,
  17176. updateTimeMs: Date.now() // Modify the existing value to trigger update.
  17177. };
  17178. if (this.error) {
  17179. targetState.error = {
  17180. code: this.error.code,
  17181. message: this.error.message
  17182. };
  17183. }
  17184. return JSON.stringify(targetState);
  17185. }
  17186. }
  17187. /**
  17188. * This class represents the immutable ClientState for a client read from
  17189. * WebStorage, containing the list of active query targets.
  17190. */
  17191. class RemoteClientState {
  17192. constructor(clientId, activeTargetIds) {
  17193. this.clientId = clientId;
  17194. this.activeTargetIds = activeTargetIds;
  17195. }
  17196. /**
  17197. * Parses a RemoteClientState from the JSON representation in WebStorage.
  17198. * Logs a warning and returns null if the format of the data is not valid.
  17199. */
  17200. static fromWebStorageEntry(clientId, value) {
  17201. const clientState = JSON.parse(value);
  17202. let validData = typeof clientState === 'object' &&
  17203. clientState.activeTargetIds instanceof Array;
  17204. let activeTargetIdsSet = targetIdSet();
  17205. for (let i = 0; validData && i < clientState.activeTargetIds.length; ++i) {
  17206. validData = isSafeInteger(clientState.activeTargetIds[i]);
  17207. activeTargetIdsSet = activeTargetIdsSet.add(clientState.activeTargetIds[i]);
  17208. }
  17209. if (validData) {
  17210. return new RemoteClientState(clientId, activeTargetIdsSet);
  17211. }
  17212. else {
  17213. logError(LOG_TAG$a, `Failed to parse client data for instance '${clientId}': ${value}`);
  17214. return null;
  17215. }
  17216. }
  17217. }
  17218. /**
  17219. * This class represents the online state for all clients participating in
  17220. * multi-tab. The online state is only written to by the primary client, and
  17221. * used in secondary clients to update their query views.
  17222. */
  17223. class SharedOnlineState {
  17224. constructor(clientId, onlineState) {
  17225. this.clientId = clientId;
  17226. this.onlineState = onlineState;
  17227. }
  17228. /**
  17229. * Parses a SharedOnlineState from its JSON representation in WebStorage.
  17230. * Logs a warning and returns null if the format of the data is not valid.
  17231. */
  17232. static fromWebStorageEntry(value) {
  17233. const onlineState = JSON.parse(value);
  17234. const validData = typeof onlineState === 'object' &&
  17235. ['Unknown', 'Online', 'Offline'].indexOf(onlineState.onlineState) !==
  17236. -1 &&
  17237. typeof onlineState.clientId === 'string';
  17238. if (validData) {
  17239. return new SharedOnlineState(onlineState.clientId, onlineState.onlineState);
  17240. }
  17241. else {
  17242. logError(LOG_TAG$a, `Failed to parse online state: ${value}`);
  17243. return null;
  17244. }
  17245. }
  17246. }
  17247. /**
  17248. * Metadata state of the local client. Unlike `RemoteClientState`, this class is
  17249. * mutable and keeps track of all pending mutations, which allows us to
  17250. * update the range of pending mutation batch IDs as new mutations are added or
  17251. * removed.
  17252. *
  17253. * The data in `LocalClientState` is not read from WebStorage and instead
  17254. * updated via its instance methods. The updated state can be serialized via
  17255. * `toWebStorageJSON()`.
  17256. */
  17257. // Visible for testing.
  17258. class LocalClientState {
  17259. constructor() {
  17260. this.activeTargetIds = targetIdSet();
  17261. }
  17262. addQueryTarget(targetId) {
  17263. this.activeTargetIds = this.activeTargetIds.add(targetId);
  17264. }
  17265. removeQueryTarget(targetId) {
  17266. this.activeTargetIds = this.activeTargetIds.delete(targetId);
  17267. }
  17268. /**
  17269. * Converts this entry into a JSON-encoded format we can use for WebStorage.
  17270. * Does not encode `clientId` as it is part of the key in WebStorage.
  17271. */
  17272. toWebStorageJSON() {
  17273. const data = {
  17274. activeTargetIds: this.activeTargetIds.toArray(),
  17275. updateTimeMs: Date.now() // Modify the existing value to trigger update.
  17276. };
  17277. return JSON.stringify(data);
  17278. }
  17279. }
  17280. /**
  17281. * `WebStorageSharedClientState` uses WebStorage (window.localStorage) as the
  17282. * backing store for the SharedClientState. It keeps track of all active
  17283. * clients and supports modifications of the local client's data.
  17284. */
  17285. class WebStorageSharedClientState {
  17286. constructor(window, queue, persistenceKey, localClientId, initialUser) {
  17287. this.window = window;
  17288. this.queue = queue;
  17289. this.persistenceKey = persistenceKey;
  17290. this.localClientId = localClientId;
  17291. this.syncEngine = null;
  17292. this.onlineStateHandler = null;
  17293. this.sequenceNumberHandler = null;
  17294. this.storageListener = this.handleWebStorageEvent.bind(this);
  17295. this.activeClients = new SortedMap(primitiveComparator);
  17296. this.started = false;
  17297. /**
  17298. * Captures WebStorage events that occur before `start()` is called. These
  17299. * events are replayed once `WebStorageSharedClientState` is started.
  17300. */
  17301. this.earlyEvents = [];
  17302. // Escape the special characters mentioned here:
  17303. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
  17304. const escapedPersistenceKey = persistenceKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  17305. this.storage = this.window.localStorage;
  17306. this.currentUser = initialUser;
  17307. this.localClientStorageKey = createWebStorageClientStateKey(this.persistenceKey, this.localClientId);
  17308. this.sequenceNumberKey = createWebStorageSequenceNumberKey(this.persistenceKey);
  17309. this.activeClients = this.activeClients.insert(this.localClientId, new LocalClientState());
  17310. this.clientStateKeyRe = new RegExp(`^${CLIENT_STATE_KEY_PREFIX}_${escapedPersistenceKey}_([^_]*)$`);
  17311. this.mutationBatchKeyRe = new RegExp(`^${MUTATION_BATCH_KEY_PREFIX}_${escapedPersistenceKey}_(\\d+)(?:_(.*))?$`);
  17312. this.queryTargetKeyRe = new RegExp(`^${QUERY_TARGET_KEY_PREFIX}_${escapedPersistenceKey}_(\\d+)$`);
  17313. this.onlineStateKey = createWebStorageOnlineStateKey(this.persistenceKey);
  17314. this.bundleLoadedKey = createBundleLoadedKey(this.persistenceKey);
  17315. // Rather than adding the storage observer during start(), we add the
  17316. // storage observer during initialization. This ensures that we collect
  17317. // events before other components populate their initial state (during their
  17318. // respective start() calls). Otherwise, we might for example miss a
  17319. // mutation that is added after LocalStore's start() processed the existing
  17320. // mutations but before we observe WebStorage events.
  17321. this.window.addEventListener('storage', this.storageListener);
  17322. }
  17323. /** Returns 'true' if WebStorage is available in the current environment. */
  17324. static isAvailable(window) {
  17325. return !!(window && window.localStorage);
  17326. }
  17327. async start() {
  17328. // Retrieve the list of existing clients to backfill the data in
  17329. // SharedClientState.
  17330. const existingClients = await this.syncEngine.getActiveClients();
  17331. for (const clientId of existingClients) {
  17332. if (clientId === this.localClientId) {
  17333. continue;
  17334. }
  17335. const storageItem = this.getItem(createWebStorageClientStateKey(this.persistenceKey, clientId));
  17336. if (storageItem) {
  17337. const clientState = RemoteClientState.fromWebStorageEntry(clientId, storageItem);
  17338. if (clientState) {
  17339. this.activeClients = this.activeClients.insert(clientState.clientId, clientState);
  17340. }
  17341. }
  17342. }
  17343. this.persistClientState();
  17344. // Check if there is an existing online state and call the callback handler
  17345. // if applicable.
  17346. const onlineStateJSON = this.storage.getItem(this.onlineStateKey);
  17347. if (onlineStateJSON) {
  17348. const onlineState = this.fromWebStorageOnlineState(onlineStateJSON);
  17349. if (onlineState) {
  17350. this.handleOnlineStateEvent(onlineState);
  17351. }
  17352. }
  17353. for (const event of this.earlyEvents) {
  17354. this.handleWebStorageEvent(event);
  17355. }
  17356. this.earlyEvents = [];
  17357. // Register a window unload hook to remove the client metadata entry from
  17358. // WebStorage even if `shutdown()` was not called.
  17359. this.window.addEventListener('pagehide', () => this.shutdown());
  17360. this.started = true;
  17361. }
  17362. writeSequenceNumber(sequenceNumber) {
  17363. this.setItem(this.sequenceNumberKey, JSON.stringify(sequenceNumber));
  17364. }
  17365. getAllActiveQueryTargets() {
  17366. return this.extractActiveQueryTargets(this.activeClients);
  17367. }
  17368. isActiveQueryTarget(targetId) {
  17369. let found = false;
  17370. this.activeClients.forEach((key, value) => {
  17371. if (value.activeTargetIds.has(targetId)) {
  17372. found = true;
  17373. }
  17374. });
  17375. return found;
  17376. }
  17377. addPendingMutation(batchId) {
  17378. this.persistMutationState(batchId, 'pending');
  17379. }
  17380. updateMutationState(batchId, state, error) {
  17381. this.persistMutationState(batchId, state, error);
  17382. // Once a final mutation result is observed by other clients, they no longer
  17383. // access the mutation's metadata entry. Since WebStorage replays events
  17384. // in order, it is safe to delete the entry right after updating it.
  17385. this.removeMutationState(batchId);
  17386. }
  17387. addLocalQueryTarget(targetId) {
  17388. let queryState = 'not-current';
  17389. // Lookup an existing query state if the target ID was already registered
  17390. // by another tab
  17391. if (this.isActiveQueryTarget(targetId)) {
  17392. const storageItem = this.storage.getItem(createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId));
  17393. if (storageItem) {
  17394. const metadata = QueryTargetMetadata.fromWebStorageEntry(targetId, storageItem);
  17395. if (metadata) {
  17396. queryState = metadata.state;
  17397. }
  17398. }
  17399. }
  17400. this.localClientState.addQueryTarget(targetId);
  17401. this.persistClientState();
  17402. return queryState;
  17403. }
  17404. removeLocalQueryTarget(targetId) {
  17405. this.localClientState.removeQueryTarget(targetId);
  17406. this.persistClientState();
  17407. }
  17408. isLocalQueryTarget(targetId) {
  17409. return this.localClientState.activeTargetIds.has(targetId);
  17410. }
  17411. clearQueryState(targetId) {
  17412. this.removeItem(createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId));
  17413. }
  17414. updateQueryState(targetId, state, error) {
  17415. this.persistQueryTargetState(targetId, state, error);
  17416. }
  17417. handleUserChange(user, removedBatchIds, addedBatchIds) {
  17418. removedBatchIds.forEach(batchId => {
  17419. this.removeMutationState(batchId);
  17420. });
  17421. this.currentUser = user;
  17422. addedBatchIds.forEach(batchId => {
  17423. this.addPendingMutation(batchId);
  17424. });
  17425. }
  17426. setOnlineState(onlineState) {
  17427. this.persistOnlineState(onlineState);
  17428. }
  17429. notifyBundleLoaded(collectionGroups) {
  17430. this.persistBundleLoadedState(collectionGroups);
  17431. }
  17432. shutdown() {
  17433. if (this.started) {
  17434. this.window.removeEventListener('storage', this.storageListener);
  17435. this.removeItem(this.localClientStorageKey);
  17436. this.started = false;
  17437. }
  17438. }
  17439. getItem(key) {
  17440. const value = this.storage.getItem(key);
  17441. logDebug(LOG_TAG$a, 'READ', key, value);
  17442. return value;
  17443. }
  17444. setItem(key, value) {
  17445. logDebug(LOG_TAG$a, 'SET', key, value);
  17446. this.storage.setItem(key, value);
  17447. }
  17448. removeItem(key) {
  17449. logDebug(LOG_TAG$a, 'REMOVE', key);
  17450. this.storage.removeItem(key);
  17451. }
  17452. handleWebStorageEvent(event) {
  17453. // Note: The function is typed to take Event to be interface-compatible with
  17454. // `Window.addEventListener`.
  17455. const storageEvent = event;
  17456. if (storageEvent.storageArea === this.storage) {
  17457. logDebug(LOG_TAG$a, 'EVENT', storageEvent.key, storageEvent.newValue);
  17458. if (storageEvent.key === this.localClientStorageKey) {
  17459. logError('Received WebStorage notification for local change. Another client might have ' +
  17460. 'garbage-collected our state');
  17461. return;
  17462. }
  17463. this.queue.enqueueRetryable(async () => {
  17464. if (!this.started) {
  17465. this.earlyEvents.push(storageEvent);
  17466. return;
  17467. }
  17468. if (storageEvent.key === null) {
  17469. return;
  17470. }
  17471. if (this.clientStateKeyRe.test(storageEvent.key)) {
  17472. if (storageEvent.newValue != null) {
  17473. const clientState = this.fromWebStorageClientState(storageEvent.key, storageEvent.newValue);
  17474. if (clientState) {
  17475. return this.handleClientStateEvent(clientState.clientId, clientState);
  17476. }
  17477. }
  17478. else {
  17479. const clientId = this.fromWebStorageClientStateKey(storageEvent.key);
  17480. return this.handleClientStateEvent(clientId, null);
  17481. }
  17482. }
  17483. else if (this.mutationBatchKeyRe.test(storageEvent.key)) {
  17484. if (storageEvent.newValue !== null) {
  17485. const mutationMetadata = this.fromWebStorageMutationMetadata(storageEvent.key, storageEvent.newValue);
  17486. if (mutationMetadata) {
  17487. return this.handleMutationBatchEvent(mutationMetadata);
  17488. }
  17489. }
  17490. }
  17491. else if (this.queryTargetKeyRe.test(storageEvent.key)) {
  17492. if (storageEvent.newValue !== null) {
  17493. const queryTargetMetadata = this.fromWebStorageQueryTargetMetadata(storageEvent.key, storageEvent.newValue);
  17494. if (queryTargetMetadata) {
  17495. return this.handleQueryTargetEvent(queryTargetMetadata);
  17496. }
  17497. }
  17498. }
  17499. else if (storageEvent.key === this.onlineStateKey) {
  17500. if (storageEvent.newValue !== null) {
  17501. const onlineState = this.fromWebStorageOnlineState(storageEvent.newValue);
  17502. if (onlineState) {
  17503. return this.handleOnlineStateEvent(onlineState);
  17504. }
  17505. }
  17506. }
  17507. else if (storageEvent.key === this.sequenceNumberKey) {
  17508. const sequenceNumber = fromWebStorageSequenceNumber(storageEvent.newValue);
  17509. if (sequenceNumber !== ListenSequence.INVALID) {
  17510. this.sequenceNumberHandler(sequenceNumber);
  17511. }
  17512. }
  17513. else if (storageEvent.key === this.bundleLoadedKey) {
  17514. const collectionGroups = this.fromWebStoreBundleLoadedState(storageEvent.newValue);
  17515. await Promise.all(collectionGroups.map(cg => this.syncEngine.synchronizeWithChangedDocuments(cg)));
  17516. }
  17517. });
  17518. }
  17519. }
  17520. get localClientState() {
  17521. return this.activeClients.get(this.localClientId);
  17522. }
  17523. persistClientState() {
  17524. this.setItem(this.localClientStorageKey, this.localClientState.toWebStorageJSON());
  17525. }
  17526. persistMutationState(batchId, state, error) {
  17527. const mutationState = new MutationMetadata(this.currentUser, batchId, state, error);
  17528. const mutationKey = createWebStorageMutationBatchKey(this.persistenceKey, this.currentUser, batchId);
  17529. this.setItem(mutationKey, mutationState.toWebStorageJSON());
  17530. }
  17531. removeMutationState(batchId) {
  17532. const mutationKey = createWebStorageMutationBatchKey(this.persistenceKey, this.currentUser, batchId);
  17533. this.removeItem(mutationKey);
  17534. }
  17535. persistOnlineState(onlineState) {
  17536. const entry = {
  17537. clientId: this.localClientId,
  17538. onlineState
  17539. };
  17540. this.storage.setItem(this.onlineStateKey, JSON.stringify(entry));
  17541. }
  17542. persistQueryTargetState(targetId, state, error) {
  17543. const targetKey = createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId);
  17544. const targetMetadata = new QueryTargetMetadata(targetId, state, error);
  17545. this.setItem(targetKey, targetMetadata.toWebStorageJSON());
  17546. }
  17547. persistBundleLoadedState(collectionGroups) {
  17548. const json = JSON.stringify(Array.from(collectionGroups));
  17549. this.setItem(this.bundleLoadedKey, json);
  17550. }
  17551. /**
  17552. * Parses a client state key in WebStorage. Returns null if the key does not
  17553. * match the expected key format.
  17554. */
  17555. fromWebStorageClientStateKey(key) {
  17556. const match = this.clientStateKeyRe.exec(key);
  17557. return match ? match[1] : null;
  17558. }
  17559. /**
  17560. * Parses a client state in WebStorage. Returns 'null' if the value could not
  17561. * be parsed.
  17562. */
  17563. fromWebStorageClientState(key, value) {
  17564. const clientId = this.fromWebStorageClientStateKey(key);
  17565. return RemoteClientState.fromWebStorageEntry(clientId, value);
  17566. }
  17567. /**
  17568. * Parses a mutation batch state in WebStorage. Returns 'null' if the value
  17569. * could not be parsed.
  17570. */
  17571. fromWebStorageMutationMetadata(key, value) {
  17572. const match = this.mutationBatchKeyRe.exec(key);
  17573. const batchId = Number(match[1]);
  17574. const userId = match[2] !== undefined ? match[2] : null;
  17575. return MutationMetadata.fromWebStorageEntry(new User(userId), batchId, value);
  17576. }
  17577. /**
  17578. * Parses a query target state from WebStorage. Returns 'null' if the value
  17579. * could not be parsed.
  17580. */
  17581. fromWebStorageQueryTargetMetadata(key, value) {
  17582. const match = this.queryTargetKeyRe.exec(key);
  17583. const targetId = Number(match[1]);
  17584. return QueryTargetMetadata.fromWebStorageEntry(targetId, value);
  17585. }
  17586. /**
  17587. * Parses an online state from WebStorage. Returns 'null' if the value
  17588. * could not be parsed.
  17589. */
  17590. fromWebStorageOnlineState(value) {
  17591. return SharedOnlineState.fromWebStorageEntry(value);
  17592. }
  17593. fromWebStoreBundleLoadedState(value) {
  17594. return JSON.parse(value);
  17595. }
  17596. async handleMutationBatchEvent(mutationBatch) {
  17597. if (mutationBatch.user.uid !== this.currentUser.uid) {
  17598. logDebug(LOG_TAG$a, `Ignoring mutation for non-active user ${mutationBatch.user.uid}`);
  17599. return;
  17600. }
  17601. return this.syncEngine.applyBatchState(mutationBatch.batchId, mutationBatch.state, mutationBatch.error);
  17602. }
  17603. handleQueryTargetEvent(targetMetadata) {
  17604. return this.syncEngine.applyTargetState(targetMetadata.targetId, targetMetadata.state, targetMetadata.error);
  17605. }
  17606. handleClientStateEvent(clientId, clientState) {
  17607. const updatedClients = clientState
  17608. ? this.activeClients.insert(clientId, clientState)
  17609. : this.activeClients.remove(clientId);
  17610. const existingTargets = this.extractActiveQueryTargets(this.activeClients);
  17611. const newTargets = this.extractActiveQueryTargets(updatedClients);
  17612. const addedTargets = [];
  17613. const removedTargets = [];
  17614. newTargets.forEach(targetId => {
  17615. if (!existingTargets.has(targetId)) {
  17616. addedTargets.push(targetId);
  17617. }
  17618. });
  17619. existingTargets.forEach(targetId => {
  17620. if (!newTargets.has(targetId)) {
  17621. removedTargets.push(targetId);
  17622. }
  17623. });
  17624. return this.syncEngine.applyActiveTargetsChange(addedTargets, removedTargets).then(() => {
  17625. this.activeClients = updatedClients;
  17626. });
  17627. }
  17628. handleOnlineStateEvent(onlineState) {
  17629. // We check whether the client that wrote this online state is still active
  17630. // by comparing its client ID to the list of clients kept active in
  17631. // IndexedDb. If a client does not update their IndexedDb client state
  17632. // within 5 seconds, it is considered inactive and we don't emit an online
  17633. // state event.
  17634. if (this.activeClients.get(onlineState.clientId)) {
  17635. this.onlineStateHandler(onlineState.onlineState);
  17636. }
  17637. }
  17638. extractActiveQueryTargets(clients) {
  17639. let activeTargets = targetIdSet();
  17640. clients.forEach((kev, value) => {
  17641. activeTargets = activeTargets.unionWith(value.activeTargetIds);
  17642. });
  17643. return activeTargets;
  17644. }
  17645. }
  17646. function fromWebStorageSequenceNumber(seqString) {
  17647. let sequenceNumber = ListenSequence.INVALID;
  17648. if (seqString != null) {
  17649. try {
  17650. const parsed = JSON.parse(seqString);
  17651. hardAssert(typeof parsed === 'number');
  17652. sequenceNumber = parsed;
  17653. }
  17654. catch (e) {
  17655. logError(LOG_TAG$a, 'Failed to read sequence number from WebStorage', e);
  17656. }
  17657. }
  17658. return sequenceNumber;
  17659. }
  17660. /**
  17661. * `MemorySharedClientState` is a simple implementation of SharedClientState for
  17662. * clients using memory persistence. The state in this class remains fully
  17663. * isolated and no synchronization is performed.
  17664. */
  17665. class MemorySharedClientState {
  17666. constructor() {
  17667. this.localState = new LocalClientState();
  17668. this.queryState = {};
  17669. this.onlineStateHandler = null;
  17670. this.sequenceNumberHandler = null;
  17671. }
  17672. addPendingMutation(batchId) {
  17673. // No op.
  17674. }
  17675. updateMutationState(batchId, state, error) {
  17676. // No op.
  17677. }
  17678. addLocalQueryTarget(targetId) {
  17679. this.localState.addQueryTarget(targetId);
  17680. return this.queryState[targetId] || 'not-current';
  17681. }
  17682. updateQueryState(targetId, state, error) {
  17683. this.queryState[targetId] = state;
  17684. }
  17685. removeLocalQueryTarget(targetId) {
  17686. this.localState.removeQueryTarget(targetId);
  17687. }
  17688. isLocalQueryTarget(targetId) {
  17689. return this.localState.activeTargetIds.has(targetId);
  17690. }
  17691. clearQueryState(targetId) {
  17692. delete this.queryState[targetId];
  17693. }
  17694. getAllActiveQueryTargets() {
  17695. return this.localState.activeTargetIds;
  17696. }
  17697. isActiveQueryTarget(targetId) {
  17698. return this.localState.activeTargetIds.has(targetId);
  17699. }
  17700. start() {
  17701. this.localState = new LocalClientState();
  17702. return Promise.resolve();
  17703. }
  17704. handleUserChange(user, removedBatchIds, addedBatchIds) {
  17705. // No op.
  17706. }
  17707. setOnlineState(onlineState) {
  17708. // No op.
  17709. }
  17710. shutdown() { }
  17711. writeSequenceNumber(sequenceNumber) { }
  17712. notifyBundleLoaded(collectionGroups) {
  17713. // No op.
  17714. }
  17715. }
  17716. /**
  17717. * @license
  17718. * Copyright 2019 Google LLC
  17719. *
  17720. * Licensed under the Apache License, Version 2.0 (the "License");
  17721. * you may not use this file except in compliance with the License.
  17722. * You may obtain a copy of the License at
  17723. *
  17724. * http://www.apache.org/licenses/LICENSE-2.0
  17725. *
  17726. * Unless required by applicable law or agreed to in writing, software
  17727. * distributed under the License is distributed on an "AS IS" BASIS,
  17728. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17729. * See the License for the specific language governing permissions and
  17730. * limitations under the License.
  17731. */
  17732. class NoopConnectivityMonitor {
  17733. addCallback(callback) {
  17734. // No-op.
  17735. }
  17736. shutdown() {
  17737. // No-op.
  17738. }
  17739. }
  17740. /**
  17741. * @license
  17742. * Copyright 2017 Google LLC
  17743. *
  17744. * Licensed under the Apache License, Version 2.0 (the "License");
  17745. * you may not use this file except in compliance with the License.
  17746. * You may obtain a copy of the License at
  17747. *
  17748. * http://www.apache.org/licenses/LICENSE-2.0
  17749. *
  17750. * Unless required by applicable law or agreed to in writing, software
  17751. * distributed under the License is distributed on an "AS IS" BASIS,
  17752. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17753. * See the License for the specific language governing permissions and
  17754. * limitations under the License.
  17755. */
  17756. /**
  17757. * Provides a simple helper class that implements the Stream interface to
  17758. * bridge to other implementations that are streams but do not implement the
  17759. * interface. The stream callbacks are invoked with the callOn... methods.
  17760. */
  17761. class StreamBridge {
  17762. constructor(args) {
  17763. this.sendFn = args.sendFn;
  17764. this.closeFn = args.closeFn;
  17765. }
  17766. onOpen(callback) {
  17767. this.wrappedOnOpen = callback;
  17768. }
  17769. onClose(callback) {
  17770. this.wrappedOnClose = callback;
  17771. }
  17772. onMessage(callback) {
  17773. this.wrappedOnMessage = callback;
  17774. }
  17775. close() {
  17776. this.closeFn();
  17777. }
  17778. send(msg) {
  17779. this.sendFn(msg);
  17780. }
  17781. callOnOpen() {
  17782. this.wrappedOnOpen();
  17783. }
  17784. callOnClose(err) {
  17785. this.wrappedOnClose(err);
  17786. }
  17787. callOnMessage(msg) {
  17788. this.wrappedOnMessage(msg);
  17789. }
  17790. }
  17791. /**
  17792. * @license
  17793. * Copyright 2017 Google LLC
  17794. *
  17795. * Licensed under the Apache License, Version 2.0 (the "License");
  17796. * you may not use this file except in compliance with the License.
  17797. * You may obtain a copy of the License at
  17798. *
  17799. * http://www.apache.org/licenses/LICENSE-2.0
  17800. *
  17801. * Unless required by applicable law or agreed to in writing, software
  17802. * distributed under the License is distributed on an "AS IS" BASIS,
  17803. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17804. * See the License for the specific language governing permissions and
  17805. * limitations under the License.
  17806. */
  17807. /*
  17808. * Utilities for dealing with node.js-style APIs. See nodePromise for more
  17809. * details.
  17810. */
  17811. /**
  17812. * Creates a node-style callback that resolves or rejects a new Promise. The
  17813. * callback is passed to the given action which can then use the callback as
  17814. * a parameter to a node-style function.
  17815. *
  17816. * The intent is to directly bridge a node-style function (which takes a
  17817. * callback) into a Promise without manually converting between the node-style
  17818. * callback and the promise at each call.
  17819. *
  17820. * In effect it allows you to convert:
  17821. *
  17822. * @example
  17823. * new Promise((resolve: (value?: fs.Stats) => void,
  17824. * reject: (error?: any) => void) => {
  17825. * fs.stat(path, (error?: any, stat?: fs.Stats) => {
  17826. * if (error) {
  17827. * reject(error);
  17828. * } else {
  17829. * resolve(stat);
  17830. * }
  17831. * });
  17832. * });
  17833. *
  17834. * Into
  17835. * @example
  17836. * nodePromise((callback: NodeCallback<fs.Stats>) => {
  17837. * fs.stat(path, callback);
  17838. * });
  17839. *
  17840. * @param action - a function that takes a node-style callback as an argument
  17841. * and then uses that callback to invoke some node-style API.
  17842. * @returns a new Promise which will be rejected if the callback is given the
  17843. * first Error parameter or will resolve to the value given otherwise.
  17844. */
  17845. function nodePromise(action) {
  17846. return new Promise((resolve, reject) => {
  17847. action((error, value) => {
  17848. if (error) {
  17849. reject(error);
  17850. }
  17851. else {
  17852. resolve(value);
  17853. }
  17854. });
  17855. });
  17856. }
  17857. /**
  17858. * @license
  17859. * Copyright 2017 Google LLC
  17860. *
  17861. * Licensed under the Apache License, Version 2.0 (the "License");
  17862. * you may not use this file except in compliance with the License.
  17863. * You may obtain a copy of the License at
  17864. *
  17865. * http://www.apache.org/licenses/LICENSE-2.0
  17866. *
  17867. * Unless required by applicable law or agreed to in writing, software
  17868. * distributed under the License is distributed on an "AS IS" BASIS,
  17869. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17870. * See the License for the specific language governing permissions and
  17871. * limitations under the License.
  17872. */
  17873. // TODO: Fetch runtime version from grpc-js/package.json instead
  17874. // when there's a cleaner way to dynamic require JSON in both Node ESM and CJS
  17875. const grpcVersion = '1.7.3';
  17876. const LOG_TAG$9 = 'Connection';
  17877. const X_GOOG_API_CLIENT_VALUE = `gl-node/${process.versions.node} fire/${SDK_VERSION} grpc/${grpcVersion}`;
  17878. function createMetadata(databasePath, authToken, appCheckToken, appId) {
  17879. hardAssert(authToken === null || authToken.type === 'OAuth');
  17880. const metadata = new grpc__namespace.Metadata();
  17881. if (authToken) {
  17882. authToken.headers.forEach((value, key) => metadata.set(key, value));
  17883. }
  17884. if (appCheckToken) {
  17885. appCheckToken.headers.forEach((value, key) => metadata.set(key, value));
  17886. }
  17887. if (appId) {
  17888. metadata.set('X-Firebase-GMPID', appId);
  17889. }
  17890. metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
  17891. // These headers are used to improve routing and project isolation by the
  17892. // backend.
  17893. // TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
  17894. // released with cl/428820046. Currently blocked because Emulators are now built with Java
  17895. // 11 from Google3.
  17896. metadata.set('Google-Cloud-Resource-Prefix', databasePath);
  17897. metadata.set('x-goog-request-params', databasePath);
  17898. return metadata;
  17899. }
  17900. /**
  17901. * A Connection implemented by GRPC-Node.
  17902. */
  17903. class GrpcConnection {
  17904. constructor(protos, databaseInfo) {
  17905. this.databaseInfo = databaseInfo;
  17906. // We cache stubs for the most-recently-used token.
  17907. this.cachedStub = null;
  17908. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  17909. this.firestore = protos['google']['firestore']['v1'];
  17910. this.databasePath = `projects/${databaseInfo.databaseId.projectId}/databases/${databaseInfo.databaseId.database}`;
  17911. }
  17912. get shouldResourcePathBeIncludedInRequest() {
  17913. // Both `invokeRPC()` and `invokeStreamingRPC()` ignore their `path` arguments, and expect
  17914. // the "path" to be part of the given `request`.
  17915. return true;
  17916. }
  17917. ensureActiveStub() {
  17918. if (!this.cachedStub) {
  17919. logDebug(LOG_TAG$9, 'Creating Firestore stub.');
  17920. const credentials = this.databaseInfo.ssl
  17921. ? grpc__namespace.credentials.createSsl()
  17922. : grpc__namespace.credentials.createInsecure();
  17923. this.cachedStub = new this.firestore.Firestore(this.databaseInfo.host, credentials);
  17924. }
  17925. return this.cachedStub;
  17926. }
  17927. invokeRPC(rpcName, path, request, authToken, appCheckToken) {
  17928. const stub = this.ensureActiveStub();
  17929. const metadata = createMetadata(this.databasePath, authToken, appCheckToken, this.databaseInfo.appId);
  17930. const jsonRequest = Object.assign({ database: this.databasePath }, request);
  17931. return nodePromise((callback) => {
  17932. logDebug(LOG_TAG$9, `RPC '${rpcName}' invoked with request:`, request);
  17933. return stub[rpcName](jsonRequest, metadata, (grpcError, value) => {
  17934. if (grpcError) {
  17935. logDebug(LOG_TAG$9, `RPC '${rpcName}' failed with error:`, grpcError);
  17936. callback(new FirestoreError(mapCodeFromRpcCode(grpcError.code), grpcError.message));
  17937. }
  17938. else {
  17939. logDebug(LOG_TAG$9, `RPC '${rpcName}' completed with response:`, value);
  17940. callback(undefined, value);
  17941. }
  17942. });
  17943. });
  17944. }
  17945. invokeStreamingRPC(rpcName, path, request, authToken, appCheckToken, expectedResponseCount) {
  17946. const results = [];
  17947. const responseDeferred = new Deferred();
  17948. logDebug(LOG_TAG$9, `RPC '${rpcName}' invoked (streaming) with request:`, request);
  17949. const stub = this.ensureActiveStub();
  17950. const metadata = createMetadata(this.databasePath, authToken, appCheckToken, this.databaseInfo.appId);
  17951. const jsonRequest = Object.assign(Object.assign({}, request), { database: this.databasePath });
  17952. const stream = stub[rpcName](jsonRequest, metadata);
  17953. let callbackFired = false;
  17954. stream.on('data', (response) => {
  17955. logDebug(LOG_TAG$9, `RPC ${rpcName} received result:`, response);
  17956. results.push(response);
  17957. if (expectedResponseCount !== undefined &&
  17958. results.length === expectedResponseCount) {
  17959. callbackFired = true;
  17960. responseDeferred.resolve(results);
  17961. }
  17962. });
  17963. stream.on('end', () => {
  17964. logDebug(LOG_TAG$9, `RPC '${rpcName}' completed.`);
  17965. if (!callbackFired) {
  17966. callbackFired = true;
  17967. responseDeferred.resolve(results);
  17968. }
  17969. });
  17970. stream.on('error', (grpcError) => {
  17971. logDebug(LOG_TAG$9, `RPC '${rpcName}' failed with error:`, grpcError);
  17972. const code = mapCodeFromRpcCode(grpcError.code);
  17973. responseDeferred.reject(new FirestoreError(code, grpcError.message));
  17974. });
  17975. return responseDeferred.promise;
  17976. }
  17977. // TODO(mikelehen): This "method" is a monster. Should be refactored.
  17978. openStream(rpcName, authToken, appCheckToken) {
  17979. const stub = this.ensureActiveStub();
  17980. const metadata = createMetadata(this.databasePath, authToken, appCheckToken, this.databaseInfo.appId);
  17981. const grpcStream = stub[rpcName](metadata);
  17982. let closed = false;
  17983. const close = (err) => {
  17984. if (!closed) {
  17985. closed = true;
  17986. stream.callOnClose(err);
  17987. grpcStream.end();
  17988. }
  17989. };
  17990. const stream = new StreamBridge({
  17991. sendFn: (msg) => {
  17992. if (!closed) {
  17993. logDebug(LOG_TAG$9, 'GRPC stream sending:', msg);
  17994. try {
  17995. grpcStream.write(msg);
  17996. }
  17997. catch (e) {
  17998. // This probably means we didn't conform to the proto. Make sure to
  17999. // log the message we sent.
  18000. logError('Failure sending:', msg);
  18001. logError('Error:', e);
  18002. throw e;
  18003. }
  18004. }
  18005. else {
  18006. logDebug(LOG_TAG$9, 'Not sending because gRPC stream is closed:', msg);
  18007. }
  18008. },
  18009. closeFn: () => {
  18010. logDebug(LOG_TAG$9, 'GRPC stream closed locally via close().');
  18011. close();
  18012. }
  18013. });
  18014. grpcStream.on('data', (msg) => {
  18015. if (!closed) {
  18016. logDebug(LOG_TAG$9, 'GRPC stream received:', msg);
  18017. stream.callOnMessage(msg);
  18018. }
  18019. });
  18020. grpcStream.on('end', () => {
  18021. logDebug(LOG_TAG$9, 'GRPC stream ended.');
  18022. close();
  18023. });
  18024. grpcStream.on('error', (grpcError) => {
  18025. if (!closed) {
  18026. logWarn(LOG_TAG$9, 'GRPC stream error. Code:', grpcError.code, 'Message:', grpcError.message);
  18027. const code = mapCodeFromRpcCode(grpcError.code);
  18028. close(new FirestoreError(code, grpcError.message));
  18029. }
  18030. });
  18031. logDebug(LOG_TAG$9, 'Opening GRPC stream');
  18032. // TODO(dimond): Since grpc has no explicit open status (or does it?) we
  18033. // simulate an onOpen in the next loop after the stream had it's listeners
  18034. // registered
  18035. setTimeout(() => {
  18036. stream.callOnOpen();
  18037. }, 0);
  18038. return stream;
  18039. }
  18040. }
  18041. const nested = {
  18042. google: {
  18043. nested: {
  18044. protobuf: {
  18045. options: {
  18046. csharp_namespace: "Google.Protobuf.WellKnownTypes",
  18047. go_package: "github.com/golang/protobuf/ptypes/wrappers",
  18048. java_package: "com.google.protobuf",
  18049. java_outer_classname: "WrappersProto",
  18050. java_multiple_files: true,
  18051. objc_class_prefix: "GPB",
  18052. cc_enable_arenas: true,
  18053. optimize_for: "SPEED"
  18054. },
  18055. nested: {
  18056. Timestamp: {
  18057. fields: {
  18058. seconds: {
  18059. type: "int64",
  18060. id: 1
  18061. },
  18062. nanos: {
  18063. type: "int32",
  18064. id: 2
  18065. }
  18066. }
  18067. },
  18068. FileDescriptorSet: {
  18069. fields: {
  18070. file: {
  18071. rule: "repeated",
  18072. type: "FileDescriptorProto",
  18073. id: 1
  18074. }
  18075. }
  18076. },
  18077. FileDescriptorProto: {
  18078. fields: {
  18079. name: {
  18080. type: "string",
  18081. id: 1
  18082. },
  18083. "package": {
  18084. type: "string",
  18085. id: 2
  18086. },
  18087. dependency: {
  18088. rule: "repeated",
  18089. type: "string",
  18090. id: 3
  18091. },
  18092. publicDependency: {
  18093. rule: "repeated",
  18094. type: "int32",
  18095. id: 10,
  18096. options: {
  18097. packed: false
  18098. }
  18099. },
  18100. weakDependency: {
  18101. rule: "repeated",
  18102. type: "int32",
  18103. id: 11,
  18104. options: {
  18105. packed: false
  18106. }
  18107. },
  18108. messageType: {
  18109. rule: "repeated",
  18110. type: "DescriptorProto",
  18111. id: 4
  18112. },
  18113. enumType: {
  18114. rule: "repeated",
  18115. type: "EnumDescriptorProto",
  18116. id: 5
  18117. },
  18118. service: {
  18119. rule: "repeated",
  18120. type: "ServiceDescriptorProto",
  18121. id: 6
  18122. },
  18123. extension: {
  18124. rule: "repeated",
  18125. type: "FieldDescriptorProto",
  18126. id: 7
  18127. },
  18128. options: {
  18129. type: "FileOptions",
  18130. id: 8
  18131. },
  18132. sourceCodeInfo: {
  18133. type: "SourceCodeInfo",
  18134. id: 9
  18135. },
  18136. syntax: {
  18137. type: "string",
  18138. id: 12
  18139. }
  18140. }
  18141. },
  18142. DescriptorProto: {
  18143. fields: {
  18144. name: {
  18145. type: "string",
  18146. id: 1
  18147. },
  18148. field: {
  18149. rule: "repeated",
  18150. type: "FieldDescriptorProto",
  18151. id: 2
  18152. },
  18153. extension: {
  18154. rule: "repeated",
  18155. type: "FieldDescriptorProto",
  18156. id: 6
  18157. },
  18158. nestedType: {
  18159. rule: "repeated",
  18160. type: "DescriptorProto",
  18161. id: 3
  18162. },
  18163. enumType: {
  18164. rule: "repeated",
  18165. type: "EnumDescriptorProto",
  18166. id: 4
  18167. },
  18168. extensionRange: {
  18169. rule: "repeated",
  18170. type: "ExtensionRange",
  18171. id: 5
  18172. },
  18173. oneofDecl: {
  18174. rule: "repeated",
  18175. type: "OneofDescriptorProto",
  18176. id: 8
  18177. },
  18178. options: {
  18179. type: "MessageOptions",
  18180. id: 7
  18181. },
  18182. reservedRange: {
  18183. rule: "repeated",
  18184. type: "ReservedRange",
  18185. id: 9
  18186. },
  18187. reservedName: {
  18188. rule: "repeated",
  18189. type: "string",
  18190. id: 10
  18191. }
  18192. },
  18193. nested: {
  18194. ExtensionRange: {
  18195. fields: {
  18196. start: {
  18197. type: "int32",
  18198. id: 1
  18199. },
  18200. end: {
  18201. type: "int32",
  18202. id: 2
  18203. }
  18204. }
  18205. },
  18206. ReservedRange: {
  18207. fields: {
  18208. start: {
  18209. type: "int32",
  18210. id: 1
  18211. },
  18212. end: {
  18213. type: "int32",
  18214. id: 2
  18215. }
  18216. }
  18217. }
  18218. }
  18219. },
  18220. FieldDescriptorProto: {
  18221. fields: {
  18222. name: {
  18223. type: "string",
  18224. id: 1
  18225. },
  18226. number: {
  18227. type: "int32",
  18228. id: 3
  18229. },
  18230. label: {
  18231. type: "Label",
  18232. id: 4
  18233. },
  18234. type: {
  18235. type: "Type",
  18236. id: 5
  18237. },
  18238. typeName: {
  18239. type: "string",
  18240. id: 6
  18241. },
  18242. extendee: {
  18243. type: "string",
  18244. id: 2
  18245. },
  18246. defaultValue: {
  18247. type: "string",
  18248. id: 7
  18249. },
  18250. oneofIndex: {
  18251. type: "int32",
  18252. id: 9
  18253. },
  18254. jsonName: {
  18255. type: "string",
  18256. id: 10
  18257. },
  18258. options: {
  18259. type: "FieldOptions",
  18260. id: 8
  18261. }
  18262. },
  18263. nested: {
  18264. Type: {
  18265. values: {
  18266. TYPE_DOUBLE: 1,
  18267. TYPE_FLOAT: 2,
  18268. TYPE_INT64: 3,
  18269. TYPE_UINT64: 4,
  18270. TYPE_INT32: 5,
  18271. TYPE_FIXED64: 6,
  18272. TYPE_FIXED32: 7,
  18273. TYPE_BOOL: 8,
  18274. TYPE_STRING: 9,
  18275. TYPE_GROUP: 10,
  18276. TYPE_MESSAGE: 11,
  18277. TYPE_BYTES: 12,
  18278. TYPE_UINT32: 13,
  18279. TYPE_ENUM: 14,
  18280. TYPE_SFIXED32: 15,
  18281. TYPE_SFIXED64: 16,
  18282. TYPE_SINT32: 17,
  18283. TYPE_SINT64: 18
  18284. }
  18285. },
  18286. Label: {
  18287. values: {
  18288. LABEL_OPTIONAL: 1,
  18289. LABEL_REQUIRED: 2,
  18290. LABEL_REPEATED: 3
  18291. }
  18292. }
  18293. }
  18294. },
  18295. OneofDescriptorProto: {
  18296. fields: {
  18297. name: {
  18298. type: "string",
  18299. id: 1
  18300. },
  18301. options: {
  18302. type: "OneofOptions",
  18303. id: 2
  18304. }
  18305. }
  18306. },
  18307. EnumDescriptorProto: {
  18308. fields: {
  18309. name: {
  18310. type: "string",
  18311. id: 1
  18312. },
  18313. value: {
  18314. rule: "repeated",
  18315. type: "EnumValueDescriptorProto",
  18316. id: 2
  18317. },
  18318. options: {
  18319. type: "EnumOptions",
  18320. id: 3
  18321. }
  18322. }
  18323. },
  18324. EnumValueDescriptorProto: {
  18325. fields: {
  18326. name: {
  18327. type: "string",
  18328. id: 1
  18329. },
  18330. number: {
  18331. type: "int32",
  18332. id: 2
  18333. },
  18334. options: {
  18335. type: "EnumValueOptions",
  18336. id: 3
  18337. }
  18338. }
  18339. },
  18340. ServiceDescriptorProto: {
  18341. fields: {
  18342. name: {
  18343. type: "string",
  18344. id: 1
  18345. },
  18346. method: {
  18347. rule: "repeated",
  18348. type: "MethodDescriptorProto",
  18349. id: 2
  18350. },
  18351. options: {
  18352. type: "ServiceOptions",
  18353. id: 3
  18354. }
  18355. }
  18356. },
  18357. MethodDescriptorProto: {
  18358. fields: {
  18359. name: {
  18360. type: "string",
  18361. id: 1
  18362. },
  18363. inputType: {
  18364. type: "string",
  18365. id: 2
  18366. },
  18367. outputType: {
  18368. type: "string",
  18369. id: 3
  18370. },
  18371. options: {
  18372. type: "MethodOptions",
  18373. id: 4
  18374. },
  18375. clientStreaming: {
  18376. type: "bool",
  18377. id: 5
  18378. },
  18379. serverStreaming: {
  18380. type: "bool",
  18381. id: 6
  18382. }
  18383. }
  18384. },
  18385. FileOptions: {
  18386. fields: {
  18387. javaPackage: {
  18388. type: "string",
  18389. id: 1
  18390. },
  18391. javaOuterClassname: {
  18392. type: "string",
  18393. id: 8
  18394. },
  18395. javaMultipleFiles: {
  18396. type: "bool",
  18397. id: 10
  18398. },
  18399. javaGenerateEqualsAndHash: {
  18400. type: "bool",
  18401. id: 20,
  18402. options: {
  18403. deprecated: true
  18404. }
  18405. },
  18406. javaStringCheckUtf8: {
  18407. type: "bool",
  18408. id: 27
  18409. },
  18410. optimizeFor: {
  18411. type: "OptimizeMode",
  18412. id: 9,
  18413. options: {
  18414. "default": "SPEED"
  18415. }
  18416. },
  18417. goPackage: {
  18418. type: "string",
  18419. id: 11
  18420. },
  18421. ccGenericServices: {
  18422. type: "bool",
  18423. id: 16
  18424. },
  18425. javaGenericServices: {
  18426. type: "bool",
  18427. id: 17
  18428. },
  18429. pyGenericServices: {
  18430. type: "bool",
  18431. id: 18
  18432. },
  18433. deprecated: {
  18434. type: "bool",
  18435. id: 23
  18436. },
  18437. ccEnableArenas: {
  18438. type: "bool",
  18439. id: 31
  18440. },
  18441. objcClassPrefix: {
  18442. type: "string",
  18443. id: 36
  18444. },
  18445. csharpNamespace: {
  18446. type: "string",
  18447. id: 37
  18448. },
  18449. uninterpretedOption: {
  18450. rule: "repeated",
  18451. type: "UninterpretedOption",
  18452. id: 999
  18453. }
  18454. },
  18455. extensions: [
  18456. [
  18457. 1000,
  18458. 536870911
  18459. ]
  18460. ],
  18461. reserved: [
  18462. [
  18463. 38,
  18464. 38
  18465. ]
  18466. ],
  18467. nested: {
  18468. OptimizeMode: {
  18469. values: {
  18470. SPEED: 1,
  18471. CODE_SIZE: 2,
  18472. LITE_RUNTIME: 3
  18473. }
  18474. }
  18475. }
  18476. },
  18477. MessageOptions: {
  18478. fields: {
  18479. messageSetWireFormat: {
  18480. type: "bool",
  18481. id: 1
  18482. },
  18483. noStandardDescriptorAccessor: {
  18484. type: "bool",
  18485. id: 2
  18486. },
  18487. deprecated: {
  18488. type: "bool",
  18489. id: 3
  18490. },
  18491. mapEntry: {
  18492. type: "bool",
  18493. id: 7
  18494. },
  18495. uninterpretedOption: {
  18496. rule: "repeated",
  18497. type: "UninterpretedOption",
  18498. id: 999
  18499. }
  18500. },
  18501. extensions: [
  18502. [
  18503. 1000,
  18504. 536870911
  18505. ]
  18506. ],
  18507. reserved: [
  18508. [
  18509. 8,
  18510. 8
  18511. ]
  18512. ]
  18513. },
  18514. FieldOptions: {
  18515. fields: {
  18516. ctype: {
  18517. type: "CType",
  18518. id: 1,
  18519. options: {
  18520. "default": "STRING"
  18521. }
  18522. },
  18523. packed: {
  18524. type: "bool",
  18525. id: 2
  18526. },
  18527. jstype: {
  18528. type: "JSType",
  18529. id: 6,
  18530. options: {
  18531. "default": "JS_NORMAL"
  18532. }
  18533. },
  18534. lazy: {
  18535. type: "bool",
  18536. id: 5
  18537. },
  18538. deprecated: {
  18539. type: "bool",
  18540. id: 3
  18541. },
  18542. weak: {
  18543. type: "bool",
  18544. id: 10
  18545. },
  18546. uninterpretedOption: {
  18547. rule: "repeated",
  18548. type: "UninterpretedOption",
  18549. id: 999
  18550. }
  18551. },
  18552. extensions: [
  18553. [
  18554. 1000,
  18555. 536870911
  18556. ]
  18557. ],
  18558. reserved: [
  18559. [
  18560. 4,
  18561. 4
  18562. ]
  18563. ],
  18564. nested: {
  18565. CType: {
  18566. values: {
  18567. STRING: 0,
  18568. CORD: 1,
  18569. STRING_PIECE: 2
  18570. }
  18571. },
  18572. JSType: {
  18573. values: {
  18574. JS_NORMAL: 0,
  18575. JS_STRING: 1,
  18576. JS_NUMBER: 2
  18577. }
  18578. }
  18579. }
  18580. },
  18581. OneofOptions: {
  18582. fields: {
  18583. uninterpretedOption: {
  18584. rule: "repeated",
  18585. type: "UninterpretedOption",
  18586. id: 999
  18587. }
  18588. },
  18589. extensions: [
  18590. [
  18591. 1000,
  18592. 536870911
  18593. ]
  18594. ]
  18595. },
  18596. EnumOptions: {
  18597. fields: {
  18598. allowAlias: {
  18599. type: "bool",
  18600. id: 2
  18601. },
  18602. deprecated: {
  18603. type: "bool",
  18604. id: 3
  18605. },
  18606. uninterpretedOption: {
  18607. rule: "repeated",
  18608. type: "UninterpretedOption",
  18609. id: 999
  18610. }
  18611. },
  18612. extensions: [
  18613. [
  18614. 1000,
  18615. 536870911
  18616. ]
  18617. ]
  18618. },
  18619. EnumValueOptions: {
  18620. fields: {
  18621. deprecated: {
  18622. type: "bool",
  18623. id: 1
  18624. },
  18625. uninterpretedOption: {
  18626. rule: "repeated",
  18627. type: "UninterpretedOption",
  18628. id: 999
  18629. }
  18630. },
  18631. extensions: [
  18632. [
  18633. 1000,
  18634. 536870911
  18635. ]
  18636. ]
  18637. },
  18638. ServiceOptions: {
  18639. fields: {
  18640. deprecated: {
  18641. type: "bool",
  18642. id: 33
  18643. },
  18644. uninterpretedOption: {
  18645. rule: "repeated",
  18646. type: "UninterpretedOption",
  18647. id: 999
  18648. }
  18649. },
  18650. extensions: [
  18651. [
  18652. 1000,
  18653. 536870911
  18654. ]
  18655. ]
  18656. },
  18657. MethodOptions: {
  18658. fields: {
  18659. deprecated: {
  18660. type: "bool",
  18661. id: 33
  18662. },
  18663. uninterpretedOption: {
  18664. rule: "repeated",
  18665. type: "UninterpretedOption",
  18666. id: 999
  18667. }
  18668. },
  18669. extensions: [
  18670. [
  18671. 1000,
  18672. 536870911
  18673. ]
  18674. ]
  18675. },
  18676. UninterpretedOption: {
  18677. fields: {
  18678. name: {
  18679. rule: "repeated",
  18680. type: "NamePart",
  18681. id: 2
  18682. },
  18683. identifierValue: {
  18684. type: "string",
  18685. id: 3
  18686. },
  18687. positiveIntValue: {
  18688. type: "uint64",
  18689. id: 4
  18690. },
  18691. negativeIntValue: {
  18692. type: "int64",
  18693. id: 5
  18694. },
  18695. doubleValue: {
  18696. type: "double",
  18697. id: 6
  18698. },
  18699. stringValue: {
  18700. type: "bytes",
  18701. id: 7
  18702. },
  18703. aggregateValue: {
  18704. type: "string",
  18705. id: 8
  18706. }
  18707. },
  18708. nested: {
  18709. NamePart: {
  18710. fields: {
  18711. namePart: {
  18712. rule: "required",
  18713. type: "string",
  18714. id: 1
  18715. },
  18716. isExtension: {
  18717. rule: "required",
  18718. type: "bool",
  18719. id: 2
  18720. }
  18721. }
  18722. }
  18723. }
  18724. },
  18725. SourceCodeInfo: {
  18726. fields: {
  18727. location: {
  18728. rule: "repeated",
  18729. type: "Location",
  18730. id: 1
  18731. }
  18732. },
  18733. nested: {
  18734. Location: {
  18735. fields: {
  18736. path: {
  18737. rule: "repeated",
  18738. type: "int32",
  18739. id: 1
  18740. },
  18741. span: {
  18742. rule: "repeated",
  18743. type: "int32",
  18744. id: 2
  18745. },
  18746. leadingComments: {
  18747. type: "string",
  18748. id: 3
  18749. },
  18750. trailingComments: {
  18751. type: "string",
  18752. id: 4
  18753. },
  18754. leadingDetachedComments: {
  18755. rule: "repeated",
  18756. type: "string",
  18757. id: 6
  18758. }
  18759. }
  18760. }
  18761. }
  18762. },
  18763. GeneratedCodeInfo: {
  18764. fields: {
  18765. annotation: {
  18766. rule: "repeated",
  18767. type: "Annotation",
  18768. id: 1
  18769. }
  18770. },
  18771. nested: {
  18772. Annotation: {
  18773. fields: {
  18774. path: {
  18775. rule: "repeated",
  18776. type: "int32",
  18777. id: 1
  18778. },
  18779. sourceFile: {
  18780. type: "string",
  18781. id: 2
  18782. },
  18783. begin: {
  18784. type: "int32",
  18785. id: 3
  18786. },
  18787. end: {
  18788. type: "int32",
  18789. id: 4
  18790. }
  18791. }
  18792. }
  18793. }
  18794. },
  18795. Struct: {
  18796. fields: {
  18797. fields: {
  18798. keyType: "string",
  18799. type: "Value",
  18800. id: 1
  18801. }
  18802. }
  18803. },
  18804. Value: {
  18805. oneofs: {
  18806. kind: {
  18807. oneof: [
  18808. "nullValue",
  18809. "numberValue",
  18810. "stringValue",
  18811. "boolValue",
  18812. "structValue",
  18813. "listValue"
  18814. ]
  18815. }
  18816. },
  18817. fields: {
  18818. nullValue: {
  18819. type: "NullValue",
  18820. id: 1
  18821. },
  18822. numberValue: {
  18823. type: "double",
  18824. id: 2
  18825. },
  18826. stringValue: {
  18827. type: "string",
  18828. id: 3
  18829. },
  18830. boolValue: {
  18831. type: "bool",
  18832. id: 4
  18833. },
  18834. structValue: {
  18835. type: "Struct",
  18836. id: 5
  18837. },
  18838. listValue: {
  18839. type: "ListValue",
  18840. id: 6
  18841. }
  18842. }
  18843. },
  18844. NullValue: {
  18845. values: {
  18846. NULL_VALUE: 0
  18847. }
  18848. },
  18849. ListValue: {
  18850. fields: {
  18851. values: {
  18852. rule: "repeated",
  18853. type: "Value",
  18854. id: 1
  18855. }
  18856. }
  18857. },
  18858. Empty: {
  18859. fields: {
  18860. }
  18861. },
  18862. DoubleValue: {
  18863. fields: {
  18864. value: {
  18865. type: "double",
  18866. id: 1
  18867. }
  18868. }
  18869. },
  18870. FloatValue: {
  18871. fields: {
  18872. value: {
  18873. type: "float",
  18874. id: 1
  18875. }
  18876. }
  18877. },
  18878. Int64Value: {
  18879. fields: {
  18880. value: {
  18881. type: "int64",
  18882. id: 1
  18883. }
  18884. }
  18885. },
  18886. UInt64Value: {
  18887. fields: {
  18888. value: {
  18889. type: "uint64",
  18890. id: 1
  18891. }
  18892. }
  18893. },
  18894. Int32Value: {
  18895. fields: {
  18896. value: {
  18897. type: "int32",
  18898. id: 1
  18899. }
  18900. }
  18901. },
  18902. UInt32Value: {
  18903. fields: {
  18904. value: {
  18905. type: "uint32",
  18906. id: 1
  18907. }
  18908. }
  18909. },
  18910. BoolValue: {
  18911. fields: {
  18912. value: {
  18913. type: "bool",
  18914. id: 1
  18915. }
  18916. }
  18917. },
  18918. StringValue: {
  18919. fields: {
  18920. value: {
  18921. type: "string",
  18922. id: 1
  18923. }
  18924. }
  18925. },
  18926. BytesValue: {
  18927. fields: {
  18928. value: {
  18929. type: "bytes",
  18930. id: 1
  18931. }
  18932. }
  18933. },
  18934. Any: {
  18935. fields: {
  18936. typeUrl: {
  18937. type: "string",
  18938. id: 1
  18939. },
  18940. value: {
  18941. type: "bytes",
  18942. id: 2
  18943. }
  18944. }
  18945. }
  18946. }
  18947. },
  18948. firestore: {
  18949. nested: {
  18950. v1: {
  18951. options: {
  18952. csharp_namespace: "Google.Cloud.Firestore.V1",
  18953. go_package: "google.golang.org/genproto/googleapis/firestore/v1;firestore",
  18954. java_multiple_files: true,
  18955. java_outer_classname: "WriteProto",
  18956. java_package: "com.google.firestore.v1",
  18957. objc_class_prefix: "GCFS",
  18958. php_namespace: "Google\\Cloud\\Firestore\\V1",
  18959. ruby_package: "Google::Cloud::Firestore::V1"
  18960. },
  18961. nested: {
  18962. AggregationResult: {
  18963. fields: {
  18964. aggregateFields: {
  18965. keyType: "string",
  18966. type: "Value",
  18967. id: 2
  18968. }
  18969. }
  18970. },
  18971. DocumentMask: {
  18972. fields: {
  18973. fieldPaths: {
  18974. rule: "repeated",
  18975. type: "string",
  18976. id: 1
  18977. }
  18978. }
  18979. },
  18980. Precondition: {
  18981. oneofs: {
  18982. conditionType: {
  18983. oneof: [
  18984. "exists",
  18985. "updateTime"
  18986. ]
  18987. }
  18988. },
  18989. fields: {
  18990. exists: {
  18991. type: "bool",
  18992. id: 1
  18993. },
  18994. updateTime: {
  18995. type: "google.protobuf.Timestamp",
  18996. id: 2
  18997. }
  18998. }
  18999. },
  19000. TransactionOptions: {
  19001. oneofs: {
  19002. mode: {
  19003. oneof: [
  19004. "readOnly",
  19005. "readWrite"
  19006. ]
  19007. }
  19008. },
  19009. fields: {
  19010. readOnly: {
  19011. type: "ReadOnly",
  19012. id: 2
  19013. },
  19014. readWrite: {
  19015. type: "ReadWrite",
  19016. id: 3
  19017. }
  19018. },
  19019. nested: {
  19020. ReadWrite: {
  19021. fields: {
  19022. retryTransaction: {
  19023. type: "bytes",
  19024. id: 1
  19025. }
  19026. }
  19027. },
  19028. ReadOnly: {
  19029. oneofs: {
  19030. consistencySelector: {
  19031. oneof: [
  19032. "readTime"
  19033. ]
  19034. }
  19035. },
  19036. fields: {
  19037. readTime: {
  19038. type: "google.protobuf.Timestamp",
  19039. id: 2
  19040. }
  19041. }
  19042. }
  19043. }
  19044. },
  19045. Document: {
  19046. fields: {
  19047. name: {
  19048. type: "string",
  19049. id: 1
  19050. },
  19051. fields: {
  19052. keyType: "string",
  19053. type: "Value",
  19054. id: 2
  19055. },
  19056. createTime: {
  19057. type: "google.protobuf.Timestamp",
  19058. id: 3
  19059. },
  19060. updateTime: {
  19061. type: "google.protobuf.Timestamp",
  19062. id: 4
  19063. }
  19064. }
  19065. },
  19066. Value: {
  19067. oneofs: {
  19068. valueType: {
  19069. oneof: [
  19070. "nullValue",
  19071. "booleanValue",
  19072. "integerValue",
  19073. "doubleValue",
  19074. "timestampValue",
  19075. "stringValue",
  19076. "bytesValue",
  19077. "referenceValue",
  19078. "geoPointValue",
  19079. "arrayValue",
  19080. "mapValue"
  19081. ]
  19082. }
  19083. },
  19084. fields: {
  19085. nullValue: {
  19086. type: "google.protobuf.NullValue",
  19087. id: 11
  19088. },
  19089. booleanValue: {
  19090. type: "bool",
  19091. id: 1
  19092. },
  19093. integerValue: {
  19094. type: "int64",
  19095. id: 2
  19096. },
  19097. doubleValue: {
  19098. type: "double",
  19099. id: 3
  19100. },
  19101. timestampValue: {
  19102. type: "google.protobuf.Timestamp",
  19103. id: 10
  19104. },
  19105. stringValue: {
  19106. type: "string",
  19107. id: 17
  19108. },
  19109. bytesValue: {
  19110. type: "bytes",
  19111. id: 18
  19112. },
  19113. referenceValue: {
  19114. type: "string",
  19115. id: 5
  19116. },
  19117. geoPointValue: {
  19118. type: "google.type.LatLng",
  19119. id: 8
  19120. },
  19121. arrayValue: {
  19122. type: "ArrayValue",
  19123. id: 9
  19124. },
  19125. mapValue: {
  19126. type: "MapValue",
  19127. id: 6
  19128. }
  19129. }
  19130. },
  19131. ArrayValue: {
  19132. fields: {
  19133. values: {
  19134. rule: "repeated",
  19135. type: "Value",
  19136. id: 1
  19137. }
  19138. }
  19139. },
  19140. MapValue: {
  19141. fields: {
  19142. fields: {
  19143. keyType: "string",
  19144. type: "Value",
  19145. id: 1
  19146. }
  19147. }
  19148. },
  19149. Firestore: {
  19150. options: {
  19151. "(google.api.default_host)": "firestore.googleapis.com",
  19152. "(google.api.oauth_scopes)": "https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/datastore"
  19153. },
  19154. methods: {
  19155. GetDocument: {
  19156. requestType: "GetDocumentRequest",
  19157. responseType: "Document",
  19158. options: {
  19159. "(google.api.http).get": "/v1/{name=projects/*/databases/*/documents/*/**}"
  19160. },
  19161. parsedOptions: [
  19162. {
  19163. "(google.api.http)": {
  19164. get: "/v1/{name=projects/*/databases/*/documents/*/**}"
  19165. }
  19166. }
  19167. ]
  19168. },
  19169. ListDocuments: {
  19170. requestType: "ListDocumentsRequest",
  19171. responseType: "ListDocumentsResponse",
  19172. options: {
  19173. "(google.api.http).get": "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}"
  19174. },
  19175. parsedOptions: [
  19176. {
  19177. "(google.api.http)": {
  19178. get: "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}"
  19179. }
  19180. }
  19181. ]
  19182. },
  19183. UpdateDocument: {
  19184. requestType: "UpdateDocumentRequest",
  19185. responseType: "Document",
  19186. options: {
  19187. "(google.api.http).patch": "/v1/{document.name=projects/*/databases/*/documents/*/**}",
  19188. "(google.api.http).body": "document",
  19189. "(google.api.method_signature)": "document,update_mask"
  19190. },
  19191. parsedOptions: [
  19192. {
  19193. "(google.api.http)": {
  19194. patch: "/v1/{document.name=projects/*/databases/*/documents/*/**}",
  19195. body: "document"
  19196. }
  19197. },
  19198. {
  19199. "(google.api.method_signature)": "document,update_mask"
  19200. }
  19201. ]
  19202. },
  19203. DeleteDocument: {
  19204. requestType: "DeleteDocumentRequest",
  19205. responseType: "google.protobuf.Empty",
  19206. options: {
  19207. "(google.api.http).delete": "/v1/{name=projects/*/databases/*/documents/*/**}",
  19208. "(google.api.method_signature)": "name"
  19209. },
  19210. parsedOptions: [
  19211. {
  19212. "(google.api.http)": {
  19213. "delete": "/v1/{name=projects/*/databases/*/documents/*/**}"
  19214. }
  19215. },
  19216. {
  19217. "(google.api.method_signature)": "name"
  19218. }
  19219. ]
  19220. },
  19221. BatchGetDocuments: {
  19222. requestType: "BatchGetDocumentsRequest",
  19223. responseType: "BatchGetDocumentsResponse",
  19224. responseStream: true,
  19225. options: {
  19226. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:batchGet",
  19227. "(google.api.http).body": "*"
  19228. },
  19229. parsedOptions: [
  19230. {
  19231. "(google.api.http)": {
  19232. post: "/v1/{database=projects/*/databases/*}/documents:batchGet",
  19233. body: "*"
  19234. }
  19235. }
  19236. ]
  19237. },
  19238. BeginTransaction: {
  19239. requestType: "BeginTransactionRequest",
  19240. responseType: "BeginTransactionResponse",
  19241. options: {
  19242. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:beginTransaction",
  19243. "(google.api.http).body": "*",
  19244. "(google.api.method_signature)": "database"
  19245. },
  19246. parsedOptions: [
  19247. {
  19248. "(google.api.http)": {
  19249. post: "/v1/{database=projects/*/databases/*}/documents:beginTransaction",
  19250. body: "*"
  19251. }
  19252. },
  19253. {
  19254. "(google.api.method_signature)": "database"
  19255. }
  19256. ]
  19257. },
  19258. Commit: {
  19259. requestType: "CommitRequest",
  19260. responseType: "CommitResponse",
  19261. options: {
  19262. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:commit",
  19263. "(google.api.http).body": "*",
  19264. "(google.api.method_signature)": "database,writes"
  19265. },
  19266. parsedOptions: [
  19267. {
  19268. "(google.api.http)": {
  19269. post: "/v1/{database=projects/*/databases/*}/documents:commit",
  19270. body: "*"
  19271. }
  19272. },
  19273. {
  19274. "(google.api.method_signature)": "database,writes"
  19275. }
  19276. ]
  19277. },
  19278. Rollback: {
  19279. requestType: "RollbackRequest",
  19280. responseType: "google.protobuf.Empty",
  19281. options: {
  19282. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:rollback",
  19283. "(google.api.http).body": "*",
  19284. "(google.api.method_signature)": "database,transaction"
  19285. },
  19286. parsedOptions: [
  19287. {
  19288. "(google.api.http)": {
  19289. post: "/v1/{database=projects/*/databases/*}/documents:rollback",
  19290. body: "*"
  19291. }
  19292. },
  19293. {
  19294. "(google.api.method_signature)": "database,transaction"
  19295. }
  19296. ]
  19297. },
  19298. RunQuery: {
  19299. requestType: "RunQueryRequest",
  19300. responseType: "RunQueryResponse",
  19301. responseStream: true,
  19302. options: {
  19303. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:runQuery",
  19304. "(google.api.http).body": "*",
  19305. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:runQuery",
  19306. "(google.api.http).additional_bindings.body": "*"
  19307. },
  19308. parsedOptions: [
  19309. {
  19310. "(google.api.http)": {
  19311. post: "/v1/{parent=projects/*/databases/*/documents}:runQuery",
  19312. body: "*",
  19313. additional_bindings: {
  19314. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:runQuery",
  19315. body: "*"
  19316. }
  19317. }
  19318. }
  19319. ]
  19320. },
  19321. RunAggregationQuery: {
  19322. requestType: "RunAggregationQueryRequest",
  19323. responseType: "RunAggregationQueryResponse",
  19324. responseStream: true,
  19325. options: {
  19326. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:runAggregationQuery",
  19327. "(google.api.http).body": "*",
  19328. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:runAggregationQuery",
  19329. "(google.api.http).additional_bindings.body": "*"
  19330. },
  19331. parsedOptions: [
  19332. {
  19333. "(google.api.http)": {
  19334. post: "/v1/{parent=projects/*/databases/*/documents}:runAggregationQuery",
  19335. body: "*",
  19336. additional_bindings: {
  19337. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:runAggregationQuery",
  19338. body: "*"
  19339. }
  19340. }
  19341. }
  19342. ]
  19343. },
  19344. PartitionQuery: {
  19345. requestType: "PartitionQueryRequest",
  19346. responseType: "PartitionQueryResponse",
  19347. options: {
  19348. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:partitionQuery",
  19349. "(google.api.http).body": "*",
  19350. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery",
  19351. "(google.api.http).additional_bindings.body": "*"
  19352. },
  19353. parsedOptions: [
  19354. {
  19355. "(google.api.http)": {
  19356. post: "/v1/{parent=projects/*/databases/*/documents}:partitionQuery",
  19357. body: "*",
  19358. additional_bindings: {
  19359. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery",
  19360. body: "*"
  19361. }
  19362. }
  19363. }
  19364. ]
  19365. },
  19366. Write: {
  19367. requestType: "WriteRequest",
  19368. requestStream: true,
  19369. responseType: "WriteResponse",
  19370. responseStream: true,
  19371. options: {
  19372. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:write",
  19373. "(google.api.http).body": "*"
  19374. },
  19375. parsedOptions: [
  19376. {
  19377. "(google.api.http)": {
  19378. post: "/v1/{database=projects/*/databases/*}/documents:write",
  19379. body: "*"
  19380. }
  19381. }
  19382. ]
  19383. },
  19384. Listen: {
  19385. requestType: "ListenRequest",
  19386. requestStream: true,
  19387. responseType: "ListenResponse",
  19388. responseStream: true,
  19389. options: {
  19390. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:listen",
  19391. "(google.api.http).body": "*"
  19392. },
  19393. parsedOptions: [
  19394. {
  19395. "(google.api.http)": {
  19396. post: "/v1/{database=projects/*/databases/*}/documents:listen",
  19397. body: "*"
  19398. }
  19399. }
  19400. ]
  19401. },
  19402. ListCollectionIds: {
  19403. requestType: "ListCollectionIdsRequest",
  19404. responseType: "ListCollectionIdsResponse",
  19405. options: {
  19406. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:listCollectionIds",
  19407. "(google.api.http).body": "*",
  19408. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:listCollectionIds",
  19409. "(google.api.http).additional_bindings.body": "*",
  19410. "(google.api.method_signature)": "parent"
  19411. },
  19412. parsedOptions: [
  19413. {
  19414. "(google.api.http)": {
  19415. post: "/v1/{parent=projects/*/databases/*/documents}:listCollectionIds",
  19416. body: "*",
  19417. additional_bindings: {
  19418. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:listCollectionIds",
  19419. body: "*"
  19420. }
  19421. }
  19422. },
  19423. {
  19424. "(google.api.method_signature)": "parent"
  19425. }
  19426. ]
  19427. },
  19428. BatchWrite: {
  19429. requestType: "BatchWriteRequest",
  19430. responseType: "BatchWriteResponse",
  19431. options: {
  19432. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:batchWrite",
  19433. "(google.api.http).body": "*"
  19434. },
  19435. parsedOptions: [
  19436. {
  19437. "(google.api.http)": {
  19438. post: "/v1/{database=projects/*/databases/*}/documents:batchWrite",
  19439. body: "*"
  19440. }
  19441. }
  19442. ]
  19443. },
  19444. CreateDocument: {
  19445. requestType: "CreateDocumentRequest",
  19446. responseType: "Document",
  19447. options: {
  19448. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}",
  19449. "(google.api.http).body": "document"
  19450. },
  19451. parsedOptions: [
  19452. {
  19453. "(google.api.http)": {
  19454. post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}",
  19455. body: "document"
  19456. }
  19457. }
  19458. ]
  19459. }
  19460. }
  19461. },
  19462. GetDocumentRequest: {
  19463. oneofs: {
  19464. consistencySelector: {
  19465. oneof: [
  19466. "transaction",
  19467. "readTime"
  19468. ]
  19469. }
  19470. },
  19471. fields: {
  19472. name: {
  19473. type: "string",
  19474. id: 1,
  19475. options: {
  19476. "(google.api.field_behavior)": "REQUIRED"
  19477. }
  19478. },
  19479. mask: {
  19480. type: "DocumentMask",
  19481. id: 2
  19482. },
  19483. transaction: {
  19484. type: "bytes",
  19485. id: 3
  19486. },
  19487. readTime: {
  19488. type: "google.protobuf.Timestamp",
  19489. id: 5
  19490. }
  19491. }
  19492. },
  19493. ListDocumentsRequest: {
  19494. oneofs: {
  19495. consistencySelector: {
  19496. oneof: [
  19497. "transaction",
  19498. "readTime"
  19499. ]
  19500. }
  19501. },
  19502. fields: {
  19503. parent: {
  19504. type: "string",
  19505. id: 1,
  19506. options: {
  19507. "(google.api.field_behavior)": "REQUIRED"
  19508. }
  19509. },
  19510. collectionId: {
  19511. type: "string",
  19512. id: 2,
  19513. options: {
  19514. "(google.api.field_behavior)": "REQUIRED"
  19515. }
  19516. },
  19517. pageSize: {
  19518. type: "int32",
  19519. id: 3
  19520. },
  19521. pageToken: {
  19522. type: "string",
  19523. id: 4
  19524. },
  19525. orderBy: {
  19526. type: "string",
  19527. id: 6
  19528. },
  19529. mask: {
  19530. type: "DocumentMask",
  19531. id: 7
  19532. },
  19533. transaction: {
  19534. type: "bytes",
  19535. id: 8
  19536. },
  19537. readTime: {
  19538. type: "google.protobuf.Timestamp",
  19539. id: 10
  19540. },
  19541. showMissing: {
  19542. type: "bool",
  19543. id: 12
  19544. }
  19545. }
  19546. },
  19547. ListDocumentsResponse: {
  19548. fields: {
  19549. documents: {
  19550. rule: "repeated",
  19551. type: "Document",
  19552. id: 1
  19553. },
  19554. nextPageToken: {
  19555. type: "string",
  19556. id: 2
  19557. }
  19558. }
  19559. },
  19560. CreateDocumentRequest: {
  19561. fields: {
  19562. parent: {
  19563. type: "string",
  19564. id: 1,
  19565. options: {
  19566. "(google.api.field_behavior)": "REQUIRED"
  19567. }
  19568. },
  19569. collectionId: {
  19570. type: "string",
  19571. id: 2,
  19572. options: {
  19573. "(google.api.field_behavior)": "REQUIRED"
  19574. }
  19575. },
  19576. documentId: {
  19577. type: "string",
  19578. id: 3
  19579. },
  19580. document: {
  19581. type: "Document",
  19582. id: 4,
  19583. options: {
  19584. "(google.api.field_behavior)": "REQUIRED"
  19585. }
  19586. },
  19587. mask: {
  19588. type: "DocumentMask",
  19589. id: 5
  19590. }
  19591. }
  19592. },
  19593. UpdateDocumentRequest: {
  19594. fields: {
  19595. document: {
  19596. type: "Document",
  19597. id: 1,
  19598. options: {
  19599. "(google.api.field_behavior)": "REQUIRED"
  19600. }
  19601. },
  19602. updateMask: {
  19603. type: "DocumentMask",
  19604. id: 2
  19605. },
  19606. mask: {
  19607. type: "DocumentMask",
  19608. id: 3
  19609. },
  19610. currentDocument: {
  19611. type: "Precondition",
  19612. id: 4
  19613. }
  19614. }
  19615. },
  19616. DeleteDocumentRequest: {
  19617. fields: {
  19618. name: {
  19619. type: "string",
  19620. id: 1,
  19621. options: {
  19622. "(google.api.field_behavior)": "REQUIRED"
  19623. }
  19624. },
  19625. currentDocument: {
  19626. type: "Precondition",
  19627. id: 2
  19628. }
  19629. }
  19630. },
  19631. BatchGetDocumentsRequest: {
  19632. oneofs: {
  19633. consistencySelector: {
  19634. oneof: [
  19635. "transaction",
  19636. "newTransaction",
  19637. "readTime"
  19638. ]
  19639. }
  19640. },
  19641. fields: {
  19642. database: {
  19643. type: "string",
  19644. id: 1,
  19645. options: {
  19646. "(google.api.field_behavior)": "REQUIRED"
  19647. }
  19648. },
  19649. documents: {
  19650. rule: "repeated",
  19651. type: "string",
  19652. id: 2
  19653. },
  19654. mask: {
  19655. type: "DocumentMask",
  19656. id: 3
  19657. },
  19658. transaction: {
  19659. type: "bytes",
  19660. id: 4
  19661. },
  19662. newTransaction: {
  19663. type: "TransactionOptions",
  19664. id: 5
  19665. },
  19666. readTime: {
  19667. type: "google.protobuf.Timestamp",
  19668. id: 7
  19669. }
  19670. }
  19671. },
  19672. BatchGetDocumentsResponse: {
  19673. oneofs: {
  19674. result: {
  19675. oneof: [
  19676. "found",
  19677. "missing"
  19678. ]
  19679. }
  19680. },
  19681. fields: {
  19682. found: {
  19683. type: "Document",
  19684. id: 1
  19685. },
  19686. missing: {
  19687. type: "string",
  19688. id: 2
  19689. },
  19690. transaction: {
  19691. type: "bytes",
  19692. id: 3
  19693. },
  19694. readTime: {
  19695. type: "google.protobuf.Timestamp",
  19696. id: 4
  19697. }
  19698. }
  19699. },
  19700. BeginTransactionRequest: {
  19701. fields: {
  19702. database: {
  19703. type: "string",
  19704. id: 1,
  19705. options: {
  19706. "(google.api.field_behavior)": "REQUIRED"
  19707. }
  19708. },
  19709. options: {
  19710. type: "TransactionOptions",
  19711. id: 2
  19712. }
  19713. }
  19714. },
  19715. BeginTransactionResponse: {
  19716. fields: {
  19717. transaction: {
  19718. type: "bytes",
  19719. id: 1
  19720. }
  19721. }
  19722. },
  19723. CommitRequest: {
  19724. fields: {
  19725. database: {
  19726. type: "string",
  19727. id: 1,
  19728. options: {
  19729. "(google.api.field_behavior)": "REQUIRED"
  19730. }
  19731. },
  19732. writes: {
  19733. rule: "repeated",
  19734. type: "Write",
  19735. id: 2
  19736. },
  19737. transaction: {
  19738. type: "bytes",
  19739. id: 3
  19740. }
  19741. }
  19742. },
  19743. CommitResponse: {
  19744. fields: {
  19745. writeResults: {
  19746. rule: "repeated",
  19747. type: "WriteResult",
  19748. id: 1
  19749. },
  19750. commitTime: {
  19751. type: "google.protobuf.Timestamp",
  19752. id: 2
  19753. }
  19754. }
  19755. },
  19756. RollbackRequest: {
  19757. fields: {
  19758. database: {
  19759. type: "string",
  19760. id: 1,
  19761. options: {
  19762. "(google.api.field_behavior)": "REQUIRED"
  19763. }
  19764. },
  19765. transaction: {
  19766. type: "bytes",
  19767. id: 2,
  19768. options: {
  19769. "(google.api.field_behavior)": "REQUIRED"
  19770. }
  19771. }
  19772. }
  19773. },
  19774. RunQueryRequest: {
  19775. oneofs: {
  19776. queryType: {
  19777. oneof: [
  19778. "structuredQuery"
  19779. ]
  19780. },
  19781. consistencySelector: {
  19782. oneof: [
  19783. "transaction",
  19784. "newTransaction",
  19785. "readTime"
  19786. ]
  19787. }
  19788. },
  19789. fields: {
  19790. parent: {
  19791. type: "string",
  19792. id: 1,
  19793. options: {
  19794. "(google.api.field_behavior)": "REQUIRED"
  19795. }
  19796. },
  19797. structuredQuery: {
  19798. type: "StructuredQuery",
  19799. id: 2
  19800. },
  19801. transaction: {
  19802. type: "bytes",
  19803. id: 5
  19804. },
  19805. newTransaction: {
  19806. type: "TransactionOptions",
  19807. id: 6
  19808. },
  19809. readTime: {
  19810. type: "google.protobuf.Timestamp",
  19811. id: 7
  19812. }
  19813. }
  19814. },
  19815. RunQueryResponse: {
  19816. fields: {
  19817. transaction: {
  19818. type: "bytes",
  19819. id: 2
  19820. },
  19821. document: {
  19822. type: "Document",
  19823. id: 1
  19824. },
  19825. readTime: {
  19826. type: "google.protobuf.Timestamp",
  19827. id: 3
  19828. },
  19829. skippedResults: {
  19830. type: "int32",
  19831. id: 4
  19832. }
  19833. }
  19834. },
  19835. RunAggregationQueryRequest: {
  19836. oneofs: {
  19837. queryType: {
  19838. oneof: [
  19839. "structuredAggregationQuery"
  19840. ]
  19841. },
  19842. consistencySelector: {
  19843. oneof: [
  19844. "transaction",
  19845. "newTransaction",
  19846. "readTime"
  19847. ]
  19848. }
  19849. },
  19850. fields: {
  19851. parent: {
  19852. type: "string",
  19853. id: 1,
  19854. options: {
  19855. "(google.api.field_behavior)": "REQUIRED"
  19856. }
  19857. },
  19858. structuredAggregationQuery: {
  19859. type: "StructuredAggregationQuery",
  19860. id: 2
  19861. },
  19862. transaction: {
  19863. type: "bytes",
  19864. id: 4
  19865. },
  19866. newTransaction: {
  19867. type: "TransactionOptions",
  19868. id: 5
  19869. },
  19870. readTime: {
  19871. type: "google.protobuf.Timestamp",
  19872. id: 6
  19873. }
  19874. }
  19875. },
  19876. RunAggregationQueryResponse: {
  19877. fields: {
  19878. result: {
  19879. type: "AggregationResult",
  19880. id: 1
  19881. },
  19882. transaction: {
  19883. type: "bytes",
  19884. id: 2
  19885. },
  19886. readTime: {
  19887. type: "google.protobuf.Timestamp",
  19888. id: 3
  19889. }
  19890. }
  19891. },
  19892. PartitionQueryRequest: {
  19893. oneofs: {
  19894. queryType: {
  19895. oneof: [
  19896. "structuredQuery"
  19897. ]
  19898. }
  19899. },
  19900. fields: {
  19901. parent: {
  19902. type: "string",
  19903. id: 1,
  19904. options: {
  19905. "(google.api.field_behavior)": "REQUIRED"
  19906. }
  19907. },
  19908. structuredQuery: {
  19909. type: "StructuredQuery",
  19910. id: 2
  19911. },
  19912. partitionCount: {
  19913. type: "int64",
  19914. id: 3
  19915. },
  19916. pageToken: {
  19917. type: "string",
  19918. id: 4
  19919. },
  19920. pageSize: {
  19921. type: "int32",
  19922. id: 5
  19923. }
  19924. }
  19925. },
  19926. PartitionQueryResponse: {
  19927. fields: {
  19928. partitions: {
  19929. rule: "repeated",
  19930. type: "Cursor",
  19931. id: 1
  19932. },
  19933. nextPageToken: {
  19934. type: "string",
  19935. id: 2
  19936. }
  19937. }
  19938. },
  19939. WriteRequest: {
  19940. fields: {
  19941. database: {
  19942. type: "string",
  19943. id: 1,
  19944. options: {
  19945. "(google.api.field_behavior)": "REQUIRED"
  19946. }
  19947. },
  19948. streamId: {
  19949. type: "string",
  19950. id: 2
  19951. },
  19952. writes: {
  19953. rule: "repeated",
  19954. type: "Write",
  19955. id: 3
  19956. },
  19957. streamToken: {
  19958. type: "bytes",
  19959. id: 4
  19960. },
  19961. labels: {
  19962. keyType: "string",
  19963. type: "string",
  19964. id: 5
  19965. }
  19966. }
  19967. },
  19968. WriteResponse: {
  19969. fields: {
  19970. streamId: {
  19971. type: "string",
  19972. id: 1
  19973. },
  19974. streamToken: {
  19975. type: "bytes",
  19976. id: 2
  19977. },
  19978. writeResults: {
  19979. rule: "repeated",
  19980. type: "WriteResult",
  19981. id: 3
  19982. },
  19983. commitTime: {
  19984. type: "google.protobuf.Timestamp",
  19985. id: 4
  19986. }
  19987. }
  19988. },
  19989. ListenRequest: {
  19990. oneofs: {
  19991. targetChange: {
  19992. oneof: [
  19993. "addTarget",
  19994. "removeTarget"
  19995. ]
  19996. }
  19997. },
  19998. fields: {
  19999. database: {
  20000. type: "string",
  20001. id: 1,
  20002. options: {
  20003. "(google.api.field_behavior)": "REQUIRED"
  20004. }
  20005. },
  20006. addTarget: {
  20007. type: "Target",
  20008. id: 2
  20009. },
  20010. removeTarget: {
  20011. type: "int32",
  20012. id: 3
  20013. },
  20014. labels: {
  20015. keyType: "string",
  20016. type: "string",
  20017. id: 4
  20018. }
  20019. }
  20020. },
  20021. ListenResponse: {
  20022. oneofs: {
  20023. responseType: {
  20024. oneof: [
  20025. "targetChange",
  20026. "documentChange",
  20027. "documentDelete",
  20028. "documentRemove",
  20029. "filter"
  20030. ]
  20031. }
  20032. },
  20033. fields: {
  20034. targetChange: {
  20035. type: "TargetChange",
  20036. id: 2
  20037. },
  20038. documentChange: {
  20039. type: "DocumentChange",
  20040. id: 3
  20041. },
  20042. documentDelete: {
  20043. type: "DocumentDelete",
  20044. id: 4
  20045. },
  20046. documentRemove: {
  20047. type: "DocumentRemove",
  20048. id: 6
  20049. },
  20050. filter: {
  20051. type: "ExistenceFilter",
  20052. id: 5
  20053. }
  20054. }
  20055. },
  20056. Target: {
  20057. oneofs: {
  20058. targetType: {
  20059. oneof: [
  20060. "query",
  20061. "documents"
  20062. ]
  20063. },
  20064. resumeType: {
  20065. oneof: [
  20066. "resumeToken",
  20067. "readTime"
  20068. ]
  20069. }
  20070. },
  20071. fields: {
  20072. query: {
  20073. type: "QueryTarget",
  20074. id: 2
  20075. },
  20076. documents: {
  20077. type: "DocumentsTarget",
  20078. id: 3
  20079. },
  20080. resumeToken: {
  20081. type: "bytes",
  20082. id: 4
  20083. },
  20084. readTime: {
  20085. type: "google.protobuf.Timestamp",
  20086. id: 11
  20087. },
  20088. targetId: {
  20089. type: "int32",
  20090. id: 5
  20091. },
  20092. once: {
  20093. type: "bool",
  20094. id: 6
  20095. }
  20096. },
  20097. nested: {
  20098. DocumentsTarget: {
  20099. fields: {
  20100. documents: {
  20101. rule: "repeated",
  20102. type: "string",
  20103. id: 2
  20104. }
  20105. }
  20106. },
  20107. QueryTarget: {
  20108. oneofs: {
  20109. queryType: {
  20110. oneof: [
  20111. "structuredQuery"
  20112. ]
  20113. }
  20114. },
  20115. fields: {
  20116. parent: {
  20117. type: "string",
  20118. id: 1
  20119. },
  20120. structuredQuery: {
  20121. type: "StructuredQuery",
  20122. id: 2
  20123. }
  20124. }
  20125. }
  20126. }
  20127. },
  20128. TargetChange: {
  20129. fields: {
  20130. targetChangeType: {
  20131. type: "TargetChangeType",
  20132. id: 1
  20133. },
  20134. targetIds: {
  20135. rule: "repeated",
  20136. type: "int32",
  20137. id: 2
  20138. },
  20139. cause: {
  20140. type: "google.rpc.Status",
  20141. id: 3
  20142. },
  20143. resumeToken: {
  20144. type: "bytes",
  20145. id: 4
  20146. },
  20147. readTime: {
  20148. type: "google.protobuf.Timestamp",
  20149. id: 6
  20150. }
  20151. },
  20152. nested: {
  20153. TargetChangeType: {
  20154. values: {
  20155. NO_CHANGE: 0,
  20156. ADD: 1,
  20157. REMOVE: 2,
  20158. CURRENT: 3,
  20159. RESET: 4
  20160. }
  20161. }
  20162. }
  20163. },
  20164. ListCollectionIdsRequest: {
  20165. fields: {
  20166. parent: {
  20167. type: "string",
  20168. id: 1,
  20169. options: {
  20170. "(google.api.field_behavior)": "REQUIRED"
  20171. }
  20172. },
  20173. pageSize: {
  20174. type: "int32",
  20175. id: 2
  20176. },
  20177. pageToken: {
  20178. type: "string",
  20179. id: 3
  20180. }
  20181. }
  20182. },
  20183. ListCollectionIdsResponse: {
  20184. fields: {
  20185. collectionIds: {
  20186. rule: "repeated",
  20187. type: "string",
  20188. id: 1
  20189. },
  20190. nextPageToken: {
  20191. type: "string",
  20192. id: 2
  20193. }
  20194. }
  20195. },
  20196. BatchWriteRequest: {
  20197. fields: {
  20198. database: {
  20199. type: "string",
  20200. id: 1,
  20201. options: {
  20202. "(google.api.field_behavior)": "REQUIRED"
  20203. }
  20204. },
  20205. writes: {
  20206. rule: "repeated",
  20207. type: "Write",
  20208. id: 2
  20209. },
  20210. labels: {
  20211. keyType: "string",
  20212. type: "string",
  20213. id: 3
  20214. }
  20215. }
  20216. },
  20217. BatchWriteResponse: {
  20218. fields: {
  20219. writeResults: {
  20220. rule: "repeated",
  20221. type: "WriteResult",
  20222. id: 1
  20223. },
  20224. status: {
  20225. rule: "repeated",
  20226. type: "google.rpc.Status",
  20227. id: 2
  20228. }
  20229. }
  20230. },
  20231. StructuredQuery: {
  20232. fields: {
  20233. select: {
  20234. type: "Projection",
  20235. id: 1
  20236. },
  20237. from: {
  20238. rule: "repeated",
  20239. type: "CollectionSelector",
  20240. id: 2
  20241. },
  20242. where: {
  20243. type: "Filter",
  20244. id: 3
  20245. },
  20246. orderBy: {
  20247. rule: "repeated",
  20248. type: "Order",
  20249. id: 4
  20250. },
  20251. startAt: {
  20252. type: "Cursor",
  20253. id: 7
  20254. },
  20255. endAt: {
  20256. type: "Cursor",
  20257. id: 8
  20258. },
  20259. offset: {
  20260. type: "int32",
  20261. id: 6
  20262. },
  20263. limit: {
  20264. type: "google.protobuf.Int32Value",
  20265. id: 5
  20266. }
  20267. },
  20268. nested: {
  20269. CollectionSelector: {
  20270. fields: {
  20271. collectionId: {
  20272. type: "string",
  20273. id: 2
  20274. },
  20275. allDescendants: {
  20276. type: "bool",
  20277. id: 3
  20278. }
  20279. }
  20280. },
  20281. Filter: {
  20282. oneofs: {
  20283. filterType: {
  20284. oneof: [
  20285. "compositeFilter",
  20286. "fieldFilter",
  20287. "unaryFilter"
  20288. ]
  20289. }
  20290. },
  20291. fields: {
  20292. compositeFilter: {
  20293. type: "CompositeFilter",
  20294. id: 1
  20295. },
  20296. fieldFilter: {
  20297. type: "FieldFilter",
  20298. id: 2
  20299. },
  20300. unaryFilter: {
  20301. type: "UnaryFilter",
  20302. id: 3
  20303. }
  20304. }
  20305. },
  20306. CompositeFilter: {
  20307. fields: {
  20308. op: {
  20309. type: "Operator",
  20310. id: 1
  20311. },
  20312. filters: {
  20313. rule: "repeated",
  20314. type: "Filter",
  20315. id: 2
  20316. }
  20317. },
  20318. nested: {
  20319. Operator: {
  20320. values: {
  20321. OPERATOR_UNSPECIFIED: 0,
  20322. AND: 1,
  20323. OR: 2
  20324. }
  20325. }
  20326. }
  20327. },
  20328. FieldFilter: {
  20329. fields: {
  20330. field: {
  20331. type: "FieldReference",
  20332. id: 1
  20333. },
  20334. op: {
  20335. type: "Operator",
  20336. id: 2
  20337. },
  20338. value: {
  20339. type: "Value",
  20340. id: 3
  20341. }
  20342. },
  20343. nested: {
  20344. Operator: {
  20345. values: {
  20346. OPERATOR_UNSPECIFIED: 0,
  20347. LESS_THAN: 1,
  20348. LESS_THAN_OR_EQUAL: 2,
  20349. GREATER_THAN: 3,
  20350. GREATER_THAN_OR_EQUAL: 4,
  20351. EQUAL: 5,
  20352. NOT_EQUAL: 6,
  20353. ARRAY_CONTAINS: 7,
  20354. IN: 8,
  20355. ARRAY_CONTAINS_ANY: 9,
  20356. NOT_IN: 10
  20357. }
  20358. }
  20359. }
  20360. },
  20361. UnaryFilter: {
  20362. oneofs: {
  20363. operandType: {
  20364. oneof: [
  20365. "field"
  20366. ]
  20367. }
  20368. },
  20369. fields: {
  20370. op: {
  20371. type: "Operator",
  20372. id: 1
  20373. },
  20374. field: {
  20375. type: "FieldReference",
  20376. id: 2
  20377. }
  20378. },
  20379. nested: {
  20380. Operator: {
  20381. values: {
  20382. OPERATOR_UNSPECIFIED: 0,
  20383. IS_NAN: 2,
  20384. IS_NULL: 3,
  20385. IS_NOT_NAN: 4,
  20386. IS_NOT_NULL: 5
  20387. }
  20388. }
  20389. }
  20390. },
  20391. Order: {
  20392. fields: {
  20393. field: {
  20394. type: "FieldReference",
  20395. id: 1
  20396. },
  20397. direction: {
  20398. type: "Direction",
  20399. id: 2
  20400. }
  20401. }
  20402. },
  20403. FieldReference: {
  20404. fields: {
  20405. fieldPath: {
  20406. type: "string",
  20407. id: 2
  20408. }
  20409. }
  20410. },
  20411. Projection: {
  20412. fields: {
  20413. fields: {
  20414. rule: "repeated",
  20415. type: "FieldReference",
  20416. id: 2
  20417. }
  20418. }
  20419. },
  20420. Direction: {
  20421. values: {
  20422. DIRECTION_UNSPECIFIED: 0,
  20423. ASCENDING: 1,
  20424. DESCENDING: 2
  20425. }
  20426. }
  20427. }
  20428. },
  20429. StructuredAggregationQuery: {
  20430. oneofs: {
  20431. queryType: {
  20432. oneof: [
  20433. "structuredQuery"
  20434. ]
  20435. }
  20436. },
  20437. fields: {
  20438. structuredQuery: {
  20439. type: "StructuredQuery",
  20440. id: 1
  20441. },
  20442. aggregations: {
  20443. rule: "repeated",
  20444. type: "Aggregation",
  20445. id: 3
  20446. }
  20447. },
  20448. nested: {
  20449. Aggregation: {
  20450. oneofs: {
  20451. operator: {
  20452. oneof: [
  20453. "count"
  20454. ]
  20455. }
  20456. },
  20457. fields: {
  20458. count: {
  20459. type: "Count",
  20460. id: 1
  20461. },
  20462. alias: {
  20463. type: "string",
  20464. id: 7
  20465. }
  20466. },
  20467. nested: {
  20468. Count: {
  20469. fields: {
  20470. upTo: {
  20471. type: "google.protobuf.Int64Value",
  20472. id: 1
  20473. }
  20474. }
  20475. }
  20476. }
  20477. }
  20478. }
  20479. },
  20480. Cursor: {
  20481. fields: {
  20482. values: {
  20483. rule: "repeated",
  20484. type: "Value",
  20485. id: 1
  20486. },
  20487. before: {
  20488. type: "bool",
  20489. id: 2
  20490. }
  20491. }
  20492. },
  20493. Write: {
  20494. oneofs: {
  20495. operation: {
  20496. oneof: [
  20497. "update",
  20498. "delete",
  20499. "verify",
  20500. "transform"
  20501. ]
  20502. }
  20503. },
  20504. fields: {
  20505. update: {
  20506. type: "Document",
  20507. id: 1
  20508. },
  20509. "delete": {
  20510. type: "string",
  20511. id: 2
  20512. },
  20513. verify: {
  20514. type: "string",
  20515. id: 5
  20516. },
  20517. transform: {
  20518. type: "DocumentTransform",
  20519. id: 6
  20520. },
  20521. updateMask: {
  20522. type: "DocumentMask",
  20523. id: 3
  20524. },
  20525. updateTransforms: {
  20526. rule: "repeated",
  20527. type: "DocumentTransform.FieldTransform",
  20528. id: 7
  20529. },
  20530. currentDocument: {
  20531. type: "Precondition",
  20532. id: 4
  20533. }
  20534. }
  20535. },
  20536. DocumentTransform: {
  20537. fields: {
  20538. document: {
  20539. type: "string",
  20540. id: 1
  20541. },
  20542. fieldTransforms: {
  20543. rule: "repeated",
  20544. type: "FieldTransform",
  20545. id: 2
  20546. }
  20547. },
  20548. nested: {
  20549. FieldTransform: {
  20550. oneofs: {
  20551. transformType: {
  20552. oneof: [
  20553. "setToServerValue",
  20554. "increment",
  20555. "maximum",
  20556. "minimum",
  20557. "appendMissingElements",
  20558. "removeAllFromArray"
  20559. ]
  20560. }
  20561. },
  20562. fields: {
  20563. fieldPath: {
  20564. type: "string",
  20565. id: 1
  20566. },
  20567. setToServerValue: {
  20568. type: "ServerValue",
  20569. id: 2
  20570. },
  20571. increment: {
  20572. type: "Value",
  20573. id: 3
  20574. },
  20575. maximum: {
  20576. type: "Value",
  20577. id: 4
  20578. },
  20579. minimum: {
  20580. type: "Value",
  20581. id: 5
  20582. },
  20583. appendMissingElements: {
  20584. type: "ArrayValue",
  20585. id: 6
  20586. },
  20587. removeAllFromArray: {
  20588. type: "ArrayValue",
  20589. id: 7
  20590. }
  20591. },
  20592. nested: {
  20593. ServerValue: {
  20594. values: {
  20595. SERVER_VALUE_UNSPECIFIED: 0,
  20596. REQUEST_TIME: 1
  20597. }
  20598. }
  20599. }
  20600. }
  20601. }
  20602. },
  20603. WriteResult: {
  20604. fields: {
  20605. updateTime: {
  20606. type: "google.protobuf.Timestamp",
  20607. id: 1
  20608. },
  20609. transformResults: {
  20610. rule: "repeated",
  20611. type: "Value",
  20612. id: 2
  20613. }
  20614. }
  20615. },
  20616. DocumentChange: {
  20617. fields: {
  20618. document: {
  20619. type: "Document",
  20620. id: 1
  20621. },
  20622. targetIds: {
  20623. rule: "repeated",
  20624. type: "int32",
  20625. id: 5
  20626. },
  20627. removedTargetIds: {
  20628. rule: "repeated",
  20629. type: "int32",
  20630. id: 6
  20631. }
  20632. }
  20633. },
  20634. DocumentDelete: {
  20635. fields: {
  20636. document: {
  20637. type: "string",
  20638. id: 1
  20639. },
  20640. removedTargetIds: {
  20641. rule: "repeated",
  20642. type: "int32",
  20643. id: 6
  20644. },
  20645. readTime: {
  20646. type: "google.protobuf.Timestamp",
  20647. id: 4
  20648. }
  20649. }
  20650. },
  20651. DocumentRemove: {
  20652. fields: {
  20653. document: {
  20654. type: "string",
  20655. id: 1
  20656. },
  20657. removedTargetIds: {
  20658. rule: "repeated",
  20659. type: "int32",
  20660. id: 2
  20661. },
  20662. readTime: {
  20663. type: "google.protobuf.Timestamp",
  20664. id: 4
  20665. }
  20666. }
  20667. },
  20668. ExistenceFilter: {
  20669. fields: {
  20670. targetId: {
  20671. type: "int32",
  20672. id: 1
  20673. },
  20674. count: {
  20675. type: "int32",
  20676. id: 2
  20677. }
  20678. }
  20679. }
  20680. }
  20681. }
  20682. }
  20683. },
  20684. api: {
  20685. options: {
  20686. go_package: "google.golang.org/genproto/googleapis/api/annotations;annotations",
  20687. java_multiple_files: true,
  20688. java_outer_classname: "HttpProto",
  20689. java_package: "com.google.api",
  20690. objc_class_prefix: "GAPI",
  20691. cc_enable_arenas: true
  20692. },
  20693. nested: {
  20694. http: {
  20695. type: "HttpRule",
  20696. id: 72295728,
  20697. extend: "google.protobuf.MethodOptions"
  20698. },
  20699. Http: {
  20700. fields: {
  20701. rules: {
  20702. rule: "repeated",
  20703. type: "HttpRule",
  20704. id: 1
  20705. }
  20706. }
  20707. },
  20708. HttpRule: {
  20709. oneofs: {
  20710. pattern: {
  20711. oneof: [
  20712. "get",
  20713. "put",
  20714. "post",
  20715. "delete",
  20716. "patch",
  20717. "custom"
  20718. ]
  20719. }
  20720. },
  20721. fields: {
  20722. get: {
  20723. type: "string",
  20724. id: 2
  20725. },
  20726. put: {
  20727. type: "string",
  20728. id: 3
  20729. },
  20730. post: {
  20731. type: "string",
  20732. id: 4
  20733. },
  20734. "delete": {
  20735. type: "string",
  20736. id: 5
  20737. },
  20738. patch: {
  20739. type: "string",
  20740. id: 6
  20741. },
  20742. custom: {
  20743. type: "CustomHttpPattern",
  20744. id: 8
  20745. },
  20746. selector: {
  20747. type: "string",
  20748. id: 1
  20749. },
  20750. body: {
  20751. type: "string",
  20752. id: 7
  20753. },
  20754. additionalBindings: {
  20755. rule: "repeated",
  20756. type: "HttpRule",
  20757. id: 11
  20758. }
  20759. }
  20760. },
  20761. CustomHttpPattern: {
  20762. fields: {
  20763. kind: {
  20764. type: "string",
  20765. id: 1
  20766. },
  20767. path: {
  20768. type: "string",
  20769. id: 2
  20770. }
  20771. }
  20772. },
  20773. methodSignature: {
  20774. rule: "repeated",
  20775. type: "string",
  20776. id: 1051,
  20777. extend: "google.protobuf.MethodOptions"
  20778. },
  20779. defaultHost: {
  20780. type: "string",
  20781. id: 1049,
  20782. extend: "google.protobuf.ServiceOptions"
  20783. },
  20784. oauthScopes: {
  20785. type: "string",
  20786. id: 1050,
  20787. extend: "google.protobuf.ServiceOptions"
  20788. },
  20789. fieldBehavior: {
  20790. rule: "repeated",
  20791. type: "google.api.FieldBehavior",
  20792. id: 1052,
  20793. extend: "google.protobuf.FieldOptions"
  20794. },
  20795. FieldBehavior: {
  20796. values: {
  20797. FIELD_BEHAVIOR_UNSPECIFIED: 0,
  20798. OPTIONAL: 1,
  20799. REQUIRED: 2,
  20800. OUTPUT_ONLY: 3,
  20801. INPUT_ONLY: 4,
  20802. IMMUTABLE: 5,
  20803. UNORDERED_LIST: 6,
  20804. NON_EMPTY_DEFAULT: 7
  20805. }
  20806. }
  20807. }
  20808. },
  20809. type: {
  20810. options: {
  20811. cc_enable_arenas: true,
  20812. go_package: "google.golang.org/genproto/googleapis/type/latlng;latlng",
  20813. java_multiple_files: true,
  20814. java_outer_classname: "LatLngProto",
  20815. java_package: "com.google.type",
  20816. objc_class_prefix: "GTP"
  20817. },
  20818. nested: {
  20819. LatLng: {
  20820. fields: {
  20821. latitude: {
  20822. type: "double",
  20823. id: 1
  20824. },
  20825. longitude: {
  20826. type: "double",
  20827. id: 2
  20828. }
  20829. }
  20830. }
  20831. }
  20832. },
  20833. rpc: {
  20834. options: {
  20835. cc_enable_arenas: true,
  20836. go_package: "google.golang.org/genproto/googleapis/rpc/status;status",
  20837. java_multiple_files: true,
  20838. java_outer_classname: "StatusProto",
  20839. java_package: "com.google.rpc",
  20840. objc_class_prefix: "RPC"
  20841. },
  20842. nested: {
  20843. Status: {
  20844. fields: {
  20845. code: {
  20846. type: "int32",
  20847. id: 1
  20848. },
  20849. message: {
  20850. type: "string",
  20851. id: 2
  20852. },
  20853. details: {
  20854. rule: "repeated",
  20855. type: "google.protobuf.Any",
  20856. id: 3
  20857. }
  20858. }
  20859. }
  20860. }
  20861. }
  20862. }
  20863. }
  20864. };
  20865. var protos = {
  20866. nested: nested
  20867. };
  20868. var protos$1 = /*#__PURE__*/Object.freeze({
  20869. __proto__: null,
  20870. nested: nested,
  20871. 'default': protos
  20872. });
  20873. /**
  20874. * @license
  20875. * Copyright 2020 Google LLC
  20876. *
  20877. * Licensed under the Apache License, Version 2.0 (the "License");
  20878. * you may not use this file except in compliance with the License.
  20879. * You may obtain a copy of the License at
  20880. *
  20881. * http://www.apache.org/licenses/LICENSE-2.0
  20882. *
  20883. * Unless required by applicable law or agreed to in writing, software
  20884. * distributed under the License is distributed on an "AS IS" BASIS,
  20885. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20886. * See the License for the specific language governing permissions and
  20887. * limitations under the License.
  20888. */
  20889. /** Used by tests so we can match @grpc/proto-loader behavior. */
  20890. const protoLoaderOptions = {
  20891. longs: String,
  20892. enums: String,
  20893. defaults: true,
  20894. oneofs: false
  20895. };
  20896. /**
  20897. * Loads the protocol buffer definitions for Firestore.
  20898. *
  20899. * @returns The GrpcObject representing our protos.
  20900. */
  20901. function loadProtos() {
  20902. const packageDefinition = protoLoader__namespace.fromJSON(protos$1, protoLoaderOptions);
  20903. return grpc__namespace.loadPackageDefinition(packageDefinition);
  20904. }
  20905. /**
  20906. * @license
  20907. * Copyright 2020 Google LLC
  20908. *
  20909. * Licensed under the Apache License, Version 2.0 (the "License");
  20910. * you may not use this file except in compliance with the License.
  20911. * You may obtain a copy of the License at
  20912. *
  20913. * http://www.apache.org/licenses/LICENSE-2.0
  20914. *
  20915. * Unless required by applicable law or agreed to in writing, software
  20916. * distributed under the License is distributed on an "AS IS" BASIS,
  20917. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20918. * See the License for the specific language governing permissions and
  20919. * limitations under the License.
  20920. */
  20921. /** Loads the GRPC stack */
  20922. function newConnection(databaseInfo) {
  20923. const protos = loadProtos();
  20924. return new GrpcConnection(protos, databaseInfo);
  20925. }
  20926. /** Return the Platform-specific connectivity monitor. */
  20927. function newConnectivityMonitor() {
  20928. return new NoopConnectivityMonitor();
  20929. }
  20930. /**
  20931. * @license
  20932. * Copyright 2020 Google LLC
  20933. *
  20934. * Licensed under the Apache License, Version 2.0 (the "License");
  20935. * you may not use this file except in compliance with the License.
  20936. * You may obtain a copy of the License at
  20937. *
  20938. * http://www.apache.org/licenses/LICENSE-2.0
  20939. *
  20940. * Unless required by applicable law or agreed to in writing, software
  20941. * distributed under the License is distributed on an "AS IS" BASIS,
  20942. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20943. * See the License for the specific language governing permissions and
  20944. * limitations under the License.
  20945. */
  20946. /** The Platform's 'window' implementation or null if not available. */
  20947. function getWindow() {
  20948. if (process.env.USE_MOCK_PERSISTENCE === 'YES') {
  20949. // eslint-disable-next-line no-restricted-globals
  20950. return window;
  20951. }
  20952. return null;
  20953. }
  20954. /** The Platform's 'document' implementation or null if not available. */
  20955. function getDocument() {
  20956. return null;
  20957. }
  20958. /**
  20959. * @license
  20960. * Copyright 2020 Google LLC
  20961. *
  20962. * Licensed under the Apache License, Version 2.0 (the "License");
  20963. * you may not use this file except in compliance with the License.
  20964. * You may obtain a copy of the License at
  20965. *
  20966. * http://www.apache.org/licenses/LICENSE-2.0
  20967. *
  20968. * Unless required by applicable law or agreed to in writing, software
  20969. * distributed under the License is distributed on an "AS IS" BASIS,
  20970. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20971. * See the License for the specific language governing permissions and
  20972. * limitations under the License.
  20973. */
  20974. function newSerializer(databaseId) {
  20975. return new JsonProtoSerializer(databaseId, /* useProto3Json= */ false);
  20976. }
  20977. /**
  20978. * An instance of the Platform's 'TextEncoder' implementation.
  20979. */
  20980. function newTextEncoder() {
  20981. return new util$1.TextEncoder();
  20982. }
  20983. /**
  20984. * An instance of the Platform's 'TextDecoder' implementation.
  20985. */
  20986. function newTextDecoder() {
  20987. return new util$1.TextDecoder('utf-8');
  20988. }
  20989. /**
  20990. * @license
  20991. * Copyright 2017 Google LLC
  20992. *
  20993. * Licensed under the Apache License, Version 2.0 (the "License");
  20994. * you may not use this file except in compliance with the License.
  20995. * You may obtain a copy of the License at
  20996. *
  20997. * http://www.apache.org/licenses/LICENSE-2.0
  20998. *
  20999. * Unless required by applicable law or agreed to in writing, software
  21000. * distributed under the License is distributed on an "AS IS" BASIS,
  21001. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21002. * See the License for the specific language governing permissions and
  21003. * limitations under the License.
  21004. */
  21005. const LOG_TAG$8 = 'ExponentialBackoff';
  21006. /**
  21007. * Initial backoff time in milliseconds after an error.
  21008. * Set to 1s according to https://cloud.google.com/apis/design/errors.
  21009. */
  21010. const DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000;
  21011. const DEFAULT_BACKOFF_FACTOR = 1.5;
  21012. /** Maximum backoff time in milliseconds */
  21013. const DEFAULT_BACKOFF_MAX_DELAY_MS = 60 * 1000;
  21014. /**
  21015. * A helper for running delayed tasks following an exponential backoff curve
  21016. * between attempts.
  21017. *
  21018. * Each delay is made up of a "base" delay which follows the exponential
  21019. * backoff curve, and a +/- 50% "jitter" that is calculated and added to the
  21020. * base delay. This prevents clients from accidentally synchronizing their
  21021. * delays causing spikes of load to the backend.
  21022. */
  21023. class ExponentialBackoff {
  21024. constructor(
  21025. /**
  21026. * The AsyncQueue to run backoff operations on.
  21027. */
  21028. queue,
  21029. /**
  21030. * The ID to use when scheduling backoff operations on the AsyncQueue.
  21031. */
  21032. timerId,
  21033. /**
  21034. * The initial delay (used as the base delay on the first retry attempt).
  21035. * Note that jitter will still be applied, so the actual delay could be as
  21036. * little as 0.5*initialDelayMs.
  21037. */
  21038. initialDelayMs = DEFAULT_BACKOFF_INITIAL_DELAY_MS,
  21039. /**
  21040. * The multiplier to use to determine the extended base delay after each
  21041. * attempt.
  21042. */
  21043. backoffFactor = DEFAULT_BACKOFF_FACTOR,
  21044. /**
  21045. * The maximum base delay after which no further backoff is performed.
  21046. * Note that jitter will still be applied, so the actual delay could be as
  21047. * much as 1.5*maxDelayMs.
  21048. */
  21049. maxDelayMs = DEFAULT_BACKOFF_MAX_DELAY_MS) {
  21050. this.queue = queue;
  21051. this.timerId = timerId;
  21052. this.initialDelayMs = initialDelayMs;
  21053. this.backoffFactor = backoffFactor;
  21054. this.maxDelayMs = maxDelayMs;
  21055. this.currentBaseMs = 0;
  21056. this.timerPromise = null;
  21057. /** The last backoff attempt, as epoch milliseconds. */
  21058. this.lastAttemptTime = Date.now();
  21059. this.reset();
  21060. }
  21061. /**
  21062. * Resets the backoff delay.
  21063. *
  21064. * The very next backoffAndWait() will have no delay. If it is called again
  21065. * (i.e. due to an error), initialDelayMs (plus jitter) will be used, and
  21066. * subsequent ones will increase according to the backoffFactor.
  21067. */
  21068. reset() {
  21069. this.currentBaseMs = 0;
  21070. }
  21071. /**
  21072. * Resets the backoff delay to the maximum delay (e.g. for use after a
  21073. * RESOURCE_EXHAUSTED error).
  21074. */
  21075. resetToMax() {
  21076. this.currentBaseMs = this.maxDelayMs;
  21077. }
  21078. /**
  21079. * Returns a promise that resolves after currentDelayMs, and increases the
  21080. * delay for any subsequent attempts. If there was a pending backoff operation
  21081. * already, it will be canceled.
  21082. */
  21083. backoffAndRun(op) {
  21084. // Cancel any pending backoff operation.
  21085. this.cancel();
  21086. // First schedule using the current base (which may be 0 and should be
  21087. // honored as such).
  21088. const desiredDelayWithJitterMs = Math.floor(this.currentBaseMs + this.jitterDelayMs());
  21089. // Guard against lastAttemptTime being in the future due to a clock change.
  21090. const delaySoFarMs = Math.max(0, Date.now() - this.lastAttemptTime);
  21091. // Guard against the backoff delay already being past.
  21092. const remainingDelayMs = Math.max(0, desiredDelayWithJitterMs - delaySoFarMs);
  21093. if (remainingDelayMs > 0) {
  21094. logDebug(LOG_TAG$8, `Backing off for ${remainingDelayMs} ms ` +
  21095. `(base delay: ${this.currentBaseMs} ms, ` +
  21096. `delay with jitter: ${desiredDelayWithJitterMs} ms, ` +
  21097. `last attempt: ${delaySoFarMs} ms ago)`);
  21098. }
  21099. this.timerPromise = this.queue.enqueueAfterDelay(this.timerId, remainingDelayMs, () => {
  21100. this.lastAttemptTime = Date.now();
  21101. return op();
  21102. });
  21103. // Apply backoff factor to determine next delay and ensure it is within
  21104. // bounds.
  21105. this.currentBaseMs *= this.backoffFactor;
  21106. if (this.currentBaseMs < this.initialDelayMs) {
  21107. this.currentBaseMs = this.initialDelayMs;
  21108. }
  21109. if (this.currentBaseMs > this.maxDelayMs) {
  21110. this.currentBaseMs = this.maxDelayMs;
  21111. }
  21112. }
  21113. skipBackoff() {
  21114. if (this.timerPromise !== null) {
  21115. this.timerPromise.skipDelay();
  21116. this.timerPromise = null;
  21117. }
  21118. }
  21119. cancel() {
  21120. if (this.timerPromise !== null) {
  21121. this.timerPromise.cancel();
  21122. this.timerPromise = null;
  21123. }
  21124. }
  21125. /** Returns a random value in the range [-currentBaseMs/2, currentBaseMs/2] */
  21126. jitterDelayMs() {
  21127. return (Math.random() - 0.5) * this.currentBaseMs;
  21128. }
  21129. }
  21130. /**
  21131. * @license
  21132. * Copyright 2017 Google LLC
  21133. *
  21134. * Licensed under the Apache License, Version 2.0 (the "License");
  21135. * you may not use this file except in compliance with the License.
  21136. * You may obtain a copy of the License at
  21137. *
  21138. * http://www.apache.org/licenses/LICENSE-2.0
  21139. *
  21140. * Unless required by applicable law or agreed to in writing, software
  21141. * distributed under the License is distributed on an "AS IS" BASIS,
  21142. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21143. * See the License for the specific language governing permissions and
  21144. * limitations under the License.
  21145. */
  21146. const LOG_TAG$7 = 'PersistentStream';
  21147. /** The time a stream stays open after it is marked idle. */
  21148. const IDLE_TIMEOUT_MS = 60 * 1000;
  21149. /** The time a stream stays open until we consider it healthy. */
  21150. const HEALTHY_TIMEOUT_MS = 10 * 1000;
  21151. /**
  21152. * A PersistentStream is an abstract base class that represents a streaming RPC
  21153. * to the Firestore backend. It's built on top of the connections own support
  21154. * for streaming RPCs, and adds several critical features for our clients:
  21155. *
  21156. * - Exponential backoff on failure
  21157. * - Authentication via CredentialsProvider
  21158. * - Dispatching all callbacks into the shared worker queue
  21159. * - Closing idle streams after 60 seconds of inactivity
  21160. *
  21161. * Subclasses of PersistentStream implement serialization of models to and
  21162. * from the JSON representation of the protocol buffers for a specific
  21163. * streaming RPC.
  21164. *
  21165. * ## Starting and Stopping
  21166. *
  21167. * Streaming RPCs are stateful and need to be start()ed before messages can
  21168. * be sent and received. The PersistentStream will call the onOpen() function
  21169. * of the listener once the stream is ready to accept requests.
  21170. *
  21171. * Should a start() fail, PersistentStream will call the registered onClose()
  21172. * listener with a FirestoreError indicating what went wrong.
  21173. *
  21174. * A PersistentStream can be started and stopped repeatedly.
  21175. *
  21176. * Generic types:
  21177. * SendType: The type of the outgoing message of the underlying
  21178. * connection stream
  21179. * ReceiveType: The type of the incoming message of the underlying
  21180. * connection stream
  21181. * ListenerType: The type of the listener that will be used for callbacks
  21182. */
  21183. class PersistentStream {
  21184. constructor(queue, connectionTimerId, idleTimerId, healthTimerId, connection, authCredentialsProvider, appCheckCredentialsProvider, listener) {
  21185. this.queue = queue;
  21186. this.idleTimerId = idleTimerId;
  21187. this.healthTimerId = healthTimerId;
  21188. this.connection = connection;
  21189. this.authCredentialsProvider = authCredentialsProvider;
  21190. this.appCheckCredentialsProvider = appCheckCredentialsProvider;
  21191. this.listener = listener;
  21192. this.state = 0 /* PersistentStreamState.Initial */;
  21193. /**
  21194. * A close count that's incremented every time the stream is closed; used by
  21195. * getCloseGuardedDispatcher() to invalidate callbacks that happen after
  21196. * close.
  21197. */
  21198. this.closeCount = 0;
  21199. this.idleTimer = null;
  21200. this.healthCheck = null;
  21201. this.stream = null;
  21202. this.backoff = new ExponentialBackoff(queue, connectionTimerId);
  21203. }
  21204. /**
  21205. * Returns true if start() has been called and no error has occurred. True
  21206. * indicates the stream is open or in the process of opening (which
  21207. * encompasses respecting backoff, getting auth tokens, and starting the
  21208. * actual RPC). Use isOpen() to determine if the stream is open and ready for
  21209. * outbound requests.
  21210. */
  21211. isStarted() {
  21212. return (this.state === 1 /* PersistentStreamState.Starting */ ||
  21213. this.state === 5 /* PersistentStreamState.Backoff */ ||
  21214. this.isOpen());
  21215. }
  21216. /**
  21217. * Returns true if the underlying RPC is open (the onOpen() listener has been
  21218. * called) and the stream is ready for outbound requests.
  21219. */
  21220. isOpen() {
  21221. return (this.state === 2 /* PersistentStreamState.Open */ ||
  21222. this.state === 3 /* PersistentStreamState.Healthy */);
  21223. }
  21224. /**
  21225. * Starts the RPC. Only allowed if isStarted() returns false. The stream is
  21226. * not immediately ready for use: onOpen() will be invoked when the RPC is
  21227. * ready for outbound requests, at which point isOpen() will return true.
  21228. *
  21229. * When start returns, isStarted() will return true.
  21230. */
  21231. start() {
  21232. if (this.state === 4 /* PersistentStreamState.Error */) {
  21233. this.performBackoff();
  21234. return;
  21235. }
  21236. this.auth();
  21237. }
  21238. /**
  21239. * Stops the RPC. This call is idempotent and allowed regardless of the
  21240. * current isStarted() state.
  21241. *
  21242. * When stop returns, isStarted() and isOpen() will both return false.
  21243. */
  21244. async stop() {
  21245. if (this.isStarted()) {
  21246. await this.close(0 /* PersistentStreamState.Initial */);
  21247. }
  21248. }
  21249. /**
  21250. * After an error the stream will usually back off on the next attempt to
  21251. * start it. If the error warrants an immediate restart of the stream, the
  21252. * sender can use this to indicate that the receiver should not back off.
  21253. *
  21254. * Each error will call the onClose() listener. That function can decide to
  21255. * inhibit backoff if required.
  21256. */
  21257. inhibitBackoff() {
  21258. this.state = 0 /* PersistentStreamState.Initial */;
  21259. this.backoff.reset();
  21260. }
  21261. /**
  21262. * Marks this stream as idle. If no further actions are performed on the
  21263. * stream for one minute, the stream will automatically close itself and
  21264. * notify the stream's onClose() handler with Status.OK. The stream will then
  21265. * be in a !isStarted() state, requiring the caller to start the stream again
  21266. * before further use.
  21267. *
  21268. * Only streams that are in state 'Open' can be marked idle, as all other
  21269. * states imply pending network operations.
  21270. */
  21271. markIdle() {
  21272. // Starts the idle time if we are in state 'Open' and are not yet already
  21273. // running a timer (in which case the previous idle timeout still applies).
  21274. if (this.isOpen() && this.idleTimer === null) {
  21275. this.idleTimer = this.queue.enqueueAfterDelay(this.idleTimerId, IDLE_TIMEOUT_MS, () => this.handleIdleCloseTimer());
  21276. }
  21277. }
  21278. /** Sends a message to the underlying stream. */
  21279. sendRequest(msg) {
  21280. this.cancelIdleCheck();
  21281. this.stream.send(msg);
  21282. }
  21283. /** Called by the idle timer when the stream should close due to inactivity. */
  21284. async handleIdleCloseTimer() {
  21285. if (this.isOpen()) {
  21286. // When timing out an idle stream there's no reason to force the stream into backoff when
  21287. // it restarts so set the stream state to Initial instead of Error.
  21288. return this.close(0 /* PersistentStreamState.Initial */);
  21289. }
  21290. }
  21291. /** Marks the stream as active again. */
  21292. cancelIdleCheck() {
  21293. if (this.idleTimer) {
  21294. this.idleTimer.cancel();
  21295. this.idleTimer = null;
  21296. }
  21297. }
  21298. /** Cancels the health check delayed operation. */
  21299. cancelHealthCheck() {
  21300. if (this.healthCheck) {
  21301. this.healthCheck.cancel();
  21302. this.healthCheck = null;
  21303. }
  21304. }
  21305. /**
  21306. * Closes the stream and cleans up as necessary:
  21307. *
  21308. * * closes the underlying GRPC stream;
  21309. * * calls the onClose handler with the given 'error';
  21310. * * sets internal stream state to 'finalState';
  21311. * * adjusts the backoff timer based on the error
  21312. *
  21313. * A new stream can be opened by calling start().
  21314. *
  21315. * @param finalState - the intended state of the stream after closing.
  21316. * @param error - the error the connection was closed with.
  21317. */
  21318. async close(finalState, error) {
  21319. // Cancel any outstanding timers (they're guaranteed not to execute).
  21320. this.cancelIdleCheck();
  21321. this.cancelHealthCheck();
  21322. this.backoff.cancel();
  21323. // Invalidates any stream-related callbacks (e.g. from auth or the
  21324. // underlying stream), guaranteeing they won't execute.
  21325. this.closeCount++;
  21326. if (finalState !== 4 /* PersistentStreamState.Error */) {
  21327. // If this is an intentional close ensure we don't delay our next connection attempt.
  21328. this.backoff.reset();
  21329. }
  21330. else if (error && error.code === Code.RESOURCE_EXHAUSTED) {
  21331. // Log the error. (Probably either 'quota exceeded' or 'max queue length reached'.)
  21332. logError(error.toString());
  21333. logError('Using maximum backoff delay to prevent overloading the backend.');
  21334. this.backoff.resetToMax();
  21335. }
  21336. else if (error &&
  21337. error.code === Code.UNAUTHENTICATED &&
  21338. this.state !== 3 /* PersistentStreamState.Healthy */) {
  21339. // "unauthenticated" error means the token was rejected. This should rarely
  21340. // happen since both Auth and AppCheck ensure a sufficient TTL when we
  21341. // request a token. If a user manually resets their system clock this can
  21342. // fail, however. In this case, we should get a Code.UNAUTHENTICATED error
  21343. // before we received the first message and we need to invalidate the token
  21344. // to ensure that we fetch a new token.
  21345. this.authCredentialsProvider.invalidateToken();
  21346. this.appCheckCredentialsProvider.invalidateToken();
  21347. }
  21348. // Clean up the underlying stream because we are no longer interested in events.
  21349. if (this.stream !== null) {
  21350. this.tearDown();
  21351. this.stream.close();
  21352. this.stream = null;
  21353. }
  21354. // This state must be assigned before calling onClose() to allow the callback to
  21355. // inhibit backoff or otherwise manipulate the state in its non-started state.
  21356. this.state = finalState;
  21357. // Notify the listener that the stream closed.
  21358. await this.listener.onClose(error);
  21359. }
  21360. /**
  21361. * Can be overridden to perform additional cleanup before the stream is closed.
  21362. * Calling super.tearDown() is not required.
  21363. */
  21364. tearDown() { }
  21365. auth() {
  21366. this.state = 1 /* PersistentStreamState.Starting */;
  21367. const dispatchIfNotClosed = this.getCloseGuardedDispatcher(this.closeCount);
  21368. // TODO(mikelehen): Just use dispatchIfNotClosed, but see TODO below.
  21369. const closeCount = this.closeCount;
  21370. Promise.all([
  21371. this.authCredentialsProvider.getToken(),
  21372. this.appCheckCredentialsProvider.getToken()
  21373. ]).then(([authToken, appCheckToken]) => {
  21374. // Stream can be stopped while waiting for authentication.
  21375. // TODO(mikelehen): We really should just use dispatchIfNotClosed
  21376. // and let this dispatch onto the queue, but that opened a spec test can
  21377. // of worms that I don't want to deal with in this PR.
  21378. if (this.closeCount === closeCount) {
  21379. // Normally we'd have to schedule the callback on the AsyncQueue.
  21380. // However, the following calls are safe to be called outside the
  21381. // AsyncQueue since they don't chain asynchronous calls
  21382. this.startStream(authToken, appCheckToken);
  21383. }
  21384. }, (error) => {
  21385. dispatchIfNotClosed(() => {
  21386. const rpcError = new FirestoreError(Code.UNKNOWN, 'Fetching auth token failed: ' + error.message);
  21387. return this.handleStreamClose(rpcError);
  21388. });
  21389. });
  21390. }
  21391. startStream(authToken, appCheckToken) {
  21392. const dispatchIfNotClosed = this.getCloseGuardedDispatcher(this.closeCount);
  21393. this.stream = this.startRpc(authToken, appCheckToken);
  21394. this.stream.onOpen(() => {
  21395. dispatchIfNotClosed(() => {
  21396. this.state = 2 /* PersistentStreamState.Open */;
  21397. this.healthCheck = this.queue.enqueueAfterDelay(this.healthTimerId, HEALTHY_TIMEOUT_MS, () => {
  21398. if (this.isOpen()) {
  21399. this.state = 3 /* PersistentStreamState.Healthy */;
  21400. }
  21401. return Promise.resolve();
  21402. });
  21403. return this.listener.onOpen();
  21404. });
  21405. });
  21406. this.stream.onClose((error) => {
  21407. dispatchIfNotClosed(() => {
  21408. return this.handleStreamClose(error);
  21409. });
  21410. });
  21411. this.stream.onMessage((msg) => {
  21412. dispatchIfNotClosed(() => {
  21413. return this.onMessage(msg);
  21414. });
  21415. });
  21416. }
  21417. performBackoff() {
  21418. this.state = 5 /* PersistentStreamState.Backoff */;
  21419. this.backoff.backoffAndRun(async () => {
  21420. this.state = 0 /* PersistentStreamState.Initial */;
  21421. this.start();
  21422. });
  21423. }
  21424. // Visible for tests
  21425. handleStreamClose(error) {
  21426. logDebug(LOG_TAG$7, `close with error: ${error}`);
  21427. this.stream = null;
  21428. // In theory the stream could close cleanly, however, in our current model
  21429. // we never expect this to happen because if we stop a stream ourselves,
  21430. // this callback will never be called. To prevent cases where we retry
  21431. // without a backoff accidentally, we set the stream to error in all cases.
  21432. return this.close(4 /* PersistentStreamState.Error */, error);
  21433. }
  21434. /**
  21435. * Returns a "dispatcher" function that dispatches operations onto the
  21436. * AsyncQueue but only runs them if closeCount remains unchanged. This allows
  21437. * us to turn auth / stream callbacks into no-ops if the stream is closed /
  21438. * re-opened, etc.
  21439. */
  21440. getCloseGuardedDispatcher(startCloseCount) {
  21441. return (fn) => {
  21442. this.queue.enqueueAndForget(() => {
  21443. if (this.closeCount === startCloseCount) {
  21444. return fn();
  21445. }
  21446. else {
  21447. logDebug(LOG_TAG$7, 'stream callback skipped by getCloseGuardedDispatcher.');
  21448. return Promise.resolve();
  21449. }
  21450. });
  21451. };
  21452. }
  21453. }
  21454. /**
  21455. * A PersistentStream that implements the Listen RPC.
  21456. *
  21457. * Once the Listen stream has called the onOpen() listener, any number of
  21458. * listen() and unlisten() calls can be made to control what changes will be
  21459. * sent from the server for ListenResponses.
  21460. */
  21461. class PersistentListenStream extends PersistentStream {
  21462. constructor(queue, connection, authCredentials, appCheckCredentials, serializer, listener) {
  21463. super(queue, "listen_stream_connection_backoff" /* TimerId.ListenStreamConnectionBackoff */, "listen_stream_idle" /* TimerId.ListenStreamIdle */, "health_check_timeout" /* TimerId.HealthCheckTimeout */, connection, authCredentials, appCheckCredentials, listener);
  21464. this.serializer = serializer;
  21465. }
  21466. startRpc(authToken, appCheckToken) {
  21467. return this.connection.openStream('Listen', authToken, appCheckToken);
  21468. }
  21469. onMessage(watchChangeProto) {
  21470. // A successful response means the stream is healthy
  21471. this.backoff.reset();
  21472. const watchChange = fromWatchChange(this.serializer, watchChangeProto);
  21473. const snapshot = versionFromListenResponse(watchChangeProto);
  21474. return this.listener.onWatchChange(watchChange, snapshot);
  21475. }
  21476. /**
  21477. * Registers interest in the results of the given target. If the target
  21478. * includes a resumeToken it will be included in the request. Results that
  21479. * affect the target will be streamed back as WatchChange messages that
  21480. * reference the targetId.
  21481. */
  21482. watch(targetData) {
  21483. const request = {};
  21484. request.database = getEncodedDatabaseId(this.serializer);
  21485. request.addTarget = toTarget(this.serializer, targetData);
  21486. const labels = toListenRequestLabels(this.serializer, targetData);
  21487. if (labels) {
  21488. request.labels = labels;
  21489. }
  21490. this.sendRequest(request);
  21491. }
  21492. /**
  21493. * Unregisters interest in the results of the target associated with the
  21494. * given targetId.
  21495. */
  21496. unwatch(targetId) {
  21497. const request = {};
  21498. request.database = getEncodedDatabaseId(this.serializer);
  21499. request.removeTarget = targetId;
  21500. this.sendRequest(request);
  21501. }
  21502. }
  21503. /**
  21504. * A Stream that implements the Write RPC.
  21505. *
  21506. * The Write RPC requires the caller to maintain special streamToken
  21507. * state in between calls, to help the server understand which responses the
  21508. * client has processed by the time the next request is made. Every response
  21509. * will contain a streamToken; this value must be passed to the next
  21510. * request.
  21511. *
  21512. * After calling start() on this stream, the next request must be a handshake,
  21513. * containing whatever streamToken is on hand. Once a response to this
  21514. * request is received, all pending mutations may be submitted. When
  21515. * submitting multiple batches of mutations at the same time, it's
  21516. * okay to use the same streamToken for the calls to writeMutations.
  21517. *
  21518. * TODO(b/33271235): Use proto types
  21519. */
  21520. class PersistentWriteStream extends PersistentStream {
  21521. constructor(queue, connection, authCredentials, appCheckCredentials, serializer, listener) {
  21522. super(queue, "write_stream_connection_backoff" /* TimerId.WriteStreamConnectionBackoff */, "write_stream_idle" /* TimerId.WriteStreamIdle */, "health_check_timeout" /* TimerId.HealthCheckTimeout */, connection, authCredentials, appCheckCredentials, listener);
  21523. this.serializer = serializer;
  21524. this.handshakeComplete_ = false;
  21525. }
  21526. /**
  21527. * Tracks whether or not a handshake has been successfully exchanged and
  21528. * the stream is ready to accept mutations.
  21529. */
  21530. get handshakeComplete() {
  21531. return this.handshakeComplete_;
  21532. }
  21533. // Override of PersistentStream.start
  21534. start() {
  21535. this.handshakeComplete_ = false;
  21536. this.lastStreamToken = undefined;
  21537. super.start();
  21538. }
  21539. tearDown() {
  21540. if (this.handshakeComplete_) {
  21541. this.writeMutations([]);
  21542. }
  21543. }
  21544. startRpc(authToken, appCheckToken) {
  21545. return this.connection.openStream('Write', authToken, appCheckToken);
  21546. }
  21547. onMessage(responseProto) {
  21548. // Always capture the last stream token.
  21549. hardAssert(!!responseProto.streamToken);
  21550. this.lastStreamToken = responseProto.streamToken;
  21551. if (!this.handshakeComplete_) {
  21552. // The first response is always the handshake response
  21553. hardAssert(!responseProto.writeResults || responseProto.writeResults.length === 0);
  21554. this.handshakeComplete_ = true;
  21555. return this.listener.onHandshakeComplete();
  21556. }
  21557. else {
  21558. // A successful first write response means the stream is healthy,
  21559. // Note, that we could consider a successful handshake healthy, however,
  21560. // the write itself might be causing an error we want to back off from.
  21561. this.backoff.reset();
  21562. const results = fromWriteResults(responseProto.writeResults, responseProto.commitTime);
  21563. const commitVersion = fromVersion(responseProto.commitTime);
  21564. return this.listener.onMutationResult(commitVersion, results);
  21565. }
  21566. }
  21567. /**
  21568. * Sends an initial streamToken to the server, performing the handshake
  21569. * required to make the StreamingWrite RPC work. Subsequent
  21570. * calls should wait until onHandshakeComplete was called.
  21571. */
  21572. writeHandshake() {
  21573. // TODO(dimond): Support stream resumption. We intentionally do not set the
  21574. // stream token on the handshake, ignoring any stream token we might have.
  21575. const request = {};
  21576. request.database = getEncodedDatabaseId(this.serializer);
  21577. this.sendRequest(request);
  21578. }
  21579. /** Sends a group of mutations to the Firestore backend to apply. */
  21580. writeMutations(mutations) {
  21581. const request = {
  21582. streamToken: this.lastStreamToken,
  21583. writes: mutations.map(mutation => toMutation(this.serializer, mutation))
  21584. };
  21585. this.sendRequest(request);
  21586. }
  21587. }
  21588. /**
  21589. * @license
  21590. * Copyright 2017 Google LLC
  21591. *
  21592. * Licensed under the Apache License, Version 2.0 (the "License");
  21593. * you may not use this file except in compliance with the License.
  21594. * You may obtain a copy of the License at
  21595. *
  21596. * http://www.apache.org/licenses/LICENSE-2.0
  21597. *
  21598. * Unless required by applicable law or agreed to in writing, software
  21599. * distributed under the License is distributed on an "AS IS" BASIS,
  21600. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21601. * See the License for the specific language governing permissions and
  21602. * limitations under the License.
  21603. */
  21604. /**
  21605. * Datastore and its related methods are a wrapper around the external Google
  21606. * Cloud Datastore grpc API, which provides an interface that is more convenient
  21607. * for the rest of the client SDK architecture to consume.
  21608. */
  21609. class Datastore {
  21610. }
  21611. /**
  21612. * An implementation of Datastore that exposes additional state for internal
  21613. * consumption.
  21614. */
  21615. class DatastoreImpl extends Datastore {
  21616. constructor(authCredentials, appCheckCredentials, connection, serializer) {
  21617. super();
  21618. this.authCredentials = authCredentials;
  21619. this.appCheckCredentials = appCheckCredentials;
  21620. this.connection = connection;
  21621. this.serializer = serializer;
  21622. this.terminated = false;
  21623. }
  21624. verifyInitialized() {
  21625. if (this.terminated) {
  21626. throw new FirestoreError(Code.FAILED_PRECONDITION, 'The client has already been terminated.');
  21627. }
  21628. }
  21629. /** Invokes the provided RPC with auth and AppCheck tokens. */
  21630. invokeRPC(rpcName, path, request) {
  21631. this.verifyInitialized();
  21632. return Promise.all([
  21633. this.authCredentials.getToken(),
  21634. this.appCheckCredentials.getToken()
  21635. ])
  21636. .then(([authToken, appCheckToken]) => {
  21637. return this.connection.invokeRPC(rpcName, path, request, authToken, appCheckToken);
  21638. })
  21639. .catch((error) => {
  21640. if (error.name === 'FirebaseError') {
  21641. if (error.code === Code.UNAUTHENTICATED) {
  21642. this.authCredentials.invalidateToken();
  21643. this.appCheckCredentials.invalidateToken();
  21644. }
  21645. throw error;
  21646. }
  21647. else {
  21648. throw new FirestoreError(Code.UNKNOWN, error.toString());
  21649. }
  21650. });
  21651. }
  21652. /** Invokes the provided RPC with streamed results with auth and AppCheck tokens. */
  21653. invokeStreamingRPC(rpcName, path, request, expectedResponseCount) {
  21654. this.verifyInitialized();
  21655. return Promise.all([
  21656. this.authCredentials.getToken(),
  21657. this.appCheckCredentials.getToken()
  21658. ])
  21659. .then(([authToken, appCheckToken]) => {
  21660. return this.connection.invokeStreamingRPC(rpcName, path, request, authToken, appCheckToken, expectedResponseCount);
  21661. })
  21662. .catch((error) => {
  21663. if (error.name === 'FirebaseError') {
  21664. if (error.code === Code.UNAUTHENTICATED) {
  21665. this.authCredentials.invalidateToken();
  21666. this.appCheckCredentials.invalidateToken();
  21667. }
  21668. throw error;
  21669. }
  21670. else {
  21671. throw new FirestoreError(Code.UNKNOWN, error.toString());
  21672. }
  21673. });
  21674. }
  21675. terminate() {
  21676. this.terminated = true;
  21677. }
  21678. }
  21679. // TODO(firestorexp): Make sure there is only one Datastore instance per
  21680. // firestore-exp client.
  21681. function newDatastore(authCredentials, appCheckCredentials, connection, serializer) {
  21682. return new DatastoreImpl(authCredentials, appCheckCredentials, connection, serializer);
  21683. }
  21684. async function invokeCommitRpc(datastore, mutations) {
  21685. const datastoreImpl = debugCast(datastore);
  21686. const path = getEncodedDatabaseId(datastoreImpl.serializer) + '/documents';
  21687. const request = {
  21688. writes: mutations.map(m => toMutation(datastoreImpl.serializer, m))
  21689. };
  21690. await datastoreImpl.invokeRPC('Commit', path, request);
  21691. }
  21692. async function invokeBatchGetDocumentsRpc(datastore, keys) {
  21693. const datastoreImpl = debugCast(datastore);
  21694. const path = getEncodedDatabaseId(datastoreImpl.serializer) + '/documents';
  21695. const request = {
  21696. documents: keys.map(k => toName(datastoreImpl.serializer, k))
  21697. };
  21698. const response = await datastoreImpl.invokeStreamingRPC('BatchGetDocuments', path, request, keys.length);
  21699. const docs = new Map();
  21700. response.forEach(proto => {
  21701. const doc = fromBatchGetDocumentsResponse(datastoreImpl.serializer, proto);
  21702. docs.set(doc.key.toString(), doc);
  21703. });
  21704. const result = [];
  21705. keys.forEach(key => {
  21706. const doc = docs.get(key.toString());
  21707. hardAssert(!!doc);
  21708. result.push(doc);
  21709. });
  21710. return result;
  21711. }
  21712. async function invokeRunAggregationQueryRpc(datastore, query) {
  21713. const datastoreImpl = debugCast(datastore);
  21714. const request = toRunAggregationQueryRequest(datastoreImpl.serializer, queryToTarget(query));
  21715. const parent = request.parent;
  21716. if (!datastoreImpl.connection.shouldResourcePathBeIncludedInRequest) {
  21717. delete request.parent;
  21718. }
  21719. const response = await datastoreImpl.invokeStreamingRPC('RunAggregationQuery', parent, request, /*expectedResponseCount=*/ 1);
  21720. return (response
  21721. // Omit RunAggregationQueryResponse that only contain readTimes.
  21722. .filter(proto => !!proto.result)
  21723. .map(proto => proto.result.aggregateFields));
  21724. }
  21725. function newPersistentWriteStream(datastore, queue, listener) {
  21726. const datastoreImpl = debugCast(datastore);
  21727. datastoreImpl.verifyInitialized();
  21728. return new PersistentWriteStream(queue, datastoreImpl.connection, datastoreImpl.authCredentials, datastoreImpl.appCheckCredentials, datastoreImpl.serializer, listener);
  21729. }
  21730. function newPersistentWatchStream(datastore, queue, listener) {
  21731. const datastoreImpl = debugCast(datastore);
  21732. datastoreImpl.verifyInitialized();
  21733. return new PersistentListenStream(queue, datastoreImpl.connection, datastoreImpl.authCredentials, datastoreImpl.appCheckCredentials, datastoreImpl.serializer, listener);
  21734. }
  21735. /**
  21736. * @license
  21737. * Copyright 2018 Google LLC
  21738. *
  21739. * Licensed under the Apache License, Version 2.0 (the "License");
  21740. * you may not use this file except in compliance with the License.
  21741. * You may obtain a copy of the License at
  21742. *
  21743. * http://www.apache.org/licenses/LICENSE-2.0
  21744. *
  21745. * Unless required by applicable law or agreed to in writing, software
  21746. * distributed under the License is distributed on an "AS IS" BASIS,
  21747. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21748. * See the License for the specific language governing permissions and
  21749. * limitations under the License.
  21750. */
  21751. const LOG_TAG$6 = 'OnlineStateTracker';
  21752. // To deal with transient failures, we allow multiple stream attempts before
  21753. // giving up and transitioning from OnlineState.Unknown to Offline.
  21754. // TODO(mikelehen): This used to be set to 2 as a mitigation for b/66228394.
  21755. // @jdimond thinks that bug is sufficiently fixed so that we can set this back
  21756. // to 1. If that works okay, we could potentially remove this logic entirely.
  21757. const MAX_WATCH_STREAM_FAILURES = 1;
  21758. // To deal with stream attempts that don't succeed or fail in a timely manner,
  21759. // we have a timeout for OnlineState to reach Online or Offline.
  21760. // If the timeout is reached, we transition to Offline rather than waiting
  21761. // indefinitely.
  21762. const ONLINE_STATE_TIMEOUT_MS = 10 * 1000;
  21763. /**
  21764. * A component used by the RemoteStore to track the OnlineState (that is,
  21765. * whether or not the client as a whole should be considered to be online or
  21766. * offline), implementing the appropriate heuristics.
  21767. *
  21768. * In particular, when the client is trying to connect to the backend, we
  21769. * allow up to MAX_WATCH_STREAM_FAILURES within ONLINE_STATE_TIMEOUT_MS for
  21770. * a connection to succeed. If we have too many failures or the timeout elapses,
  21771. * then we set the OnlineState to Offline, and the client will behave as if
  21772. * it is offline (get()s will return cached data, etc.).
  21773. */
  21774. class OnlineStateTracker {
  21775. constructor(asyncQueue, onlineStateHandler) {
  21776. this.asyncQueue = asyncQueue;
  21777. this.onlineStateHandler = onlineStateHandler;
  21778. /** The current OnlineState. */
  21779. this.state = "Unknown" /* OnlineState.Unknown */;
  21780. /**
  21781. * A count of consecutive failures to open the stream. If it reaches the
  21782. * maximum defined by MAX_WATCH_STREAM_FAILURES, we'll set the OnlineState to
  21783. * Offline.
  21784. */
  21785. this.watchStreamFailures = 0;
  21786. /**
  21787. * A timer that elapses after ONLINE_STATE_TIMEOUT_MS, at which point we
  21788. * transition from OnlineState.Unknown to OnlineState.Offline without waiting
  21789. * for the stream to actually fail (MAX_WATCH_STREAM_FAILURES times).
  21790. */
  21791. this.onlineStateTimer = null;
  21792. /**
  21793. * Whether the client should log a warning message if it fails to connect to
  21794. * the backend (initially true, cleared after a successful stream, or if we've
  21795. * logged the message already).
  21796. */
  21797. this.shouldWarnClientIsOffline = true;
  21798. }
  21799. /**
  21800. * Called by RemoteStore when a watch stream is started (including on each
  21801. * backoff attempt).
  21802. *
  21803. * If this is the first attempt, it sets the OnlineState to Unknown and starts
  21804. * the onlineStateTimer.
  21805. */
  21806. handleWatchStreamStart() {
  21807. if (this.watchStreamFailures === 0) {
  21808. this.setAndBroadcast("Unknown" /* OnlineState.Unknown */);
  21809. this.onlineStateTimer = this.asyncQueue.enqueueAfterDelay("online_state_timeout" /* TimerId.OnlineStateTimeout */, ONLINE_STATE_TIMEOUT_MS, () => {
  21810. this.onlineStateTimer = null;
  21811. this.logClientOfflineWarningIfNecessary(`Backend didn't respond within ${ONLINE_STATE_TIMEOUT_MS / 1000} ` +
  21812. `seconds.`);
  21813. this.setAndBroadcast("Offline" /* OnlineState.Offline */);
  21814. // NOTE: handleWatchStreamFailure() will continue to increment
  21815. // watchStreamFailures even though we are already marked Offline,
  21816. // but this is non-harmful.
  21817. return Promise.resolve();
  21818. });
  21819. }
  21820. }
  21821. /**
  21822. * Updates our OnlineState as appropriate after the watch stream reports a
  21823. * failure. The first failure moves us to the 'Unknown' state. We then may
  21824. * allow multiple failures (based on MAX_WATCH_STREAM_FAILURES) before we
  21825. * actually transition to the 'Offline' state.
  21826. */
  21827. handleWatchStreamFailure(error) {
  21828. if (this.state === "Online" /* OnlineState.Online */) {
  21829. this.setAndBroadcast("Unknown" /* OnlineState.Unknown */);
  21830. }
  21831. else {
  21832. this.watchStreamFailures++;
  21833. if (this.watchStreamFailures >= MAX_WATCH_STREAM_FAILURES) {
  21834. this.clearOnlineStateTimer();
  21835. this.logClientOfflineWarningIfNecessary(`Connection failed ${MAX_WATCH_STREAM_FAILURES} ` +
  21836. `times. Most recent error: ${error.toString()}`);
  21837. this.setAndBroadcast("Offline" /* OnlineState.Offline */);
  21838. }
  21839. }
  21840. }
  21841. /**
  21842. * Explicitly sets the OnlineState to the specified state.
  21843. *
  21844. * Note that this resets our timers / failure counters, etc. used by our
  21845. * Offline heuristics, so must not be used in place of
  21846. * handleWatchStreamStart() and handleWatchStreamFailure().
  21847. */
  21848. set(newState) {
  21849. this.clearOnlineStateTimer();
  21850. this.watchStreamFailures = 0;
  21851. if (newState === "Online" /* OnlineState.Online */) {
  21852. // We've connected to watch at least once. Don't warn the developer
  21853. // about being offline going forward.
  21854. this.shouldWarnClientIsOffline = false;
  21855. }
  21856. this.setAndBroadcast(newState);
  21857. }
  21858. setAndBroadcast(newState) {
  21859. if (newState !== this.state) {
  21860. this.state = newState;
  21861. this.onlineStateHandler(newState);
  21862. }
  21863. }
  21864. logClientOfflineWarningIfNecessary(details) {
  21865. const message = `Could not reach Cloud Firestore backend. ${details}\n` +
  21866. `This typically indicates that your device does not have a healthy ` +
  21867. `Internet connection at the moment. The client will operate in offline ` +
  21868. `mode until it is able to successfully connect to the backend.`;
  21869. if (this.shouldWarnClientIsOffline) {
  21870. logError(message);
  21871. this.shouldWarnClientIsOffline = false;
  21872. }
  21873. else {
  21874. logDebug(LOG_TAG$6, message);
  21875. }
  21876. }
  21877. clearOnlineStateTimer() {
  21878. if (this.onlineStateTimer !== null) {
  21879. this.onlineStateTimer.cancel();
  21880. this.onlineStateTimer = null;
  21881. }
  21882. }
  21883. }
  21884. /**
  21885. * @license
  21886. * Copyright 2017 Google LLC
  21887. *
  21888. * Licensed under the Apache License, Version 2.0 (the "License");
  21889. * you may not use this file except in compliance with the License.
  21890. * You may obtain a copy of the License at
  21891. *
  21892. * http://www.apache.org/licenses/LICENSE-2.0
  21893. *
  21894. * Unless required by applicable law or agreed to in writing, software
  21895. * distributed under the License is distributed on an "AS IS" BASIS,
  21896. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21897. * See the License for the specific language governing permissions and
  21898. * limitations under the License.
  21899. */
  21900. const LOG_TAG$5 = 'RemoteStore';
  21901. // TODO(b/35853402): Negotiate this with the stream.
  21902. const MAX_PENDING_WRITES = 10;
  21903. class RemoteStoreImpl {
  21904. constructor(
  21905. /**
  21906. * The local store, used to fill the write pipeline with outbound mutations.
  21907. */
  21908. localStore,
  21909. /** The client-side proxy for interacting with the backend. */
  21910. datastore, asyncQueue, onlineStateHandler, connectivityMonitor) {
  21911. this.localStore = localStore;
  21912. this.datastore = datastore;
  21913. this.asyncQueue = asyncQueue;
  21914. this.remoteSyncer = {};
  21915. /**
  21916. * A list of up to MAX_PENDING_WRITES writes that we have fetched from the
  21917. * LocalStore via fillWritePipeline() and have or will send to the write
  21918. * stream.
  21919. *
  21920. * Whenever writePipeline.length > 0 the RemoteStore will attempt to start or
  21921. * restart the write stream. When the stream is established the writes in the
  21922. * pipeline will be sent in order.
  21923. *
  21924. * Writes remain in writePipeline until they are acknowledged by the backend
  21925. * and thus will automatically be re-sent if the stream is interrupted /
  21926. * restarted before they're acknowledged.
  21927. *
  21928. * Write responses from the backend are linked to their originating request
  21929. * purely based on order, and so we can just shift() writes from the front of
  21930. * the writePipeline as we receive responses.
  21931. */
  21932. this.writePipeline = [];
  21933. /**
  21934. * A mapping of watched targets that the client cares about tracking and the
  21935. * user has explicitly called a 'listen' for this target.
  21936. *
  21937. * These targets may or may not have been sent to or acknowledged by the
  21938. * server. On re-establishing the listen stream, these targets should be sent
  21939. * to the server. The targets removed with unlistens are removed eagerly
  21940. * without waiting for confirmation from the listen stream.
  21941. */
  21942. this.listenTargets = new Map();
  21943. /**
  21944. * A set of reasons for why the RemoteStore may be offline. If empty, the
  21945. * RemoteStore may start its network connections.
  21946. */
  21947. this.offlineCauses = new Set();
  21948. /**
  21949. * Event handlers that get called when the network is disabled or enabled.
  21950. *
  21951. * PORTING NOTE: These functions are used on the Web client to create the
  21952. * underlying streams (to support tree-shakeable streams). On Android and iOS,
  21953. * the streams are created during construction of RemoteStore.
  21954. */
  21955. this.onNetworkStatusChange = [];
  21956. this.connectivityMonitor = connectivityMonitor;
  21957. this.connectivityMonitor.addCallback((_) => {
  21958. asyncQueue.enqueueAndForget(async () => {
  21959. // Porting Note: Unlike iOS, `restartNetwork()` is called even when the
  21960. // network becomes unreachable as we don't have any other way to tear
  21961. // down our streams.
  21962. if (canUseNetwork(this)) {
  21963. logDebug(LOG_TAG$5, 'Restarting streams for network reachability change.');
  21964. await restartNetwork(this);
  21965. }
  21966. });
  21967. });
  21968. this.onlineStateTracker = new OnlineStateTracker(asyncQueue, onlineStateHandler);
  21969. }
  21970. }
  21971. function newRemoteStore(localStore, datastore, asyncQueue, onlineStateHandler, connectivityMonitor) {
  21972. return new RemoteStoreImpl(localStore, datastore, asyncQueue, onlineStateHandler, connectivityMonitor);
  21973. }
  21974. /** Re-enables the network. Idempotent. */
  21975. function remoteStoreEnableNetwork(remoteStore) {
  21976. const remoteStoreImpl = debugCast(remoteStore);
  21977. remoteStoreImpl.offlineCauses.delete(0 /* OfflineCause.UserDisabled */);
  21978. return enableNetworkInternal(remoteStoreImpl);
  21979. }
  21980. async function enableNetworkInternal(remoteStoreImpl) {
  21981. if (canUseNetwork(remoteStoreImpl)) {
  21982. for (const networkStatusHandler of remoteStoreImpl.onNetworkStatusChange) {
  21983. await networkStatusHandler(/* enabled= */ true);
  21984. }
  21985. }
  21986. }
  21987. /**
  21988. * Temporarily disables the network. The network can be re-enabled using
  21989. * enableNetwork().
  21990. */
  21991. async function remoteStoreDisableNetwork(remoteStore) {
  21992. const remoteStoreImpl = debugCast(remoteStore);
  21993. remoteStoreImpl.offlineCauses.add(0 /* OfflineCause.UserDisabled */);
  21994. await disableNetworkInternal(remoteStoreImpl);
  21995. // Set the OnlineState to Offline so get()s return from cache, etc.
  21996. remoteStoreImpl.onlineStateTracker.set("Offline" /* OnlineState.Offline */);
  21997. }
  21998. async function disableNetworkInternal(remoteStoreImpl) {
  21999. for (const networkStatusHandler of remoteStoreImpl.onNetworkStatusChange) {
  22000. await networkStatusHandler(/* enabled= */ false);
  22001. }
  22002. }
  22003. async function remoteStoreShutdown(remoteStore) {
  22004. const remoteStoreImpl = debugCast(remoteStore);
  22005. logDebug(LOG_TAG$5, 'RemoteStore shutting down.');
  22006. remoteStoreImpl.offlineCauses.add(5 /* OfflineCause.Shutdown */);
  22007. await disableNetworkInternal(remoteStoreImpl);
  22008. remoteStoreImpl.connectivityMonitor.shutdown();
  22009. // Set the OnlineState to Unknown (rather than Offline) to avoid potentially
  22010. // triggering spurious listener events with cached data, etc.
  22011. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22012. }
  22013. /**
  22014. * Starts new listen for the given target. Uses resume token if provided. It
  22015. * is a no-op if the target of given `TargetData` is already being listened to.
  22016. */
  22017. function remoteStoreListen(remoteStore, targetData) {
  22018. const remoteStoreImpl = debugCast(remoteStore);
  22019. if (remoteStoreImpl.listenTargets.has(targetData.targetId)) {
  22020. return;
  22021. }
  22022. // Mark this as something the client is currently listening for.
  22023. remoteStoreImpl.listenTargets.set(targetData.targetId, targetData);
  22024. if (shouldStartWatchStream(remoteStoreImpl)) {
  22025. // The listen will be sent in onWatchStreamOpen
  22026. startWatchStream(remoteStoreImpl);
  22027. }
  22028. else if (ensureWatchStream(remoteStoreImpl).isOpen()) {
  22029. sendWatchRequest(remoteStoreImpl, targetData);
  22030. }
  22031. }
  22032. /**
  22033. * Removes the listen from server. It is a no-op if the given target id is
  22034. * not being listened to.
  22035. */
  22036. function remoteStoreUnlisten(remoteStore, targetId) {
  22037. const remoteStoreImpl = debugCast(remoteStore);
  22038. const watchStream = ensureWatchStream(remoteStoreImpl);
  22039. remoteStoreImpl.listenTargets.delete(targetId);
  22040. if (watchStream.isOpen()) {
  22041. sendUnwatchRequest(remoteStoreImpl, targetId);
  22042. }
  22043. if (remoteStoreImpl.listenTargets.size === 0) {
  22044. if (watchStream.isOpen()) {
  22045. watchStream.markIdle();
  22046. }
  22047. else if (canUseNetwork(remoteStoreImpl)) {
  22048. // Revert to OnlineState.Unknown if the watch stream is not open and we
  22049. // have no listeners, since without any listens to send we cannot
  22050. // confirm if the stream is healthy and upgrade to OnlineState.Online.
  22051. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22052. }
  22053. }
  22054. }
  22055. /**
  22056. * We need to increment the the expected number of pending responses we're due
  22057. * from watch so we wait for the ack to process any messages from this target.
  22058. */
  22059. function sendWatchRequest(remoteStoreImpl, targetData) {
  22060. remoteStoreImpl.watchChangeAggregator.recordPendingTargetRequest(targetData.targetId);
  22061. ensureWatchStream(remoteStoreImpl).watch(targetData);
  22062. }
  22063. /**
  22064. * We need to increment the expected number of pending responses we're due
  22065. * from watch so we wait for the removal on the server before we process any
  22066. * messages from this target.
  22067. */
  22068. function sendUnwatchRequest(remoteStoreImpl, targetId) {
  22069. remoteStoreImpl.watchChangeAggregator.recordPendingTargetRequest(targetId);
  22070. ensureWatchStream(remoteStoreImpl).unwatch(targetId);
  22071. }
  22072. function startWatchStream(remoteStoreImpl) {
  22073. remoteStoreImpl.watchChangeAggregator = new WatchChangeAggregator({
  22074. getRemoteKeysForTarget: targetId => remoteStoreImpl.remoteSyncer.getRemoteKeysForTarget(targetId),
  22075. getTargetDataForTarget: targetId => remoteStoreImpl.listenTargets.get(targetId) || null
  22076. });
  22077. ensureWatchStream(remoteStoreImpl).start();
  22078. remoteStoreImpl.onlineStateTracker.handleWatchStreamStart();
  22079. }
  22080. /**
  22081. * Returns whether the watch stream should be started because it's necessary
  22082. * and has not yet been started.
  22083. */
  22084. function shouldStartWatchStream(remoteStoreImpl) {
  22085. return (canUseNetwork(remoteStoreImpl) &&
  22086. !ensureWatchStream(remoteStoreImpl).isStarted() &&
  22087. remoteStoreImpl.listenTargets.size > 0);
  22088. }
  22089. function canUseNetwork(remoteStore) {
  22090. const remoteStoreImpl = debugCast(remoteStore);
  22091. return remoteStoreImpl.offlineCauses.size === 0;
  22092. }
  22093. function cleanUpWatchStreamState(remoteStoreImpl) {
  22094. remoteStoreImpl.watchChangeAggregator = undefined;
  22095. }
  22096. async function onWatchStreamOpen(remoteStoreImpl) {
  22097. remoteStoreImpl.listenTargets.forEach((targetData, targetId) => {
  22098. sendWatchRequest(remoteStoreImpl, targetData);
  22099. });
  22100. }
  22101. async function onWatchStreamClose(remoteStoreImpl, error) {
  22102. cleanUpWatchStreamState(remoteStoreImpl);
  22103. // If we still need the watch stream, retry the connection.
  22104. if (shouldStartWatchStream(remoteStoreImpl)) {
  22105. remoteStoreImpl.onlineStateTracker.handleWatchStreamFailure(error);
  22106. startWatchStream(remoteStoreImpl);
  22107. }
  22108. else {
  22109. // No need to restart watch stream because there are no active targets.
  22110. // The online state is set to unknown because there is no active attempt
  22111. // at establishing a connection
  22112. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22113. }
  22114. }
  22115. async function onWatchStreamChange(remoteStoreImpl, watchChange, snapshotVersion) {
  22116. // Mark the client as online since we got a message from the server
  22117. remoteStoreImpl.onlineStateTracker.set("Online" /* OnlineState.Online */);
  22118. if (watchChange instanceof WatchTargetChange &&
  22119. watchChange.state === 2 /* WatchTargetChangeState.Removed */ &&
  22120. watchChange.cause) {
  22121. // There was an error on a target, don't wait for a consistent snapshot
  22122. // to raise events
  22123. try {
  22124. await handleTargetError(remoteStoreImpl, watchChange);
  22125. }
  22126. catch (e) {
  22127. logDebug(LOG_TAG$5, 'Failed to remove targets %s: %s ', watchChange.targetIds.join(','), e);
  22128. await disableNetworkUntilRecovery(remoteStoreImpl, e);
  22129. }
  22130. return;
  22131. }
  22132. if (watchChange instanceof DocumentWatchChange) {
  22133. remoteStoreImpl.watchChangeAggregator.handleDocumentChange(watchChange);
  22134. }
  22135. else if (watchChange instanceof ExistenceFilterChange) {
  22136. remoteStoreImpl.watchChangeAggregator.handleExistenceFilter(watchChange);
  22137. }
  22138. else {
  22139. remoteStoreImpl.watchChangeAggregator.handleTargetChange(watchChange);
  22140. }
  22141. if (!snapshotVersion.isEqual(SnapshotVersion.min())) {
  22142. try {
  22143. const lastRemoteSnapshotVersion = await localStoreGetLastRemoteSnapshotVersion(remoteStoreImpl.localStore);
  22144. if (snapshotVersion.compareTo(lastRemoteSnapshotVersion) >= 0) {
  22145. // We have received a target change with a global snapshot if the snapshot
  22146. // version is not equal to SnapshotVersion.min().
  22147. await raiseWatchSnapshot(remoteStoreImpl, snapshotVersion);
  22148. }
  22149. }
  22150. catch (e) {
  22151. logDebug(LOG_TAG$5, 'Failed to raise snapshot:', e);
  22152. await disableNetworkUntilRecovery(remoteStoreImpl, e);
  22153. }
  22154. }
  22155. }
  22156. /**
  22157. * Recovery logic for IndexedDB errors that takes the network offline until
  22158. * `op` succeeds. Retries are scheduled with backoff using
  22159. * `enqueueRetryable()`. If `op()` is not provided, IndexedDB access is
  22160. * validated via a generic operation.
  22161. *
  22162. * The returned Promise is resolved once the network is disabled and before
  22163. * any retry attempt.
  22164. */
  22165. async function disableNetworkUntilRecovery(remoteStoreImpl, e, op) {
  22166. if (isIndexedDbTransactionError(e)) {
  22167. remoteStoreImpl.offlineCauses.add(1 /* OfflineCause.IndexedDbFailed */);
  22168. // Disable network and raise offline snapshots
  22169. await disableNetworkInternal(remoteStoreImpl);
  22170. remoteStoreImpl.onlineStateTracker.set("Offline" /* OnlineState.Offline */);
  22171. if (!op) {
  22172. // Use a simple read operation to determine if IndexedDB recovered.
  22173. // Ideally, we would expose a health check directly on SimpleDb, but
  22174. // RemoteStore only has access to persistence through LocalStore.
  22175. op = () => localStoreGetLastRemoteSnapshotVersion(remoteStoreImpl.localStore);
  22176. }
  22177. // Probe IndexedDB periodically and re-enable network
  22178. remoteStoreImpl.asyncQueue.enqueueRetryable(async () => {
  22179. logDebug(LOG_TAG$5, 'Retrying IndexedDB access');
  22180. await op();
  22181. remoteStoreImpl.offlineCauses.delete(1 /* OfflineCause.IndexedDbFailed */);
  22182. await enableNetworkInternal(remoteStoreImpl);
  22183. });
  22184. }
  22185. else {
  22186. throw e;
  22187. }
  22188. }
  22189. /**
  22190. * Executes `op`. If `op` fails, takes the network offline until `op`
  22191. * succeeds. Returns after the first attempt.
  22192. */
  22193. function executeWithRecovery(remoteStoreImpl, op) {
  22194. return op().catch(e => disableNetworkUntilRecovery(remoteStoreImpl, e, op));
  22195. }
  22196. /**
  22197. * Takes a batch of changes from the Datastore, repackages them as a
  22198. * RemoteEvent, and passes that on to the listener, which is typically the
  22199. * SyncEngine.
  22200. */
  22201. function raiseWatchSnapshot(remoteStoreImpl, snapshotVersion) {
  22202. const remoteEvent = remoteStoreImpl.watchChangeAggregator.createRemoteEvent(snapshotVersion);
  22203. // Update in-memory resume tokens. LocalStore will update the
  22204. // persistent view of these when applying the completed RemoteEvent.
  22205. remoteEvent.targetChanges.forEach((change, targetId) => {
  22206. if (change.resumeToken.approximateByteSize() > 0) {
  22207. const targetData = remoteStoreImpl.listenTargets.get(targetId);
  22208. // A watched target might have been removed already.
  22209. if (targetData) {
  22210. remoteStoreImpl.listenTargets.set(targetId, targetData.withResumeToken(change.resumeToken, snapshotVersion));
  22211. }
  22212. }
  22213. });
  22214. // Re-establish listens for the targets that have been invalidated by
  22215. // existence filter mismatches.
  22216. remoteEvent.targetMismatches.forEach(targetId => {
  22217. const targetData = remoteStoreImpl.listenTargets.get(targetId);
  22218. if (!targetData) {
  22219. // A watched target might have been removed already.
  22220. return;
  22221. }
  22222. // Clear the resume token for the target, since we're in a known mismatch
  22223. // state.
  22224. remoteStoreImpl.listenTargets.set(targetId, targetData.withResumeToken(ByteString.EMPTY_BYTE_STRING, targetData.snapshotVersion));
  22225. // Cause a hard reset by unwatching and rewatching immediately, but
  22226. // deliberately don't send a resume token so that we get a full update.
  22227. sendUnwatchRequest(remoteStoreImpl, targetId);
  22228. // Mark the target we send as being on behalf of an existence filter
  22229. // mismatch, but don't actually retain that in listenTargets. This ensures
  22230. // that we flag the first re-listen this way without impacting future
  22231. // listens of this target (that might happen e.g. on reconnect).
  22232. const requestTargetData = new TargetData(targetData.target, targetId, 1 /* TargetPurpose.ExistenceFilterMismatch */, targetData.sequenceNumber);
  22233. sendWatchRequest(remoteStoreImpl, requestTargetData);
  22234. });
  22235. return remoteStoreImpl.remoteSyncer.applyRemoteEvent(remoteEvent);
  22236. }
  22237. /** Handles an error on a target */
  22238. async function handleTargetError(remoteStoreImpl, watchChange) {
  22239. const error = watchChange.cause;
  22240. for (const targetId of watchChange.targetIds) {
  22241. // A watched target might have been removed already.
  22242. if (remoteStoreImpl.listenTargets.has(targetId)) {
  22243. await remoteStoreImpl.remoteSyncer.rejectListen(targetId, error);
  22244. remoteStoreImpl.listenTargets.delete(targetId);
  22245. remoteStoreImpl.watchChangeAggregator.removeTarget(targetId);
  22246. }
  22247. }
  22248. }
  22249. /**
  22250. * Attempts to fill our write pipeline with writes from the LocalStore.
  22251. *
  22252. * Called internally to bootstrap or refill the write pipeline and by
  22253. * SyncEngine whenever there are new mutations to process.
  22254. *
  22255. * Starts the write stream if necessary.
  22256. */
  22257. async function fillWritePipeline(remoteStore) {
  22258. const remoteStoreImpl = debugCast(remoteStore);
  22259. const writeStream = ensureWriteStream(remoteStoreImpl);
  22260. let lastBatchIdRetrieved = remoteStoreImpl.writePipeline.length > 0
  22261. ? remoteStoreImpl.writePipeline[remoteStoreImpl.writePipeline.length - 1]
  22262. .batchId
  22263. : BATCHID_UNKNOWN;
  22264. while (canAddToWritePipeline(remoteStoreImpl)) {
  22265. try {
  22266. const batch = await localStoreGetNextMutationBatch(remoteStoreImpl.localStore, lastBatchIdRetrieved);
  22267. if (batch === null) {
  22268. if (remoteStoreImpl.writePipeline.length === 0) {
  22269. writeStream.markIdle();
  22270. }
  22271. break;
  22272. }
  22273. else {
  22274. lastBatchIdRetrieved = batch.batchId;
  22275. addToWritePipeline(remoteStoreImpl, batch);
  22276. }
  22277. }
  22278. catch (e) {
  22279. await disableNetworkUntilRecovery(remoteStoreImpl, e);
  22280. }
  22281. }
  22282. if (shouldStartWriteStream(remoteStoreImpl)) {
  22283. startWriteStream(remoteStoreImpl);
  22284. }
  22285. }
  22286. /**
  22287. * Returns true if we can add to the write pipeline (i.e. the network is
  22288. * enabled and the write pipeline is not full).
  22289. */
  22290. function canAddToWritePipeline(remoteStoreImpl) {
  22291. return (canUseNetwork(remoteStoreImpl) &&
  22292. remoteStoreImpl.writePipeline.length < MAX_PENDING_WRITES);
  22293. }
  22294. /**
  22295. * Queues additional writes to be sent to the write stream, sending them
  22296. * immediately if the write stream is established.
  22297. */
  22298. function addToWritePipeline(remoteStoreImpl, batch) {
  22299. remoteStoreImpl.writePipeline.push(batch);
  22300. const writeStream = ensureWriteStream(remoteStoreImpl);
  22301. if (writeStream.isOpen() && writeStream.handshakeComplete) {
  22302. writeStream.writeMutations(batch.mutations);
  22303. }
  22304. }
  22305. function shouldStartWriteStream(remoteStoreImpl) {
  22306. return (canUseNetwork(remoteStoreImpl) &&
  22307. !ensureWriteStream(remoteStoreImpl).isStarted() &&
  22308. remoteStoreImpl.writePipeline.length > 0);
  22309. }
  22310. function startWriteStream(remoteStoreImpl) {
  22311. ensureWriteStream(remoteStoreImpl).start();
  22312. }
  22313. async function onWriteStreamOpen(remoteStoreImpl) {
  22314. ensureWriteStream(remoteStoreImpl).writeHandshake();
  22315. }
  22316. async function onWriteHandshakeComplete(remoteStoreImpl) {
  22317. const writeStream = ensureWriteStream(remoteStoreImpl);
  22318. // Send the write pipeline now that the stream is established.
  22319. for (const batch of remoteStoreImpl.writePipeline) {
  22320. writeStream.writeMutations(batch.mutations);
  22321. }
  22322. }
  22323. async function onMutationResult(remoteStoreImpl, commitVersion, results) {
  22324. const batch = remoteStoreImpl.writePipeline.shift();
  22325. const success = MutationBatchResult.from(batch, commitVersion, results);
  22326. await executeWithRecovery(remoteStoreImpl, () => remoteStoreImpl.remoteSyncer.applySuccessfulWrite(success));
  22327. // It's possible that with the completion of this mutation another
  22328. // slot has freed up.
  22329. await fillWritePipeline(remoteStoreImpl);
  22330. }
  22331. async function onWriteStreamClose(remoteStoreImpl, error) {
  22332. // If the write stream closed after the write handshake completes, a write
  22333. // operation failed and we fail the pending operation.
  22334. if (error && ensureWriteStream(remoteStoreImpl).handshakeComplete) {
  22335. // This error affects the actual write.
  22336. await handleWriteError(remoteStoreImpl, error);
  22337. }
  22338. // The write stream might have been started by refilling the write
  22339. // pipeline for failed writes
  22340. if (shouldStartWriteStream(remoteStoreImpl)) {
  22341. startWriteStream(remoteStoreImpl);
  22342. }
  22343. }
  22344. async function handleWriteError(remoteStoreImpl, error) {
  22345. // Only handle permanent errors here. If it's transient, just let the retry
  22346. // logic kick in.
  22347. if (isPermanentWriteError(error.code)) {
  22348. // This was a permanent error, the request itself was the problem
  22349. // so it's not going to succeed if we resend it.
  22350. const batch = remoteStoreImpl.writePipeline.shift();
  22351. // In this case it's also unlikely that the server itself is melting
  22352. // down -- this was just a bad request so inhibit backoff on the next
  22353. // restart.
  22354. ensureWriteStream(remoteStoreImpl).inhibitBackoff();
  22355. await executeWithRecovery(remoteStoreImpl, () => remoteStoreImpl.remoteSyncer.rejectFailedWrite(batch.batchId, error));
  22356. // It's possible that with the completion of this mutation
  22357. // another slot has freed up.
  22358. await fillWritePipeline(remoteStoreImpl);
  22359. }
  22360. }
  22361. async function restartNetwork(remoteStore) {
  22362. const remoteStoreImpl = debugCast(remoteStore);
  22363. remoteStoreImpl.offlineCauses.add(4 /* OfflineCause.ConnectivityChange */);
  22364. await disableNetworkInternal(remoteStoreImpl);
  22365. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22366. remoteStoreImpl.offlineCauses.delete(4 /* OfflineCause.ConnectivityChange */);
  22367. await enableNetworkInternal(remoteStoreImpl);
  22368. }
  22369. async function remoteStoreHandleCredentialChange(remoteStore, user) {
  22370. const remoteStoreImpl = debugCast(remoteStore);
  22371. remoteStoreImpl.asyncQueue.verifyOperationInProgress();
  22372. logDebug(LOG_TAG$5, 'RemoteStore received new credentials');
  22373. const usesNetwork = canUseNetwork(remoteStoreImpl);
  22374. // Tear down and re-create our network streams. This will ensure we get a
  22375. // fresh auth token for the new user and re-fill the write pipeline with
  22376. // new mutations from the LocalStore (since mutations are per-user).
  22377. remoteStoreImpl.offlineCauses.add(3 /* OfflineCause.CredentialChange */);
  22378. await disableNetworkInternal(remoteStoreImpl);
  22379. if (usesNetwork) {
  22380. // Don't set the network status to Unknown if we are offline.
  22381. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22382. }
  22383. await remoteStoreImpl.remoteSyncer.handleCredentialChange(user);
  22384. remoteStoreImpl.offlineCauses.delete(3 /* OfflineCause.CredentialChange */);
  22385. await enableNetworkInternal(remoteStoreImpl);
  22386. }
  22387. /**
  22388. * Toggles the network state when the client gains or loses its primary lease.
  22389. */
  22390. async function remoteStoreApplyPrimaryState(remoteStore, isPrimary) {
  22391. const remoteStoreImpl = debugCast(remoteStore);
  22392. if (isPrimary) {
  22393. remoteStoreImpl.offlineCauses.delete(2 /* OfflineCause.IsSecondary */);
  22394. await enableNetworkInternal(remoteStoreImpl);
  22395. }
  22396. else if (!isPrimary) {
  22397. remoteStoreImpl.offlineCauses.add(2 /* OfflineCause.IsSecondary */);
  22398. await disableNetworkInternal(remoteStoreImpl);
  22399. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22400. }
  22401. }
  22402. /**
  22403. * If not yet initialized, registers the WatchStream and its network state
  22404. * callback with `remoteStoreImpl`. Returns the existing stream if one is
  22405. * already available.
  22406. *
  22407. * PORTING NOTE: On iOS and Android, the WatchStream gets registered on startup.
  22408. * This is not done on Web to allow it to be tree-shaken.
  22409. */
  22410. function ensureWatchStream(remoteStoreImpl) {
  22411. if (!remoteStoreImpl.watchStream) {
  22412. // Create stream (but note that it is not started yet).
  22413. remoteStoreImpl.watchStream = newPersistentWatchStream(remoteStoreImpl.datastore, remoteStoreImpl.asyncQueue, {
  22414. onOpen: onWatchStreamOpen.bind(null, remoteStoreImpl),
  22415. onClose: onWatchStreamClose.bind(null, remoteStoreImpl),
  22416. onWatchChange: onWatchStreamChange.bind(null, remoteStoreImpl)
  22417. });
  22418. remoteStoreImpl.onNetworkStatusChange.push(async (enabled) => {
  22419. if (enabled) {
  22420. remoteStoreImpl.watchStream.inhibitBackoff();
  22421. if (shouldStartWatchStream(remoteStoreImpl)) {
  22422. startWatchStream(remoteStoreImpl);
  22423. }
  22424. else {
  22425. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22426. }
  22427. }
  22428. else {
  22429. await remoteStoreImpl.watchStream.stop();
  22430. cleanUpWatchStreamState(remoteStoreImpl);
  22431. }
  22432. });
  22433. }
  22434. return remoteStoreImpl.watchStream;
  22435. }
  22436. /**
  22437. * If not yet initialized, registers the WriteStream and its network state
  22438. * callback with `remoteStoreImpl`. Returns the existing stream if one is
  22439. * already available.
  22440. *
  22441. * PORTING NOTE: On iOS and Android, the WriteStream gets registered on startup.
  22442. * This is not done on Web to allow it to be tree-shaken.
  22443. */
  22444. function ensureWriteStream(remoteStoreImpl) {
  22445. if (!remoteStoreImpl.writeStream) {
  22446. // Create stream (but note that it is not started yet).
  22447. remoteStoreImpl.writeStream = newPersistentWriteStream(remoteStoreImpl.datastore, remoteStoreImpl.asyncQueue, {
  22448. onOpen: onWriteStreamOpen.bind(null, remoteStoreImpl),
  22449. onClose: onWriteStreamClose.bind(null, remoteStoreImpl),
  22450. onHandshakeComplete: onWriteHandshakeComplete.bind(null, remoteStoreImpl),
  22451. onMutationResult: onMutationResult.bind(null, remoteStoreImpl)
  22452. });
  22453. remoteStoreImpl.onNetworkStatusChange.push(async (enabled) => {
  22454. if (enabled) {
  22455. remoteStoreImpl.writeStream.inhibitBackoff();
  22456. // This will start the write stream if necessary.
  22457. await fillWritePipeline(remoteStoreImpl);
  22458. }
  22459. else {
  22460. await remoteStoreImpl.writeStream.stop();
  22461. if (remoteStoreImpl.writePipeline.length > 0) {
  22462. logDebug(LOG_TAG$5, `Stopping write stream with ${remoteStoreImpl.writePipeline.length} pending writes`);
  22463. remoteStoreImpl.writePipeline = [];
  22464. }
  22465. }
  22466. });
  22467. }
  22468. return remoteStoreImpl.writeStream;
  22469. }
  22470. /**
  22471. * @license
  22472. * Copyright 2017 Google LLC
  22473. *
  22474. * Licensed under the Apache License, Version 2.0 (the "License");
  22475. * you may not use this file except in compliance with the License.
  22476. * You may obtain a copy of the License at
  22477. *
  22478. * http://www.apache.org/licenses/LICENSE-2.0
  22479. *
  22480. * Unless required by applicable law or agreed to in writing, software
  22481. * distributed under the License is distributed on an "AS IS" BASIS,
  22482. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22483. * See the License for the specific language governing permissions and
  22484. * limitations under the License.
  22485. */
  22486. const LOG_TAG$4 = 'AsyncQueue';
  22487. /**
  22488. * Represents an operation scheduled to be run in the future on an AsyncQueue.
  22489. *
  22490. * It is created via DelayedOperation.createAndSchedule().
  22491. *
  22492. * Supports cancellation (via cancel()) and early execution (via skipDelay()).
  22493. *
  22494. * Note: We implement `PromiseLike` instead of `Promise`, as the `Promise` type
  22495. * in newer versions of TypeScript defines `finally`, which is not available in
  22496. * IE.
  22497. */
  22498. class DelayedOperation {
  22499. constructor(asyncQueue, timerId, targetTimeMs, op, removalCallback) {
  22500. this.asyncQueue = asyncQueue;
  22501. this.timerId = timerId;
  22502. this.targetTimeMs = targetTimeMs;
  22503. this.op = op;
  22504. this.removalCallback = removalCallback;
  22505. this.deferred = new Deferred();
  22506. this.then = this.deferred.promise.then.bind(this.deferred.promise);
  22507. // It's normal for the deferred promise to be canceled (due to cancellation)
  22508. // and so we attach a dummy catch callback to avoid
  22509. // 'UnhandledPromiseRejectionWarning' log spam.
  22510. this.deferred.promise.catch(err => { });
  22511. }
  22512. /**
  22513. * Creates and returns a DelayedOperation that has been scheduled to be
  22514. * executed on the provided asyncQueue after the provided delayMs.
  22515. *
  22516. * @param asyncQueue - The queue to schedule the operation on.
  22517. * @param id - A Timer ID identifying the type of operation this is.
  22518. * @param delayMs - The delay (ms) before the operation should be scheduled.
  22519. * @param op - The operation to run.
  22520. * @param removalCallback - A callback to be called synchronously once the
  22521. * operation is executed or canceled, notifying the AsyncQueue to remove it
  22522. * from its delayedOperations list.
  22523. * PORTING NOTE: This exists to prevent making removeDelayedOperation() and
  22524. * the DelayedOperation class public.
  22525. */
  22526. static createAndSchedule(asyncQueue, timerId, delayMs, op, removalCallback) {
  22527. const targetTime = Date.now() + delayMs;
  22528. const delayedOp = new DelayedOperation(asyncQueue, timerId, targetTime, op, removalCallback);
  22529. delayedOp.start(delayMs);
  22530. return delayedOp;
  22531. }
  22532. /**
  22533. * Starts the timer. This is called immediately after construction by
  22534. * createAndSchedule().
  22535. */
  22536. start(delayMs) {
  22537. this.timerHandle = setTimeout(() => this.handleDelayElapsed(), delayMs);
  22538. }
  22539. /**
  22540. * Queues the operation to run immediately (if it hasn't already been run or
  22541. * canceled).
  22542. */
  22543. skipDelay() {
  22544. return this.handleDelayElapsed();
  22545. }
  22546. /**
  22547. * Cancels the operation if it hasn't already been executed or canceled. The
  22548. * promise will be rejected.
  22549. *
  22550. * As long as the operation has not yet been run, calling cancel() provides a
  22551. * guarantee that the operation will not be run.
  22552. */
  22553. cancel(reason) {
  22554. if (this.timerHandle !== null) {
  22555. this.clearTimeout();
  22556. this.deferred.reject(new FirestoreError(Code.CANCELLED, 'Operation cancelled' + (reason ? ': ' + reason : '')));
  22557. }
  22558. }
  22559. handleDelayElapsed() {
  22560. this.asyncQueue.enqueueAndForget(() => {
  22561. if (this.timerHandle !== null) {
  22562. this.clearTimeout();
  22563. return this.op().then(result => {
  22564. return this.deferred.resolve(result);
  22565. });
  22566. }
  22567. else {
  22568. return Promise.resolve();
  22569. }
  22570. });
  22571. }
  22572. clearTimeout() {
  22573. if (this.timerHandle !== null) {
  22574. this.removalCallback(this);
  22575. clearTimeout(this.timerHandle);
  22576. this.timerHandle = null;
  22577. }
  22578. }
  22579. }
  22580. /**
  22581. * Returns a FirestoreError that can be surfaced to the user if the provided
  22582. * error is an IndexedDbTransactionError. Re-throws the error otherwise.
  22583. */
  22584. function wrapInUserErrorIfRecoverable(e, msg) {
  22585. logError(LOG_TAG$4, `${msg}: ${e}`);
  22586. if (isIndexedDbTransactionError(e)) {
  22587. return new FirestoreError(Code.UNAVAILABLE, `${msg}: ${e}`);
  22588. }
  22589. else {
  22590. throw e;
  22591. }
  22592. }
  22593. /**
  22594. * @license
  22595. * Copyright 2017 Google LLC
  22596. *
  22597. * Licensed under the Apache License, Version 2.0 (the "License");
  22598. * you may not use this file except in compliance with the License.
  22599. * You may obtain a copy of the License at
  22600. *
  22601. * http://www.apache.org/licenses/LICENSE-2.0
  22602. *
  22603. * Unless required by applicable law or agreed to in writing, software
  22604. * distributed under the License is distributed on an "AS IS" BASIS,
  22605. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22606. * See the License for the specific language governing permissions and
  22607. * limitations under the License.
  22608. */
  22609. /**
  22610. * DocumentSet is an immutable (copy-on-write) collection that holds documents
  22611. * in order specified by the provided comparator. We always add a document key
  22612. * comparator on top of what is provided to guarantee document equality based on
  22613. * the key.
  22614. */
  22615. class DocumentSet {
  22616. /** The default ordering is by key if the comparator is omitted */
  22617. constructor(comp) {
  22618. // We are adding document key comparator to the end as it's the only
  22619. // guaranteed unique property of a document.
  22620. if (comp) {
  22621. this.comparator = (d1, d2) => comp(d1, d2) || DocumentKey.comparator(d1.key, d2.key);
  22622. }
  22623. else {
  22624. this.comparator = (d1, d2) => DocumentKey.comparator(d1.key, d2.key);
  22625. }
  22626. this.keyedMap = documentMap();
  22627. this.sortedSet = new SortedMap(this.comparator);
  22628. }
  22629. /**
  22630. * Returns an empty copy of the existing DocumentSet, using the same
  22631. * comparator.
  22632. */
  22633. static emptySet(oldSet) {
  22634. return new DocumentSet(oldSet.comparator);
  22635. }
  22636. has(key) {
  22637. return this.keyedMap.get(key) != null;
  22638. }
  22639. get(key) {
  22640. return this.keyedMap.get(key);
  22641. }
  22642. first() {
  22643. return this.sortedSet.minKey();
  22644. }
  22645. last() {
  22646. return this.sortedSet.maxKey();
  22647. }
  22648. isEmpty() {
  22649. return this.sortedSet.isEmpty();
  22650. }
  22651. /**
  22652. * Returns the index of the provided key in the document set, or -1 if the
  22653. * document key is not present in the set;
  22654. */
  22655. indexOf(key) {
  22656. const doc = this.keyedMap.get(key);
  22657. return doc ? this.sortedSet.indexOf(doc) : -1;
  22658. }
  22659. get size() {
  22660. return this.sortedSet.size;
  22661. }
  22662. /** Iterates documents in order defined by "comparator" */
  22663. forEach(cb) {
  22664. this.sortedSet.inorderTraversal((k, v) => {
  22665. cb(k);
  22666. return false;
  22667. });
  22668. }
  22669. /** Inserts or updates a document with the same key */
  22670. add(doc) {
  22671. // First remove the element if we have it.
  22672. const set = this.delete(doc.key);
  22673. return set.copy(set.keyedMap.insert(doc.key, doc), set.sortedSet.insert(doc, null));
  22674. }
  22675. /** Deletes a document with a given key */
  22676. delete(key) {
  22677. const doc = this.get(key);
  22678. if (!doc) {
  22679. return this;
  22680. }
  22681. return this.copy(this.keyedMap.remove(key), this.sortedSet.remove(doc));
  22682. }
  22683. isEqual(other) {
  22684. if (!(other instanceof DocumentSet)) {
  22685. return false;
  22686. }
  22687. if (this.size !== other.size) {
  22688. return false;
  22689. }
  22690. const thisIt = this.sortedSet.getIterator();
  22691. const otherIt = other.sortedSet.getIterator();
  22692. while (thisIt.hasNext()) {
  22693. const thisDoc = thisIt.getNext().key;
  22694. const otherDoc = otherIt.getNext().key;
  22695. if (!thisDoc.isEqual(otherDoc)) {
  22696. return false;
  22697. }
  22698. }
  22699. return true;
  22700. }
  22701. toString() {
  22702. const docStrings = [];
  22703. this.forEach(doc => {
  22704. docStrings.push(doc.toString());
  22705. });
  22706. if (docStrings.length === 0) {
  22707. return 'DocumentSet ()';
  22708. }
  22709. else {
  22710. return 'DocumentSet (\n ' + docStrings.join(' \n') + '\n)';
  22711. }
  22712. }
  22713. copy(keyedMap, sortedSet) {
  22714. const newSet = new DocumentSet();
  22715. newSet.comparator = this.comparator;
  22716. newSet.keyedMap = keyedMap;
  22717. newSet.sortedSet = sortedSet;
  22718. return newSet;
  22719. }
  22720. }
  22721. /**
  22722. * @license
  22723. * Copyright 2017 Google LLC
  22724. *
  22725. * Licensed under the Apache License, Version 2.0 (the "License");
  22726. * you may not use this file except in compliance with the License.
  22727. * You may obtain a copy of the License at
  22728. *
  22729. * http://www.apache.org/licenses/LICENSE-2.0
  22730. *
  22731. * Unless required by applicable law or agreed to in writing, software
  22732. * distributed under the License is distributed on an "AS IS" BASIS,
  22733. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22734. * See the License for the specific language governing permissions and
  22735. * limitations under the License.
  22736. */
  22737. /**
  22738. * DocumentChangeSet keeps track of a set of changes to docs in a query, merging
  22739. * duplicate events for the same doc.
  22740. */
  22741. class DocumentChangeSet {
  22742. constructor() {
  22743. this.changeMap = new SortedMap(DocumentKey.comparator);
  22744. }
  22745. track(change) {
  22746. const key = change.doc.key;
  22747. const oldChange = this.changeMap.get(key);
  22748. if (!oldChange) {
  22749. this.changeMap = this.changeMap.insert(key, change);
  22750. return;
  22751. }
  22752. // Merge the new change with the existing change.
  22753. if (change.type !== 0 /* ChangeType.Added */ &&
  22754. oldChange.type === 3 /* ChangeType.Metadata */) {
  22755. this.changeMap = this.changeMap.insert(key, change);
  22756. }
  22757. else if (change.type === 3 /* ChangeType.Metadata */ &&
  22758. oldChange.type !== 1 /* ChangeType.Removed */) {
  22759. this.changeMap = this.changeMap.insert(key, {
  22760. type: oldChange.type,
  22761. doc: change.doc
  22762. });
  22763. }
  22764. else if (change.type === 2 /* ChangeType.Modified */ &&
  22765. oldChange.type === 2 /* ChangeType.Modified */) {
  22766. this.changeMap = this.changeMap.insert(key, {
  22767. type: 2 /* ChangeType.Modified */,
  22768. doc: change.doc
  22769. });
  22770. }
  22771. else if (change.type === 2 /* ChangeType.Modified */ &&
  22772. oldChange.type === 0 /* ChangeType.Added */) {
  22773. this.changeMap = this.changeMap.insert(key, {
  22774. type: 0 /* ChangeType.Added */,
  22775. doc: change.doc
  22776. });
  22777. }
  22778. else if (change.type === 1 /* ChangeType.Removed */ &&
  22779. oldChange.type === 0 /* ChangeType.Added */) {
  22780. this.changeMap = this.changeMap.remove(key);
  22781. }
  22782. else if (change.type === 1 /* ChangeType.Removed */ &&
  22783. oldChange.type === 2 /* ChangeType.Modified */) {
  22784. this.changeMap = this.changeMap.insert(key, {
  22785. type: 1 /* ChangeType.Removed */,
  22786. doc: oldChange.doc
  22787. });
  22788. }
  22789. else if (change.type === 0 /* ChangeType.Added */ &&
  22790. oldChange.type === 1 /* ChangeType.Removed */) {
  22791. this.changeMap = this.changeMap.insert(key, {
  22792. type: 2 /* ChangeType.Modified */,
  22793. doc: change.doc
  22794. });
  22795. }
  22796. else {
  22797. // This includes these cases, which don't make sense:
  22798. // Added->Added
  22799. // Removed->Removed
  22800. // Modified->Added
  22801. // Removed->Modified
  22802. // Metadata->Added
  22803. // Removed->Metadata
  22804. fail();
  22805. }
  22806. }
  22807. getChanges() {
  22808. const changes = [];
  22809. this.changeMap.inorderTraversal((key, change) => {
  22810. changes.push(change);
  22811. });
  22812. return changes;
  22813. }
  22814. }
  22815. class ViewSnapshot {
  22816. constructor(query, docs, oldDocs, docChanges, mutatedKeys, fromCache, syncStateChanged, excludesMetadataChanges, hasCachedResults) {
  22817. this.query = query;
  22818. this.docs = docs;
  22819. this.oldDocs = oldDocs;
  22820. this.docChanges = docChanges;
  22821. this.mutatedKeys = mutatedKeys;
  22822. this.fromCache = fromCache;
  22823. this.syncStateChanged = syncStateChanged;
  22824. this.excludesMetadataChanges = excludesMetadataChanges;
  22825. this.hasCachedResults = hasCachedResults;
  22826. }
  22827. /** Returns a view snapshot as if all documents in the snapshot were added. */
  22828. static fromInitialDocuments(query, documents, mutatedKeys, fromCache, hasCachedResults) {
  22829. const changes = [];
  22830. documents.forEach(doc => {
  22831. changes.push({ type: 0 /* ChangeType.Added */, doc });
  22832. });
  22833. return new ViewSnapshot(query, documents, DocumentSet.emptySet(documents), changes, mutatedKeys, fromCache,
  22834. /* syncStateChanged= */ true,
  22835. /* excludesMetadataChanges= */ false, hasCachedResults);
  22836. }
  22837. get hasPendingWrites() {
  22838. return !this.mutatedKeys.isEmpty();
  22839. }
  22840. isEqual(other) {
  22841. if (this.fromCache !== other.fromCache ||
  22842. this.hasCachedResults !== other.hasCachedResults ||
  22843. this.syncStateChanged !== other.syncStateChanged ||
  22844. !this.mutatedKeys.isEqual(other.mutatedKeys) ||
  22845. !queryEquals(this.query, other.query) ||
  22846. !this.docs.isEqual(other.docs) ||
  22847. !this.oldDocs.isEqual(other.oldDocs)) {
  22848. return false;
  22849. }
  22850. const changes = this.docChanges;
  22851. const otherChanges = other.docChanges;
  22852. if (changes.length !== otherChanges.length) {
  22853. return false;
  22854. }
  22855. for (let i = 0; i < changes.length; i++) {
  22856. if (changes[i].type !== otherChanges[i].type ||
  22857. !changes[i].doc.isEqual(otherChanges[i].doc)) {
  22858. return false;
  22859. }
  22860. }
  22861. return true;
  22862. }
  22863. }
  22864. /**
  22865. * @license
  22866. * Copyright 2017 Google LLC
  22867. *
  22868. * Licensed under the Apache License, Version 2.0 (the "License");
  22869. * you may not use this file except in compliance with the License.
  22870. * You may obtain a copy of the License at
  22871. *
  22872. * http://www.apache.org/licenses/LICENSE-2.0
  22873. *
  22874. * Unless required by applicable law or agreed to in writing, software
  22875. * distributed under the License is distributed on an "AS IS" BASIS,
  22876. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22877. * See the License for the specific language governing permissions and
  22878. * limitations under the License.
  22879. */
  22880. /**
  22881. * Holds the listeners and the last received ViewSnapshot for a query being
  22882. * tracked by EventManager.
  22883. */
  22884. class QueryListenersInfo {
  22885. constructor() {
  22886. this.viewSnap = undefined;
  22887. this.listeners = [];
  22888. }
  22889. }
  22890. function newEventManager() {
  22891. return new EventManagerImpl();
  22892. }
  22893. class EventManagerImpl {
  22894. constructor() {
  22895. this.queries = new ObjectMap(q => canonifyQuery(q), queryEquals);
  22896. this.onlineState = "Unknown" /* OnlineState.Unknown */;
  22897. this.snapshotsInSyncListeners = new Set();
  22898. }
  22899. }
  22900. async function eventManagerListen(eventManager, listener) {
  22901. const eventManagerImpl = debugCast(eventManager);
  22902. const query = listener.query;
  22903. let firstListen = false;
  22904. let queryInfo = eventManagerImpl.queries.get(query);
  22905. if (!queryInfo) {
  22906. firstListen = true;
  22907. queryInfo = new QueryListenersInfo();
  22908. }
  22909. if (firstListen) {
  22910. try {
  22911. queryInfo.viewSnap = await eventManagerImpl.onListen(query);
  22912. }
  22913. catch (e) {
  22914. const firestoreError = wrapInUserErrorIfRecoverable(e, `Initialization of query '${stringifyQuery(listener.query)}' failed`);
  22915. listener.onError(firestoreError);
  22916. return;
  22917. }
  22918. }
  22919. eventManagerImpl.queries.set(query, queryInfo);
  22920. queryInfo.listeners.push(listener);
  22921. // Run global snapshot listeners if a consistent snapshot has been emitted.
  22922. listener.applyOnlineStateChange(eventManagerImpl.onlineState);
  22923. if (queryInfo.viewSnap) {
  22924. const raisedEvent = listener.onViewSnapshot(queryInfo.viewSnap);
  22925. if (raisedEvent) {
  22926. raiseSnapshotsInSyncEvent(eventManagerImpl);
  22927. }
  22928. }
  22929. }
  22930. async function eventManagerUnlisten(eventManager, listener) {
  22931. const eventManagerImpl = debugCast(eventManager);
  22932. const query = listener.query;
  22933. let lastListen = false;
  22934. const queryInfo = eventManagerImpl.queries.get(query);
  22935. if (queryInfo) {
  22936. const i = queryInfo.listeners.indexOf(listener);
  22937. if (i >= 0) {
  22938. queryInfo.listeners.splice(i, 1);
  22939. lastListen = queryInfo.listeners.length === 0;
  22940. }
  22941. }
  22942. if (lastListen) {
  22943. eventManagerImpl.queries.delete(query);
  22944. return eventManagerImpl.onUnlisten(query);
  22945. }
  22946. }
  22947. function eventManagerOnWatchChange(eventManager, viewSnaps) {
  22948. const eventManagerImpl = debugCast(eventManager);
  22949. let raisedEvent = false;
  22950. for (const viewSnap of viewSnaps) {
  22951. const query = viewSnap.query;
  22952. const queryInfo = eventManagerImpl.queries.get(query);
  22953. if (queryInfo) {
  22954. for (const listener of queryInfo.listeners) {
  22955. if (listener.onViewSnapshot(viewSnap)) {
  22956. raisedEvent = true;
  22957. }
  22958. }
  22959. queryInfo.viewSnap = viewSnap;
  22960. }
  22961. }
  22962. if (raisedEvent) {
  22963. raiseSnapshotsInSyncEvent(eventManagerImpl);
  22964. }
  22965. }
  22966. function eventManagerOnWatchError(eventManager, query, error) {
  22967. const eventManagerImpl = debugCast(eventManager);
  22968. const queryInfo = eventManagerImpl.queries.get(query);
  22969. if (queryInfo) {
  22970. for (const listener of queryInfo.listeners) {
  22971. listener.onError(error);
  22972. }
  22973. }
  22974. // Remove all listeners. NOTE: We don't need to call syncEngine.unlisten()
  22975. // after an error.
  22976. eventManagerImpl.queries.delete(query);
  22977. }
  22978. function eventManagerOnOnlineStateChange(eventManager, onlineState) {
  22979. const eventManagerImpl = debugCast(eventManager);
  22980. eventManagerImpl.onlineState = onlineState;
  22981. let raisedEvent = false;
  22982. eventManagerImpl.queries.forEach((_, queryInfo) => {
  22983. for (const listener of queryInfo.listeners) {
  22984. // Run global snapshot listeners if a consistent snapshot has been emitted.
  22985. if (listener.applyOnlineStateChange(onlineState)) {
  22986. raisedEvent = true;
  22987. }
  22988. }
  22989. });
  22990. if (raisedEvent) {
  22991. raiseSnapshotsInSyncEvent(eventManagerImpl);
  22992. }
  22993. }
  22994. function addSnapshotsInSyncListener(eventManager, observer) {
  22995. const eventManagerImpl = debugCast(eventManager);
  22996. eventManagerImpl.snapshotsInSyncListeners.add(observer);
  22997. // Immediately fire an initial event, indicating all existing listeners
  22998. // are in-sync.
  22999. observer.next();
  23000. }
  23001. function removeSnapshotsInSyncListener(eventManager, observer) {
  23002. const eventManagerImpl = debugCast(eventManager);
  23003. eventManagerImpl.snapshotsInSyncListeners.delete(observer);
  23004. }
  23005. // Call all global snapshot listeners that have been set.
  23006. function raiseSnapshotsInSyncEvent(eventManagerImpl) {
  23007. eventManagerImpl.snapshotsInSyncListeners.forEach(observer => {
  23008. observer.next();
  23009. });
  23010. }
  23011. /**
  23012. * QueryListener takes a series of internal view snapshots and determines
  23013. * when to raise the event.
  23014. *
  23015. * It uses an Observer to dispatch events.
  23016. */
  23017. class QueryListener {
  23018. constructor(query, queryObserver, options) {
  23019. this.query = query;
  23020. this.queryObserver = queryObserver;
  23021. /**
  23022. * Initial snapshots (e.g. from cache) may not be propagated to the wrapped
  23023. * observer. This flag is set to true once we've actually raised an event.
  23024. */
  23025. this.raisedInitialEvent = false;
  23026. this.snap = null;
  23027. this.onlineState = "Unknown" /* OnlineState.Unknown */;
  23028. this.options = options || {};
  23029. }
  23030. /**
  23031. * Applies the new ViewSnapshot to this listener, raising a user-facing event
  23032. * if applicable (depending on what changed, whether the user has opted into
  23033. * metadata-only changes, etc.). Returns true if a user-facing event was
  23034. * indeed raised.
  23035. */
  23036. onViewSnapshot(snap) {
  23037. if (!this.options.includeMetadataChanges) {
  23038. // Remove the metadata only changes.
  23039. const docChanges = [];
  23040. for (const docChange of snap.docChanges) {
  23041. if (docChange.type !== 3 /* ChangeType.Metadata */) {
  23042. docChanges.push(docChange);
  23043. }
  23044. }
  23045. snap = new ViewSnapshot(snap.query, snap.docs, snap.oldDocs, docChanges, snap.mutatedKeys, snap.fromCache, snap.syncStateChanged,
  23046. /* excludesMetadataChanges= */ true, snap.hasCachedResults);
  23047. }
  23048. let raisedEvent = false;
  23049. if (!this.raisedInitialEvent) {
  23050. if (this.shouldRaiseInitialEvent(snap, this.onlineState)) {
  23051. this.raiseInitialEvent(snap);
  23052. raisedEvent = true;
  23053. }
  23054. }
  23055. else if (this.shouldRaiseEvent(snap)) {
  23056. this.queryObserver.next(snap);
  23057. raisedEvent = true;
  23058. }
  23059. this.snap = snap;
  23060. return raisedEvent;
  23061. }
  23062. onError(error) {
  23063. this.queryObserver.error(error);
  23064. }
  23065. /** Returns whether a snapshot was raised. */
  23066. applyOnlineStateChange(onlineState) {
  23067. this.onlineState = onlineState;
  23068. let raisedEvent = false;
  23069. if (this.snap &&
  23070. !this.raisedInitialEvent &&
  23071. this.shouldRaiseInitialEvent(this.snap, onlineState)) {
  23072. this.raiseInitialEvent(this.snap);
  23073. raisedEvent = true;
  23074. }
  23075. return raisedEvent;
  23076. }
  23077. shouldRaiseInitialEvent(snap, onlineState) {
  23078. // Always raise the first event when we're synced
  23079. if (!snap.fromCache) {
  23080. return true;
  23081. }
  23082. // NOTE: We consider OnlineState.Unknown as online (it should become Offline
  23083. // or Online if we wait long enough).
  23084. const maybeOnline = onlineState !== "Offline" /* OnlineState.Offline */;
  23085. // Don't raise the event if we're online, aren't synced yet (checked
  23086. // above) and are waiting for a sync.
  23087. if (this.options.waitForSyncWhenOnline && maybeOnline) {
  23088. return false;
  23089. }
  23090. // Raise data from cache if we have any documents, have cached results before,
  23091. // or we are offline.
  23092. return (!snap.docs.isEmpty() ||
  23093. snap.hasCachedResults ||
  23094. onlineState === "Offline" /* OnlineState.Offline */);
  23095. }
  23096. shouldRaiseEvent(snap) {
  23097. // We don't need to handle includeDocumentMetadataChanges here because
  23098. // the Metadata only changes have already been stripped out if needed.
  23099. // At this point the only changes we will see are the ones we should
  23100. // propagate.
  23101. if (snap.docChanges.length > 0) {
  23102. return true;
  23103. }
  23104. const hasPendingWritesChanged = this.snap && this.snap.hasPendingWrites !== snap.hasPendingWrites;
  23105. if (snap.syncStateChanged || hasPendingWritesChanged) {
  23106. return this.options.includeMetadataChanges === true;
  23107. }
  23108. // Generally we should have hit one of the cases above, but it's possible
  23109. // to get here if there were only metadata docChanges and they got
  23110. // stripped out.
  23111. return false;
  23112. }
  23113. raiseInitialEvent(snap) {
  23114. snap = ViewSnapshot.fromInitialDocuments(snap.query, snap.docs, snap.mutatedKeys, snap.fromCache, snap.hasCachedResults);
  23115. this.raisedInitialEvent = true;
  23116. this.queryObserver.next(snap);
  23117. }
  23118. }
  23119. /**
  23120. * @license
  23121. * Copyright 2017 Google LLC
  23122. *
  23123. * Licensed under the Apache License, Version 2.0 (the "License");
  23124. * you may not use this file except in compliance with the License.
  23125. * You may obtain a copy of the License at
  23126. *
  23127. * http://www.apache.org/licenses/LICENSE-2.0
  23128. *
  23129. * Unless required by applicable law or agreed to in writing, software
  23130. * distributed under the License is distributed on an "AS IS" BASIS,
  23131. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23132. * See the License for the specific language governing permissions and
  23133. * limitations under the License.
  23134. */
  23135. /**
  23136. * A set of changes to what documents are currently in view and out of view for
  23137. * a given query. These changes are sent to the LocalStore by the View (via
  23138. * the SyncEngine) and are used to pin / unpin documents as appropriate.
  23139. */
  23140. class LocalViewChanges {
  23141. constructor(targetId, fromCache, addedKeys, removedKeys) {
  23142. this.targetId = targetId;
  23143. this.fromCache = fromCache;
  23144. this.addedKeys = addedKeys;
  23145. this.removedKeys = removedKeys;
  23146. }
  23147. static fromSnapshot(targetId, viewSnapshot) {
  23148. let addedKeys = documentKeySet();
  23149. let removedKeys = documentKeySet();
  23150. for (const docChange of viewSnapshot.docChanges) {
  23151. switch (docChange.type) {
  23152. case 0 /* ChangeType.Added */:
  23153. addedKeys = addedKeys.add(docChange.doc.key);
  23154. break;
  23155. case 1 /* ChangeType.Removed */:
  23156. removedKeys = removedKeys.add(docChange.doc.key);
  23157. break;
  23158. // do nothing
  23159. }
  23160. }
  23161. return new LocalViewChanges(targetId, viewSnapshot.fromCache, addedKeys, removedKeys);
  23162. }
  23163. }
  23164. /**
  23165. * @license
  23166. * Copyright 2020 Google LLC
  23167. *
  23168. * Licensed under the Apache License, Version 2.0 (the "License");
  23169. * you may not use this file except in compliance with the License.
  23170. * You may obtain a copy of the License at
  23171. *
  23172. * http://www.apache.org/licenses/LICENSE-2.0
  23173. *
  23174. * Unless required by applicable law or agreed to in writing, software
  23175. * distributed under the License is distributed on an "AS IS" BASIS,
  23176. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23177. * See the License for the specific language governing permissions and
  23178. * limitations under the License.
  23179. */
  23180. /**
  23181. * Helper to convert objects from bundles to model objects in the SDK.
  23182. */
  23183. class BundleConverterImpl {
  23184. constructor(serializer) {
  23185. this.serializer = serializer;
  23186. }
  23187. toDocumentKey(name) {
  23188. return fromName(this.serializer, name);
  23189. }
  23190. /**
  23191. * Converts a BundleDocument to a MutableDocument.
  23192. */
  23193. toMutableDocument(bundledDoc) {
  23194. if (bundledDoc.metadata.exists) {
  23195. return fromDocument(this.serializer, bundledDoc.document, false);
  23196. }
  23197. else {
  23198. return MutableDocument.newNoDocument(this.toDocumentKey(bundledDoc.metadata.name), this.toSnapshotVersion(bundledDoc.metadata.readTime));
  23199. }
  23200. }
  23201. toSnapshotVersion(time) {
  23202. return fromVersion(time);
  23203. }
  23204. }
  23205. /**
  23206. * A class to process the elements from a bundle, load them into local
  23207. * storage and provide progress update while loading.
  23208. */
  23209. class BundleLoader {
  23210. constructor(bundleMetadata, localStore, serializer) {
  23211. this.bundleMetadata = bundleMetadata;
  23212. this.localStore = localStore;
  23213. this.serializer = serializer;
  23214. /** Batched queries to be saved into storage */
  23215. this.queries = [];
  23216. /** Batched documents to be saved into storage */
  23217. this.documents = [];
  23218. /** The collection groups affected by this bundle. */
  23219. this.collectionGroups = new Set();
  23220. this.progress = bundleInitialProgress(bundleMetadata);
  23221. }
  23222. /**
  23223. * Adds an element from the bundle to the loader.
  23224. *
  23225. * Returns a new progress if adding the element leads to a new progress,
  23226. * otherwise returns null.
  23227. */
  23228. addSizedElement(element) {
  23229. this.progress.bytesLoaded += element.byteLength;
  23230. let documentsLoaded = this.progress.documentsLoaded;
  23231. if (element.payload.namedQuery) {
  23232. this.queries.push(element.payload.namedQuery);
  23233. }
  23234. else if (element.payload.documentMetadata) {
  23235. this.documents.push({ metadata: element.payload.documentMetadata });
  23236. if (!element.payload.documentMetadata.exists) {
  23237. ++documentsLoaded;
  23238. }
  23239. const path = ResourcePath.fromString(element.payload.documentMetadata.name);
  23240. this.collectionGroups.add(path.get(path.length - 2));
  23241. }
  23242. else if (element.payload.document) {
  23243. this.documents[this.documents.length - 1].document =
  23244. element.payload.document;
  23245. ++documentsLoaded;
  23246. }
  23247. if (documentsLoaded !== this.progress.documentsLoaded) {
  23248. this.progress.documentsLoaded = documentsLoaded;
  23249. return Object.assign({}, this.progress);
  23250. }
  23251. return null;
  23252. }
  23253. getQueryDocumentMapping(documents) {
  23254. const queryDocumentMap = new Map();
  23255. const bundleConverter = new BundleConverterImpl(this.serializer);
  23256. for (const bundleDoc of documents) {
  23257. if (bundleDoc.metadata.queries) {
  23258. const documentKey = bundleConverter.toDocumentKey(bundleDoc.metadata.name);
  23259. for (const queryName of bundleDoc.metadata.queries) {
  23260. const documentKeys = (queryDocumentMap.get(queryName) || documentKeySet()).add(documentKey);
  23261. queryDocumentMap.set(queryName, documentKeys);
  23262. }
  23263. }
  23264. }
  23265. return queryDocumentMap;
  23266. }
  23267. /**
  23268. * Update the progress to 'Success' and return the updated progress.
  23269. */
  23270. async complete() {
  23271. const changedDocs = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
  23272. const queryDocumentMap = this.getQueryDocumentMapping(this.documents);
  23273. for (const q of this.queries) {
  23274. await localStoreSaveNamedQuery(this.localStore, q, queryDocumentMap.get(q.name));
  23275. }
  23276. this.progress.taskState = 'Success';
  23277. return {
  23278. progress: this.progress,
  23279. changedCollectionGroups: this.collectionGroups,
  23280. changedDocs
  23281. };
  23282. }
  23283. }
  23284. /**
  23285. * Returns a `LoadBundleTaskProgress` representing the initial progress of
  23286. * loading a bundle.
  23287. */
  23288. function bundleInitialProgress(metadata) {
  23289. return {
  23290. taskState: 'Running',
  23291. documentsLoaded: 0,
  23292. bytesLoaded: 0,
  23293. totalDocuments: metadata.totalDocuments,
  23294. totalBytes: metadata.totalBytes
  23295. };
  23296. }
  23297. /**
  23298. * Returns a `LoadBundleTaskProgress` representing the progress that the loading
  23299. * has succeeded.
  23300. */
  23301. function bundleSuccessProgress(metadata) {
  23302. return {
  23303. taskState: 'Success',
  23304. documentsLoaded: metadata.totalDocuments,
  23305. bytesLoaded: metadata.totalBytes,
  23306. totalDocuments: metadata.totalDocuments,
  23307. totalBytes: metadata.totalBytes
  23308. };
  23309. }
  23310. /**
  23311. * @license
  23312. * Copyright 2017 Google LLC
  23313. *
  23314. * Licensed under the Apache License, Version 2.0 (the "License");
  23315. * you may not use this file except in compliance with the License.
  23316. * You may obtain a copy of the License at
  23317. *
  23318. * http://www.apache.org/licenses/LICENSE-2.0
  23319. *
  23320. * Unless required by applicable law or agreed to in writing, software
  23321. * distributed under the License is distributed on an "AS IS" BASIS,
  23322. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23323. * See the License for the specific language governing permissions and
  23324. * limitations under the License.
  23325. */
  23326. class AddedLimboDocument {
  23327. constructor(key) {
  23328. this.key = key;
  23329. }
  23330. }
  23331. class RemovedLimboDocument {
  23332. constructor(key) {
  23333. this.key = key;
  23334. }
  23335. }
  23336. /**
  23337. * View is responsible for computing the final merged truth of what docs are in
  23338. * a query. It gets notified of local and remote changes to docs, and applies
  23339. * the query filters and limits to determine the most correct possible results.
  23340. */
  23341. class View {
  23342. constructor(query,
  23343. /** Documents included in the remote target */
  23344. _syncedDocuments) {
  23345. this.query = query;
  23346. this._syncedDocuments = _syncedDocuments;
  23347. this.syncState = null;
  23348. this.hasCachedResults = false;
  23349. /**
  23350. * A flag whether the view is current with the backend. A view is considered
  23351. * current after it has seen the current flag from the backend and did not
  23352. * lose consistency within the watch stream (e.g. because of an existence
  23353. * filter mismatch).
  23354. */
  23355. this.current = false;
  23356. /** Documents in the view but not in the remote target */
  23357. this.limboDocuments = documentKeySet();
  23358. /** Document Keys that have local changes */
  23359. this.mutatedKeys = documentKeySet();
  23360. this.docComparator = newQueryComparator(query);
  23361. this.documentSet = new DocumentSet(this.docComparator);
  23362. }
  23363. /**
  23364. * The set of remote documents that the server has told us belongs to the target associated with
  23365. * this view.
  23366. */
  23367. get syncedDocuments() {
  23368. return this._syncedDocuments;
  23369. }
  23370. /**
  23371. * Iterates over a set of doc changes, applies the query limit, and computes
  23372. * what the new results should be, what the changes were, and whether we may
  23373. * need to go back to the local cache for more results. Does not make any
  23374. * changes to the view.
  23375. * @param docChanges - The doc changes to apply to this view.
  23376. * @param previousChanges - If this is being called with a refill, then start
  23377. * with this set of docs and changes instead of the current view.
  23378. * @returns a new set of docs, changes, and refill flag.
  23379. */
  23380. computeDocChanges(docChanges, previousChanges) {
  23381. const changeSet = previousChanges
  23382. ? previousChanges.changeSet
  23383. : new DocumentChangeSet();
  23384. const oldDocumentSet = previousChanges
  23385. ? previousChanges.documentSet
  23386. : this.documentSet;
  23387. let newMutatedKeys = previousChanges
  23388. ? previousChanges.mutatedKeys
  23389. : this.mutatedKeys;
  23390. let newDocumentSet = oldDocumentSet;
  23391. let needsRefill = false;
  23392. // Track the last doc in a (full) limit. This is necessary, because some
  23393. // update (a delete, or an update moving a doc past the old limit) might
  23394. // mean there is some other document in the local cache that either should
  23395. // come (1) between the old last limit doc and the new last document, in the
  23396. // case of updates, or (2) after the new last document, in the case of
  23397. // deletes. So we keep this doc at the old limit to compare the updates to.
  23398. //
  23399. // Note that this should never get used in a refill (when previousChanges is
  23400. // set), because there will only be adds -- no deletes or updates.
  23401. const lastDocInLimit = this.query.limitType === "F" /* LimitType.First */ &&
  23402. oldDocumentSet.size === this.query.limit
  23403. ? oldDocumentSet.last()
  23404. : null;
  23405. const firstDocInLimit = this.query.limitType === "L" /* LimitType.Last */ &&
  23406. oldDocumentSet.size === this.query.limit
  23407. ? oldDocumentSet.first()
  23408. : null;
  23409. docChanges.inorderTraversal((key, entry) => {
  23410. const oldDoc = oldDocumentSet.get(key);
  23411. const newDoc = queryMatches(this.query, entry) ? entry : null;
  23412. const oldDocHadPendingMutations = oldDoc
  23413. ? this.mutatedKeys.has(oldDoc.key)
  23414. : false;
  23415. const newDocHasPendingMutations = newDoc
  23416. ? newDoc.hasLocalMutations ||
  23417. // We only consider committed mutations for documents that were
  23418. // mutated during the lifetime of the view.
  23419. (this.mutatedKeys.has(newDoc.key) && newDoc.hasCommittedMutations)
  23420. : false;
  23421. let changeApplied = false;
  23422. // Calculate change
  23423. if (oldDoc && newDoc) {
  23424. const docsEqual = oldDoc.data.isEqual(newDoc.data);
  23425. if (!docsEqual) {
  23426. if (!this.shouldWaitForSyncedDocument(oldDoc, newDoc)) {
  23427. changeSet.track({
  23428. type: 2 /* ChangeType.Modified */,
  23429. doc: newDoc
  23430. });
  23431. changeApplied = true;
  23432. if ((lastDocInLimit &&
  23433. this.docComparator(newDoc, lastDocInLimit) > 0) ||
  23434. (firstDocInLimit &&
  23435. this.docComparator(newDoc, firstDocInLimit) < 0)) {
  23436. // This doc moved from inside the limit to outside the limit.
  23437. // That means there may be some other doc in the local cache
  23438. // that should be included instead.
  23439. needsRefill = true;
  23440. }
  23441. }
  23442. }
  23443. else if (oldDocHadPendingMutations !== newDocHasPendingMutations) {
  23444. changeSet.track({ type: 3 /* ChangeType.Metadata */, doc: newDoc });
  23445. changeApplied = true;
  23446. }
  23447. }
  23448. else if (!oldDoc && newDoc) {
  23449. changeSet.track({ type: 0 /* ChangeType.Added */, doc: newDoc });
  23450. changeApplied = true;
  23451. }
  23452. else if (oldDoc && !newDoc) {
  23453. changeSet.track({ type: 1 /* ChangeType.Removed */, doc: oldDoc });
  23454. changeApplied = true;
  23455. if (lastDocInLimit || firstDocInLimit) {
  23456. // A doc was removed from a full limit query. We'll need to
  23457. // requery from the local cache to see if we know about some other
  23458. // doc that should be in the results.
  23459. needsRefill = true;
  23460. }
  23461. }
  23462. if (changeApplied) {
  23463. if (newDoc) {
  23464. newDocumentSet = newDocumentSet.add(newDoc);
  23465. if (newDocHasPendingMutations) {
  23466. newMutatedKeys = newMutatedKeys.add(key);
  23467. }
  23468. else {
  23469. newMutatedKeys = newMutatedKeys.delete(key);
  23470. }
  23471. }
  23472. else {
  23473. newDocumentSet = newDocumentSet.delete(key);
  23474. newMutatedKeys = newMutatedKeys.delete(key);
  23475. }
  23476. }
  23477. });
  23478. // Drop documents out to meet limit/limitToLast requirement.
  23479. if (this.query.limit !== null) {
  23480. while (newDocumentSet.size > this.query.limit) {
  23481. const oldDoc = this.query.limitType === "F" /* LimitType.First */
  23482. ? newDocumentSet.last()
  23483. : newDocumentSet.first();
  23484. newDocumentSet = newDocumentSet.delete(oldDoc.key);
  23485. newMutatedKeys = newMutatedKeys.delete(oldDoc.key);
  23486. changeSet.track({ type: 1 /* ChangeType.Removed */, doc: oldDoc });
  23487. }
  23488. }
  23489. return {
  23490. documentSet: newDocumentSet,
  23491. changeSet,
  23492. needsRefill,
  23493. mutatedKeys: newMutatedKeys
  23494. };
  23495. }
  23496. shouldWaitForSyncedDocument(oldDoc, newDoc) {
  23497. // We suppress the initial change event for documents that were modified as
  23498. // part of a write acknowledgment (e.g. when the value of a server transform
  23499. // is applied) as Watch will send us the same document again.
  23500. // By suppressing the event, we only raise two user visible events (one with
  23501. // `hasPendingWrites` and the final state of the document) instead of three
  23502. // (one with `hasPendingWrites`, the modified document with
  23503. // `hasPendingWrites` and the final state of the document).
  23504. return (oldDoc.hasLocalMutations &&
  23505. newDoc.hasCommittedMutations &&
  23506. !newDoc.hasLocalMutations);
  23507. }
  23508. /**
  23509. * Updates the view with the given ViewDocumentChanges and optionally updates
  23510. * limbo docs and sync state from the provided target change.
  23511. * @param docChanges - The set of changes to make to the view's docs.
  23512. * @param updateLimboDocuments - Whether to update limbo documents based on
  23513. * this change.
  23514. * @param targetChange - A target change to apply for computing limbo docs and
  23515. * sync state.
  23516. * @returns A new ViewChange with the given docs, changes, and sync state.
  23517. */
  23518. // PORTING NOTE: The iOS/Android clients always compute limbo document changes.
  23519. applyChanges(docChanges, updateLimboDocuments, targetChange) {
  23520. const oldDocs = this.documentSet;
  23521. this.documentSet = docChanges.documentSet;
  23522. this.mutatedKeys = docChanges.mutatedKeys;
  23523. // Sort changes based on type and query comparator
  23524. const changes = docChanges.changeSet.getChanges();
  23525. changes.sort((c1, c2) => {
  23526. return (compareChangeType(c1.type, c2.type) ||
  23527. this.docComparator(c1.doc, c2.doc));
  23528. });
  23529. this.applyTargetChange(targetChange);
  23530. const limboChanges = updateLimboDocuments
  23531. ? this.updateLimboDocuments()
  23532. : [];
  23533. const synced = this.limboDocuments.size === 0 && this.current;
  23534. const newSyncState = synced ? 1 /* SyncState.Synced */ : 0 /* SyncState.Local */;
  23535. const syncStateChanged = newSyncState !== this.syncState;
  23536. this.syncState = newSyncState;
  23537. if (changes.length === 0 && !syncStateChanged) {
  23538. // no changes
  23539. return { limboChanges };
  23540. }
  23541. else {
  23542. const snap = new ViewSnapshot(this.query, docChanges.documentSet, oldDocs, changes, docChanges.mutatedKeys, newSyncState === 0 /* SyncState.Local */, syncStateChanged,
  23543. /* excludesMetadataChanges= */ false, targetChange
  23544. ? targetChange.resumeToken.approximateByteSize() > 0
  23545. : false);
  23546. return {
  23547. snapshot: snap,
  23548. limboChanges
  23549. };
  23550. }
  23551. }
  23552. /**
  23553. * Applies an OnlineState change to the view, potentially generating a
  23554. * ViewChange if the view's syncState changes as a result.
  23555. */
  23556. applyOnlineStateChange(onlineState) {
  23557. if (this.current && onlineState === "Offline" /* OnlineState.Offline */) {
  23558. // If we're offline, set `current` to false and then call applyChanges()
  23559. // to refresh our syncState and generate a ViewChange as appropriate. We
  23560. // are guaranteed to get a new TargetChange that sets `current` back to
  23561. // true once the client is back online.
  23562. this.current = false;
  23563. return this.applyChanges({
  23564. documentSet: this.documentSet,
  23565. changeSet: new DocumentChangeSet(),
  23566. mutatedKeys: this.mutatedKeys,
  23567. needsRefill: false
  23568. },
  23569. /* updateLimboDocuments= */ false);
  23570. }
  23571. else {
  23572. // No effect, just return a no-op ViewChange.
  23573. return { limboChanges: [] };
  23574. }
  23575. }
  23576. /**
  23577. * Returns whether the doc for the given key should be in limbo.
  23578. */
  23579. shouldBeInLimbo(key) {
  23580. // If the remote end says it's part of this query, it's not in limbo.
  23581. if (this._syncedDocuments.has(key)) {
  23582. return false;
  23583. }
  23584. // The local store doesn't think it's a result, so it shouldn't be in limbo.
  23585. if (!this.documentSet.has(key)) {
  23586. return false;
  23587. }
  23588. // If there are local changes to the doc, they might explain why the server
  23589. // doesn't know that it's part of the query. So don't put it in limbo.
  23590. // TODO(klimt): Ideally, we would only consider changes that might actually
  23591. // affect this specific query.
  23592. if (this.documentSet.get(key).hasLocalMutations) {
  23593. return false;
  23594. }
  23595. // Everything else is in limbo.
  23596. return true;
  23597. }
  23598. /**
  23599. * Updates syncedDocuments, current, and limbo docs based on the given change.
  23600. * Returns the list of changes to which docs are in limbo.
  23601. */
  23602. applyTargetChange(targetChange) {
  23603. if (targetChange) {
  23604. targetChange.addedDocuments.forEach(key => (this._syncedDocuments = this._syncedDocuments.add(key)));
  23605. targetChange.modifiedDocuments.forEach(key => {
  23606. });
  23607. targetChange.removedDocuments.forEach(key => (this._syncedDocuments = this._syncedDocuments.delete(key)));
  23608. this.current = targetChange.current;
  23609. }
  23610. }
  23611. updateLimboDocuments() {
  23612. // We can only determine limbo documents when we're in-sync with the server.
  23613. if (!this.current) {
  23614. return [];
  23615. }
  23616. // TODO(klimt): Do this incrementally so that it's not quadratic when
  23617. // updating many documents.
  23618. const oldLimboDocuments = this.limboDocuments;
  23619. this.limboDocuments = documentKeySet();
  23620. this.documentSet.forEach(doc => {
  23621. if (this.shouldBeInLimbo(doc.key)) {
  23622. this.limboDocuments = this.limboDocuments.add(doc.key);
  23623. }
  23624. });
  23625. // Diff the new limbo docs with the old limbo docs.
  23626. const changes = [];
  23627. oldLimboDocuments.forEach(key => {
  23628. if (!this.limboDocuments.has(key)) {
  23629. changes.push(new RemovedLimboDocument(key));
  23630. }
  23631. });
  23632. this.limboDocuments.forEach(key => {
  23633. if (!oldLimboDocuments.has(key)) {
  23634. changes.push(new AddedLimboDocument(key));
  23635. }
  23636. });
  23637. return changes;
  23638. }
  23639. /**
  23640. * Update the in-memory state of the current view with the state read from
  23641. * persistence.
  23642. *
  23643. * We update the query view whenever a client's primary status changes:
  23644. * - When a client transitions from primary to secondary, it can miss
  23645. * LocalStorage updates and its query views may temporarily not be
  23646. * synchronized with the state on disk.
  23647. * - For secondary to primary transitions, the client needs to update the list
  23648. * of `syncedDocuments` since secondary clients update their query views
  23649. * based purely on synthesized RemoteEvents.
  23650. *
  23651. * @param queryResult.documents - The documents that match the query according
  23652. * to the LocalStore.
  23653. * @param queryResult.remoteKeys - The keys of the documents that match the
  23654. * query according to the backend.
  23655. *
  23656. * @returns The ViewChange that resulted from this synchronization.
  23657. */
  23658. // PORTING NOTE: Multi-tab only.
  23659. synchronizeWithPersistedState(queryResult) {
  23660. this._syncedDocuments = queryResult.remoteKeys;
  23661. this.limboDocuments = documentKeySet();
  23662. const docChanges = this.computeDocChanges(queryResult.documents);
  23663. return this.applyChanges(docChanges, /*updateLimboDocuments=*/ true);
  23664. }
  23665. /**
  23666. * Returns a view snapshot as if this query was just listened to. Contains
  23667. * a document add for every existing document and the `fromCache` and
  23668. * `hasPendingWrites` status of the already established view.
  23669. */
  23670. // PORTING NOTE: Multi-tab only.
  23671. computeInitialSnapshot() {
  23672. return ViewSnapshot.fromInitialDocuments(this.query, this.documentSet, this.mutatedKeys, this.syncState === 0 /* SyncState.Local */, this.hasCachedResults);
  23673. }
  23674. }
  23675. function compareChangeType(c1, c2) {
  23676. const order = (change) => {
  23677. switch (change) {
  23678. case 0 /* ChangeType.Added */:
  23679. return 1;
  23680. case 2 /* ChangeType.Modified */:
  23681. return 2;
  23682. case 3 /* ChangeType.Metadata */:
  23683. // A metadata change is converted to a modified change at the public
  23684. // api layer. Since we sort by document key and then change type,
  23685. // metadata and modified changes must be sorted equivalently.
  23686. return 2;
  23687. case 1 /* ChangeType.Removed */:
  23688. return 0;
  23689. default:
  23690. return fail();
  23691. }
  23692. };
  23693. return order(c1) - order(c2);
  23694. }
  23695. /**
  23696. * @license
  23697. * Copyright 2020 Google LLC
  23698. *
  23699. * Licensed under the Apache License, Version 2.0 (the "License");
  23700. * you may not use this file except in compliance with the License.
  23701. * You may obtain a copy of the License at
  23702. *
  23703. * http://www.apache.org/licenses/LICENSE-2.0
  23704. *
  23705. * Unless required by applicable law or agreed to in writing, software
  23706. * distributed under the License is distributed on an "AS IS" BASIS,
  23707. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23708. * See the License for the specific language governing permissions and
  23709. * limitations under the License.
  23710. */
  23711. const LOG_TAG$3 = 'SyncEngine';
  23712. /**
  23713. * QueryView contains all of the data that SyncEngine needs to keep track of for
  23714. * a particular query.
  23715. */
  23716. class QueryView {
  23717. constructor(
  23718. /**
  23719. * The query itself.
  23720. */
  23721. query,
  23722. /**
  23723. * The target number created by the client that is used in the watch
  23724. * stream to identify this query.
  23725. */
  23726. targetId,
  23727. /**
  23728. * The view is responsible for computing the final merged truth of what
  23729. * docs are in the query. It gets notified of local and remote changes,
  23730. * and applies the query filters and limits to determine the most correct
  23731. * possible results.
  23732. */
  23733. view) {
  23734. this.query = query;
  23735. this.targetId = targetId;
  23736. this.view = view;
  23737. }
  23738. }
  23739. /** Tracks a limbo resolution. */
  23740. class LimboResolution {
  23741. constructor(key) {
  23742. this.key = key;
  23743. /**
  23744. * Set to true once we've received a document. This is used in
  23745. * getRemoteKeysForTarget() and ultimately used by WatchChangeAggregator to
  23746. * decide whether it needs to manufacture a delete event for the target once
  23747. * the target is CURRENT.
  23748. */
  23749. this.receivedDocument = false;
  23750. }
  23751. }
  23752. /**
  23753. * An implementation of `SyncEngine` coordinating with other parts of SDK.
  23754. *
  23755. * The parts of SyncEngine that act as a callback to RemoteStore need to be
  23756. * registered individually. This is done in `syncEngineWrite()` and
  23757. * `syncEngineListen()` (as well as `applyPrimaryState()`) as these methods
  23758. * serve as entry points to RemoteStore's functionality.
  23759. *
  23760. * Note: some field defined in this class might have public access level, but
  23761. * the class is not exported so they are only accessible from this module.
  23762. * This is useful to implement optional features (like bundles) in free
  23763. * functions, such that they are tree-shakeable.
  23764. */
  23765. class SyncEngineImpl {
  23766. constructor(localStore, remoteStore, eventManager,
  23767. // PORTING NOTE: Manages state synchronization in multi-tab environments.
  23768. sharedClientState, currentUser, maxConcurrentLimboResolutions) {
  23769. this.localStore = localStore;
  23770. this.remoteStore = remoteStore;
  23771. this.eventManager = eventManager;
  23772. this.sharedClientState = sharedClientState;
  23773. this.currentUser = currentUser;
  23774. this.maxConcurrentLimboResolutions = maxConcurrentLimboResolutions;
  23775. this.syncEngineListener = {};
  23776. this.queryViewsByQuery = new ObjectMap(q => canonifyQuery(q), queryEquals);
  23777. this.queriesByTarget = new Map();
  23778. /**
  23779. * The keys of documents that are in limbo for which we haven't yet started a
  23780. * limbo resolution query. The strings in this set are the result of calling
  23781. * `key.path.canonicalString()` where `key` is a `DocumentKey` object.
  23782. *
  23783. * The `Set` type was chosen because it provides efficient lookup and removal
  23784. * of arbitrary elements and it also maintains insertion order, providing the
  23785. * desired queue-like FIFO semantics.
  23786. */
  23787. this.enqueuedLimboResolutions = new Set();
  23788. /**
  23789. * Keeps track of the target ID for each document that is in limbo with an
  23790. * active target.
  23791. */
  23792. this.activeLimboTargetsByKey = new SortedMap(DocumentKey.comparator);
  23793. /**
  23794. * Keeps track of the information about an active limbo resolution for each
  23795. * active target ID that was started for the purpose of limbo resolution.
  23796. */
  23797. this.activeLimboResolutionsByTarget = new Map();
  23798. this.limboDocumentRefs = new ReferenceSet();
  23799. /** Stores user completion handlers, indexed by User and BatchId. */
  23800. this.mutationUserCallbacks = {};
  23801. /** Stores user callbacks waiting for all pending writes to be acknowledged. */
  23802. this.pendingWritesCallbacks = new Map();
  23803. this.limboTargetIdGenerator = TargetIdGenerator.forSyncEngine();
  23804. this.onlineState = "Unknown" /* OnlineState.Unknown */;
  23805. // The primary state is set to `true` or `false` immediately after Firestore
  23806. // startup. In the interim, a client should only be considered primary if
  23807. // `isPrimary` is true.
  23808. this._isPrimaryClient = undefined;
  23809. }
  23810. get isPrimaryClient() {
  23811. return this._isPrimaryClient === true;
  23812. }
  23813. }
  23814. function newSyncEngine(localStore, remoteStore, eventManager,
  23815. // PORTING NOTE: Manages state synchronization in multi-tab environments.
  23816. sharedClientState, currentUser, maxConcurrentLimboResolutions, isPrimary) {
  23817. const syncEngine = new SyncEngineImpl(localStore, remoteStore, eventManager, sharedClientState, currentUser, maxConcurrentLimboResolutions);
  23818. if (isPrimary) {
  23819. syncEngine._isPrimaryClient = true;
  23820. }
  23821. return syncEngine;
  23822. }
  23823. /**
  23824. * Initiates the new listen, resolves promise when listen enqueued to the
  23825. * server. All the subsequent view snapshots or errors are sent to the
  23826. * subscribed handlers. Returns the initial snapshot.
  23827. */
  23828. async function syncEngineListen(syncEngine, query) {
  23829. const syncEngineImpl = ensureWatchCallbacks(syncEngine);
  23830. let targetId;
  23831. let viewSnapshot;
  23832. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  23833. if (queryView) {
  23834. // PORTING NOTE: With Multi-Tab Web, it is possible that a query view
  23835. // already exists when EventManager calls us for the first time. This
  23836. // happens when the primary tab is already listening to this query on
  23837. // behalf of another tab and the user of the primary also starts listening
  23838. // to the query. EventManager will not have an assigned target ID in this
  23839. // case and calls `listen` to obtain this ID.
  23840. targetId = queryView.targetId;
  23841. syncEngineImpl.sharedClientState.addLocalQueryTarget(targetId);
  23842. viewSnapshot = queryView.view.computeInitialSnapshot();
  23843. }
  23844. else {
  23845. const targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, queryToTarget(query));
  23846. if (syncEngineImpl.isPrimaryClient) {
  23847. remoteStoreListen(syncEngineImpl.remoteStore, targetData);
  23848. }
  23849. const status = syncEngineImpl.sharedClientState.addLocalQueryTarget(targetData.targetId);
  23850. targetId = targetData.targetId;
  23851. viewSnapshot = await initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, status === 'current', targetData.resumeToken);
  23852. }
  23853. return viewSnapshot;
  23854. }
  23855. /**
  23856. * Registers a view for a previously unknown query and computes its initial
  23857. * snapshot.
  23858. */
  23859. async function initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, current, resumeToken) {
  23860. // PORTING NOTE: On Web only, we inject the code that registers new Limbo
  23861. // targets based on view changes. This allows us to only depend on Limbo
  23862. // changes when user code includes queries.
  23863. syncEngineImpl.applyDocChanges = (queryView, changes, remoteEvent) => applyDocChanges(syncEngineImpl, queryView, changes, remoteEvent);
  23864. const queryResult = await localStoreExecuteQuery(syncEngineImpl.localStore, query,
  23865. /* usePreviousResults= */ true);
  23866. const view = new View(query, queryResult.remoteKeys);
  23867. const viewDocChanges = view.computeDocChanges(queryResult.documents);
  23868. const synthesizedTargetChange = TargetChange.createSynthesizedTargetChangeForCurrentChange(targetId, current && syncEngineImpl.onlineState !== "Offline" /* OnlineState.Offline */, resumeToken);
  23869. const viewChange = view.applyChanges(viewDocChanges,
  23870. /* updateLimboDocuments= */ syncEngineImpl.isPrimaryClient, synthesizedTargetChange);
  23871. updateTrackedLimbos(syncEngineImpl, targetId, viewChange.limboChanges);
  23872. const data = new QueryView(query, targetId, view);
  23873. syncEngineImpl.queryViewsByQuery.set(query, data);
  23874. if (syncEngineImpl.queriesByTarget.has(targetId)) {
  23875. syncEngineImpl.queriesByTarget.get(targetId).push(query);
  23876. }
  23877. else {
  23878. syncEngineImpl.queriesByTarget.set(targetId, [query]);
  23879. }
  23880. return viewChange.snapshot;
  23881. }
  23882. /** Stops listening to the query. */
  23883. async function syncEngineUnlisten(syncEngine, query) {
  23884. const syncEngineImpl = debugCast(syncEngine);
  23885. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  23886. // Only clean up the query view and target if this is the only query mapped
  23887. // to the target.
  23888. const queries = syncEngineImpl.queriesByTarget.get(queryView.targetId);
  23889. if (queries.length > 1) {
  23890. syncEngineImpl.queriesByTarget.set(queryView.targetId, queries.filter(q => !queryEquals(q, query)));
  23891. syncEngineImpl.queryViewsByQuery.delete(query);
  23892. return;
  23893. }
  23894. // No other queries are mapped to the target, clean up the query and the target.
  23895. if (syncEngineImpl.isPrimaryClient) {
  23896. // We need to remove the local query target first to allow us to verify
  23897. // whether any other client is still interested in this target.
  23898. syncEngineImpl.sharedClientState.removeLocalQueryTarget(queryView.targetId);
  23899. const targetRemainsActive = syncEngineImpl.sharedClientState.isActiveQueryTarget(queryView.targetId);
  23900. if (!targetRemainsActive) {
  23901. await localStoreReleaseTarget(syncEngineImpl.localStore, queryView.targetId,
  23902. /*keepPersistedTargetData=*/ false)
  23903. .then(() => {
  23904. syncEngineImpl.sharedClientState.clearQueryState(queryView.targetId);
  23905. remoteStoreUnlisten(syncEngineImpl.remoteStore, queryView.targetId);
  23906. removeAndCleanupTarget(syncEngineImpl, queryView.targetId);
  23907. })
  23908. .catch(ignoreIfPrimaryLeaseLoss);
  23909. }
  23910. }
  23911. else {
  23912. removeAndCleanupTarget(syncEngineImpl, queryView.targetId);
  23913. await localStoreReleaseTarget(syncEngineImpl.localStore, queryView.targetId,
  23914. /*keepPersistedTargetData=*/ true);
  23915. }
  23916. }
  23917. /**
  23918. * Initiates the write of local mutation batch which involves adding the
  23919. * writes to the mutation queue, notifying the remote store about new
  23920. * mutations and raising events for any changes this write caused.
  23921. *
  23922. * The promise returned by this call is resolved when the above steps
  23923. * have completed, *not* when the write was acked by the backend. The
  23924. * userCallback is resolved once the write was acked/rejected by the
  23925. * backend (or failed locally for any other reason).
  23926. */
  23927. async function syncEngineWrite(syncEngine, batch, userCallback) {
  23928. const syncEngineImpl = syncEngineEnsureWriteCallbacks(syncEngine);
  23929. try {
  23930. const result = await localStoreWriteLocally(syncEngineImpl.localStore, batch);
  23931. syncEngineImpl.sharedClientState.addPendingMutation(result.batchId);
  23932. addMutationCallback(syncEngineImpl, result.batchId, userCallback);
  23933. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, result.changes);
  23934. await fillWritePipeline(syncEngineImpl.remoteStore);
  23935. }
  23936. catch (e) {
  23937. // If we can't persist the mutation, we reject the user callback and
  23938. // don't send the mutation. The user can then retry the write.
  23939. const error = wrapInUserErrorIfRecoverable(e, `Failed to persist write`);
  23940. userCallback.reject(error);
  23941. }
  23942. }
  23943. /**
  23944. * Applies one remote event to the sync engine, notifying any views of the
  23945. * changes, and releasing any pending mutation batches that would become
  23946. * visible because of the snapshot version the remote event contains.
  23947. */
  23948. async function syncEngineApplyRemoteEvent(syncEngine, remoteEvent) {
  23949. const syncEngineImpl = debugCast(syncEngine);
  23950. try {
  23951. const changes = await localStoreApplyRemoteEventToLocalCache(syncEngineImpl.localStore, remoteEvent);
  23952. // Update `receivedDocument` as appropriate for any limbo targets.
  23953. remoteEvent.targetChanges.forEach((targetChange, targetId) => {
  23954. const limboResolution = syncEngineImpl.activeLimboResolutionsByTarget.get(targetId);
  23955. if (limboResolution) {
  23956. // Since this is a limbo resolution lookup, it's for a single document
  23957. // and it could be added, modified, or removed, but not a combination.
  23958. hardAssert(targetChange.addedDocuments.size +
  23959. targetChange.modifiedDocuments.size +
  23960. targetChange.removedDocuments.size <=
  23961. 1);
  23962. if (targetChange.addedDocuments.size > 0) {
  23963. limboResolution.receivedDocument = true;
  23964. }
  23965. else if (targetChange.modifiedDocuments.size > 0) {
  23966. hardAssert(limboResolution.receivedDocument);
  23967. }
  23968. else if (targetChange.removedDocuments.size > 0) {
  23969. hardAssert(limboResolution.receivedDocument);
  23970. limboResolution.receivedDocument = false;
  23971. }
  23972. else {
  23973. // This was probably just a CURRENT targetChange or similar.
  23974. }
  23975. }
  23976. });
  23977. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, remoteEvent);
  23978. }
  23979. catch (error) {
  23980. await ignoreIfPrimaryLeaseLoss(error);
  23981. }
  23982. }
  23983. /**
  23984. * Applies an OnlineState change to the sync engine and notifies any views of
  23985. * the change.
  23986. */
  23987. function syncEngineApplyOnlineStateChange(syncEngine, onlineState, source) {
  23988. const syncEngineImpl = debugCast(syncEngine);
  23989. // If we are the secondary client, we explicitly ignore the remote store's
  23990. // online state (the local client may go offline, even though the primary
  23991. // tab remains online) and only apply the primary tab's online state from
  23992. // SharedClientState.
  23993. if ((syncEngineImpl.isPrimaryClient &&
  23994. source === 0 /* OnlineStateSource.RemoteStore */) ||
  23995. (!syncEngineImpl.isPrimaryClient &&
  23996. source === 1 /* OnlineStateSource.SharedClientState */)) {
  23997. const newViewSnapshots = [];
  23998. syncEngineImpl.queryViewsByQuery.forEach((query, queryView) => {
  23999. const viewChange = queryView.view.applyOnlineStateChange(onlineState);
  24000. if (viewChange.snapshot) {
  24001. newViewSnapshots.push(viewChange.snapshot);
  24002. }
  24003. });
  24004. eventManagerOnOnlineStateChange(syncEngineImpl.eventManager, onlineState);
  24005. if (newViewSnapshots.length) {
  24006. syncEngineImpl.syncEngineListener.onWatchChange(newViewSnapshots);
  24007. }
  24008. syncEngineImpl.onlineState = onlineState;
  24009. if (syncEngineImpl.isPrimaryClient) {
  24010. syncEngineImpl.sharedClientState.setOnlineState(onlineState);
  24011. }
  24012. }
  24013. }
  24014. /**
  24015. * Rejects the listen for the given targetID. This can be triggered by the
  24016. * backend for any active target.
  24017. *
  24018. * @param syncEngine - The sync engine implementation.
  24019. * @param targetId - The targetID corresponds to one previously initiated by the
  24020. * user as part of TargetData passed to listen() on RemoteStore.
  24021. * @param err - A description of the condition that has forced the rejection.
  24022. * Nearly always this will be an indication that the user is no longer
  24023. * authorized to see the data matching the target.
  24024. */
  24025. async function syncEngineRejectListen(syncEngine, targetId, err) {
  24026. const syncEngineImpl = debugCast(syncEngine);
  24027. // PORTING NOTE: Multi-tab only.
  24028. syncEngineImpl.sharedClientState.updateQueryState(targetId, 'rejected', err);
  24029. const limboResolution = syncEngineImpl.activeLimboResolutionsByTarget.get(targetId);
  24030. const limboKey = limboResolution && limboResolution.key;
  24031. if (limboKey) {
  24032. // TODO(klimt): We really only should do the following on permission
  24033. // denied errors, but we don't have the cause code here.
  24034. // It's a limbo doc. Create a synthetic event saying it was deleted.
  24035. // This is kind of a hack. Ideally, we would have a method in the local
  24036. // store to purge a document. However, it would be tricky to keep all of
  24037. // the local store's invariants with another method.
  24038. let documentUpdates = new SortedMap(DocumentKey.comparator);
  24039. // TODO(b/217189216): This limbo document should ideally have a read time,
  24040. // so that it is picked up by any read-time based scans. The backend,
  24041. // however, does not send a read time for target removals.
  24042. documentUpdates = documentUpdates.insert(limboKey, MutableDocument.newNoDocument(limboKey, SnapshotVersion.min()));
  24043. const resolvedLimboDocuments = documentKeySet().add(limboKey);
  24044. const event = new RemoteEvent(SnapshotVersion.min(),
  24045. /* targetChanges= */ new Map(),
  24046. /* targetMismatches= */ new SortedSet(primitiveComparator), documentUpdates, resolvedLimboDocuments);
  24047. await syncEngineApplyRemoteEvent(syncEngineImpl, event);
  24048. // Since this query failed, we won't want to manually unlisten to it.
  24049. // We only remove it from bookkeeping after we successfully applied the
  24050. // RemoteEvent. If `applyRemoteEvent()` throws, we want to re-listen to
  24051. // this query when the RemoteStore restarts the Watch stream, which should
  24052. // re-trigger the target failure.
  24053. syncEngineImpl.activeLimboTargetsByKey =
  24054. syncEngineImpl.activeLimboTargetsByKey.remove(limboKey);
  24055. syncEngineImpl.activeLimboResolutionsByTarget.delete(targetId);
  24056. pumpEnqueuedLimboResolutions(syncEngineImpl);
  24057. }
  24058. else {
  24059. await localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  24060. /* keepPersistedTargetData */ false)
  24061. .then(() => removeAndCleanupTarget(syncEngineImpl, targetId, err))
  24062. .catch(ignoreIfPrimaryLeaseLoss);
  24063. }
  24064. }
  24065. async function syncEngineApplySuccessfulWrite(syncEngine, mutationBatchResult) {
  24066. const syncEngineImpl = debugCast(syncEngine);
  24067. const batchId = mutationBatchResult.batch.batchId;
  24068. try {
  24069. const changes = await localStoreAcknowledgeBatch(syncEngineImpl.localStore, mutationBatchResult);
  24070. // The local store may or may not be able to apply the write result and
  24071. // raise events immediately (depending on whether the watcher is caught
  24072. // up), so we raise user callbacks first so that they consistently happen
  24073. // before listen events.
  24074. processUserCallback(syncEngineImpl, batchId, /*error=*/ null);
  24075. triggerPendingWritesCallbacks(syncEngineImpl, batchId);
  24076. syncEngineImpl.sharedClientState.updateMutationState(batchId, 'acknowledged');
  24077. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes);
  24078. }
  24079. catch (error) {
  24080. await ignoreIfPrimaryLeaseLoss(error);
  24081. }
  24082. }
  24083. async function syncEngineRejectFailedWrite(syncEngine, batchId, error) {
  24084. const syncEngineImpl = debugCast(syncEngine);
  24085. try {
  24086. const changes = await localStoreRejectBatch(syncEngineImpl.localStore, batchId);
  24087. // The local store may or may not be able to apply the write result and
  24088. // raise events immediately (depending on whether the watcher is caught up),
  24089. // so we raise user callbacks first so that they consistently happen before
  24090. // listen events.
  24091. processUserCallback(syncEngineImpl, batchId, error);
  24092. triggerPendingWritesCallbacks(syncEngineImpl, batchId);
  24093. syncEngineImpl.sharedClientState.updateMutationState(batchId, 'rejected', error);
  24094. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes);
  24095. }
  24096. catch (error) {
  24097. await ignoreIfPrimaryLeaseLoss(error);
  24098. }
  24099. }
  24100. /**
  24101. * Registers a user callback that resolves when all pending mutations at the moment of calling
  24102. * are acknowledged .
  24103. */
  24104. async function syncEngineRegisterPendingWritesCallback(syncEngine, callback) {
  24105. const syncEngineImpl = debugCast(syncEngine);
  24106. if (!canUseNetwork(syncEngineImpl.remoteStore)) {
  24107. logDebug(LOG_TAG$3, 'The network is disabled. The task returned by ' +
  24108. "'awaitPendingWrites()' will not complete until the network is enabled.");
  24109. }
  24110. try {
  24111. const highestBatchId = await localStoreGetHighestUnacknowledgedBatchId(syncEngineImpl.localStore);
  24112. if (highestBatchId === BATCHID_UNKNOWN) {
  24113. // Trigger the callback right away if there is no pending writes at the moment.
  24114. callback.resolve();
  24115. return;
  24116. }
  24117. const callbacks = syncEngineImpl.pendingWritesCallbacks.get(highestBatchId) || [];
  24118. callbacks.push(callback);
  24119. syncEngineImpl.pendingWritesCallbacks.set(highestBatchId, callbacks);
  24120. }
  24121. catch (e) {
  24122. const firestoreError = wrapInUserErrorIfRecoverable(e, 'Initialization of waitForPendingWrites() operation failed');
  24123. callback.reject(firestoreError);
  24124. }
  24125. }
  24126. /**
  24127. * Triggers the callbacks that are waiting for this batch id to get acknowledged by server,
  24128. * if there are any.
  24129. */
  24130. function triggerPendingWritesCallbacks(syncEngineImpl, batchId) {
  24131. (syncEngineImpl.pendingWritesCallbacks.get(batchId) || []).forEach(callback => {
  24132. callback.resolve();
  24133. });
  24134. syncEngineImpl.pendingWritesCallbacks.delete(batchId);
  24135. }
  24136. /** Reject all outstanding callbacks waiting for pending writes to complete. */
  24137. function rejectOutstandingPendingWritesCallbacks(syncEngineImpl, errorMessage) {
  24138. syncEngineImpl.pendingWritesCallbacks.forEach(callbacks => {
  24139. callbacks.forEach(callback => {
  24140. callback.reject(new FirestoreError(Code.CANCELLED, errorMessage));
  24141. });
  24142. });
  24143. syncEngineImpl.pendingWritesCallbacks.clear();
  24144. }
  24145. function addMutationCallback(syncEngineImpl, batchId, callback) {
  24146. let newCallbacks = syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()];
  24147. if (!newCallbacks) {
  24148. newCallbacks = new SortedMap(primitiveComparator);
  24149. }
  24150. newCallbacks = newCallbacks.insert(batchId, callback);
  24151. syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()] =
  24152. newCallbacks;
  24153. }
  24154. /**
  24155. * Resolves or rejects the user callback for the given batch and then discards
  24156. * it.
  24157. */
  24158. function processUserCallback(syncEngine, batchId, error) {
  24159. const syncEngineImpl = debugCast(syncEngine);
  24160. let newCallbacks = syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()];
  24161. // NOTE: Mutations restored from persistence won't have callbacks, so it's
  24162. // okay for there to be no callback for this ID.
  24163. if (newCallbacks) {
  24164. const callback = newCallbacks.get(batchId);
  24165. if (callback) {
  24166. if (error) {
  24167. callback.reject(error);
  24168. }
  24169. else {
  24170. callback.resolve();
  24171. }
  24172. newCallbacks = newCallbacks.remove(batchId);
  24173. }
  24174. syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()] =
  24175. newCallbacks;
  24176. }
  24177. }
  24178. function removeAndCleanupTarget(syncEngineImpl, targetId, error = null) {
  24179. syncEngineImpl.sharedClientState.removeLocalQueryTarget(targetId);
  24180. for (const query of syncEngineImpl.queriesByTarget.get(targetId)) {
  24181. syncEngineImpl.queryViewsByQuery.delete(query);
  24182. if (error) {
  24183. syncEngineImpl.syncEngineListener.onWatchError(query, error);
  24184. }
  24185. }
  24186. syncEngineImpl.queriesByTarget.delete(targetId);
  24187. if (syncEngineImpl.isPrimaryClient) {
  24188. const limboKeys = syncEngineImpl.limboDocumentRefs.removeReferencesForId(targetId);
  24189. limboKeys.forEach(limboKey => {
  24190. const isReferenced = syncEngineImpl.limboDocumentRefs.containsKey(limboKey);
  24191. if (!isReferenced) {
  24192. // We removed the last reference for this key
  24193. removeLimboTarget(syncEngineImpl, limboKey);
  24194. }
  24195. });
  24196. }
  24197. }
  24198. function removeLimboTarget(syncEngineImpl, key) {
  24199. syncEngineImpl.enqueuedLimboResolutions.delete(key.path.canonicalString());
  24200. // It's possible that the target already got removed because the query failed. In that case,
  24201. // the key won't exist in `limboTargetsByKey`. Only do the cleanup if we still have the target.
  24202. const limboTargetId = syncEngineImpl.activeLimboTargetsByKey.get(key);
  24203. if (limboTargetId === null) {
  24204. // This target already got removed, because the query failed.
  24205. return;
  24206. }
  24207. remoteStoreUnlisten(syncEngineImpl.remoteStore, limboTargetId);
  24208. syncEngineImpl.activeLimboTargetsByKey =
  24209. syncEngineImpl.activeLimboTargetsByKey.remove(key);
  24210. syncEngineImpl.activeLimboResolutionsByTarget.delete(limboTargetId);
  24211. pumpEnqueuedLimboResolutions(syncEngineImpl);
  24212. }
  24213. function updateTrackedLimbos(syncEngineImpl, targetId, limboChanges) {
  24214. for (const limboChange of limboChanges) {
  24215. if (limboChange instanceof AddedLimboDocument) {
  24216. syncEngineImpl.limboDocumentRefs.addReference(limboChange.key, targetId);
  24217. trackLimboChange(syncEngineImpl, limboChange);
  24218. }
  24219. else if (limboChange instanceof RemovedLimboDocument) {
  24220. logDebug(LOG_TAG$3, 'Document no longer in limbo: ' + limboChange.key);
  24221. syncEngineImpl.limboDocumentRefs.removeReference(limboChange.key, targetId);
  24222. const isReferenced = syncEngineImpl.limboDocumentRefs.containsKey(limboChange.key);
  24223. if (!isReferenced) {
  24224. // We removed the last reference for this key
  24225. removeLimboTarget(syncEngineImpl, limboChange.key);
  24226. }
  24227. }
  24228. else {
  24229. fail();
  24230. }
  24231. }
  24232. }
  24233. function trackLimboChange(syncEngineImpl, limboChange) {
  24234. const key = limboChange.key;
  24235. const keyString = key.path.canonicalString();
  24236. if (!syncEngineImpl.activeLimboTargetsByKey.get(key) &&
  24237. !syncEngineImpl.enqueuedLimboResolutions.has(keyString)) {
  24238. logDebug(LOG_TAG$3, 'New document in limbo: ' + key);
  24239. syncEngineImpl.enqueuedLimboResolutions.add(keyString);
  24240. pumpEnqueuedLimboResolutions(syncEngineImpl);
  24241. }
  24242. }
  24243. /**
  24244. * Starts listens for documents in limbo that are enqueued for resolution,
  24245. * subject to a maximum number of concurrent resolutions.
  24246. *
  24247. * Without bounding the number of concurrent resolutions, the server can fail
  24248. * with "resource exhausted" errors which can lead to pathological client
  24249. * behavior as seen in https://github.com/firebase/firebase-js-sdk/issues/2683.
  24250. */
  24251. function pumpEnqueuedLimboResolutions(syncEngineImpl) {
  24252. while (syncEngineImpl.enqueuedLimboResolutions.size > 0 &&
  24253. syncEngineImpl.activeLimboTargetsByKey.size <
  24254. syncEngineImpl.maxConcurrentLimboResolutions) {
  24255. const keyString = syncEngineImpl.enqueuedLimboResolutions
  24256. .values()
  24257. .next().value;
  24258. syncEngineImpl.enqueuedLimboResolutions.delete(keyString);
  24259. const key = new DocumentKey(ResourcePath.fromString(keyString));
  24260. const limboTargetId = syncEngineImpl.limboTargetIdGenerator.next();
  24261. syncEngineImpl.activeLimboResolutionsByTarget.set(limboTargetId, new LimboResolution(key));
  24262. syncEngineImpl.activeLimboTargetsByKey =
  24263. syncEngineImpl.activeLimboTargetsByKey.insert(key, limboTargetId);
  24264. remoteStoreListen(syncEngineImpl.remoteStore, new TargetData(queryToTarget(newQueryForPath(key.path)), limboTargetId, 2 /* TargetPurpose.LimboResolution */, ListenSequence.INVALID));
  24265. }
  24266. }
  24267. async function syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, changes, remoteEvent) {
  24268. const syncEngineImpl = debugCast(syncEngine);
  24269. const newSnaps = [];
  24270. const docChangesInAllViews = [];
  24271. const queriesProcessed = [];
  24272. if (syncEngineImpl.queryViewsByQuery.isEmpty()) {
  24273. // Return early since `onWatchChange()` might not have been assigned yet.
  24274. return;
  24275. }
  24276. syncEngineImpl.queryViewsByQuery.forEach((_, queryView) => {
  24277. queriesProcessed.push(syncEngineImpl
  24278. .applyDocChanges(queryView, changes, remoteEvent)
  24279. .then(viewSnapshot => {
  24280. // If there are changes, or we are handling a global snapshot, notify
  24281. // secondary clients to update query state.
  24282. if (viewSnapshot || remoteEvent) {
  24283. if (syncEngineImpl.isPrimaryClient) {
  24284. syncEngineImpl.sharedClientState.updateQueryState(queryView.targetId, (viewSnapshot === null || viewSnapshot === void 0 ? void 0 : viewSnapshot.fromCache) ? 'not-current' : 'current');
  24285. }
  24286. }
  24287. // Update views if there are actual changes.
  24288. if (!!viewSnapshot) {
  24289. newSnaps.push(viewSnapshot);
  24290. const docChanges = LocalViewChanges.fromSnapshot(queryView.targetId, viewSnapshot);
  24291. docChangesInAllViews.push(docChanges);
  24292. }
  24293. }));
  24294. });
  24295. await Promise.all(queriesProcessed);
  24296. syncEngineImpl.syncEngineListener.onWatchChange(newSnaps);
  24297. await localStoreNotifyLocalViewChanges(syncEngineImpl.localStore, docChangesInAllViews);
  24298. }
  24299. async function applyDocChanges(syncEngineImpl, queryView, changes, remoteEvent) {
  24300. let viewDocChanges = queryView.view.computeDocChanges(changes);
  24301. if (viewDocChanges.needsRefill) {
  24302. // The query has a limit and some docs were removed, so we need
  24303. // to re-run the query against the local store to make sure we
  24304. // didn't lose any good docs that had been past the limit.
  24305. viewDocChanges = await localStoreExecuteQuery(syncEngineImpl.localStore, queryView.query,
  24306. /* usePreviousResults= */ false).then(({ documents }) => {
  24307. return queryView.view.computeDocChanges(documents, viewDocChanges);
  24308. });
  24309. }
  24310. const targetChange = remoteEvent && remoteEvent.targetChanges.get(queryView.targetId);
  24311. const viewChange = queryView.view.applyChanges(viewDocChanges,
  24312. /* updateLimboDocuments= */ syncEngineImpl.isPrimaryClient, targetChange);
  24313. updateTrackedLimbos(syncEngineImpl, queryView.targetId, viewChange.limboChanges);
  24314. return viewChange.snapshot;
  24315. }
  24316. async function syncEngineHandleCredentialChange(syncEngine, user) {
  24317. const syncEngineImpl = debugCast(syncEngine);
  24318. const userChanged = !syncEngineImpl.currentUser.isEqual(user);
  24319. if (userChanged) {
  24320. logDebug(LOG_TAG$3, 'User change. New user:', user.toKey());
  24321. const result = await localStoreHandleUserChange(syncEngineImpl.localStore, user);
  24322. syncEngineImpl.currentUser = user;
  24323. // Fails tasks waiting for pending writes requested by previous user.
  24324. rejectOutstandingPendingWritesCallbacks(syncEngineImpl, "'waitForPendingWrites' promise is rejected due to a user change.");
  24325. // TODO(b/114226417): Consider calling this only in the primary tab.
  24326. syncEngineImpl.sharedClientState.handleUserChange(user, result.removedBatchIds, result.addedBatchIds);
  24327. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, result.affectedDocuments);
  24328. }
  24329. }
  24330. function syncEngineGetRemoteKeysForTarget(syncEngine, targetId) {
  24331. const syncEngineImpl = debugCast(syncEngine);
  24332. const limboResolution = syncEngineImpl.activeLimboResolutionsByTarget.get(targetId);
  24333. if (limboResolution && limboResolution.receivedDocument) {
  24334. return documentKeySet().add(limboResolution.key);
  24335. }
  24336. else {
  24337. let keySet = documentKeySet();
  24338. const queries = syncEngineImpl.queriesByTarget.get(targetId);
  24339. if (!queries) {
  24340. return keySet;
  24341. }
  24342. for (const query of queries) {
  24343. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  24344. keySet = keySet.unionWith(queryView.view.syncedDocuments);
  24345. }
  24346. return keySet;
  24347. }
  24348. }
  24349. /**
  24350. * Reconcile the list of synced documents in an existing view with those
  24351. * from persistence.
  24352. */
  24353. async function synchronizeViewAndComputeSnapshot(syncEngine, queryView) {
  24354. const syncEngineImpl = debugCast(syncEngine);
  24355. const queryResult = await localStoreExecuteQuery(syncEngineImpl.localStore, queryView.query,
  24356. /* usePreviousResults= */ true);
  24357. const viewSnapshot = queryView.view.synchronizeWithPersistedState(queryResult);
  24358. if (syncEngineImpl.isPrimaryClient) {
  24359. updateTrackedLimbos(syncEngineImpl, queryView.targetId, viewSnapshot.limboChanges);
  24360. }
  24361. return viewSnapshot;
  24362. }
  24363. /**
  24364. * Retrieves newly changed documents from remote document cache and raises
  24365. * snapshots if needed.
  24366. */
  24367. // PORTING NOTE: Multi-Tab only.
  24368. async function syncEngineSynchronizeWithChangedDocuments(syncEngine, collectionGroup) {
  24369. const syncEngineImpl = debugCast(syncEngine);
  24370. return localStoreGetNewDocumentChanges(syncEngineImpl.localStore, collectionGroup).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
  24371. }
  24372. /** Applies a mutation state to an existing batch. */
  24373. // PORTING NOTE: Multi-Tab only.
  24374. async function syncEngineApplyBatchState(syncEngine, batchId, batchState, error) {
  24375. const syncEngineImpl = debugCast(syncEngine);
  24376. const documents = await localStoreLookupMutationDocuments(syncEngineImpl.localStore, batchId);
  24377. if (documents === null) {
  24378. // A throttled tab may not have seen the mutation before it was completed
  24379. // and removed from the mutation queue, in which case we won't have cached
  24380. // the affected documents. In this case we can safely ignore the update
  24381. // since that means we didn't apply the mutation locally at all (if we
  24382. // had, we would have cached the affected documents), and so we will just
  24383. // see any resulting document changes via normal remote document updates
  24384. // as applicable.
  24385. logDebug(LOG_TAG$3, 'Cannot apply mutation batch with id: ' + batchId);
  24386. return;
  24387. }
  24388. if (batchState === 'pending') {
  24389. // If we are the primary client, we need to send this write to the
  24390. // backend. Secondary clients will ignore these writes since their remote
  24391. // connection is disabled.
  24392. await fillWritePipeline(syncEngineImpl.remoteStore);
  24393. }
  24394. else if (batchState === 'acknowledged' || batchState === 'rejected') {
  24395. // NOTE: Both these methods are no-ops for batches that originated from
  24396. // other clients.
  24397. processUserCallback(syncEngineImpl, batchId, error ? error : null);
  24398. triggerPendingWritesCallbacks(syncEngineImpl, batchId);
  24399. localStoreRemoveCachedMutationBatchMetadata(syncEngineImpl.localStore, batchId);
  24400. }
  24401. else {
  24402. fail();
  24403. }
  24404. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, documents);
  24405. }
  24406. /** Applies a query target change from a different tab. */
  24407. // PORTING NOTE: Multi-Tab only.
  24408. async function syncEngineApplyPrimaryState(syncEngine, isPrimary) {
  24409. const syncEngineImpl = debugCast(syncEngine);
  24410. ensureWatchCallbacks(syncEngineImpl);
  24411. syncEngineEnsureWriteCallbacks(syncEngineImpl);
  24412. if (isPrimary === true && syncEngineImpl._isPrimaryClient !== true) {
  24413. // Secondary tabs only maintain Views for their local listeners and the
  24414. // Views internal state may not be 100% populated (in particular
  24415. // secondary tabs don't track syncedDocuments, the set of documents the
  24416. // server considers to be in the target). So when a secondary becomes
  24417. // primary, we need to need to make sure that all views for all targets
  24418. // match the state on disk.
  24419. const activeTargets = syncEngineImpl.sharedClientState.getAllActiveQueryTargets();
  24420. const activeQueries = await synchronizeQueryViewsAndRaiseSnapshots(syncEngineImpl, activeTargets.toArray());
  24421. syncEngineImpl._isPrimaryClient = true;
  24422. await remoteStoreApplyPrimaryState(syncEngineImpl.remoteStore, true);
  24423. for (const targetData of activeQueries) {
  24424. remoteStoreListen(syncEngineImpl.remoteStore, targetData);
  24425. }
  24426. }
  24427. else if (isPrimary === false && syncEngineImpl._isPrimaryClient !== false) {
  24428. const activeTargets = [];
  24429. let p = Promise.resolve();
  24430. syncEngineImpl.queriesByTarget.forEach((_, targetId) => {
  24431. if (syncEngineImpl.sharedClientState.isLocalQueryTarget(targetId)) {
  24432. activeTargets.push(targetId);
  24433. }
  24434. else {
  24435. p = p.then(() => {
  24436. removeAndCleanupTarget(syncEngineImpl, targetId);
  24437. return localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  24438. /*keepPersistedTargetData=*/ true);
  24439. });
  24440. }
  24441. remoteStoreUnlisten(syncEngineImpl.remoteStore, targetId);
  24442. });
  24443. await p;
  24444. await synchronizeQueryViewsAndRaiseSnapshots(syncEngineImpl, activeTargets);
  24445. resetLimboDocuments(syncEngineImpl);
  24446. syncEngineImpl._isPrimaryClient = false;
  24447. await remoteStoreApplyPrimaryState(syncEngineImpl.remoteStore, false);
  24448. }
  24449. }
  24450. // PORTING NOTE: Multi-Tab only.
  24451. function resetLimboDocuments(syncEngine) {
  24452. const syncEngineImpl = debugCast(syncEngine);
  24453. syncEngineImpl.activeLimboResolutionsByTarget.forEach((_, targetId) => {
  24454. remoteStoreUnlisten(syncEngineImpl.remoteStore, targetId);
  24455. });
  24456. syncEngineImpl.limboDocumentRefs.removeAllReferences();
  24457. syncEngineImpl.activeLimboResolutionsByTarget = new Map();
  24458. syncEngineImpl.activeLimboTargetsByKey = new SortedMap(DocumentKey.comparator);
  24459. }
  24460. /**
  24461. * Reconcile the query views of the provided query targets with the state from
  24462. * persistence. Raises snapshots for any changes that affect the local
  24463. * client and returns the updated state of all target's query data.
  24464. *
  24465. * @param syncEngine - The sync engine implementation
  24466. * @param targets - the list of targets with views that need to be recomputed
  24467. * @param transitionToPrimary - `true` iff the tab transitions from a secondary
  24468. * tab to a primary tab
  24469. */
  24470. // PORTING NOTE: Multi-Tab only.
  24471. async function synchronizeQueryViewsAndRaiseSnapshots(syncEngine, targets, transitionToPrimary) {
  24472. const syncEngineImpl = debugCast(syncEngine);
  24473. const activeQueries = [];
  24474. const newViewSnapshots = [];
  24475. for (const targetId of targets) {
  24476. let targetData;
  24477. const queries = syncEngineImpl.queriesByTarget.get(targetId);
  24478. if (queries && queries.length !== 0) {
  24479. // For queries that have a local View, we fetch their current state
  24480. // from LocalStore (as the resume token and the snapshot version
  24481. // might have changed) and reconcile their views with the persisted
  24482. // state (the list of syncedDocuments may have gotten out of sync).
  24483. targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, queryToTarget(queries[0]));
  24484. for (const query of queries) {
  24485. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  24486. const viewChange = await synchronizeViewAndComputeSnapshot(syncEngineImpl, queryView);
  24487. if (viewChange.snapshot) {
  24488. newViewSnapshots.push(viewChange.snapshot);
  24489. }
  24490. }
  24491. }
  24492. else {
  24493. // For queries that never executed on this client, we need to
  24494. // allocate the target in LocalStore and initialize a new View.
  24495. const target = await localStoreGetCachedTarget(syncEngineImpl.localStore, targetId);
  24496. targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, target);
  24497. await initializeViewAndComputeSnapshot(syncEngineImpl, synthesizeTargetToQuery(target), targetId,
  24498. /*current=*/ false, targetData.resumeToken);
  24499. }
  24500. activeQueries.push(targetData);
  24501. }
  24502. syncEngineImpl.syncEngineListener.onWatchChange(newViewSnapshots);
  24503. return activeQueries;
  24504. }
  24505. /**
  24506. * Creates a `Query` object from the specified `Target`. There is no way to
  24507. * obtain the original `Query`, so we synthesize a `Query` from the `Target`
  24508. * object.
  24509. *
  24510. * The synthesized result might be different from the original `Query`, but
  24511. * since the synthesized `Query` should return the same results as the
  24512. * original one (only the presentation of results might differ), the potential
  24513. * difference will not cause issues.
  24514. */
  24515. // PORTING NOTE: Multi-Tab only.
  24516. function synthesizeTargetToQuery(target) {
  24517. return newQuery(target.path, target.collectionGroup, target.orderBy, target.filters, target.limit, "F" /* LimitType.First */, target.startAt, target.endAt);
  24518. }
  24519. /** Returns the IDs of the clients that are currently active. */
  24520. // PORTING NOTE: Multi-Tab only.
  24521. function syncEngineGetActiveClients(syncEngine) {
  24522. const syncEngineImpl = debugCast(syncEngine);
  24523. return localStoreGetActiveClients(syncEngineImpl.localStore);
  24524. }
  24525. /** Applies a query target change from a different tab. */
  24526. // PORTING NOTE: Multi-Tab only.
  24527. async function syncEngineApplyTargetState(syncEngine, targetId, state, error) {
  24528. const syncEngineImpl = debugCast(syncEngine);
  24529. if (syncEngineImpl._isPrimaryClient) {
  24530. // If we receive a target state notification via WebStorage, we are
  24531. // either already secondary or another tab has taken the primary lease.
  24532. logDebug(LOG_TAG$3, 'Ignoring unexpected query state notification.');
  24533. return;
  24534. }
  24535. const query = syncEngineImpl.queriesByTarget.get(targetId);
  24536. if (query && query.length > 0) {
  24537. switch (state) {
  24538. case 'current':
  24539. case 'not-current': {
  24540. const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore, queryCollectionGroup(query[0]));
  24541. const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(targetId, state === 'current', ByteString.EMPTY_BYTE_STRING);
  24542. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, synthesizedRemoteEvent);
  24543. break;
  24544. }
  24545. case 'rejected': {
  24546. await localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  24547. /* keepPersistedTargetData */ true);
  24548. removeAndCleanupTarget(syncEngineImpl, targetId, error);
  24549. break;
  24550. }
  24551. default:
  24552. fail();
  24553. }
  24554. }
  24555. }
  24556. /** Adds or removes Watch targets for queries from different tabs. */
  24557. async function syncEngineApplyActiveTargetsChange(syncEngine, added, removed) {
  24558. const syncEngineImpl = ensureWatchCallbacks(syncEngine);
  24559. if (!syncEngineImpl._isPrimaryClient) {
  24560. return;
  24561. }
  24562. for (const targetId of added) {
  24563. if (syncEngineImpl.queriesByTarget.has(targetId)) {
  24564. // A target might have been added in a previous attempt
  24565. logDebug(LOG_TAG$3, 'Adding an already active target ' + targetId);
  24566. continue;
  24567. }
  24568. const target = await localStoreGetCachedTarget(syncEngineImpl.localStore, targetId);
  24569. const targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, target);
  24570. await initializeViewAndComputeSnapshot(syncEngineImpl, synthesizeTargetToQuery(target), targetData.targetId,
  24571. /*current=*/ false, targetData.resumeToken);
  24572. remoteStoreListen(syncEngineImpl.remoteStore, targetData);
  24573. }
  24574. for (const targetId of removed) {
  24575. // Check that the target is still active since the target might have been
  24576. // removed if it has been rejected by the backend.
  24577. if (!syncEngineImpl.queriesByTarget.has(targetId)) {
  24578. continue;
  24579. }
  24580. // Release queries that are still active.
  24581. await localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  24582. /* keepPersistedTargetData */ false)
  24583. .then(() => {
  24584. remoteStoreUnlisten(syncEngineImpl.remoteStore, targetId);
  24585. removeAndCleanupTarget(syncEngineImpl, targetId);
  24586. })
  24587. .catch(ignoreIfPrimaryLeaseLoss);
  24588. }
  24589. }
  24590. function ensureWatchCallbacks(syncEngine) {
  24591. const syncEngineImpl = debugCast(syncEngine);
  24592. syncEngineImpl.remoteStore.remoteSyncer.applyRemoteEvent =
  24593. syncEngineApplyRemoteEvent.bind(null, syncEngineImpl);
  24594. syncEngineImpl.remoteStore.remoteSyncer.getRemoteKeysForTarget =
  24595. syncEngineGetRemoteKeysForTarget.bind(null, syncEngineImpl);
  24596. syncEngineImpl.remoteStore.remoteSyncer.rejectListen =
  24597. syncEngineRejectListen.bind(null, syncEngineImpl);
  24598. syncEngineImpl.syncEngineListener.onWatchChange =
  24599. eventManagerOnWatchChange.bind(null, syncEngineImpl.eventManager);
  24600. syncEngineImpl.syncEngineListener.onWatchError =
  24601. eventManagerOnWatchError.bind(null, syncEngineImpl.eventManager);
  24602. return syncEngineImpl;
  24603. }
  24604. function syncEngineEnsureWriteCallbacks(syncEngine) {
  24605. const syncEngineImpl = debugCast(syncEngine);
  24606. syncEngineImpl.remoteStore.remoteSyncer.applySuccessfulWrite =
  24607. syncEngineApplySuccessfulWrite.bind(null, syncEngineImpl);
  24608. syncEngineImpl.remoteStore.remoteSyncer.rejectFailedWrite =
  24609. syncEngineRejectFailedWrite.bind(null, syncEngineImpl);
  24610. return syncEngineImpl;
  24611. }
  24612. /**
  24613. * Loads a Firestore bundle into the SDK. The returned promise resolves when
  24614. * the bundle finished loading.
  24615. *
  24616. * @param syncEngine - SyncEngine to use.
  24617. * @param bundleReader - Bundle to load into the SDK.
  24618. * @param task - LoadBundleTask used to update the loading progress to public API.
  24619. */
  24620. function syncEngineLoadBundle(syncEngine, bundleReader, task) {
  24621. const syncEngineImpl = debugCast(syncEngine);
  24622. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  24623. loadBundleImpl(syncEngineImpl, bundleReader, task).then(collectionGroups => {
  24624. syncEngineImpl.sharedClientState.notifyBundleLoaded(collectionGroups);
  24625. });
  24626. }
  24627. /** Loads a bundle and returns the list of affected collection groups. */
  24628. async function loadBundleImpl(syncEngine, reader, task) {
  24629. try {
  24630. const metadata = await reader.getMetadata();
  24631. const skip = await localStoreHasNewerBundle(syncEngine.localStore, metadata);
  24632. if (skip) {
  24633. await reader.close();
  24634. task._completeWith(bundleSuccessProgress(metadata));
  24635. return Promise.resolve(new Set());
  24636. }
  24637. task._updateProgress(bundleInitialProgress(metadata));
  24638. const loader = new BundleLoader(metadata, syncEngine.localStore, reader.serializer);
  24639. let element = await reader.nextElement();
  24640. while (element) {
  24641. ;
  24642. const progress = await loader.addSizedElement(element);
  24643. if (progress) {
  24644. task._updateProgress(progress);
  24645. }
  24646. element = await reader.nextElement();
  24647. }
  24648. const result = await loader.complete();
  24649. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, result.changedDocs,
  24650. /* remoteEvent */ undefined);
  24651. // Save metadata, so loading the same bundle will skip.
  24652. await localStoreSaveBundle(syncEngine.localStore, metadata);
  24653. task._completeWith(result.progress);
  24654. return Promise.resolve(result.changedCollectionGroups);
  24655. }
  24656. catch (e) {
  24657. logWarn(LOG_TAG$3, `Loading bundle failed with ${e}`);
  24658. task._failWith(e);
  24659. return Promise.resolve(new Set());
  24660. }
  24661. }
  24662. /**
  24663. * @license
  24664. * Copyright 2020 Google LLC
  24665. *
  24666. * Licensed under the Apache License, Version 2.0 (the "License");
  24667. * you may not use this file except in compliance with the License.
  24668. * You may obtain a copy of the License at
  24669. *
  24670. * http://www.apache.org/licenses/LICENSE-2.0
  24671. *
  24672. * Unless required by applicable law or agreed to in writing, software
  24673. * distributed under the License is distributed on an "AS IS" BASIS,
  24674. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24675. * See the License for the specific language governing permissions and
  24676. * limitations under the License.
  24677. */
  24678. /**
  24679. * Provides all components needed for Firestore with in-memory persistence.
  24680. * Uses EagerGC garbage collection.
  24681. */
  24682. class MemoryOfflineComponentProvider {
  24683. constructor() {
  24684. this.synchronizeTabs = false;
  24685. }
  24686. async initialize(cfg) {
  24687. this.serializer = newSerializer(cfg.databaseInfo.databaseId);
  24688. this.sharedClientState = this.createSharedClientState(cfg);
  24689. this.persistence = this.createPersistence(cfg);
  24690. await this.persistence.start();
  24691. this.localStore = this.createLocalStore(cfg);
  24692. this.gcScheduler = this.createGarbageCollectionScheduler(cfg, this.localStore);
  24693. this.indexBackfillerScheduler = this.createIndexBackfillerScheduler(cfg, this.localStore);
  24694. }
  24695. createGarbageCollectionScheduler(cfg, localStore) {
  24696. return null;
  24697. }
  24698. createIndexBackfillerScheduler(cfg, localStore) {
  24699. return null;
  24700. }
  24701. createLocalStore(cfg) {
  24702. return newLocalStore(this.persistence, new QueryEngine(), cfg.initialUser, this.serializer);
  24703. }
  24704. createPersistence(cfg) {
  24705. return new MemoryPersistence(MemoryEagerDelegate.factory, this.serializer);
  24706. }
  24707. createSharedClientState(cfg) {
  24708. return new MemorySharedClientState();
  24709. }
  24710. async terminate() {
  24711. if (this.gcScheduler) {
  24712. this.gcScheduler.stop();
  24713. }
  24714. await this.sharedClientState.shutdown();
  24715. await this.persistence.shutdown();
  24716. }
  24717. }
  24718. /**
  24719. * Provides all components needed for Firestore with IndexedDB persistence.
  24720. */
  24721. class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
  24722. constructor(onlineComponentProvider, cacheSizeBytes, forceOwnership) {
  24723. super();
  24724. this.onlineComponentProvider = onlineComponentProvider;
  24725. this.cacheSizeBytes = cacheSizeBytes;
  24726. this.forceOwnership = forceOwnership;
  24727. this.synchronizeTabs = false;
  24728. }
  24729. async initialize(cfg) {
  24730. await super.initialize(cfg);
  24731. await this.onlineComponentProvider.initialize(this, cfg);
  24732. // Enqueue writes from a previous session
  24733. await syncEngineEnsureWriteCallbacks(this.onlineComponentProvider.syncEngine);
  24734. await fillWritePipeline(this.onlineComponentProvider.remoteStore);
  24735. // NOTE: This will immediately call the listener, so we make sure to
  24736. // set it after localStore / remoteStore are started.
  24737. await this.persistence.setPrimaryStateListener(() => {
  24738. if (this.gcScheduler && !this.gcScheduler.started) {
  24739. this.gcScheduler.start();
  24740. }
  24741. if (this.indexBackfillerScheduler &&
  24742. !this.indexBackfillerScheduler.started) {
  24743. this.indexBackfillerScheduler.start();
  24744. }
  24745. return Promise.resolve();
  24746. });
  24747. }
  24748. createLocalStore(cfg) {
  24749. return newLocalStore(this.persistence, new QueryEngine(), cfg.initialUser, this.serializer);
  24750. }
  24751. createGarbageCollectionScheduler(cfg, localStore) {
  24752. const garbageCollector = this.persistence.referenceDelegate.garbageCollector;
  24753. return new LruScheduler(garbageCollector, cfg.asyncQueue, localStore);
  24754. }
  24755. createIndexBackfillerScheduler(cfg, localStore) {
  24756. const indexBackfiller = new IndexBackfiller(localStore, this.persistence);
  24757. return new IndexBackfillerScheduler(cfg.asyncQueue, indexBackfiller);
  24758. }
  24759. createPersistence(cfg) {
  24760. const persistenceKey = indexedDbStoragePrefix(cfg.databaseInfo.databaseId, cfg.databaseInfo.persistenceKey);
  24761. const lruParams = this.cacheSizeBytes !== undefined
  24762. ? LruParams.withCacheSize(this.cacheSizeBytes)
  24763. : LruParams.DEFAULT;
  24764. return new IndexedDbPersistence(this.synchronizeTabs, persistenceKey, cfg.clientId, lruParams, cfg.asyncQueue, getWindow(), getDocument(), this.serializer, this.sharedClientState, !!this.forceOwnership);
  24765. }
  24766. createSharedClientState(cfg) {
  24767. return new MemorySharedClientState();
  24768. }
  24769. }
  24770. /**
  24771. * Provides all components needed for Firestore with multi-tab IndexedDB
  24772. * persistence.
  24773. *
  24774. * In the legacy client, this provider is used to provide both multi-tab and
  24775. * non-multi-tab persistence since we cannot tell at build time whether
  24776. * `synchronizeTabs` will be enabled.
  24777. */
  24778. class MultiTabOfflineComponentProvider extends IndexedDbOfflineComponentProvider {
  24779. constructor(onlineComponentProvider, cacheSizeBytes) {
  24780. super(onlineComponentProvider, cacheSizeBytes, /* forceOwnership= */ false);
  24781. this.onlineComponentProvider = onlineComponentProvider;
  24782. this.cacheSizeBytes = cacheSizeBytes;
  24783. this.synchronizeTabs = true;
  24784. }
  24785. async initialize(cfg) {
  24786. await super.initialize(cfg);
  24787. const syncEngine = this.onlineComponentProvider.syncEngine;
  24788. if (this.sharedClientState instanceof WebStorageSharedClientState) {
  24789. this.sharedClientState.syncEngine = {
  24790. applyBatchState: syncEngineApplyBatchState.bind(null, syncEngine),
  24791. applyTargetState: syncEngineApplyTargetState.bind(null, syncEngine),
  24792. applyActiveTargetsChange: syncEngineApplyActiveTargetsChange.bind(null, syncEngine),
  24793. getActiveClients: syncEngineGetActiveClients.bind(null, syncEngine),
  24794. synchronizeWithChangedDocuments: syncEngineSynchronizeWithChangedDocuments.bind(null, syncEngine)
  24795. };
  24796. await this.sharedClientState.start();
  24797. }
  24798. // NOTE: This will immediately call the listener, so we make sure to
  24799. // set it after localStore / remoteStore are started.
  24800. await this.persistence.setPrimaryStateListener(async (isPrimary) => {
  24801. await syncEngineApplyPrimaryState(this.onlineComponentProvider.syncEngine, isPrimary);
  24802. if (this.gcScheduler) {
  24803. if (isPrimary && !this.gcScheduler.started) {
  24804. this.gcScheduler.start();
  24805. }
  24806. else if (!isPrimary) {
  24807. this.gcScheduler.stop();
  24808. }
  24809. }
  24810. if (this.indexBackfillerScheduler) {
  24811. if (isPrimary && !this.indexBackfillerScheduler.started) {
  24812. this.indexBackfillerScheduler.start();
  24813. }
  24814. else if (!isPrimary) {
  24815. this.indexBackfillerScheduler.stop();
  24816. }
  24817. }
  24818. });
  24819. }
  24820. createSharedClientState(cfg) {
  24821. const window = getWindow();
  24822. if (!WebStorageSharedClientState.isAvailable(window)) {
  24823. throw new FirestoreError(Code.UNIMPLEMENTED, 'IndexedDB persistence is only available on platforms that support LocalStorage.');
  24824. }
  24825. const persistenceKey = indexedDbStoragePrefix(cfg.databaseInfo.databaseId, cfg.databaseInfo.persistenceKey);
  24826. return new WebStorageSharedClientState(window, cfg.asyncQueue, persistenceKey, cfg.clientId, cfg.initialUser);
  24827. }
  24828. }
  24829. /**
  24830. * Initializes and wires the components that are needed to interface with the
  24831. * network.
  24832. */
  24833. class OnlineComponentProvider {
  24834. async initialize(offlineComponentProvider, cfg) {
  24835. if (this.localStore) {
  24836. // OnlineComponentProvider may get initialized multiple times if
  24837. // multi-tab persistence is used.
  24838. return;
  24839. }
  24840. this.localStore = offlineComponentProvider.localStore;
  24841. this.sharedClientState = offlineComponentProvider.sharedClientState;
  24842. this.datastore = this.createDatastore(cfg);
  24843. this.remoteStore = this.createRemoteStore(cfg);
  24844. this.eventManager = this.createEventManager(cfg);
  24845. this.syncEngine = this.createSyncEngine(cfg,
  24846. /* startAsPrimary=*/ !offlineComponentProvider.synchronizeTabs);
  24847. this.sharedClientState.onlineStateHandler = onlineState => syncEngineApplyOnlineStateChange(this.syncEngine, onlineState, 1 /* OnlineStateSource.SharedClientState */);
  24848. this.remoteStore.remoteSyncer.handleCredentialChange =
  24849. syncEngineHandleCredentialChange.bind(null, this.syncEngine);
  24850. await remoteStoreApplyPrimaryState(this.remoteStore, this.syncEngine.isPrimaryClient);
  24851. }
  24852. createEventManager(cfg) {
  24853. return newEventManager();
  24854. }
  24855. createDatastore(cfg) {
  24856. const serializer = newSerializer(cfg.databaseInfo.databaseId);
  24857. const connection = newConnection(cfg.databaseInfo);
  24858. return newDatastore(cfg.authCredentials, cfg.appCheckCredentials, connection, serializer);
  24859. }
  24860. createRemoteStore(cfg) {
  24861. return newRemoteStore(this.localStore, this.datastore, cfg.asyncQueue, onlineState => syncEngineApplyOnlineStateChange(this.syncEngine, onlineState, 0 /* OnlineStateSource.RemoteStore */), newConnectivityMonitor());
  24862. }
  24863. createSyncEngine(cfg, startAsPrimary) {
  24864. return newSyncEngine(this.localStore, this.remoteStore, this.eventManager, this.sharedClientState, cfg.initialUser, cfg.maxConcurrentLimboResolutions, startAsPrimary);
  24865. }
  24866. terminate() {
  24867. return remoteStoreShutdown(this.remoteStore);
  24868. }
  24869. }
  24870. /**
  24871. * @license
  24872. * Copyright 2020 Google LLC
  24873. *
  24874. * Licensed under the Apache License, Version 2.0 (the "License");
  24875. * you may not use this file except in compliance with the License.
  24876. * You may obtain a copy of the License at
  24877. *
  24878. * http://www.apache.org/licenses/LICENSE-2.0
  24879. *
  24880. * Unless required by applicable law or agreed to in writing, software
  24881. * distributed under the License is distributed on an "AS IS" BASIS,
  24882. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24883. * See the License for the specific language governing permissions and
  24884. * limitations under the License.
  24885. */
  24886. /**
  24887. * How many bytes to read each time when `ReadableStreamReader.read()` is
  24888. * called. Only applicable for byte streams that we control (e.g. those backed
  24889. * by an UInt8Array).
  24890. */
  24891. const DEFAULT_BYTES_PER_READ = 10240;
  24892. /**
  24893. * Builds a `ByteStreamReader` from a UInt8Array.
  24894. * @param source - The data source to use.
  24895. * @param bytesPerRead - How many bytes each `read()` from the returned reader
  24896. * will read.
  24897. */
  24898. function toByteStreamReaderHelper(source, bytesPerRead = DEFAULT_BYTES_PER_READ) {
  24899. let readFrom = 0;
  24900. // The TypeScript definition for ReadableStreamReader changed. We use
  24901. // `any` here to allow this code to compile with different versions.
  24902. // See https://github.com/microsoft/TypeScript/issues/42970
  24903. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  24904. const reader = {
  24905. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  24906. async read() {
  24907. if (readFrom < source.byteLength) {
  24908. const result = {
  24909. value: source.slice(readFrom, readFrom + bytesPerRead),
  24910. done: false
  24911. };
  24912. readFrom += bytesPerRead;
  24913. return result;
  24914. }
  24915. return { done: true };
  24916. },
  24917. async cancel() { },
  24918. releaseLock() { },
  24919. closed: Promise.reject('unimplemented')
  24920. };
  24921. return reader;
  24922. }
  24923. /**
  24924. * @license
  24925. * Copyright 2017 Google LLC
  24926. *
  24927. * Licensed under the Apache License, Version 2.0 (the "License");
  24928. * you may not use this file except in compliance with the License.
  24929. * You may obtain a copy of the License at
  24930. *
  24931. * http://www.apache.org/licenses/LICENSE-2.0
  24932. *
  24933. * Unless required by applicable law or agreed to in writing, software
  24934. * distributed under the License is distributed on an "AS IS" BASIS,
  24935. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24936. * See the License for the specific language governing permissions and
  24937. * limitations under the License.
  24938. */
  24939. function validateNonEmptyArgument(functionName, argumentName, argument) {
  24940. if (!argument) {
  24941. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() cannot be called with an empty ${argumentName}.`);
  24942. }
  24943. }
  24944. /**
  24945. * Validates that two boolean options are not set at the same time.
  24946. * @internal
  24947. */
  24948. function validateIsNotUsedTogether(optionName1, argument1, optionName2, argument2) {
  24949. if (argument1 === true && argument2 === true) {
  24950. throw new FirestoreError(Code.INVALID_ARGUMENT, `${optionName1} and ${optionName2} cannot be used together.`);
  24951. }
  24952. }
  24953. /**
  24954. * Validates that `path` refers to a document (indicated by the fact it contains
  24955. * an even numbers of segments).
  24956. */
  24957. function validateDocumentPath(path) {
  24958. if (!DocumentKey.isDocumentKey(path)) {
  24959. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid document reference. Document references must have an even number of segments, but ${path} has ${path.length}.`);
  24960. }
  24961. }
  24962. /**
  24963. * Validates that `path` refers to a collection (indicated by the fact it
  24964. * contains an odd numbers of segments).
  24965. */
  24966. function validateCollectionPath(path) {
  24967. if (DocumentKey.isDocumentKey(path)) {
  24968. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid collection reference. Collection references must have an odd number of segments, but ${path} has ${path.length}.`);
  24969. }
  24970. }
  24971. /**
  24972. * Returns true if it's a non-null object without a custom prototype
  24973. * (i.e. excludes Array, Date, etc.).
  24974. */
  24975. function isPlainObject(input) {
  24976. return (typeof input === 'object' &&
  24977. input !== null &&
  24978. (Object.getPrototypeOf(input) === Object.prototype ||
  24979. Object.getPrototypeOf(input) === null));
  24980. }
  24981. /** Returns a string describing the type / value of the provided input. */
  24982. function valueDescription(input) {
  24983. if (input === undefined) {
  24984. return 'undefined';
  24985. }
  24986. else if (input === null) {
  24987. return 'null';
  24988. }
  24989. else if (typeof input === 'string') {
  24990. if (input.length > 20) {
  24991. input = `${input.substring(0, 20)}...`;
  24992. }
  24993. return JSON.stringify(input);
  24994. }
  24995. else if (typeof input === 'number' || typeof input === 'boolean') {
  24996. return '' + input;
  24997. }
  24998. else if (typeof input === 'object') {
  24999. if (input instanceof Array) {
  25000. return 'an array';
  25001. }
  25002. else {
  25003. const customObjectName = tryGetCustomObjectType(input);
  25004. if (customObjectName) {
  25005. return `a custom ${customObjectName} object`;
  25006. }
  25007. else {
  25008. return 'an object';
  25009. }
  25010. }
  25011. }
  25012. else if (typeof input === 'function') {
  25013. return 'a function';
  25014. }
  25015. else {
  25016. return fail();
  25017. }
  25018. }
  25019. /** try to get the constructor name for an object. */
  25020. function tryGetCustomObjectType(input) {
  25021. if (input.constructor) {
  25022. return input.constructor.name;
  25023. }
  25024. return null;
  25025. }
  25026. /**
  25027. * Casts `obj` to `T`, optionally unwrapping Compat types to expose the
  25028. * underlying instance. Throws if `obj` is not an instance of `T`.
  25029. *
  25030. * This cast is used in the Lite and Full SDK to verify instance types for
  25031. * arguments passed to the public API.
  25032. * @internal
  25033. */
  25034. function cast(obj,
  25035. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25036. constructor) {
  25037. if ('_delegate' in obj) {
  25038. // Unwrap Compat types
  25039. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25040. obj = obj._delegate;
  25041. }
  25042. if (!(obj instanceof constructor)) {
  25043. if (constructor.name === obj.constructor.name) {
  25044. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Type does not match the expected instance. Did you pass a ' +
  25045. `reference from a different Firestore SDK?`);
  25046. }
  25047. else {
  25048. const description = valueDescription(obj);
  25049. throw new FirestoreError(Code.INVALID_ARGUMENT, `Expected type '${constructor.name}', but it was: ${description}`);
  25050. }
  25051. }
  25052. return obj;
  25053. }
  25054. function validatePositiveNumber(functionName, n) {
  25055. if (n <= 0) {
  25056. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires a positive number, but it was: ${n}.`);
  25057. }
  25058. }
  25059. /**
  25060. * @license
  25061. * Copyright 2020 Google LLC
  25062. *
  25063. * Licensed under the Apache License, Version 2.0 (the "License");
  25064. * you may not use this file except in compliance with the License.
  25065. * You may obtain a copy of the License at
  25066. *
  25067. * http://www.apache.org/licenses/LICENSE-2.0
  25068. *
  25069. * Unless required by applicable law or agreed to in writing, software
  25070. * distributed under the License is distributed on an "AS IS" BASIS,
  25071. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25072. * See the License for the specific language governing permissions and
  25073. * limitations under the License.
  25074. */
  25075. /**
  25076. * On Node, only supported data source is a `Uint8Array` for now.
  25077. */
  25078. function toByteStreamReader(source, bytesPerRead) {
  25079. if (!(source instanceof Uint8Array)) {
  25080. throw new FirestoreError(Code.INVALID_ARGUMENT, `NodePlatform.toByteStreamReader expects source to be Uint8Array, got ${valueDescription(source)}`);
  25081. }
  25082. return toByteStreamReaderHelper(source, bytesPerRead);
  25083. }
  25084. /**
  25085. * @license
  25086. * Copyright 2017 Google LLC
  25087. *
  25088. * Licensed under the Apache License, Version 2.0 (the "License");
  25089. * you may not use this file except in compliance with the License.
  25090. * You may obtain a copy of the License at
  25091. *
  25092. * http://www.apache.org/licenses/LICENSE-2.0
  25093. *
  25094. * Unless required by applicable law or agreed to in writing, software
  25095. * distributed under the License is distributed on an "AS IS" BASIS,
  25096. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25097. * See the License for the specific language governing permissions and
  25098. * limitations under the License.
  25099. */
  25100. /*
  25101. * A wrapper implementation of Observer<T> that will dispatch events
  25102. * asynchronously. To allow immediate silencing, a mute call is added which
  25103. * causes events scheduled to no longer be raised.
  25104. */
  25105. class AsyncObserver {
  25106. constructor(observer) {
  25107. this.observer = observer;
  25108. /**
  25109. * When set to true, will not raise future events. Necessary to deal with
  25110. * async detachment of listener.
  25111. */
  25112. this.muted = false;
  25113. }
  25114. next(value) {
  25115. if (this.observer.next) {
  25116. this.scheduleEvent(this.observer.next, value);
  25117. }
  25118. }
  25119. error(error) {
  25120. if (this.observer.error) {
  25121. this.scheduleEvent(this.observer.error, error);
  25122. }
  25123. else {
  25124. logError('Uncaught Error in snapshot listener:', error.toString());
  25125. }
  25126. }
  25127. mute() {
  25128. this.muted = true;
  25129. }
  25130. scheduleEvent(eventHandler, event) {
  25131. if (!this.muted) {
  25132. setTimeout(() => {
  25133. if (!this.muted) {
  25134. eventHandler(event);
  25135. }
  25136. }, 0);
  25137. }
  25138. }
  25139. }
  25140. /**
  25141. * @license
  25142. * Copyright 2020 Google LLC
  25143. *
  25144. * Licensed under the Apache License, Version 2.0 (the "License");
  25145. * you may not use this file except in compliance with the License.
  25146. * You may obtain a copy of the License at
  25147. *
  25148. * http://www.apache.org/licenses/LICENSE-2.0
  25149. *
  25150. * Unless required by applicable law or agreed to in writing, software
  25151. * distributed under the License is distributed on an "AS IS" BASIS,
  25152. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25153. * See the License for the specific language governing permissions and
  25154. * limitations under the License.
  25155. */
  25156. /**
  25157. * A complete element in the bundle stream, together with the byte length it
  25158. * occupies in the stream.
  25159. */
  25160. class SizedBundleElement {
  25161. constructor(payload,
  25162. // How many bytes this element takes to store in the bundle.
  25163. byteLength) {
  25164. this.payload = payload;
  25165. this.byteLength = byteLength;
  25166. }
  25167. isBundleMetadata() {
  25168. return 'metadata' in this.payload;
  25169. }
  25170. }
  25171. /**
  25172. * @license
  25173. * Copyright 2020 Google LLC
  25174. *
  25175. * Licensed under the Apache License, Version 2.0 (the "License");
  25176. * you may not use this file except in compliance with the License.
  25177. * You may obtain a copy of the License at
  25178. *
  25179. * http://www.apache.org/licenses/LICENSE-2.0
  25180. *
  25181. * Unless required by applicable law or agreed to in writing, software
  25182. * distributed under the License is distributed on an "AS IS" BASIS,
  25183. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25184. * See the License for the specific language governing permissions and
  25185. * limitations under the License.
  25186. */
  25187. /**
  25188. * A class representing a bundle.
  25189. *
  25190. * Takes a bundle stream or buffer, and presents abstractions to read bundled
  25191. * elements out of the underlying content.
  25192. */
  25193. class BundleReaderImpl {
  25194. constructor(
  25195. /** The reader to read from underlying binary bundle data source. */
  25196. reader, serializer) {
  25197. this.reader = reader;
  25198. this.serializer = serializer;
  25199. /** Cached bundle metadata. */
  25200. this.metadata = new Deferred();
  25201. /**
  25202. * Internal buffer to hold bundle content, accumulating incomplete element
  25203. * content.
  25204. */
  25205. this.buffer = new Uint8Array();
  25206. this.textDecoder = newTextDecoder();
  25207. // Read the metadata (which is the first element).
  25208. this.nextElementImpl().then(element => {
  25209. if (element && element.isBundleMetadata()) {
  25210. this.metadata.resolve(element.payload.metadata);
  25211. }
  25212. else {
  25213. this.metadata.reject(new Error(`The first element of the bundle is not a metadata, it is
  25214. ${JSON.stringify(element === null || element === void 0 ? void 0 : element.payload)}`));
  25215. }
  25216. }, error => this.metadata.reject(error));
  25217. }
  25218. close() {
  25219. return this.reader.cancel();
  25220. }
  25221. async getMetadata() {
  25222. return this.metadata.promise;
  25223. }
  25224. async nextElement() {
  25225. // Makes sure metadata is read before proceeding.
  25226. await this.getMetadata();
  25227. return this.nextElementImpl();
  25228. }
  25229. /**
  25230. * Reads from the head of internal buffer, and pulling more data from
  25231. * underlying stream if a complete element cannot be found, until an
  25232. * element(including the prefixed length and the JSON string) is found.
  25233. *
  25234. * Once a complete element is read, it is dropped from internal buffer.
  25235. *
  25236. * Returns either the bundled element, or null if we have reached the end of
  25237. * the stream.
  25238. */
  25239. async nextElementImpl() {
  25240. const lengthBuffer = await this.readLength();
  25241. if (lengthBuffer === null) {
  25242. return null;
  25243. }
  25244. const lengthString = this.textDecoder.decode(lengthBuffer);
  25245. const length = Number(lengthString);
  25246. if (isNaN(length)) {
  25247. this.raiseError(`length string (${lengthString}) is not valid number`);
  25248. }
  25249. const jsonString = await this.readJsonString(length);
  25250. return new SizedBundleElement(JSON.parse(jsonString), lengthBuffer.length + length);
  25251. }
  25252. /** First index of '{' from the underlying buffer. */
  25253. indexOfOpenBracket() {
  25254. return this.buffer.findIndex(v => v === '{'.charCodeAt(0));
  25255. }
  25256. /**
  25257. * Reads from the beginning of the internal buffer, until the first '{', and
  25258. * return the content.
  25259. *
  25260. * If reached end of the stream, returns a null.
  25261. */
  25262. async readLength() {
  25263. while (this.indexOfOpenBracket() < 0) {
  25264. const done = await this.pullMoreDataToBuffer();
  25265. if (done) {
  25266. break;
  25267. }
  25268. }
  25269. // Broke out of the loop because underlying stream is closed, and there
  25270. // happens to be no more data to process.
  25271. if (this.buffer.length === 0) {
  25272. return null;
  25273. }
  25274. const position = this.indexOfOpenBracket();
  25275. // Broke out of the loop because underlying stream is closed, but still
  25276. // cannot find an open bracket.
  25277. if (position < 0) {
  25278. this.raiseError('Reached the end of bundle when a length string is expected.');
  25279. }
  25280. const result = this.buffer.slice(0, position);
  25281. // Update the internal buffer to drop the read length.
  25282. this.buffer = this.buffer.slice(position);
  25283. return result;
  25284. }
  25285. /**
  25286. * Reads from a specified position from the internal buffer, for a specified
  25287. * number of bytes, pulling more data from the underlying stream if needed.
  25288. *
  25289. * Returns a string decoded from the read bytes.
  25290. */
  25291. async readJsonString(length) {
  25292. while (this.buffer.length < length) {
  25293. const done = await this.pullMoreDataToBuffer();
  25294. if (done) {
  25295. this.raiseError('Reached the end of bundle when more is expected.');
  25296. }
  25297. }
  25298. const result = this.textDecoder.decode(this.buffer.slice(0, length));
  25299. // Update the internal buffer to drop the read json string.
  25300. this.buffer = this.buffer.slice(length);
  25301. return result;
  25302. }
  25303. raiseError(message) {
  25304. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  25305. this.reader.cancel();
  25306. throw new Error(`Invalid bundle format: ${message}`);
  25307. }
  25308. /**
  25309. * Pulls more data from underlying stream to internal buffer.
  25310. * Returns a boolean indicating whether the stream is finished.
  25311. */
  25312. async pullMoreDataToBuffer() {
  25313. const result = await this.reader.read();
  25314. if (!result.done) {
  25315. const newBuffer = new Uint8Array(this.buffer.length + result.value.length);
  25316. newBuffer.set(this.buffer);
  25317. newBuffer.set(result.value, this.buffer.length);
  25318. this.buffer = newBuffer;
  25319. }
  25320. return result.done;
  25321. }
  25322. }
  25323. function newBundleReader(reader, serializer) {
  25324. return new BundleReaderImpl(reader, serializer);
  25325. }
  25326. /**
  25327. * @license
  25328. * Copyright 2022 Google LLC
  25329. *
  25330. * Licensed under the Apache License, Version 2.0 (the "License");
  25331. * you may not use this file except in compliance with the License.
  25332. * You may obtain a copy of the License at
  25333. *
  25334. * http://www.apache.org/licenses/LICENSE-2.0
  25335. *
  25336. * Unless required by applicable law or agreed to in writing, software
  25337. * distributed under the License is distributed on an "AS IS" BASIS,
  25338. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25339. * See the License for the specific language governing permissions and
  25340. * limitations under the License.
  25341. */
  25342. /**
  25343. * Represents an aggregation that can be performed by Firestore.
  25344. */
  25345. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  25346. class AggregateField {
  25347. constructor() {
  25348. /** A type string to uniquely identify instances of this class. */
  25349. this.type = 'AggregateField';
  25350. }
  25351. }
  25352. /**
  25353. * The results of executing an aggregation query.
  25354. */
  25355. class AggregateQuerySnapshot {
  25356. /** @hideconstructor */
  25357. constructor(query, _data) {
  25358. this._data = _data;
  25359. /** A type string to uniquely identify instances of this class. */
  25360. this.type = 'AggregateQuerySnapshot';
  25361. this.query = query;
  25362. }
  25363. /**
  25364. * Returns the results of the aggregations performed over the underlying
  25365. * query.
  25366. *
  25367. * The keys of the returned object will be the same as those of the
  25368. * `AggregateSpec` object specified to the aggregation method, and the values
  25369. * will be the corresponding aggregation result.
  25370. *
  25371. * @returns The results of the aggregations performed over the underlying
  25372. * query.
  25373. */
  25374. data() {
  25375. return this._data;
  25376. }
  25377. }
  25378. /**
  25379. * @license
  25380. * Copyright 2022 Google LLC
  25381. *
  25382. * Licensed under the Apache License, Version 2.0 (the "License");
  25383. * you may not use this file except in compliance with the License.
  25384. * You may obtain a copy of the License at
  25385. *
  25386. * http://www.apache.org/licenses/LICENSE-2.0
  25387. *
  25388. * Unless required by applicable law or agreed to in writing, software
  25389. * distributed under the License is distributed on an "AS IS" BASIS,
  25390. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25391. * See the License for the specific language governing permissions and
  25392. * limitations under the License.
  25393. */
  25394. /**
  25395. * CountQueryRunner encapsulates the logic needed to run the count aggregation
  25396. * queries.
  25397. */
  25398. class CountQueryRunner {
  25399. constructor(query, datastore, userDataWriter) {
  25400. this.query = query;
  25401. this.datastore = datastore;
  25402. this.userDataWriter = userDataWriter;
  25403. }
  25404. run() {
  25405. return invokeRunAggregationQueryRpc(this.datastore, this.query._query).then(result => {
  25406. hardAssert(result[0] !== undefined);
  25407. const counts = Object.entries(result[0])
  25408. .filter(([key, value]) => key === 'count_alias')
  25409. .map(([key, value]) => this.userDataWriter.convertValue(value));
  25410. const countValue = counts[0];
  25411. hardAssert(typeof countValue === 'number');
  25412. return Promise.resolve(new AggregateQuerySnapshot(this.query, {
  25413. count: countValue
  25414. }));
  25415. });
  25416. }
  25417. }
  25418. /**
  25419. * @license
  25420. * Copyright 2017 Google LLC
  25421. *
  25422. * Licensed under the Apache License, Version 2.0 (the "License");
  25423. * you may not use this file except in compliance with the License.
  25424. * You may obtain a copy of the License at
  25425. *
  25426. * http://www.apache.org/licenses/LICENSE-2.0
  25427. *
  25428. * Unless required by applicable law or agreed to in writing, software
  25429. * distributed under the License is distributed on an "AS IS" BASIS,
  25430. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25431. * See the License for the specific language governing permissions and
  25432. * limitations under the License.
  25433. */
  25434. /**
  25435. * Internal transaction object responsible for accumulating the mutations to
  25436. * perform and the base versions for any documents read.
  25437. */
  25438. class Transaction$2 {
  25439. constructor(datastore) {
  25440. this.datastore = datastore;
  25441. // The version of each document that was read during this transaction.
  25442. this.readVersions = new Map();
  25443. this.mutations = [];
  25444. this.committed = false;
  25445. /**
  25446. * A deferred usage error that occurred previously in this transaction that
  25447. * will cause the transaction to fail once it actually commits.
  25448. */
  25449. this.lastWriteError = null;
  25450. /**
  25451. * Set of documents that have been written in the transaction.
  25452. *
  25453. * When there's more than one write to the same key in a transaction, any
  25454. * writes after the first are handled differently.
  25455. */
  25456. this.writtenDocs = new Set();
  25457. }
  25458. async lookup(keys) {
  25459. this.ensureCommitNotCalled();
  25460. if (this.mutations.length > 0) {
  25461. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Firestore transactions require all reads to be executed before all writes.');
  25462. }
  25463. const docs = await invokeBatchGetDocumentsRpc(this.datastore, keys);
  25464. docs.forEach(doc => this.recordVersion(doc));
  25465. return docs;
  25466. }
  25467. set(key, data) {
  25468. this.write(data.toMutation(key, this.precondition(key)));
  25469. this.writtenDocs.add(key.toString());
  25470. }
  25471. update(key, data) {
  25472. try {
  25473. this.write(data.toMutation(key, this.preconditionForUpdate(key)));
  25474. }
  25475. catch (e) {
  25476. this.lastWriteError = e;
  25477. }
  25478. this.writtenDocs.add(key.toString());
  25479. }
  25480. delete(key) {
  25481. this.write(new DeleteMutation(key, this.precondition(key)));
  25482. this.writtenDocs.add(key.toString());
  25483. }
  25484. async commit() {
  25485. this.ensureCommitNotCalled();
  25486. if (this.lastWriteError) {
  25487. throw this.lastWriteError;
  25488. }
  25489. const unwritten = this.readVersions;
  25490. // For each mutation, note that the doc was written.
  25491. this.mutations.forEach(mutation => {
  25492. unwritten.delete(mutation.key.toString());
  25493. });
  25494. // For each document that was read but not written to, we want to perform
  25495. // a `verify` operation.
  25496. unwritten.forEach((_, path) => {
  25497. const key = DocumentKey.fromPath(path);
  25498. this.mutations.push(new VerifyMutation(key, this.precondition(key)));
  25499. });
  25500. await invokeCommitRpc(this.datastore, this.mutations);
  25501. this.committed = true;
  25502. }
  25503. recordVersion(doc) {
  25504. let docVersion;
  25505. if (doc.isFoundDocument()) {
  25506. docVersion = doc.version;
  25507. }
  25508. else if (doc.isNoDocument()) {
  25509. // Represent a deleted doc using SnapshotVersion.min().
  25510. docVersion = SnapshotVersion.min();
  25511. }
  25512. else {
  25513. throw fail();
  25514. }
  25515. const existingVersion = this.readVersions.get(doc.key.toString());
  25516. if (existingVersion) {
  25517. if (!docVersion.isEqual(existingVersion)) {
  25518. // This transaction will fail no matter what.
  25519. throw new FirestoreError(Code.ABORTED, 'Document version changed between two reads.');
  25520. }
  25521. }
  25522. else {
  25523. this.readVersions.set(doc.key.toString(), docVersion);
  25524. }
  25525. }
  25526. /**
  25527. * Returns the version of this document when it was read in this transaction,
  25528. * as a precondition, or no precondition if it was not read.
  25529. */
  25530. precondition(key) {
  25531. const version = this.readVersions.get(key.toString());
  25532. if (!this.writtenDocs.has(key.toString()) && version) {
  25533. if (version.isEqual(SnapshotVersion.min())) {
  25534. return Precondition.exists(false);
  25535. }
  25536. else {
  25537. return Precondition.updateTime(version);
  25538. }
  25539. }
  25540. else {
  25541. return Precondition.none();
  25542. }
  25543. }
  25544. /**
  25545. * Returns the precondition for a document if the operation is an update.
  25546. */
  25547. preconditionForUpdate(key) {
  25548. const version = this.readVersions.get(key.toString());
  25549. // The first time a document is written, we want to take into account the
  25550. // read time and existence
  25551. if (!this.writtenDocs.has(key.toString()) && version) {
  25552. if (version.isEqual(SnapshotVersion.min())) {
  25553. // The document doesn't exist, so fail the transaction.
  25554. // This has to be validated locally because you can't send a
  25555. // precondition that a document does not exist without changing the
  25556. // semantics of the backend write to be an insert. This is the reverse
  25557. // of what we want, since we want to assert that the document doesn't
  25558. // exist but then send the update and have it fail. Since we can't
  25559. // express that to the backend, we have to validate locally.
  25560. // Note: this can change once we can send separate verify writes in the
  25561. // transaction.
  25562. throw new FirestoreError(Code.INVALID_ARGUMENT, "Can't update a document that doesn't exist.");
  25563. }
  25564. // Document exists, base precondition on document update time.
  25565. return Precondition.updateTime(version);
  25566. }
  25567. else {
  25568. // Document was not read, so we just use the preconditions for a blind
  25569. // update.
  25570. return Precondition.exists(true);
  25571. }
  25572. }
  25573. write(mutation) {
  25574. this.ensureCommitNotCalled();
  25575. this.mutations.push(mutation);
  25576. }
  25577. ensureCommitNotCalled() {
  25578. }
  25579. }
  25580. /**
  25581. * @license
  25582. * Copyright 2019 Google LLC
  25583. *
  25584. * Licensed under the Apache License, Version 2.0 (the "License");
  25585. * you may not use this file except in compliance with the License.
  25586. * You may obtain a copy of the License at
  25587. *
  25588. * http://www.apache.org/licenses/LICENSE-2.0
  25589. *
  25590. * Unless required by applicable law or agreed to in writing, software
  25591. * distributed under the License is distributed on an "AS IS" BASIS,
  25592. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25593. * See the License for the specific language governing permissions and
  25594. * limitations under the License.
  25595. */
  25596. /**
  25597. * TransactionRunner encapsulates the logic needed to run and retry transactions
  25598. * with backoff.
  25599. */
  25600. class TransactionRunner {
  25601. constructor(asyncQueue, datastore, options, updateFunction, deferred) {
  25602. this.asyncQueue = asyncQueue;
  25603. this.datastore = datastore;
  25604. this.options = options;
  25605. this.updateFunction = updateFunction;
  25606. this.deferred = deferred;
  25607. this.attemptsRemaining = options.maxAttempts;
  25608. this.backoff = new ExponentialBackoff(this.asyncQueue, "transaction_retry" /* TimerId.TransactionRetry */);
  25609. }
  25610. /** Runs the transaction and sets the result on deferred. */
  25611. run() {
  25612. this.attemptsRemaining -= 1;
  25613. this.runWithBackOff();
  25614. }
  25615. runWithBackOff() {
  25616. this.backoff.backoffAndRun(async () => {
  25617. const transaction = new Transaction$2(this.datastore);
  25618. const userPromise = this.tryRunUpdateFunction(transaction);
  25619. if (userPromise) {
  25620. userPromise
  25621. .then(result => {
  25622. this.asyncQueue.enqueueAndForget(() => {
  25623. return transaction
  25624. .commit()
  25625. .then(() => {
  25626. this.deferred.resolve(result);
  25627. })
  25628. .catch(commitError => {
  25629. this.handleTransactionError(commitError);
  25630. });
  25631. });
  25632. })
  25633. .catch(userPromiseError => {
  25634. this.handleTransactionError(userPromiseError);
  25635. });
  25636. }
  25637. });
  25638. }
  25639. tryRunUpdateFunction(transaction) {
  25640. try {
  25641. const userPromise = this.updateFunction(transaction);
  25642. if (isNullOrUndefined(userPromise) ||
  25643. !userPromise.catch ||
  25644. !userPromise.then) {
  25645. this.deferred.reject(Error('Transaction callback must return a Promise'));
  25646. return null;
  25647. }
  25648. return userPromise;
  25649. }
  25650. catch (error) {
  25651. // Do not retry errors thrown by user provided updateFunction.
  25652. this.deferred.reject(error);
  25653. return null;
  25654. }
  25655. }
  25656. handleTransactionError(error) {
  25657. if (this.attemptsRemaining > 0 && this.isRetryableTransactionError(error)) {
  25658. this.attemptsRemaining -= 1;
  25659. this.asyncQueue.enqueueAndForget(() => {
  25660. this.runWithBackOff();
  25661. return Promise.resolve();
  25662. });
  25663. }
  25664. else {
  25665. this.deferred.reject(error);
  25666. }
  25667. }
  25668. isRetryableTransactionError(error) {
  25669. if (error.name === 'FirebaseError') {
  25670. // In transactions, the backend will fail outdated reads with FAILED_PRECONDITION and
  25671. // non-matching document versions with ABORTED. These errors should be retried.
  25672. const code = error.code;
  25673. return (code === 'aborted' ||
  25674. code === 'failed-precondition' ||
  25675. code === 'already-exists' ||
  25676. !isPermanentError(code));
  25677. }
  25678. return false;
  25679. }
  25680. }
  25681. /**
  25682. * @license
  25683. * Copyright 2017 Google LLC
  25684. *
  25685. * Licensed under the Apache License, Version 2.0 (the "License");
  25686. * you may not use this file except in compliance with the License.
  25687. * You may obtain a copy of the License at
  25688. *
  25689. * http://www.apache.org/licenses/LICENSE-2.0
  25690. *
  25691. * Unless required by applicable law or agreed to in writing, software
  25692. * distributed under the License is distributed on an "AS IS" BASIS,
  25693. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25694. * See the License for the specific language governing permissions and
  25695. * limitations under the License.
  25696. */
  25697. const LOG_TAG$2 = 'FirestoreClient';
  25698. const MAX_CONCURRENT_LIMBO_RESOLUTIONS = 100;
  25699. /**
  25700. * FirestoreClient is a top-level class that constructs and owns all of the
  25701. * pieces of the client SDK architecture. It is responsible for creating the
  25702. * async queue that is shared by all of the other components in the system.
  25703. */
  25704. class FirestoreClient {
  25705. constructor(authCredentials, appCheckCredentials,
  25706. /**
  25707. * Asynchronous queue responsible for all of our internal processing. When
  25708. * we get incoming work from the user (via public API) or the network
  25709. * (incoming GRPC messages), we should always schedule onto this queue.
  25710. * This ensures all of our work is properly serialized (e.g. we don't
  25711. * start processing a new operation while the previous one is waiting for
  25712. * an async I/O to complete).
  25713. */
  25714. asyncQueue, databaseInfo) {
  25715. this.authCredentials = authCredentials;
  25716. this.appCheckCredentials = appCheckCredentials;
  25717. this.asyncQueue = asyncQueue;
  25718. this.databaseInfo = databaseInfo;
  25719. this.user = User.UNAUTHENTICATED;
  25720. this.clientId = AutoId.newId();
  25721. this.authCredentialListener = () => Promise.resolve();
  25722. this.appCheckCredentialListener = () => Promise.resolve();
  25723. this.authCredentials.start(asyncQueue, async (user) => {
  25724. logDebug(LOG_TAG$2, 'Received user=', user.uid);
  25725. await this.authCredentialListener(user);
  25726. this.user = user;
  25727. });
  25728. this.appCheckCredentials.start(asyncQueue, newAppCheckToken => {
  25729. logDebug(LOG_TAG$2, 'Received new app check token=', newAppCheckToken);
  25730. return this.appCheckCredentialListener(newAppCheckToken, this.user);
  25731. });
  25732. }
  25733. async getConfiguration() {
  25734. return {
  25735. asyncQueue: this.asyncQueue,
  25736. databaseInfo: this.databaseInfo,
  25737. clientId: this.clientId,
  25738. authCredentials: this.authCredentials,
  25739. appCheckCredentials: this.appCheckCredentials,
  25740. initialUser: this.user,
  25741. maxConcurrentLimboResolutions: MAX_CONCURRENT_LIMBO_RESOLUTIONS
  25742. };
  25743. }
  25744. setCredentialChangeListener(listener) {
  25745. this.authCredentialListener = listener;
  25746. }
  25747. setAppCheckTokenChangeListener(listener) {
  25748. this.appCheckCredentialListener = listener;
  25749. }
  25750. /**
  25751. * Checks that the client has not been terminated. Ensures that other methods on
  25752. * this class cannot be called after the client is terminated.
  25753. */
  25754. verifyNotTerminated() {
  25755. if (this.asyncQueue.isShuttingDown) {
  25756. throw new FirestoreError(Code.FAILED_PRECONDITION, 'The client has already been terminated.');
  25757. }
  25758. }
  25759. terminate() {
  25760. this.asyncQueue.enterRestrictedMode();
  25761. const deferred = new Deferred();
  25762. this.asyncQueue.enqueueAndForgetEvenWhileRestricted(async () => {
  25763. try {
  25764. if (this.onlineComponents) {
  25765. await this.onlineComponents.terminate();
  25766. }
  25767. if (this.offlineComponents) {
  25768. await this.offlineComponents.terminate();
  25769. }
  25770. // The credentials provider must be terminated after shutting down the
  25771. // RemoteStore as it will prevent the RemoteStore from retrieving auth
  25772. // tokens.
  25773. this.authCredentials.shutdown();
  25774. this.appCheckCredentials.shutdown();
  25775. deferred.resolve();
  25776. }
  25777. catch (e) {
  25778. const firestoreError = wrapInUserErrorIfRecoverable(e, `Failed to shutdown persistence`);
  25779. deferred.reject(firestoreError);
  25780. }
  25781. });
  25782. return deferred.promise;
  25783. }
  25784. }
  25785. async function setOfflineComponentProvider(client, offlineComponentProvider) {
  25786. client.asyncQueue.verifyOperationInProgress();
  25787. logDebug(LOG_TAG$2, 'Initializing OfflineComponentProvider');
  25788. const configuration = await client.getConfiguration();
  25789. await offlineComponentProvider.initialize(configuration);
  25790. let currentUser = configuration.initialUser;
  25791. client.setCredentialChangeListener(async (user) => {
  25792. if (!currentUser.isEqual(user)) {
  25793. await localStoreHandleUserChange(offlineComponentProvider.localStore, user);
  25794. currentUser = user;
  25795. }
  25796. });
  25797. // When a user calls clearPersistence() in one client, all other clients
  25798. // need to be terminated to allow the delete to succeed.
  25799. offlineComponentProvider.persistence.setDatabaseDeletedListener(() => client.terminate());
  25800. client.offlineComponents = offlineComponentProvider;
  25801. }
  25802. async function setOnlineComponentProvider(client, onlineComponentProvider) {
  25803. client.asyncQueue.verifyOperationInProgress();
  25804. const offlineComponentProvider = await ensureOfflineComponents(client);
  25805. logDebug(LOG_TAG$2, 'Initializing OnlineComponentProvider');
  25806. const configuration = await client.getConfiguration();
  25807. await onlineComponentProvider.initialize(offlineComponentProvider, configuration);
  25808. // The CredentialChangeListener of the online component provider takes
  25809. // precedence over the offline component provider.
  25810. client.setCredentialChangeListener(user => remoteStoreHandleCredentialChange(onlineComponentProvider.remoteStore, user));
  25811. client.setAppCheckTokenChangeListener((_, user) => remoteStoreHandleCredentialChange(onlineComponentProvider.remoteStore, user));
  25812. client.onlineComponents = onlineComponentProvider;
  25813. }
  25814. async function ensureOfflineComponents(client) {
  25815. if (!client.offlineComponents) {
  25816. logDebug(LOG_TAG$2, 'Using default OfflineComponentProvider');
  25817. await setOfflineComponentProvider(client, new MemoryOfflineComponentProvider());
  25818. }
  25819. return client.offlineComponents;
  25820. }
  25821. async function ensureOnlineComponents(client) {
  25822. if (!client.onlineComponents) {
  25823. logDebug(LOG_TAG$2, 'Using default OnlineComponentProvider');
  25824. await setOnlineComponentProvider(client, new OnlineComponentProvider());
  25825. }
  25826. return client.onlineComponents;
  25827. }
  25828. function getPersistence(client) {
  25829. return ensureOfflineComponents(client).then(c => c.persistence);
  25830. }
  25831. function getLocalStore(client) {
  25832. return ensureOfflineComponents(client).then(c => c.localStore);
  25833. }
  25834. function getRemoteStore(client) {
  25835. return ensureOnlineComponents(client).then(c => c.remoteStore);
  25836. }
  25837. function getSyncEngine(client) {
  25838. return ensureOnlineComponents(client).then(c => c.syncEngine);
  25839. }
  25840. function getDatastore(client) {
  25841. return ensureOnlineComponents(client).then(c => c.datastore);
  25842. }
  25843. async function getEventManager(client) {
  25844. const onlineComponentProvider = await ensureOnlineComponents(client);
  25845. const eventManager = onlineComponentProvider.eventManager;
  25846. eventManager.onListen = syncEngineListen.bind(null, onlineComponentProvider.syncEngine);
  25847. eventManager.onUnlisten = syncEngineUnlisten.bind(null, onlineComponentProvider.syncEngine);
  25848. return eventManager;
  25849. }
  25850. /** Enables the network connection and re-enqueues all pending operations. */
  25851. function firestoreClientEnableNetwork(client) {
  25852. return client.asyncQueue.enqueue(async () => {
  25853. const persistence = await getPersistence(client);
  25854. const remoteStore = await getRemoteStore(client);
  25855. persistence.setNetworkEnabled(true);
  25856. return remoteStoreEnableNetwork(remoteStore);
  25857. });
  25858. }
  25859. /** Disables the network connection. Pending operations will not complete. */
  25860. function firestoreClientDisableNetwork(client) {
  25861. return client.asyncQueue.enqueue(async () => {
  25862. const persistence = await getPersistence(client);
  25863. const remoteStore = await getRemoteStore(client);
  25864. persistence.setNetworkEnabled(false);
  25865. return remoteStoreDisableNetwork(remoteStore);
  25866. });
  25867. }
  25868. /**
  25869. * Returns a Promise that resolves when all writes that were pending at the time
  25870. * this method was called received server acknowledgement. An acknowledgement
  25871. * can be either acceptance or rejection.
  25872. */
  25873. function firestoreClientWaitForPendingWrites(client) {
  25874. const deferred = new Deferred();
  25875. client.asyncQueue.enqueueAndForget(async () => {
  25876. const syncEngine = await getSyncEngine(client);
  25877. return syncEngineRegisterPendingWritesCallback(syncEngine, deferred);
  25878. });
  25879. return deferred.promise;
  25880. }
  25881. function firestoreClientListen(client, query, options, observer) {
  25882. const wrappedObserver = new AsyncObserver(observer);
  25883. const listener = new QueryListener(query, wrappedObserver, options);
  25884. client.asyncQueue.enqueueAndForget(async () => {
  25885. const eventManager = await getEventManager(client);
  25886. return eventManagerListen(eventManager, listener);
  25887. });
  25888. return () => {
  25889. wrappedObserver.mute();
  25890. client.asyncQueue.enqueueAndForget(async () => {
  25891. const eventManager = await getEventManager(client);
  25892. return eventManagerUnlisten(eventManager, listener);
  25893. });
  25894. };
  25895. }
  25896. function firestoreClientGetDocumentFromLocalCache(client, docKey) {
  25897. const deferred = new Deferred();
  25898. client.asyncQueue.enqueueAndForget(async () => {
  25899. const localStore = await getLocalStore(client);
  25900. return readDocumentFromCache(localStore, docKey, deferred);
  25901. });
  25902. return deferred.promise;
  25903. }
  25904. function firestoreClientGetDocumentViaSnapshotListener(client, key, options = {}) {
  25905. const deferred = new Deferred();
  25906. client.asyncQueue.enqueueAndForget(async () => {
  25907. const eventManager = await getEventManager(client);
  25908. return readDocumentViaSnapshotListener(eventManager, client.asyncQueue, key, options, deferred);
  25909. });
  25910. return deferred.promise;
  25911. }
  25912. function firestoreClientGetDocumentsFromLocalCache(client, query) {
  25913. const deferred = new Deferred();
  25914. client.asyncQueue.enqueueAndForget(async () => {
  25915. const localStore = await getLocalStore(client);
  25916. return executeQueryFromCache(localStore, query, deferred);
  25917. });
  25918. return deferred.promise;
  25919. }
  25920. function firestoreClientGetDocumentsViaSnapshotListener(client, query, options = {}) {
  25921. const deferred = new Deferred();
  25922. client.asyncQueue.enqueueAndForget(async () => {
  25923. const eventManager = await getEventManager(client);
  25924. return executeQueryViaSnapshotListener(eventManager, client.asyncQueue, query, options, deferred);
  25925. });
  25926. return deferred.promise;
  25927. }
  25928. function firestoreClientWrite(client, mutations) {
  25929. const deferred = new Deferred();
  25930. client.asyncQueue.enqueueAndForget(async () => {
  25931. const syncEngine = await getSyncEngine(client);
  25932. return syncEngineWrite(syncEngine, mutations, deferred);
  25933. });
  25934. return deferred.promise;
  25935. }
  25936. function firestoreClientAddSnapshotsInSyncListener(client, observer) {
  25937. const wrappedObserver = new AsyncObserver(observer);
  25938. client.asyncQueue.enqueueAndForget(async () => {
  25939. const eventManager = await getEventManager(client);
  25940. return addSnapshotsInSyncListener(eventManager, wrappedObserver);
  25941. });
  25942. return () => {
  25943. wrappedObserver.mute();
  25944. client.asyncQueue.enqueueAndForget(async () => {
  25945. const eventManager = await getEventManager(client);
  25946. return removeSnapshotsInSyncListener(eventManager, wrappedObserver);
  25947. });
  25948. };
  25949. }
  25950. /**
  25951. * Takes an updateFunction in which a set of reads and writes can be performed
  25952. * atomically. In the updateFunction, the client can read and write values
  25953. * using the supplied transaction object. After the updateFunction, all
  25954. * changes will be committed. If a retryable error occurs (ex: some other
  25955. * client has changed any of the data referenced), then the updateFunction
  25956. * will be called again after a backoff. If the updateFunction still fails
  25957. * after all retries, then the transaction will be rejected.
  25958. *
  25959. * The transaction object passed to the updateFunction contains methods for
  25960. * accessing documents and collections. Unlike other datastore access, data
  25961. * accessed with the transaction will not reflect local changes that have not
  25962. * been committed. For this reason, it is required that all reads are
  25963. * performed before any writes. Transactions must be performed while online.
  25964. */
  25965. function firestoreClientTransaction(client, updateFunction, options) {
  25966. const deferred = new Deferred();
  25967. client.asyncQueue.enqueueAndForget(async () => {
  25968. const datastore = await getDatastore(client);
  25969. new TransactionRunner(client.asyncQueue, datastore, options, updateFunction, deferred).run();
  25970. });
  25971. return deferred.promise;
  25972. }
  25973. function firestoreClientRunCountQuery(client, query, userDataWriter) {
  25974. const deferred = new Deferred();
  25975. client.asyncQueue.enqueueAndForget(async () => {
  25976. try {
  25977. const remoteStore = await getRemoteStore(client);
  25978. if (!canUseNetwork(remoteStore)) {
  25979. deferred.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get count result because the client is offline.'));
  25980. }
  25981. else {
  25982. const datastore = await getDatastore(client);
  25983. const result = new CountQueryRunner(query, datastore, userDataWriter).run();
  25984. deferred.resolve(result);
  25985. }
  25986. }
  25987. catch (e) {
  25988. deferred.reject(e);
  25989. }
  25990. });
  25991. return deferred.promise;
  25992. }
  25993. async function readDocumentFromCache(localStore, docKey, result) {
  25994. try {
  25995. const document = await localStoreReadDocument(localStore, docKey);
  25996. if (document.isFoundDocument()) {
  25997. result.resolve(document);
  25998. }
  25999. else if (document.isNoDocument()) {
  26000. result.resolve(null);
  26001. }
  26002. else {
  26003. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get document from cache. (However, this document may ' +
  26004. "exist on the server. Run again without setting 'source' in " +
  26005. 'the GetOptions to attempt to retrieve the document from the ' +
  26006. 'server.)'));
  26007. }
  26008. }
  26009. catch (e) {
  26010. const firestoreError = wrapInUserErrorIfRecoverable(e, `Failed to get document '${docKey} from cache`);
  26011. result.reject(firestoreError);
  26012. }
  26013. }
  26014. /**
  26015. * Retrieves a latency-compensated document from the backend via a
  26016. * SnapshotListener.
  26017. */
  26018. function readDocumentViaSnapshotListener(eventManager, asyncQueue, key, options, result) {
  26019. const wrappedObserver = new AsyncObserver({
  26020. next: (snap) => {
  26021. // Remove query first before passing event to user to avoid
  26022. // user actions affecting the now stale query.
  26023. asyncQueue.enqueueAndForget(() => eventManagerUnlisten(eventManager, listener));
  26024. const exists = snap.docs.has(key);
  26025. if (!exists && snap.fromCache) {
  26026. // TODO(dimond): If we're online and the document doesn't
  26027. // exist then we resolve with a doc.exists set to false. If
  26028. // we're offline however, we reject the Promise in this
  26029. // case. Two options: 1) Cache the negative response from
  26030. // the server so we can deliver that even when you're
  26031. // offline 2) Actually reject the Promise in the online case
  26032. // if the document doesn't exist.
  26033. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get document because the client is offline.'));
  26034. }
  26035. else if (exists &&
  26036. snap.fromCache &&
  26037. options &&
  26038. options.source === 'server') {
  26039. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get document from server. (However, this ' +
  26040. 'document does exist in the local cache. Run again ' +
  26041. 'without setting source to "server" to ' +
  26042. 'retrieve the cached document.)'));
  26043. }
  26044. else {
  26045. result.resolve(snap);
  26046. }
  26047. },
  26048. error: e => result.reject(e)
  26049. });
  26050. const listener = new QueryListener(newQueryForPath(key.path), wrappedObserver, {
  26051. includeMetadataChanges: true,
  26052. waitForSyncWhenOnline: true
  26053. });
  26054. return eventManagerListen(eventManager, listener);
  26055. }
  26056. async function executeQueryFromCache(localStore, query, result) {
  26057. try {
  26058. const queryResult = await localStoreExecuteQuery(localStore, query,
  26059. /* usePreviousResults= */ true);
  26060. const view = new View(query, queryResult.remoteKeys);
  26061. const viewDocChanges = view.computeDocChanges(queryResult.documents);
  26062. const viewChange = view.applyChanges(viewDocChanges,
  26063. /* updateLimboDocuments= */ false);
  26064. result.resolve(viewChange.snapshot);
  26065. }
  26066. catch (e) {
  26067. const firestoreError = wrapInUserErrorIfRecoverable(e, `Failed to execute query '${query} against cache`);
  26068. result.reject(firestoreError);
  26069. }
  26070. }
  26071. /**
  26072. * Retrieves a latency-compensated query snapshot from the backend via a
  26073. * SnapshotListener.
  26074. */
  26075. function executeQueryViaSnapshotListener(eventManager, asyncQueue, query, options, result) {
  26076. const wrappedObserver = new AsyncObserver({
  26077. next: snapshot => {
  26078. // Remove query first before passing event to user to avoid
  26079. // user actions affecting the now stale query.
  26080. asyncQueue.enqueueAndForget(() => eventManagerUnlisten(eventManager, listener));
  26081. if (snapshot.fromCache && options.source === 'server') {
  26082. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get documents from server. (However, these ' +
  26083. 'documents may exist in the local cache. Run again ' +
  26084. 'without setting source to "server" to ' +
  26085. 'retrieve the cached documents.)'));
  26086. }
  26087. else {
  26088. result.resolve(snapshot);
  26089. }
  26090. },
  26091. error: e => result.reject(e)
  26092. });
  26093. const listener = new QueryListener(query, wrappedObserver, {
  26094. includeMetadataChanges: true,
  26095. waitForSyncWhenOnline: true
  26096. });
  26097. return eventManagerListen(eventManager, listener);
  26098. }
  26099. function firestoreClientLoadBundle(client, databaseId, data, resultTask) {
  26100. const reader = createBundleReader(data, newSerializer(databaseId));
  26101. client.asyncQueue.enqueueAndForget(async () => {
  26102. syncEngineLoadBundle(await getSyncEngine(client), reader, resultTask);
  26103. });
  26104. }
  26105. function firestoreClientGetNamedQuery(client, queryName) {
  26106. return client.asyncQueue.enqueue(async () => localStoreGetNamedQuery(await getLocalStore(client), queryName));
  26107. }
  26108. function createBundleReader(data, serializer) {
  26109. let content;
  26110. if (typeof data === 'string') {
  26111. content = newTextEncoder().encode(data);
  26112. }
  26113. else {
  26114. content = data;
  26115. }
  26116. return newBundleReader(toByteStreamReader(content), serializer);
  26117. }
  26118. /**
  26119. * @license
  26120. * Copyright 2020 Google LLC
  26121. *
  26122. * Licensed under the Apache License, Version 2.0 (the "License");
  26123. * you may not use this file except in compliance with the License.
  26124. * You may obtain a copy of the License at
  26125. *
  26126. * http://www.apache.org/licenses/LICENSE-2.0
  26127. *
  26128. * Unless required by applicable law or agreed to in writing, software
  26129. * distributed under the License is distributed on an "AS IS" BASIS,
  26130. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26131. * See the License for the specific language governing permissions and
  26132. * limitations under the License.
  26133. */
  26134. const LOG_TAG$1 = 'ComponentProvider';
  26135. /**
  26136. * An instance map that ensures only one Datastore exists per Firestore
  26137. * instance.
  26138. */
  26139. const datastoreInstances = new Map();
  26140. /**
  26141. * Removes all components associated with the provided instance. Must be called
  26142. * when the `Firestore` instance is terminated.
  26143. */
  26144. function removeComponents(firestore) {
  26145. const datastore = datastoreInstances.get(firestore);
  26146. if (datastore) {
  26147. logDebug(LOG_TAG$1, 'Removing Datastore');
  26148. datastoreInstances.delete(firestore);
  26149. datastore.terminate();
  26150. }
  26151. }
  26152. function makeDatabaseInfo(databaseId, appId, persistenceKey, settings) {
  26153. return new DatabaseInfo(databaseId, appId, persistenceKey, settings.host, settings.ssl, settings.experimentalForceLongPolling, settings.experimentalAutoDetectLongPolling, settings.useFetchStreams);
  26154. }
  26155. /**
  26156. * @license
  26157. * Copyright 2020 Google LLC
  26158. *
  26159. * Licensed under the Apache License, Version 2.0 (the "License");
  26160. * you may not use this file except in compliance with the License.
  26161. * You may obtain a copy of the License at
  26162. *
  26163. * http://www.apache.org/licenses/LICENSE-2.0
  26164. *
  26165. * Unless required by applicable law or agreed to in writing, software
  26166. * distributed under the License is distributed on an "AS IS" BASIS,
  26167. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26168. * See the License for the specific language governing permissions and
  26169. * limitations under the License.
  26170. */
  26171. // settings() defaults:
  26172. const DEFAULT_HOST = 'firestore.googleapis.com';
  26173. const DEFAULT_SSL = true;
  26174. /**
  26175. * A concrete type describing all the values that can be applied via a
  26176. * user-supplied `FirestoreSettings` object. This is a separate type so that
  26177. * defaults can be supplied and the value can be checked for equality.
  26178. */
  26179. class FirestoreSettingsImpl {
  26180. constructor(settings) {
  26181. var _a;
  26182. if (settings.host === undefined) {
  26183. if (settings.ssl !== undefined) {
  26184. throw new FirestoreError(Code.INVALID_ARGUMENT, "Can't provide ssl option if host option is not set");
  26185. }
  26186. this.host = DEFAULT_HOST;
  26187. this.ssl = DEFAULT_SSL;
  26188. }
  26189. else {
  26190. this.host = settings.host;
  26191. this.ssl = (_a = settings.ssl) !== null && _a !== void 0 ? _a : DEFAULT_SSL;
  26192. }
  26193. this.credentials = settings.credentials;
  26194. this.ignoreUndefinedProperties = !!settings.ignoreUndefinedProperties;
  26195. if (settings.cacheSizeBytes === undefined) {
  26196. this.cacheSizeBytes = LRU_DEFAULT_CACHE_SIZE_BYTES;
  26197. }
  26198. else {
  26199. if (settings.cacheSizeBytes !== LRU_COLLECTION_DISABLED &&
  26200. settings.cacheSizeBytes < LRU_MINIMUM_CACHE_SIZE_BYTES) {
  26201. throw new FirestoreError(Code.INVALID_ARGUMENT, `cacheSizeBytes must be at least ${LRU_MINIMUM_CACHE_SIZE_BYTES}`);
  26202. }
  26203. else {
  26204. this.cacheSizeBytes = settings.cacheSizeBytes;
  26205. }
  26206. }
  26207. this.experimentalForceLongPolling = !!settings.experimentalForceLongPolling;
  26208. this.experimentalAutoDetectLongPolling =
  26209. !!settings.experimentalAutoDetectLongPolling;
  26210. this.useFetchStreams = !!settings.useFetchStreams;
  26211. validateIsNotUsedTogether('experimentalForceLongPolling', settings.experimentalForceLongPolling, 'experimentalAutoDetectLongPolling', settings.experimentalAutoDetectLongPolling);
  26212. }
  26213. isEqual(other) {
  26214. return (this.host === other.host &&
  26215. this.ssl === other.ssl &&
  26216. this.credentials === other.credentials &&
  26217. this.cacheSizeBytes === other.cacheSizeBytes &&
  26218. this.experimentalForceLongPolling ===
  26219. other.experimentalForceLongPolling &&
  26220. this.experimentalAutoDetectLongPolling ===
  26221. other.experimentalAutoDetectLongPolling &&
  26222. this.ignoreUndefinedProperties === other.ignoreUndefinedProperties &&
  26223. this.useFetchStreams === other.useFetchStreams);
  26224. }
  26225. }
  26226. /**
  26227. * @license
  26228. * Copyright 2020 Google LLC
  26229. *
  26230. * Licensed under the Apache License, Version 2.0 (the "License");
  26231. * you may not use this file except in compliance with the License.
  26232. * You may obtain a copy of the License at
  26233. *
  26234. * http://www.apache.org/licenses/LICENSE-2.0
  26235. *
  26236. * Unless required by applicable law or agreed to in writing, software
  26237. * distributed under the License is distributed on an "AS IS" BASIS,
  26238. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26239. * See the License for the specific language governing permissions and
  26240. * limitations under the License.
  26241. */
  26242. /**
  26243. * The Cloud Firestore service interface.
  26244. *
  26245. * Do not call this constructor directly. Instead, use {@link (getFirestore:1)}.
  26246. */
  26247. class Firestore$1 {
  26248. /** @hideconstructor */
  26249. constructor(_authCredentials, _appCheckCredentials, _databaseId, _app) {
  26250. this._authCredentials = _authCredentials;
  26251. this._appCheckCredentials = _appCheckCredentials;
  26252. this._databaseId = _databaseId;
  26253. this._app = _app;
  26254. /**
  26255. * Whether it's a Firestore or Firestore Lite instance.
  26256. */
  26257. this.type = 'firestore-lite';
  26258. this._persistenceKey = '(lite)';
  26259. this._settings = new FirestoreSettingsImpl({});
  26260. this._settingsFrozen = false;
  26261. }
  26262. /**
  26263. * The {@link @firebase/app#FirebaseApp} associated with this `Firestore` service
  26264. * instance.
  26265. */
  26266. get app() {
  26267. if (!this._app) {
  26268. throw new FirestoreError(Code.FAILED_PRECONDITION, "Firestore was not initialized using the Firebase SDK. 'app' is " +
  26269. 'not available');
  26270. }
  26271. return this._app;
  26272. }
  26273. get _initialized() {
  26274. return this._settingsFrozen;
  26275. }
  26276. get _terminated() {
  26277. return this._terminateTask !== undefined;
  26278. }
  26279. _setSettings(settings) {
  26280. if (this._settingsFrozen) {
  26281. throw new FirestoreError(Code.FAILED_PRECONDITION, 'Firestore has already been started and its settings can no longer ' +
  26282. 'be changed. You can only modify settings before calling any other ' +
  26283. 'methods on a Firestore object.');
  26284. }
  26285. this._settings = new FirestoreSettingsImpl(settings);
  26286. if (settings.credentials !== undefined) {
  26287. this._authCredentials = makeAuthCredentialsProvider(settings.credentials);
  26288. }
  26289. }
  26290. _getSettings() {
  26291. return this._settings;
  26292. }
  26293. _freezeSettings() {
  26294. this._settingsFrozen = true;
  26295. return this._settings;
  26296. }
  26297. _delete() {
  26298. if (!this._terminateTask) {
  26299. this._terminateTask = this._terminate();
  26300. }
  26301. return this._terminateTask;
  26302. }
  26303. /** Returns a JSON-serializable representation of this `Firestore` instance. */
  26304. toJSON() {
  26305. return {
  26306. app: this._app,
  26307. databaseId: this._databaseId,
  26308. settings: this._settings
  26309. };
  26310. }
  26311. /**
  26312. * Terminates all components used by this client. Subclasses can override
  26313. * this method to clean up their own dependencies, but must also call this
  26314. * method.
  26315. *
  26316. * Only ever called once.
  26317. */
  26318. _terminate() {
  26319. removeComponents(this);
  26320. return Promise.resolve();
  26321. }
  26322. }
  26323. /**
  26324. * Modify this instance to communicate with the Cloud Firestore emulator.
  26325. *
  26326. * Note: This must be called before this instance has been used to do any
  26327. * operations.
  26328. *
  26329. * @param firestore - The `Firestore` instance to configure to connect to the
  26330. * emulator.
  26331. * @param host - the emulator host (ex: localhost).
  26332. * @param port - the emulator port (ex: 9000).
  26333. * @param options.mockUserToken - the mock auth token to use for unit testing
  26334. * Security Rules.
  26335. */
  26336. function connectFirestoreEmulator(firestore, host, port, options = {}) {
  26337. var _a;
  26338. firestore = cast(firestore, Firestore$1);
  26339. const settings = firestore._getSettings();
  26340. if (settings.host !== DEFAULT_HOST && settings.host !== host) {
  26341. logWarn('Host has been set in both settings() and useEmulator(), emulator host ' +
  26342. 'will be used');
  26343. }
  26344. firestore._setSettings(Object.assign(Object.assign({}, settings), { host: `${host}:${port}`, ssl: false }));
  26345. if (options.mockUserToken) {
  26346. let token;
  26347. let user;
  26348. if (typeof options.mockUserToken === 'string') {
  26349. token = options.mockUserToken;
  26350. user = User.MOCK_USER;
  26351. }
  26352. else {
  26353. // Let createMockUserToken validate first (catches common mistakes like
  26354. // invalid field "uid" and missing field "sub" / "user_id".)
  26355. token = util.createMockUserToken(options.mockUserToken, (_a = firestore._app) === null || _a === void 0 ? void 0 : _a.options.projectId);
  26356. const uid = options.mockUserToken.sub || options.mockUserToken.user_id;
  26357. if (!uid) {
  26358. throw new FirestoreError(Code.INVALID_ARGUMENT, "mockUserToken must contain 'sub' or 'user_id' field!");
  26359. }
  26360. user = new User(uid);
  26361. }
  26362. firestore._authCredentials = new EmulatorAuthCredentialsProvider(new OAuthToken(token, user));
  26363. }
  26364. }
  26365. /**
  26366. * @license
  26367. * Copyright 2020 Google LLC
  26368. *
  26369. * Licensed under the Apache License, Version 2.0 (the "License");
  26370. * you may not use this file except in compliance with the License.
  26371. * You may obtain a copy of the License at
  26372. *
  26373. * http://www.apache.org/licenses/LICENSE-2.0
  26374. *
  26375. * Unless required by applicable law or agreed to in writing, software
  26376. * distributed under the License is distributed on an "AS IS" BASIS,
  26377. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26378. * See the License for the specific language governing permissions and
  26379. * limitations under the License.
  26380. */
  26381. /**
  26382. * A `DocumentReference` refers to a document location in a Firestore database
  26383. * and can be used to write, read, or listen to the location. The document at
  26384. * the referenced location may or may not exist.
  26385. */
  26386. class DocumentReference {
  26387. /** @hideconstructor */
  26388. constructor(firestore,
  26389. /**
  26390. * If provided, the `FirestoreDataConverter` associated with this instance.
  26391. */
  26392. converter, _key) {
  26393. this.converter = converter;
  26394. this._key = _key;
  26395. /** The type of this Firestore reference. */
  26396. this.type = 'document';
  26397. this.firestore = firestore;
  26398. }
  26399. get _path() {
  26400. return this._key.path;
  26401. }
  26402. /**
  26403. * The document's identifier within its collection.
  26404. */
  26405. get id() {
  26406. return this._key.path.lastSegment();
  26407. }
  26408. /**
  26409. * A string representing the path of the referenced document (relative
  26410. * to the root of the database).
  26411. */
  26412. get path() {
  26413. return this._key.path.canonicalString();
  26414. }
  26415. /**
  26416. * The collection this `DocumentReference` belongs to.
  26417. */
  26418. get parent() {
  26419. return new CollectionReference(this.firestore, this.converter, this._key.path.popLast());
  26420. }
  26421. withConverter(converter) {
  26422. return new DocumentReference(this.firestore, converter, this._key);
  26423. }
  26424. }
  26425. /**
  26426. * A `Query` refers to a query which you can read or listen to. You can also
  26427. * construct refined `Query` objects by adding filters and ordering.
  26428. */
  26429. class Query {
  26430. // This is the lite version of the Query class in the main SDK.
  26431. /** @hideconstructor protected */
  26432. constructor(firestore,
  26433. /**
  26434. * If provided, the `FirestoreDataConverter` associated with this instance.
  26435. */
  26436. converter, _query) {
  26437. this.converter = converter;
  26438. this._query = _query;
  26439. /** The type of this Firestore reference. */
  26440. this.type = 'query';
  26441. this.firestore = firestore;
  26442. }
  26443. withConverter(converter) {
  26444. return new Query(this.firestore, converter, this._query);
  26445. }
  26446. }
  26447. /**
  26448. * A `CollectionReference` object can be used for adding documents, getting
  26449. * document references, and querying for documents (using {@link query}).
  26450. */
  26451. class CollectionReference extends Query {
  26452. /** @hideconstructor */
  26453. constructor(firestore, converter, _path) {
  26454. super(firestore, converter, newQueryForPath(_path));
  26455. this._path = _path;
  26456. /** The type of this Firestore reference. */
  26457. this.type = 'collection';
  26458. }
  26459. /** The collection's identifier. */
  26460. get id() {
  26461. return this._query.path.lastSegment();
  26462. }
  26463. /**
  26464. * A string representing the path of the referenced collection (relative
  26465. * to the root of the database).
  26466. */
  26467. get path() {
  26468. return this._query.path.canonicalString();
  26469. }
  26470. /**
  26471. * A reference to the containing `DocumentReference` if this is a
  26472. * subcollection. If this isn't a subcollection, the reference is null.
  26473. */
  26474. get parent() {
  26475. const parentPath = this._path.popLast();
  26476. if (parentPath.isEmpty()) {
  26477. return null;
  26478. }
  26479. else {
  26480. return new DocumentReference(this.firestore,
  26481. /* converter= */ null, new DocumentKey(parentPath));
  26482. }
  26483. }
  26484. withConverter(converter) {
  26485. return new CollectionReference(this.firestore, converter, this._path);
  26486. }
  26487. }
  26488. function collection(parent, path, ...pathSegments) {
  26489. parent = util.getModularInstance(parent);
  26490. validateNonEmptyArgument('collection', 'path', path);
  26491. if (parent instanceof Firestore$1) {
  26492. const absolutePath = ResourcePath.fromString(path, ...pathSegments);
  26493. validateCollectionPath(absolutePath);
  26494. return new CollectionReference(parent, /* converter= */ null, absolutePath);
  26495. }
  26496. else {
  26497. if (!(parent instanceof DocumentReference) &&
  26498. !(parent instanceof CollectionReference)) {
  26499. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Expected first argument to collection() to be a CollectionReference, ' +
  26500. 'a DocumentReference or FirebaseFirestore');
  26501. }
  26502. const absolutePath = parent._path.child(ResourcePath.fromString(path, ...pathSegments));
  26503. validateCollectionPath(absolutePath);
  26504. return new CollectionReference(parent.firestore,
  26505. /* converter= */ null, absolutePath);
  26506. }
  26507. }
  26508. // TODO(firestorelite): Consider using ErrorFactory -
  26509. // https://github.com/firebase/firebase-js-sdk/blob/0131e1f/packages/util/src/errors.ts#L106
  26510. /**
  26511. * Creates and returns a new `Query` instance that includes all documents in the
  26512. * database that are contained in a collection or subcollection with the
  26513. * given `collectionId`.
  26514. *
  26515. * @param firestore - A reference to the root `Firestore` instance.
  26516. * @param collectionId - Identifies the collections to query over. Every
  26517. * collection or subcollection with this ID as the last segment of its path
  26518. * will be included. Cannot contain a slash.
  26519. * @returns The created `Query`.
  26520. */
  26521. function collectionGroup(firestore, collectionId) {
  26522. firestore = cast(firestore, Firestore$1);
  26523. validateNonEmptyArgument('collectionGroup', 'collection id', collectionId);
  26524. if (collectionId.indexOf('/') >= 0) {
  26525. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid collection ID '${collectionId}' passed to function ` +
  26526. `collectionGroup(). Collection IDs must not contain '/'.`);
  26527. }
  26528. return new Query(firestore,
  26529. /* converter= */ null, newQueryForCollectionGroup(collectionId));
  26530. }
  26531. function doc(parent, path, ...pathSegments) {
  26532. parent = util.getModularInstance(parent);
  26533. // We allow omission of 'pathString' but explicitly prohibit passing in both
  26534. // 'undefined' and 'null'.
  26535. if (arguments.length === 1) {
  26536. path = AutoId.newId();
  26537. }
  26538. validateNonEmptyArgument('doc', 'path', path);
  26539. if (parent instanceof Firestore$1) {
  26540. const absolutePath = ResourcePath.fromString(path, ...pathSegments);
  26541. validateDocumentPath(absolutePath);
  26542. return new DocumentReference(parent,
  26543. /* converter= */ null, new DocumentKey(absolutePath));
  26544. }
  26545. else {
  26546. if (!(parent instanceof DocumentReference) &&
  26547. !(parent instanceof CollectionReference)) {
  26548. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Expected first argument to collection() to be a CollectionReference, ' +
  26549. 'a DocumentReference or FirebaseFirestore');
  26550. }
  26551. const absolutePath = parent._path.child(ResourcePath.fromString(path, ...pathSegments));
  26552. validateDocumentPath(absolutePath);
  26553. return new DocumentReference(parent.firestore, parent instanceof CollectionReference ? parent.converter : null, new DocumentKey(absolutePath));
  26554. }
  26555. }
  26556. /**
  26557. * Returns true if the provided references are equal.
  26558. *
  26559. * @param left - A reference to compare.
  26560. * @param right - A reference to compare.
  26561. * @returns true if the references point to the same location in the same
  26562. * Firestore database.
  26563. */
  26564. function refEqual(left, right) {
  26565. left = util.getModularInstance(left);
  26566. right = util.getModularInstance(right);
  26567. if ((left instanceof DocumentReference ||
  26568. left instanceof CollectionReference) &&
  26569. (right instanceof DocumentReference || right instanceof CollectionReference)) {
  26570. return (left.firestore === right.firestore &&
  26571. left.path === right.path &&
  26572. left.converter === right.converter);
  26573. }
  26574. return false;
  26575. }
  26576. /**
  26577. * Returns true if the provided queries point to the same collection and apply
  26578. * the same constraints.
  26579. *
  26580. * @param left - A `Query` to compare.
  26581. * @param right - A `Query` to compare.
  26582. * @returns true if the references point to the same location in the same
  26583. * Firestore database.
  26584. */
  26585. function queryEqual(left, right) {
  26586. left = util.getModularInstance(left);
  26587. right = util.getModularInstance(right);
  26588. if (left instanceof Query && right instanceof Query) {
  26589. return (left.firestore === right.firestore &&
  26590. queryEquals(left._query, right._query) &&
  26591. left.converter === right.converter);
  26592. }
  26593. return false;
  26594. }
  26595. /**
  26596. * @license
  26597. * Copyright 2020 Google LLC
  26598. *
  26599. * Licensed under the Apache License, Version 2.0 (the "License");
  26600. * you may not use this file except in compliance with the License.
  26601. * You may obtain a copy of the License at
  26602. *
  26603. * http://www.apache.org/licenses/LICENSE-2.0
  26604. *
  26605. * Unless required by applicable law or agreed to in writing, software
  26606. * distributed under the License is distributed on an "AS IS" BASIS,
  26607. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26608. * See the License for the specific language governing permissions and
  26609. * limitations under the License.
  26610. */
  26611. const LOG_TAG = 'AsyncQueue';
  26612. class AsyncQueueImpl {
  26613. constructor() {
  26614. // The last promise in the queue.
  26615. this.tail = Promise.resolve();
  26616. // A list of retryable operations. Retryable operations are run in order and
  26617. // retried with backoff.
  26618. this.retryableOps = [];
  26619. // Is this AsyncQueue being shut down? Once it is set to true, it will not
  26620. // be changed again.
  26621. this._isShuttingDown = false;
  26622. // Operations scheduled to be queued in the future. Operations are
  26623. // automatically removed after they are run or canceled.
  26624. this.delayedOperations = [];
  26625. // visible for testing
  26626. this.failure = null;
  26627. // Flag set while there's an outstanding AsyncQueue operation, used for
  26628. // assertion sanity-checks.
  26629. this.operationInProgress = false;
  26630. // Enabled during shutdown on Safari to prevent future access to IndexedDB.
  26631. this.skipNonRestrictedTasks = false;
  26632. // List of TimerIds to fast-forward delays for.
  26633. this.timerIdsToSkip = [];
  26634. // Backoff timer used to schedule retries for retryable operations
  26635. this.backoff = new ExponentialBackoff(this, "async_queue_retry" /* TimerId.AsyncQueueRetry */);
  26636. // Visibility handler that triggers an immediate retry of all retryable
  26637. // operations. Meant to speed up recovery when we regain file system access
  26638. // after page comes into foreground.
  26639. this.visibilityHandler = () => {
  26640. this.backoff.skipBackoff();
  26641. };
  26642. }
  26643. get isShuttingDown() {
  26644. return this._isShuttingDown;
  26645. }
  26646. /**
  26647. * Adds a new operation to the queue without waiting for it to complete (i.e.
  26648. * we ignore the Promise result).
  26649. */
  26650. enqueueAndForget(op) {
  26651. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  26652. this.enqueue(op);
  26653. }
  26654. enqueueAndForgetEvenWhileRestricted(op) {
  26655. this.verifyNotFailed();
  26656. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  26657. this.enqueueInternal(op);
  26658. }
  26659. enterRestrictedMode(purgeExistingTasks) {
  26660. if (!this._isShuttingDown) {
  26661. this._isShuttingDown = true;
  26662. this.skipNonRestrictedTasks = purgeExistingTasks || false;
  26663. }
  26664. }
  26665. enqueue(op) {
  26666. this.verifyNotFailed();
  26667. if (this._isShuttingDown) {
  26668. // Return a Promise which never resolves.
  26669. return new Promise(() => { });
  26670. }
  26671. // Create a deferred Promise that we can return to the callee. This
  26672. // allows us to return a "hanging Promise" only to the callee and still
  26673. // advance the queue even when the operation is not run.
  26674. const task = new Deferred();
  26675. return this.enqueueInternal(() => {
  26676. if (this._isShuttingDown && this.skipNonRestrictedTasks) {
  26677. // We do not resolve 'task'
  26678. return Promise.resolve();
  26679. }
  26680. op().then(task.resolve, task.reject);
  26681. return task.promise;
  26682. }).then(() => task.promise);
  26683. }
  26684. enqueueRetryable(op) {
  26685. this.enqueueAndForget(() => {
  26686. this.retryableOps.push(op);
  26687. return this.retryNextOp();
  26688. });
  26689. }
  26690. /**
  26691. * Runs the next operation from the retryable queue. If the operation fails,
  26692. * reschedules with backoff.
  26693. */
  26694. async retryNextOp() {
  26695. if (this.retryableOps.length === 0) {
  26696. return;
  26697. }
  26698. try {
  26699. await this.retryableOps[0]();
  26700. this.retryableOps.shift();
  26701. this.backoff.reset();
  26702. }
  26703. catch (e) {
  26704. if (isIndexedDbTransactionError(e)) {
  26705. logDebug(LOG_TAG, 'Operation failed with retryable error: ' + e);
  26706. }
  26707. else {
  26708. throw e; // Failure will be handled by AsyncQueue
  26709. }
  26710. }
  26711. if (this.retryableOps.length > 0) {
  26712. // If there are additional operations, we re-schedule `retryNextOp()`.
  26713. // This is necessary to run retryable operations that failed during
  26714. // their initial attempt since we don't know whether they are already
  26715. // enqueued. If, for example, `op1`, `op2`, `op3` are enqueued and `op1`
  26716. // needs to be re-run, we will run `op1`, `op1`, `op2` using the
  26717. // already enqueued calls to `retryNextOp()`. `op3()` will then run in the
  26718. // call scheduled here.
  26719. // Since `backoffAndRun()` cancels an existing backoff and schedules a
  26720. // new backoff on every call, there is only ever a single additional
  26721. // operation in the queue.
  26722. this.backoff.backoffAndRun(() => this.retryNextOp());
  26723. }
  26724. }
  26725. enqueueInternal(op) {
  26726. const newTail = this.tail.then(() => {
  26727. this.operationInProgress = true;
  26728. return op()
  26729. .catch((error) => {
  26730. this.failure = error;
  26731. this.operationInProgress = false;
  26732. const message = getMessageOrStack(error);
  26733. logError('INTERNAL UNHANDLED ERROR: ', message);
  26734. // Re-throw the error so that this.tail becomes a rejected Promise and
  26735. // all further attempts to chain (via .then) will just short-circuit
  26736. // and return the rejected Promise.
  26737. throw error;
  26738. })
  26739. .then(result => {
  26740. this.operationInProgress = false;
  26741. return result;
  26742. });
  26743. });
  26744. this.tail = newTail;
  26745. return newTail;
  26746. }
  26747. enqueueAfterDelay(timerId, delayMs, op) {
  26748. this.verifyNotFailed();
  26749. // Fast-forward delays for timerIds that have been overriden.
  26750. if (this.timerIdsToSkip.indexOf(timerId) > -1) {
  26751. delayMs = 0;
  26752. }
  26753. const delayedOp = DelayedOperation.createAndSchedule(this, timerId, delayMs, op, removedOp => this.removeDelayedOperation(removedOp));
  26754. this.delayedOperations.push(delayedOp);
  26755. return delayedOp;
  26756. }
  26757. verifyNotFailed() {
  26758. if (this.failure) {
  26759. fail();
  26760. }
  26761. }
  26762. verifyOperationInProgress() {
  26763. }
  26764. /**
  26765. * Waits until all currently queued tasks are finished executing. Delayed
  26766. * operations are not run.
  26767. */
  26768. async drain() {
  26769. // Operations in the queue prior to draining may have enqueued additional
  26770. // operations. Keep draining the queue until the tail is no longer advanced,
  26771. // which indicates that no more new operations were enqueued and that all
  26772. // operations were executed.
  26773. let currentTail;
  26774. do {
  26775. currentTail = this.tail;
  26776. await currentTail;
  26777. } while (currentTail !== this.tail);
  26778. }
  26779. /**
  26780. * For Tests: Determine if a delayed operation with a particular TimerId
  26781. * exists.
  26782. */
  26783. containsDelayedOperation(timerId) {
  26784. for (const op of this.delayedOperations) {
  26785. if (op.timerId === timerId) {
  26786. return true;
  26787. }
  26788. }
  26789. return false;
  26790. }
  26791. /**
  26792. * For Tests: Runs some or all delayed operations early.
  26793. *
  26794. * @param lastTimerId - Delayed operations up to and including this TimerId
  26795. * will be drained. Pass TimerId.All to run all delayed operations.
  26796. * @returns a Promise that resolves once all operations have been run.
  26797. */
  26798. runAllDelayedOperationsUntil(lastTimerId) {
  26799. // Note that draining may generate more delayed ops, so we do that first.
  26800. return this.drain().then(() => {
  26801. // Run ops in the same order they'd run if they ran naturally.
  26802. this.delayedOperations.sort((a, b) => a.targetTimeMs - b.targetTimeMs);
  26803. for (const op of this.delayedOperations) {
  26804. op.skipDelay();
  26805. if (lastTimerId !== "all" /* TimerId.All */ && op.timerId === lastTimerId) {
  26806. break;
  26807. }
  26808. }
  26809. return this.drain();
  26810. });
  26811. }
  26812. /**
  26813. * For Tests: Skip all subsequent delays for a timer id.
  26814. */
  26815. skipDelaysForTimerId(timerId) {
  26816. this.timerIdsToSkip.push(timerId);
  26817. }
  26818. /** Called once a DelayedOperation is run or canceled. */
  26819. removeDelayedOperation(op) {
  26820. // NOTE: indexOf / slice are O(n), but delayedOperations is expected to be small.
  26821. const index = this.delayedOperations.indexOf(op);
  26822. this.delayedOperations.splice(index, 1);
  26823. }
  26824. }
  26825. function newAsyncQueue() {
  26826. return new AsyncQueueImpl();
  26827. }
  26828. /**
  26829. * Chrome includes Error.message in Error.stack. Other browsers do not.
  26830. * This returns expected output of message + stack when available.
  26831. * @param error - Error or FirestoreError
  26832. */
  26833. function getMessageOrStack(error) {
  26834. let message = error.message || '';
  26835. if (error.stack) {
  26836. if (error.stack.includes(error.message)) {
  26837. message = error.stack;
  26838. }
  26839. else {
  26840. message = error.message + '\n' + error.stack;
  26841. }
  26842. }
  26843. return message;
  26844. }
  26845. /**
  26846. * @license
  26847. * Copyright 2020 Google LLC
  26848. *
  26849. * Licensed under the Apache License, Version 2.0 (the "License");
  26850. * you may not use this file except in compliance with the License.
  26851. * You may obtain a copy of the License at
  26852. *
  26853. * http://www.apache.org/licenses/LICENSE-2.0
  26854. *
  26855. * Unless required by applicable law or agreed to in writing, software
  26856. * distributed under the License is distributed on an "AS IS" BASIS,
  26857. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26858. * See the License for the specific language governing permissions and
  26859. * limitations under the License.
  26860. */
  26861. /**
  26862. * Represents the task of loading a Firestore bundle. It provides progress of bundle
  26863. * loading, as well as task completion and error events.
  26864. *
  26865. * The API is compatible with `Promise<LoadBundleTaskProgress>`.
  26866. */
  26867. class LoadBundleTask {
  26868. constructor() {
  26869. this._progressObserver = {};
  26870. this._taskCompletionResolver = new Deferred();
  26871. this._lastProgress = {
  26872. taskState: 'Running',
  26873. totalBytes: 0,
  26874. totalDocuments: 0,
  26875. bytesLoaded: 0,
  26876. documentsLoaded: 0
  26877. };
  26878. }
  26879. /**
  26880. * Registers functions to listen to bundle loading progress events.
  26881. * @param next - Called when there is a progress update from bundle loading. Typically `next` calls occur
  26882. * each time a Firestore document is loaded from the bundle.
  26883. * @param error - Called when an error occurs during bundle loading. The task aborts after reporting the
  26884. * error, and there should be no more updates after this.
  26885. * @param complete - Called when the loading task is complete.
  26886. */
  26887. onProgress(next, error, complete) {
  26888. this._progressObserver = {
  26889. next,
  26890. error,
  26891. complete
  26892. };
  26893. }
  26894. /**
  26895. * Implements the `Promise<LoadBundleTaskProgress>.catch` interface.
  26896. *
  26897. * @param onRejected - Called when an error occurs during bundle loading.
  26898. */
  26899. catch(onRejected) {
  26900. return this._taskCompletionResolver.promise.catch(onRejected);
  26901. }
  26902. /**
  26903. * Implements the `Promise<LoadBundleTaskProgress>.then` interface.
  26904. *
  26905. * @param onFulfilled - Called on the completion of the loading task with a final `LoadBundleTaskProgress` update.
  26906. * The update will always have its `taskState` set to `"Success"`.
  26907. * @param onRejected - Called when an error occurs during bundle loading.
  26908. */
  26909. then(onFulfilled, onRejected) {
  26910. return this._taskCompletionResolver.promise.then(onFulfilled, onRejected);
  26911. }
  26912. /**
  26913. * Notifies all observers that bundle loading has completed, with a provided
  26914. * `LoadBundleTaskProgress` object.
  26915. *
  26916. * @private
  26917. */
  26918. _completeWith(progress) {
  26919. this._updateProgress(progress);
  26920. if (this._progressObserver.complete) {
  26921. this._progressObserver.complete();
  26922. }
  26923. this._taskCompletionResolver.resolve(progress);
  26924. }
  26925. /**
  26926. * Notifies all observers that bundle loading has failed, with a provided
  26927. * `Error` as the reason.
  26928. *
  26929. * @private
  26930. */
  26931. _failWith(error) {
  26932. this._lastProgress.taskState = 'Error';
  26933. if (this._progressObserver.next) {
  26934. this._progressObserver.next(this._lastProgress);
  26935. }
  26936. if (this._progressObserver.error) {
  26937. this._progressObserver.error(error);
  26938. }
  26939. this._taskCompletionResolver.reject(error);
  26940. }
  26941. /**
  26942. * Notifies a progress update of loading a bundle.
  26943. * @param progress - The new progress.
  26944. *
  26945. * @private
  26946. */
  26947. _updateProgress(progress) {
  26948. this._lastProgress = progress;
  26949. if (this._progressObserver.next) {
  26950. this._progressObserver.next(progress);
  26951. }
  26952. }
  26953. }
  26954. /**
  26955. * @license
  26956. * Copyright 2020 Google LLC
  26957. *
  26958. * Licensed under the Apache License, Version 2.0 (the "License");
  26959. * you may not use this file except in compliance with the License.
  26960. * You may obtain a copy of the License at
  26961. *
  26962. * http://www.apache.org/licenses/LICENSE-2.0
  26963. *
  26964. * Unless required by applicable law or agreed to in writing, software
  26965. * distributed under the License is distributed on an "AS IS" BASIS,
  26966. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26967. * See the License for the specific language governing permissions and
  26968. * limitations under the License.
  26969. */
  26970. /** DOMException error code constants. */
  26971. const DOM_EXCEPTION_INVALID_STATE = 11;
  26972. const DOM_EXCEPTION_ABORTED = 20;
  26973. const DOM_EXCEPTION_QUOTA_EXCEEDED = 22;
  26974. /**
  26975. * Constant used to indicate the LRU garbage collection should be disabled.
  26976. * Set this value as the `cacheSizeBytes` on the settings passed to the
  26977. * {@link Firestore} instance.
  26978. */
  26979. const CACHE_SIZE_UNLIMITED = LRU_COLLECTION_DISABLED;
  26980. /**
  26981. * The Cloud Firestore service interface.
  26982. *
  26983. * Do not call this constructor directly. Instead, use {@link (getFirestore:1)}.
  26984. */
  26985. class Firestore extends Firestore$1 {
  26986. /** @hideconstructor */
  26987. constructor(authCredentialsProvider, appCheckCredentialsProvider, databaseId, app) {
  26988. super(authCredentialsProvider, appCheckCredentialsProvider, databaseId, app);
  26989. /**
  26990. * Whether it's a {@link Firestore} or Firestore Lite instance.
  26991. */
  26992. this.type = 'firestore';
  26993. this._queue = newAsyncQueue();
  26994. this._persistenceKey = (app === null || app === void 0 ? void 0 : app.name) || '[DEFAULT]';
  26995. }
  26996. _terminate() {
  26997. if (!this._firestoreClient) {
  26998. // The client must be initialized to ensure that all subsequent API
  26999. // usage throws an exception.
  27000. configureFirestore(this);
  27001. }
  27002. return this._firestoreClient.terminate();
  27003. }
  27004. }
  27005. /**
  27006. * Initializes a new instance of {@link Firestore} with the provided settings.
  27007. * Can only be called before any other function, including
  27008. * {@link (getFirestore:1)}. If the custom settings are empty, this function is
  27009. * equivalent to calling {@link (getFirestore:1)}.
  27010. *
  27011. * @param app - The {@link @firebase/app#FirebaseApp} with which the {@link Firestore} instance will
  27012. * be associated.
  27013. * @param settings - A settings object to configure the {@link Firestore} instance.
  27014. * @param databaseId - The name of database.
  27015. * @returns A newly initialized {@link Firestore} instance.
  27016. */
  27017. function initializeFirestore(app$1, settings, databaseId) {
  27018. if (!databaseId) {
  27019. databaseId = DEFAULT_DATABASE_NAME;
  27020. }
  27021. const provider = app._getProvider(app$1, 'firestore');
  27022. if (provider.isInitialized(databaseId)) {
  27023. const existingInstance = provider.getImmediate({
  27024. identifier: databaseId
  27025. });
  27026. const initialSettings = provider.getOptions(databaseId);
  27027. if (util.deepEqual(initialSettings, settings)) {
  27028. return existingInstance;
  27029. }
  27030. else {
  27031. throw new FirestoreError(Code.FAILED_PRECONDITION, 'initializeFirestore() has already been called with ' +
  27032. 'different options. To avoid this error, call initializeFirestore() with the ' +
  27033. 'same options as when it was originally called, or call getFirestore() to return the' +
  27034. ' already initialized instance.');
  27035. }
  27036. }
  27037. if (settings.cacheSizeBytes !== undefined &&
  27038. settings.cacheSizeBytes !== CACHE_SIZE_UNLIMITED &&
  27039. settings.cacheSizeBytes < LRU_MINIMUM_CACHE_SIZE_BYTES) {
  27040. throw new FirestoreError(Code.INVALID_ARGUMENT, `cacheSizeBytes must be at least ${LRU_MINIMUM_CACHE_SIZE_BYTES}`);
  27041. }
  27042. return provider.initialize({
  27043. options: settings,
  27044. instanceIdentifier: databaseId
  27045. });
  27046. }
  27047. function getFirestore(appOrDatabaseId, optionalDatabaseId) {
  27048. const app$1 = typeof appOrDatabaseId === 'object' ? appOrDatabaseId : app.getApp();
  27049. const databaseId = typeof appOrDatabaseId === 'string'
  27050. ? appOrDatabaseId
  27051. : optionalDatabaseId || DEFAULT_DATABASE_NAME;
  27052. const db = app._getProvider(app$1, 'firestore').getImmediate({
  27053. identifier: databaseId
  27054. });
  27055. if (!db._initialized) {
  27056. const emulator = util.getDefaultEmulatorHostnameAndPort('firestore');
  27057. if (emulator) {
  27058. connectFirestoreEmulator(db, ...emulator);
  27059. }
  27060. }
  27061. return db;
  27062. }
  27063. /**
  27064. * @internal
  27065. */
  27066. function ensureFirestoreConfigured(firestore) {
  27067. if (!firestore._firestoreClient) {
  27068. configureFirestore(firestore);
  27069. }
  27070. firestore._firestoreClient.verifyNotTerminated();
  27071. return firestore._firestoreClient;
  27072. }
  27073. function configureFirestore(firestore) {
  27074. var _a;
  27075. const settings = firestore._freezeSettings();
  27076. const databaseInfo = makeDatabaseInfo(firestore._databaseId, ((_a = firestore._app) === null || _a === void 0 ? void 0 : _a.options.appId) || '', firestore._persistenceKey, settings);
  27077. firestore._firestoreClient = new FirestoreClient(firestore._authCredentials, firestore._appCheckCredentials, firestore._queue, databaseInfo);
  27078. }
  27079. /**
  27080. * Attempts to enable persistent storage, if possible.
  27081. *
  27082. * Must be called before any other functions (other than
  27083. * {@link initializeFirestore}, {@link (getFirestore:1)} or
  27084. * {@link clearIndexedDbPersistence}.
  27085. *
  27086. * If this fails, `enableIndexedDbPersistence()` will reject the promise it
  27087. * returns. Note that even after this failure, the {@link Firestore} instance will
  27088. * remain usable, however offline persistence will be disabled.
  27089. *
  27090. * There are several reasons why this can fail, which can be identified by
  27091. * the `code` on the error.
  27092. *
  27093. * * failed-precondition: The app is already open in another browser tab.
  27094. * * unimplemented: The browser is incompatible with the offline
  27095. * persistence implementation.
  27096. *
  27097. * @param firestore - The {@link Firestore} instance to enable persistence for.
  27098. * @param persistenceSettings - Optional settings object to configure
  27099. * persistence.
  27100. * @returns A `Promise` that represents successfully enabling persistent storage.
  27101. */
  27102. function enableIndexedDbPersistence(firestore, persistenceSettings) {
  27103. firestore = cast(firestore, Firestore);
  27104. verifyNotInitialized(firestore);
  27105. const client = ensureFirestoreConfigured(firestore);
  27106. const settings = firestore._freezeSettings();
  27107. const onlineComponentProvider = new OnlineComponentProvider();
  27108. const offlineComponentProvider = new IndexedDbOfflineComponentProvider(onlineComponentProvider, settings.cacheSizeBytes, persistenceSettings === null || persistenceSettings === void 0 ? void 0 : persistenceSettings.forceOwnership);
  27109. return setPersistenceProviders(client, onlineComponentProvider, offlineComponentProvider);
  27110. }
  27111. /**
  27112. * Attempts to enable multi-tab persistent storage, if possible. If enabled
  27113. * across all tabs, all operations share access to local persistence, including
  27114. * shared execution of queries and latency-compensated local document updates
  27115. * across all connected instances.
  27116. *
  27117. * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise
  27118. * it returns. Note that even after this failure, the {@link Firestore} instance will
  27119. * remain usable, however offline persistence will be disabled.
  27120. *
  27121. * There are several reasons why this can fail, which can be identified by
  27122. * the `code` on the error.
  27123. *
  27124. * * failed-precondition: The app is already open in another browser tab and
  27125. * multi-tab is not enabled.
  27126. * * unimplemented: The browser is incompatible with the offline
  27127. * persistence implementation.
  27128. *
  27129. * @param firestore - The {@link Firestore} instance to enable persistence for.
  27130. * @returns A `Promise` that represents successfully enabling persistent
  27131. * storage.
  27132. */
  27133. function enableMultiTabIndexedDbPersistence(firestore) {
  27134. firestore = cast(firestore, Firestore);
  27135. verifyNotInitialized(firestore);
  27136. const client = ensureFirestoreConfigured(firestore);
  27137. const settings = firestore._freezeSettings();
  27138. const onlineComponentProvider = new OnlineComponentProvider();
  27139. const offlineComponentProvider = new MultiTabOfflineComponentProvider(onlineComponentProvider, settings.cacheSizeBytes);
  27140. return setPersistenceProviders(client, onlineComponentProvider, offlineComponentProvider);
  27141. }
  27142. /**
  27143. * Registers both the `OfflineComponentProvider` and `OnlineComponentProvider`.
  27144. * If the operation fails with a recoverable error (see
  27145. * `canRecoverFromIndexedDbError()` below), the returned Promise is rejected
  27146. * but the client remains usable.
  27147. */
  27148. function setPersistenceProviders(client, onlineComponentProvider, offlineComponentProvider) {
  27149. const persistenceResult = new Deferred();
  27150. return client.asyncQueue
  27151. .enqueue(async () => {
  27152. try {
  27153. await setOfflineComponentProvider(client, offlineComponentProvider);
  27154. await setOnlineComponentProvider(client, onlineComponentProvider);
  27155. persistenceResult.resolve();
  27156. }
  27157. catch (e) {
  27158. const error = e;
  27159. if (!canFallbackFromIndexedDbError(error)) {
  27160. throw error;
  27161. }
  27162. logWarn('Error enabling offline persistence. Falling back to ' +
  27163. 'persistence disabled: ' +
  27164. error);
  27165. persistenceResult.reject(error);
  27166. }
  27167. })
  27168. .then(() => persistenceResult.promise);
  27169. }
  27170. /**
  27171. * Decides whether the provided error allows us to gracefully disable
  27172. * persistence (as opposed to crashing the client).
  27173. */
  27174. function canFallbackFromIndexedDbError(error) {
  27175. if (error.name === 'FirebaseError') {
  27176. return (error.code === Code.FAILED_PRECONDITION ||
  27177. error.code === Code.UNIMPLEMENTED);
  27178. }
  27179. else if (typeof DOMException !== 'undefined' &&
  27180. error instanceof DOMException) {
  27181. // There are a few known circumstances where we can open IndexedDb but
  27182. // trying to read/write will fail (e.g. quota exceeded). For
  27183. // well-understood cases, we attempt to detect these and then gracefully
  27184. // fall back to memory persistence.
  27185. // NOTE: Rather than continue to add to this list, we could decide to
  27186. // always fall back, with the risk that we might accidentally hide errors
  27187. // representing actual SDK bugs.
  27188. return (
  27189. // When the browser is out of quota we could get either quota exceeded
  27190. // or an aborted error depending on whether the error happened during
  27191. // schema migration.
  27192. error.code === DOM_EXCEPTION_QUOTA_EXCEEDED ||
  27193. error.code === DOM_EXCEPTION_ABORTED ||
  27194. // Firefox Private Browsing mode disables IndexedDb and returns
  27195. // INVALID_STATE for any usage.
  27196. error.code === DOM_EXCEPTION_INVALID_STATE);
  27197. }
  27198. return true;
  27199. }
  27200. /**
  27201. * Clears the persistent storage. This includes pending writes and cached
  27202. * documents.
  27203. *
  27204. * Must be called while the {@link Firestore} instance is not started (after the app is
  27205. * terminated or when the app is first initialized). On startup, this function
  27206. * must be called before other functions (other than {@link
  27207. * initializeFirestore} or {@link (getFirestore:1)})). If the {@link Firestore}
  27208. * instance is still running, the promise will be rejected with the error code
  27209. * of `failed-precondition`.
  27210. *
  27211. * Note: `clearIndexedDbPersistence()` is primarily intended to help write
  27212. * reliable tests that use Cloud Firestore. It uses an efficient mechanism for
  27213. * dropping existing data but does not attempt to securely overwrite or
  27214. * otherwise make cached data unrecoverable. For applications that are sensitive
  27215. * to the disclosure of cached data in between user sessions, we strongly
  27216. * recommend not enabling persistence at all.
  27217. *
  27218. * @param firestore - The {@link Firestore} instance to clear persistence for.
  27219. * @returns A `Promise` that is resolved when the persistent storage is
  27220. * cleared. Otherwise, the promise is rejected with an error.
  27221. */
  27222. function clearIndexedDbPersistence(firestore) {
  27223. if (firestore._initialized && !firestore._terminated) {
  27224. throw new FirestoreError(Code.FAILED_PRECONDITION, 'Persistence can only be cleared before a Firestore instance is ' +
  27225. 'initialized or after it is terminated.');
  27226. }
  27227. const deferred = new Deferred();
  27228. firestore._queue.enqueueAndForgetEvenWhileRestricted(async () => {
  27229. try {
  27230. await indexedDbClearPersistence(indexedDbStoragePrefix(firestore._databaseId, firestore._persistenceKey));
  27231. deferred.resolve();
  27232. }
  27233. catch (e) {
  27234. deferred.reject(e);
  27235. }
  27236. });
  27237. return deferred.promise;
  27238. }
  27239. /**
  27240. * Waits until all currently pending writes for the active user have been
  27241. * acknowledged by the backend.
  27242. *
  27243. * The returned promise resolves immediately if there are no outstanding writes.
  27244. * Otherwise, the promise waits for all previously issued writes (including
  27245. * those written in a previous app session), but it does not wait for writes
  27246. * that were added after the function is called. If you want to wait for
  27247. * additional writes, call `waitForPendingWrites()` again.
  27248. *
  27249. * Any outstanding `waitForPendingWrites()` promises are rejected during user
  27250. * changes.
  27251. *
  27252. * @returns A `Promise` which resolves when all currently pending writes have been
  27253. * acknowledged by the backend.
  27254. */
  27255. function waitForPendingWrites(firestore) {
  27256. firestore = cast(firestore, Firestore);
  27257. const client = ensureFirestoreConfigured(firestore);
  27258. return firestoreClientWaitForPendingWrites(client);
  27259. }
  27260. /**
  27261. * Re-enables use of the network for this {@link Firestore} instance after a prior
  27262. * call to {@link disableNetwork}.
  27263. *
  27264. * @returns A `Promise` that is resolved once the network has been enabled.
  27265. */
  27266. function enableNetwork(firestore) {
  27267. firestore = cast(firestore, Firestore);
  27268. const client = ensureFirestoreConfigured(firestore);
  27269. return firestoreClientEnableNetwork(client);
  27270. }
  27271. /**
  27272. * Disables network usage for this instance. It can be re-enabled via {@link
  27273. * enableNetwork}. While the network is disabled, any snapshot listeners,
  27274. * `getDoc()` or `getDocs()` calls will return results from cache, and any write
  27275. * operations will be queued until the network is restored.
  27276. *
  27277. * @returns A `Promise` that is resolved once the network has been disabled.
  27278. */
  27279. function disableNetwork(firestore) {
  27280. firestore = cast(firestore, Firestore);
  27281. const client = ensureFirestoreConfigured(firestore);
  27282. return firestoreClientDisableNetwork(client);
  27283. }
  27284. /**
  27285. * Terminates the provided {@link Firestore} instance.
  27286. *
  27287. * After calling `terminate()` only the `clearIndexedDbPersistence()` function
  27288. * may be used. Any other function will throw a `FirestoreError`.
  27289. *
  27290. * To restart after termination, create a new instance of FirebaseFirestore with
  27291. * {@link (getFirestore:1)}.
  27292. *
  27293. * Termination does not cancel any pending writes, and any promises that are
  27294. * awaiting a response from the server will not be resolved. If you have
  27295. * persistence enabled, the next time you start this instance, it will resume
  27296. * sending these writes to the server.
  27297. *
  27298. * Note: Under normal circumstances, calling `terminate()` is not required. This
  27299. * function is useful only when you want to force this instance to release all
  27300. * of its resources or in combination with `clearIndexedDbPersistence()` to
  27301. * ensure that all local state is destroyed between test runs.
  27302. *
  27303. * @returns A `Promise` that is resolved when the instance has been successfully
  27304. * terminated.
  27305. */
  27306. function terminate(firestore) {
  27307. app._removeServiceInstance(firestore.app, 'firestore', firestore._databaseId.database);
  27308. return firestore._delete();
  27309. }
  27310. /**
  27311. * Loads a Firestore bundle into the local cache.
  27312. *
  27313. * @param firestore - The {@link Firestore} instance to load bundles for.
  27314. * @param bundleData - An object representing the bundle to be loaded. Valid
  27315. * objects are `ArrayBuffer`, `ReadableStream<Uint8Array>` or `string`.
  27316. *
  27317. * @returns A `LoadBundleTask` object, which notifies callers with progress
  27318. * updates, and completion or error events. It can be used as a
  27319. * `Promise<LoadBundleTaskProgress>`.
  27320. */
  27321. function loadBundle(firestore, bundleData) {
  27322. firestore = cast(firestore, Firestore);
  27323. const client = ensureFirestoreConfigured(firestore);
  27324. const resultTask = new LoadBundleTask();
  27325. firestoreClientLoadBundle(client, firestore._databaseId, bundleData, resultTask);
  27326. return resultTask;
  27327. }
  27328. /**
  27329. * Reads a Firestore {@link Query} from local cache, identified by the given
  27330. * name.
  27331. *
  27332. * The named queries are packaged into bundles on the server side (along
  27333. * with resulting documents), and loaded to local cache using `loadBundle`. Once
  27334. * in local cache, use this method to extract a {@link Query} by name.
  27335. *
  27336. * @param firestore - The {@link Firestore} instance to read the query from.
  27337. * @param name - The name of the query.
  27338. * @returns A `Promise` that is resolved with the Query or `null`.
  27339. */
  27340. function namedQuery(firestore, name) {
  27341. firestore = cast(firestore, Firestore);
  27342. const client = ensureFirestoreConfigured(firestore);
  27343. return firestoreClientGetNamedQuery(client, name).then(namedQuery => {
  27344. if (!namedQuery) {
  27345. return null;
  27346. }
  27347. return new Query(firestore, null, namedQuery.query);
  27348. });
  27349. }
  27350. function verifyNotInitialized(firestore) {
  27351. if (firestore._initialized || firestore._terminated) {
  27352. throw new FirestoreError(Code.FAILED_PRECONDITION, 'Firestore has already been started and persistence can no longer be ' +
  27353. 'enabled. You can only enable persistence before calling any other ' +
  27354. 'methods on a Firestore object.');
  27355. }
  27356. }
  27357. /**
  27358. * @license
  27359. * Copyright 2020 Google LLC
  27360. *
  27361. * Licensed under the Apache License, Version 2.0 (the "License");
  27362. * you may not use this file except in compliance with the License.
  27363. * You may obtain a copy of the License at
  27364. *
  27365. * http://www.apache.org/licenses/LICENSE-2.0
  27366. *
  27367. * Unless required by applicable law or agreed to in writing, software
  27368. * distributed under the License is distributed on an "AS IS" BASIS,
  27369. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27370. * See the License for the specific language governing permissions and
  27371. * limitations under the License.
  27372. */
  27373. function registerFirestore(variant, useFetchStreams = true) {
  27374. setSDKVersion(app.SDK_VERSION);
  27375. app._registerComponent(new component.Component('firestore', (container, { instanceIdentifier: databaseId, options: settings }) => {
  27376. const app = container.getProvider('app').getImmediate();
  27377. const firestoreInstance = new Firestore(new FirebaseAuthCredentialsProvider(container.getProvider('auth-internal')), new FirebaseAppCheckTokenProvider(container.getProvider('app-check-internal')), databaseIdFromApp(app, databaseId), app);
  27378. settings = Object.assign({ useFetchStreams }, settings);
  27379. firestoreInstance._setSettings(settings);
  27380. return firestoreInstance;
  27381. }, 'PUBLIC').setMultipleInstances(true));
  27382. app.registerVersion(name, version$1, variant);
  27383. // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
  27384. app.registerVersion(name, version$1, 'cjs2017');
  27385. }
  27386. /**
  27387. * @license
  27388. * Copyright 2017 Google LLC
  27389. *
  27390. * Licensed under the Apache License, Version 2.0 (the "License");
  27391. * you may not use this file except in compliance with the License.
  27392. * You may obtain a copy of the License at
  27393. *
  27394. * http://www.apache.org/licenses/LICENSE-2.0
  27395. *
  27396. * Unless required by applicable law or agreed to in writing, software
  27397. * distributed under the License is distributed on an "AS IS" BASIS,
  27398. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27399. * See the License for the specific language governing permissions and
  27400. * limitations under the License.
  27401. */
  27402. function isPartialObserver(obj) {
  27403. return implementsAnyMethods(obj, ['next', 'error', 'complete']);
  27404. }
  27405. /**
  27406. * Returns true if obj is an object and contains at least one of the specified
  27407. * methods.
  27408. */
  27409. function implementsAnyMethods(obj, methods) {
  27410. if (typeof obj !== 'object' || obj === null) {
  27411. return false;
  27412. }
  27413. const object = obj;
  27414. for (const method of methods) {
  27415. if (method in object && typeof object[method] === 'function') {
  27416. return true;
  27417. }
  27418. }
  27419. return false;
  27420. }
  27421. /**
  27422. * @license
  27423. * Copyright 2020 Google LLC
  27424. *
  27425. * Licensed under the Apache License, Version 2.0 (the "License");
  27426. * you may not use this file except in compliance with the License.
  27427. * You may obtain a copy of the License at
  27428. *
  27429. * http://www.apache.org/licenses/LICENSE-2.0
  27430. *
  27431. * Unless required by applicable law or agreed to in writing, software
  27432. * distributed under the License is distributed on an "AS IS" BASIS,
  27433. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27434. * See the License for the specific language governing permissions and
  27435. * limitations under the License.
  27436. */
  27437. /**
  27438. * An immutable object representing an array of bytes.
  27439. */
  27440. class Bytes {
  27441. /** @hideconstructor */
  27442. constructor(byteString) {
  27443. this._byteString = byteString;
  27444. }
  27445. /**
  27446. * Creates a new `Bytes` object from the given Base64 string, converting it to
  27447. * bytes.
  27448. *
  27449. * @param base64 - The Base64 string used to create the `Bytes` object.
  27450. */
  27451. static fromBase64String(base64) {
  27452. try {
  27453. return new Bytes(ByteString.fromBase64String(base64));
  27454. }
  27455. catch (e) {
  27456. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Failed to construct data from Base64 string: ' + e);
  27457. }
  27458. }
  27459. /**
  27460. * Creates a new `Bytes` object from the given Uint8Array.
  27461. *
  27462. * @param array - The Uint8Array used to create the `Bytes` object.
  27463. */
  27464. static fromUint8Array(array) {
  27465. return new Bytes(ByteString.fromUint8Array(array));
  27466. }
  27467. /**
  27468. * Returns the underlying bytes as a Base64-encoded string.
  27469. *
  27470. * @returns The Base64-encoded string created from the `Bytes` object.
  27471. */
  27472. toBase64() {
  27473. return this._byteString.toBase64();
  27474. }
  27475. /**
  27476. * Returns the underlying bytes in a new `Uint8Array`.
  27477. *
  27478. * @returns The Uint8Array created from the `Bytes` object.
  27479. */
  27480. toUint8Array() {
  27481. return this._byteString.toUint8Array();
  27482. }
  27483. /**
  27484. * Returns a string representation of the `Bytes` object.
  27485. *
  27486. * @returns A string representation of the `Bytes` object.
  27487. */
  27488. toString() {
  27489. return 'Bytes(base64: ' + this.toBase64() + ')';
  27490. }
  27491. /**
  27492. * Returns true if this `Bytes` object is equal to the provided one.
  27493. *
  27494. * @param other - The `Bytes` object to compare against.
  27495. * @returns true if this `Bytes` object is equal to the provided one.
  27496. */
  27497. isEqual(other) {
  27498. return this._byteString.isEqual(other._byteString);
  27499. }
  27500. }
  27501. /**
  27502. * @license
  27503. * Copyright 2020 Google LLC
  27504. *
  27505. * Licensed under the Apache License, Version 2.0 (the "License");
  27506. * you may not use this file except in compliance with the License.
  27507. * You may obtain a copy of the License at
  27508. *
  27509. * http://www.apache.org/licenses/LICENSE-2.0
  27510. *
  27511. * Unless required by applicable law or agreed to in writing, software
  27512. * distributed under the License is distributed on an "AS IS" BASIS,
  27513. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27514. * See the License for the specific language governing permissions and
  27515. * limitations under the License.
  27516. */
  27517. /**
  27518. * A `FieldPath` refers to a field in a document. The path may consist of a
  27519. * single field name (referring to a top-level field in the document), or a
  27520. * list of field names (referring to a nested field in the document).
  27521. *
  27522. * Create a `FieldPath` by providing field names. If more than one field
  27523. * name is provided, the path will point to a nested field in a document.
  27524. */
  27525. class FieldPath {
  27526. /**
  27527. * Creates a `FieldPath` from the provided field names. If more than one field
  27528. * name is provided, the path will point to a nested field in a document.
  27529. *
  27530. * @param fieldNames - A list of field names.
  27531. */
  27532. constructor(...fieldNames) {
  27533. for (let i = 0; i < fieldNames.length; ++i) {
  27534. if (fieldNames[i].length === 0) {
  27535. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid field name at argument $(i + 1). ` +
  27536. 'Field names must not be empty.');
  27537. }
  27538. }
  27539. this._internalPath = new FieldPath$1(fieldNames);
  27540. }
  27541. /**
  27542. * Returns true if this `FieldPath` is equal to the provided one.
  27543. *
  27544. * @param other - The `FieldPath` to compare against.
  27545. * @returns true if this `FieldPath` is equal to the provided one.
  27546. */
  27547. isEqual(other) {
  27548. return this._internalPath.isEqual(other._internalPath);
  27549. }
  27550. }
  27551. /**
  27552. * Returns a special sentinel `FieldPath` to refer to the ID of a document.
  27553. * It can be used in queries to sort or filter by the document ID.
  27554. */
  27555. function documentId() {
  27556. return new FieldPath(DOCUMENT_KEY_NAME);
  27557. }
  27558. /**
  27559. * @license
  27560. * Copyright 2020 Google LLC
  27561. *
  27562. * Licensed under the Apache License, Version 2.0 (the "License");
  27563. * you may not use this file except in compliance with the License.
  27564. * You may obtain a copy of the License at
  27565. *
  27566. * http://www.apache.org/licenses/LICENSE-2.0
  27567. *
  27568. * Unless required by applicable law or agreed to in writing, software
  27569. * distributed under the License is distributed on an "AS IS" BASIS,
  27570. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27571. * See the License for the specific language governing permissions and
  27572. * limitations under the License.
  27573. */
  27574. /**
  27575. * Sentinel values that can be used when writing document fields with `set()`
  27576. * or `update()`.
  27577. */
  27578. class FieldValue {
  27579. /**
  27580. * @param _methodName - The public API endpoint that returns this class.
  27581. * @hideconstructor
  27582. */
  27583. constructor(_methodName) {
  27584. this._methodName = _methodName;
  27585. }
  27586. }
  27587. /**
  27588. * @license
  27589. * Copyright 2017 Google LLC
  27590. *
  27591. * Licensed under the Apache License, Version 2.0 (the "License");
  27592. * you may not use this file except in compliance with the License.
  27593. * You may obtain a copy of the License at
  27594. *
  27595. * http://www.apache.org/licenses/LICENSE-2.0
  27596. *
  27597. * Unless required by applicable law or agreed to in writing, software
  27598. * distributed under the License is distributed on an "AS IS" BASIS,
  27599. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27600. * See the License for the specific language governing permissions and
  27601. * limitations under the License.
  27602. */
  27603. /**
  27604. * An immutable object representing a geographic location in Firestore. The
  27605. * location is represented as latitude/longitude pair.
  27606. *
  27607. * Latitude values are in the range of [-90, 90].
  27608. * Longitude values are in the range of [-180, 180].
  27609. */
  27610. class GeoPoint {
  27611. /**
  27612. * Creates a new immutable `GeoPoint` object with the provided latitude and
  27613. * longitude values.
  27614. * @param latitude - The latitude as number between -90 and 90.
  27615. * @param longitude - The longitude as number between -180 and 180.
  27616. */
  27617. constructor(latitude, longitude) {
  27618. if (!isFinite(latitude) || latitude < -90 || latitude > 90) {
  27619. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Latitude must be a number between -90 and 90, but was: ' + latitude);
  27620. }
  27621. if (!isFinite(longitude) || longitude < -180 || longitude > 180) {
  27622. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Longitude must be a number between -180 and 180, but was: ' + longitude);
  27623. }
  27624. this._lat = latitude;
  27625. this._long = longitude;
  27626. }
  27627. /**
  27628. * The latitude of this `GeoPoint` instance.
  27629. */
  27630. get latitude() {
  27631. return this._lat;
  27632. }
  27633. /**
  27634. * The longitude of this `GeoPoint` instance.
  27635. */
  27636. get longitude() {
  27637. return this._long;
  27638. }
  27639. /**
  27640. * Returns true if this `GeoPoint` is equal to the provided one.
  27641. *
  27642. * @param other - The `GeoPoint` to compare against.
  27643. * @returns true if this `GeoPoint` is equal to the provided one.
  27644. */
  27645. isEqual(other) {
  27646. return this._lat === other._lat && this._long === other._long;
  27647. }
  27648. /** Returns a JSON-serializable representation of this GeoPoint. */
  27649. toJSON() {
  27650. return { latitude: this._lat, longitude: this._long };
  27651. }
  27652. /**
  27653. * Actually private to JS consumers of our API, so this function is prefixed
  27654. * with an underscore.
  27655. */
  27656. _compareTo(other) {
  27657. return (primitiveComparator(this._lat, other._lat) ||
  27658. primitiveComparator(this._long, other._long));
  27659. }
  27660. }
  27661. /**
  27662. * @license
  27663. * Copyright 2017 Google LLC
  27664. *
  27665. * Licensed under the Apache License, Version 2.0 (the "License");
  27666. * you may not use this file except in compliance with the License.
  27667. * You may obtain a copy of the License at
  27668. *
  27669. * http://www.apache.org/licenses/LICENSE-2.0
  27670. *
  27671. * Unless required by applicable law or agreed to in writing, software
  27672. * distributed under the License is distributed on an "AS IS" BASIS,
  27673. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27674. * See the License for the specific language governing permissions and
  27675. * limitations under the License.
  27676. */
  27677. const RESERVED_FIELD_REGEX = /^__.*__$/;
  27678. /** The result of parsing document data (e.g. for a setData call). */
  27679. class ParsedSetData {
  27680. constructor(data, fieldMask, fieldTransforms) {
  27681. this.data = data;
  27682. this.fieldMask = fieldMask;
  27683. this.fieldTransforms = fieldTransforms;
  27684. }
  27685. toMutation(key, precondition) {
  27686. if (this.fieldMask !== null) {
  27687. return new PatchMutation(key, this.data, this.fieldMask, precondition, this.fieldTransforms);
  27688. }
  27689. else {
  27690. return new SetMutation(key, this.data, precondition, this.fieldTransforms);
  27691. }
  27692. }
  27693. }
  27694. /** The result of parsing "update" data (i.e. for an updateData call). */
  27695. class ParsedUpdateData {
  27696. constructor(data,
  27697. // The fieldMask does not include document transforms.
  27698. fieldMask, fieldTransforms) {
  27699. this.data = data;
  27700. this.fieldMask = fieldMask;
  27701. this.fieldTransforms = fieldTransforms;
  27702. }
  27703. toMutation(key, precondition) {
  27704. return new PatchMutation(key, this.data, this.fieldMask, precondition, this.fieldTransforms);
  27705. }
  27706. }
  27707. function isWrite(dataSource) {
  27708. switch (dataSource) {
  27709. case 0 /* UserDataSource.Set */: // fall through
  27710. case 2 /* UserDataSource.MergeSet */: // fall through
  27711. case 1 /* UserDataSource.Update */:
  27712. return true;
  27713. case 3 /* UserDataSource.Argument */:
  27714. case 4 /* UserDataSource.ArrayArgument */:
  27715. return false;
  27716. default:
  27717. throw fail();
  27718. }
  27719. }
  27720. /** A "context" object passed around while parsing user data. */
  27721. class ParseContextImpl {
  27722. /**
  27723. * Initializes a ParseContext with the given source and path.
  27724. *
  27725. * @param settings - The settings for the parser.
  27726. * @param databaseId - The database ID of the Firestore instance.
  27727. * @param serializer - The serializer to use to generate the Value proto.
  27728. * @param ignoreUndefinedProperties - Whether to ignore undefined properties
  27729. * rather than throw.
  27730. * @param fieldTransforms - A mutable list of field transforms encountered
  27731. * while parsing the data.
  27732. * @param fieldMask - A mutable list of field paths encountered while parsing
  27733. * the data.
  27734. *
  27735. * TODO(b/34871131): We don't support array paths right now, so path can be
  27736. * null to indicate the context represents any location within an array (in
  27737. * which case certain features will not work and errors will be somewhat
  27738. * compromised).
  27739. */
  27740. constructor(settings, databaseId, serializer, ignoreUndefinedProperties, fieldTransforms, fieldMask) {
  27741. this.settings = settings;
  27742. this.databaseId = databaseId;
  27743. this.serializer = serializer;
  27744. this.ignoreUndefinedProperties = ignoreUndefinedProperties;
  27745. // Minor hack: If fieldTransforms is undefined, we assume this is an
  27746. // external call and we need to validate the entire path.
  27747. if (fieldTransforms === undefined) {
  27748. this.validatePath();
  27749. }
  27750. this.fieldTransforms = fieldTransforms || [];
  27751. this.fieldMask = fieldMask || [];
  27752. }
  27753. get path() {
  27754. return this.settings.path;
  27755. }
  27756. get dataSource() {
  27757. return this.settings.dataSource;
  27758. }
  27759. /** Returns a new context with the specified settings overwritten. */
  27760. contextWith(configuration) {
  27761. return new ParseContextImpl(Object.assign(Object.assign({}, this.settings), configuration), this.databaseId, this.serializer, this.ignoreUndefinedProperties, this.fieldTransforms, this.fieldMask);
  27762. }
  27763. childContextForField(field) {
  27764. var _a;
  27765. const childPath = (_a = this.path) === null || _a === void 0 ? void 0 : _a.child(field);
  27766. const context = this.contextWith({ path: childPath, arrayElement: false });
  27767. context.validatePathSegment(field);
  27768. return context;
  27769. }
  27770. childContextForFieldPath(field) {
  27771. var _a;
  27772. const childPath = (_a = this.path) === null || _a === void 0 ? void 0 : _a.child(field);
  27773. const context = this.contextWith({ path: childPath, arrayElement: false });
  27774. context.validatePath();
  27775. return context;
  27776. }
  27777. childContextForArray(index) {
  27778. // TODO(b/34871131): We don't support array paths right now; so make path
  27779. // undefined.
  27780. return this.contextWith({ path: undefined, arrayElement: true });
  27781. }
  27782. createError(reason) {
  27783. return createError(reason, this.settings.methodName, this.settings.hasConverter || false, this.path, this.settings.targetDoc);
  27784. }
  27785. /** Returns 'true' if 'fieldPath' was traversed when creating this context. */
  27786. contains(fieldPath) {
  27787. return (this.fieldMask.find(field => fieldPath.isPrefixOf(field)) !== undefined ||
  27788. this.fieldTransforms.find(transform => fieldPath.isPrefixOf(transform.field)) !== undefined);
  27789. }
  27790. validatePath() {
  27791. // TODO(b/34871131): Remove null check once we have proper paths for fields
  27792. // within arrays.
  27793. if (!this.path) {
  27794. return;
  27795. }
  27796. for (let i = 0; i < this.path.length; i++) {
  27797. this.validatePathSegment(this.path.get(i));
  27798. }
  27799. }
  27800. validatePathSegment(segment) {
  27801. if (segment.length === 0) {
  27802. throw this.createError('Document fields must not be empty');
  27803. }
  27804. if (isWrite(this.dataSource) && RESERVED_FIELD_REGEX.test(segment)) {
  27805. throw this.createError('Document fields cannot begin and end with "__"');
  27806. }
  27807. }
  27808. }
  27809. /**
  27810. * Helper for parsing raw user input (provided via the API) into internal model
  27811. * classes.
  27812. */
  27813. class UserDataReader {
  27814. constructor(databaseId, ignoreUndefinedProperties, serializer) {
  27815. this.databaseId = databaseId;
  27816. this.ignoreUndefinedProperties = ignoreUndefinedProperties;
  27817. this.serializer = serializer || newSerializer(databaseId);
  27818. }
  27819. /** Creates a new top-level parse context. */
  27820. createContext(dataSource, methodName, targetDoc, hasConverter = false) {
  27821. return new ParseContextImpl({
  27822. dataSource,
  27823. methodName,
  27824. targetDoc,
  27825. path: FieldPath$1.emptyPath(),
  27826. arrayElement: false,
  27827. hasConverter
  27828. }, this.databaseId, this.serializer, this.ignoreUndefinedProperties);
  27829. }
  27830. }
  27831. function newUserDataReader(firestore) {
  27832. const settings = firestore._freezeSettings();
  27833. const serializer = newSerializer(firestore._databaseId);
  27834. return new UserDataReader(firestore._databaseId, !!settings.ignoreUndefinedProperties, serializer);
  27835. }
  27836. /** Parse document data from a set() call. */
  27837. function parseSetData(userDataReader, methodName, targetDoc, input, hasConverter, options = {}) {
  27838. const context = userDataReader.createContext(options.merge || options.mergeFields
  27839. ? 2 /* UserDataSource.MergeSet */
  27840. : 0 /* UserDataSource.Set */, methodName, targetDoc, hasConverter);
  27841. validatePlainObject('Data must be an object, but it was:', context, input);
  27842. const updateData = parseObject(input, context);
  27843. let fieldMask;
  27844. let fieldTransforms;
  27845. if (options.merge) {
  27846. fieldMask = new FieldMask(context.fieldMask);
  27847. fieldTransforms = context.fieldTransforms;
  27848. }
  27849. else if (options.mergeFields) {
  27850. const validatedFieldPaths = [];
  27851. for (const stringOrFieldPath of options.mergeFields) {
  27852. const fieldPath = fieldPathFromArgument$1(methodName, stringOrFieldPath, targetDoc);
  27853. if (!context.contains(fieldPath)) {
  27854. throw new FirestoreError(Code.INVALID_ARGUMENT, `Field '${fieldPath}' is specified in your field mask but missing from your input data.`);
  27855. }
  27856. if (!fieldMaskContains(validatedFieldPaths, fieldPath)) {
  27857. validatedFieldPaths.push(fieldPath);
  27858. }
  27859. }
  27860. fieldMask = new FieldMask(validatedFieldPaths);
  27861. fieldTransforms = context.fieldTransforms.filter(transform => fieldMask.covers(transform.field));
  27862. }
  27863. else {
  27864. fieldMask = null;
  27865. fieldTransforms = context.fieldTransforms;
  27866. }
  27867. return new ParsedSetData(new ObjectValue(updateData), fieldMask, fieldTransforms);
  27868. }
  27869. class DeleteFieldValueImpl extends FieldValue {
  27870. _toFieldTransform(context) {
  27871. if (context.dataSource === 2 /* UserDataSource.MergeSet */) {
  27872. // No transform to add for a delete, but we need to add it to our
  27873. // fieldMask so it gets deleted.
  27874. context.fieldMask.push(context.path);
  27875. }
  27876. else if (context.dataSource === 1 /* UserDataSource.Update */) {
  27877. throw context.createError(`${this._methodName}() can only appear at the top level ` +
  27878. 'of your update data');
  27879. }
  27880. else {
  27881. // We shouldn't encounter delete sentinels for queries or non-merge set() calls.
  27882. throw context.createError(`${this._methodName}() cannot be used with set() unless you pass ` +
  27883. '{merge:true}');
  27884. }
  27885. return null;
  27886. }
  27887. isEqual(other) {
  27888. return other instanceof DeleteFieldValueImpl;
  27889. }
  27890. }
  27891. /**
  27892. * Creates a child context for parsing SerializableFieldValues.
  27893. *
  27894. * This is different than calling `ParseContext.contextWith` because it keeps
  27895. * the fieldTransforms and fieldMask separate.
  27896. *
  27897. * The created context has its `dataSource` set to `UserDataSource.Argument`.
  27898. * Although these values are used with writes, any elements in these FieldValues
  27899. * are not considered writes since they cannot contain any FieldValue sentinels,
  27900. * etc.
  27901. *
  27902. * @param fieldValue - The sentinel FieldValue for which to create a child
  27903. * context.
  27904. * @param context - The parent context.
  27905. * @param arrayElement - Whether or not the FieldValue has an array.
  27906. */
  27907. function createSentinelChildContext(fieldValue, context, arrayElement) {
  27908. return new ParseContextImpl({
  27909. dataSource: 3 /* UserDataSource.Argument */,
  27910. targetDoc: context.settings.targetDoc,
  27911. methodName: fieldValue._methodName,
  27912. arrayElement
  27913. }, context.databaseId, context.serializer, context.ignoreUndefinedProperties);
  27914. }
  27915. class ServerTimestampFieldValueImpl extends FieldValue {
  27916. _toFieldTransform(context) {
  27917. return new FieldTransform(context.path, new ServerTimestampTransform());
  27918. }
  27919. isEqual(other) {
  27920. return other instanceof ServerTimestampFieldValueImpl;
  27921. }
  27922. }
  27923. class ArrayUnionFieldValueImpl extends FieldValue {
  27924. constructor(methodName, _elements) {
  27925. super(methodName);
  27926. this._elements = _elements;
  27927. }
  27928. _toFieldTransform(context) {
  27929. const parseContext = createSentinelChildContext(this, context,
  27930. /*array=*/ true);
  27931. const parsedElements = this._elements.map(element => parseData(element, parseContext));
  27932. const arrayUnion = new ArrayUnionTransformOperation(parsedElements);
  27933. return new FieldTransform(context.path, arrayUnion);
  27934. }
  27935. isEqual(other) {
  27936. // TODO(mrschmidt): Implement isEquals
  27937. return this === other;
  27938. }
  27939. }
  27940. class ArrayRemoveFieldValueImpl extends FieldValue {
  27941. constructor(methodName, _elements) {
  27942. super(methodName);
  27943. this._elements = _elements;
  27944. }
  27945. _toFieldTransform(context) {
  27946. const parseContext = createSentinelChildContext(this, context,
  27947. /*array=*/ true);
  27948. const parsedElements = this._elements.map(element => parseData(element, parseContext));
  27949. const arrayUnion = new ArrayRemoveTransformOperation(parsedElements);
  27950. return new FieldTransform(context.path, arrayUnion);
  27951. }
  27952. isEqual(other) {
  27953. // TODO(mrschmidt): Implement isEquals
  27954. return this === other;
  27955. }
  27956. }
  27957. class NumericIncrementFieldValueImpl extends FieldValue {
  27958. constructor(methodName, _operand) {
  27959. super(methodName);
  27960. this._operand = _operand;
  27961. }
  27962. _toFieldTransform(context) {
  27963. const numericIncrement = new NumericIncrementTransformOperation(context.serializer, toNumber(context.serializer, this._operand));
  27964. return new FieldTransform(context.path, numericIncrement);
  27965. }
  27966. isEqual(other) {
  27967. // TODO(mrschmidt): Implement isEquals
  27968. return this === other;
  27969. }
  27970. }
  27971. /** Parse update data from an update() call. */
  27972. function parseUpdateData(userDataReader, methodName, targetDoc, input) {
  27973. const context = userDataReader.createContext(1 /* UserDataSource.Update */, methodName, targetDoc);
  27974. validatePlainObject('Data must be an object, but it was:', context, input);
  27975. const fieldMaskPaths = [];
  27976. const updateData = ObjectValue.empty();
  27977. forEach(input, (key, value) => {
  27978. const path = fieldPathFromDotSeparatedString(methodName, key, targetDoc);
  27979. // For Compat types, we have to "extract" the underlying types before
  27980. // performing validation.
  27981. value = util.getModularInstance(value);
  27982. const childContext = context.childContextForFieldPath(path);
  27983. if (value instanceof DeleteFieldValueImpl) {
  27984. // Add it to the field mask, but don't add anything to updateData.
  27985. fieldMaskPaths.push(path);
  27986. }
  27987. else {
  27988. const parsedValue = parseData(value, childContext);
  27989. if (parsedValue != null) {
  27990. fieldMaskPaths.push(path);
  27991. updateData.set(path, parsedValue);
  27992. }
  27993. }
  27994. });
  27995. const mask = new FieldMask(fieldMaskPaths);
  27996. return new ParsedUpdateData(updateData, mask, context.fieldTransforms);
  27997. }
  27998. /** Parse update data from a list of field/value arguments. */
  27999. function parseUpdateVarargs(userDataReader, methodName, targetDoc, field, value, moreFieldsAndValues) {
  28000. const context = userDataReader.createContext(1 /* UserDataSource.Update */, methodName, targetDoc);
  28001. const keys = [fieldPathFromArgument$1(methodName, field, targetDoc)];
  28002. const values = [value];
  28003. if (moreFieldsAndValues.length % 2 !== 0) {
  28004. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${methodName}() needs to be called with an even number ` +
  28005. 'of arguments that alternate between field names and values.');
  28006. }
  28007. for (let i = 0; i < moreFieldsAndValues.length; i += 2) {
  28008. keys.push(fieldPathFromArgument$1(methodName, moreFieldsAndValues[i]));
  28009. values.push(moreFieldsAndValues[i + 1]);
  28010. }
  28011. const fieldMaskPaths = [];
  28012. const updateData = ObjectValue.empty();
  28013. // We iterate in reverse order to pick the last value for a field if the
  28014. // user specified the field multiple times.
  28015. for (let i = keys.length - 1; i >= 0; --i) {
  28016. if (!fieldMaskContains(fieldMaskPaths, keys[i])) {
  28017. const path = keys[i];
  28018. let value = values[i];
  28019. // For Compat types, we have to "extract" the underlying types before
  28020. // performing validation.
  28021. value = util.getModularInstance(value);
  28022. const childContext = context.childContextForFieldPath(path);
  28023. if (value instanceof DeleteFieldValueImpl) {
  28024. // Add it to the field mask, but don't add anything to updateData.
  28025. fieldMaskPaths.push(path);
  28026. }
  28027. else {
  28028. const parsedValue = parseData(value, childContext);
  28029. if (parsedValue != null) {
  28030. fieldMaskPaths.push(path);
  28031. updateData.set(path, parsedValue);
  28032. }
  28033. }
  28034. }
  28035. }
  28036. const mask = new FieldMask(fieldMaskPaths);
  28037. return new ParsedUpdateData(updateData, mask, context.fieldTransforms);
  28038. }
  28039. /**
  28040. * Parse a "query value" (e.g. value in a where filter or a value in a cursor
  28041. * bound).
  28042. *
  28043. * @param allowArrays - Whether the query value is an array that may directly
  28044. * contain additional arrays (e.g. the operand of an `in` query).
  28045. */
  28046. function parseQueryValue(userDataReader, methodName, input, allowArrays = false) {
  28047. const context = userDataReader.createContext(allowArrays ? 4 /* UserDataSource.ArrayArgument */ : 3 /* UserDataSource.Argument */, methodName);
  28048. const parsed = parseData(input, context);
  28049. return parsed;
  28050. }
  28051. /**
  28052. * Parses user data to Protobuf Values.
  28053. *
  28054. * @param input - Data to be parsed.
  28055. * @param context - A context object representing the current path being parsed,
  28056. * the source of the data being parsed, etc.
  28057. * @returns The parsed value, or null if the value was a FieldValue sentinel
  28058. * that should not be included in the resulting parsed data.
  28059. */
  28060. function parseData(input, context) {
  28061. // Unwrap the API type from the Compat SDK. This will return the API type
  28062. // from firestore-exp.
  28063. input = util.getModularInstance(input);
  28064. if (looksLikeJsonObject(input)) {
  28065. validatePlainObject('Unsupported field value:', context, input);
  28066. return parseObject(input, context);
  28067. }
  28068. else if (input instanceof FieldValue) {
  28069. // FieldValues usually parse into transforms (except deleteField())
  28070. // in which case we do not want to include this field in our parsed data
  28071. // (as doing so will overwrite the field directly prior to the transform
  28072. // trying to transform it). So we don't add this location to
  28073. // context.fieldMask and we return null as our parsing result.
  28074. parseSentinelFieldValue(input, context);
  28075. return null;
  28076. }
  28077. else if (input === undefined && context.ignoreUndefinedProperties) {
  28078. // If the input is undefined it can never participate in the fieldMask, so
  28079. // don't handle this below. If `ignoreUndefinedProperties` is false,
  28080. // `parseScalarValue` will reject an undefined value.
  28081. return null;
  28082. }
  28083. else {
  28084. // If context.path is null we are inside an array and we don't support
  28085. // field mask paths more granular than the top-level array.
  28086. if (context.path) {
  28087. context.fieldMask.push(context.path);
  28088. }
  28089. if (input instanceof Array) {
  28090. // TODO(b/34871131): Include the path containing the array in the error
  28091. // message.
  28092. // In the case of IN queries, the parsed data is an array (representing
  28093. // the set of values to be included for the IN query) that may directly
  28094. // contain additional arrays (each representing an individual field
  28095. // value), so we disable this validation.
  28096. if (context.settings.arrayElement &&
  28097. context.dataSource !== 4 /* UserDataSource.ArrayArgument */) {
  28098. throw context.createError('Nested arrays are not supported');
  28099. }
  28100. return parseArray(input, context);
  28101. }
  28102. else {
  28103. return parseScalarValue(input, context);
  28104. }
  28105. }
  28106. }
  28107. function parseObject(obj, context) {
  28108. const fields = {};
  28109. if (isEmpty(obj)) {
  28110. // If we encounter an empty object, we explicitly add it to the update
  28111. // mask to ensure that the server creates a map entry.
  28112. if (context.path && context.path.length > 0) {
  28113. context.fieldMask.push(context.path);
  28114. }
  28115. }
  28116. else {
  28117. forEach(obj, (key, val) => {
  28118. const parsedValue = parseData(val, context.childContextForField(key));
  28119. if (parsedValue != null) {
  28120. fields[key] = parsedValue;
  28121. }
  28122. });
  28123. }
  28124. return { mapValue: { fields } };
  28125. }
  28126. function parseArray(array, context) {
  28127. const values = [];
  28128. let entryIndex = 0;
  28129. for (const entry of array) {
  28130. let parsedEntry = parseData(entry, context.childContextForArray(entryIndex));
  28131. if (parsedEntry == null) {
  28132. // Just include nulls in the array for fields being replaced with a
  28133. // sentinel.
  28134. parsedEntry = { nullValue: 'NULL_VALUE' };
  28135. }
  28136. values.push(parsedEntry);
  28137. entryIndex++;
  28138. }
  28139. return { arrayValue: { values } };
  28140. }
  28141. /**
  28142. * "Parses" the provided FieldValueImpl, adding any necessary transforms to
  28143. * context.fieldTransforms.
  28144. */
  28145. function parseSentinelFieldValue(value, context) {
  28146. // Sentinels are only supported with writes, and not within arrays.
  28147. if (!isWrite(context.dataSource)) {
  28148. throw context.createError(`${value._methodName}() can only be used with update() and set()`);
  28149. }
  28150. if (!context.path) {
  28151. throw context.createError(`${value._methodName}() is not currently supported inside arrays`);
  28152. }
  28153. const fieldTransform = value._toFieldTransform(context);
  28154. if (fieldTransform) {
  28155. context.fieldTransforms.push(fieldTransform);
  28156. }
  28157. }
  28158. /**
  28159. * Helper to parse a scalar value (i.e. not an Object, Array, or FieldValue)
  28160. *
  28161. * @returns The parsed value
  28162. */
  28163. function parseScalarValue(value, context) {
  28164. value = util.getModularInstance(value);
  28165. if (value === null) {
  28166. return { nullValue: 'NULL_VALUE' };
  28167. }
  28168. else if (typeof value === 'number') {
  28169. return toNumber(context.serializer, value);
  28170. }
  28171. else if (typeof value === 'boolean') {
  28172. return { booleanValue: value };
  28173. }
  28174. else if (typeof value === 'string') {
  28175. return { stringValue: value };
  28176. }
  28177. else if (value instanceof Date) {
  28178. const timestamp = Timestamp.fromDate(value);
  28179. return {
  28180. timestampValue: toTimestamp(context.serializer, timestamp)
  28181. };
  28182. }
  28183. else if (value instanceof Timestamp) {
  28184. // Firestore backend truncates precision down to microseconds. To ensure
  28185. // offline mode works the same with regards to truncation, perform the
  28186. // truncation immediately without waiting for the backend to do that.
  28187. const timestamp = new Timestamp(value.seconds, Math.floor(value.nanoseconds / 1000) * 1000);
  28188. return {
  28189. timestampValue: toTimestamp(context.serializer, timestamp)
  28190. };
  28191. }
  28192. else if (value instanceof GeoPoint) {
  28193. return {
  28194. geoPointValue: {
  28195. latitude: value.latitude,
  28196. longitude: value.longitude
  28197. }
  28198. };
  28199. }
  28200. else if (value instanceof Bytes) {
  28201. return { bytesValue: toBytes(context.serializer, value._byteString) };
  28202. }
  28203. else if (value instanceof DocumentReference) {
  28204. const thisDb = context.databaseId;
  28205. const otherDb = value.firestore._databaseId;
  28206. if (!otherDb.isEqual(thisDb)) {
  28207. throw context.createError('Document reference is for database ' +
  28208. `${otherDb.projectId}/${otherDb.database} but should be ` +
  28209. `for database ${thisDb.projectId}/${thisDb.database}`);
  28210. }
  28211. return {
  28212. referenceValue: toResourceName(value.firestore._databaseId || context.databaseId, value._key.path)
  28213. };
  28214. }
  28215. else {
  28216. throw context.createError(`Unsupported field value: ${valueDescription(value)}`);
  28217. }
  28218. }
  28219. /**
  28220. * Checks whether an object looks like a JSON object that should be converted
  28221. * into a struct. Normal class/prototype instances are considered to look like
  28222. * JSON objects since they should be converted to a struct value. Arrays, Dates,
  28223. * GeoPoints, etc. are not considered to look like JSON objects since they map
  28224. * to specific FieldValue types other than ObjectValue.
  28225. */
  28226. function looksLikeJsonObject(input) {
  28227. return (typeof input === 'object' &&
  28228. input !== null &&
  28229. !(input instanceof Array) &&
  28230. !(input instanceof Date) &&
  28231. !(input instanceof Timestamp) &&
  28232. !(input instanceof GeoPoint) &&
  28233. !(input instanceof Bytes) &&
  28234. !(input instanceof DocumentReference) &&
  28235. !(input instanceof FieldValue));
  28236. }
  28237. function validatePlainObject(message, context, input) {
  28238. if (!looksLikeJsonObject(input) || !isPlainObject(input)) {
  28239. const description = valueDescription(input);
  28240. if (description === 'an object') {
  28241. // Massage the error if it was an object.
  28242. throw context.createError(message + ' a custom object');
  28243. }
  28244. else {
  28245. throw context.createError(message + ' ' + description);
  28246. }
  28247. }
  28248. }
  28249. /**
  28250. * Helper that calls fromDotSeparatedString() but wraps any error thrown.
  28251. */
  28252. function fieldPathFromArgument$1(methodName, path, targetDoc) {
  28253. // If required, replace the FieldPath Compat class with with the firestore-exp
  28254. // FieldPath.
  28255. path = util.getModularInstance(path);
  28256. if (path instanceof FieldPath) {
  28257. return path._internalPath;
  28258. }
  28259. else if (typeof path === 'string') {
  28260. return fieldPathFromDotSeparatedString(methodName, path);
  28261. }
  28262. else {
  28263. const message = 'Field path arguments must be of type string or ';
  28264. throw createError(message, methodName,
  28265. /* hasConverter= */ false,
  28266. /* path= */ undefined, targetDoc);
  28267. }
  28268. }
  28269. /**
  28270. * Matches any characters in a field path string that are reserved.
  28271. */
  28272. const FIELD_PATH_RESERVED = new RegExp('[~\\*/\\[\\]]');
  28273. /**
  28274. * Wraps fromDotSeparatedString with an error message about the method that
  28275. * was thrown.
  28276. * @param methodName - The publicly visible method name
  28277. * @param path - The dot-separated string form of a field path which will be
  28278. * split on dots.
  28279. * @param targetDoc - The document against which the field path will be
  28280. * evaluated.
  28281. */
  28282. function fieldPathFromDotSeparatedString(methodName, path, targetDoc) {
  28283. const found = path.search(FIELD_PATH_RESERVED);
  28284. if (found >= 0) {
  28285. throw createError(`Invalid field path (${path}). Paths must not contain ` +
  28286. `'~', '*', '/', '[', or ']'`, methodName,
  28287. /* hasConverter= */ false,
  28288. /* path= */ undefined, targetDoc);
  28289. }
  28290. try {
  28291. return new FieldPath(...path.split('.'))._internalPath;
  28292. }
  28293. catch (e) {
  28294. throw createError(`Invalid field path (${path}). Paths must not be empty, ` +
  28295. `begin with '.', end with '.', or contain '..'`, methodName,
  28296. /* hasConverter= */ false,
  28297. /* path= */ undefined, targetDoc);
  28298. }
  28299. }
  28300. function createError(reason, methodName, hasConverter, path, targetDoc) {
  28301. const hasPath = path && !path.isEmpty();
  28302. const hasDocument = targetDoc !== undefined;
  28303. let message = `Function ${methodName}() called with invalid data`;
  28304. if (hasConverter) {
  28305. message += ' (via `toFirestore()`)';
  28306. }
  28307. message += '. ';
  28308. let description = '';
  28309. if (hasPath || hasDocument) {
  28310. description += ' (found';
  28311. if (hasPath) {
  28312. description += ` in field ${path}`;
  28313. }
  28314. if (hasDocument) {
  28315. description += ` in document ${targetDoc}`;
  28316. }
  28317. description += ')';
  28318. }
  28319. return new FirestoreError(Code.INVALID_ARGUMENT, message + reason + description);
  28320. }
  28321. /** Checks `haystack` if FieldPath `needle` is present. Runs in O(n). */
  28322. function fieldMaskContains(haystack, needle) {
  28323. return haystack.some(v => v.isEqual(needle));
  28324. }
  28325. /**
  28326. * @license
  28327. * Copyright 2020 Google LLC
  28328. *
  28329. * Licensed under the Apache License, Version 2.0 (the "License");
  28330. * you may not use this file except in compliance with the License.
  28331. * You may obtain a copy of the License at
  28332. *
  28333. * http://www.apache.org/licenses/LICENSE-2.0
  28334. *
  28335. * Unless required by applicable law or agreed to in writing, software
  28336. * distributed under the License is distributed on an "AS IS" BASIS,
  28337. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28338. * See the License for the specific language governing permissions and
  28339. * limitations under the License.
  28340. */
  28341. /**
  28342. * A `DocumentSnapshot` contains data read from a document in your Firestore
  28343. * database. The data can be extracted with `.data()` or `.get(<field>)` to
  28344. * get a specific field.
  28345. *
  28346. * For a `DocumentSnapshot` that points to a non-existing document, any data
  28347. * access will return 'undefined'. You can use the `exists()` method to
  28348. * explicitly verify a document's existence.
  28349. */
  28350. class DocumentSnapshot$1 {
  28351. // Note: This class is stripped down version of the DocumentSnapshot in
  28352. // the legacy SDK. The changes are:
  28353. // - No support for SnapshotMetadata.
  28354. // - No support for SnapshotOptions.
  28355. /** @hideconstructor protected */
  28356. constructor(_firestore, _userDataWriter, _key, _document, _converter) {
  28357. this._firestore = _firestore;
  28358. this._userDataWriter = _userDataWriter;
  28359. this._key = _key;
  28360. this._document = _document;
  28361. this._converter = _converter;
  28362. }
  28363. /** Property of the `DocumentSnapshot` that provides the document's ID. */
  28364. get id() {
  28365. return this._key.path.lastSegment();
  28366. }
  28367. /**
  28368. * The `DocumentReference` for the document included in the `DocumentSnapshot`.
  28369. */
  28370. get ref() {
  28371. return new DocumentReference(this._firestore, this._converter, this._key);
  28372. }
  28373. /**
  28374. * Signals whether or not the document at the snapshot's location exists.
  28375. *
  28376. * @returns true if the document exists.
  28377. */
  28378. exists() {
  28379. return this._document !== null;
  28380. }
  28381. /**
  28382. * Retrieves all fields in the document as an `Object`. Returns `undefined` if
  28383. * the document doesn't exist.
  28384. *
  28385. * @returns An `Object` containing all fields in the document or `undefined`
  28386. * if the document doesn't exist.
  28387. */
  28388. data() {
  28389. if (!this._document) {
  28390. return undefined;
  28391. }
  28392. else if (this._converter) {
  28393. // We only want to use the converter and create a new DocumentSnapshot
  28394. // if a converter has been provided.
  28395. const snapshot = new QueryDocumentSnapshot$1(this._firestore, this._userDataWriter, this._key, this._document,
  28396. /* converter= */ null);
  28397. return this._converter.fromFirestore(snapshot);
  28398. }
  28399. else {
  28400. return this._userDataWriter.convertValue(this._document.data.value);
  28401. }
  28402. }
  28403. /**
  28404. * Retrieves the field specified by `fieldPath`. Returns `undefined` if the
  28405. * document or field doesn't exist.
  28406. *
  28407. * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific
  28408. * field.
  28409. * @returns The data at the specified field location or undefined if no such
  28410. * field exists in the document.
  28411. */
  28412. // We are using `any` here to avoid an explicit cast by our users.
  28413. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  28414. get(fieldPath) {
  28415. if (this._document) {
  28416. const value = this._document.data.field(fieldPathFromArgument('DocumentSnapshot.get', fieldPath));
  28417. if (value !== null) {
  28418. return this._userDataWriter.convertValue(value);
  28419. }
  28420. }
  28421. return undefined;
  28422. }
  28423. }
  28424. /**
  28425. * A `QueryDocumentSnapshot` contains data read from a document in your
  28426. * Firestore database as part of a query. The document is guaranteed to exist
  28427. * and its data can be extracted with `.data()` or `.get(<field>)` to get a
  28428. * specific field.
  28429. *
  28430. * A `QueryDocumentSnapshot` offers the same API surface as a
  28431. * `DocumentSnapshot`. Since query results contain only existing documents, the
  28432. * `exists` property will always be true and `data()` will never return
  28433. * 'undefined'.
  28434. */
  28435. class QueryDocumentSnapshot$1 extends DocumentSnapshot$1 {
  28436. /**
  28437. * Retrieves all fields in the document as an `Object`.
  28438. *
  28439. * @override
  28440. * @returns An `Object` containing all fields in the document.
  28441. */
  28442. data() {
  28443. return super.data();
  28444. }
  28445. }
  28446. /**
  28447. * Helper that calls `fromDotSeparatedString()` but wraps any error thrown.
  28448. */
  28449. function fieldPathFromArgument(methodName, arg) {
  28450. if (typeof arg === 'string') {
  28451. return fieldPathFromDotSeparatedString(methodName, arg);
  28452. }
  28453. else if (arg instanceof FieldPath) {
  28454. return arg._internalPath;
  28455. }
  28456. else {
  28457. return arg._delegate._internalPath;
  28458. }
  28459. }
  28460. /**
  28461. * @license
  28462. * Copyright 2020 Google LLC
  28463. *
  28464. * Licensed under the Apache License, Version 2.0 (the "License");
  28465. * you may not use this file except in compliance with the License.
  28466. * You may obtain a copy of the License at
  28467. *
  28468. * http://www.apache.org/licenses/LICENSE-2.0
  28469. *
  28470. * Unless required by applicable law or agreed to in writing, software
  28471. * distributed under the License is distributed on an "AS IS" BASIS,
  28472. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28473. * See the License for the specific language governing permissions and
  28474. * limitations under the License.
  28475. */
  28476. function validateHasExplicitOrderByForLimitToLast(query) {
  28477. if (query.limitType === "L" /* LimitType.Last */ &&
  28478. query.explicitOrderBy.length === 0) {
  28479. throw new FirestoreError(Code.UNIMPLEMENTED, 'limitToLast() queries require specifying at least one orderBy() clause');
  28480. }
  28481. }
  28482. /**
  28483. * An `AppliableConstraint` is an abstraction of a constraint that can be applied
  28484. * to a Firestore query.
  28485. */
  28486. class AppliableConstraint {
  28487. }
  28488. /**
  28489. * A `QueryConstraint` is used to narrow the set of documents returned by a
  28490. * Firestore query. `QueryConstraint`s are created by invoking {@link where},
  28491. * {@link orderBy}, {@link startAt}, {@link startAfter}, {@link
  28492. * endBefore}, {@link endAt}, {@link limit}, {@link limitToLast} and
  28493. * can then be passed to {@link query} to create a new query instance that
  28494. * also contains this `QueryConstraint`.
  28495. */
  28496. class QueryConstraint extends AppliableConstraint {
  28497. }
  28498. function query(query, queryConstraint, ...additionalQueryConstraints) {
  28499. let queryConstraints = [];
  28500. if (queryConstraint instanceof AppliableConstraint) {
  28501. queryConstraints.push(queryConstraint);
  28502. }
  28503. queryConstraints = queryConstraints.concat(additionalQueryConstraints);
  28504. validateQueryConstraintArray(queryConstraints);
  28505. for (const constraint of queryConstraints) {
  28506. query = constraint._apply(query);
  28507. }
  28508. return query;
  28509. }
  28510. /**
  28511. * A `QueryFieldFilterConstraint` is used to narrow the set of documents returned by
  28512. * a Firestore query by filtering on one or more document fields.
  28513. * `QueryFieldFilterConstraint`s are created by invoking {@link where} and can then
  28514. * be passed to {@link query} to create a new query instance that also contains
  28515. * this `QueryFieldFilterConstraint`.
  28516. */
  28517. class QueryFieldFilterConstraint extends QueryConstraint {
  28518. /**
  28519. * @internal
  28520. */
  28521. constructor(_field, _op, _value) {
  28522. super();
  28523. this._field = _field;
  28524. this._op = _op;
  28525. this._value = _value;
  28526. /** The type of this query constraint */
  28527. this.type = 'where';
  28528. }
  28529. static _create(_field, _op, _value) {
  28530. return new QueryFieldFilterConstraint(_field, _op, _value);
  28531. }
  28532. _apply(query) {
  28533. const filter = this._parse(query);
  28534. validateNewFieldFilter(query._query, filter);
  28535. return new Query(query.firestore, query.converter, queryWithAddedFilter(query._query, filter));
  28536. }
  28537. _parse(query) {
  28538. const reader = newUserDataReader(query.firestore);
  28539. const filter = newQueryFilter(query._query, 'where', reader, query.firestore._databaseId, this._field, this._op, this._value);
  28540. return filter;
  28541. }
  28542. }
  28543. /**
  28544. * Creates a {@link QueryFieldFilterConstraint} that enforces that documents
  28545. * must contain the specified field and that the value should satisfy the
  28546. * relation constraint provided.
  28547. *
  28548. * @param fieldPath - The path to compare
  28549. * @param opStr - The operation string (e.g "&lt;", "&lt;=", "==", "&lt;",
  28550. * "&lt;=", "!=").
  28551. * @param value - The value for comparison
  28552. * @returns The created {@link QueryFieldFilterConstraint}.
  28553. */
  28554. function where(fieldPath, opStr, value) {
  28555. const op = opStr;
  28556. const field = fieldPathFromArgument('where', fieldPath);
  28557. return QueryFieldFilterConstraint._create(field, op, value);
  28558. }
  28559. /**
  28560. * A `QueryCompositeFilterConstraint` is used to narrow the set of documents
  28561. * returned by a Firestore query by performing the logical OR or AND of multiple
  28562. * {@link QueryFieldFilterConstraint}s or {@link QueryCompositeFilterConstraint}s.
  28563. * `QueryCompositeFilterConstraint`s are created by invoking {@link or} or
  28564. * {@link and} and can then be passed to {@link query} to create a new query
  28565. * instance that also contains the `QueryCompositeFilterConstraint`.
  28566. * @internal TODO remove this internal tag with OR Query support in the server
  28567. */
  28568. class QueryCompositeFilterConstraint extends AppliableConstraint {
  28569. /**
  28570. * @internal
  28571. */
  28572. constructor(
  28573. /** The type of this query constraint */
  28574. type, _queryConstraints) {
  28575. super();
  28576. this.type = type;
  28577. this._queryConstraints = _queryConstraints;
  28578. }
  28579. static _create(type, _queryConstraints) {
  28580. return new QueryCompositeFilterConstraint(type, _queryConstraints);
  28581. }
  28582. _parse(query) {
  28583. const parsedFilters = this._queryConstraints
  28584. .map(queryConstraint => {
  28585. return queryConstraint._parse(query);
  28586. })
  28587. .filter(parsedFilter => parsedFilter.getFilters().length > 0);
  28588. if (parsedFilters.length === 1) {
  28589. return parsedFilters[0];
  28590. }
  28591. return CompositeFilter.create(parsedFilters, this._getOperator());
  28592. }
  28593. _apply(query) {
  28594. const parsedFilter = this._parse(query);
  28595. if (parsedFilter.getFilters().length === 0) {
  28596. // Return the existing query if not adding any more filters (e.g. an empty
  28597. // composite filter).
  28598. return query;
  28599. }
  28600. validateNewFilter(query._query, parsedFilter);
  28601. return new Query(query.firestore, query.converter, queryWithAddedFilter(query._query, parsedFilter));
  28602. }
  28603. _getQueryConstraints() {
  28604. return this._queryConstraints;
  28605. }
  28606. _getOperator() {
  28607. return this.type === 'and' ? "and" /* CompositeOperator.AND */ : "or" /* CompositeOperator.OR */;
  28608. }
  28609. }
  28610. /**
  28611. * Creates a new {@link QueryCompositeFilterConstraint} that is a disjunction of
  28612. * the given filter constraints. A disjunction filter includes a document if it
  28613. * satisfies any of the given filters.
  28614. *
  28615. * @param queryConstraints - Optional. The list of
  28616. * {@link QueryFilterConstraint}s to perform a disjunction for. These must be
  28617. * created with calls to {@link where}, {@link or}, or {@link and}.
  28618. * @returns The newly created {@link QueryCompositeFilterConstraint}.
  28619. * @internal TODO remove this internal tag with OR Query support in the server
  28620. */
  28621. function or(...queryConstraints) {
  28622. // Only support QueryFilterConstraints
  28623. queryConstraints.forEach(queryConstraint => validateQueryFilterConstraint('or', queryConstraint));
  28624. return QueryCompositeFilterConstraint._create("or" /* CompositeOperator.OR */, queryConstraints);
  28625. }
  28626. /**
  28627. * Creates a new {@link QueryCompositeFilterConstraint} that is a conjunction of
  28628. * the given filter constraints. A conjunction filter includes a document if it
  28629. * satisfies all of the given filters.
  28630. *
  28631. * @param queryConstraints - Optional. The list of
  28632. * {@link QueryFilterConstraint}s to perform a conjunction for. These must be
  28633. * created with calls to {@link where}, {@link or}, or {@link and}.
  28634. * @returns The newly created {@link QueryCompositeFilterConstraint}.
  28635. * @internal TODO remove this internal tag with OR Query support in the server
  28636. */
  28637. function and(...queryConstraints) {
  28638. // Only support QueryFilterConstraints
  28639. queryConstraints.forEach(queryConstraint => validateQueryFilterConstraint('and', queryConstraint));
  28640. return QueryCompositeFilterConstraint._create("and" /* CompositeOperator.AND */, queryConstraints);
  28641. }
  28642. /**
  28643. * A `QueryOrderByConstraint` is used to sort the set of documents returned by a
  28644. * Firestore query. `QueryOrderByConstraint`s are created by invoking
  28645. * {@link orderBy} and can then be passed to {@link query} to create a new query
  28646. * instance that also contains this `QueryOrderByConstraint`.
  28647. *
  28648. * Note: Documents that do not contain the orderBy field will not be present in
  28649. * the query result.
  28650. */
  28651. class QueryOrderByConstraint extends QueryConstraint {
  28652. /**
  28653. * @internal
  28654. */
  28655. constructor(_field, _direction) {
  28656. super();
  28657. this._field = _field;
  28658. this._direction = _direction;
  28659. /** The type of this query constraint */
  28660. this.type = 'orderBy';
  28661. }
  28662. static _create(_field, _direction) {
  28663. return new QueryOrderByConstraint(_field, _direction);
  28664. }
  28665. _apply(query) {
  28666. const orderBy = newQueryOrderBy(query._query, this._field, this._direction);
  28667. return new Query(query.firestore, query.converter, queryWithAddedOrderBy(query._query, orderBy));
  28668. }
  28669. }
  28670. /**
  28671. * Creates a {@link QueryOrderByConstraint} that sorts the query result by the
  28672. * specified field, optionally in descending order instead of ascending.
  28673. *
  28674. * Note: Documents that do not contain the specified field will not be present
  28675. * in the query result.
  28676. *
  28677. * @param fieldPath - The field to sort by.
  28678. * @param directionStr - Optional direction to sort by ('asc' or 'desc'). If
  28679. * not specified, order will be ascending.
  28680. * @returns The created {@link QueryOrderByConstraint}.
  28681. */
  28682. function orderBy(fieldPath, directionStr = 'asc') {
  28683. const direction = directionStr;
  28684. const path = fieldPathFromArgument('orderBy', fieldPath);
  28685. return QueryOrderByConstraint._create(path, direction);
  28686. }
  28687. /**
  28688. * A `QueryLimitConstraint` is used to limit the number of documents returned by
  28689. * a Firestore query.
  28690. * `QueryLimitConstraint`s are created by invoking {@link limit} or
  28691. * {@link limitToLast} and can then be passed to {@link query} to create a new
  28692. * query instance that also contains this `QueryLimitConstraint`.
  28693. */
  28694. class QueryLimitConstraint extends QueryConstraint {
  28695. /**
  28696. * @internal
  28697. */
  28698. constructor(
  28699. /** The type of this query constraint */
  28700. type, _limit, _limitType) {
  28701. super();
  28702. this.type = type;
  28703. this._limit = _limit;
  28704. this._limitType = _limitType;
  28705. }
  28706. static _create(type, _limit, _limitType) {
  28707. return new QueryLimitConstraint(type, _limit, _limitType);
  28708. }
  28709. _apply(query) {
  28710. return new Query(query.firestore, query.converter, queryWithLimit(query._query, this._limit, this._limitType));
  28711. }
  28712. }
  28713. /**
  28714. * Creates a {@link QueryLimitConstraint} that only returns the first matching
  28715. * documents.
  28716. *
  28717. * @param limit - The maximum number of items to return.
  28718. * @returns The created {@link QueryLimitConstraint}.
  28719. */
  28720. function limit(limit) {
  28721. validatePositiveNumber('limit', limit);
  28722. return QueryLimitConstraint._create('limit', limit, "F" /* LimitType.First */);
  28723. }
  28724. /**
  28725. * Creates a {@link QueryLimitConstraint} that only returns the last matching
  28726. * documents.
  28727. *
  28728. * You must specify at least one `orderBy` clause for `limitToLast` queries,
  28729. * otherwise an exception will be thrown during execution.
  28730. *
  28731. * @param limit - The maximum number of items to return.
  28732. * @returns The created {@link QueryLimitConstraint}.
  28733. */
  28734. function limitToLast(limit) {
  28735. validatePositiveNumber('limitToLast', limit);
  28736. return QueryLimitConstraint._create('limitToLast', limit, "L" /* LimitType.Last */);
  28737. }
  28738. /**
  28739. * A `QueryStartAtConstraint` is used to exclude documents from the start of a
  28740. * result set returned by a Firestore query.
  28741. * `QueryStartAtConstraint`s are created by invoking {@link (startAt:1)} or
  28742. * {@link (startAfter:1)} and can then be passed to {@link query} to create a
  28743. * new query instance that also contains this `QueryStartAtConstraint`.
  28744. */
  28745. class QueryStartAtConstraint extends QueryConstraint {
  28746. /**
  28747. * @internal
  28748. */
  28749. constructor(
  28750. /** The type of this query constraint */
  28751. type, _docOrFields, _inclusive) {
  28752. super();
  28753. this.type = type;
  28754. this._docOrFields = _docOrFields;
  28755. this._inclusive = _inclusive;
  28756. }
  28757. static _create(type, _docOrFields, _inclusive) {
  28758. return new QueryStartAtConstraint(type, _docOrFields, _inclusive);
  28759. }
  28760. _apply(query) {
  28761. const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
  28762. return new Query(query.firestore, query.converter, queryWithStartAt(query._query, bound));
  28763. }
  28764. }
  28765. function startAt(...docOrFields) {
  28766. return QueryStartAtConstraint._create('startAt', docOrFields,
  28767. /*inclusive=*/ true);
  28768. }
  28769. function startAfter(...docOrFields) {
  28770. return QueryStartAtConstraint._create('startAfter', docOrFields,
  28771. /*inclusive=*/ false);
  28772. }
  28773. /**
  28774. * A `QueryEndAtConstraint` is used to exclude documents from the end of a
  28775. * result set returned by a Firestore query.
  28776. * `QueryEndAtConstraint`s are created by invoking {@link (endAt:1)} or
  28777. * {@link (endBefore:1)} and can then be passed to {@link query} to create a new
  28778. * query instance that also contains this `QueryEndAtConstraint`.
  28779. */
  28780. class QueryEndAtConstraint extends QueryConstraint {
  28781. /**
  28782. * @internal
  28783. */
  28784. constructor(
  28785. /** The type of this query constraint */
  28786. type, _docOrFields, _inclusive) {
  28787. super();
  28788. this.type = type;
  28789. this._docOrFields = _docOrFields;
  28790. this._inclusive = _inclusive;
  28791. }
  28792. static _create(type, _docOrFields, _inclusive) {
  28793. return new QueryEndAtConstraint(type, _docOrFields, _inclusive);
  28794. }
  28795. _apply(query) {
  28796. const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
  28797. return new Query(query.firestore, query.converter, queryWithEndAt(query._query, bound));
  28798. }
  28799. }
  28800. function endBefore(...docOrFields) {
  28801. return QueryEndAtConstraint._create('endBefore', docOrFields,
  28802. /*inclusive=*/ false);
  28803. }
  28804. function endAt(...docOrFields) {
  28805. return QueryEndAtConstraint._create('endAt', docOrFields,
  28806. /*inclusive=*/ true);
  28807. }
  28808. /** Helper function to create a bound from a document or fields */
  28809. function newQueryBoundFromDocOrFields(query, methodName, docOrFields, inclusive) {
  28810. docOrFields[0] = util.getModularInstance(docOrFields[0]);
  28811. if (docOrFields[0] instanceof DocumentSnapshot$1) {
  28812. return newQueryBoundFromDocument(query._query, query.firestore._databaseId, methodName, docOrFields[0]._document, inclusive);
  28813. }
  28814. else {
  28815. const reader = newUserDataReader(query.firestore);
  28816. return newQueryBoundFromFields(query._query, query.firestore._databaseId, reader, methodName, docOrFields, inclusive);
  28817. }
  28818. }
  28819. function newQueryFilter(query, methodName, dataReader, databaseId, fieldPath, op, value) {
  28820. let fieldValue;
  28821. if (fieldPath.isKeyField()) {
  28822. if (op === "array-contains" /* Operator.ARRAY_CONTAINS */ || op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  28823. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid Query. You can't perform '${op}' queries on documentId().`);
  28824. }
  28825. else if (op === "in" /* Operator.IN */ || op === "not-in" /* Operator.NOT_IN */) {
  28826. validateDisjunctiveFilterElements(value, op);
  28827. const referenceList = [];
  28828. for (const arrayValue of value) {
  28829. referenceList.push(parseDocumentIdValue(databaseId, query, arrayValue));
  28830. }
  28831. fieldValue = { arrayValue: { values: referenceList } };
  28832. }
  28833. else {
  28834. fieldValue = parseDocumentIdValue(databaseId, query, value);
  28835. }
  28836. }
  28837. else {
  28838. if (op === "in" /* Operator.IN */ ||
  28839. op === "not-in" /* Operator.NOT_IN */ ||
  28840. op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  28841. validateDisjunctiveFilterElements(value, op);
  28842. }
  28843. fieldValue = parseQueryValue(dataReader, methodName, value,
  28844. /* allowArrays= */ op === "in" /* Operator.IN */ || op === "not-in" /* Operator.NOT_IN */);
  28845. }
  28846. const filter = FieldFilter.create(fieldPath, op, fieldValue);
  28847. return filter;
  28848. }
  28849. function newQueryOrderBy(query, fieldPath, direction) {
  28850. if (query.startAt !== null) {
  28851. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You must not call startAt() or startAfter() before ' +
  28852. 'calling orderBy().');
  28853. }
  28854. if (query.endAt !== null) {
  28855. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You must not call endAt() or endBefore() before ' +
  28856. 'calling orderBy().');
  28857. }
  28858. const orderBy = new OrderBy(fieldPath, direction);
  28859. validateNewOrderBy(query, orderBy);
  28860. return orderBy;
  28861. }
  28862. /**
  28863. * Create a `Bound` from a query and a document.
  28864. *
  28865. * Note that the `Bound` will always include the key of the document
  28866. * and so only the provided document will compare equal to the returned
  28867. * position.
  28868. *
  28869. * Will throw if the document does not contain all fields of the order by
  28870. * of the query or if any of the fields in the order by are an uncommitted
  28871. * server timestamp.
  28872. */
  28873. function newQueryBoundFromDocument(query, databaseId, methodName, doc, inclusive) {
  28874. if (!doc) {
  28875. throw new FirestoreError(Code.NOT_FOUND, `Can't use a DocumentSnapshot that doesn't exist for ` +
  28876. `${methodName}().`);
  28877. }
  28878. const components = [];
  28879. // Because people expect to continue/end a query at the exact document
  28880. // provided, we need to use the implicit sort order rather than the explicit
  28881. // sort order, because it's guaranteed to contain the document key. That way
  28882. // the position becomes unambiguous and the query continues/ends exactly at
  28883. // the provided document. Without the key (by using the explicit sort
  28884. // orders), multiple documents could match the position, yielding duplicate
  28885. // results.
  28886. for (const orderBy of queryOrderBy(query)) {
  28887. if (orderBy.field.isKeyField()) {
  28888. components.push(refValue(databaseId, doc.key));
  28889. }
  28890. else {
  28891. const value = doc.data.field(orderBy.field);
  28892. if (isServerTimestamp(value)) {
  28893. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You are trying to start or end a query using a ' +
  28894. 'document for which the field "' +
  28895. orderBy.field +
  28896. '" is an uncommitted server timestamp. (Since the value of ' +
  28897. 'this field is unknown, you cannot start/end a query with it.)');
  28898. }
  28899. else if (value !== null) {
  28900. components.push(value);
  28901. }
  28902. else {
  28903. const field = orderBy.field.canonicalString();
  28904. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. You are trying to start or end a query using a ` +
  28905. `document for which the field '${field}' (used as the ` +
  28906. `orderBy) does not exist.`);
  28907. }
  28908. }
  28909. }
  28910. return new Bound(components, inclusive);
  28911. }
  28912. /**
  28913. * Converts a list of field values to a `Bound` for the given query.
  28914. */
  28915. function newQueryBoundFromFields(query, databaseId, dataReader, methodName, values, inclusive) {
  28916. // Use explicit order by's because it has to match the query the user made
  28917. const orderBy = query.explicitOrderBy;
  28918. if (values.length > orderBy.length) {
  28919. throw new FirestoreError(Code.INVALID_ARGUMENT, `Too many arguments provided to ${methodName}(). ` +
  28920. `The number of arguments must be less than or equal to the ` +
  28921. `number of orderBy() clauses`);
  28922. }
  28923. const components = [];
  28924. for (let i = 0; i < values.length; i++) {
  28925. const rawValue = values[i];
  28926. const orderByComponent = orderBy[i];
  28927. if (orderByComponent.field.isKeyField()) {
  28928. if (typeof rawValue !== 'string') {
  28929. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. Expected a string for document ID in ` +
  28930. `${methodName}(), but got a ${typeof rawValue}`);
  28931. }
  28932. if (!isCollectionGroupQuery(query) && rawValue.indexOf('/') !== -1) {
  28933. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection and ordering by documentId(), ` +
  28934. `the value passed to ${methodName}() must be a plain document ID, but ` +
  28935. `'${rawValue}' contains a slash.`);
  28936. }
  28937. const path = query.path.child(ResourcePath.fromString(rawValue));
  28938. if (!DocumentKey.isDocumentKey(path)) {
  28939. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection group and ordering by ` +
  28940. `documentId(), the value passed to ${methodName}() must result in a ` +
  28941. `valid document path, but '${path}' is not because it contains an odd number ` +
  28942. `of segments.`);
  28943. }
  28944. const key = new DocumentKey(path);
  28945. components.push(refValue(databaseId, key));
  28946. }
  28947. else {
  28948. const wrapped = parseQueryValue(dataReader, methodName, rawValue);
  28949. components.push(wrapped);
  28950. }
  28951. }
  28952. return new Bound(components, inclusive);
  28953. }
  28954. /**
  28955. * Parses the given `documentIdValue` into a `ReferenceValue`, throwing
  28956. * appropriate errors if the value is anything other than a `DocumentReference`
  28957. * or `string`, or if the string is malformed.
  28958. */
  28959. function parseDocumentIdValue(databaseId, query, documentIdValue) {
  28960. documentIdValue = util.getModularInstance(documentIdValue);
  28961. if (typeof documentIdValue === 'string') {
  28962. if (documentIdValue === '') {
  28963. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. When querying with documentId(), you ' +
  28964. 'must provide a valid document ID, but it was an empty string.');
  28965. }
  28966. if (!isCollectionGroupQuery(query) && documentIdValue.indexOf('/') !== -1) {
  28967. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection by ` +
  28968. `documentId(), you must provide a plain document ID, but ` +
  28969. `'${documentIdValue}' contains a '/' character.`);
  28970. }
  28971. const path = query.path.child(ResourcePath.fromString(documentIdValue));
  28972. if (!DocumentKey.isDocumentKey(path)) {
  28973. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection group by ` +
  28974. `documentId(), the value provided must result in a valid document path, ` +
  28975. `but '${path}' is not because it has an odd number of segments (${path.length}).`);
  28976. }
  28977. return refValue(databaseId, new DocumentKey(path));
  28978. }
  28979. else if (documentIdValue instanceof DocumentReference) {
  28980. return refValue(databaseId, documentIdValue._key);
  28981. }
  28982. else {
  28983. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying with documentId(), you must provide a valid ` +
  28984. `string or a DocumentReference, but it was: ` +
  28985. `${valueDescription(documentIdValue)}.`);
  28986. }
  28987. }
  28988. /**
  28989. * Validates that the value passed into a disjunctive filter satisfies all
  28990. * array requirements.
  28991. */
  28992. function validateDisjunctiveFilterElements(value, operator) {
  28993. if (!Array.isArray(value) || value.length === 0) {
  28994. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid Query. A non-empty array is required for ' +
  28995. `'${operator.toString()}' filters.`);
  28996. }
  28997. if (value.length > 10) {
  28998. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid Query. '${operator.toString()}' filters support a ` +
  28999. 'maximum of 10 elements in the value array.');
  29000. }
  29001. }
  29002. /**
  29003. * Given an operator, returns the set of operators that cannot be used with it.
  29004. *
  29005. * Operators in a query must adhere to the following set of rules:
  29006. * 1. Only one array operator is allowed.
  29007. * 2. Only one disjunctive operator is allowed.
  29008. * 3. `NOT_EQUAL` cannot be used with another `NOT_EQUAL` operator.
  29009. * 4. `NOT_IN` cannot be used with array, disjunctive, or `NOT_EQUAL` operators.
  29010. *
  29011. * Array operators: `ARRAY_CONTAINS`, `ARRAY_CONTAINS_ANY`
  29012. * Disjunctive operators: `IN`, `ARRAY_CONTAINS_ANY`, `NOT_IN`
  29013. */
  29014. function conflictingOps(op) {
  29015. switch (op) {
  29016. case "!=" /* Operator.NOT_EQUAL */:
  29017. return ["!=" /* Operator.NOT_EQUAL */, "not-in" /* Operator.NOT_IN */];
  29018. case "array-contains" /* Operator.ARRAY_CONTAINS */:
  29019. return [
  29020. "array-contains" /* Operator.ARRAY_CONTAINS */,
  29021. "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */,
  29022. "not-in" /* Operator.NOT_IN */
  29023. ];
  29024. case "in" /* Operator.IN */:
  29025. return ["array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */, "in" /* Operator.IN */, "not-in" /* Operator.NOT_IN */];
  29026. case "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */:
  29027. return [
  29028. "array-contains" /* Operator.ARRAY_CONTAINS */,
  29029. "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */,
  29030. "in" /* Operator.IN */,
  29031. "not-in" /* Operator.NOT_IN */
  29032. ];
  29033. case "not-in" /* Operator.NOT_IN */:
  29034. return [
  29035. "array-contains" /* Operator.ARRAY_CONTAINS */,
  29036. "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */,
  29037. "in" /* Operator.IN */,
  29038. "not-in" /* Operator.NOT_IN */,
  29039. "!=" /* Operator.NOT_EQUAL */
  29040. ];
  29041. default:
  29042. return [];
  29043. }
  29044. }
  29045. function validateNewFieldFilter(query, fieldFilter) {
  29046. if (fieldFilter.isInequality()) {
  29047. const existingInequality = getInequalityFilterField(query);
  29048. const newInequality = fieldFilter.field;
  29049. if (existingInequality !== null &&
  29050. !existingInequality.isEqual(newInequality)) {
  29051. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. All where filters with an inequality' +
  29052. ' (<, <=, !=, not-in, >, or >=) must be on the same field. But you have' +
  29053. ` inequality filters on '${existingInequality.toString()}'` +
  29054. ` and '${newInequality.toString()}'`);
  29055. }
  29056. const firstOrderByField = getFirstOrderByField(query);
  29057. if (firstOrderByField !== null) {
  29058. validateOrderByAndInequalityMatch(query, newInequality, firstOrderByField);
  29059. }
  29060. }
  29061. const conflictingOp = findOpInsideFilters(query.filters, conflictingOps(fieldFilter.op));
  29062. if (conflictingOp !== null) {
  29063. // Special case when it's a duplicate op to give a slightly clearer error message.
  29064. if (conflictingOp === fieldFilter.op) {
  29065. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You cannot use more than one ' +
  29066. `'${fieldFilter.op.toString()}' filter.`);
  29067. }
  29068. else {
  29069. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. You cannot use '${fieldFilter.op.toString()}' filters ` +
  29070. `with '${conflictingOp.toString()}' filters.`);
  29071. }
  29072. }
  29073. }
  29074. function validateNewFilter(query, filter) {
  29075. let testQuery = query;
  29076. const subFilters = filter.getFlattenedFilters();
  29077. for (const subFilter of subFilters) {
  29078. validateNewFieldFilter(testQuery, subFilter);
  29079. testQuery = queryWithAddedFilter(testQuery, subFilter);
  29080. }
  29081. }
  29082. // Checks if any of the provided filter operators are included in the given list of filters and
  29083. // returns the first one that is, or null if none are.
  29084. function findOpInsideFilters(filters, operators) {
  29085. for (const filter of filters) {
  29086. for (const fieldFilter of filter.getFlattenedFilters()) {
  29087. if (operators.indexOf(fieldFilter.op) >= 0) {
  29088. return fieldFilter.op;
  29089. }
  29090. }
  29091. }
  29092. return null;
  29093. }
  29094. function validateNewOrderBy(query, orderBy) {
  29095. if (getFirstOrderByField(query) === null) {
  29096. // This is the first order by. It must match any inequality.
  29097. const inequalityField = getInequalityFilterField(query);
  29098. if (inequalityField !== null) {
  29099. validateOrderByAndInequalityMatch(query, inequalityField, orderBy.field);
  29100. }
  29101. }
  29102. }
  29103. function validateOrderByAndInequalityMatch(baseQuery, inequality, orderBy) {
  29104. if (!orderBy.isEqual(inequality)) {
  29105. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. You have a where filter with an inequality ` +
  29106. `(<, <=, !=, not-in, >, or >=) on field '${inequality.toString()}' ` +
  29107. `and so you must also use '${inequality.toString()}' ` +
  29108. `as your first argument to orderBy(), but your first orderBy() ` +
  29109. `is on field '${orderBy.toString()}' instead.`);
  29110. }
  29111. }
  29112. function validateQueryFilterConstraint(functionName, queryConstraint) {
  29113. if (!(queryConstraint instanceof QueryFieldFilterConstraint) &&
  29114. !(queryConstraint instanceof QueryCompositeFilterConstraint)) {
  29115. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires AppliableConstraints created with a call to 'where(...)', 'or(...)', or 'and(...)'.`);
  29116. }
  29117. }
  29118. function validateQueryConstraintArray(queryConstraint) {
  29119. const compositeFilterCount = queryConstraint.filter(filter => filter instanceof QueryCompositeFilterConstraint).length;
  29120. const fieldFilterCount = queryConstraint.filter(filter => filter instanceof QueryFieldFilterConstraint).length;
  29121. if (compositeFilterCount > 1 ||
  29122. (compositeFilterCount > 0 && fieldFilterCount > 0)) {
  29123. throw new FirestoreError(Code.INVALID_ARGUMENT, 'InvalidQuery. When using composite filters, you cannot use ' +
  29124. 'more than one filter at the top level. Consider nesting the multiple ' +
  29125. 'filters within an `and(...)` statement. For example: ' +
  29126. 'change `query(query, where(...), or(...))` to ' +
  29127. '`query(query, and(where(...), or(...)))`.');
  29128. }
  29129. }
  29130. /**
  29131. * @license
  29132. * Copyright 2020 Google LLC
  29133. *
  29134. * Licensed under the Apache License, Version 2.0 (the "License");
  29135. * you may not use this file except in compliance with the License.
  29136. * You may obtain a copy of the License at
  29137. *
  29138. * http://www.apache.org/licenses/LICENSE-2.0
  29139. *
  29140. * Unless required by applicable law or agreed to in writing, software
  29141. * distributed under the License is distributed on an "AS IS" BASIS,
  29142. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29143. * See the License for the specific language governing permissions and
  29144. * limitations under the License.
  29145. */
  29146. /**
  29147. * Converts Firestore's internal types to the JavaScript types that we expose
  29148. * to the user.
  29149. *
  29150. * @internal
  29151. */
  29152. class AbstractUserDataWriter {
  29153. convertValue(value, serverTimestampBehavior = 'none') {
  29154. switch (typeOrder(value)) {
  29155. case 0 /* TypeOrder.NullValue */:
  29156. return null;
  29157. case 1 /* TypeOrder.BooleanValue */:
  29158. return value.booleanValue;
  29159. case 2 /* TypeOrder.NumberValue */:
  29160. return normalizeNumber(value.integerValue || value.doubleValue);
  29161. case 3 /* TypeOrder.TimestampValue */:
  29162. return this.convertTimestamp(value.timestampValue);
  29163. case 4 /* TypeOrder.ServerTimestampValue */:
  29164. return this.convertServerTimestamp(value, serverTimestampBehavior);
  29165. case 5 /* TypeOrder.StringValue */:
  29166. return value.stringValue;
  29167. case 6 /* TypeOrder.BlobValue */:
  29168. return this.convertBytes(normalizeByteString(value.bytesValue));
  29169. case 7 /* TypeOrder.RefValue */:
  29170. return this.convertReference(value.referenceValue);
  29171. case 8 /* TypeOrder.GeoPointValue */:
  29172. return this.convertGeoPoint(value.geoPointValue);
  29173. case 9 /* TypeOrder.ArrayValue */:
  29174. return this.convertArray(value.arrayValue, serverTimestampBehavior);
  29175. case 10 /* TypeOrder.ObjectValue */:
  29176. return this.convertObject(value.mapValue, serverTimestampBehavior);
  29177. default:
  29178. throw fail();
  29179. }
  29180. }
  29181. convertObject(mapValue, serverTimestampBehavior) {
  29182. const result = {};
  29183. forEach(mapValue.fields, (key, value) => {
  29184. result[key] = this.convertValue(value, serverTimestampBehavior);
  29185. });
  29186. return result;
  29187. }
  29188. convertGeoPoint(value) {
  29189. return new GeoPoint(normalizeNumber(value.latitude), normalizeNumber(value.longitude));
  29190. }
  29191. convertArray(arrayValue, serverTimestampBehavior) {
  29192. return (arrayValue.values || []).map(value => this.convertValue(value, serverTimestampBehavior));
  29193. }
  29194. convertServerTimestamp(value, serverTimestampBehavior) {
  29195. switch (serverTimestampBehavior) {
  29196. case 'previous':
  29197. const previousValue = getPreviousValue(value);
  29198. if (previousValue == null) {
  29199. return null;
  29200. }
  29201. return this.convertValue(previousValue, serverTimestampBehavior);
  29202. case 'estimate':
  29203. return this.convertTimestamp(getLocalWriteTime(value));
  29204. default:
  29205. return null;
  29206. }
  29207. }
  29208. convertTimestamp(value) {
  29209. const normalizedValue = normalizeTimestamp(value);
  29210. return new Timestamp(normalizedValue.seconds, normalizedValue.nanos);
  29211. }
  29212. convertDocumentKey(name, expectedDatabaseId) {
  29213. const resourcePath = ResourcePath.fromString(name);
  29214. hardAssert(isValidResourceName(resourcePath));
  29215. const databaseId = new DatabaseId(resourcePath.get(1), resourcePath.get(3));
  29216. const key = new DocumentKey(resourcePath.popFirst(5));
  29217. if (!databaseId.isEqual(expectedDatabaseId)) {
  29218. // TODO(b/64130202): Somehow support foreign references.
  29219. logError(`Document ${key} contains a document ` +
  29220. `reference within a different database (` +
  29221. `${databaseId.projectId}/${databaseId.database}) which is not ` +
  29222. `supported. It will be treated as a reference in the current ` +
  29223. `database (${expectedDatabaseId.projectId}/${expectedDatabaseId.database}) ` +
  29224. `instead.`);
  29225. }
  29226. return key;
  29227. }
  29228. }
  29229. /**
  29230. * @license
  29231. * Copyright 2020 Google LLC
  29232. *
  29233. * Licensed under the Apache License, Version 2.0 (the "License");
  29234. * you may not use this file except in compliance with the License.
  29235. * You may obtain a copy of the License at
  29236. *
  29237. * http://www.apache.org/licenses/LICENSE-2.0
  29238. *
  29239. * Unless required by applicable law or agreed to in writing, software
  29240. * distributed under the License is distributed on an "AS IS" BASIS,
  29241. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29242. * See the License for the specific language governing permissions and
  29243. * limitations under the License.
  29244. */
  29245. /**
  29246. * Converts custom model object of type T into `DocumentData` by applying the
  29247. * converter if it exists.
  29248. *
  29249. * This function is used when converting user objects to `DocumentData`
  29250. * because we want to provide the user with a more specific error message if
  29251. * their `set()` or fails due to invalid data originating from a `toFirestore()`
  29252. * call.
  29253. */
  29254. function applyFirestoreDataConverter(converter, value, options) {
  29255. let convertedValue;
  29256. if (converter) {
  29257. if (options && (options.merge || options.mergeFields)) {
  29258. // Cast to `any` in order to satisfy the union type constraint on
  29259. // toFirestore().
  29260. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  29261. convertedValue = converter.toFirestore(value, options);
  29262. }
  29263. else {
  29264. convertedValue = converter.toFirestore(value);
  29265. }
  29266. }
  29267. else {
  29268. convertedValue = value;
  29269. }
  29270. return convertedValue;
  29271. }
  29272. class LiteUserDataWriter extends AbstractUserDataWriter {
  29273. constructor(firestore) {
  29274. super();
  29275. this.firestore = firestore;
  29276. }
  29277. convertBytes(bytes) {
  29278. return new Bytes(bytes);
  29279. }
  29280. convertReference(name) {
  29281. const key = this.convertDocumentKey(name, this.firestore._databaseId);
  29282. return new DocumentReference(this.firestore, /* converter= */ null, key);
  29283. }
  29284. }
  29285. /**
  29286. * @license
  29287. * Copyright 2020 Google LLC
  29288. *
  29289. * Licensed under the Apache License, Version 2.0 (the "License");
  29290. * you may not use this file except in compliance with the License.
  29291. * You may obtain a copy of the License at
  29292. *
  29293. * http://www.apache.org/licenses/LICENSE-2.0
  29294. *
  29295. * Unless required by applicable law or agreed to in writing, software
  29296. * distributed under the License is distributed on an "AS IS" BASIS,
  29297. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29298. * See the License for the specific language governing permissions and
  29299. * limitations under the License.
  29300. */
  29301. /**
  29302. * Metadata about a snapshot, describing the state of the snapshot.
  29303. */
  29304. class SnapshotMetadata {
  29305. /** @hideconstructor */
  29306. constructor(hasPendingWrites, fromCache) {
  29307. this.hasPendingWrites = hasPendingWrites;
  29308. this.fromCache = fromCache;
  29309. }
  29310. /**
  29311. * Returns true if this `SnapshotMetadata` is equal to the provided one.
  29312. *
  29313. * @param other - The `SnapshotMetadata` to compare against.
  29314. * @returns true if this `SnapshotMetadata` is equal to the provided one.
  29315. */
  29316. isEqual(other) {
  29317. return (this.hasPendingWrites === other.hasPendingWrites &&
  29318. this.fromCache === other.fromCache);
  29319. }
  29320. }
  29321. /**
  29322. * A `DocumentSnapshot` contains data read from a document in your Firestore
  29323. * database. The data can be extracted with `.data()` or `.get(<field>)` to
  29324. * get a specific field.
  29325. *
  29326. * For a `DocumentSnapshot` that points to a non-existing document, any data
  29327. * access will return 'undefined'. You can use the `exists()` method to
  29328. * explicitly verify a document's existence.
  29329. */
  29330. class DocumentSnapshot extends DocumentSnapshot$1 {
  29331. /** @hideconstructor protected */
  29332. constructor(_firestore, userDataWriter, key, document, metadata, converter) {
  29333. super(_firestore, userDataWriter, key, document, converter);
  29334. this._firestore = _firestore;
  29335. this._firestoreImpl = _firestore;
  29336. this.metadata = metadata;
  29337. }
  29338. /**
  29339. * Returns whether or not the data exists. True if the document exists.
  29340. */
  29341. exists() {
  29342. return super.exists();
  29343. }
  29344. /**
  29345. * Retrieves all fields in the document as an `Object`. Returns `undefined` if
  29346. * the document doesn't exist.
  29347. *
  29348. * By default, `serverTimestamp()` values that have not yet been
  29349. * set to their final value will be returned as `null`. You can override
  29350. * this by passing an options object.
  29351. *
  29352. * @param options - An options object to configure how data is retrieved from
  29353. * the snapshot (for example the desired behavior for server timestamps that
  29354. * have not yet been set to their final value).
  29355. * @returns An `Object` containing all fields in the document or `undefined` if
  29356. * the document doesn't exist.
  29357. */
  29358. data(options = {}) {
  29359. if (!this._document) {
  29360. return undefined;
  29361. }
  29362. else if (this._converter) {
  29363. // We only want to use the converter and create a new DocumentSnapshot
  29364. // if a converter has been provided.
  29365. const snapshot = new QueryDocumentSnapshot(this._firestore, this._userDataWriter, this._key, this._document, this.metadata,
  29366. /* converter= */ null);
  29367. return this._converter.fromFirestore(snapshot, options);
  29368. }
  29369. else {
  29370. return this._userDataWriter.convertValue(this._document.data.value, options.serverTimestamps);
  29371. }
  29372. }
  29373. /**
  29374. * Retrieves the field specified by `fieldPath`. Returns `undefined` if the
  29375. * document or field doesn't exist.
  29376. *
  29377. * By default, a `serverTimestamp()` that has not yet been set to
  29378. * its final value will be returned as `null`. You can override this by
  29379. * passing an options object.
  29380. *
  29381. * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific
  29382. * field.
  29383. * @param options - An options object to configure how the field is retrieved
  29384. * from the snapshot (for example the desired behavior for server timestamps
  29385. * that have not yet been set to their final value).
  29386. * @returns The data at the specified field location or undefined if no such
  29387. * field exists in the document.
  29388. */
  29389. // We are using `any` here to avoid an explicit cast by our users.
  29390. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  29391. get(fieldPath, options = {}) {
  29392. if (this._document) {
  29393. const value = this._document.data.field(fieldPathFromArgument('DocumentSnapshot.get', fieldPath));
  29394. if (value !== null) {
  29395. return this._userDataWriter.convertValue(value, options.serverTimestamps);
  29396. }
  29397. }
  29398. return undefined;
  29399. }
  29400. }
  29401. /**
  29402. * A `QueryDocumentSnapshot` contains data read from a document in your
  29403. * Firestore database as part of a query. The document is guaranteed to exist
  29404. * and its data can be extracted with `.data()` or `.get(<field>)` to get a
  29405. * specific field.
  29406. *
  29407. * A `QueryDocumentSnapshot` offers the same API surface as a
  29408. * `DocumentSnapshot`. Since query results contain only existing documents, the
  29409. * `exists` property will always be true and `data()` will never return
  29410. * 'undefined'.
  29411. */
  29412. class QueryDocumentSnapshot extends DocumentSnapshot {
  29413. /**
  29414. * Retrieves all fields in the document as an `Object`.
  29415. *
  29416. * By default, `serverTimestamp()` values that have not yet been
  29417. * set to their final value will be returned as `null`. You can override
  29418. * this by passing an options object.
  29419. *
  29420. * @override
  29421. * @param options - An options object to configure how data is retrieved from
  29422. * the snapshot (for example the desired behavior for server timestamps that
  29423. * have not yet been set to their final value).
  29424. * @returns An `Object` containing all fields in the document.
  29425. */
  29426. data(options = {}) {
  29427. return super.data(options);
  29428. }
  29429. }
  29430. /**
  29431. * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects
  29432. * representing the results of a query. The documents can be accessed as an
  29433. * array via the `docs` property or enumerated using the `forEach` method. The
  29434. * number of documents can be determined via the `empty` and `size`
  29435. * properties.
  29436. */
  29437. class QuerySnapshot {
  29438. /** @hideconstructor */
  29439. constructor(_firestore, _userDataWriter, query, _snapshot) {
  29440. this._firestore = _firestore;
  29441. this._userDataWriter = _userDataWriter;
  29442. this._snapshot = _snapshot;
  29443. this.metadata = new SnapshotMetadata(_snapshot.hasPendingWrites, _snapshot.fromCache);
  29444. this.query = query;
  29445. }
  29446. /** An array of all the documents in the `QuerySnapshot`. */
  29447. get docs() {
  29448. const result = [];
  29449. this.forEach(doc => result.push(doc));
  29450. return result;
  29451. }
  29452. /** The number of documents in the `QuerySnapshot`. */
  29453. get size() {
  29454. return this._snapshot.docs.size;
  29455. }
  29456. /** True if there are no documents in the `QuerySnapshot`. */
  29457. get empty() {
  29458. return this.size === 0;
  29459. }
  29460. /**
  29461. * Enumerates all of the documents in the `QuerySnapshot`.
  29462. *
  29463. * @param callback - A callback to be called with a `QueryDocumentSnapshot` for
  29464. * each document in the snapshot.
  29465. * @param thisArg - The `this` binding for the callback.
  29466. */
  29467. forEach(callback, thisArg) {
  29468. this._snapshot.docs.forEach(doc => {
  29469. 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));
  29470. });
  29471. }
  29472. /**
  29473. * Returns an array of the documents changes since the last snapshot. If this
  29474. * is the first snapshot, all documents will be in the list as 'added'
  29475. * changes.
  29476. *
  29477. * @param options - `SnapshotListenOptions` that control whether metadata-only
  29478. * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger
  29479. * snapshot events.
  29480. */
  29481. docChanges(options = {}) {
  29482. const includeMetadataChanges = !!options.includeMetadataChanges;
  29483. if (includeMetadataChanges && this._snapshot.excludesMetadataChanges) {
  29484. throw new FirestoreError(Code.INVALID_ARGUMENT, 'To include metadata changes with your document changes, you must ' +
  29485. 'also pass { includeMetadataChanges:true } to onSnapshot().');
  29486. }
  29487. if (!this._cachedChanges ||
  29488. this._cachedChangesIncludeMetadataChanges !== includeMetadataChanges) {
  29489. this._cachedChanges = changesFromSnapshot(this, includeMetadataChanges);
  29490. this._cachedChangesIncludeMetadataChanges = includeMetadataChanges;
  29491. }
  29492. return this._cachedChanges;
  29493. }
  29494. }
  29495. /** Calculates the array of `DocumentChange`s for a given `ViewSnapshot`. */
  29496. function changesFromSnapshot(querySnapshot, includeMetadataChanges) {
  29497. if (querySnapshot._snapshot.oldDocs.isEmpty()) {
  29498. let index = 0;
  29499. return querySnapshot._snapshot.docChanges.map(change => {
  29500. 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);
  29501. change.doc;
  29502. return {
  29503. type: 'added',
  29504. doc,
  29505. oldIndex: -1,
  29506. newIndex: index++
  29507. };
  29508. });
  29509. }
  29510. else {
  29511. // A `DocumentSet` that is updated incrementally as changes are applied to use
  29512. // to lookup the index of a document.
  29513. let indexTracker = querySnapshot._snapshot.oldDocs;
  29514. return querySnapshot._snapshot.docChanges
  29515. .filter(change => includeMetadataChanges || change.type !== 3 /* ChangeType.Metadata */)
  29516. .map(change => {
  29517. 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);
  29518. let oldIndex = -1;
  29519. let newIndex = -1;
  29520. if (change.type !== 0 /* ChangeType.Added */) {
  29521. oldIndex = indexTracker.indexOf(change.doc.key);
  29522. indexTracker = indexTracker.delete(change.doc.key);
  29523. }
  29524. if (change.type !== 1 /* ChangeType.Removed */) {
  29525. indexTracker = indexTracker.add(change.doc);
  29526. newIndex = indexTracker.indexOf(change.doc.key);
  29527. }
  29528. return {
  29529. type: resultChangeType(change.type),
  29530. doc,
  29531. oldIndex,
  29532. newIndex
  29533. };
  29534. });
  29535. }
  29536. }
  29537. function resultChangeType(type) {
  29538. switch (type) {
  29539. case 0 /* ChangeType.Added */:
  29540. return 'added';
  29541. case 2 /* ChangeType.Modified */:
  29542. case 3 /* ChangeType.Metadata */:
  29543. return 'modified';
  29544. case 1 /* ChangeType.Removed */:
  29545. return 'removed';
  29546. default:
  29547. return fail();
  29548. }
  29549. }
  29550. // TODO(firestoreexp): Add tests for snapshotEqual with different snapshot
  29551. // metadata
  29552. /**
  29553. * Returns true if the provided snapshots are equal.
  29554. *
  29555. * @param left - A snapshot to compare.
  29556. * @param right - A snapshot to compare.
  29557. * @returns true if the snapshots are equal.
  29558. */
  29559. function snapshotEqual(left, right) {
  29560. if (left instanceof DocumentSnapshot && right instanceof DocumentSnapshot) {
  29561. return (left._firestore === right._firestore &&
  29562. left._key.isEqual(right._key) &&
  29563. (left._document === null
  29564. ? right._document === null
  29565. : left._document.isEqual(right._document)) &&
  29566. left._converter === right._converter);
  29567. }
  29568. else if (left instanceof QuerySnapshot && right instanceof QuerySnapshot) {
  29569. return (left._firestore === right._firestore &&
  29570. queryEqual(left.query, right.query) &&
  29571. left.metadata.isEqual(right.metadata) &&
  29572. left._snapshot.isEqual(right._snapshot));
  29573. }
  29574. return false;
  29575. }
  29576. /**
  29577. * @license
  29578. * Copyright 2020 Google LLC
  29579. *
  29580. * Licensed under the Apache License, Version 2.0 (the "License");
  29581. * you may not use this file except in compliance with the License.
  29582. * You may obtain a copy of the License at
  29583. *
  29584. * http://www.apache.org/licenses/LICENSE-2.0
  29585. *
  29586. * Unless required by applicable law or agreed to in writing, software
  29587. * distributed under the License is distributed on an "AS IS" BASIS,
  29588. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29589. * See the License for the specific language governing permissions and
  29590. * limitations under the License.
  29591. */
  29592. /**
  29593. * Reads the document referred to by this `DocumentReference`.
  29594. *
  29595. * Note: `getDoc()` attempts to provide up-to-date data when possible by waiting
  29596. * for data from the server, but it may return cached data or fail if you are
  29597. * offline and the server cannot be reached. To specify this behavior, invoke
  29598. * {@link getDocFromCache} or {@link getDocFromServer}.
  29599. *
  29600. * @param reference - The reference of the document to fetch.
  29601. * @returns A Promise resolved with a `DocumentSnapshot` containing the
  29602. * current document contents.
  29603. */
  29604. function getDoc(reference) {
  29605. reference = cast(reference, DocumentReference);
  29606. const firestore = cast(reference.firestore, Firestore);
  29607. const client = ensureFirestoreConfigured(firestore);
  29608. return firestoreClientGetDocumentViaSnapshotListener(client, reference._key).then(snapshot => convertToDocSnapshot(firestore, reference, snapshot));
  29609. }
  29610. class ExpUserDataWriter extends AbstractUserDataWriter {
  29611. constructor(firestore) {
  29612. super();
  29613. this.firestore = firestore;
  29614. }
  29615. convertBytes(bytes) {
  29616. return new Bytes(bytes);
  29617. }
  29618. convertReference(name) {
  29619. const key = this.convertDocumentKey(name, this.firestore._databaseId);
  29620. return new DocumentReference(this.firestore, /* converter= */ null, key);
  29621. }
  29622. }
  29623. /**
  29624. * Reads the document referred to by this `DocumentReference` from cache.
  29625. * Returns an error if the document is not currently cached.
  29626. *
  29627. * @returns A `Promise` resolved with a `DocumentSnapshot` containing the
  29628. * current document contents.
  29629. */
  29630. function getDocFromCache(reference) {
  29631. reference = cast(reference, DocumentReference);
  29632. const firestore = cast(reference.firestore, Firestore);
  29633. const client = ensureFirestoreConfigured(firestore);
  29634. const userDataWriter = new ExpUserDataWriter(firestore);
  29635. return firestoreClientGetDocumentFromLocalCache(client, reference._key).then(doc => new DocumentSnapshot(firestore, userDataWriter, reference._key, doc, new SnapshotMetadata(doc !== null && doc.hasLocalMutations,
  29636. /* fromCache= */ true), reference.converter));
  29637. }
  29638. /**
  29639. * Reads the document referred to by this `DocumentReference` from the server.
  29640. * Returns an error if the network is not available.
  29641. *
  29642. * @returns A `Promise` resolved with a `DocumentSnapshot` containing the
  29643. * current document contents.
  29644. */
  29645. function getDocFromServer(reference) {
  29646. reference = cast(reference, DocumentReference);
  29647. const firestore = cast(reference.firestore, Firestore);
  29648. const client = ensureFirestoreConfigured(firestore);
  29649. return firestoreClientGetDocumentViaSnapshotListener(client, reference._key, {
  29650. source: 'server'
  29651. }).then(snapshot => convertToDocSnapshot(firestore, reference, snapshot));
  29652. }
  29653. /**
  29654. * Executes the query and returns the results as a `QuerySnapshot`.
  29655. *
  29656. * Note: `getDocs()` attempts to provide up-to-date data when possible by
  29657. * waiting for data from the server, but it may return cached data or fail if
  29658. * you are offline and the server cannot be reached. To specify this behavior,
  29659. * invoke {@link getDocsFromCache} or {@link getDocsFromServer}.
  29660. *
  29661. * @returns A `Promise` that will be resolved with the results of the query.
  29662. */
  29663. function getDocs(query) {
  29664. query = cast(query, Query);
  29665. const firestore = cast(query.firestore, Firestore);
  29666. const client = ensureFirestoreConfigured(firestore);
  29667. const userDataWriter = new ExpUserDataWriter(firestore);
  29668. validateHasExplicitOrderByForLimitToLast(query._query);
  29669. return firestoreClientGetDocumentsViaSnapshotListener(client, query._query).then(snapshot => new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  29670. }
  29671. /**
  29672. * Executes the query and returns the results as a `QuerySnapshot` from cache.
  29673. * Returns an empty result set if no documents matching the query are currently
  29674. * cached.
  29675. *
  29676. * @returns A `Promise` that will be resolved with the results of the query.
  29677. */
  29678. function getDocsFromCache(query) {
  29679. query = cast(query, Query);
  29680. const firestore = cast(query.firestore, Firestore);
  29681. const client = ensureFirestoreConfigured(firestore);
  29682. const userDataWriter = new ExpUserDataWriter(firestore);
  29683. return firestoreClientGetDocumentsFromLocalCache(client, query._query).then(snapshot => new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  29684. }
  29685. /**
  29686. * Executes the query and returns the results as a `QuerySnapshot` from the
  29687. * server. Returns an error if the network is not available.
  29688. *
  29689. * @returns A `Promise` that will be resolved with the results of the query.
  29690. */
  29691. function getDocsFromServer(query) {
  29692. query = cast(query, Query);
  29693. const firestore = cast(query.firestore, Firestore);
  29694. const client = ensureFirestoreConfigured(firestore);
  29695. const userDataWriter = new ExpUserDataWriter(firestore);
  29696. return firestoreClientGetDocumentsViaSnapshotListener(client, query._query, {
  29697. source: 'server'
  29698. }).then(snapshot => new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  29699. }
  29700. function setDoc(reference, data, options) {
  29701. reference = cast(reference, DocumentReference);
  29702. const firestore = cast(reference.firestore, Firestore);
  29703. const convertedValue = applyFirestoreDataConverter(reference.converter, data, options);
  29704. const dataReader = newUserDataReader(firestore);
  29705. const parsed = parseSetData(dataReader, 'setDoc', reference._key, convertedValue, reference.converter !== null, options);
  29706. const mutation = parsed.toMutation(reference._key, Precondition.none());
  29707. return executeWrite(firestore, [mutation]);
  29708. }
  29709. function updateDoc(reference, fieldOrUpdateData, value, ...moreFieldsAndValues) {
  29710. reference = cast(reference, DocumentReference);
  29711. const firestore = cast(reference.firestore, Firestore);
  29712. const dataReader = newUserDataReader(firestore);
  29713. // For Compat types, we have to "extract" the underlying types before
  29714. // performing validation.
  29715. fieldOrUpdateData = util.getModularInstance(fieldOrUpdateData);
  29716. let parsed;
  29717. if (typeof fieldOrUpdateData === 'string' ||
  29718. fieldOrUpdateData instanceof FieldPath) {
  29719. parsed = parseUpdateVarargs(dataReader, 'updateDoc', reference._key, fieldOrUpdateData, value, moreFieldsAndValues);
  29720. }
  29721. else {
  29722. parsed = parseUpdateData(dataReader, 'updateDoc', reference._key, fieldOrUpdateData);
  29723. }
  29724. const mutation = parsed.toMutation(reference._key, Precondition.exists(true));
  29725. return executeWrite(firestore, [mutation]);
  29726. }
  29727. /**
  29728. * Deletes the document referred to by the specified `DocumentReference`.
  29729. *
  29730. * @param reference - A reference to the document to delete.
  29731. * @returns A Promise resolved once the document has been successfully
  29732. * deleted from the backend (note that it won't resolve while you're offline).
  29733. */
  29734. function deleteDoc(reference) {
  29735. const firestore = cast(reference.firestore, Firestore);
  29736. const mutations = [new DeleteMutation(reference._key, Precondition.none())];
  29737. return executeWrite(firestore, mutations);
  29738. }
  29739. /**
  29740. * Add a new document to specified `CollectionReference` with the given data,
  29741. * assigning it a document ID automatically.
  29742. *
  29743. * @param reference - A reference to the collection to add this document to.
  29744. * @param data - An Object containing the data for the new document.
  29745. * @returns A `Promise` resolved with a `DocumentReference` pointing to the
  29746. * newly created document after it has been written to the backend (Note that it
  29747. * won't resolve while you're offline).
  29748. */
  29749. function addDoc(reference, data) {
  29750. const firestore = cast(reference.firestore, Firestore);
  29751. const docRef = doc(reference);
  29752. const convertedValue = applyFirestoreDataConverter(reference.converter, data);
  29753. const dataReader = newUserDataReader(reference.firestore);
  29754. const parsed = parseSetData(dataReader, 'addDoc', docRef._key, convertedValue, reference.converter !== null, {});
  29755. const mutation = parsed.toMutation(docRef._key, Precondition.exists(false));
  29756. return executeWrite(firestore, [mutation]).then(() => docRef);
  29757. }
  29758. function onSnapshot(reference, ...args) {
  29759. var _a, _b, _c;
  29760. reference = util.getModularInstance(reference);
  29761. let options = {
  29762. includeMetadataChanges: false
  29763. };
  29764. let currArg = 0;
  29765. if (typeof args[currArg] === 'object' && !isPartialObserver(args[currArg])) {
  29766. options = args[currArg];
  29767. currArg++;
  29768. }
  29769. const internalOptions = {
  29770. includeMetadataChanges: options.includeMetadataChanges
  29771. };
  29772. if (isPartialObserver(args[currArg])) {
  29773. const userObserver = args[currArg];
  29774. args[currArg] = (_a = userObserver.next) === null || _a === void 0 ? void 0 : _a.bind(userObserver);
  29775. args[currArg + 1] = (_b = userObserver.error) === null || _b === void 0 ? void 0 : _b.bind(userObserver);
  29776. args[currArg + 2] = (_c = userObserver.complete) === null || _c === void 0 ? void 0 : _c.bind(userObserver);
  29777. }
  29778. let observer;
  29779. let firestore;
  29780. let internalQuery;
  29781. if (reference instanceof DocumentReference) {
  29782. firestore = cast(reference.firestore, Firestore);
  29783. internalQuery = newQueryForPath(reference._key.path);
  29784. observer = {
  29785. next: snapshot => {
  29786. if (args[currArg]) {
  29787. args[currArg](convertToDocSnapshot(firestore, reference, snapshot));
  29788. }
  29789. },
  29790. error: args[currArg + 1],
  29791. complete: args[currArg + 2]
  29792. };
  29793. }
  29794. else {
  29795. const query = cast(reference, Query);
  29796. firestore = cast(query.firestore, Firestore);
  29797. internalQuery = query._query;
  29798. const userDataWriter = new ExpUserDataWriter(firestore);
  29799. observer = {
  29800. next: snapshot => {
  29801. if (args[currArg]) {
  29802. args[currArg](new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  29803. }
  29804. },
  29805. error: args[currArg + 1],
  29806. complete: args[currArg + 2]
  29807. };
  29808. validateHasExplicitOrderByForLimitToLast(reference._query);
  29809. }
  29810. const client = ensureFirestoreConfigured(firestore);
  29811. return firestoreClientListen(client, internalQuery, internalOptions, observer);
  29812. }
  29813. function onSnapshotsInSync(firestore, arg) {
  29814. firestore = cast(firestore, Firestore);
  29815. const client = ensureFirestoreConfigured(firestore);
  29816. const observer = isPartialObserver(arg)
  29817. ? arg
  29818. : {
  29819. next: arg
  29820. };
  29821. return firestoreClientAddSnapshotsInSyncListener(client, observer);
  29822. }
  29823. /**
  29824. * Locally writes `mutations` on the async queue.
  29825. * @internal
  29826. */
  29827. function executeWrite(firestore, mutations) {
  29828. const client = ensureFirestoreConfigured(firestore);
  29829. return firestoreClientWrite(client, mutations);
  29830. }
  29831. /**
  29832. * Converts a {@link ViewSnapshot} that contains the single document specified by `ref`
  29833. * to a {@link DocumentSnapshot}.
  29834. */
  29835. function convertToDocSnapshot(firestore, ref, snapshot) {
  29836. const doc = snapshot.docs.get(ref._key);
  29837. const userDataWriter = new ExpUserDataWriter(firestore);
  29838. return new DocumentSnapshot(firestore, userDataWriter, ref._key, doc, new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache), ref.converter);
  29839. }
  29840. /**
  29841. * @license
  29842. * Copyright 2022 Google LLC
  29843. *
  29844. * Licensed under the Apache License, Version 2.0 (the "License");
  29845. * you may not use this file except in compliance with the License.
  29846. * You may obtain a copy of the License at
  29847. *
  29848. * http://www.apache.org/licenses/LICENSE-2.0
  29849. *
  29850. * Unless required by applicable law or agreed to in writing, software
  29851. * distributed under the License is distributed on an "AS IS" BASIS,
  29852. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29853. * See the License for the specific language governing permissions and
  29854. * limitations under the License.
  29855. */
  29856. /**
  29857. * Compares two `AggregateQuerySnapshot` instances for equality.
  29858. *
  29859. * Two `AggregateQuerySnapshot` instances are considered "equal" if they have
  29860. * underlying queries that compare equal, and the same data.
  29861. *
  29862. * @param left - The first `AggregateQuerySnapshot` to compare.
  29863. * @param right - The second `AggregateQuerySnapshot` to compare.
  29864. *
  29865. * @returns `true` if the objects are "equal", as defined above, or `false`
  29866. * otherwise.
  29867. */
  29868. function aggregateQuerySnapshotEqual(left, right) {
  29869. return (queryEqual(left.query, right.query) && util.deepEqual(left.data(), right.data()));
  29870. }
  29871. /**
  29872. * @license
  29873. * Copyright 2022 Google LLC
  29874. *
  29875. * Licensed under the Apache License, Version 2.0 (the "License");
  29876. * you may not use this file except in compliance with the License.
  29877. * You may obtain a copy of the License at
  29878. *
  29879. * http://www.apache.org/licenses/LICENSE-2.0
  29880. *
  29881. * Unless required by applicable law or agreed to in writing, software
  29882. * distributed under the License is distributed on an "AS IS" BASIS,
  29883. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29884. * See the License for the specific language governing permissions and
  29885. * limitations under the License.
  29886. */
  29887. /**
  29888. * Calculates the number of documents in the result set of the given query,
  29889. * without actually downloading the documents.
  29890. *
  29891. * Using this function to count the documents is efficient because only the
  29892. * final count, not the documents' data, is downloaded. This function can even
  29893. * count the documents if the result set would be prohibitively large to
  29894. * download entirely (e.g. thousands of documents).
  29895. *
  29896. * The result received from the server is presented, unaltered, without
  29897. * considering any local state. That is, documents in the local cache are not
  29898. * taken into consideration, neither are local modifications not yet
  29899. * synchronized with the server. Previously-downloaded results, if any, are not
  29900. * used: every request using this source necessarily involves a round trip to
  29901. * the server.
  29902. *
  29903. * @param query - The query whose result set size to calculate.
  29904. * @returns A Promise that will be resolved with the count; the count can be
  29905. * retrieved from `snapshot.data().count`, where `snapshot` is the
  29906. * `AggregateQuerySnapshot` to which the returned Promise resolves.
  29907. */
  29908. function getCountFromServer(query) {
  29909. const firestore = cast(query.firestore, Firestore);
  29910. const client = ensureFirestoreConfigured(firestore);
  29911. const userDataWriter = new ExpUserDataWriter(firestore);
  29912. return firestoreClientRunCountQuery(client, query, userDataWriter);
  29913. }
  29914. /**
  29915. * @license
  29916. * Copyright 2022 Google LLC
  29917. *
  29918. * Licensed under the Apache License, Version 2.0 (the "License");
  29919. * you may not use this file except in compliance with the License.
  29920. * You may obtain a copy of the License at
  29921. *
  29922. * http://www.apache.org/licenses/LICENSE-2.0
  29923. *
  29924. * Unless required by applicable law or agreed to in writing, software
  29925. * distributed under the License is distributed on an "AS IS" BASIS,
  29926. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29927. * See the License for the specific language governing permissions and
  29928. * limitations under the License.
  29929. */
  29930. const DEFAULT_TRANSACTION_OPTIONS = {
  29931. maxAttempts: 5
  29932. };
  29933. function validateTransactionOptions(options) {
  29934. if (options.maxAttempts < 1) {
  29935. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Max attempts must be at least 1');
  29936. }
  29937. }
  29938. /**
  29939. * @license
  29940. * Copyright 2020 Google LLC
  29941. *
  29942. * Licensed under the Apache License, Version 2.0 (the "License");
  29943. * you may not use this file except in compliance with the License.
  29944. * You may obtain a copy of the License at
  29945. *
  29946. * http://www.apache.org/licenses/LICENSE-2.0
  29947. *
  29948. * Unless required by applicable law or agreed to in writing, software
  29949. * distributed under the License is distributed on an "AS IS" BASIS,
  29950. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29951. * See the License for the specific language governing permissions and
  29952. * limitations under the License.
  29953. */
  29954. /**
  29955. * A write batch, used to perform multiple writes as a single atomic unit.
  29956. *
  29957. * A `WriteBatch` object can be acquired by calling {@link writeBatch}. It
  29958. * provides methods for adding writes to the write batch. None of the writes
  29959. * will be committed (or visible locally) until {@link WriteBatch.commit} is
  29960. * called.
  29961. */
  29962. class WriteBatch {
  29963. /** @hideconstructor */
  29964. constructor(_firestore, _commitHandler) {
  29965. this._firestore = _firestore;
  29966. this._commitHandler = _commitHandler;
  29967. this._mutations = [];
  29968. this._committed = false;
  29969. this._dataReader = newUserDataReader(_firestore);
  29970. }
  29971. set(documentRef, data, options) {
  29972. this._verifyNotCommitted();
  29973. const ref = validateReference(documentRef, this._firestore);
  29974. const convertedValue = applyFirestoreDataConverter(ref.converter, data, options);
  29975. const parsed = parseSetData(this._dataReader, 'WriteBatch.set', ref._key, convertedValue, ref.converter !== null, options);
  29976. this._mutations.push(parsed.toMutation(ref._key, Precondition.none()));
  29977. return this;
  29978. }
  29979. update(documentRef, fieldOrUpdateData, value, ...moreFieldsAndValues) {
  29980. this._verifyNotCommitted();
  29981. const ref = validateReference(documentRef, this._firestore);
  29982. // For Compat types, we have to "extract" the underlying types before
  29983. // performing validation.
  29984. fieldOrUpdateData = util.getModularInstance(fieldOrUpdateData);
  29985. let parsed;
  29986. if (typeof fieldOrUpdateData === 'string' ||
  29987. fieldOrUpdateData instanceof FieldPath) {
  29988. parsed = parseUpdateVarargs(this._dataReader, 'WriteBatch.update', ref._key, fieldOrUpdateData, value, moreFieldsAndValues);
  29989. }
  29990. else {
  29991. parsed = parseUpdateData(this._dataReader, 'WriteBatch.update', ref._key, fieldOrUpdateData);
  29992. }
  29993. this._mutations.push(parsed.toMutation(ref._key, Precondition.exists(true)));
  29994. return this;
  29995. }
  29996. /**
  29997. * Deletes the document referred to by the provided {@link DocumentReference}.
  29998. *
  29999. * @param documentRef - A reference to the document to be deleted.
  30000. * @returns This `WriteBatch` instance. Used for chaining method calls.
  30001. */
  30002. delete(documentRef) {
  30003. this._verifyNotCommitted();
  30004. const ref = validateReference(documentRef, this._firestore);
  30005. this._mutations = this._mutations.concat(new DeleteMutation(ref._key, Precondition.none()));
  30006. return this;
  30007. }
  30008. /**
  30009. * Commits all of the writes in this write batch as a single atomic unit.
  30010. *
  30011. * The result of these writes will only be reflected in document reads that
  30012. * occur after the returned promise resolves. If the client is offline, the
  30013. * write fails. If you would like to see local modifications or buffer writes
  30014. * until the client is online, use the full Firestore SDK.
  30015. *
  30016. * @returns A `Promise` resolved once all of the writes in the batch have been
  30017. * successfully written to the backend as an atomic unit (note that it won't
  30018. * resolve while you're offline).
  30019. */
  30020. commit() {
  30021. this._verifyNotCommitted();
  30022. this._committed = true;
  30023. if (this._mutations.length > 0) {
  30024. return this._commitHandler(this._mutations);
  30025. }
  30026. return Promise.resolve();
  30027. }
  30028. _verifyNotCommitted() {
  30029. if (this._committed) {
  30030. throw new FirestoreError(Code.FAILED_PRECONDITION, 'A write batch can no longer be used after commit() ' +
  30031. 'has been called.');
  30032. }
  30033. }
  30034. }
  30035. function validateReference(documentRef, firestore) {
  30036. documentRef = util.getModularInstance(documentRef);
  30037. if (documentRef.firestore !== firestore) {
  30038. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Provided document reference is from a different Firestore instance.');
  30039. }
  30040. else {
  30041. return documentRef;
  30042. }
  30043. }
  30044. /**
  30045. * @license
  30046. * Copyright 2020 Google LLC
  30047. *
  30048. * Licensed under the Apache License, Version 2.0 (the "License");
  30049. * you may not use this file except in compliance with the License.
  30050. * You may obtain a copy of the License at
  30051. *
  30052. * http://www.apache.org/licenses/LICENSE-2.0
  30053. *
  30054. * Unless required by applicable law or agreed to in writing, software
  30055. * distributed under the License is distributed on an "AS IS" BASIS,
  30056. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30057. * See the License for the specific language governing permissions and
  30058. * limitations under the License.
  30059. */
  30060. // TODO(mrschmidt) Consider using `BaseTransaction` as the base class in the
  30061. // legacy SDK.
  30062. /**
  30063. * A reference to a transaction.
  30064. *
  30065. * The `Transaction` object passed to a transaction's `updateFunction` provides
  30066. * the methods to read and write data within the transaction context. See
  30067. * {@link runTransaction}.
  30068. */
  30069. class Transaction$1 {
  30070. /** @hideconstructor */
  30071. constructor(_firestore, _transaction) {
  30072. this._firestore = _firestore;
  30073. this._transaction = _transaction;
  30074. this._dataReader = newUserDataReader(_firestore);
  30075. }
  30076. /**
  30077. * Reads the document referenced by the provided {@link DocumentReference}.
  30078. *
  30079. * @param documentRef - A reference to the document to be read.
  30080. * @returns A `DocumentSnapshot` with the read data.
  30081. */
  30082. get(documentRef) {
  30083. const ref = validateReference(documentRef, this._firestore);
  30084. const userDataWriter = new LiteUserDataWriter(this._firestore);
  30085. return this._transaction.lookup([ref._key]).then(docs => {
  30086. if (!docs || docs.length !== 1) {
  30087. return fail();
  30088. }
  30089. const doc = docs[0];
  30090. if (doc.isFoundDocument()) {
  30091. return new DocumentSnapshot$1(this._firestore, userDataWriter, doc.key, doc, ref.converter);
  30092. }
  30093. else if (doc.isNoDocument()) {
  30094. return new DocumentSnapshot$1(this._firestore, userDataWriter, ref._key, null, ref.converter);
  30095. }
  30096. else {
  30097. throw fail();
  30098. }
  30099. });
  30100. }
  30101. set(documentRef, value, options) {
  30102. const ref = validateReference(documentRef, this._firestore);
  30103. const convertedValue = applyFirestoreDataConverter(ref.converter, value, options);
  30104. const parsed = parseSetData(this._dataReader, 'Transaction.set', ref._key, convertedValue, ref.converter !== null, options);
  30105. this._transaction.set(ref._key, parsed);
  30106. return this;
  30107. }
  30108. update(documentRef, fieldOrUpdateData, value, ...moreFieldsAndValues) {
  30109. const ref = validateReference(documentRef, this._firestore);
  30110. // For Compat types, we have to "extract" the underlying types before
  30111. // performing validation.
  30112. fieldOrUpdateData = util.getModularInstance(fieldOrUpdateData);
  30113. let parsed;
  30114. if (typeof fieldOrUpdateData === 'string' ||
  30115. fieldOrUpdateData instanceof FieldPath) {
  30116. parsed = parseUpdateVarargs(this._dataReader, 'Transaction.update', ref._key, fieldOrUpdateData, value, moreFieldsAndValues);
  30117. }
  30118. else {
  30119. parsed = parseUpdateData(this._dataReader, 'Transaction.update', ref._key, fieldOrUpdateData);
  30120. }
  30121. this._transaction.update(ref._key, parsed);
  30122. return this;
  30123. }
  30124. /**
  30125. * Deletes the document referred to by the provided {@link DocumentReference}.
  30126. *
  30127. * @param documentRef - A reference to the document to be deleted.
  30128. * @returns This `Transaction` instance. Used for chaining method calls.
  30129. */
  30130. delete(documentRef) {
  30131. const ref = validateReference(documentRef, this._firestore);
  30132. this._transaction.delete(ref._key);
  30133. return this;
  30134. }
  30135. }
  30136. /**
  30137. * @license
  30138. * Copyright 2020 Google LLC
  30139. *
  30140. * Licensed under the Apache License, Version 2.0 (the "License");
  30141. * you may not use this file except in compliance with the License.
  30142. * You may obtain a copy of the License at
  30143. *
  30144. * http://www.apache.org/licenses/LICENSE-2.0
  30145. *
  30146. * Unless required by applicable law or agreed to in writing, software
  30147. * distributed under the License is distributed on an "AS IS" BASIS,
  30148. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30149. * See the License for the specific language governing permissions and
  30150. * limitations under the License.
  30151. */
  30152. /**
  30153. * A reference to a transaction.
  30154. *
  30155. * The `Transaction` object passed to a transaction's `updateFunction` provides
  30156. * the methods to read and write data within the transaction context. See
  30157. * {@link runTransaction}.
  30158. */
  30159. class Transaction extends Transaction$1 {
  30160. // This class implements the same logic as the Transaction API in the Lite SDK
  30161. // but is subclassed in order to return its own DocumentSnapshot types.
  30162. /** @hideconstructor */
  30163. constructor(_firestore, _transaction) {
  30164. super(_firestore, _transaction);
  30165. this._firestore = _firestore;
  30166. }
  30167. /**
  30168. * Reads the document referenced by the provided {@link DocumentReference}.
  30169. *
  30170. * @param documentRef - A reference to the document to be read.
  30171. * @returns A `DocumentSnapshot` with the read data.
  30172. */
  30173. get(documentRef) {
  30174. const ref = validateReference(documentRef, this._firestore);
  30175. const userDataWriter = new ExpUserDataWriter(this._firestore);
  30176. return super
  30177. .get(documentRef)
  30178. .then(liteDocumentSnapshot => new DocumentSnapshot(this._firestore, userDataWriter, ref._key, liteDocumentSnapshot._document, new SnapshotMetadata(
  30179. /* hasPendingWrites= */ false,
  30180. /* fromCache= */ false), ref.converter));
  30181. }
  30182. }
  30183. /**
  30184. * Executes the given `updateFunction` and then attempts to commit the changes
  30185. * applied within the transaction. If any document read within the transaction
  30186. * has changed, Cloud Firestore retries the `updateFunction`. If it fails to
  30187. * commit after 5 attempts, the transaction fails.
  30188. *
  30189. * The maximum number of writes allowed in a single transaction is 500.
  30190. *
  30191. * @param firestore - A reference to the Firestore database to run this
  30192. * transaction against.
  30193. * @param updateFunction - The function to execute within the transaction
  30194. * context.
  30195. * @param options - An options object to configure maximum number of attempts to
  30196. * commit.
  30197. * @returns If the transaction completed successfully or was explicitly aborted
  30198. * (the `updateFunction` returned a failed promise), the promise returned by the
  30199. * `updateFunction `is returned here. Otherwise, if the transaction failed, a
  30200. * rejected promise with the corresponding failure error is returned.
  30201. */
  30202. function runTransaction(firestore, updateFunction, options) {
  30203. firestore = cast(firestore, Firestore);
  30204. const optionsWithDefaults = Object.assign(Object.assign({}, DEFAULT_TRANSACTION_OPTIONS), options);
  30205. validateTransactionOptions(optionsWithDefaults);
  30206. const client = ensureFirestoreConfigured(firestore);
  30207. return firestoreClientTransaction(client, internalTransaction => updateFunction(new Transaction(firestore, internalTransaction)), optionsWithDefaults);
  30208. }
  30209. /**
  30210. * @license
  30211. * Copyright 2020 Google LLC
  30212. *
  30213. * Licensed under the Apache License, Version 2.0 (the "License");
  30214. * you may not use this file except in compliance with the License.
  30215. * You may obtain a copy of the License at
  30216. *
  30217. * http://www.apache.org/licenses/LICENSE-2.0
  30218. *
  30219. * Unless required by applicable law or agreed to in writing, software
  30220. * distributed under the License is distributed on an "AS IS" BASIS,
  30221. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30222. * See the License for the specific language governing permissions and
  30223. * limitations under the License.
  30224. */
  30225. /**
  30226. * Returns a sentinel for use with {@link @firebase/firestore/lite#(updateDoc:1)} or
  30227. * {@link @firebase/firestore/lite#(setDoc:1)} with `{merge: true}` to mark a field for deletion.
  30228. */
  30229. function deleteField() {
  30230. return new DeleteFieldValueImpl('deleteField');
  30231. }
  30232. /**
  30233. * Returns a sentinel used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link @firebase/firestore/lite#(updateDoc:1)} to
  30234. * include a server-generated timestamp in the written data.
  30235. */
  30236. function serverTimestamp() {
  30237. return new ServerTimestampFieldValueImpl('serverTimestamp');
  30238. }
  30239. /**
  30240. * Returns a special value that can be used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link
  30241. * @firebase/firestore/lite#(updateDoc:1)} that tells the server to union the given elements with any array
  30242. * value that already exists on the server. Each specified element that doesn't
  30243. * already exist in the array will be added to the end. If the field being
  30244. * modified is not already an array it will be overwritten with an array
  30245. * containing exactly the specified elements.
  30246. *
  30247. * @param elements - The elements to union into the array.
  30248. * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
  30249. * `updateDoc()`.
  30250. */
  30251. function arrayUnion(...elements) {
  30252. // NOTE: We don't actually parse the data until it's used in set() or
  30253. // update() since we'd need the Firestore instance to do this.
  30254. return new ArrayUnionFieldValueImpl('arrayUnion', elements);
  30255. }
  30256. /**
  30257. * Returns a special value that can be used with {@link (setDoc:1)} or {@link
  30258. * updateDoc:1} that tells the server to remove the given elements from any
  30259. * array value that already exists on the server. All instances of each element
  30260. * specified will be removed from the array. If the field being modified is not
  30261. * already an array it will be overwritten with an empty array.
  30262. *
  30263. * @param elements - The elements to remove from the array.
  30264. * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
  30265. * `updateDoc()`
  30266. */
  30267. function arrayRemove(...elements) {
  30268. // NOTE: We don't actually parse the data until it's used in set() or
  30269. // update() since we'd need the Firestore instance to do this.
  30270. return new ArrayRemoveFieldValueImpl('arrayRemove', elements);
  30271. }
  30272. /**
  30273. * Returns a special value that can be used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link
  30274. * @firebase/firestore/lite#(updateDoc:1)} that tells the server to increment the field's current value by
  30275. * the given value.
  30276. *
  30277. * If either the operand or the current field value uses floating point
  30278. * precision, all arithmetic follows IEEE 754 semantics. If both values are
  30279. * integers, values outside of JavaScript's safe number range
  30280. * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject to
  30281. * precision loss. Furthermore, once processed by the Firestore backend, all
  30282. * integer operations are capped between -2^63 and 2^63-1.
  30283. *
  30284. * If the current field value is not of type `number`, or if the field does not
  30285. * yet exist, the transformation sets the field to the given value.
  30286. *
  30287. * @param n - The value to increment by.
  30288. * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
  30289. * `updateDoc()`
  30290. */
  30291. function increment(n) {
  30292. return new NumericIncrementFieldValueImpl('increment', n);
  30293. }
  30294. /**
  30295. * @license
  30296. * Copyright 2020 Google LLC
  30297. *
  30298. * Licensed under the Apache License, Version 2.0 (the "License");
  30299. * you may not use this file except in compliance with the License.
  30300. * You may obtain a copy of the License at
  30301. *
  30302. * http://www.apache.org/licenses/LICENSE-2.0
  30303. *
  30304. * Unless required by applicable law or agreed to in writing, software
  30305. * distributed under the License is distributed on an "AS IS" BASIS,
  30306. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30307. * See the License for the specific language governing permissions and
  30308. * limitations under the License.
  30309. */
  30310. /**
  30311. * Creates a write batch, used for performing multiple writes as a single
  30312. * atomic operation. The maximum number of writes allowed in a single {@link WriteBatch}
  30313. * is 500.
  30314. *
  30315. * Unlike transactions, write batches are persisted offline and therefore are
  30316. * preferable when you don't need to condition your writes on read data.
  30317. *
  30318. * @returns A {@link WriteBatch} that can be used to atomically execute multiple
  30319. * writes.
  30320. */
  30321. function writeBatch(firestore) {
  30322. firestore = cast(firestore, Firestore);
  30323. ensureFirestoreConfigured(firestore);
  30324. return new WriteBatch(firestore, mutations => executeWrite(firestore, mutations));
  30325. }
  30326. /**
  30327. * @license
  30328. * Copyright 2021 Google LLC
  30329. *
  30330. * Licensed under the Apache License, Version 2.0 (the "License");
  30331. * you may not use this file except in compliance with the License.
  30332. * You may obtain a copy of the License at
  30333. *
  30334. * http://www.apache.org/licenses/LICENSE-2.0
  30335. *
  30336. * Unless required by applicable law or agreed to in writing, software
  30337. * distributed under the License is distributed on an "AS IS" BASIS,
  30338. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30339. * See the License for the specific language governing permissions and
  30340. * limitations under the License.
  30341. */
  30342. function setIndexConfiguration(firestore, jsonOrConfiguration) {
  30343. var _a;
  30344. firestore = cast(firestore, Firestore);
  30345. const client = ensureFirestoreConfigured(firestore);
  30346. // PORTING NOTE: We don't return an error if the user has not enabled
  30347. // persistence since `enableIndexeddbPersistence()` can fail on the Web.
  30348. if (!((_a = client.offlineComponents) === null || _a === void 0 ? void 0 : _a.indexBackfillerScheduler)) {
  30349. logWarn('Cannot enable indexes when persistence is disabled');
  30350. return Promise.resolve();
  30351. }
  30352. const parsedIndexes = parseIndexes(jsonOrConfiguration);
  30353. return getLocalStore(client).then(localStore => localStoreConfigureFieldIndexes(localStore, parsedIndexes));
  30354. }
  30355. function parseIndexes(jsonOrConfiguration) {
  30356. const indexConfiguration = typeof jsonOrConfiguration === 'string'
  30357. ? tryParseJson(jsonOrConfiguration)
  30358. : jsonOrConfiguration;
  30359. const parsedIndexes = [];
  30360. if (Array.isArray(indexConfiguration.indexes)) {
  30361. for (const index of indexConfiguration.indexes) {
  30362. const collectionGroup = tryGetString(index, 'collectionGroup');
  30363. const segments = [];
  30364. if (Array.isArray(index.fields)) {
  30365. for (const field of index.fields) {
  30366. const fieldPathString = tryGetString(field, 'fieldPath');
  30367. const fieldPath = fieldPathFromDotSeparatedString('setIndexConfiguration', fieldPathString);
  30368. if (field.arrayConfig === 'CONTAINS') {
  30369. segments.push(new IndexSegment(fieldPath, 2 /* IndexKind.CONTAINS */));
  30370. }
  30371. else if (field.order === 'ASCENDING') {
  30372. segments.push(new IndexSegment(fieldPath, 0 /* IndexKind.ASCENDING */));
  30373. }
  30374. else if (field.order === 'DESCENDING') {
  30375. segments.push(new IndexSegment(fieldPath, 1 /* IndexKind.DESCENDING */));
  30376. }
  30377. }
  30378. }
  30379. parsedIndexes.push(new FieldIndex(FieldIndex.UNKNOWN_ID, collectionGroup, segments, IndexState.empty()));
  30380. }
  30381. }
  30382. return parsedIndexes;
  30383. }
  30384. function tryParseJson(json) {
  30385. try {
  30386. return JSON.parse(json);
  30387. }
  30388. catch (e) {
  30389. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Failed to parse JSON: ' + (e === null || e === void 0 ? void 0 : e.message));
  30390. }
  30391. }
  30392. function tryGetString(data, property) {
  30393. if (typeof data[property] !== 'string') {
  30394. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Missing string value for: ' + property);
  30395. }
  30396. return data[property];
  30397. }
  30398. /**
  30399. * @license
  30400. * Copyright 2021 Google LLC
  30401. *
  30402. * Licensed under the Apache License, Version 2.0 (the "License");
  30403. * you may not use this file except in compliance with the License.
  30404. * You may obtain a copy of the License at
  30405. *
  30406. * http://www.apache.org/licenses/LICENSE-2.0
  30407. *
  30408. * Unless required by applicable law or agreed to in writing, software
  30409. * distributed under the License is distributed on an "AS IS" BASIS,
  30410. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30411. * See the License for the specific language governing permissions and
  30412. * limitations under the License.
  30413. */
  30414. registerFirestore('node');
  30415. exports.AbstractUserDataWriter = AbstractUserDataWriter;
  30416. exports.AggregateField = AggregateField;
  30417. exports.AggregateQuerySnapshot = AggregateQuerySnapshot;
  30418. exports.Bytes = Bytes;
  30419. exports.CACHE_SIZE_UNLIMITED = CACHE_SIZE_UNLIMITED;
  30420. exports.CollectionReference = CollectionReference;
  30421. exports.DocumentReference = DocumentReference;
  30422. exports.DocumentSnapshot = DocumentSnapshot;
  30423. exports.FieldPath = FieldPath;
  30424. exports.FieldValue = FieldValue;
  30425. exports.Firestore = Firestore;
  30426. exports.FirestoreError = FirestoreError;
  30427. exports.GeoPoint = GeoPoint;
  30428. exports.LoadBundleTask = LoadBundleTask;
  30429. exports.Query = Query;
  30430. exports.QueryCompositeFilterConstraint = QueryCompositeFilterConstraint;
  30431. exports.QueryConstraint = QueryConstraint;
  30432. exports.QueryDocumentSnapshot = QueryDocumentSnapshot;
  30433. exports.QueryEndAtConstraint = QueryEndAtConstraint;
  30434. exports.QueryFieldFilterConstraint = QueryFieldFilterConstraint;
  30435. exports.QueryLimitConstraint = QueryLimitConstraint;
  30436. exports.QueryOrderByConstraint = QueryOrderByConstraint;
  30437. exports.QuerySnapshot = QuerySnapshot;
  30438. exports.QueryStartAtConstraint = QueryStartAtConstraint;
  30439. exports.SnapshotMetadata = SnapshotMetadata;
  30440. exports.Timestamp = Timestamp;
  30441. exports.Transaction = Transaction;
  30442. exports.WriteBatch = WriteBatch;
  30443. exports._DatabaseId = DatabaseId;
  30444. exports._DocumentKey = DocumentKey;
  30445. exports._EmptyAppCheckTokenProvider = EmptyAppCheckTokenProvider;
  30446. exports._EmptyAuthCredentialsProvider = EmptyAuthCredentialsProvider;
  30447. exports._FieldPath = FieldPath$1;
  30448. exports._cast = cast;
  30449. exports._debugAssert = debugAssert;
  30450. exports._isBase64Available = isBase64Available;
  30451. exports._logWarn = logWarn;
  30452. exports._validateIsNotUsedTogether = validateIsNotUsedTogether;
  30453. exports.addDoc = addDoc;
  30454. exports.aggregateQuerySnapshotEqual = aggregateQuerySnapshotEqual;
  30455. exports.and = and;
  30456. exports.arrayRemove = arrayRemove;
  30457. exports.arrayUnion = arrayUnion;
  30458. exports.clearIndexedDbPersistence = clearIndexedDbPersistence;
  30459. exports.collection = collection;
  30460. exports.collectionGroup = collectionGroup;
  30461. exports.connectFirestoreEmulator = connectFirestoreEmulator;
  30462. exports.deleteDoc = deleteDoc;
  30463. exports.deleteField = deleteField;
  30464. exports.disableNetwork = disableNetwork;
  30465. exports.doc = doc;
  30466. exports.documentId = documentId;
  30467. exports.enableIndexedDbPersistence = enableIndexedDbPersistence;
  30468. exports.enableMultiTabIndexedDbPersistence = enableMultiTabIndexedDbPersistence;
  30469. exports.enableNetwork = enableNetwork;
  30470. exports.endAt = endAt;
  30471. exports.endBefore = endBefore;
  30472. exports.ensureFirestoreConfigured = ensureFirestoreConfigured;
  30473. exports.executeWrite = executeWrite;
  30474. exports.getCountFromServer = getCountFromServer;
  30475. exports.getDoc = getDoc;
  30476. exports.getDocFromCache = getDocFromCache;
  30477. exports.getDocFromServer = getDocFromServer;
  30478. exports.getDocs = getDocs;
  30479. exports.getDocsFromCache = getDocsFromCache;
  30480. exports.getDocsFromServer = getDocsFromServer;
  30481. exports.getFirestore = getFirestore;
  30482. exports.increment = increment;
  30483. exports.initializeFirestore = initializeFirestore;
  30484. exports.limit = limit;
  30485. exports.limitToLast = limitToLast;
  30486. exports.loadBundle = loadBundle;
  30487. exports.namedQuery = namedQuery;
  30488. exports.onSnapshot = onSnapshot;
  30489. exports.onSnapshotsInSync = onSnapshotsInSync;
  30490. exports.or = or;
  30491. exports.orderBy = orderBy;
  30492. exports.query = query;
  30493. exports.queryEqual = queryEqual;
  30494. exports.refEqual = refEqual;
  30495. exports.runTransaction = runTransaction;
  30496. exports.serverTimestamp = serverTimestamp;
  30497. exports.setDoc = setDoc;
  30498. exports.setIndexConfiguration = setIndexConfiguration;
  30499. exports.setLogLevel = setLogLevel;
  30500. exports.snapshotEqual = snapshotEqual;
  30501. exports.startAfter = startAfter;
  30502. exports.startAt = startAt;
  30503. exports.terminate = terminate;
  30504. exports.updateDoc = updateDoc;
  30505. exports.waitForPendingWrites = waitForPendingWrites;
  30506. exports.where = where;
  30507. exports.writeBatch = writeBatch;
  30508. //# sourceMappingURL=index.node.cjs.js.map