index.node.mjs 1.2 MB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334253352533625337253382533925340253412534225343253442534525346253472534825349253502535125352253532535425355253562535725358253592536025361253622536325364253652536625367253682536925370253712537225373253742537525376253772537825379253802538125382253832538425385253862538725388253892539025391253922539325394253952539625397253982539925400254012540225403254042540525406254072540825409254102541125412254132541425415254162541725418254192542025421254222542325424254252542625427254282542925430254312543225433254342543525436254372543825439254402544125442254432544425445254462544725448254492545025451254522545325454254552545625457254582545925460254612546225463254642546525466254672546825469254702547125472254732547425475254762547725478254792548025481254822548325484254852548625487254882548925490254912549225493254942549525496254972549825499255002550125502255032550425505255062550725508255092551025511255122551325514255152551625517255182551925520255212552225523255242552525526255272552825529255302553125532255332553425535255362553725538255392554025541255422554325544255452554625547255482554925550255512555225553255542555525556255572555825559255602556125562255632556425565255662556725568255692557025571255722557325574255752557625577255782557925580255812558225583255842558525586255872558825589255902559125592255932559425595255962559725598255992560025601256022560325604256052560625607256082560925610256112561225613256142561525616256172561825619256202562125622256232562425625256262562725628256292563025631256322563325634256352563625637256382563925640256412564225643256442564525646256472564825649256502565125652256532565425655256562565725658256592566025661256622566325664256652566625667256682566925670256712567225673256742567525676256772567825679256802568125682256832568425685256862568725688256892569025691256922569325694256952569625697256982569925700257012570225703257042570525706257072570825709257102571125712257132571425715257162571725718257192572025721257222572325724257252572625727257282572925730257312573225733257342573525736257372573825739257402574125742257432574425745257462574725748257492575025751257522575325754257552575625757257582575925760257612576225763257642576525766257672576825769257702577125772257732577425775257762577725778257792578025781257822578325784257852578625787257882578925790257912579225793257942579525796257972579825799258002580125802258032580425805258062580725808258092581025811258122581325814258152581625817258182581925820258212582225823258242582525826258272582825829258302583125832258332583425835258362583725838258392584025841258422584325844258452584625847258482584925850258512585225853258542585525856258572585825859258602586125862258632586425865258662586725868258692587025871258722587325874258752587625877258782587925880258812588225883258842588525886258872588825889258902589125892258932589425895258962589725898258992590025901259022590325904259052590625907259082590925910259112591225913259142591525916259172591825919259202592125922259232592425925259262592725928259292593025931259322593325934259352593625937259382593925940259412594225943259442594525946259472594825949259502595125952259532595425955259562595725958259592596025961259622596325964259652596625967259682596925970259712597225973259742597525976259772597825979259802598125982259832598425985259862598725988259892599025991259922599325994259952599625997259982599926000260012600226003260042600526006260072600826009260102601126012260132601426015260162601726018260192602026021260222602326024260252602626027260282602926030260312603226033260342603526036260372603826039260402604126042260432604426045260462604726048260492605026051260522605326054260552605626057260582605926060260612606226063260642606526066260672606826069260702607126072260732607426075260762607726078260792608026081260822608326084260852608626087260882608926090260912609226093260942609526096260972609826099261002610126102261032610426105261062610726108261092611026111261122611326114261152611626117261182611926120261212612226123261242612526126261272612826129261302613126132261332613426135261362613726138261392614026141261422614326144261452614626147261482614926150261512615226153261542615526156261572615826159261602616126162261632616426165261662616726168261692617026171261722617326174261752617626177261782617926180261812618226183261842618526186261872618826189261902619126192261932619426195261962619726198261992620026201262022620326204262052620626207262082620926210262112621226213262142621526216262172621826219262202622126222262232622426225262262622726228262292623026231262322623326234262352623626237262382623926240262412624226243262442624526246262472624826249262502625126252262532625426255262562625726258262592626026261262622626326264262652626626267262682626926270262712627226273262742627526276262772627826279262802628126282262832628426285262862628726288262892629026291262922629326294262952629626297262982629926300263012630226303263042630526306263072630826309263102631126312263132631426315263162631726318263192632026321263222632326324263252632626327263282632926330263312633226333263342633526336263372633826339263402634126342263432634426345263462634726348263492635026351263522635326354263552635626357263582635926360263612636226363263642636526366263672636826369263702637126372263732637426375263762637726378263792638026381263822638326384263852638626387263882638926390263912639226393263942639526396263972639826399264002640126402264032640426405264062640726408264092641026411264122641326414264152641626417264182641926420264212642226423264242642526426264272642826429264302643126432264332643426435264362643726438264392644026441264422644326444264452644626447264482644926450264512645226453264542645526456264572645826459264602646126462264632646426465264662646726468264692647026471264722647326474264752647626477264782647926480264812648226483264842648526486264872648826489264902649126492264932649426495264962649726498264992650026501265022650326504265052650626507265082650926510265112651226513265142651526516265172651826519265202652126522265232652426525265262652726528265292653026531265322653326534265352653626537265382653926540265412654226543265442654526546265472654826549265502655126552265532655426555265562655726558265592656026561265622656326564265652656626567265682656926570265712657226573265742657526576265772657826579265802658126582265832658426585265862658726588265892659026591265922659326594265952659626597265982659926600266012660226603266042660526606266072660826609266102661126612266132661426615266162661726618266192662026621266222662326624266252662626627266282662926630266312663226633266342663526636266372663826639266402664126642266432664426645266462664726648266492665026651266522665326654266552665626657266582665926660266612666226663266642666526666266672666826669266702667126672266732667426675266762667726678266792668026681266822668326684266852668626687266882668926690266912669226693266942669526696266972669826699267002670126702267032670426705267062670726708267092671026711267122671326714267152671626717267182671926720267212672226723267242672526726267272672826729267302673126732267332673426735267362673726738267392674026741267422674326744267452674626747267482674926750267512675226753267542675526756267572675826759267602676126762267632676426765267662676726768267692677026771267722677326774267752677626777267782677926780267812678226783267842678526786267872678826789267902679126792267932679426795267962679726798267992680026801268022680326804268052680626807268082680926810268112681226813268142681526816268172681826819268202682126822268232682426825268262682726828268292683026831268322683326834268352683626837268382683926840268412684226843268442684526846268472684826849268502685126852268532685426855268562685726858268592686026861268622686326864268652686626867268682686926870268712687226873268742687526876268772687826879268802688126882268832688426885268862688726888268892689026891268922689326894268952689626897268982689926900269012690226903269042690526906269072690826909269102691126912269132691426915269162691726918269192692026921269222692326924269252692626927269282692926930269312693226933269342693526936269372693826939269402694126942269432694426945269462694726948269492695026951269522695326954269552695626957269582695926960269612696226963269642696526966269672696826969269702697126972269732697426975269762697726978269792698026981269822698326984269852698626987269882698926990269912699226993269942699526996269972699826999270002700127002270032700427005270062700727008270092701027011270122701327014270152701627017270182701927020270212702227023270242702527026270272702827029270302703127032270332703427035270362703727038270392704027041270422704327044270452704627047270482704927050270512705227053270542705527056270572705827059270602706127062270632706427065270662706727068270692707027071270722707327074270752707627077270782707927080270812708227083270842708527086270872708827089270902709127092270932709427095270962709727098270992710027101271022710327104271052710627107271082710927110271112711227113271142711527116271172711827119271202712127122271232712427125271262712727128271292713027131271322713327134271352713627137271382713927140271412714227143271442714527146271472714827149271502715127152271532715427155271562715727158271592716027161271622716327164271652716627167271682716927170271712717227173271742717527176271772717827179271802718127182271832718427185271862718727188271892719027191271922719327194271952719627197271982719927200272012720227203272042720527206272072720827209272102721127212272132721427215272162721727218272192722027221272222722327224272252722627227272282722927230272312723227233272342723527236272372723827239272402724127242272432724427245272462724727248272492725027251272522725327254272552725627257272582725927260272612726227263272642726527266272672726827269272702727127272272732727427275272762727727278272792728027281272822728327284272852728627287272882728927290272912729227293272942729527296272972729827299273002730127302273032730427305273062730727308273092731027311273122731327314273152731627317273182731927320273212732227323273242732527326273272732827329273302733127332273332733427335273362733727338273392734027341273422734327344273452734627347273482734927350273512735227353273542735527356273572735827359273602736127362273632736427365273662736727368273692737027371273722737327374273752737627377273782737927380273812738227383273842738527386273872738827389273902739127392273932739427395273962739727398273992740027401274022740327404274052740627407274082740927410274112741227413274142741527416274172741827419274202742127422274232742427425274262742727428274292743027431274322743327434274352743627437274382743927440274412744227443274442744527446274472744827449274502745127452274532745427455274562745727458274592746027461274622746327464274652746627467274682746927470274712747227473274742747527476274772747827479274802748127482274832748427485274862748727488274892749027491274922749327494274952749627497274982749927500275012750227503275042750527506275072750827509275102751127512275132751427515275162751727518275192752027521275222752327524275252752627527275282752927530275312753227533275342753527536275372753827539275402754127542275432754427545275462754727548275492755027551275522755327554275552755627557275582755927560275612756227563275642756527566275672756827569275702757127572275732757427575275762757727578275792758027581275822758327584275852758627587275882758927590275912759227593275942759527596275972759827599276002760127602276032760427605276062760727608276092761027611276122761327614276152761627617276182761927620276212762227623276242762527626276272762827629276302763127632276332763427635276362763727638276392764027641276422764327644276452764627647276482764927650276512765227653276542765527656276572765827659276602766127662276632766427665276662766727668276692767027671276722767327674276752767627677276782767927680276812768227683276842768527686276872768827689276902769127692276932769427695276962769727698276992770027701277022770327704277052770627707277082770927710277112771227713277142771527716277172771827719277202772127722277232772427725277262772727728277292773027731277322773327734277352773627737277382773927740277412774227743277442774527746277472774827749277502775127752277532775427755277562775727758277592776027761277622776327764277652776627767277682776927770277712777227773277742777527776277772777827779277802778127782277832778427785277862778727788277892779027791277922779327794277952779627797277982779927800278012780227803278042780527806278072780827809278102781127812278132781427815278162781727818278192782027821278222782327824278252782627827278282782927830278312783227833278342783527836278372783827839278402784127842278432784427845278462784727848278492785027851278522785327854278552785627857278582785927860278612786227863278642786527866278672786827869278702787127872278732787427875278762787727878278792788027881278822788327884278852788627887278882788927890278912789227893278942789527896278972789827899279002790127902279032790427905279062790727908279092791027911279122791327914279152791627917279182791927920279212792227923279242792527926279272792827929279302793127932279332793427935279362793727938279392794027941279422794327944279452794627947279482794927950279512795227953279542795527956279572795827959279602796127962279632796427965279662796727968279692797027971279722797327974279752797627977279782797927980279812798227983279842798527986279872798827989279902799127992279932799427995279962799727998279992800028001280022800328004280052800628007280082800928010280112801228013280142801528016280172801828019280202802128022280232802428025280262802728028280292803028031280322803328034280352803628037280382803928040280412804228043280442804528046280472804828049280502805128052280532805428055280562805728058280592806028061280622806328064280652806628067280682806928070280712807228073280742807528076280772807828079280802808128082280832808428085280862808728088280892809028091280922809328094280952809628097280982809928100281012810228103281042810528106281072810828109281102811128112281132811428115281162811728118281192812028121281222812328124281252812628127281282812928130281312813228133281342813528136281372813828139281402814128142281432814428145281462814728148281492815028151281522815328154281552815628157281582815928160281612816228163281642816528166281672816828169281702817128172281732817428175281762817728178281792818028181281822818328184281852818628187281882818928190281912819228193281942819528196281972819828199282002820128202282032820428205282062820728208282092821028211282122821328214282152821628217282182821928220282212822228223282242822528226282272822828229282302823128232282332823428235282362823728238282392824028241282422824328244282452824628247282482824928250282512825228253282542825528256282572825828259282602826128262282632826428265282662826728268282692827028271282722827328274282752827628277282782827928280282812828228283282842828528286282872828828289282902829128292282932829428295282962829728298282992830028301283022830328304283052830628307283082830928310283112831228313283142831528316283172831828319283202832128322283232832428325283262832728328283292833028331283322833328334283352833628337283382833928340283412834228343283442834528346283472834828349283502835128352283532835428355283562835728358283592836028361283622836328364283652836628367283682836928370283712837228373283742837528376283772837828379283802838128382283832838428385283862838728388283892839028391283922839328394283952839628397283982839928400284012840228403284042840528406284072840828409284102841128412284132841428415284162841728418284192842028421284222842328424284252842628427284282842928430284312843228433284342843528436284372843828439284402844128442284432844428445284462844728448284492845028451284522845328454284552845628457284582845928460284612846228463284642846528466284672846828469284702847128472284732847428475284762847728478284792848028481284822848328484284852848628487284882848928490284912849228493284942849528496284972849828499285002850128502285032850428505285062850728508285092851028511285122851328514285152851628517285182851928520285212852228523285242852528526285272852828529285302853128532285332853428535285362853728538285392854028541285422854328544285452854628547285482854928550285512855228553285542855528556285572855828559285602856128562285632856428565285662856728568285692857028571285722857328574285752857628577285782857928580285812858228583285842858528586285872858828589285902859128592285932859428595285962859728598285992860028601286022860328604286052860628607286082860928610286112861228613286142861528616286172861828619286202862128622286232862428625286262862728628286292863028631286322863328634286352863628637286382863928640286412864228643286442864528646286472864828649286502865128652286532865428655286562865728658286592866028661286622866328664286652866628667286682866928670286712867228673286742867528676286772867828679286802868128682286832868428685286862868728688286892869028691286922869328694286952869628697286982869928700287012870228703287042870528706287072870828709287102871128712287132871428715287162871728718287192872028721287222872328724287252872628727287282872928730287312873228733287342873528736287372873828739287402874128742287432874428745287462874728748287492875028751287522875328754287552875628757287582875928760287612876228763287642876528766287672876828769287702877128772287732877428775287762877728778287792878028781287822878328784287852878628787287882878928790287912879228793287942879528796287972879828799288002880128802288032880428805288062880728808288092881028811288122881328814288152881628817288182881928820288212882228823288242882528826288272882828829288302883128832288332883428835288362883728838288392884028841288422884328844288452884628847288482884928850288512885228853288542885528856288572885828859288602886128862288632886428865288662886728868288692887028871288722887328874288752887628877288782887928880288812888228883288842888528886288872888828889288902889128892288932889428895288962889728898288992890028901289022890328904289052890628907289082890928910289112891228913289142891528916289172891828919289202892128922289232892428925289262892728928289292893028931289322893328934289352893628937289382893928940289412894228943289442894528946289472894828949289502895128952289532895428955289562895728958289592896028961289622896328964289652896628967289682896928970289712897228973289742897528976289772897828979289802898128982289832898428985289862898728988289892899028991289922899328994289952899628997289982899929000290012900229003290042900529006290072900829009290102901129012290132901429015290162901729018290192902029021290222902329024290252902629027290282902929030290312903229033290342903529036290372903829039290402904129042290432904429045290462904729048290492905029051290522905329054290552905629057290582905929060290612906229063290642906529066290672906829069290702907129072290732907429075290762907729078290792908029081290822908329084290852908629087290882908929090290912909229093290942909529096290972909829099291002910129102291032910429105291062910729108291092911029111291122911329114291152911629117291182911929120291212912229123291242912529126291272912829129291302913129132291332913429135291362913729138291392914029141291422914329144291452914629147291482914929150291512915229153291542915529156291572915829159291602916129162291632916429165291662916729168291692917029171291722917329174291752917629177291782917929180291812918229183291842918529186291872918829189291902919129192291932919429195291962919729198291992920029201292022920329204292052920629207292082920929210292112921229213292142921529216292172921829219292202922129222292232922429225292262922729228292292923029231292322923329234292352923629237292382923929240292412924229243292442924529246292472924829249292502925129252292532925429255292562925729258292592926029261292622926329264292652926629267292682926929270292712927229273292742927529276292772927829279292802928129282292832928429285292862928729288292892929029291292922929329294292952929629297292982929929300293012930229303293042930529306293072930829309293102931129312293132931429315293162931729318293192932029321293222932329324293252932629327293282932929330293312933229333293342933529336293372933829339293402934129342293432934429345293462934729348293492935029351293522935329354293552935629357293582935929360293612936229363293642936529366293672936829369293702937129372293732937429375293762937729378293792938029381293822938329384293852938629387293882938929390293912939229393293942939529396293972939829399294002940129402294032940429405294062940729408294092941029411294122941329414294152941629417294182941929420294212942229423294242942529426294272942829429294302943129432294332943429435294362943729438294392944029441294422944329444294452944629447294482944929450294512945229453294542945529456294572945829459294602946129462294632946429465294662946729468294692947029471294722947329474294752947629477294782947929480294812948229483294842948529486294872948829489294902949129492294932949429495294962949729498294992950029501295022950329504295052950629507295082950929510295112951229513295142951529516295172951829519295202952129522295232952429525295262952729528295292953029531295322953329534295352953629537295382953929540295412954229543295442954529546295472954829549295502955129552295532955429555295562955729558295592956029561295622956329564295652956629567295682956929570295712957229573295742957529576295772957829579295802958129582295832958429585295862958729588295892959029591295922959329594295952959629597295982959929600296012960229603296042960529606296072960829609296102961129612296132961429615296162961729618296192962029621296222962329624296252962629627296282962929630296312963229633296342963529636296372963829639296402964129642296432964429645296462964729648296492965029651296522965329654296552965629657296582965929660296612966229663296642966529666296672966829669296702967129672296732967429675296762967729678296792968029681296822968329684296852968629687296882968929690296912969229693296942969529696296972969829699297002970129702297032970429705297062970729708297092971029711297122971329714297152971629717297182971929720297212972229723297242972529726297272972829729297302973129732297332973429735297362973729738297392974029741297422974329744297452974629747297482974929750297512975229753297542975529756297572975829759297602976129762297632976429765297662976729768297692977029771297722977329774297752977629777297782977929780297812978229783297842978529786297872978829789297902979129792297932979429795297962979729798297992980029801298022980329804298052980629807298082980929810298112981229813298142981529816298172981829819298202982129822298232982429825298262982729828298292983029831298322983329834298352983629837298382983929840298412984229843298442984529846298472984829849298502985129852298532985429855298562985729858298592986029861298622986329864298652986629867298682986929870298712987229873298742987529876298772987829879298802988129882298832988429885298862988729888298892989029891298922989329894298952989629897298982989929900299012990229903299042990529906299072990829909299102991129912299132991429915299162991729918299192992029921299222992329924299252992629927299282992929930299312993229933299342993529936299372993829939299402994129942299432994429945299462994729948299492995029951299522995329954299552995629957299582995929960299612996229963299642996529966299672996829969299702997129972299732997429975299762997729978299792998029981299822998329984299852998629987299882998929990299912999229993299942999529996299972999829999300003000130002300033000430005300063000730008300093001030011300123001330014300153001630017300183001930020300213002230023300243002530026300273002830029300303003130032300333003430035300363003730038300393004030041300423004330044300453004630047300483004930050300513005230053300543005530056300573005830059300603006130062300633006430065300663006730068300693007030071300723007330074300753007630077300783007930080300813008230083300843008530086300873008830089300903009130092300933009430095300963009730098300993010030101301023010330104301053010630107301083010930110301113011230113301143011530116301173011830119301203012130122301233012430125301263012730128301293013030131301323013330134301353013630137301383013930140301413014230143301443014530146301473014830149301503015130152301533015430155301563015730158301593016030161301623016330164301653016630167301683016930170301713017230173301743017530176301773017830179301803018130182301833018430185301863018730188301893019030191301923019330194301953019630197301983019930200302013020230203302043020530206302073020830209302103021130212302133021430215302163021730218302193022030221302223022330224302253022630227302283022930230302313023230233302343023530236302373023830239302403024130242302433024430245302463024730248302493025030251302523025330254302553025630257302583025930260302613026230263302643026530266302673026830269302703027130272302733027430275302763027730278302793028030281302823028330284302853028630287302883028930290302913029230293302943029530296302973029830299303003030130302303033030430305303063030730308303093031030311303123031330314303153031630317303183031930320303213032230323303243032530326303273032830329303303033130332303333033430335303363033730338303393034030341303423034330344303453034630347303483034930350303513035230353303543035530356303573035830359303603036130362303633036430365303663036730368303693037030371303723037330374303753037630377303783037930380303813038230383303843038530386303873038830389303903039130392303933039430395303963039730398303993040030401304023040330404304053040630407304083040930410304113041230413304143041530416304173041830419304203042130422304233042430425304263042730428304293043030431304323043330434304353043630437304383043930440304413044230443304443044530446304473044830449304503045130452304533045430455304563045730458304593046030461304623046330464304653046630467304683046930470304713047230473304743047530476304773047830479304803048130482304833048430485304863048730488304893049030491304923049330494304953049630497304983049930500305013050230503305043050530506305073050830509305103051130512305133051430515305163051730518305193052030521305223052330524305253052630527305283052930530305313053230533305343053530536305373053830539305403054130542305433054430545305463054730548305493055030551305523055330554305553055630557305583055930560305613056230563305643056530566305673056830569305703057130572305733057430575305763057730578305793058030581305823058330584305853058630587305883058930590305913059230593305943059530596305973059830599306003060130602306033060430605306063060730608306093061030611306123061330614306153061630617306183061930620306213062230623306243062530626306273062830629306303063130632306333063430635306363063730638306393064030641306423064330644306453064630647306483064930650306513065230653306543065530656306573065830659306603066130662306633066430665306663066730668306693067030671306723067330674306753067630677306783067930680306813068230683306843068530686306873068830689306903069130692306933069430695306963069730698306993070030701307023070330704307053070630707307083070930710307113071230713307143071530716307173071830719307203072130722307233072430725307263072730728307293073030731307323073330734307353073630737307383073930740307413074230743307443074530746307473074830749307503075130752307533075430755307563075730758307593076030761307623076330764307653076630767307683076930770307713077230773307743077530776307773077830779307803078130782307833078430785307863078730788307893079030791307923079330794307953079630797307983079930800308013080230803308043080530806308073080830809308103081130812308133081430815308163081730818308193082030821308223082330824308253082630827308283082930830308313083230833308343083530836308373083830839308403084130842308433084430845308463084730848308493085030851308523085330854308553085630857308583085930860308613086230863308643086530866308673086830869308703087130872308733087430875308763087730878308793088030881308823088330884308853088630887308883088930890308913089230893308943089530896308973089830899309003090130902309033090430905309063090730908309093091030911309123091330914309153091630917309183091930920309213092230923309243092530926309273092830929309303093130932309333093430935309363093730938309393094030941309423094330944309453094630947309483094930950309513095230953309543095530956309573095830959309603096130962309633096430965309663096730968309693097030971309723097330974309753097630977309783097930980309813098230983309843098530986309873098830989309903099130992309933099430995309963099730998309993100031001310023100331004310053100631007310083100931010310113101231013310143101531016310173101831019310203102131022310233102431025310263102731028310293103031031310323103331034310353103631037310383103931040310413104231043310443104531046310473104831049310503105131052310533105431055310563105731058310593106031061310623106331064310653106631067310683106931070310713107231073310743107531076310773107831079310803108131082310833108431085310863108731088310893109031091310923109331094310953109631097310983109931100311013110231103311043110531106311073110831109311103111131112311133111431115311163111731118311193112031121311223112331124311253112631127311283112931130311313113231133311343113531136311373113831139311403114131142311433114431145311463114731148311493115031151311523115331154311553115631157311583115931160311613116231163311643116531166311673116831169311703117131172311733117431175311763117731178311793118031181311823118331184311853118631187311883118931190311913119231193311943119531196311973119831199312003120131202312033120431205312063120731208312093121031211312123121331214312153121631217312183121931220312213122231223312243122531226312273122831229312303123131232312333123431235312363123731238312393124031241312423124331244312453124631247312483124931250312513125231253312543125531256312573125831259312603126131262312633126431265312663126731268312693127031271312723127331274312753127631277312783127931280312813128231283312843128531286312873128831289312903129131292312933129431295312963129731298312993130031301313023130331304313053130631307313083130931310313113131231313313143131531316313173131831319313203132131322313233132431325313263132731328313293133031331313323133331334313353133631337313383133931340313413134231343313443134531346313473134831349313503135131352313533135431355313563135731358313593136031361313623136331364313653136631367313683136931370313713137231373313743137531376313773137831379313803138131382313833138431385313863138731388313893139031391313923139331394313953139631397313983139931400314013140231403314043140531406314073140831409314103141131412314133141431415314163141731418314193142031421314223142331424314253142631427314283142931430314313143231433314343143531436314373143831439314403144131442314433144431445314463144731448314493145031451314523145331454314553145631457314583145931460314613146231463314643146531466314673146831469314703147131472314733147431475314763147731478314793148031481314823148331484314853148631487314883148931490314913149231493314943149531496314973149831499315003150131502315033150431505315063150731508315093151031511315123151331514315153151631517315183151931520315213152231523315243152531526315273152831529315303153131532315333153431535315363153731538315393154031541315423154331544315453154631547315483154931550315513155231553315543155531556315573155831559315603156131562315633156431565315663156731568315693157031571315723157331574315753157631577315783157931580315813158231583315843158531586315873158831589315903159131592315933159431595315963159731598315993160031601316023160331604316053160631607316083160931610316113161231613316143161531616316173161831619316203162131622316233162431625316263162731628316293163031631316323163331634316353163631637316383163931640316413164231643316443164531646316473164831649316503165131652316533165431655316563165731658316593166031661316623166331664316653166631667
  1. import { _getProvider, getApp, _removeServiceInstance, _registerComponent, registerVersion, SDK_VERSION as SDK_VERSION$1 } from '@firebase/app';
  2. import { Component } from '@firebase/component';
  3. import { Logger, LogLevel } from '@firebase/logger';
  4. import { inspect, TextEncoder, TextDecoder } from 'util';
  5. import { FirebaseError, createMockUserToken, getModularInstance, deepEqual, getDefaultEmulatorHostnameAndPort, getUA, isIndexedDBAvailable, isSafari } from '@firebase/util';
  6. import { randomBytes as randomBytes$1 } from 'crypto';
  7. import { Integer, Md5 } from '@firebase/webchannel-wrapper';
  8. import * as grpc from '@grpc/grpc-js';
  9. import * as protoLoader from '@grpc/proto-loader';
  10. const name = "@firebase/firestore";
  11. const version$1 = "3.13.0";
  12. /**
  13. * @license
  14. * Copyright 2017 Google LLC
  15. *
  16. * Licensed under the Apache License, Version 2.0 (the "License");
  17. * you may not use this file except in compliance with the License.
  18. * You may obtain a copy of the License at
  19. *
  20. * http://www.apache.org/licenses/LICENSE-2.0
  21. *
  22. * Unless required by applicable law or agreed to in writing, software
  23. * distributed under the License is distributed on an "AS IS" BASIS,
  24. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25. * See the License for the specific language governing permissions and
  26. * limitations under the License.
  27. */
  28. /**
  29. * Simple wrapper around a nullable UID. Mostly exists to make code more
  30. * readable.
  31. */
  32. class User {
  33. constructor(uid) {
  34. this.uid = uid;
  35. }
  36. isAuthenticated() {
  37. return this.uid != null;
  38. }
  39. /**
  40. * Returns a key representing this user, suitable for inclusion in a
  41. * dictionary.
  42. */
  43. toKey() {
  44. if (this.isAuthenticated()) {
  45. return 'uid:' + this.uid;
  46. }
  47. else {
  48. return 'anonymous-user';
  49. }
  50. }
  51. isEqual(otherUser) {
  52. return otherUser.uid === this.uid;
  53. }
  54. }
  55. /** A user with a null UID. */
  56. User.UNAUTHENTICATED = new User(null);
  57. // TODO(mikelehen): Look into getting a proper uid-equivalent for
  58. // non-FirebaseAuth providers.
  59. User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
  60. User.FIRST_PARTY = new User('first-party-uid');
  61. User.MOCK_USER = new User('mock-user');
  62. const version = "9.23.0";
  63. /**
  64. * @license
  65. * Copyright 2017 Google LLC
  66. *
  67. * Licensed under the Apache License, Version 2.0 (the "License");
  68. * you may not use this file except in compliance with the License.
  69. * You may obtain a copy of the License at
  70. *
  71. * http://www.apache.org/licenses/LICENSE-2.0
  72. *
  73. * Unless required by applicable law or agreed to in writing, software
  74. * distributed under the License is distributed on an "AS IS" BASIS,
  75. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  76. * See the License for the specific language governing permissions and
  77. * limitations under the License.
  78. */
  79. let SDK_VERSION = version;
  80. function setSDKVersion(version) {
  81. SDK_VERSION = version;
  82. }
  83. /**
  84. * @license
  85. * Copyright 2020 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. /** Formats an object as a JSON string, suitable for logging. */
  100. function formatJSON(value) {
  101. // util.inspect() results in much more readable output than JSON.stringify()
  102. return inspect(value, { depth: 100 });
  103. }
  104. /**
  105. * @license
  106. * Copyright 2017 Google LLC
  107. *
  108. * Licensed under the Apache License, Version 2.0 (the "License");
  109. * you may not use this file except in compliance with the License.
  110. * You may obtain a copy of the License at
  111. *
  112. * http://www.apache.org/licenses/LICENSE-2.0
  113. *
  114. * Unless required by applicable law or agreed to in writing, software
  115. * distributed under the License is distributed on an "AS IS" BASIS,
  116. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  117. * See the License for the specific language governing permissions and
  118. * limitations under the License.
  119. */
  120. const logClient = new Logger('@firebase/firestore');
  121. // Helper methods are needed because variables can't be exported as read/write
  122. function getLogLevel() {
  123. return logClient.logLevel;
  124. }
  125. /**
  126. * Sets the verbosity of Cloud Firestore logs (debug, error, or silent).
  127. *
  128. * @param logLevel - The verbosity you set for activity and error logging. Can
  129. * be any of the following values:
  130. *
  131. * <ul>
  132. * <li>`debug` for the most verbose logging level, primarily for
  133. * debugging.</li>
  134. * <li>`error` to log errors only.</li>
  135. * <li><code>`silent` to turn off logging.</li>
  136. * </ul>
  137. */
  138. function setLogLevel(logLevel) {
  139. logClient.setLogLevel(logLevel);
  140. }
  141. function logDebug(msg, ...obj) {
  142. if (logClient.logLevel <= LogLevel.DEBUG) {
  143. const args = obj.map(argToString);
  144. logClient.debug(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
  145. }
  146. }
  147. function logError(msg, ...obj) {
  148. if (logClient.logLevel <= LogLevel.ERROR) {
  149. const args = obj.map(argToString);
  150. logClient.error(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
  151. }
  152. }
  153. /**
  154. * @internal
  155. */
  156. function logWarn(msg, ...obj) {
  157. if (logClient.logLevel <= LogLevel.WARN) {
  158. const args = obj.map(argToString);
  159. logClient.warn(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
  160. }
  161. }
  162. /**
  163. * Converts an additional log parameter to a string representation.
  164. */
  165. function argToString(obj) {
  166. if (typeof obj === 'string') {
  167. return obj;
  168. }
  169. else {
  170. try {
  171. return formatJSON(obj);
  172. }
  173. catch (e) {
  174. // Converting to JSON failed, just log the object directly
  175. return obj;
  176. }
  177. }
  178. }
  179. /**
  180. * @license
  181. * Copyright 2017 Google LLC
  182. *
  183. * Licensed under the Apache License, Version 2.0 (the "License");
  184. * you may not use this file except in compliance with the License.
  185. * You may obtain a copy of the License at
  186. *
  187. * http://www.apache.org/licenses/LICENSE-2.0
  188. *
  189. * Unless required by applicable law or agreed to in writing, software
  190. * distributed under the License is distributed on an "AS IS" BASIS,
  191. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  192. * See the License for the specific language governing permissions and
  193. * limitations under the License.
  194. */
  195. /**
  196. * Unconditionally fails, throwing an Error with the given message.
  197. * Messages are stripped in production builds.
  198. *
  199. * Returns `never` and can be used in expressions:
  200. * @example
  201. * let futureVar = fail('not implemented yet');
  202. */
  203. function fail(failure = 'Unexpected state') {
  204. // Log the failure in addition to throw an exception, just in case the
  205. // exception is swallowed.
  206. const message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ` + failure;
  207. logError(message);
  208. // NOTE: We don't use FirestoreError here because these are internal failures
  209. // that cannot be handled by the user. (Also it would create a circular
  210. // dependency between the error and assert modules which doesn't work.)
  211. throw new Error(message);
  212. }
  213. /**
  214. * Fails if the given assertion condition is false, throwing an Error with the
  215. * given message if it did.
  216. *
  217. * Messages are stripped in production builds.
  218. */
  219. function hardAssert(assertion, message) {
  220. if (!assertion) {
  221. fail();
  222. }
  223. }
  224. /**
  225. * Fails if the given assertion condition is false, throwing an Error with the
  226. * given message if it did.
  227. *
  228. * The code of callsites invoking this function are stripped out in production
  229. * builds. Any side-effects of code within the debugAssert() invocation will not
  230. * happen in this case.
  231. *
  232. * @internal
  233. */
  234. function debugAssert(assertion, message) {
  235. if (!assertion) {
  236. fail();
  237. }
  238. }
  239. /**
  240. * Casts `obj` to `T`. In non-production builds, verifies that `obj` is an
  241. * instance of `T` before casting.
  242. */
  243. function debugCast(obj,
  244. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  245. constructor) {
  246. return obj;
  247. }
  248. /**
  249. * @license
  250. * Copyright 2017 Google LLC
  251. *
  252. * Licensed under the Apache License, Version 2.0 (the "License");
  253. * you may not use this file except in compliance with the License.
  254. * You may obtain a copy of the License at
  255. *
  256. * http://www.apache.org/licenses/LICENSE-2.0
  257. *
  258. * Unless required by applicable law or agreed to in writing, software
  259. * distributed under the License is distributed on an "AS IS" BASIS,
  260. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  261. * See the License for the specific language governing permissions and
  262. * limitations under the License.
  263. */
  264. const Code = {
  265. // Causes are copied from:
  266. // https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h
  267. /** Not an error; returned on success. */
  268. OK: 'ok',
  269. /** The operation was cancelled (typically by the caller). */
  270. CANCELLED: 'cancelled',
  271. /** Unknown error or an error from a different error domain. */
  272. UNKNOWN: 'unknown',
  273. /**
  274. * Client specified an invalid argument. Note that this differs from
  275. * FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
  276. * problematic regardless of the state of the system (e.g., a malformed file
  277. * name).
  278. */
  279. INVALID_ARGUMENT: 'invalid-argument',
  280. /**
  281. * Deadline expired before operation could complete. For operations that
  282. * change the state of the system, this error may be returned even if the
  283. * operation has completed successfully. For example, a successful response
  284. * from a server could have been delayed long enough for the deadline to
  285. * expire.
  286. */
  287. DEADLINE_EXCEEDED: 'deadline-exceeded',
  288. /** Some requested entity (e.g., file or directory) was not found. */
  289. NOT_FOUND: 'not-found',
  290. /**
  291. * Some entity that we attempted to create (e.g., file or directory) already
  292. * exists.
  293. */
  294. ALREADY_EXISTS: 'already-exists',
  295. /**
  296. * The caller does not have permission to execute the specified operation.
  297. * PERMISSION_DENIED must not be used for rejections caused by exhausting
  298. * some resource (use RESOURCE_EXHAUSTED instead for those errors).
  299. * PERMISSION_DENIED must not be used if the caller can not be identified
  300. * (use UNAUTHENTICATED instead for those errors).
  301. */
  302. PERMISSION_DENIED: 'permission-denied',
  303. /**
  304. * The request does not have valid authentication credentials for the
  305. * operation.
  306. */
  307. UNAUTHENTICATED: 'unauthenticated',
  308. /**
  309. * Some resource has been exhausted, perhaps a per-user quota, or perhaps the
  310. * entire file system is out of space.
  311. */
  312. RESOURCE_EXHAUSTED: 'resource-exhausted',
  313. /**
  314. * Operation was rejected because the system is not in a state required for
  315. * the operation's execution. For example, directory to be deleted may be
  316. * non-empty, an rmdir operation is applied to a non-directory, etc.
  317. *
  318. * A litmus test that may help a service implementor in deciding
  319. * between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
  320. * (a) Use UNAVAILABLE if the client can retry just the failing call.
  321. * (b) Use ABORTED if the client should retry at a higher-level
  322. * (e.g., restarting a read-modify-write sequence).
  323. * (c) Use FAILED_PRECONDITION if the client should not retry until
  324. * the system state has been explicitly fixed. E.g., if an "rmdir"
  325. * fails because the directory is non-empty, FAILED_PRECONDITION
  326. * should be returned since the client should not retry unless
  327. * they have first fixed up the directory by deleting files from it.
  328. * (d) Use FAILED_PRECONDITION if the client performs conditional
  329. * REST Get/Update/Delete on a resource and the resource on the
  330. * server does not match the condition. E.g., conflicting
  331. * read-modify-write on the same resource.
  332. */
  333. FAILED_PRECONDITION: 'failed-precondition',
  334. /**
  335. * The operation was aborted, typically due to a concurrency issue like
  336. * sequencer check failures, transaction aborts, etc.
  337. *
  338. * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
  339. * and UNAVAILABLE.
  340. */
  341. ABORTED: 'aborted',
  342. /**
  343. * Operation was attempted past the valid range. E.g., seeking or reading
  344. * past end of file.
  345. *
  346. * Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed
  347. * if the system state changes. For example, a 32-bit file system will
  348. * generate INVALID_ARGUMENT if asked to read at an offset that is not in the
  349. * range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from
  350. * an offset past the current file size.
  351. *
  352. * There is a fair bit of overlap between FAILED_PRECONDITION and
  353. * OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
  354. * when it applies so that callers who are iterating through a space can
  355. * easily look for an OUT_OF_RANGE error to detect when they are done.
  356. */
  357. OUT_OF_RANGE: 'out-of-range',
  358. /** Operation is not implemented or not supported/enabled in this service. */
  359. UNIMPLEMENTED: 'unimplemented',
  360. /**
  361. * Internal errors. Means some invariants expected by underlying System has
  362. * been broken. If you see one of these errors, Something is very broken.
  363. */
  364. INTERNAL: 'internal',
  365. /**
  366. * The service is currently unavailable. This is a most likely a transient
  367. * condition and may be corrected by retrying with a backoff.
  368. *
  369. * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
  370. * and UNAVAILABLE.
  371. */
  372. UNAVAILABLE: 'unavailable',
  373. /** Unrecoverable data loss or corruption. */
  374. DATA_LOSS: 'data-loss'
  375. };
  376. /** An error returned by a Firestore operation. */
  377. class FirestoreError extends FirebaseError {
  378. /** @hideconstructor */
  379. constructor(
  380. /**
  381. * The backend error code associated with this error.
  382. */
  383. code,
  384. /**
  385. * A custom error description.
  386. */
  387. message) {
  388. super(code, message);
  389. this.code = code;
  390. this.message = message;
  391. // HACK: We write a toString property directly because Error is not a real
  392. // class and so inheritance does not work correctly. We could alternatively
  393. // do the same "back-door inheritance" trick that FirebaseError does.
  394. this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`;
  395. }
  396. }
  397. /**
  398. * @license
  399. * Copyright 2017 Google LLC
  400. *
  401. * Licensed under the Apache License, Version 2.0 (the "License");
  402. * you may not use this file except in compliance with the License.
  403. * You may obtain a copy of the License at
  404. *
  405. * http://www.apache.org/licenses/LICENSE-2.0
  406. *
  407. * Unless required by applicable law or agreed to in writing, software
  408. * distributed under the License is distributed on an "AS IS" BASIS,
  409. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  410. * See the License for the specific language governing permissions and
  411. * limitations under the License.
  412. */
  413. class Deferred {
  414. constructor() {
  415. this.promise = new Promise((resolve, reject) => {
  416. this.resolve = resolve;
  417. this.reject = reject;
  418. });
  419. }
  420. }
  421. /**
  422. * @license
  423. * Copyright 2017 Google LLC
  424. *
  425. * Licensed under the Apache License, Version 2.0 (the "License");
  426. * you may not use this file except in compliance with the License.
  427. * You may obtain a copy of the License at
  428. *
  429. * http://www.apache.org/licenses/LICENSE-2.0
  430. *
  431. * Unless required by applicable law or agreed to in writing, software
  432. * distributed under the License is distributed on an "AS IS" BASIS,
  433. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  434. * See the License for the specific language governing permissions and
  435. * limitations under the License.
  436. */
  437. class OAuthToken {
  438. constructor(value, user) {
  439. this.user = user;
  440. this.type = 'OAuth';
  441. this.headers = new Map();
  442. this.headers.set('Authorization', `Bearer ${value}`);
  443. }
  444. }
  445. /**
  446. * A CredentialsProvider that always yields an empty token.
  447. * @internal
  448. */
  449. class EmptyAuthCredentialsProvider {
  450. getToken() {
  451. return Promise.resolve(null);
  452. }
  453. invalidateToken() { }
  454. start(asyncQueue, changeListener) {
  455. // Fire with initial user.
  456. asyncQueue.enqueueRetryable(() => changeListener(User.UNAUTHENTICATED));
  457. }
  458. shutdown() { }
  459. }
  460. /**
  461. * A CredentialsProvider that always returns a constant token. Used for
  462. * emulator token mocking.
  463. */
  464. class EmulatorAuthCredentialsProvider {
  465. constructor(token) {
  466. this.token = token;
  467. /**
  468. * Stores the listener registered with setChangeListener()
  469. * This isn't actually necessary since the UID never changes, but we use this
  470. * to verify the listen contract is adhered to in tests.
  471. */
  472. this.changeListener = null;
  473. }
  474. getToken() {
  475. return Promise.resolve(this.token);
  476. }
  477. invalidateToken() { }
  478. start(asyncQueue, changeListener) {
  479. this.changeListener = changeListener;
  480. // Fire with initial user.
  481. asyncQueue.enqueueRetryable(() => changeListener(this.token.user));
  482. }
  483. shutdown() {
  484. this.changeListener = null;
  485. }
  486. }
  487. class FirebaseAuthCredentialsProvider {
  488. constructor(authProvider) {
  489. this.authProvider = authProvider;
  490. /** Tracks the current User. */
  491. this.currentUser = User.UNAUTHENTICATED;
  492. /**
  493. * Counter used to detect if the token changed while a getToken request was
  494. * outstanding.
  495. */
  496. this.tokenCounter = 0;
  497. this.forceRefresh = false;
  498. this.auth = null;
  499. }
  500. start(asyncQueue, changeListener) {
  501. let lastTokenId = this.tokenCounter;
  502. // A change listener that prevents double-firing for the same token change.
  503. const guardedChangeListener = user => {
  504. if (this.tokenCounter !== lastTokenId) {
  505. lastTokenId = this.tokenCounter;
  506. return changeListener(user);
  507. }
  508. else {
  509. return Promise.resolve();
  510. }
  511. };
  512. // A promise that can be waited on to block on the next token change.
  513. // This promise is re-created after each change.
  514. let nextToken = new Deferred();
  515. this.tokenListener = () => {
  516. this.tokenCounter++;
  517. this.currentUser = this.getUser();
  518. nextToken.resolve();
  519. nextToken = new Deferred();
  520. asyncQueue.enqueueRetryable(() => guardedChangeListener(this.currentUser));
  521. };
  522. const awaitNextToken = () => {
  523. const currentTokenAttempt = nextToken;
  524. asyncQueue.enqueueRetryable(async () => {
  525. await currentTokenAttempt.promise;
  526. await guardedChangeListener(this.currentUser);
  527. });
  528. };
  529. const registerAuth = (auth) => {
  530. logDebug('FirebaseAuthCredentialsProvider', 'Auth detected');
  531. this.auth = auth;
  532. this.auth.addAuthTokenListener(this.tokenListener);
  533. awaitNextToken();
  534. };
  535. this.authProvider.onInit(auth => registerAuth(auth));
  536. // Our users can initialize Auth right after Firestore, so we give it
  537. // a chance to register itself with the component framework before we
  538. // determine whether to start up in unauthenticated mode.
  539. setTimeout(() => {
  540. if (!this.auth) {
  541. const auth = this.authProvider.getImmediate({ optional: true });
  542. if (auth) {
  543. registerAuth(auth);
  544. }
  545. else {
  546. // If auth is still not available, proceed with `null` user
  547. logDebug('FirebaseAuthCredentialsProvider', 'Auth not yet detected');
  548. nextToken.resolve();
  549. nextToken = new Deferred();
  550. }
  551. }
  552. }, 0);
  553. awaitNextToken();
  554. }
  555. getToken() {
  556. // Take note of the current value of the tokenCounter so that this method
  557. // can fail (with an ABORTED error) if there is a token change while the
  558. // request is outstanding.
  559. const initialTokenCounter = this.tokenCounter;
  560. const forceRefresh = this.forceRefresh;
  561. this.forceRefresh = false;
  562. if (!this.auth) {
  563. return Promise.resolve(null);
  564. }
  565. return this.auth.getToken(forceRefresh).then(tokenData => {
  566. // Cancel the request since the token changed while the request was
  567. // outstanding so the response is potentially for a previous user (which
  568. // user, we can't be sure).
  569. if (this.tokenCounter !== initialTokenCounter) {
  570. logDebug('FirebaseAuthCredentialsProvider', 'getToken aborted due to token change.');
  571. return this.getToken();
  572. }
  573. else {
  574. if (tokenData) {
  575. hardAssert(typeof tokenData.accessToken === 'string');
  576. return new OAuthToken(tokenData.accessToken, this.currentUser);
  577. }
  578. else {
  579. return null;
  580. }
  581. }
  582. });
  583. }
  584. invalidateToken() {
  585. this.forceRefresh = true;
  586. }
  587. shutdown() {
  588. if (this.auth) {
  589. this.auth.removeAuthTokenListener(this.tokenListener);
  590. }
  591. }
  592. // Auth.getUid() can return null even with a user logged in. It is because
  593. // getUid() is synchronous, but the auth code populating Uid is asynchronous.
  594. // This method should only be called in the AuthTokenListener callback
  595. // to guarantee to get the actual user.
  596. getUser() {
  597. const currentUid = this.auth && this.auth.getUid();
  598. hardAssert(currentUid === null || typeof currentUid === 'string');
  599. return new User(currentUid);
  600. }
  601. }
  602. /*
  603. * FirstPartyToken provides a fresh token each time its value
  604. * is requested, because if the token is too old, requests will be rejected.
  605. * Technically this may no longer be necessary since the SDK should gracefully
  606. * recover from unauthenticated errors (see b/33147818 for context), but it's
  607. * safer to keep the implementation as-is.
  608. */
  609. class FirstPartyToken {
  610. constructor(sessionIndex, iamToken, authTokenFactory) {
  611. this.sessionIndex = sessionIndex;
  612. this.iamToken = iamToken;
  613. this.authTokenFactory = authTokenFactory;
  614. this.type = 'FirstParty';
  615. this.user = User.FIRST_PARTY;
  616. this._headers = new Map();
  617. }
  618. /**
  619. * Gets an authorization token, using a provided factory function, or return
  620. * null.
  621. */
  622. getAuthToken() {
  623. if (this.authTokenFactory) {
  624. return this.authTokenFactory();
  625. }
  626. else {
  627. return null;
  628. }
  629. }
  630. get headers() {
  631. this._headers.set('X-Goog-AuthUser', this.sessionIndex);
  632. // Use array notation to prevent minification
  633. const authHeaderTokenValue = this.getAuthToken();
  634. if (authHeaderTokenValue) {
  635. this._headers.set('Authorization', authHeaderTokenValue);
  636. }
  637. if (this.iamToken) {
  638. this._headers.set('X-Goog-Iam-Authorization-Token', this.iamToken);
  639. }
  640. return this._headers;
  641. }
  642. }
  643. /*
  644. * Provides user credentials required for the Firestore JavaScript SDK
  645. * to authenticate the user, using technique that is only available
  646. * to applications hosted by Google.
  647. */
  648. class FirstPartyAuthCredentialsProvider {
  649. constructor(sessionIndex, iamToken, authTokenFactory) {
  650. this.sessionIndex = sessionIndex;
  651. this.iamToken = iamToken;
  652. this.authTokenFactory = authTokenFactory;
  653. }
  654. getToken() {
  655. return Promise.resolve(new FirstPartyToken(this.sessionIndex, this.iamToken, this.authTokenFactory));
  656. }
  657. start(asyncQueue, changeListener) {
  658. // Fire with initial uid.
  659. asyncQueue.enqueueRetryable(() => changeListener(User.FIRST_PARTY));
  660. }
  661. shutdown() { }
  662. invalidateToken() { }
  663. }
  664. class AppCheckToken {
  665. constructor(value) {
  666. this.value = value;
  667. this.type = 'AppCheck';
  668. this.headers = new Map();
  669. if (value && value.length > 0) {
  670. this.headers.set('x-firebase-appcheck', this.value);
  671. }
  672. }
  673. }
  674. class FirebaseAppCheckTokenProvider {
  675. constructor(appCheckProvider) {
  676. this.appCheckProvider = appCheckProvider;
  677. this.forceRefresh = false;
  678. this.appCheck = null;
  679. this.latestAppCheckToken = null;
  680. }
  681. start(asyncQueue, changeListener) {
  682. const onTokenChanged = tokenResult => {
  683. if (tokenResult.error != null) {
  684. logDebug('FirebaseAppCheckTokenProvider', `Error getting App Check token; using placeholder token instead. Error: ${tokenResult.error.message}`);
  685. }
  686. const tokenUpdated = tokenResult.token !== this.latestAppCheckToken;
  687. this.latestAppCheckToken = tokenResult.token;
  688. logDebug('FirebaseAppCheckTokenProvider', `Received ${tokenUpdated ? 'new' : 'existing'} token.`);
  689. return tokenUpdated
  690. ? changeListener(tokenResult.token)
  691. : Promise.resolve();
  692. };
  693. this.tokenListener = (tokenResult) => {
  694. asyncQueue.enqueueRetryable(() => onTokenChanged(tokenResult));
  695. };
  696. const registerAppCheck = (appCheck) => {
  697. logDebug('FirebaseAppCheckTokenProvider', 'AppCheck detected');
  698. this.appCheck = appCheck;
  699. this.appCheck.addTokenListener(this.tokenListener);
  700. };
  701. this.appCheckProvider.onInit(appCheck => registerAppCheck(appCheck));
  702. // Our users can initialize AppCheck after Firestore, so we give it
  703. // a chance to register itself with the component framework.
  704. setTimeout(() => {
  705. if (!this.appCheck) {
  706. const appCheck = this.appCheckProvider.getImmediate({ optional: true });
  707. if (appCheck) {
  708. registerAppCheck(appCheck);
  709. }
  710. else {
  711. // If AppCheck is still not available, proceed without it.
  712. logDebug('FirebaseAppCheckTokenProvider', 'AppCheck not yet detected');
  713. }
  714. }
  715. }, 0);
  716. }
  717. getToken() {
  718. const forceRefresh = this.forceRefresh;
  719. this.forceRefresh = false;
  720. if (!this.appCheck) {
  721. return Promise.resolve(null);
  722. }
  723. return this.appCheck.getToken(forceRefresh).then(tokenResult => {
  724. if (tokenResult) {
  725. hardAssert(typeof tokenResult.token === 'string');
  726. this.latestAppCheckToken = tokenResult.token;
  727. return new AppCheckToken(tokenResult.token);
  728. }
  729. else {
  730. return null;
  731. }
  732. });
  733. }
  734. invalidateToken() {
  735. this.forceRefresh = true;
  736. }
  737. shutdown() {
  738. if (this.appCheck) {
  739. this.appCheck.removeTokenListener(this.tokenListener);
  740. }
  741. }
  742. }
  743. /**
  744. * An AppCheck token provider that always yields an empty token.
  745. * @internal
  746. */
  747. class EmptyAppCheckTokenProvider {
  748. getToken() {
  749. return Promise.resolve(new AppCheckToken(''));
  750. }
  751. invalidateToken() { }
  752. start(asyncQueue, changeListener) { }
  753. shutdown() { }
  754. }
  755. /**
  756. * Builds a CredentialsProvider depending on the type of
  757. * the credentials passed in.
  758. */
  759. function makeAuthCredentialsProvider(credentials) {
  760. if (!credentials) {
  761. return new EmptyAuthCredentialsProvider();
  762. }
  763. switch (credentials['type']) {
  764. case 'firstParty':
  765. return new FirstPartyAuthCredentialsProvider(credentials['sessionIndex'] || '0', credentials['iamToken'] || null, credentials['authTokenFactory'] || null);
  766. case 'provider':
  767. return credentials['client'];
  768. default:
  769. throw new FirestoreError(Code.INVALID_ARGUMENT, 'makeAuthCredentialsProvider failed due to invalid credential type');
  770. }
  771. }
  772. /**
  773. * @license
  774. * Copyright 2020 Google LLC
  775. *
  776. * Licensed under the Apache License, Version 2.0 (the "License");
  777. * you may not use this file except in compliance with the License.
  778. * You may obtain a copy of the License at
  779. *
  780. * http://www.apache.org/licenses/LICENSE-2.0
  781. *
  782. * Unless required by applicable law or agreed to in writing, software
  783. * distributed under the License is distributed on an "AS IS" BASIS,
  784. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  785. * See the License for the specific language governing permissions and
  786. * limitations under the License.
  787. */
  788. /**
  789. * Generates `nBytes` of random bytes.
  790. *
  791. * If `nBytes < 0` , an error will be thrown.
  792. */
  793. function randomBytes(nBytes) {
  794. return randomBytes$1(nBytes);
  795. }
  796. /**
  797. * @license
  798. * Copyright 2017 Google LLC
  799. *
  800. * Licensed under the Apache License, Version 2.0 (the "License");
  801. * you may not use this file except in compliance with the License.
  802. * You may obtain a copy of the License at
  803. *
  804. * http://www.apache.org/licenses/LICENSE-2.0
  805. *
  806. * Unless required by applicable law or agreed to in writing, software
  807. * distributed under the License is distributed on an "AS IS" BASIS,
  808. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  809. * See the License for the specific language governing permissions and
  810. * limitations under the License.
  811. */
  812. class AutoId {
  813. static newId() {
  814. // Alphanumeric characters
  815. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  816. // The largest byte value that is a multiple of `char.length`.
  817. const maxMultiple = Math.floor(256 / chars.length) * chars.length;
  818. let autoId = '';
  819. const targetLength = 20;
  820. while (autoId.length < targetLength) {
  821. const bytes = randomBytes(40);
  822. for (let i = 0; i < bytes.length; ++i) {
  823. // Only accept values that are [0, maxMultiple), this ensures they can
  824. // be evenly mapped to indices of `chars` via a modulo operation.
  825. if (autoId.length < targetLength && bytes[i] < maxMultiple) {
  826. autoId += chars.charAt(bytes[i] % chars.length);
  827. }
  828. }
  829. }
  830. return autoId;
  831. }
  832. }
  833. function primitiveComparator(left, right) {
  834. if (left < right) {
  835. return -1;
  836. }
  837. if (left > right) {
  838. return 1;
  839. }
  840. return 0;
  841. }
  842. /** Helper to compare arrays using isEqual(). */
  843. function arrayEquals(left, right, comparator) {
  844. if (left.length !== right.length) {
  845. return false;
  846. }
  847. return left.every((value, index) => comparator(value, right[index]));
  848. }
  849. /**
  850. * Returns the immediate lexicographically-following string. This is useful to
  851. * construct an inclusive range for indexeddb iterators.
  852. */
  853. function immediateSuccessor(s) {
  854. // Return the input string, with an additional NUL byte appended.
  855. return s + '\0';
  856. }
  857. /**
  858. * @license
  859. * Copyright 2017 Google LLC
  860. *
  861. * Licensed under the Apache License, Version 2.0 (the "License");
  862. * you may not use this file except in compliance with the License.
  863. * You may obtain a copy of the License at
  864. *
  865. * http://www.apache.org/licenses/LICENSE-2.0
  866. *
  867. * Unless required by applicable law or agreed to in writing, software
  868. * distributed under the License is distributed on an "AS IS" BASIS,
  869. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  870. * See the License for the specific language governing permissions and
  871. * limitations under the License.
  872. */
  873. // The earliest date supported by Firestore timestamps (0001-01-01T00:00:00Z).
  874. const MIN_SECONDS = -62135596800;
  875. // Number of nanoseconds in a millisecond.
  876. const MS_TO_NANOS = 1e6;
  877. /**
  878. * A `Timestamp` represents a point in time independent of any time zone or
  879. * calendar, represented as seconds and fractions of seconds at nanosecond
  880. * resolution in UTC Epoch time.
  881. *
  882. * It is encoded using the Proleptic Gregorian Calendar which extends the
  883. * Gregorian calendar backwards to year one. It is encoded assuming all minutes
  884. * are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
  885. * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
  886. * 9999-12-31T23:59:59.999999999Z.
  887. *
  888. * For examples and further specifications, refer to the
  889. * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}.
  890. */
  891. class Timestamp {
  892. /**
  893. * Creates a new timestamp.
  894. *
  895. * @param seconds - The number of seconds of UTC time since Unix epoch
  896. * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
  897. * 9999-12-31T23:59:59Z inclusive.
  898. * @param nanoseconds - The non-negative fractions of a second at nanosecond
  899. * resolution. Negative second values with fractions must still have
  900. * non-negative nanoseconds values that count forward in time. Must be
  901. * from 0 to 999,999,999 inclusive.
  902. */
  903. constructor(
  904. /**
  905. * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
  906. */
  907. seconds,
  908. /**
  909. * The fractions of a second at nanosecond resolution.*
  910. */
  911. nanoseconds) {
  912. this.seconds = seconds;
  913. this.nanoseconds = nanoseconds;
  914. if (nanoseconds < 0) {
  915. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
  916. }
  917. if (nanoseconds >= 1e9) {
  918. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
  919. }
  920. if (seconds < MIN_SECONDS) {
  921. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
  922. }
  923. // This will break in the year 10,000.
  924. if (seconds >= 253402300800) {
  925. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
  926. }
  927. }
  928. /**
  929. * Creates a new timestamp with the current date, with millisecond precision.
  930. *
  931. * @returns a new timestamp representing the current date.
  932. */
  933. static now() {
  934. return Timestamp.fromMillis(Date.now());
  935. }
  936. /**
  937. * Creates a new timestamp from the given date.
  938. *
  939. * @param date - The date to initialize the `Timestamp` from.
  940. * @returns A new `Timestamp` representing the same point in time as the given
  941. * date.
  942. */
  943. static fromDate(date) {
  944. return Timestamp.fromMillis(date.getTime());
  945. }
  946. /**
  947. * Creates a new timestamp from the given number of milliseconds.
  948. *
  949. * @param milliseconds - Number of milliseconds since Unix epoch
  950. * 1970-01-01T00:00:00Z.
  951. * @returns A new `Timestamp` representing the same point in time as the given
  952. * number of milliseconds.
  953. */
  954. static fromMillis(milliseconds) {
  955. const seconds = Math.floor(milliseconds / 1000);
  956. const nanos = Math.floor((milliseconds - seconds * 1000) * MS_TO_NANOS);
  957. return new Timestamp(seconds, nanos);
  958. }
  959. /**
  960. * Converts a `Timestamp` to a JavaScript `Date` object. This conversion
  961. * causes a loss of precision since `Date` objects only support millisecond
  962. * precision.
  963. *
  964. * @returns JavaScript `Date` object representing the same point in time as
  965. * this `Timestamp`, with millisecond precision.
  966. */
  967. toDate() {
  968. return new Date(this.toMillis());
  969. }
  970. /**
  971. * Converts a `Timestamp` to a numeric timestamp (in milliseconds since
  972. * epoch). This operation causes a loss of precision.
  973. *
  974. * @returns The point in time corresponding to this timestamp, represented as
  975. * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
  976. */
  977. toMillis() {
  978. return this.seconds * 1000 + this.nanoseconds / MS_TO_NANOS;
  979. }
  980. _compareTo(other) {
  981. if (this.seconds === other.seconds) {
  982. return primitiveComparator(this.nanoseconds, other.nanoseconds);
  983. }
  984. return primitiveComparator(this.seconds, other.seconds);
  985. }
  986. /**
  987. * Returns true if this `Timestamp` is equal to the provided one.
  988. *
  989. * @param other - The `Timestamp` to compare against.
  990. * @returns true if this `Timestamp` is equal to the provided one.
  991. */
  992. isEqual(other) {
  993. return (other.seconds === this.seconds && other.nanoseconds === this.nanoseconds);
  994. }
  995. /** Returns a textual representation of this `Timestamp`. */
  996. toString() {
  997. return ('Timestamp(seconds=' +
  998. this.seconds +
  999. ', nanoseconds=' +
  1000. this.nanoseconds +
  1001. ')');
  1002. }
  1003. /** Returns a JSON-serializable representation of this `Timestamp`. */
  1004. toJSON() {
  1005. return { seconds: this.seconds, nanoseconds: this.nanoseconds };
  1006. }
  1007. /**
  1008. * Converts this object to a primitive string, which allows `Timestamp` objects
  1009. * to be compared using the `>`, `<=`, `>=` and `>` operators.
  1010. */
  1011. valueOf() {
  1012. // This method returns a string of the form <seconds>.<nanoseconds> where
  1013. // <seconds> is translated to have a non-negative value and both <seconds>
  1014. // and <nanoseconds> are left-padded with zeroes to be a consistent length.
  1015. // Strings with this format then have a lexiographical ordering that matches
  1016. // the expected ordering. The <seconds> translation is done to avoid having
  1017. // a leading negative sign (i.e. a leading '-' character) in its string
  1018. // representation, which would affect its lexiographical ordering.
  1019. const adjustedSeconds = this.seconds - MIN_SECONDS;
  1020. // Note: Up to 12 decimal digits are required to represent all valid
  1021. // 'seconds' values.
  1022. const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
  1023. const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
  1024. return formattedSeconds + '.' + formattedNanoseconds;
  1025. }
  1026. }
  1027. /**
  1028. * @license
  1029. * Copyright 2017 Google LLC
  1030. *
  1031. * Licensed under the Apache License, Version 2.0 (the "License");
  1032. * you may not use this file except in compliance with the License.
  1033. * You may obtain a copy of the License at
  1034. *
  1035. * http://www.apache.org/licenses/LICENSE-2.0
  1036. *
  1037. * Unless required by applicable law or agreed to in writing, software
  1038. * distributed under the License is distributed on an "AS IS" BASIS,
  1039. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1040. * See the License for the specific language governing permissions and
  1041. * limitations under the License.
  1042. */
  1043. /**
  1044. * A version of a document in Firestore. This corresponds to the version
  1045. * timestamp, such as update_time or read_time.
  1046. */
  1047. class SnapshotVersion {
  1048. constructor(timestamp) {
  1049. this.timestamp = timestamp;
  1050. }
  1051. static fromTimestamp(value) {
  1052. return new SnapshotVersion(value);
  1053. }
  1054. static min() {
  1055. return new SnapshotVersion(new Timestamp(0, 0));
  1056. }
  1057. static max() {
  1058. return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
  1059. }
  1060. compareTo(other) {
  1061. return this.timestamp._compareTo(other.timestamp);
  1062. }
  1063. isEqual(other) {
  1064. return this.timestamp.isEqual(other.timestamp);
  1065. }
  1066. /** Returns a number representation of the version for use in spec tests. */
  1067. toMicroseconds() {
  1068. // Convert to microseconds.
  1069. return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;
  1070. }
  1071. toString() {
  1072. return 'SnapshotVersion(' + this.timestamp.toString() + ')';
  1073. }
  1074. toTimestamp() {
  1075. return this.timestamp;
  1076. }
  1077. }
  1078. /**
  1079. * @license
  1080. * Copyright 2017 Google LLC
  1081. *
  1082. * Licensed under the Apache License, Version 2.0 (the "License");
  1083. * you may not use this file except in compliance with the License.
  1084. * You may obtain a copy of the License at
  1085. *
  1086. * http://www.apache.org/licenses/LICENSE-2.0
  1087. *
  1088. * Unless required by applicable law or agreed to in writing, software
  1089. * distributed under the License is distributed on an "AS IS" BASIS,
  1090. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1091. * See the License for the specific language governing permissions and
  1092. * limitations under the License.
  1093. */
  1094. const DOCUMENT_KEY_NAME = '__name__';
  1095. /**
  1096. * Path represents an ordered sequence of string segments.
  1097. */
  1098. class BasePath {
  1099. constructor(segments, offset, length) {
  1100. if (offset === undefined) {
  1101. offset = 0;
  1102. }
  1103. else if (offset > segments.length) {
  1104. fail();
  1105. }
  1106. if (length === undefined) {
  1107. length = segments.length - offset;
  1108. }
  1109. else if (length > segments.length - offset) {
  1110. fail();
  1111. }
  1112. this.segments = segments;
  1113. this.offset = offset;
  1114. this.len = length;
  1115. }
  1116. get length() {
  1117. return this.len;
  1118. }
  1119. isEqual(other) {
  1120. return BasePath.comparator(this, other) === 0;
  1121. }
  1122. child(nameOrPath) {
  1123. const segments = this.segments.slice(this.offset, this.limit());
  1124. if (nameOrPath instanceof BasePath) {
  1125. nameOrPath.forEach(segment => {
  1126. segments.push(segment);
  1127. });
  1128. }
  1129. else {
  1130. segments.push(nameOrPath);
  1131. }
  1132. return this.construct(segments);
  1133. }
  1134. /** The index of one past the last segment of the path. */
  1135. limit() {
  1136. return this.offset + this.length;
  1137. }
  1138. popFirst(size) {
  1139. size = size === undefined ? 1 : size;
  1140. return this.construct(this.segments, this.offset + size, this.length - size);
  1141. }
  1142. popLast() {
  1143. return this.construct(this.segments, this.offset, this.length - 1);
  1144. }
  1145. firstSegment() {
  1146. return this.segments[this.offset];
  1147. }
  1148. lastSegment() {
  1149. return this.get(this.length - 1);
  1150. }
  1151. get(index) {
  1152. return this.segments[this.offset + index];
  1153. }
  1154. isEmpty() {
  1155. return this.length === 0;
  1156. }
  1157. isPrefixOf(other) {
  1158. if (other.length < this.length) {
  1159. return false;
  1160. }
  1161. for (let i = 0; i < this.length; i++) {
  1162. if (this.get(i) !== other.get(i)) {
  1163. return false;
  1164. }
  1165. }
  1166. return true;
  1167. }
  1168. isImmediateParentOf(potentialChild) {
  1169. if (this.length + 1 !== potentialChild.length) {
  1170. return false;
  1171. }
  1172. for (let i = 0; i < this.length; i++) {
  1173. if (this.get(i) !== potentialChild.get(i)) {
  1174. return false;
  1175. }
  1176. }
  1177. return true;
  1178. }
  1179. forEach(fn) {
  1180. for (let i = this.offset, end = this.limit(); i < end; i++) {
  1181. fn(this.segments[i]);
  1182. }
  1183. }
  1184. toArray() {
  1185. return this.segments.slice(this.offset, this.limit());
  1186. }
  1187. static comparator(p1, p2) {
  1188. const len = Math.min(p1.length, p2.length);
  1189. for (let i = 0; i < len; i++) {
  1190. const left = p1.get(i);
  1191. const right = p2.get(i);
  1192. if (left < right) {
  1193. return -1;
  1194. }
  1195. if (left > right) {
  1196. return 1;
  1197. }
  1198. }
  1199. if (p1.length < p2.length) {
  1200. return -1;
  1201. }
  1202. if (p1.length > p2.length) {
  1203. return 1;
  1204. }
  1205. return 0;
  1206. }
  1207. }
  1208. /**
  1209. * A slash-separated path for navigating resources (documents and collections)
  1210. * within Firestore.
  1211. *
  1212. * @internal
  1213. */
  1214. class ResourcePath extends BasePath {
  1215. construct(segments, offset, length) {
  1216. return new ResourcePath(segments, offset, length);
  1217. }
  1218. canonicalString() {
  1219. // NOTE: The client is ignorant of any path segments containing escape
  1220. // sequences (e.g. __id123__) and just passes them through raw (they exist
  1221. // for legacy reasons and should not be used frequently).
  1222. return this.toArray().join('/');
  1223. }
  1224. toString() {
  1225. return this.canonicalString();
  1226. }
  1227. /**
  1228. * Creates a resource path from the given slash-delimited string. If multiple
  1229. * arguments are provided, all components are combined. Leading and trailing
  1230. * slashes from all components are ignored.
  1231. */
  1232. static fromString(...pathComponents) {
  1233. // NOTE: The client is ignorant of any path segments containing escape
  1234. // sequences (e.g. __id123__) and just passes them through raw (they exist
  1235. // for legacy reasons and should not be used frequently).
  1236. const segments = [];
  1237. for (const path of pathComponents) {
  1238. if (path.indexOf('//') >= 0) {
  1239. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid segment (${path}). Paths must not contain // in them.`);
  1240. }
  1241. // Strip leading and traling slashed.
  1242. segments.push(...path.split('/').filter(segment => segment.length > 0));
  1243. }
  1244. return new ResourcePath(segments);
  1245. }
  1246. static emptyPath() {
  1247. return new ResourcePath([]);
  1248. }
  1249. }
  1250. const identifierRegExp = /^[_a-zA-Z][_a-zA-Z0-9]*$/;
  1251. /**
  1252. * A dot-separated path for navigating sub-objects within a document.
  1253. * @internal
  1254. */
  1255. class FieldPath$1 extends BasePath {
  1256. construct(segments, offset, length) {
  1257. return new FieldPath$1(segments, offset, length);
  1258. }
  1259. /**
  1260. * Returns true if the string could be used as a segment in a field path
  1261. * without escaping.
  1262. */
  1263. static isValidIdentifier(segment) {
  1264. return identifierRegExp.test(segment);
  1265. }
  1266. canonicalString() {
  1267. return this.toArray()
  1268. .map(str => {
  1269. str = str.replace(/\\/g, '\\\\').replace(/`/g, '\\`');
  1270. if (!FieldPath$1.isValidIdentifier(str)) {
  1271. str = '`' + str + '`';
  1272. }
  1273. return str;
  1274. })
  1275. .join('.');
  1276. }
  1277. toString() {
  1278. return this.canonicalString();
  1279. }
  1280. /**
  1281. * Returns true if this field references the key of a document.
  1282. */
  1283. isKeyField() {
  1284. return this.length === 1 && this.get(0) === DOCUMENT_KEY_NAME;
  1285. }
  1286. /**
  1287. * The field designating the key of a document.
  1288. */
  1289. static keyField() {
  1290. return new FieldPath$1([DOCUMENT_KEY_NAME]);
  1291. }
  1292. /**
  1293. * Parses a field string from the given server-formatted string.
  1294. *
  1295. * - Splitting the empty string is not allowed (for now at least).
  1296. * - Empty segments within the string (e.g. if there are two consecutive
  1297. * separators) are not allowed.
  1298. *
  1299. * TODO(b/37244157): we should make this more strict. Right now, it allows
  1300. * non-identifier path components, even if they aren't escaped.
  1301. */
  1302. static fromServerFormat(path) {
  1303. const segments = [];
  1304. let current = '';
  1305. let i = 0;
  1306. const addCurrentSegment = () => {
  1307. if (current.length === 0) {
  1308. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid field path (${path}). Paths must not be empty, begin ` +
  1309. `with '.', end with '.', or contain '..'`);
  1310. }
  1311. segments.push(current);
  1312. current = '';
  1313. };
  1314. let inBackticks = false;
  1315. while (i < path.length) {
  1316. const c = path[i];
  1317. if (c === '\\') {
  1318. if (i + 1 === path.length) {
  1319. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has trailing escape character: ' + path);
  1320. }
  1321. const next = path[i + 1];
  1322. if (!(next === '\\' || next === '.' || next === '`')) {
  1323. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has invalid escape sequence: ' + path);
  1324. }
  1325. current += next;
  1326. i += 2;
  1327. }
  1328. else if (c === '`') {
  1329. inBackticks = !inBackticks;
  1330. i++;
  1331. }
  1332. else if (c === '.' && !inBackticks) {
  1333. addCurrentSegment();
  1334. i++;
  1335. }
  1336. else {
  1337. current += c;
  1338. i++;
  1339. }
  1340. }
  1341. addCurrentSegment();
  1342. if (inBackticks) {
  1343. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Unterminated ` in path: ' + path);
  1344. }
  1345. return new FieldPath$1(segments);
  1346. }
  1347. static emptyPath() {
  1348. return new FieldPath$1([]);
  1349. }
  1350. }
  1351. /**
  1352. * @license
  1353. * Copyright 2017 Google LLC
  1354. *
  1355. * Licensed under the Apache License, Version 2.0 (the "License");
  1356. * you may not use this file except in compliance with the License.
  1357. * You may obtain a copy of the License at
  1358. *
  1359. * http://www.apache.org/licenses/LICENSE-2.0
  1360. *
  1361. * Unless required by applicable law or agreed to in writing, software
  1362. * distributed under the License is distributed on an "AS IS" BASIS,
  1363. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1364. * See the License for the specific language governing permissions and
  1365. * limitations under the License.
  1366. */
  1367. /**
  1368. * @internal
  1369. */
  1370. class DocumentKey {
  1371. constructor(path) {
  1372. this.path = path;
  1373. }
  1374. static fromPath(path) {
  1375. return new DocumentKey(ResourcePath.fromString(path));
  1376. }
  1377. static fromName(name) {
  1378. return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
  1379. }
  1380. static empty() {
  1381. return new DocumentKey(ResourcePath.emptyPath());
  1382. }
  1383. get collectionGroup() {
  1384. return this.path.popLast().lastSegment();
  1385. }
  1386. /** Returns true if the document is in the specified collectionId. */
  1387. hasCollectionId(collectionId) {
  1388. return (this.path.length >= 2 &&
  1389. this.path.get(this.path.length - 2) === collectionId);
  1390. }
  1391. /** Returns the collection group (i.e. the name of the parent collection) for this key. */
  1392. getCollectionGroup() {
  1393. return this.path.get(this.path.length - 2);
  1394. }
  1395. /** Returns the fully qualified path to the parent collection. */
  1396. getCollectionPath() {
  1397. return this.path.popLast();
  1398. }
  1399. isEqual(other) {
  1400. return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
  1401. }
  1402. toString() {
  1403. return this.path.toString();
  1404. }
  1405. static comparator(k1, k2) {
  1406. return ResourcePath.comparator(k1.path, k2.path);
  1407. }
  1408. static isDocumentKey(path) {
  1409. return path.length % 2 === 0;
  1410. }
  1411. /**
  1412. * Creates and returns a new document key with the given segments.
  1413. *
  1414. * @param segments - The segments of the path to the document
  1415. * @returns A new instance of DocumentKey
  1416. */
  1417. static fromSegments(segments) {
  1418. return new DocumentKey(new ResourcePath(segments.slice()));
  1419. }
  1420. }
  1421. /**
  1422. * @license
  1423. * Copyright 2021 Google LLC
  1424. *
  1425. * Licensed under the Apache License, Version 2.0 (the "License");
  1426. * you may not use this file except in compliance with the License.
  1427. * You may obtain a copy of the License at
  1428. *
  1429. * http://www.apache.org/licenses/LICENSE-2.0
  1430. *
  1431. * Unless required by applicable law or agreed to in writing, software
  1432. * distributed under the License is distributed on an "AS IS" BASIS,
  1433. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1434. * See the License for the specific language governing permissions and
  1435. * limitations under the License.
  1436. */
  1437. /**
  1438. * The initial mutation batch id for each index. Gets updated during index
  1439. * backfill.
  1440. */
  1441. const INITIAL_LARGEST_BATCH_ID = -1;
  1442. /**
  1443. * The initial sequence number for each index. Gets updated during index
  1444. * backfill.
  1445. */
  1446. const INITIAL_SEQUENCE_NUMBER = 0;
  1447. /**
  1448. * An index definition for field indexes in Firestore.
  1449. *
  1450. * Every index is associated with a collection. The definition contains a list
  1451. * of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
  1452. * `CONTAINS` for ArrayContains/ArrayContainsAny queries).
  1453. *
  1454. * Unlike the backend, the SDK does not differentiate between collection or
  1455. * collection group-scoped indices. Every index can be used for both single
  1456. * collection and collection group queries.
  1457. */
  1458. class FieldIndex {
  1459. constructor(
  1460. /**
  1461. * The index ID. Returns -1 if the index ID is not available (e.g. the index
  1462. * has not yet been persisted).
  1463. */
  1464. indexId,
  1465. /** The collection ID this index applies to. */
  1466. collectionGroup,
  1467. /** The field segments for this index. */
  1468. fields,
  1469. /** Shows how up-to-date the index is for the current user. */
  1470. indexState) {
  1471. this.indexId = indexId;
  1472. this.collectionGroup = collectionGroup;
  1473. this.fields = fields;
  1474. this.indexState = indexState;
  1475. }
  1476. }
  1477. /** An ID for an index that has not yet been added to persistence. */
  1478. FieldIndex.UNKNOWN_ID = -1;
  1479. /** Returns the ArrayContains/ArrayContainsAny segment for this index. */
  1480. function fieldIndexGetArraySegment(fieldIndex) {
  1481. return fieldIndex.fields.find(s => s.kind === 2 /* IndexKind.CONTAINS */);
  1482. }
  1483. /** Returns all directional (ascending/descending) segments for this index. */
  1484. function fieldIndexGetDirectionalSegments(fieldIndex) {
  1485. return fieldIndex.fields.filter(s => s.kind !== 2 /* IndexKind.CONTAINS */);
  1486. }
  1487. /**
  1488. * Returns the order of the document key component for the given index.
  1489. *
  1490. * PORTING NOTE: This is only used in the Web IndexedDb implementation.
  1491. */
  1492. function fieldIndexGetKeyOrder(fieldIndex) {
  1493. const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
  1494. return directionalSegments.length === 0
  1495. ? 0 /* IndexKind.ASCENDING */
  1496. : directionalSegments[directionalSegments.length - 1].kind;
  1497. }
  1498. /**
  1499. * Compares indexes by collection group and segments. Ignores update time and
  1500. * index ID.
  1501. */
  1502. function fieldIndexSemanticComparator(left, right) {
  1503. let cmp = primitiveComparator(left.collectionGroup, right.collectionGroup);
  1504. if (cmp !== 0) {
  1505. return cmp;
  1506. }
  1507. for (let i = 0; i < Math.min(left.fields.length, right.fields.length); ++i) {
  1508. cmp = indexSegmentComparator(left.fields[i], right.fields[i]);
  1509. if (cmp !== 0) {
  1510. return cmp;
  1511. }
  1512. }
  1513. return primitiveComparator(left.fields.length, right.fields.length);
  1514. }
  1515. /** Returns a debug representation of the field index */
  1516. function fieldIndexToString(fieldIndex) {
  1517. return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
  1518. }
  1519. /** An index component consisting of field path and index type. */
  1520. class IndexSegment {
  1521. constructor(
  1522. /** The field path of the component. */
  1523. fieldPath,
  1524. /** The fields sorting order. */
  1525. kind) {
  1526. this.fieldPath = fieldPath;
  1527. this.kind = kind;
  1528. }
  1529. }
  1530. function indexSegmentComparator(left, right) {
  1531. const cmp = FieldPath$1.comparator(left.fieldPath, right.fieldPath);
  1532. if (cmp !== 0) {
  1533. return cmp;
  1534. }
  1535. return primitiveComparator(left.kind, right.kind);
  1536. }
  1537. /**
  1538. * Stores the "high water mark" that indicates how updated the Index is for the
  1539. * current user.
  1540. */
  1541. class IndexState {
  1542. constructor(
  1543. /**
  1544. * Indicates when the index was last updated (relative to other indexes).
  1545. */
  1546. sequenceNumber,
  1547. /** The the latest indexed read time, document and batch id. */
  1548. offset) {
  1549. this.sequenceNumber = sequenceNumber;
  1550. this.offset = offset;
  1551. }
  1552. /** The state of an index that has not yet been backfilled. */
  1553. static empty() {
  1554. return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
  1555. }
  1556. }
  1557. /**
  1558. * Creates an offset that matches all documents with a read time higher than
  1559. * `readTime`.
  1560. */
  1561. function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
  1562. // We want to create an offset that matches all documents with a read time
  1563. // greater than the provided read time. To do so, we technically need to
  1564. // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
  1565. // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
  1566. // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
  1567. // all valid document IDs.
  1568. const successorSeconds = readTime.toTimestamp().seconds;
  1569. const successorNanos = readTime.toTimestamp().nanoseconds + 1;
  1570. const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
  1571. ? new Timestamp(successorSeconds + 1, 0)
  1572. : new Timestamp(successorSeconds, successorNanos));
  1573. return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
  1574. }
  1575. /** Creates a new offset based on the provided document. */
  1576. function newIndexOffsetFromDocument(document) {
  1577. return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
  1578. }
  1579. /**
  1580. * Stores the latest read time, document and batch ID that were processed for an
  1581. * index.
  1582. */
  1583. class IndexOffset {
  1584. constructor(
  1585. /**
  1586. * The latest read time version that has been indexed by Firestore for this
  1587. * field index.
  1588. */
  1589. readTime,
  1590. /**
  1591. * The key of the last document that was indexed for this query. Use
  1592. * `DocumentKey.empty()` if no document has been indexed.
  1593. */
  1594. documentKey,
  1595. /*
  1596. * The largest mutation batch id that's been processed by Firestore.
  1597. */
  1598. largestBatchId) {
  1599. this.readTime = readTime;
  1600. this.documentKey = documentKey;
  1601. this.largestBatchId = largestBatchId;
  1602. }
  1603. /** Returns an offset that sorts before all regular offsets. */
  1604. static min() {
  1605. return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
  1606. }
  1607. /** Returns an offset that sorts after all regular offsets. */
  1608. static max() {
  1609. return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
  1610. }
  1611. }
  1612. function indexOffsetComparator(left, right) {
  1613. let cmp = left.readTime.compareTo(right.readTime);
  1614. if (cmp !== 0) {
  1615. return cmp;
  1616. }
  1617. cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
  1618. if (cmp !== 0) {
  1619. return cmp;
  1620. }
  1621. return primitiveComparator(left.largestBatchId, right.largestBatchId);
  1622. }
  1623. /**
  1624. * @license
  1625. * Copyright 2020 Google LLC
  1626. *
  1627. * Licensed under the Apache License, Version 2.0 (the "License");
  1628. * you may not use this file except in compliance with the License.
  1629. * You may obtain a copy of the License at
  1630. *
  1631. * http://www.apache.org/licenses/LICENSE-2.0
  1632. *
  1633. * Unless required by applicable law or agreed to in writing, software
  1634. * distributed under the License is distributed on an "AS IS" BASIS,
  1635. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1636. * See the License for the specific language governing permissions and
  1637. * limitations under the License.
  1638. */
  1639. const PRIMARY_LEASE_LOST_ERROR_MSG = 'The current tab is not in the required state to perform this operation. ' +
  1640. 'It might be necessary to refresh the browser tab.';
  1641. /**
  1642. * A base class representing a persistence transaction, encapsulating both the
  1643. * transaction's sequence numbers as well as a list of onCommitted listeners.
  1644. *
  1645. * When you call Persistence.runTransaction(), it will create a transaction and
  1646. * pass it to your callback. You then pass it to any method that operates
  1647. * on persistence.
  1648. */
  1649. class PersistenceTransaction {
  1650. constructor() {
  1651. this.onCommittedListeners = [];
  1652. }
  1653. addOnCommittedListener(listener) {
  1654. this.onCommittedListeners.push(listener);
  1655. }
  1656. raiseOnCommittedEvent() {
  1657. this.onCommittedListeners.forEach(listener => listener());
  1658. }
  1659. }
  1660. /**
  1661. * @license
  1662. * Copyright 2017 Google LLC
  1663. *
  1664. * Licensed under the Apache License, Version 2.0 (the "License");
  1665. * you may not use this file except in compliance with the License.
  1666. * You may obtain a copy of the License at
  1667. *
  1668. * http://www.apache.org/licenses/LICENSE-2.0
  1669. *
  1670. * Unless required by applicable law or agreed to in writing, software
  1671. * distributed under the License is distributed on an "AS IS" BASIS,
  1672. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1673. * See the License for the specific language governing permissions and
  1674. * limitations under the License.
  1675. */
  1676. /**
  1677. * Verifies the error thrown by a LocalStore operation. If a LocalStore
  1678. * operation fails because the primary lease has been taken by another client,
  1679. * we ignore the error (the persistence layer will immediately call
  1680. * `applyPrimaryLease` to propagate the primary state change). All other errors
  1681. * are re-thrown.
  1682. *
  1683. * @param err - An error returned by a LocalStore operation.
  1684. * @returns A Promise that resolves after we recovered, or the original error.
  1685. */
  1686. async function ignoreIfPrimaryLeaseLoss(err) {
  1687. if (err.code === Code.FAILED_PRECONDITION &&
  1688. err.message === PRIMARY_LEASE_LOST_ERROR_MSG) {
  1689. logDebug('LocalStore', 'Unexpectedly lost primary lease');
  1690. }
  1691. else {
  1692. throw err;
  1693. }
  1694. }
  1695. /**
  1696. * @license
  1697. * Copyright 2017 Google LLC
  1698. *
  1699. * Licensed under the Apache License, Version 2.0 (the "License");
  1700. * you may not use this file except in compliance with the License.
  1701. * You may obtain a copy of the License at
  1702. *
  1703. * http://www.apache.org/licenses/LICENSE-2.0
  1704. *
  1705. * Unless required by applicable law or agreed to in writing, software
  1706. * distributed under the License is distributed on an "AS IS" BASIS,
  1707. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1708. * See the License for the specific language governing permissions and
  1709. * limitations under the License.
  1710. */
  1711. /**
  1712. * PersistencePromise is essentially a re-implementation of Promise except
  1713. * it has a .next() method instead of .then() and .next() and .catch() callbacks
  1714. * are executed synchronously when a PersistencePromise resolves rather than
  1715. * asynchronously (Promise implementations use setImmediate() or similar).
  1716. *
  1717. * This is necessary to interoperate with IndexedDB which will automatically
  1718. * commit transactions if control is returned to the event loop without
  1719. * synchronously initiating another operation on the transaction.
  1720. *
  1721. * NOTE: .then() and .catch() only allow a single consumer, unlike normal
  1722. * Promises.
  1723. */
  1724. class PersistencePromise {
  1725. constructor(callback) {
  1726. // NOTE: next/catchCallback will always point to our own wrapper functions,
  1727. // not the user's raw next() or catch() callbacks.
  1728. this.nextCallback = null;
  1729. this.catchCallback = null;
  1730. // When the operation resolves, we'll set result or error and mark isDone.
  1731. this.result = undefined;
  1732. this.error = undefined;
  1733. this.isDone = false;
  1734. // Set to true when .then() or .catch() are called and prevents additional
  1735. // chaining.
  1736. this.callbackAttached = false;
  1737. callback(value => {
  1738. this.isDone = true;
  1739. this.result = value;
  1740. if (this.nextCallback) {
  1741. // value should be defined unless T is Void, but we can't express
  1742. // that in the type system.
  1743. this.nextCallback(value);
  1744. }
  1745. }, error => {
  1746. this.isDone = true;
  1747. this.error = error;
  1748. if (this.catchCallback) {
  1749. this.catchCallback(error);
  1750. }
  1751. });
  1752. }
  1753. catch(fn) {
  1754. return this.next(undefined, fn);
  1755. }
  1756. next(nextFn, catchFn) {
  1757. if (this.callbackAttached) {
  1758. fail();
  1759. }
  1760. this.callbackAttached = true;
  1761. if (this.isDone) {
  1762. if (!this.error) {
  1763. return this.wrapSuccess(nextFn, this.result);
  1764. }
  1765. else {
  1766. return this.wrapFailure(catchFn, this.error);
  1767. }
  1768. }
  1769. else {
  1770. return new PersistencePromise((resolve, reject) => {
  1771. this.nextCallback = (value) => {
  1772. this.wrapSuccess(nextFn, value).next(resolve, reject);
  1773. };
  1774. this.catchCallback = (error) => {
  1775. this.wrapFailure(catchFn, error).next(resolve, reject);
  1776. };
  1777. });
  1778. }
  1779. }
  1780. toPromise() {
  1781. return new Promise((resolve, reject) => {
  1782. this.next(resolve, reject);
  1783. });
  1784. }
  1785. wrapUserFunction(fn) {
  1786. try {
  1787. const result = fn();
  1788. if (result instanceof PersistencePromise) {
  1789. return result;
  1790. }
  1791. else {
  1792. return PersistencePromise.resolve(result);
  1793. }
  1794. }
  1795. catch (e) {
  1796. return PersistencePromise.reject(e);
  1797. }
  1798. }
  1799. wrapSuccess(nextFn, value) {
  1800. if (nextFn) {
  1801. return this.wrapUserFunction(() => nextFn(value));
  1802. }
  1803. else {
  1804. // If there's no nextFn, then R must be the same as T
  1805. return PersistencePromise.resolve(value);
  1806. }
  1807. }
  1808. wrapFailure(catchFn, error) {
  1809. if (catchFn) {
  1810. return this.wrapUserFunction(() => catchFn(error));
  1811. }
  1812. else {
  1813. return PersistencePromise.reject(error);
  1814. }
  1815. }
  1816. static resolve(result) {
  1817. return new PersistencePromise((resolve, reject) => {
  1818. resolve(result);
  1819. });
  1820. }
  1821. static reject(error) {
  1822. return new PersistencePromise((resolve, reject) => {
  1823. reject(error);
  1824. });
  1825. }
  1826. static waitFor(
  1827. // Accept all Promise types in waitFor().
  1828. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  1829. all) {
  1830. return new PersistencePromise((resolve, reject) => {
  1831. let expectedCount = 0;
  1832. let resolvedCount = 0;
  1833. let done = false;
  1834. all.forEach(element => {
  1835. ++expectedCount;
  1836. element.next(() => {
  1837. ++resolvedCount;
  1838. if (done && resolvedCount === expectedCount) {
  1839. resolve();
  1840. }
  1841. }, err => reject(err));
  1842. });
  1843. done = true;
  1844. if (resolvedCount === expectedCount) {
  1845. resolve();
  1846. }
  1847. });
  1848. }
  1849. /**
  1850. * Given an array of predicate functions that asynchronously evaluate to a
  1851. * boolean, implements a short-circuiting `or` between the results. Predicates
  1852. * will be evaluated until one of them returns `true`, then stop. The final
  1853. * result will be whether any of them returned `true`.
  1854. */
  1855. static or(predicates) {
  1856. let p = PersistencePromise.resolve(false);
  1857. for (const predicate of predicates) {
  1858. p = p.next(isTrue => {
  1859. if (isTrue) {
  1860. return PersistencePromise.resolve(isTrue);
  1861. }
  1862. else {
  1863. return predicate();
  1864. }
  1865. });
  1866. }
  1867. return p;
  1868. }
  1869. static forEach(collection, f) {
  1870. const promises = [];
  1871. collection.forEach((r, s) => {
  1872. promises.push(f.call(this, r, s));
  1873. });
  1874. return this.waitFor(promises);
  1875. }
  1876. /**
  1877. * Concurrently map all array elements through asynchronous function.
  1878. */
  1879. static mapArray(array, f) {
  1880. return new PersistencePromise((resolve, reject) => {
  1881. const expectedCount = array.length;
  1882. const results = new Array(expectedCount);
  1883. let resolvedCount = 0;
  1884. for (let i = 0; i < expectedCount; i++) {
  1885. const current = i;
  1886. f(array[current]).next(result => {
  1887. results[current] = result;
  1888. ++resolvedCount;
  1889. if (resolvedCount === expectedCount) {
  1890. resolve(results);
  1891. }
  1892. }, err => reject(err));
  1893. }
  1894. });
  1895. }
  1896. /**
  1897. * An alternative to recursive PersistencePromise calls, that avoids
  1898. * potential memory problems from unbounded chains of promises.
  1899. *
  1900. * The `action` will be called repeatedly while `condition` is true.
  1901. */
  1902. static doWhile(condition, action) {
  1903. return new PersistencePromise((resolve, reject) => {
  1904. const process = () => {
  1905. if (condition() === true) {
  1906. action().next(() => {
  1907. process();
  1908. }, reject);
  1909. }
  1910. else {
  1911. resolve();
  1912. }
  1913. };
  1914. process();
  1915. });
  1916. }
  1917. }
  1918. /**
  1919. * @license
  1920. * Copyright 2017 Google LLC
  1921. *
  1922. * Licensed under the Apache License, Version 2.0 (the "License");
  1923. * you may not use this file except in compliance with the License.
  1924. * You may obtain a copy of the License at
  1925. *
  1926. * http://www.apache.org/licenses/LICENSE-2.0
  1927. *
  1928. * Unless required by applicable law or agreed to in writing, software
  1929. * distributed under the License is distributed on an "AS IS" BASIS,
  1930. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1931. * See the License for the specific language governing permissions and
  1932. * limitations under the License.
  1933. */
  1934. // References to `window` are guarded by SimpleDb.isAvailable()
  1935. /* eslint-disable no-restricted-globals */
  1936. const LOG_TAG$i = 'SimpleDb';
  1937. /**
  1938. * The maximum number of retry attempts for an IndexedDb transaction that fails
  1939. * with a DOMException.
  1940. */
  1941. const TRANSACTION_RETRY_COUNT = 3;
  1942. /**
  1943. * Wraps an IDBTransaction and exposes a store() method to get a handle to a
  1944. * specific object store.
  1945. */
  1946. class SimpleDbTransaction {
  1947. constructor(action, transaction) {
  1948. this.action = action;
  1949. this.transaction = transaction;
  1950. this.aborted = false;
  1951. /**
  1952. * A `Promise` that resolves with the result of the IndexedDb transaction.
  1953. */
  1954. this.completionDeferred = new Deferred();
  1955. this.transaction.oncomplete = () => {
  1956. this.completionDeferred.resolve();
  1957. };
  1958. this.transaction.onabort = () => {
  1959. if (transaction.error) {
  1960. this.completionDeferred.reject(new IndexedDbTransactionError(action, transaction.error));
  1961. }
  1962. else {
  1963. this.completionDeferred.resolve();
  1964. }
  1965. };
  1966. this.transaction.onerror = (event) => {
  1967. const error = checkForAndReportiOSError(event.target.error);
  1968. this.completionDeferred.reject(new IndexedDbTransactionError(action, error));
  1969. };
  1970. }
  1971. static open(db, action, mode, objectStoreNames) {
  1972. try {
  1973. return new SimpleDbTransaction(action, db.transaction(objectStoreNames, mode));
  1974. }
  1975. catch (e) {
  1976. throw new IndexedDbTransactionError(action, e);
  1977. }
  1978. }
  1979. get completionPromise() {
  1980. return this.completionDeferred.promise;
  1981. }
  1982. abort(error) {
  1983. if (error) {
  1984. this.completionDeferred.reject(error);
  1985. }
  1986. if (!this.aborted) {
  1987. logDebug(LOG_TAG$i, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
  1988. this.aborted = true;
  1989. this.transaction.abort();
  1990. }
  1991. }
  1992. maybeCommit() {
  1993. // If the browser supports V3 IndexedDB, we invoke commit() explicitly to
  1994. // speed up index DB processing if the event loop remains blocks.
  1995. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  1996. const maybeV3IndexedDb = this.transaction;
  1997. if (!this.aborted && typeof maybeV3IndexedDb.commit === 'function') {
  1998. maybeV3IndexedDb.commit();
  1999. }
  2000. }
  2001. /**
  2002. * Returns a SimpleDbStore<KeyType, ValueType> for the specified store. All
  2003. * operations performed on the SimpleDbStore happen within the context of this
  2004. * transaction and it cannot be used anymore once the transaction is
  2005. * completed.
  2006. *
  2007. * Note that we can't actually enforce that the KeyType and ValueType are
  2008. * correct, but they allow type safety through the rest of the consuming code.
  2009. */
  2010. store(storeName) {
  2011. const store = this.transaction.objectStore(storeName);
  2012. return new SimpleDbStore(store);
  2013. }
  2014. }
  2015. /**
  2016. * Provides a wrapper around IndexedDb with a simplified interface that uses
  2017. * Promise-like return values to chain operations. Real promises cannot be used
  2018. * since .then() continuations are executed asynchronously (e.g. via
  2019. * .setImmediate), which would cause IndexedDB to end the transaction.
  2020. * See PersistencePromise for more details.
  2021. */
  2022. class SimpleDb {
  2023. /*
  2024. * Creates a new SimpleDb wrapper for IndexedDb database `name`.
  2025. *
  2026. * Note that `version` must not be a downgrade. IndexedDB does not support
  2027. * downgrading the schema version. We currently do not support any way to do
  2028. * versioning outside of IndexedDB's versioning mechanism, as only
  2029. * version-upgrade transactions are allowed to do things like create
  2030. * objectstores.
  2031. */
  2032. constructor(name, version, schemaConverter) {
  2033. this.name = name;
  2034. this.version = version;
  2035. this.schemaConverter = schemaConverter;
  2036. const iOSVersion = SimpleDb.getIOSVersion(getUA());
  2037. // NOTE: According to https://bugs.webkit.org/show_bug.cgi?id=197050, the
  2038. // bug we're checking for should exist in iOS >= 12.2 and < 13, but for
  2039. // whatever reason it's much harder to hit after 12.2 so we only proactively
  2040. // log on 12.2.
  2041. if (iOSVersion === 12.2) {
  2042. logError('Firestore persistence suffers from a bug in iOS 12.2 ' +
  2043. 'Safari that may cause your app to stop working. See ' +
  2044. 'https://stackoverflow.com/q/56496296/110915 for details ' +
  2045. 'and a potential workaround.');
  2046. }
  2047. }
  2048. /** Deletes the specified database. */
  2049. static delete(name) {
  2050. logDebug(LOG_TAG$i, 'Removing database:', name);
  2051. return wrapRequest(window.indexedDB.deleteDatabase(name)).toPromise();
  2052. }
  2053. /** Returns true if IndexedDB is available in the current environment. */
  2054. static isAvailable() {
  2055. if (!isIndexedDBAvailable()) {
  2056. return false;
  2057. }
  2058. if (SimpleDb.isMockPersistence()) {
  2059. return true;
  2060. }
  2061. // We extensively use indexed array values and compound keys,
  2062. // which IE and Edge do not support. However, they still have indexedDB
  2063. // defined on the window, so we need to check for them here and make sure
  2064. // to return that persistence is not enabled for those browsers.
  2065. // For tracking support of this feature, see here:
  2066. // https://developer.microsoft.com/en-us/microsoft-edge/platform/status/indexeddbarraysandmultientrysupport/
  2067. // Check the UA string to find out the browser.
  2068. const ua = getUA();
  2069. // IE 10
  2070. // ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';
  2071. // IE 11
  2072. // ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
  2073. // Edge
  2074. // ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML,
  2075. // like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';
  2076. // iOS Safari: Disable for users running iOS version < 10.
  2077. const iOSVersion = SimpleDb.getIOSVersion(ua);
  2078. const isUnsupportedIOS = 0 < iOSVersion && iOSVersion < 10;
  2079. // Android browser: Disable for userse running version < 4.5.
  2080. const androidVersion = SimpleDb.getAndroidVersion(ua);
  2081. const isUnsupportedAndroid = 0 < androidVersion && androidVersion < 4.5;
  2082. if (ua.indexOf('MSIE ') > 0 ||
  2083. ua.indexOf('Trident/') > 0 ||
  2084. ua.indexOf('Edge/') > 0 ||
  2085. isUnsupportedIOS ||
  2086. isUnsupportedAndroid) {
  2087. return false;
  2088. }
  2089. else {
  2090. return true;
  2091. }
  2092. }
  2093. /**
  2094. * Returns true if the backing IndexedDB store is the Node IndexedDBShim
  2095. * (see https://github.com/axemclion/IndexedDBShim).
  2096. */
  2097. static isMockPersistence() {
  2098. var _a;
  2099. return (typeof process !== 'undefined' &&
  2100. ((_a = process.env) === null || _a === void 0 ? void 0 : _a.USE_MOCK_PERSISTENCE) === 'YES');
  2101. }
  2102. /** Helper to get a typed SimpleDbStore from a transaction. */
  2103. static getStore(txn, store) {
  2104. return txn.store(store);
  2105. }
  2106. // visible for testing
  2107. /** Parse User Agent to determine iOS version. Returns -1 if not found. */
  2108. static getIOSVersion(ua) {
  2109. const iOSVersionRegex = ua.match(/i(?:phone|pad|pod) os ([\d_]+)/i);
  2110. const version = iOSVersionRegex
  2111. ? iOSVersionRegex[1].split('_').slice(0, 2).join('.')
  2112. : '-1';
  2113. return Number(version);
  2114. }
  2115. // visible for testing
  2116. /** Parse User Agent to determine Android version. Returns -1 if not found. */
  2117. static getAndroidVersion(ua) {
  2118. const androidVersionRegex = ua.match(/Android ([\d.]+)/i);
  2119. const version = androidVersionRegex
  2120. ? androidVersionRegex[1].split('.').slice(0, 2).join('.')
  2121. : '-1';
  2122. return Number(version);
  2123. }
  2124. /**
  2125. * Opens the specified database, creating or upgrading it if necessary.
  2126. */
  2127. async ensureDb(action) {
  2128. if (!this.db) {
  2129. logDebug(LOG_TAG$i, 'Opening database:', this.name);
  2130. this.db = await new Promise((resolve, reject) => {
  2131. // TODO(mikelehen): Investigate browser compatibility.
  2132. // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
  2133. // suggests IE9 and older WebKit browsers handle upgrade
  2134. // differently. They expect setVersion, as described here:
  2135. // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion
  2136. const request = indexedDB.open(this.name, this.version);
  2137. request.onsuccess = (event) => {
  2138. const db = event.target.result;
  2139. resolve(db);
  2140. };
  2141. request.onblocked = () => {
  2142. reject(new IndexedDbTransactionError(action, 'Cannot upgrade IndexedDB schema while another tab is open. ' +
  2143. 'Close all tabs that access Firestore and reload this page to proceed.'));
  2144. };
  2145. request.onerror = (event) => {
  2146. const error = event.target.error;
  2147. if (error.name === 'VersionError') {
  2148. reject(new FirestoreError(Code.FAILED_PRECONDITION, 'A newer version of the Firestore SDK was previously used and so the persisted ' +
  2149. 'data is not compatible with the version of the SDK you are now using. The SDK ' +
  2150. 'will operate with persistence disabled. If you need persistence, please ' +
  2151. 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' +
  2152. 'data for your app to start fresh.'));
  2153. }
  2154. else if (error.name === 'InvalidStateError') {
  2155. reject(new FirestoreError(Code.FAILED_PRECONDITION, 'Unable to open an IndexedDB connection. This could be due to running in a ' +
  2156. 'private browsing session on a browser whose private browsing sessions do not ' +
  2157. 'support IndexedDB: ' +
  2158. error));
  2159. }
  2160. else {
  2161. reject(new IndexedDbTransactionError(action, error));
  2162. }
  2163. };
  2164. request.onupgradeneeded = (event) => {
  2165. logDebug(LOG_TAG$i, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
  2166. const db = event.target.result;
  2167. this.schemaConverter
  2168. .createOrUpgrade(db, request.transaction, event.oldVersion, this.version)
  2169. .next(() => {
  2170. logDebug(LOG_TAG$i, 'Database upgrade to version ' + this.version + ' complete');
  2171. });
  2172. };
  2173. });
  2174. }
  2175. if (this.versionchangelistener) {
  2176. this.db.onversionchange = event => this.versionchangelistener(event);
  2177. }
  2178. return this.db;
  2179. }
  2180. setVersionChangeListener(versionChangeListener) {
  2181. this.versionchangelistener = versionChangeListener;
  2182. if (this.db) {
  2183. this.db.onversionchange = (event) => {
  2184. return versionChangeListener(event);
  2185. };
  2186. }
  2187. }
  2188. async runTransaction(action, mode, objectStores, transactionFn) {
  2189. const readonly = mode === 'readonly';
  2190. let attemptNumber = 0;
  2191. while (true) {
  2192. ++attemptNumber;
  2193. try {
  2194. this.db = await this.ensureDb(action);
  2195. const transaction = SimpleDbTransaction.open(this.db, action, readonly ? 'readonly' : 'readwrite', objectStores);
  2196. const transactionFnResult = transactionFn(transaction)
  2197. .next(result => {
  2198. transaction.maybeCommit();
  2199. return result;
  2200. })
  2201. .catch(error => {
  2202. // Abort the transaction if there was an error.
  2203. transaction.abort(error);
  2204. // We cannot actually recover, and calling `abort()` will cause the transaction's
  2205. // completion promise to be rejected. This in turn means that we won't use
  2206. // `transactionFnResult` below. We return a rejection here so that we don't add the
  2207. // possibility of returning `void` to the type of `transactionFnResult`.
  2208. return PersistencePromise.reject(error);
  2209. })
  2210. .toPromise();
  2211. // As noted above, errors are propagated by aborting the transaction. So
  2212. // we swallow any error here to avoid the browser logging it as unhandled.
  2213. transactionFnResult.catch(() => { });
  2214. // Wait for the transaction to complete (i.e. IndexedDb's onsuccess event to
  2215. // fire), but still return the original transactionFnResult back to the
  2216. // caller.
  2217. await transaction.completionPromise;
  2218. return transactionFnResult;
  2219. }
  2220. catch (e) {
  2221. const error = e;
  2222. // TODO(schmidt-sebastian): We could probably be smarter about this and
  2223. // not retry exceptions that are likely unrecoverable (such as quota
  2224. // exceeded errors).
  2225. // Note: We cannot use an instanceof check for FirestoreException, since the
  2226. // exception is wrapped in a generic error by our async/await handling.
  2227. const retryable = error.name !== 'FirebaseError' &&
  2228. attemptNumber < TRANSACTION_RETRY_COUNT;
  2229. logDebug(LOG_TAG$i, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
  2230. this.close();
  2231. if (!retryable) {
  2232. return Promise.reject(error);
  2233. }
  2234. }
  2235. }
  2236. }
  2237. close() {
  2238. if (this.db) {
  2239. this.db.close();
  2240. }
  2241. this.db = undefined;
  2242. }
  2243. }
  2244. /**
  2245. * A controller for iterating over a key range or index. It allows an iterate
  2246. * callback to delete the currently-referenced object, or jump to a new key
  2247. * within the key range or index.
  2248. */
  2249. class IterationController {
  2250. constructor(dbCursor) {
  2251. this.dbCursor = dbCursor;
  2252. this.shouldStop = false;
  2253. this.nextKey = null;
  2254. }
  2255. get isDone() {
  2256. return this.shouldStop;
  2257. }
  2258. get skipToKey() {
  2259. return this.nextKey;
  2260. }
  2261. set cursor(value) {
  2262. this.dbCursor = value;
  2263. }
  2264. /**
  2265. * This function can be called to stop iteration at any point.
  2266. */
  2267. done() {
  2268. this.shouldStop = true;
  2269. }
  2270. /**
  2271. * This function can be called to skip to that next key, which could be
  2272. * an index or a primary key.
  2273. */
  2274. skip(key) {
  2275. this.nextKey = key;
  2276. }
  2277. /**
  2278. * Delete the current cursor value from the object store.
  2279. *
  2280. * NOTE: You CANNOT do this with a keysOnly query.
  2281. */
  2282. delete() {
  2283. return wrapRequest(this.dbCursor.delete());
  2284. }
  2285. }
  2286. /** An error that wraps exceptions that thrown during IndexedDB execution. */
  2287. class IndexedDbTransactionError extends FirestoreError {
  2288. constructor(actionName, cause) {
  2289. super(Code.UNAVAILABLE, `IndexedDB transaction '${actionName}' failed: ${cause}`);
  2290. this.name = 'IndexedDbTransactionError';
  2291. }
  2292. }
  2293. /** Verifies whether `e` is an IndexedDbTransactionError. */
  2294. function isIndexedDbTransactionError(e) {
  2295. // Use name equality, as instanceof checks on errors don't work with errors
  2296. // that wrap other errors.
  2297. return e.name === 'IndexedDbTransactionError';
  2298. }
  2299. /**
  2300. * A wrapper around an IDBObjectStore providing an API that:
  2301. *
  2302. * 1) Has generic KeyType / ValueType parameters to provide strongly-typed
  2303. * methods for acting against the object store.
  2304. * 2) Deals with IndexedDB's onsuccess / onerror event callbacks, making every
  2305. * method return a PersistencePromise instead.
  2306. * 3) Provides a higher-level API to avoid needing to do excessive wrapping of
  2307. * intermediate IndexedDB types (IDBCursorWithValue, etc.)
  2308. */
  2309. class SimpleDbStore {
  2310. constructor(store) {
  2311. this.store = store;
  2312. }
  2313. put(keyOrValue, value) {
  2314. let request;
  2315. if (value !== undefined) {
  2316. logDebug(LOG_TAG$i, 'PUT', this.store.name, keyOrValue, value);
  2317. request = this.store.put(value, keyOrValue);
  2318. }
  2319. else {
  2320. logDebug(LOG_TAG$i, 'PUT', this.store.name, '<auto-key>', keyOrValue);
  2321. request = this.store.put(keyOrValue);
  2322. }
  2323. return wrapRequest(request);
  2324. }
  2325. /**
  2326. * Adds a new value into an Object Store and returns the new key. Similar to
  2327. * IndexedDb's `add()`, this method will fail on primary key collisions.
  2328. *
  2329. * @param value - The object to write.
  2330. * @returns The key of the value to add.
  2331. */
  2332. add(value) {
  2333. logDebug(LOG_TAG$i, 'ADD', this.store.name, value, value);
  2334. const request = this.store.add(value);
  2335. return wrapRequest(request);
  2336. }
  2337. /**
  2338. * Gets the object with the specified key from the specified store, or null
  2339. * if no object exists with the specified key.
  2340. *
  2341. * @key The key of the object to get.
  2342. * @returns The object with the specified key or null if no object exists.
  2343. */
  2344. get(key) {
  2345. const request = this.store.get(key);
  2346. // We're doing an unsafe cast to ValueType.
  2347. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  2348. return wrapRequest(request).next(result => {
  2349. // Normalize nonexistence to null.
  2350. if (result === undefined) {
  2351. result = null;
  2352. }
  2353. logDebug(LOG_TAG$i, 'GET', this.store.name, key, result);
  2354. return result;
  2355. });
  2356. }
  2357. delete(key) {
  2358. logDebug(LOG_TAG$i, 'DELETE', this.store.name, key);
  2359. const request = this.store.delete(key);
  2360. return wrapRequest(request);
  2361. }
  2362. /**
  2363. * If we ever need more of the count variants, we can add overloads. For now,
  2364. * all we need is to count everything in a store.
  2365. *
  2366. * Returns the number of rows in the store.
  2367. */
  2368. count() {
  2369. logDebug(LOG_TAG$i, 'COUNT', this.store.name);
  2370. const request = this.store.count();
  2371. return wrapRequest(request);
  2372. }
  2373. loadAll(indexOrRange, range) {
  2374. const iterateOptions = this.options(indexOrRange, range);
  2375. // Use `getAll()` if the browser supports IndexedDB v3, as it is roughly
  2376. // 20% faster. Unfortunately, getAll() does not support custom indices.
  2377. if (!iterateOptions.index && typeof this.store.getAll === 'function') {
  2378. const request = this.store.getAll(iterateOptions.range);
  2379. return new PersistencePromise((resolve, reject) => {
  2380. request.onerror = (event) => {
  2381. reject(event.target.error);
  2382. };
  2383. request.onsuccess = (event) => {
  2384. resolve(event.target.result);
  2385. };
  2386. });
  2387. }
  2388. else {
  2389. const cursor = this.cursor(iterateOptions);
  2390. const results = [];
  2391. return this.iterateCursor(cursor, (key, value) => {
  2392. results.push(value);
  2393. }).next(() => {
  2394. return results;
  2395. });
  2396. }
  2397. }
  2398. /**
  2399. * Loads the first `count` elements from the provided index range. Loads all
  2400. * elements if no limit is provided.
  2401. */
  2402. loadFirst(range, count) {
  2403. const request = this.store.getAll(range, count === null ? undefined : count);
  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. deleteAll(indexOrRange, range) {
  2414. logDebug(LOG_TAG$i, 'DELETE ALL', this.store.name);
  2415. const options = this.options(indexOrRange, range);
  2416. options.keysOnly = false;
  2417. const cursor = this.cursor(options);
  2418. return this.iterateCursor(cursor, (key, value, control) => {
  2419. // NOTE: Calling delete() on a cursor is documented as more efficient than
  2420. // calling delete() on an object store with a single key
  2421. // (https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/delete),
  2422. // however, this requires us *not* to use a keysOnly cursor
  2423. // (https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/delete). We
  2424. // may want to compare the performance of each method.
  2425. return control.delete();
  2426. });
  2427. }
  2428. iterate(optionsOrCallback, callback) {
  2429. let options;
  2430. if (!callback) {
  2431. options = {};
  2432. callback = optionsOrCallback;
  2433. }
  2434. else {
  2435. options = optionsOrCallback;
  2436. }
  2437. const cursor = this.cursor(options);
  2438. return this.iterateCursor(cursor, callback);
  2439. }
  2440. /**
  2441. * Iterates over a store, but waits for the given callback to complete for
  2442. * each entry before iterating the next entry. This allows the callback to do
  2443. * asynchronous work to determine if this iteration should continue.
  2444. *
  2445. * The provided callback should return `true` to continue iteration, and
  2446. * `false` otherwise.
  2447. */
  2448. iterateSerial(callback) {
  2449. const cursorRequest = this.cursor({});
  2450. return new PersistencePromise((resolve, reject) => {
  2451. cursorRequest.onerror = (event) => {
  2452. const error = checkForAndReportiOSError(event.target.error);
  2453. reject(error);
  2454. };
  2455. cursorRequest.onsuccess = (event) => {
  2456. const cursor = event.target.result;
  2457. if (!cursor) {
  2458. resolve();
  2459. return;
  2460. }
  2461. callback(cursor.primaryKey, cursor.value).next(shouldContinue => {
  2462. if (shouldContinue) {
  2463. cursor.continue();
  2464. }
  2465. else {
  2466. resolve();
  2467. }
  2468. });
  2469. };
  2470. });
  2471. }
  2472. iterateCursor(cursorRequest, fn) {
  2473. const results = [];
  2474. return new PersistencePromise((resolve, reject) => {
  2475. cursorRequest.onerror = (event) => {
  2476. reject(event.target.error);
  2477. };
  2478. cursorRequest.onsuccess = (event) => {
  2479. const cursor = event.target.result;
  2480. if (!cursor) {
  2481. resolve();
  2482. return;
  2483. }
  2484. const controller = new IterationController(cursor);
  2485. const userResult = fn(cursor.primaryKey, cursor.value, controller);
  2486. if (userResult instanceof PersistencePromise) {
  2487. const userPromise = userResult.catch(err => {
  2488. controller.done();
  2489. return PersistencePromise.reject(err);
  2490. });
  2491. results.push(userPromise);
  2492. }
  2493. if (controller.isDone) {
  2494. resolve();
  2495. }
  2496. else if (controller.skipToKey === null) {
  2497. cursor.continue();
  2498. }
  2499. else {
  2500. cursor.continue(controller.skipToKey);
  2501. }
  2502. };
  2503. }).next(() => PersistencePromise.waitFor(results));
  2504. }
  2505. options(indexOrRange, range) {
  2506. let indexName = undefined;
  2507. if (indexOrRange !== undefined) {
  2508. if (typeof indexOrRange === 'string') {
  2509. indexName = indexOrRange;
  2510. }
  2511. else {
  2512. range = indexOrRange;
  2513. }
  2514. }
  2515. return { index: indexName, range };
  2516. }
  2517. cursor(options) {
  2518. let direction = 'next';
  2519. if (options.reverse) {
  2520. direction = 'prev';
  2521. }
  2522. if (options.index) {
  2523. const index = this.store.index(options.index);
  2524. if (options.keysOnly) {
  2525. return index.openKeyCursor(options.range, direction);
  2526. }
  2527. else {
  2528. return index.openCursor(options.range, direction);
  2529. }
  2530. }
  2531. else {
  2532. return this.store.openCursor(options.range, direction);
  2533. }
  2534. }
  2535. }
  2536. /**
  2537. * Wraps an IDBRequest in a PersistencePromise, using the onsuccess / onerror
  2538. * handlers to resolve / reject the PersistencePromise as appropriate.
  2539. */
  2540. function wrapRequest(request) {
  2541. return new PersistencePromise((resolve, reject) => {
  2542. request.onsuccess = (event) => {
  2543. const result = event.target.result;
  2544. resolve(result);
  2545. };
  2546. request.onerror = (event) => {
  2547. const error = checkForAndReportiOSError(event.target.error);
  2548. reject(error);
  2549. };
  2550. });
  2551. }
  2552. // Guard so we only report the error once.
  2553. let reportedIOSError = false;
  2554. function checkForAndReportiOSError(error) {
  2555. const iOSVersion = SimpleDb.getIOSVersion(getUA());
  2556. if (iOSVersion >= 12.2 && iOSVersion < 13) {
  2557. const IOS_ERROR = 'An internal error was encountered in the Indexed Database server';
  2558. if (error.message.indexOf(IOS_ERROR) >= 0) {
  2559. // Wrap error in a more descriptive one.
  2560. const newError = new FirestoreError('internal', `IOS_INDEXEDDB_BUG1: IndexedDb has thrown '${IOS_ERROR}'. This is likely ` +
  2561. `due to an unavoidable bug in iOS. See https://stackoverflow.com/q/56496296/110915 ` +
  2562. `for details and a potential workaround.`);
  2563. if (!reportedIOSError) {
  2564. reportedIOSError = true;
  2565. // Throw a global exception outside of this promise chain, for the user to
  2566. // potentially catch.
  2567. setTimeout(() => {
  2568. throw newError;
  2569. }, 0);
  2570. }
  2571. return newError;
  2572. }
  2573. }
  2574. return error;
  2575. }
  2576. const LOG_TAG$h = 'IndexBackiller';
  2577. /** How long we wait to try running index backfill after SDK initialization. */
  2578. const INITIAL_BACKFILL_DELAY_MS = 15 * 1000;
  2579. /** Minimum amount of time between backfill checks, after the first one. */
  2580. const REGULAR_BACKFILL_DELAY_MS = 60 * 1000;
  2581. /** The maximum number of documents to process each time backfill() is called. */
  2582. const MAX_DOCUMENTS_TO_PROCESS = 50;
  2583. /** This class is responsible for the scheduling of Index Backfiller. */
  2584. class IndexBackfillerScheduler {
  2585. constructor(asyncQueue, backfiller) {
  2586. this.asyncQueue = asyncQueue;
  2587. this.backfiller = backfiller;
  2588. this.task = null;
  2589. }
  2590. start() {
  2591. this.schedule(INITIAL_BACKFILL_DELAY_MS);
  2592. }
  2593. stop() {
  2594. if (this.task) {
  2595. this.task.cancel();
  2596. this.task = null;
  2597. }
  2598. }
  2599. get started() {
  2600. return this.task !== null;
  2601. }
  2602. schedule(delay) {
  2603. logDebug(LOG_TAG$h, `Scheduled in ${delay}ms`);
  2604. this.task = this.asyncQueue.enqueueAfterDelay("index_backfill" /* TimerId.IndexBackfill */, delay, async () => {
  2605. this.task = null;
  2606. try {
  2607. const documentsProcessed = await this.backfiller.backfill();
  2608. logDebug(LOG_TAG$h, `Documents written: ${documentsProcessed}`);
  2609. }
  2610. catch (e) {
  2611. if (isIndexedDbTransactionError(e)) {
  2612. logDebug(LOG_TAG$h, 'Ignoring IndexedDB error during index backfill: ', e);
  2613. }
  2614. else {
  2615. await ignoreIfPrimaryLeaseLoss(e);
  2616. }
  2617. }
  2618. await this.schedule(REGULAR_BACKFILL_DELAY_MS);
  2619. });
  2620. }
  2621. }
  2622. /** Implements the steps for backfilling indexes. */
  2623. class IndexBackfiller {
  2624. constructor(
  2625. /**
  2626. * LocalStore provides access to IndexManager and LocalDocumentView.
  2627. * These properties will update when the user changes. Consequently,
  2628. * making a local copy of IndexManager and LocalDocumentView will require
  2629. * updates over time. The simpler solution is to rely on LocalStore to have
  2630. * an up-to-date references to IndexManager and LocalDocumentStore.
  2631. */
  2632. localStore, persistence) {
  2633. this.localStore = localStore;
  2634. this.persistence = persistence;
  2635. }
  2636. async backfill(maxDocumentsToProcess = MAX_DOCUMENTS_TO_PROCESS) {
  2637. return this.persistence.runTransaction('Backfill Indexes', 'readwrite-primary', txn => this.writeIndexEntries(txn, maxDocumentsToProcess));
  2638. }
  2639. /** Writes index entries until the cap is reached. Returns the number of documents processed. */
  2640. writeIndexEntries(transation, maxDocumentsToProcess) {
  2641. const processedCollectionGroups = new Set();
  2642. let documentsRemaining = maxDocumentsToProcess;
  2643. let continueLoop = true;
  2644. return PersistencePromise.doWhile(() => continueLoop === true && documentsRemaining > 0, () => {
  2645. return this.localStore.indexManager
  2646. .getNextCollectionGroupToUpdate(transation)
  2647. .next((collectionGroup) => {
  2648. if (collectionGroup === null ||
  2649. processedCollectionGroups.has(collectionGroup)) {
  2650. continueLoop = false;
  2651. }
  2652. else {
  2653. logDebug(LOG_TAG$h, `Processing collection: ${collectionGroup}`);
  2654. return this.writeEntriesForCollectionGroup(transation, collectionGroup, documentsRemaining).next(documentsProcessed => {
  2655. documentsRemaining -= documentsProcessed;
  2656. processedCollectionGroups.add(collectionGroup);
  2657. });
  2658. }
  2659. });
  2660. }).next(() => maxDocumentsToProcess - documentsRemaining);
  2661. }
  2662. /**
  2663. * Writes entries for the provided collection group. Returns the number of documents processed.
  2664. */
  2665. writeEntriesForCollectionGroup(transaction, collectionGroup, documentsRemainingUnderCap) {
  2666. // Use the earliest offset of all field indexes to query the local cache.
  2667. return this.localStore.indexManager
  2668. .getMinOffsetFromCollectionGroup(transaction, collectionGroup)
  2669. .next(existingOffset => this.localStore.localDocuments
  2670. .getNextDocuments(transaction, collectionGroup, existingOffset, documentsRemainingUnderCap)
  2671. .next(nextBatch => {
  2672. const docs = nextBatch.changes;
  2673. return this.localStore.indexManager
  2674. .updateIndexEntries(transaction, docs)
  2675. .next(() => this.getNewOffset(existingOffset, nextBatch))
  2676. .next(newOffset => {
  2677. logDebug(LOG_TAG$h, `Updating offset: ${newOffset}`);
  2678. return this.localStore.indexManager.updateCollectionGroup(transaction, collectionGroup, newOffset);
  2679. })
  2680. .next(() => docs.size);
  2681. }));
  2682. }
  2683. /** Returns the next offset based on the provided documents. */
  2684. getNewOffset(existingOffset, lookupResult) {
  2685. let maxOffset = existingOffset;
  2686. lookupResult.changes.forEach((key, document) => {
  2687. const newOffset = newIndexOffsetFromDocument(document);
  2688. if (indexOffsetComparator(newOffset, maxOffset) > 0) {
  2689. maxOffset = newOffset;
  2690. }
  2691. });
  2692. return new IndexOffset(maxOffset.readTime, maxOffset.documentKey, Math.max(lookupResult.batchId, existingOffset.largestBatchId));
  2693. }
  2694. }
  2695. /**
  2696. * @license
  2697. * Copyright 2018 Google LLC
  2698. *
  2699. * Licensed under the Apache License, Version 2.0 (the "License");
  2700. * you may not use this file except in compliance with the License.
  2701. * You may obtain a copy of the License at
  2702. *
  2703. * http://www.apache.org/licenses/LICENSE-2.0
  2704. *
  2705. * Unless required by applicable law or agreed to in writing, software
  2706. * distributed under the License is distributed on an "AS IS" BASIS,
  2707. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2708. * See the License for the specific language governing permissions and
  2709. * limitations under the License.
  2710. */
  2711. /**
  2712. * `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to
  2713. * exceed. All subsequent calls to next will return increasing values. If provided with a
  2714. * `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as
  2715. * well as write out sequence numbers that it produces via `next()`.
  2716. */
  2717. class ListenSequence {
  2718. constructor(previousValue, sequenceNumberSyncer) {
  2719. this.previousValue = previousValue;
  2720. if (sequenceNumberSyncer) {
  2721. sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber => this.setPreviousValue(sequenceNumber);
  2722. this.writeNewSequenceNumber = sequenceNumber => sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);
  2723. }
  2724. }
  2725. setPreviousValue(externalPreviousValue) {
  2726. this.previousValue = Math.max(externalPreviousValue, this.previousValue);
  2727. return this.previousValue;
  2728. }
  2729. next() {
  2730. const nextValue = ++this.previousValue;
  2731. if (this.writeNewSequenceNumber) {
  2732. this.writeNewSequenceNumber(nextValue);
  2733. }
  2734. return nextValue;
  2735. }
  2736. }
  2737. ListenSequence.INVALID = -1;
  2738. /**
  2739. * @license
  2740. * Copyright 2017 Google LLC
  2741. *
  2742. * Licensed under the Apache License, Version 2.0 (the "License");
  2743. * you may not use this file except in compliance with the License.
  2744. * You may obtain a copy of the License at
  2745. *
  2746. * http://www.apache.org/licenses/LICENSE-2.0
  2747. *
  2748. * Unless required by applicable law or agreed to in writing, software
  2749. * distributed under the License is distributed on an "AS IS" BASIS,
  2750. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2751. * See the License for the specific language governing permissions and
  2752. * limitations under the License.
  2753. */
  2754. const escapeChar = '\u0001';
  2755. const encodedSeparatorChar = '\u0001';
  2756. const encodedNul = '\u0010';
  2757. const encodedEscape = '\u0011';
  2758. /**
  2759. * Encodes a resource path into a IndexedDb-compatible string form.
  2760. */
  2761. function encodeResourcePath(path) {
  2762. let result = '';
  2763. for (let i = 0; i < path.length; i++) {
  2764. if (result.length > 0) {
  2765. result = encodeSeparator(result);
  2766. }
  2767. result = encodeSegment(path.get(i), result);
  2768. }
  2769. return encodeSeparator(result);
  2770. }
  2771. /** Encodes a single segment of a resource path into the given result */
  2772. function encodeSegment(segment, resultBuf) {
  2773. let result = resultBuf;
  2774. const length = segment.length;
  2775. for (let i = 0; i < length; i++) {
  2776. const c = segment.charAt(i);
  2777. switch (c) {
  2778. case '\0':
  2779. result += escapeChar + encodedNul;
  2780. break;
  2781. case escapeChar:
  2782. result += escapeChar + encodedEscape;
  2783. break;
  2784. default:
  2785. result += c;
  2786. }
  2787. }
  2788. return result;
  2789. }
  2790. /** Encodes a path separator into the given result */
  2791. function encodeSeparator(result) {
  2792. return result + escapeChar + encodedSeparatorChar;
  2793. }
  2794. /**
  2795. * Decodes the given IndexedDb-compatible string form of a resource path into
  2796. * a ResourcePath instance. Note that this method is not suitable for use with
  2797. * decoding resource names from the server; those are One Platform format
  2798. * strings.
  2799. */
  2800. function decodeResourcePath(path) {
  2801. // Event the empty path must encode as a path of at least length 2. A path
  2802. // with exactly 2 must be the empty path.
  2803. const length = path.length;
  2804. hardAssert(length >= 2);
  2805. if (length === 2) {
  2806. hardAssert(path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar);
  2807. return ResourcePath.emptyPath();
  2808. }
  2809. // Escape characters cannot exist past the second-to-last position in the
  2810. // source value.
  2811. const lastReasonableEscapeIndex = length - 2;
  2812. const segments = [];
  2813. let segmentBuilder = '';
  2814. for (let start = 0; start < length;) {
  2815. // The last two characters of a valid encoded path must be a separator, so
  2816. // there must be an end to this segment.
  2817. const end = path.indexOf(escapeChar, start);
  2818. if (end < 0 || end > lastReasonableEscapeIndex) {
  2819. fail();
  2820. }
  2821. const next = path.charAt(end + 1);
  2822. switch (next) {
  2823. case encodedSeparatorChar:
  2824. const currentPiece = path.substring(start, end);
  2825. let segment;
  2826. if (segmentBuilder.length === 0) {
  2827. // Avoid copying for the common case of a segment that excludes \0
  2828. // and \001
  2829. segment = currentPiece;
  2830. }
  2831. else {
  2832. segmentBuilder += currentPiece;
  2833. segment = segmentBuilder;
  2834. segmentBuilder = '';
  2835. }
  2836. segments.push(segment);
  2837. break;
  2838. case encodedNul:
  2839. segmentBuilder += path.substring(start, end);
  2840. segmentBuilder += '\0';
  2841. break;
  2842. case encodedEscape:
  2843. // The escape character can be used in the output to encode itself.
  2844. segmentBuilder += path.substring(start, end + 1);
  2845. break;
  2846. default:
  2847. fail();
  2848. }
  2849. start = end + 2;
  2850. }
  2851. return new ResourcePath(segments);
  2852. }
  2853. /**
  2854. * @license
  2855. * Copyright 2022 Google LLC
  2856. *
  2857. * Licensed under the Apache License, Version 2.0 (the "License");
  2858. * you may not use this file except in compliance with the License.
  2859. * You may obtain a copy of the License at
  2860. *
  2861. * http://www.apache.org/licenses/LICENSE-2.0
  2862. *
  2863. * Unless required by applicable law or agreed to in writing, software
  2864. * distributed under the License is distributed on an "AS IS" BASIS,
  2865. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2866. * See the License for the specific language governing permissions and
  2867. * limitations under the License.
  2868. */
  2869. const DbRemoteDocumentStore$1 = 'remoteDocuments';
  2870. /**
  2871. * @license
  2872. * Copyright 2022 Google LLC
  2873. *
  2874. * Licensed under the Apache License, Version 2.0 (the "License");
  2875. * you may not use this file except in compliance with the License.
  2876. * You may obtain a copy of the License at
  2877. *
  2878. * http://www.apache.org/licenses/LICENSE-2.0
  2879. *
  2880. * Unless required by applicable law or agreed to in writing, software
  2881. * distributed under the License is distributed on an "AS IS" BASIS,
  2882. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2883. * See the License for the specific language governing permissions and
  2884. * limitations under the License.
  2885. */
  2886. /**
  2887. * Name of the IndexedDb object store.
  2888. *
  2889. * Note that the name 'owner' is chosen to ensure backwards compatibility with
  2890. * older clients that only supported single locked access to the persistence
  2891. * layer.
  2892. */
  2893. const DbPrimaryClientStore = 'owner';
  2894. /**
  2895. * The key string used for the single object that exists in the
  2896. * DbPrimaryClient store.
  2897. */
  2898. const DbPrimaryClientKey = 'owner';
  2899. /** Name of the IndexedDb object store. */
  2900. const DbMutationQueueStore = 'mutationQueues';
  2901. /** Keys are automatically assigned via the userId property. */
  2902. const DbMutationQueueKeyPath = 'userId';
  2903. /** Name of the IndexedDb object store. */
  2904. const DbMutationBatchStore = 'mutations';
  2905. /** Keys are automatically assigned via the userId, batchId properties. */
  2906. const DbMutationBatchKeyPath = 'batchId';
  2907. /** The index name for lookup of mutations by user. */
  2908. const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
  2909. /** The user mutations index is keyed by [userId, batchId] pairs. */
  2910. const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
  2911. /**
  2912. * Creates a [userId] key for use in the DbDocumentMutations index to iterate
  2913. * over all of a user's document mutations.
  2914. */
  2915. function newDbDocumentMutationPrefixForUser(userId) {
  2916. return [userId];
  2917. }
  2918. /**
  2919. * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
  2920. * index to iterate over all at document mutations for a given path or lower.
  2921. */
  2922. function newDbDocumentMutationPrefixForPath(userId, path) {
  2923. return [userId, encodeResourcePath(path)];
  2924. }
  2925. /**
  2926. * Creates a full index key of [userId, encodedPath, batchId] for inserting
  2927. * and deleting into the DbDocumentMutations index.
  2928. */
  2929. function newDbDocumentMutationKey(userId, path, batchId) {
  2930. return [userId, encodeResourcePath(path), batchId];
  2931. }
  2932. /**
  2933. * Because we store all the useful information for this store in the key,
  2934. * there is no useful information to store as the value. The raw (unencoded)
  2935. * path cannot be stored because IndexedDb doesn't store prototype
  2936. * information.
  2937. */
  2938. const DbDocumentMutationPlaceholder = {};
  2939. const DbDocumentMutationStore = 'documentMutations';
  2940. const DbRemoteDocumentStore = 'remoteDocumentsV14';
  2941. /**
  2942. * The primary key of the remote documents store, which allows for efficient
  2943. * access by collection path and read time.
  2944. */
  2945. const DbRemoteDocumentKeyPath = [
  2946. 'prefixPath',
  2947. 'collectionGroup',
  2948. 'readTime',
  2949. 'documentId'
  2950. ];
  2951. /** An index that provides access to documents by key. */
  2952. const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
  2953. const DbRemoteDocumentDocumentKeyIndexPath = [
  2954. 'prefixPath',
  2955. 'collectionGroup',
  2956. 'documentId'
  2957. ];
  2958. /**
  2959. * An index that provides access to documents by collection group and read
  2960. * time.
  2961. *
  2962. * This index is used by the index backfiller.
  2963. */
  2964. const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
  2965. const DbRemoteDocumentCollectionGroupIndexPath = [
  2966. 'collectionGroup',
  2967. 'readTime',
  2968. 'prefixPath',
  2969. 'documentId'
  2970. ];
  2971. const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
  2972. const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
  2973. const DbTargetStore = 'targets';
  2974. /** Keys are automatically assigned via the targetId property. */
  2975. const DbTargetKeyPath = 'targetId';
  2976. /** The name of the queryTargets index. */
  2977. const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
  2978. /**
  2979. * The index of all canonicalIds to the targets that they match. This is not
  2980. * a unique mapping because canonicalId does not promise a unique name for all
  2981. * possible queries, so we append the targetId to make the mapping unique.
  2982. */
  2983. const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
  2984. /** Name of the IndexedDb object store. */
  2985. const DbTargetDocumentStore = 'targetDocuments';
  2986. /** Keys are automatically assigned via the targetId, path properties. */
  2987. const DbTargetDocumentKeyPath = ['targetId', 'path'];
  2988. /** The index name for the reverse index. */
  2989. const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
  2990. /** We also need to create the reverse index for these properties. */
  2991. const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
  2992. /**
  2993. * The key string used for the single object that exists in the
  2994. * DbTargetGlobal store.
  2995. */
  2996. const DbTargetGlobalKey = 'targetGlobalKey';
  2997. const DbTargetGlobalStore = 'targetGlobal';
  2998. /** Name of the IndexedDb object store. */
  2999. const DbCollectionParentStore = 'collectionParents';
  3000. /** Keys are automatically assigned via the collectionId, parent properties. */
  3001. const DbCollectionParentKeyPath = ['collectionId', 'parent'];
  3002. /** Name of the IndexedDb object store. */
  3003. const DbClientMetadataStore = 'clientMetadata';
  3004. /** Keys are automatically assigned via the clientId properties. */
  3005. const DbClientMetadataKeyPath = 'clientId';
  3006. /** Name of the IndexedDb object store. */
  3007. const DbBundleStore = 'bundles';
  3008. const DbBundleKeyPath = 'bundleId';
  3009. /** Name of the IndexedDb object store. */
  3010. const DbNamedQueryStore = 'namedQueries';
  3011. const DbNamedQueryKeyPath = 'name';
  3012. /** Name of the IndexedDb object store. */
  3013. const DbIndexConfigurationStore = 'indexConfiguration';
  3014. const DbIndexConfigurationKeyPath = 'indexId';
  3015. /**
  3016. * An index that provides access to the index configurations by collection
  3017. * group.
  3018. *
  3019. * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
  3020. * not possible here as the Web client supports concurrent access to
  3021. * persistence via multi-tab.
  3022. */
  3023. const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
  3024. const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
  3025. /** Name of the IndexedDb object store. */
  3026. const DbIndexStateStore = 'indexState';
  3027. const DbIndexStateKeyPath = ['indexId', 'uid'];
  3028. /**
  3029. * An index that provides access to documents in a collection sorted by last
  3030. * update time. Used by the backfiller.
  3031. *
  3032. * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
  3033. * not possible here as the Web client supports concurrent access to
  3034. * persistence via multi-tab.
  3035. */
  3036. const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
  3037. const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
  3038. /** Name of the IndexedDb object store. */
  3039. const DbIndexEntryStore = 'indexEntries';
  3040. const DbIndexEntryKeyPath = [
  3041. 'indexId',
  3042. 'uid',
  3043. 'arrayValue',
  3044. 'directionalValue',
  3045. 'orderedDocumentKey',
  3046. 'documentKey'
  3047. ];
  3048. const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
  3049. const DbIndexEntryDocumentKeyIndexPath = [
  3050. 'indexId',
  3051. 'uid',
  3052. 'orderedDocumentKey'
  3053. ];
  3054. /** Name of the IndexedDb object store. */
  3055. const DbDocumentOverlayStore = 'documentOverlays';
  3056. const DbDocumentOverlayKeyPath = [
  3057. 'userId',
  3058. 'collectionPath',
  3059. 'documentId'
  3060. ];
  3061. const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
  3062. const DbDocumentOverlayCollectionPathOverlayIndexPath = [
  3063. 'userId',
  3064. 'collectionPath',
  3065. 'largestBatchId'
  3066. ];
  3067. const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
  3068. const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
  3069. 'userId',
  3070. 'collectionGroup',
  3071. 'largestBatchId'
  3072. ];
  3073. // Visible for testing
  3074. const V1_STORES = [
  3075. DbMutationQueueStore,
  3076. DbMutationBatchStore,
  3077. DbDocumentMutationStore,
  3078. DbRemoteDocumentStore$1,
  3079. DbTargetStore,
  3080. DbPrimaryClientStore,
  3081. DbTargetGlobalStore,
  3082. DbTargetDocumentStore
  3083. ];
  3084. // Visible for testing
  3085. const V3_STORES = V1_STORES;
  3086. // Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
  3087. const V4_STORES = [...V3_STORES, DbClientMetadataStore];
  3088. const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
  3089. const V8_STORES = [...V6_STORES, DbCollectionParentStore];
  3090. const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
  3091. const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
  3092. const V13_STORES = [
  3093. DbMutationQueueStore,
  3094. DbMutationBatchStore,
  3095. DbDocumentMutationStore,
  3096. DbRemoteDocumentStore,
  3097. DbTargetStore,
  3098. DbPrimaryClientStore,
  3099. DbTargetGlobalStore,
  3100. DbTargetDocumentStore,
  3101. DbClientMetadataStore,
  3102. DbRemoteDocumentGlobalStore,
  3103. DbCollectionParentStore,
  3104. DbBundleStore,
  3105. DbNamedQueryStore,
  3106. DbDocumentOverlayStore
  3107. ];
  3108. const V14_STORES = V13_STORES;
  3109. const V15_STORES = [
  3110. ...V14_STORES,
  3111. DbIndexConfigurationStore,
  3112. DbIndexStateStore,
  3113. DbIndexEntryStore
  3114. ];
  3115. /** Returns the object stores for the provided schema. */
  3116. function getObjectStores(schemaVersion) {
  3117. if (schemaVersion === 15) {
  3118. return V15_STORES;
  3119. }
  3120. else if (schemaVersion === 14) {
  3121. return V14_STORES;
  3122. }
  3123. else if (schemaVersion === 13) {
  3124. return V13_STORES;
  3125. }
  3126. else if (schemaVersion === 12) {
  3127. return V12_STORES;
  3128. }
  3129. else if (schemaVersion === 11) {
  3130. return V11_STORES;
  3131. }
  3132. else {
  3133. fail();
  3134. }
  3135. }
  3136. /**
  3137. * @license
  3138. * Copyright 2020 Google LLC
  3139. *
  3140. * Licensed under the Apache License, Version 2.0 (the "License");
  3141. * you may not use this file except in compliance with the License.
  3142. * You may obtain a copy of the License at
  3143. *
  3144. * http://www.apache.org/licenses/LICENSE-2.0
  3145. *
  3146. * Unless required by applicable law or agreed to in writing, software
  3147. * distributed under the License is distributed on an "AS IS" BASIS,
  3148. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3149. * See the License for the specific language governing permissions and
  3150. * limitations under the License.
  3151. */
  3152. class IndexedDbTransaction extends PersistenceTransaction {
  3153. constructor(simpleDbTransaction, currentSequenceNumber) {
  3154. super();
  3155. this.simpleDbTransaction = simpleDbTransaction;
  3156. this.currentSequenceNumber = currentSequenceNumber;
  3157. }
  3158. }
  3159. function getStore(txn, store) {
  3160. const indexedDbTransaction = debugCast(txn);
  3161. return SimpleDb.getStore(indexedDbTransaction.simpleDbTransaction, store);
  3162. }
  3163. /**
  3164. * @license
  3165. * Copyright 2017 Google LLC
  3166. *
  3167. * Licensed under the Apache License, Version 2.0 (the "License");
  3168. * you may not use this file except in compliance with the License.
  3169. * You may obtain a copy of the License at
  3170. *
  3171. * http://www.apache.org/licenses/LICENSE-2.0
  3172. *
  3173. * Unless required by applicable law or agreed to in writing, software
  3174. * distributed under the License is distributed on an "AS IS" BASIS,
  3175. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3176. * See the License for the specific language governing permissions and
  3177. * limitations under the License.
  3178. */
  3179. function objectSize(obj) {
  3180. let count = 0;
  3181. for (const key in obj) {
  3182. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3183. count++;
  3184. }
  3185. }
  3186. return count;
  3187. }
  3188. function forEach(obj, fn) {
  3189. for (const key in obj) {
  3190. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3191. fn(key, obj[key]);
  3192. }
  3193. }
  3194. }
  3195. function mapToArray(obj, fn) {
  3196. const result = [];
  3197. for (const key in obj) {
  3198. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3199. result.push(fn(obj[key], key, obj));
  3200. }
  3201. }
  3202. return result;
  3203. }
  3204. function isEmpty(obj) {
  3205. for (const key in obj) {
  3206. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3207. return false;
  3208. }
  3209. }
  3210. return true;
  3211. }
  3212. /**
  3213. * @license
  3214. * Copyright 2017 Google LLC
  3215. *
  3216. * Licensed under the Apache License, Version 2.0 (the "License");
  3217. * you may not use this file except in compliance with the License.
  3218. * You may obtain a copy of the License at
  3219. *
  3220. * http://www.apache.org/licenses/LICENSE-2.0
  3221. *
  3222. * Unless required by applicable law or agreed to in writing, software
  3223. * distributed under the License is distributed on an "AS IS" BASIS,
  3224. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3225. * See the License for the specific language governing permissions and
  3226. * limitations under the License.
  3227. */
  3228. // An immutable sorted map implementation, based on a Left-leaning Red-Black
  3229. // tree.
  3230. class SortedMap {
  3231. constructor(comparator, root) {
  3232. this.comparator = comparator;
  3233. this.root = root ? root : LLRBNode.EMPTY;
  3234. }
  3235. // Returns a copy of the map, with the specified key/value added or replaced.
  3236. insert(key, value) {
  3237. return new SortedMap(this.comparator, this.root
  3238. .insert(key, value, this.comparator)
  3239. .copy(null, null, LLRBNode.BLACK, null, null));
  3240. }
  3241. // Returns a copy of the map, with the specified key removed.
  3242. remove(key) {
  3243. return new SortedMap(this.comparator, this.root
  3244. .remove(key, this.comparator)
  3245. .copy(null, null, LLRBNode.BLACK, null, null));
  3246. }
  3247. // Returns the value of the node with the given key, or null.
  3248. get(key) {
  3249. let node = this.root;
  3250. while (!node.isEmpty()) {
  3251. const cmp = this.comparator(key, node.key);
  3252. if (cmp === 0) {
  3253. return node.value;
  3254. }
  3255. else if (cmp < 0) {
  3256. node = node.left;
  3257. }
  3258. else if (cmp > 0) {
  3259. node = node.right;
  3260. }
  3261. }
  3262. return null;
  3263. }
  3264. // Returns the index of the element in this sorted map, or -1 if it doesn't
  3265. // exist.
  3266. indexOf(key) {
  3267. // Number of nodes that were pruned when descending right
  3268. let prunedNodes = 0;
  3269. let node = this.root;
  3270. while (!node.isEmpty()) {
  3271. const cmp = this.comparator(key, node.key);
  3272. if (cmp === 0) {
  3273. return prunedNodes + node.left.size;
  3274. }
  3275. else if (cmp < 0) {
  3276. node = node.left;
  3277. }
  3278. else {
  3279. // Count all nodes left of the node plus the node itself
  3280. prunedNodes += node.left.size + 1;
  3281. node = node.right;
  3282. }
  3283. }
  3284. // Node not found
  3285. return -1;
  3286. }
  3287. isEmpty() {
  3288. return this.root.isEmpty();
  3289. }
  3290. // Returns the total number of nodes in the map.
  3291. get size() {
  3292. return this.root.size;
  3293. }
  3294. // Returns the minimum key in the map.
  3295. minKey() {
  3296. return this.root.minKey();
  3297. }
  3298. // Returns the maximum key in the map.
  3299. maxKey() {
  3300. return this.root.maxKey();
  3301. }
  3302. // Traverses the map in key order and calls the specified action function
  3303. // for each key/value pair. If action returns true, traversal is aborted.
  3304. // Returns the first truthy value returned by action, or the last falsey
  3305. // value returned by action.
  3306. inorderTraversal(action) {
  3307. return this.root.inorderTraversal(action);
  3308. }
  3309. forEach(fn) {
  3310. this.inorderTraversal((k, v) => {
  3311. fn(k, v);
  3312. return false;
  3313. });
  3314. }
  3315. toString() {
  3316. const descriptions = [];
  3317. this.inorderTraversal((k, v) => {
  3318. descriptions.push(`${k}:${v}`);
  3319. return false;
  3320. });
  3321. return `{${descriptions.join(', ')}}`;
  3322. }
  3323. // Traverses the map in reverse key order and calls the specified action
  3324. // function for each key/value pair. If action returns true, traversal is
  3325. // aborted.
  3326. // Returns the first truthy value returned by action, or the last falsey
  3327. // value returned by action.
  3328. reverseTraversal(action) {
  3329. return this.root.reverseTraversal(action);
  3330. }
  3331. // Returns an iterator over the SortedMap.
  3332. getIterator() {
  3333. return new SortedMapIterator(this.root, null, this.comparator, false);
  3334. }
  3335. getIteratorFrom(key) {
  3336. return new SortedMapIterator(this.root, key, this.comparator, false);
  3337. }
  3338. getReverseIterator() {
  3339. return new SortedMapIterator(this.root, null, this.comparator, true);
  3340. }
  3341. getReverseIteratorFrom(key) {
  3342. return new SortedMapIterator(this.root, key, this.comparator, true);
  3343. }
  3344. } // end SortedMap
  3345. // An iterator over an LLRBNode.
  3346. class SortedMapIterator {
  3347. constructor(node, startKey, comparator, isReverse) {
  3348. this.isReverse = isReverse;
  3349. this.nodeStack = [];
  3350. let cmp = 1;
  3351. while (!node.isEmpty()) {
  3352. cmp = startKey ? comparator(node.key, startKey) : 1;
  3353. // flip the comparison if we're going in reverse
  3354. if (startKey && isReverse) {
  3355. cmp *= -1;
  3356. }
  3357. if (cmp < 0) {
  3358. // This node is less than our start key. ignore it
  3359. if (this.isReverse) {
  3360. node = node.left;
  3361. }
  3362. else {
  3363. node = node.right;
  3364. }
  3365. }
  3366. else if (cmp === 0) {
  3367. // This node is exactly equal to our start key. Push it on the stack,
  3368. // but stop iterating;
  3369. this.nodeStack.push(node);
  3370. break;
  3371. }
  3372. else {
  3373. // This node is greater than our start key, add it to the stack and move
  3374. // to the next one
  3375. this.nodeStack.push(node);
  3376. if (this.isReverse) {
  3377. node = node.right;
  3378. }
  3379. else {
  3380. node = node.left;
  3381. }
  3382. }
  3383. }
  3384. }
  3385. getNext() {
  3386. let node = this.nodeStack.pop();
  3387. const result = { key: node.key, value: node.value };
  3388. if (this.isReverse) {
  3389. node = node.left;
  3390. while (!node.isEmpty()) {
  3391. this.nodeStack.push(node);
  3392. node = node.right;
  3393. }
  3394. }
  3395. else {
  3396. node = node.right;
  3397. while (!node.isEmpty()) {
  3398. this.nodeStack.push(node);
  3399. node = node.left;
  3400. }
  3401. }
  3402. return result;
  3403. }
  3404. hasNext() {
  3405. return this.nodeStack.length > 0;
  3406. }
  3407. peek() {
  3408. if (this.nodeStack.length === 0) {
  3409. return null;
  3410. }
  3411. const node = this.nodeStack[this.nodeStack.length - 1];
  3412. return { key: node.key, value: node.value };
  3413. }
  3414. } // end SortedMapIterator
  3415. // Represents a node in a Left-leaning Red-Black tree.
  3416. class LLRBNode {
  3417. constructor(key, value, color, left, right) {
  3418. this.key = key;
  3419. this.value = value;
  3420. this.color = color != null ? color : LLRBNode.RED;
  3421. this.left = left != null ? left : LLRBNode.EMPTY;
  3422. this.right = right != null ? right : LLRBNode.EMPTY;
  3423. this.size = this.left.size + 1 + this.right.size;
  3424. }
  3425. // Returns a copy of the current node, optionally replacing pieces of it.
  3426. copy(key, value, color, left, right) {
  3427. 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);
  3428. }
  3429. isEmpty() {
  3430. return false;
  3431. }
  3432. // Traverses the tree in key order and calls the specified action function
  3433. // for each node. If action returns true, traversal is aborted.
  3434. // Returns the first truthy value returned by action, or the last falsey
  3435. // value returned by action.
  3436. inorderTraversal(action) {
  3437. return (this.left.inorderTraversal(action) ||
  3438. action(this.key, this.value) ||
  3439. this.right.inorderTraversal(action));
  3440. }
  3441. // Traverses the tree in reverse key order and calls the specified action
  3442. // function for each node. If action returns true, traversal is aborted.
  3443. // Returns the first truthy value returned by action, or the last falsey
  3444. // value returned by action.
  3445. reverseTraversal(action) {
  3446. return (this.right.reverseTraversal(action) ||
  3447. action(this.key, this.value) ||
  3448. this.left.reverseTraversal(action));
  3449. }
  3450. // Returns the minimum node in the tree.
  3451. min() {
  3452. if (this.left.isEmpty()) {
  3453. return this;
  3454. }
  3455. else {
  3456. return this.left.min();
  3457. }
  3458. }
  3459. // Returns the maximum key in the tree.
  3460. minKey() {
  3461. return this.min().key;
  3462. }
  3463. // Returns the maximum key in the tree.
  3464. maxKey() {
  3465. if (this.right.isEmpty()) {
  3466. return this.key;
  3467. }
  3468. else {
  3469. return this.right.maxKey();
  3470. }
  3471. }
  3472. // Returns new tree, with the key/value added.
  3473. insert(key, value, comparator) {
  3474. let n = this;
  3475. const cmp = comparator(key, n.key);
  3476. if (cmp < 0) {
  3477. n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
  3478. }
  3479. else if (cmp === 0) {
  3480. n = n.copy(null, value, null, null, null);
  3481. }
  3482. else {
  3483. n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
  3484. }
  3485. return n.fixUp();
  3486. }
  3487. removeMin() {
  3488. if (this.left.isEmpty()) {
  3489. return LLRBNode.EMPTY;
  3490. }
  3491. let n = this;
  3492. if (!n.left.isRed() && !n.left.left.isRed()) {
  3493. n = n.moveRedLeft();
  3494. }
  3495. n = n.copy(null, null, null, n.left.removeMin(), null);
  3496. return n.fixUp();
  3497. }
  3498. // Returns new tree, with the specified item removed.
  3499. remove(key, comparator) {
  3500. let smallest;
  3501. let n = this;
  3502. if (comparator(key, n.key) < 0) {
  3503. if (!n.left.isEmpty() && !n.left.isRed() && !n.left.left.isRed()) {
  3504. n = n.moveRedLeft();
  3505. }
  3506. n = n.copy(null, null, null, n.left.remove(key, comparator), null);
  3507. }
  3508. else {
  3509. if (n.left.isRed()) {
  3510. n = n.rotateRight();
  3511. }
  3512. if (!n.right.isEmpty() && !n.right.isRed() && !n.right.left.isRed()) {
  3513. n = n.moveRedRight();
  3514. }
  3515. if (comparator(key, n.key) === 0) {
  3516. if (n.right.isEmpty()) {
  3517. return LLRBNode.EMPTY;
  3518. }
  3519. else {
  3520. smallest = n.right.min();
  3521. n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin());
  3522. }
  3523. }
  3524. n = n.copy(null, null, null, null, n.right.remove(key, comparator));
  3525. }
  3526. return n.fixUp();
  3527. }
  3528. isRed() {
  3529. return this.color;
  3530. }
  3531. // Returns new tree after performing any needed rotations.
  3532. fixUp() {
  3533. let n = this;
  3534. if (n.right.isRed() && !n.left.isRed()) {
  3535. n = n.rotateLeft();
  3536. }
  3537. if (n.left.isRed() && n.left.left.isRed()) {
  3538. n = n.rotateRight();
  3539. }
  3540. if (n.left.isRed() && n.right.isRed()) {
  3541. n = n.colorFlip();
  3542. }
  3543. return n;
  3544. }
  3545. moveRedLeft() {
  3546. let n = this.colorFlip();
  3547. if (n.right.left.isRed()) {
  3548. n = n.copy(null, null, null, null, n.right.rotateRight());
  3549. n = n.rotateLeft();
  3550. n = n.colorFlip();
  3551. }
  3552. return n;
  3553. }
  3554. moveRedRight() {
  3555. let n = this.colorFlip();
  3556. if (n.left.left.isRed()) {
  3557. n = n.rotateRight();
  3558. n = n.colorFlip();
  3559. }
  3560. return n;
  3561. }
  3562. rotateLeft() {
  3563. const nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
  3564. return this.right.copy(null, null, this.color, nl, null);
  3565. }
  3566. rotateRight() {
  3567. const nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
  3568. return this.left.copy(null, null, this.color, null, nr);
  3569. }
  3570. colorFlip() {
  3571. const left = this.left.copy(null, null, !this.left.color, null, null);
  3572. const right = this.right.copy(null, null, !this.right.color, null, null);
  3573. return this.copy(null, null, !this.color, left, right);
  3574. }
  3575. // For testing.
  3576. checkMaxDepth() {
  3577. const blackDepth = this.check();
  3578. if (Math.pow(2.0, blackDepth) <= this.size + 1) {
  3579. return true;
  3580. }
  3581. else {
  3582. return false;
  3583. }
  3584. }
  3585. // In a balanced RB tree, the black-depth (number of black nodes) from root to
  3586. // leaves is equal on both sides. This function verifies that or asserts.
  3587. check() {
  3588. if (this.isRed() && this.left.isRed()) {
  3589. throw fail();
  3590. }
  3591. if (this.right.isRed()) {
  3592. throw fail();
  3593. }
  3594. const blackDepth = this.left.check();
  3595. if (blackDepth !== this.right.check()) {
  3596. throw fail();
  3597. }
  3598. else {
  3599. return blackDepth + (this.isRed() ? 0 : 1);
  3600. }
  3601. }
  3602. } // end LLRBNode
  3603. // Empty node is shared between all LLRB trees.
  3604. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  3605. LLRBNode.EMPTY = null;
  3606. LLRBNode.RED = true;
  3607. LLRBNode.BLACK = false;
  3608. // Represents an empty node (a leaf node in the Red-Black Tree).
  3609. class LLRBEmptyNode {
  3610. constructor() {
  3611. this.size = 0;
  3612. }
  3613. get key() {
  3614. throw fail();
  3615. }
  3616. get value() {
  3617. throw fail();
  3618. }
  3619. get color() {
  3620. throw fail();
  3621. }
  3622. get left() {
  3623. throw fail();
  3624. }
  3625. get right() {
  3626. throw fail();
  3627. }
  3628. // Returns a copy of the current node.
  3629. copy(key, value, color, left, right) {
  3630. return this;
  3631. }
  3632. // Returns a copy of the tree, with the specified key/value added.
  3633. insert(key, value, comparator) {
  3634. return new LLRBNode(key, value);
  3635. }
  3636. // Returns a copy of the tree, with the specified key removed.
  3637. remove(key, comparator) {
  3638. return this;
  3639. }
  3640. isEmpty() {
  3641. return true;
  3642. }
  3643. inorderTraversal(action) {
  3644. return false;
  3645. }
  3646. reverseTraversal(action) {
  3647. return false;
  3648. }
  3649. minKey() {
  3650. return null;
  3651. }
  3652. maxKey() {
  3653. return null;
  3654. }
  3655. isRed() {
  3656. return false;
  3657. }
  3658. // For testing.
  3659. checkMaxDepth() {
  3660. return true;
  3661. }
  3662. check() {
  3663. return 0;
  3664. }
  3665. } // end LLRBEmptyNode
  3666. LLRBNode.EMPTY = new LLRBEmptyNode();
  3667. /**
  3668. * @license
  3669. * Copyright 2017 Google LLC
  3670. *
  3671. * Licensed under the Apache License, Version 2.0 (the "License");
  3672. * you may not use this file except in compliance with the License.
  3673. * You may obtain a copy of the License at
  3674. *
  3675. * http://www.apache.org/licenses/LICENSE-2.0
  3676. *
  3677. * Unless required by applicable law or agreed to in writing, software
  3678. * distributed under the License is distributed on an "AS IS" BASIS,
  3679. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3680. * See the License for the specific language governing permissions and
  3681. * limitations under the License.
  3682. */
  3683. /**
  3684. * SortedSet is an immutable (copy-on-write) collection that holds elements
  3685. * in order specified by the provided comparator.
  3686. *
  3687. * NOTE: if provided comparator returns 0 for two elements, we consider them to
  3688. * be equal!
  3689. */
  3690. class SortedSet {
  3691. constructor(comparator) {
  3692. this.comparator = comparator;
  3693. this.data = new SortedMap(this.comparator);
  3694. }
  3695. has(elem) {
  3696. return this.data.get(elem) !== null;
  3697. }
  3698. first() {
  3699. return this.data.minKey();
  3700. }
  3701. last() {
  3702. return this.data.maxKey();
  3703. }
  3704. get size() {
  3705. return this.data.size;
  3706. }
  3707. indexOf(elem) {
  3708. return this.data.indexOf(elem);
  3709. }
  3710. /** Iterates elements in order defined by "comparator" */
  3711. forEach(cb) {
  3712. this.data.inorderTraversal((k, v) => {
  3713. cb(k);
  3714. return false;
  3715. });
  3716. }
  3717. /** Iterates over `elem`s such that: range[0] &lt;= elem &lt; range[1]. */
  3718. forEachInRange(range, cb) {
  3719. const iter = this.data.getIteratorFrom(range[0]);
  3720. while (iter.hasNext()) {
  3721. const elem = iter.getNext();
  3722. if (this.comparator(elem.key, range[1]) >= 0) {
  3723. return;
  3724. }
  3725. cb(elem.key);
  3726. }
  3727. }
  3728. /**
  3729. * Iterates over `elem`s such that: start &lt;= elem until false is returned.
  3730. */
  3731. forEachWhile(cb, start) {
  3732. let iter;
  3733. if (start !== undefined) {
  3734. iter = this.data.getIteratorFrom(start);
  3735. }
  3736. else {
  3737. iter = this.data.getIterator();
  3738. }
  3739. while (iter.hasNext()) {
  3740. const elem = iter.getNext();
  3741. const result = cb(elem.key);
  3742. if (!result) {
  3743. return;
  3744. }
  3745. }
  3746. }
  3747. /** Finds the least element greater than or equal to `elem`. */
  3748. firstAfterOrEqual(elem) {
  3749. const iter = this.data.getIteratorFrom(elem);
  3750. return iter.hasNext() ? iter.getNext().key : null;
  3751. }
  3752. getIterator() {
  3753. return new SortedSetIterator(this.data.getIterator());
  3754. }
  3755. getIteratorFrom(key) {
  3756. return new SortedSetIterator(this.data.getIteratorFrom(key));
  3757. }
  3758. /** Inserts or updates an element */
  3759. add(elem) {
  3760. return this.copy(this.data.remove(elem).insert(elem, true));
  3761. }
  3762. /** Deletes an element */
  3763. delete(elem) {
  3764. if (!this.has(elem)) {
  3765. return this;
  3766. }
  3767. return this.copy(this.data.remove(elem));
  3768. }
  3769. isEmpty() {
  3770. return this.data.isEmpty();
  3771. }
  3772. unionWith(other) {
  3773. let result = this;
  3774. // Make sure `result` always refers to the larger one of the two sets.
  3775. if (result.size < other.size) {
  3776. result = other;
  3777. other = this;
  3778. }
  3779. other.forEach(elem => {
  3780. result = result.add(elem);
  3781. });
  3782. return result;
  3783. }
  3784. isEqual(other) {
  3785. if (!(other instanceof SortedSet)) {
  3786. return false;
  3787. }
  3788. if (this.size !== other.size) {
  3789. return false;
  3790. }
  3791. const thisIt = this.data.getIterator();
  3792. const otherIt = other.data.getIterator();
  3793. while (thisIt.hasNext()) {
  3794. const thisElem = thisIt.getNext().key;
  3795. const otherElem = otherIt.getNext().key;
  3796. if (this.comparator(thisElem, otherElem) !== 0) {
  3797. return false;
  3798. }
  3799. }
  3800. return true;
  3801. }
  3802. toArray() {
  3803. const res = [];
  3804. this.forEach(targetId => {
  3805. res.push(targetId);
  3806. });
  3807. return res;
  3808. }
  3809. toString() {
  3810. const result = [];
  3811. this.forEach(elem => result.push(elem));
  3812. return 'SortedSet(' + result.toString() + ')';
  3813. }
  3814. copy(data) {
  3815. const result = new SortedSet(this.comparator);
  3816. result.data = data;
  3817. return result;
  3818. }
  3819. }
  3820. class SortedSetIterator {
  3821. constructor(iter) {
  3822. this.iter = iter;
  3823. }
  3824. getNext() {
  3825. return this.iter.getNext().key;
  3826. }
  3827. hasNext() {
  3828. return this.iter.hasNext();
  3829. }
  3830. }
  3831. /**
  3832. * Compares two sorted sets for equality using their natural ordering. The
  3833. * method computes the intersection and invokes `onAdd` for every element that
  3834. * is in `after` but not `before`. `onRemove` is invoked for every element in
  3835. * `before` but missing from `after`.
  3836. *
  3837. * The method creates a copy of both `before` and `after` and runs in O(n log
  3838. * n), where n is the size of the two lists.
  3839. *
  3840. * @param before - The elements that exist in the original set.
  3841. * @param after - The elements to diff against the original set.
  3842. * @param comparator - The comparator for the elements in before and after.
  3843. * @param onAdd - A function to invoke for every element that is part of `
  3844. * after` but not `before`.
  3845. * @param onRemove - A function to invoke for every element that is part of
  3846. * `before` but not `after`.
  3847. */
  3848. function diffSortedSets(before, after, comparator, onAdd, onRemove) {
  3849. const beforeIt = before.getIterator();
  3850. const afterIt = after.getIterator();
  3851. let beforeValue = advanceIterator(beforeIt);
  3852. let afterValue = advanceIterator(afterIt);
  3853. // Walk through the two sets at the same time, using the ordering defined by
  3854. // `comparator`.
  3855. while (beforeValue || afterValue) {
  3856. let added = false;
  3857. let removed = false;
  3858. if (beforeValue && afterValue) {
  3859. const cmp = comparator(beforeValue, afterValue);
  3860. if (cmp < 0) {
  3861. // The element was removed if the next element in our ordered
  3862. // walkthrough is only in `before`.
  3863. removed = true;
  3864. }
  3865. else if (cmp > 0) {
  3866. // The element was added if the next element in our ordered walkthrough
  3867. // is only in `after`.
  3868. added = true;
  3869. }
  3870. }
  3871. else if (beforeValue != null) {
  3872. removed = true;
  3873. }
  3874. else {
  3875. added = true;
  3876. }
  3877. if (added) {
  3878. onAdd(afterValue);
  3879. afterValue = advanceIterator(afterIt);
  3880. }
  3881. else if (removed) {
  3882. onRemove(beforeValue);
  3883. beforeValue = advanceIterator(beforeIt);
  3884. }
  3885. else {
  3886. beforeValue = advanceIterator(beforeIt);
  3887. afterValue = advanceIterator(afterIt);
  3888. }
  3889. }
  3890. }
  3891. /**
  3892. * Returns the next element from the iterator or `undefined` if none available.
  3893. */
  3894. function advanceIterator(it) {
  3895. return it.hasNext() ? it.getNext() : undefined;
  3896. }
  3897. /**
  3898. * @license
  3899. * Copyright 2020 Google LLC
  3900. *
  3901. * Licensed under the Apache License, Version 2.0 (the "License");
  3902. * you may not use this file except in compliance with the License.
  3903. * You may obtain a copy of the License at
  3904. *
  3905. * http://www.apache.org/licenses/LICENSE-2.0
  3906. *
  3907. * Unless required by applicable law or agreed to in writing, software
  3908. * distributed under the License is distributed on an "AS IS" BASIS,
  3909. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3910. * See the License for the specific language governing permissions and
  3911. * limitations under the License.
  3912. */
  3913. /**
  3914. * Provides a set of fields that can be used to partially patch a document.
  3915. * FieldMask is used in conjunction with ObjectValue.
  3916. * Examples:
  3917. * foo - Overwrites foo entirely with the provided value. If foo is not
  3918. * present in the companion ObjectValue, the field is deleted.
  3919. * foo.bar - Overwrites only the field bar of the object foo.
  3920. * If foo is not an object, foo is replaced with an object
  3921. * containing foo
  3922. */
  3923. class FieldMask {
  3924. constructor(fields) {
  3925. this.fields = fields;
  3926. // TODO(dimond): validation of FieldMask
  3927. // Sort the field mask to support `FieldMask.isEqual()` and assert below.
  3928. fields.sort(FieldPath$1.comparator);
  3929. }
  3930. static empty() {
  3931. return new FieldMask([]);
  3932. }
  3933. /**
  3934. * Returns a new FieldMask object that is the result of adding all the given
  3935. * fields paths to this field mask.
  3936. */
  3937. unionWith(extraFields) {
  3938. let mergedMaskSet = new SortedSet(FieldPath$1.comparator);
  3939. for (const fieldPath of this.fields) {
  3940. mergedMaskSet = mergedMaskSet.add(fieldPath);
  3941. }
  3942. for (const fieldPath of extraFields) {
  3943. mergedMaskSet = mergedMaskSet.add(fieldPath);
  3944. }
  3945. return new FieldMask(mergedMaskSet.toArray());
  3946. }
  3947. /**
  3948. * Verifies that `fieldPath` is included by at least one field in this field
  3949. * mask.
  3950. *
  3951. * This is an O(n) operation, where `n` is the size of the field mask.
  3952. */
  3953. covers(fieldPath) {
  3954. for (const fieldMaskPath of this.fields) {
  3955. if (fieldMaskPath.isPrefixOf(fieldPath)) {
  3956. return true;
  3957. }
  3958. }
  3959. return false;
  3960. }
  3961. isEqual(other) {
  3962. return arrayEquals(this.fields, other.fields, (l, r) => l.isEqual(r));
  3963. }
  3964. }
  3965. /**
  3966. * @license
  3967. * Copyright 2020 Google LLC
  3968. *
  3969. * Licensed under the Apache License, Version 2.0 (the "License");
  3970. * you may not use this file except in compliance with the License.
  3971. * You may obtain a copy of the License at
  3972. *
  3973. * http://www.apache.org/licenses/LICENSE-2.0
  3974. *
  3975. * Unless required by applicable law or agreed to in writing, software
  3976. * distributed under the License is distributed on an "AS IS" BASIS,
  3977. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3978. * See the License for the specific language governing permissions and
  3979. * limitations under the License.
  3980. */
  3981. /** Converts a Base64 encoded string to a binary string. */
  3982. function decodeBase64(encoded) {
  3983. // Note: We used to validate the base64 string here via a regular expression.
  3984. // This was removed to improve the performance of indexing.
  3985. return Buffer.from(encoded, 'base64').toString('binary');
  3986. }
  3987. /** Converts a binary string to a Base64 encoded string. */
  3988. function encodeBase64(raw) {
  3989. return Buffer.from(raw, 'binary').toString('base64');
  3990. }
  3991. /** True if and only if the Base64 conversion functions are available. */
  3992. function isBase64Available() {
  3993. return true;
  3994. }
  3995. /**
  3996. * @license
  3997. * Copyright 2020 Google LLC
  3998. *
  3999. * Licensed under the Apache License, Version 2.0 (the "License");
  4000. * you may not use this file except in compliance with the License.
  4001. * You may obtain a copy of the License at
  4002. *
  4003. * http://www.apache.org/licenses/LICENSE-2.0
  4004. *
  4005. * Unless required by applicable law or agreed to in writing, software
  4006. * distributed under the License is distributed on an "AS IS" BASIS,
  4007. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4008. * See the License for the specific language governing permissions and
  4009. * limitations under the License.
  4010. */
  4011. /**
  4012. * Immutable class that represents a "proto" byte string.
  4013. *
  4014. * Proto byte strings can either be Base64-encoded strings or Uint8Arrays when
  4015. * sent on the wire. This class abstracts away this differentiation by holding
  4016. * the proto byte string in a common class that must be converted into a string
  4017. * before being sent as a proto.
  4018. * @internal
  4019. */
  4020. class ByteString {
  4021. constructor(binaryString) {
  4022. this.binaryString = binaryString;
  4023. }
  4024. static fromBase64String(base64) {
  4025. const binaryString = decodeBase64(base64);
  4026. return new ByteString(binaryString);
  4027. }
  4028. static fromUint8Array(array) {
  4029. // TODO(indexing); Remove the copy of the byte string here as this method
  4030. // is frequently called during indexing.
  4031. const binaryString = binaryStringFromUint8Array(array);
  4032. return new ByteString(binaryString);
  4033. }
  4034. [Symbol.iterator]() {
  4035. let i = 0;
  4036. return {
  4037. next: () => {
  4038. if (i < this.binaryString.length) {
  4039. return { value: this.binaryString.charCodeAt(i++), done: false };
  4040. }
  4041. else {
  4042. return { value: undefined, done: true };
  4043. }
  4044. }
  4045. };
  4046. }
  4047. toBase64() {
  4048. return encodeBase64(this.binaryString);
  4049. }
  4050. toUint8Array() {
  4051. return uint8ArrayFromBinaryString(this.binaryString);
  4052. }
  4053. approximateByteSize() {
  4054. return this.binaryString.length * 2;
  4055. }
  4056. compareTo(other) {
  4057. return primitiveComparator(this.binaryString, other.binaryString);
  4058. }
  4059. isEqual(other) {
  4060. return this.binaryString === other.binaryString;
  4061. }
  4062. }
  4063. ByteString.EMPTY_BYTE_STRING = new ByteString('');
  4064. /**
  4065. * Helper function to convert an Uint8array to a binary string.
  4066. */
  4067. function binaryStringFromUint8Array(array) {
  4068. let binaryString = '';
  4069. for (let i = 0; i < array.length; ++i) {
  4070. binaryString += String.fromCharCode(array[i]);
  4071. }
  4072. return binaryString;
  4073. }
  4074. /**
  4075. * Helper function to convert a binary string to an Uint8Array.
  4076. */
  4077. function uint8ArrayFromBinaryString(binaryString) {
  4078. const buffer = new Uint8Array(binaryString.length);
  4079. for (let i = 0; i < binaryString.length; i++) {
  4080. buffer[i] = binaryString.charCodeAt(i);
  4081. }
  4082. return buffer;
  4083. }
  4084. /**
  4085. * @license
  4086. * Copyright 2020 Google LLC
  4087. *
  4088. * Licensed under the Apache License, Version 2.0 (the "License");
  4089. * you may not use this file except in compliance with the License.
  4090. * You may obtain a copy of the License at
  4091. *
  4092. * http://www.apache.org/licenses/LICENSE-2.0
  4093. *
  4094. * Unless required by applicable law or agreed to in writing, software
  4095. * distributed under the License is distributed on an "AS IS" BASIS,
  4096. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4097. * See the License for the specific language governing permissions and
  4098. * limitations under the License.
  4099. */
  4100. // A RegExp matching ISO 8601 UTC timestamps with optional fraction.
  4101. const ISO_TIMESTAMP_REG_EXP = new RegExp(/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.(\d+))?Z$/);
  4102. /**
  4103. * Converts the possible Proto values for a timestamp value into a "seconds and
  4104. * nanos" representation.
  4105. */
  4106. function normalizeTimestamp(date) {
  4107. hardAssert(!!date);
  4108. // The json interface (for the browser) will return an iso timestamp string,
  4109. // while the proto js library (for node) will return a
  4110. // google.protobuf.Timestamp instance.
  4111. if (typeof date === 'string') {
  4112. // The date string can have higher precision (nanos) than the Date class
  4113. // (millis), so we do some custom parsing here.
  4114. // Parse the nanos right out of the string.
  4115. let nanos = 0;
  4116. const fraction = ISO_TIMESTAMP_REG_EXP.exec(date);
  4117. hardAssert(!!fraction);
  4118. if (fraction[1]) {
  4119. // Pad the fraction out to 9 digits (nanos).
  4120. let nanoStr = fraction[1];
  4121. nanoStr = (nanoStr + '000000000').substr(0, 9);
  4122. nanos = Number(nanoStr);
  4123. }
  4124. // Parse the date to get the seconds.
  4125. const parsedDate = new Date(date);
  4126. const seconds = Math.floor(parsedDate.getTime() / 1000);
  4127. return { seconds, nanos };
  4128. }
  4129. else {
  4130. // TODO(b/37282237): Use strings for Proto3 timestamps
  4131. // assert(!this.options.useProto3Json,
  4132. // 'The timestamp instance format requires Proto JS.');
  4133. const seconds = normalizeNumber(date.seconds);
  4134. const nanos = normalizeNumber(date.nanos);
  4135. return { seconds, nanos };
  4136. }
  4137. }
  4138. /**
  4139. * Converts the possible Proto types for numbers into a JavaScript number.
  4140. * Returns 0 if the value is not numeric.
  4141. */
  4142. function normalizeNumber(value) {
  4143. // TODO(bjornick): Handle int64 greater than 53 bits.
  4144. if (typeof value === 'number') {
  4145. return value;
  4146. }
  4147. else if (typeof value === 'string') {
  4148. return Number(value);
  4149. }
  4150. else {
  4151. return 0;
  4152. }
  4153. }
  4154. /** Converts the possible Proto types for Blobs into a ByteString. */
  4155. function normalizeByteString(blob) {
  4156. if (typeof blob === 'string') {
  4157. return ByteString.fromBase64String(blob);
  4158. }
  4159. else {
  4160. return ByteString.fromUint8Array(blob);
  4161. }
  4162. }
  4163. /**
  4164. * @license
  4165. * Copyright 2020 Google LLC
  4166. *
  4167. * Licensed under the Apache License, Version 2.0 (the "License");
  4168. * you may not use this file except in compliance with the License.
  4169. * You may obtain a copy of the License at
  4170. *
  4171. * http://www.apache.org/licenses/LICENSE-2.0
  4172. *
  4173. * Unless required by applicable law or agreed to in writing, software
  4174. * distributed under the License is distributed on an "AS IS" BASIS,
  4175. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4176. * See the License for the specific language governing permissions and
  4177. * limitations under the License.
  4178. */
  4179. /**
  4180. * Represents a locally-applied ServerTimestamp.
  4181. *
  4182. * Server Timestamps are backed by MapValues that contain an internal field
  4183. * `__type__` with a value of `server_timestamp`. The previous value and local
  4184. * write time are stored in its `__previous_value__` and `__local_write_time__`
  4185. * fields respectively.
  4186. *
  4187. * Notes:
  4188. * - ServerTimestampValue instances are created as the result of applying a
  4189. * transform. They can only exist in the local view of a document. Therefore
  4190. * they do not need to be parsed or serialized.
  4191. * - When evaluated locally (e.g. for snapshot.data()), they by default
  4192. * evaluate to `null`. This behavior can be configured by passing custom
  4193. * FieldValueOptions to value().
  4194. * - With respect to other ServerTimestampValues, they sort by their
  4195. * localWriteTime.
  4196. */
  4197. const SERVER_TIMESTAMP_SENTINEL = 'server_timestamp';
  4198. const TYPE_KEY = '__type__';
  4199. const PREVIOUS_VALUE_KEY = '__previous_value__';
  4200. const LOCAL_WRITE_TIME_KEY = '__local_write_time__';
  4201. function isServerTimestamp(value) {
  4202. var _a, _b;
  4203. 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;
  4204. return type === SERVER_TIMESTAMP_SENTINEL;
  4205. }
  4206. /**
  4207. * Creates a new ServerTimestamp proto value (using the internal format).
  4208. */
  4209. function serverTimestamp$1(localWriteTime, previousValue) {
  4210. const mapValue = {
  4211. fields: {
  4212. [TYPE_KEY]: {
  4213. stringValue: SERVER_TIMESTAMP_SENTINEL
  4214. },
  4215. [LOCAL_WRITE_TIME_KEY]: {
  4216. timestampValue: {
  4217. seconds: localWriteTime.seconds,
  4218. nanos: localWriteTime.nanoseconds
  4219. }
  4220. }
  4221. }
  4222. };
  4223. // We should avoid storing deeply nested server timestamp map values
  4224. // because we never use the intermediate "previous values".
  4225. // For example:
  4226. // previous: 42L, add: t1, result: t1 -> 42L
  4227. // previous: t1, add: t2, result: t2 -> 42L (NOT t2 -> t1 -> 42L)
  4228. // previous: t2, add: t3, result: t3 -> 42L (NOT t3 -> t2 -> t1 -> 42L)
  4229. // `getPreviousValue` recursively traverses server timestamps to find the
  4230. // least recent Value.
  4231. if (previousValue && isServerTimestamp(previousValue)) {
  4232. previousValue = getPreviousValue(previousValue);
  4233. }
  4234. if (previousValue) {
  4235. mapValue.fields[PREVIOUS_VALUE_KEY] = previousValue;
  4236. }
  4237. return { mapValue };
  4238. }
  4239. /**
  4240. * Returns the value of the field before this ServerTimestamp was set.
  4241. *
  4242. * Preserving the previous values allows the user to display the last resoled
  4243. * value until the backend responds with the timestamp.
  4244. */
  4245. function getPreviousValue(value) {
  4246. const previousValue = value.mapValue.fields[PREVIOUS_VALUE_KEY];
  4247. if (isServerTimestamp(previousValue)) {
  4248. return getPreviousValue(previousValue);
  4249. }
  4250. return previousValue;
  4251. }
  4252. /**
  4253. * Returns the local time at which this timestamp was first set.
  4254. */
  4255. function getLocalWriteTime(value) {
  4256. const localWriteTime = normalizeTimestamp(value.mapValue.fields[LOCAL_WRITE_TIME_KEY].timestampValue);
  4257. return new Timestamp(localWriteTime.seconds, localWriteTime.nanos);
  4258. }
  4259. /**
  4260. * @license
  4261. * Copyright 2017 Google LLC
  4262. *
  4263. * Licensed under the Apache License, Version 2.0 (the "License");
  4264. * you may not use this file except in compliance with the License.
  4265. * You may obtain a copy of the License at
  4266. *
  4267. * http://www.apache.org/licenses/LICENSE-2.0
  4268. *
  4269. * Unless required by applicable law or agreed to in writing, software
  4270. * distributed under the License is distributed on an "AS IS" BASIS,
  4271. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4272. * See the License for the specific language governing permissions and
  4273. * limitations under the License.
  4274. */
  4275. class DatabaseInfo {
  4276. /**
  4277. * Constructs a DatabaseInfo using the provided host, databaseId and
  4278. * persistenceKey.
  4279. *
  4280. * @param databaseId - The database to use.
  4281. * @param appId - The Firebase App Id.
  4282. * @param persistenceKey - A unique identifier for this Firestore's local
  4283. * storage (used in conjunction with the databaseId).
  4284. * @param host - The Firestore backend host to connect to.
  4285. * @param ssl - Whether to use SSL when connecting.
  4286. * @param forceLongPolling - Whether to use the forceLongPolling option
  4287. * when using WebChannel as the network transport.
  4288. * @param autoDetectLongPolling - Whether to use the detectBufferingProxy
  4289. * option when using WebChannel as the network transport.
  4290. * @param longPollingOptions Options that configure long-polling.
  4291. * @param useFetchStreams Whether to use the Fetch API instead of
  4292. * XMLHTTPRequest
  4293. */
  4294. constructor(databaseId, appId, persistenceKey, host, ssl, forceLongPolling, autoDetectLongPolling, longPollingOptions, useFetchStreams) {
  4295. this.databaseId = databaseId;
  4296. this.appId = appId;
  4297. this.persistenceKey = persistenceKey;
  4298. this.host = host;
  4299. this.ssl = ssl;
  4300. this.forceLongPolling = forceLongPolling;
  4301. this.autoDetectLongPolling = autoDetectLongPolling;
  4302. this.longPollingOptions = longPollingOptions;
  4303. this.useFetchStreams = useFetchStreams;
  4304. }
  4305. }
  4306. /** The default database name for a project. */
  4307. const DEFAULT_DATABASE_NAME = '(default)';
  4308. /**
  4309. * Represents the database ID a Firestore client is associated with.
  4310. * @internal
  4311. */
  4312. class DatabaseId {
  4313. constructor(projectId, database) {
  4314. this.projectId = projectId;
  4315. this.database = database ? database : DEFAULT_DATABASE_NAME;
  4316. }
  4317. static empty() {
  4318. return new DatabaseId('', '');
  4319. }
  4320. get isDefaultDatabase() {
  4321. return this.database === DEFAULT_DATABASE_NAME;
  4322. }
  4323. isEqual(other) {
  4324. return (other instanceof DatabaseId &&
  4325. other.projectId === this.projectId &&
  4326. other.database === this.database);
  4327. }
  4328. }
  4329. function databaseIdFromApp(app, database) {
  4330. if (!Object.prototype.hasOwnProperty.apply(app.options, ['projectId'])) {
  4331. throw new FirestoreError(Code.INVALID_ARGUMENT, '"projectId" not provided in firebase.initializeApp.');
  4332. }
  4333. return new DatabaseId(app.options.projectId, database);
  4334. }
  4335. /**
  4336. * @license
  4337. * Copyright 2017 Google LLC
  4338. *
  4339. * Licensed under the Apache License, Version 2.0 (the "License");
  4340. * you may not use this file except in compliance with the License.
  4341. * You may obtain a copy of the License at
  4342. *
  4343. * http://www.apache.org/licenses/LICENSE-2.0
  4344. *
  4345. * Unless required by applicable law or agreed to in writing, software
  4346. * distributed under the License is distributed on an "AS IS" BASIS,
  4347. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4348. * See the License for the specific language governing permissions and
  4349. * limitations under the License.
  4350. */
  4351. /** Sentinel value that sorts before any Mutation Batch ID. */
  4352. const BATCHID_UNKNOWN = -1;
  4353. /**
  4354. * Returns whether a variable is either undefined or null.
  4355. */
  4356. function isNullOrUndefined(value) {
  4357. return value === null || value === undefined;
  4358. }
  4359. /** Returns whether the value represents -0. */
  4360. function isNegativeZero(value) {
  4361. // Detect if the value is -0.0. Based on polyfill from
  4362. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
  4363. return value === 0 && 1 / value === 1 / -0;
  4364. }
  4365. /**
  4366. * Returns whether a value is an integer and in the safe integer range
  4367. * @param value - The value to test for being an integer and in the safe range
  4368. */
  4369. function isSafeInteger(value) {
  4370. return (typeof value === 'number' &&
  4371. Number.isInteger(value) &&
  4372. !isNegativeZero(value) &&
  4373. value <= Number.MAX_SAFE_INTEGER &&
  4374. value >= Number.MIN_SAFE_INTEGER);
  4375. }
  4376. /**
  4377. * @license
  4378. * Copyright 2020 Google LLC
  4379. *
  4380. * Licensed under the Apache License, Version 2.0 (the "License");
  4381. * you may not use this file except in compliance with the License.
  4382. * You may obtain a copy of the License at
  4383. *
  4384. * http://www.apache.org/licenses/LICENSE-2.0
  4385. *
  4386. * Unless required by applicable law or agreed to in writing, software
  4387. * distributed under the License is distributed on an "AS IS" BASIS,
  4388. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4389. * See the License for the specific language governing permissions and
  4390. * limitations under the License.
  4391. */
  4392. const MAX_VALUE_TYPE = '__max__';
  4393. const MAX_VALUE = {
  4394. mapValue: {
  4395. fields: {
  4396. '__type__': { stringValue: MAX_VALUE_TYPE }
  4397. }
  4398. }
  4399. };
  4400. const MIN_VALUE = {
  4401. nullValue: 'NULL_VALUE'
  4402. };
  4403. /** Extracts the backend's type order for the provided value. */
  4404. function typeOrder(value) {
  4405. if ('nullValue' in value) {
  4406. return 0 /* TypeOrder.NullValue */;
  4407. }
  4408. else if ('booleanValue' in value) {
  4409. return 1 /* TypeOrder.BooleanValue */;
  4410. }
  4411. else if ('integerValue' in value || 'doubleValue' in value) {
  4412. return 2 /* TypeOrder.NumberValue */;
  4413. }
  4414. else if ('timestampValue' in value) {
  4415. return 3 /* TypeOrder.TimestampValue */;
  4416. }
  4417. else if ('stringValue' in value) {
  4418. return 5 /* TypeOrder.StringValue */;
  4419. }
  4420. else if ('bytesValue' in value) {
  4421. return 6 /* TypeOrder.BlobValue */;
  4422. }
  4423. else if ('referenceValue' in value) {
  4424. return 7 /* TypeOrder.RefValue */;
  4425. }
  4426. else if ('geoPointValue' in value) {
  4427. return 8 /* TypeOrder.GeoPointValue */;
  4428. }
  4429. else if ('arrayValue' in value) {
  4430. return 9 /* TypeOrder.ArrayValue */;
  4431. }
  4432. else if ('mapValue' in value) {
  4433. if (isServerTimestamp(value)) {
  4434. return 4 /* TypeOrder.ServerTimestampValue */;
  4435. }
  4436. else if (isMaxValue(value)) {
  4437. return 9007199254740991 /* TypeOrder.MaxValue */;
  4438. }
  4439. return 10 /* TypeOrder.ObjectValue */;
  4440. }
  4441. else {
  4442. return fail();
  4443. }
  4444. }
  4445. /** Tests `left` and `right` for equality based on the backend semantics. */
  4446. function valueEquals(left, right) {
  4447. if (left === right) {
  4448. return true;
  4449. }
  4450. const leftType = typeOrder(left);
  4451. const rightType = typeOrder(right);
  4452. if (leftType !== rightType) {
  4453. return false;
  4454. }
  4455. switch (leftType) {
  4456. case 0 /* TypeOrder.NullValue */:
  4457. return true;
  4458. case 1 /* TypeOrder.BooleanValue */:
  4459. return left.booleanValue === right.booleanValue;
  4460. case 4 /* TypeOrder.ServerTimestampValue */:
  4461. return getLocalWriteTime(left).isEqual(getLocalWriteTime(right));
  4462. case 3 /* TypeOrder.TimestampValue */:
  4463. return timestampEquals(left, right);
  4464. case 5 /* TypeOrder.StringValue */:
  4465. return left.stringValue === right.stringValue;
  4466. case 6 /* TypeOrder.BlobValue */:
  4467. return blobEquals(left, right);
  4468. case 7 /* TypeOrder.RefValue */:
  4469. return left.referenceValue === right.referenceValue;
  4470. case 8 /* TypeOrder.GeoPointValue */:
  4471. return geoPointEquals(left, right);
  4472. case 2 /* TypeOrder.NumberValue */:
  4473. return numberEquals(left, right);
  4474. case 9 /* TypeOrder.ArrayValue */:
  4475. return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
  4476. case 10 /* TypeOrder.ObjectValue */:
  4477. return objectEquals(left, right);
  4478. case 9007199254740991 /* TypeOrder.MaxValue */:
  4479. return true;
  4480. default:
  4481. return fail();
  4482. }
  4483. }
  4484. function timestampEquals(left, right) {
  4485. if (typeof left.timestampValue === 'string' &&
  4486. typeof right.timestampValue === 'string' &&
  4487. left.timestampValue.length === right.timestampValue.length) {
  4488. // Use string equality for ISO 8601 timestamps
  4489. return left.timestampValue === right.timestampValue;
  4490. }
  4491. const leftTimestamp = normalizeTimestamp(left.timestampValue);
  4492. const rightTimestamp = normalizeTimestamp(right.timestampValue);
  4493. return (leftTimestamp.seconds === rightTimestamp.seconds &&
  4494. leftTimestamp.nanos === rightTimestamp.nanos);
  4495. }
  4496. function geoPointEquals(left, right) {
  4497. return (normalizeNumber(left.geoPointValue.latitude) ===
  4498. normalizeNumber(right.geoPointValue.latitude) &&
  4499. normalizeNumber(left.geoPointValue.longitude) ===
  4500. normalizeNumber(right.geoPointValue.longitude));
  4501. }
  4502. function blobEquals(left, right) {
  4503. return normalizeByteString(left.bytesValue).isEqual(normalizeByteString(right.bytesValue));
  4504. }
  4505. function numberEquals(left, right) {
  4506. if ('integerValue' in left && 'integerValue' in right) {
  4507. return (normalizeNumber(left.integerValue) === normalizeNumber(right.integerValue));
  4508. }
  4509. else if ('doubleValue' in left && 'doubleValue' in right) {
  4510. const n1 = normalizeNumber(left.doubleValue);
  4511. const n2 = normalizeNumber(right.doubleValue);
  4512. if (n1 === n2) {
  4513. return isNegativeZero(n1) === isNegativeZero(n2);
  4514. }
  4515. else {
  4516. return isNaN(n1) && isNaN(n2);
  4517. }
  4518. }
  4519. return false;
  4520. }
  4521. function objectEquals(left, right) {
  4522. const leftMap = left.mapValue.fields || {};
  4523. const rightMap = right.mapValue.fields || {};
  4524. if (objectSize(leftMap) !== objectSize(rightMap)) {
  4525. return false;
  4526. }
  4527. for (const key in leftMap) {
  4528. if (leftMap.hasOwnProperty(key)) {
  4529. if (rightMap[key] === undefined ||
  4530. !valueEquals(leftMap[key], rightMap[key])) {
  4531. return false;
  4532. }
  4533. }
  4534. }
  4535. return true;
  4536. }
  4537. /** Returns true if the ArrayValue contains the specified element. */
  4538. function arrayValueContains(haystack, needle) {
  4539. return ((haystack.values || []).find(v => valueEquals(v, needle)) !== undefined);
  4540. }
  4541. function valueCompare(left, right) {
  4542. if (left === right) {
  4543. return 0;
  4544. }
  4545. const leftType = typeOrder(left);
  4546. const rightType = typeOrder(right);
  4547. if (leftType !== rightType) {
  4548. return primitiveComparator(leftType, rightType);
  4549. }
  4550. switch (leftType) {
  4551. case 0 /* TypeOrder.NullValue */:
  4552. case 9007199254740991 /* TypeOrder.MaxValue */:
  4553. return 0;
  4554. case 1 /* TypeOrder.BooleanValue */:
  4555. return primitiveComparator(left.booleanValue, right.booleanValue);
  4556. case 2 /* TypeOrder.NumberValue */:
  4557. return compareNumbers(left, right);
  4558. case 3 /* TypeOrder.TimestampValue */:
  4559. return compareTimestamps(left.timestampValue, right.timestampValue);
  4560. case 4 /* TypeOrder.ServerTimestampValue */:
  4561. return compareTimestamps(getLocalWriteTime(left), getLocalWriteTime(right));
  4562. case 5 /* TypeOrder.StringValue */:
  4563. return primitiveComparator(left.stringValue, right.stringValue);
  4564. case 6 /* TypeOrder.BlobValue */:
  4565. return compareBlobs(left.bytesValue, right.bytesValue);
  4566. case 7 /* TypeOrder.RefValue */:
  4567. return compareReferences(left.referenceValue, right.referenceValue);
  4568. case 8 /* TypeOrder.GeoPointValue */:
  4569. return compareGeoPoints(left.geoPointValue, right.geoPointValue);
  4570. case 9 /* TypeOrder.ArrayValue */:
  4571. return compareArrays(left.arrayValue, right.arrayValue);
  4572. case 10 /* TypeOrder.ObjectValue */:
  4573. return compareMaps(left.mapValue, right.mapValue);
  4574. default:
  4575. throw fail();
  4576. }
  4577. }
  4578. function compareNumbers(left, right) {
  4579. const leftNumber = normalizeNumber(left.integerValue || left.doubleValue);
  4580. const rightNumber = normalizeNumber(right.integerValue || right.doubleValue);
  4581. if (leftNumber < rightNumber) {
  4582. return -1;
  4583. }
  4584. else if (leftNumber > rightNumber) {
  4585. return 1;
  4586. }
  4587. else if (leftNumber === rightNumber) {
  4588. return 0;
  4589. }
  4590. else {
  4591. // one or both are NaN.
  4592. if (isNaN(leftNumber)) {
  4593. return isNaN(rightNumber) ? 0 : -1;
  4594. }
  4595. else {
  4596. return 1;
  4597. }
  4598. }
  4599. }
  4600. function compareTimestamps(left, right) {
  4601. if (typeof left === 'string' &&
  4602. typeof right === 'string' &&
  4603. left.length === right.length) {
  4604. return primitiveComparator(left, right);
  4605. }
  4606. const leftTimestamp = normalizeTimestamp(left);
  4607. const rightTimestamp = normalizeTimestamp(right);
  4608. const comparison = primitiveComparator(leftTimestamp.seconds, rightTimestamp.seconds);
  4609. if (comparison !== 0) {
  4610. return comparison;
  4611. }
  4612. return primitiveComparator(leftTimestamp.nanos, rightTimestamp.nanos);
  4613. }
  4614. function compareReferences(leftPath, rightPath) {
  4615. const leftSegments = leftPath.split('/');
  4616. const rightSegments = rightPath.split('/');
  4617. for (let i = 0; i < leftSegments.length && i < rightSegments.length; i++) {
  4618. const comparison = primitiveComparator(leftSegments[i], rightSegments[i]);
  4619. if (comparison !== 0) {
  4620. return comparison;
  4621. }
  4622. }
  4623. return primitiveComparator(leftSegments.length, rightSegments.length);
  4624. }
  4625. function compareGeoPoints(left, right) {
  4626. const comparison = primitiveComparator(normalizeNumber(left.latitude), normalizeNumber(right.latitude));
  4627. if (comparison !== 0) {
  4628. return comparison;
  4629. }
  4630. return primitiveComparator(normalizeNumber(left.longitude), normalizeNumber(right.longitude));
  4631. }
  4632. function compareBlobs(left, right) {
  4633. const leftBytes = normalizeByteString(left);
  4634. const rightBytes = normalizeByteString(right);
  4635. return leftBytes.compareTo(rightBytes);
  4636. }
  4637. function compareArrays(left, right) {
  4638. const leftArray = left.values || [];
  4639. const rightArray = right.values || [];
  4640. for (let i = 0; i < leftArray.length && i < rightArray.length; ++i) {
  4641. const compare = valueCompare(leftArray[i], rightArray[i]);
  4642. if (compare) {
  4643. return compare;
  4644. }
  4645. }
  4646. return primitiveComparator(leftArray.length, rightArray.length);
  4647. }
  4648. function compareMaps(left, right) {
  4649. if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) {
  4650. return 0;
  4651. }
  4652. else if (left === MAX_VALUE.mapValue) {
  4653. return 1;
  4654. }
  4655. else if (right === MAX_VALUE.mapValue) {
  4656. return -1;
  4657. }
  4658. const leftMap = left.fields || {};
  4659. const leftKeys = Object.keys(leftMap);
  4660. const rightMap = right.fields || {};
  4661. const rightKeys = Object.keys(rightMap);
  4662. // Even though MapValues are likely sorted correctly based on their insertion
  4663. // order (e.g. when received from the backend), local modifications can bring
  4664. // elements out of order. We need to re-sort the elements to ensure that
  4665. // canonical IDs are independent of insertion order.
  4666. leftKeys.sort();
  4667. rightKeys.sort();
  4668. for (let i = 0; i < leftKeys.length && i < rightKeys.length; ++i) {
  4669. const keyCompare = primitiveComparator(leftKeys[i], rightKeys[i]);
  4670. if (keyCompare !== 0) {
  4671. return keyCompare;
  4672. }
  4673. const compare = valueCompare(leftMap[leftKeys[i]], rightMap[rightKeys[i]]);
  4674. if (compare !== 0) {
  4675. return compare;
  4676. }
  4677. }
  4678. return primitiveComparator(leftKeys.length, rightKeys.length);
  4679. }
  4680. /**
  4681. * Generates the canonical ID for the provided field value (as used in Target
  4682. * serialization).
  4683. */
  4684. function canonicalId(value) {
  4685. return canonifyValue(value);
  4686. }
  4687. function canonifyValue(value) {
  4688. if ('nullValue' in value) {
  4689. return 'null';
  4690. }
  4691. else if ('booleanValue' in value) {
  4692. return '' + value.booleanValue;
  4693. }
  4694. else if ('integerValue' in value) {
  4695. return '' + value.integerValue;
  4696. }
  4697. else if ('doubleValue' in value) {
  4698. return '' + value.doubleValue;
  4699. }
  4700. else if ('timestampValue' in value) {
  4701. return canonifyTimestamp(value.timestampValue);
  4702. }
  4703. else if ('stringValue' in value) {
  4704. return value.stringValue;
  4705. }
  4706. else if ('bytesValue' in value) {
  4707. return canonifyByteString(value.bytesValue);
  4708. }
  4709. else if ('referenceValue' in value) {
  4710. return canonifyReference(value.referenceValue);
  4711. }
  4712. else if ('geoPointValue' in value) {
  4713. return canonifyGeoPoint(value.geoPointValue);
  4714. }
  4715. else if ('arrayValue' in value) {
  4716. return canonifyArray(value.arrayValue);
  4717. }
  4718. else if ('mapValue' in value) {
  4719. return canonifyMap(value.mapValue);
  4720. }
  4721. else {
  4722. return fail();
  4723. }
  4724. }
  4725. function canonifyByteString(byteString) {
  4726. return normalizeByteString(byteString).toBase64();
  4727. }
  4728. function canonifyTimestamp(timestamp) {
  4729. const normalizedTimestamp = normalizeTimestamp(timestamp);
  4730. return `time(${normalizedTimestamp.seconds},${normalizedTimestamp.nanos})`;
  4731. }
  4732. function canonifyGeoPoint(geoPoint) {
  4733. return `geo(${geoPoint.latitude},${geoPoint.longitude})`;
  4734. }
  4735. function canonifyReference(referenceValue) {
  4736. return DocumentKey.fromName(referenceValue).toString();
  4737. }
  4738. function canonifyMap(mapValue) {
  4739. // Iteration order in JavaScript is not guaranteed. To ensure that we generate
  4740. // matching canonical IDs for identical maps, we need to sort the keys.
  4741. const sortedKeys = Object.keys(mapValue.fields || {}).sort();
  4742. let result = '{';
  4743. let first = true;
  4744. for (const key of sortedKeys) {
  4745. if (!first) {
  4746. result += ',';
  4747. }
  4748. else {
  4749. first = false;
  4750. }
  4751. result += `${key}:${canonifyValue(mapValue.fields[key])}`;
  4752. }
  4753. return result + '}';
  4754. }
  4755. function canonifyArray(arrayValue) {
  4756. let result = '[';
  4757. let first = true;
  4758. for (const value of arrayValue.values || []) {
  4759. if (!first) {
  4760. result += ',';
  4761. }
  4762. else {
  4763. first = false;
  4764. }
  4765. result += canonifyValue(value);
  4766. }
  4767. return result + ']';
  4768. }
  4769. /**
  4770. * Returns an approximate (and wildly inaccurate) in-memory size for the field
  4771. * value.
  4772. *
  4773. * The memory size takes into account only the actual user data as it resides
  4774. * in memory and ignores object overhead.
  4775. */
  4776. function estimateByteSize(value) {
  4777. switch (typeOrder(value)) {
  4778. case 0 /* TypeOrder.NullValue */:
  4779. return 4;
  4780. case 1 /* TypeOrder.BooleanValue */:
  4781. return 4;
  4782. case 2 /* TypeOrder.NumberValue */:
  4783. return 8;
  4784. case 3 /* TypeOrder.TimestampValue */:
  4785. // Timestamps are made up of two distinct numbers (seconds + nanoseconds)
  4786. return 16;
  4787. case 4 /* TypeOrder.ServerTimestampValue */:
  4788. const previousValue = getPreviousValue(value);
  4789. return previousValue ? 16 + estimateByteSize(previousValue) : 16;
  4790. case 5 /* TypeOrder.StringValue */:
  4791. // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures:
  4792. // "JavaScript's String type is [...] a set of elements of 16-bit unsigned
  4793. // integer values"
  4794. return value.stringValue.length * 2;
  4795. case 6 /* TypeOrder.BlobValue */:
  4796. return normalizeByteString(value.bytesValue).approximateByteSize();
  4797. case 7 /* TypeOrder.RefValue */:
  4798. return value.referenceValue.length;
  4799. case 8 /* TypeOrder.GeoPointValue */:
  4800. // GeoPoints are made up of two distinct numbers (latitude + longitude)
  4801. return 16;
  4802. case 9 /* TypeOrder.ArrayValue */:
  4803. return estimateArrayByteSize(value.arrayValue);
  4804. case 10 /* TypeOrder.ObjectValue */:
  4805. return estimateMapByteSize(value.mapValue);
  4806. default:
  4807. throw fail();
  4808. }
  4809. }
  4810. function estimateMapByteSize(mapValue) {
  4811. let size = 0;
  4812. forEach(mapValue.fields, (key, val) => {
  4813. size += key.length + estimateByteSize(val);
  4814. });
  4815. return size;
  4816. }
  4817. function estimateArrayByteSize(arrayValue) {
  4818. return (arrayValue.values || []).reduce((previousSize, value) => previousSize + estimateByteSize(value), 0);
  4819. }
  4820. /** Returns a reference value for the provided database and key. */
  4821. function refValue(databaseId, key) {
  4822. return {
  4823. referenceValue: `projects/${databaseId.projectId}/databases/${databaseId.database}/documents/${key.path.canonicalString()}`
  4824. };
  4825. }
  4826. /** Returns true if `value` is an IntegerValue . */
  4827. function isInteger(value) {
  4828. return !!value && 'integerValue' in value;
  4829. }
  4830. /** Returns true if `value` is a DoubleValue. */
  4831. function isDouble(value) {
  4832. return !!value && 'doubleValue' in value;
  4833. }
  4834. /** Returns true if `value` is either an IntegerValue or a DoubleValue. */
  4835. function isNumber(value) {
  4836. return isInteger(value) || isDouble(value);
  4837. }
  4838. /** Returns true if `value` is an ArrayValue. */
  4839. function isArray(value) {
  4840. return !!value && 'arrayValue' in value;
  4841. }
  4842. /** Returns true if `value` is a NullValue. */
  4843. function isNullValue(value) {
  4844. return !!value && 'nullValue' in value;
  4845. }
  4846. /** Returns true if `value` is NaN. */
  4847. function isNanValue(value) {
  4848. return !!value && 'doubleValue' in value && isNaN(Number(value.doubleValue));
  4849. }
  4850. /** Returns true if `value` is a MapValue. */
  4851. function isMapValue(value) {
  4852. return !!value && 'mapValue' in value;
  4853. }
  4854. /** Creates a deep copy of `source`. */
  4855. function deepClone(source) {
  4856. if (source.geoPointValue) {
  4857. return { geoPointValue: Object.assign({}, source.geoPointValue) };
  4858. }
  4859. else if (source.timestampValue &&
  4860. typeof source.timestampValue === 'object') {
  4861. return { timestampValue: Object.assign({}, source.timestampValue) };
  4862. }
  4863. else if (source.mapValue) {
  4864. const target = { mapValue: { fields: {} } };
  4865. forEach(source.mapValue.fields, (key, val) => (target.mapValue.fields[key] = deepClone(val)));
  4866. return target;
  4867. }
  4868. else if (source.arrayValue) {
  4869. const target = { arrayValue: { values: [] } };
  4870. for (let i = 0; i < (source.arrayValue.values || []).length; ++i) {
  4871. target.arrayValue.values[i] = deepClone(source.arrayValue.values[i]);
  4872. }
  4873. return target;
  4874. }
  4875. else {
  4876. return Object.assign({}, source);
  4877. }
  4878. }
  4879. /** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
  4880. function isMaxValue(value) {
  4881. return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
  4882. MAX_VALUE_TYPE);
  4883. }
  4884. /** Returns the lowest value for the given value type (inclusive). */
  4885. function valuesGetLowerBound(value) {
  4886. if ('nullValue' in value) {
  4887. return MIN_VALUE;
  4888. }
  4889. else if ('booleanValue' in value) {
  4890. return { booleanValue: false };
  4891. }
  4892. else if ('integerValue' in value || 'doubleValue' in value) {
  4893. return { doubleValue: NaN };
  4894. }
  4895. else if ('timestampValue' in value) {
  4896. return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
  4897. }
  4898. else if ('stringValue' in value) {
  4899. return { stringValue: '' };
  4900. }
  4901. else if ('bytesValue' in value) {
  4902. return { bytesValue: '' };
  4903. }
  4904. else if ('referenceValue' in value) {
  4905. return refValue(DatabaseId.empty(), DocumentKey.empty());
  4906. }
  4907. else if ('geoPointValue' in value) {
  4908. return { geoPointValue: { latitude: -90, longitude: -180 } };
  4909. }
  4910. else if ('arrayValue' in value) {
  4911. return { arrayValue: {} };
  4912. }
  4913. else if ('mapValue' in value) {
  4914. return { mapValue: {} };
  4915. }
  4916. else {
  4917. return fail();
  4918. }
  4919. }
  4920. /** Returns the largest value for the given value type (exclusive). */
  4921. function valuesGetUpperBound(value) {
  4922. if ('nullValue' in value) {
  4923. return { booleanValue: false };
  4924. }
  4925. else if ('booleanValue' in value) {
  4926. return { doubleValue: NaN };
  4927. }
  4928. else if ('integerValue' in value || 'doubleValue' in value) {
  4929. return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
  4930. }
  4931. else if ('timestampValue' in value) {
  4932. return { stringValue: '' };
  4933. }
  4934. else if ('stringValue' in value) {
  4935. return { bytesValue: '' };
  4936. }
  4937. else if ('bytesValue' in value) {
  4938. return refValue(DatabaseId.empty(), DocumentKey.empty());
  4939. }
  4940. else if ('referenceValue' in value) {
  4941. return { geoPointValue: { latitude: -90, longitude: -180 } };
  4942. }
  4943. else if ('geoPointValue' in value) {
  4944. return { arrayValue: {} };
  4945. }
  4946. else if ('arrayValue' in value) {
  4947. return { mapValue: {} };
  4948. }
  4949. else if ('mapValue' in value) {
  4950. return MAX_VALUE;
  4951. }
  4952. else {
  4953. return fail();
  4954. }
  4955. }
  4956. function lowerBoundCompare(left, right) {
  4957. const cmp = valueCompare(left.value, right.value);
  4958. if (cmp !== 0) {
  4959. return cmp;
  4960. }
  4961. if (left.inclusive && !right.inclusive) {
  4962. return -1;
  4963. }
  4964. else if (!left.inclusive && right.inclusive) {
  4965. return 1;
  4966. }
  4967. return 0;
  4968. }
  4969. function upperBoundCompare(left, right) {
  4970. const cmp = valueCompare(left.value, right.value);
  4971. if (cmp !== 0) {
  4972. return cmp;
  4973. }
  4974. if (left.inclusive && !right.inclusive) {
  4975. return 1;
  4976. }
  4977. else if (!left.inclusive && right.inclusive) {
  4978. return -1;
  4979. }
  4980. return 0;
  4981. }
  4982. /**
  4983. * @license
  4984. * Copyright 2017 Google LLC
  4985. *
  4986. * Licensed under the Apache License, Version 2.0 (the "License");
  4987. * you may not use this file except in compliance with the License.
  4988. * You may obtain a copy of the License at
  4989. *
  4990. * http://www.apache.org/licenses/LICENSE-2.0
  4991. *
  4992. * Unless required by applicable law or agreed to in writing, software
  4993. * distributed under the License is distributed on an "AS IS" BASIS,
  4994. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4995. * See the License for the specific language governing permissions and
  4996. * limitations under the License.
  4997. */
  4998. /**
  4999. * An ObjectValue represents a MapValue in the Firestore Proto and offers the
  5000. * ability to add and remove fields (via the ObjectValueBuilder).
  5001. */
  5002. class ObjectValue {
  5003. constructor(value) {
  5004. this.value = value;
  5005. }
  5006. static empty() {
  5007. return new ObjectValue({ mapValue: {} });
  5008. }
  5009. /**
  5010. * Returns the value at the given path or null.
  5011. *
  5012. * @param path - the path to search
  5013. * @returns The value at the path or null if the path is not set.
  5014. */
  5015. field(path) {
  5016. if (path.isEmpty()) {
  5017. return this.value;
  5018. }
  5019. else {
  5020. let currentLevel = this.value;
  5021. for (let i = 0; i < path.length - 1; ++i) {
  5022. currentLevel = (currentLevel.mapValue.fields || {})[path.get(i)];
  5023. if (!isMapValue(currentLevel)) {
  5024. return null;
  5025. }
  5026. }
  5027. currentLevel = (currentLevel.mapValue.fields || {})[path.lastSegment()];
  5028. return currentLevel || null;
  5029. }
  5030. }
  5031. /**
  5032. * Sets the field to the provided value.
  5033. *
  5034. * @param path - The field path to set.
  5035. * @param value - The value to set.
  5036. */
  5037. set(path, value) {
  5038. const fieldsMap = this.getFieldsMap(path.popLast());
  5039. fieldsMap[path.lastSegment()] = deepClone(value);
  5040. }
  5041. /**
  5042. * Sets the provided fields to the provided values.
  5043. *
  5044. * @param data - A map of fields to values (or null for deletes).
  5045. */
  5046. setAll(data) {
  5047. let parent = FieldPath$1.emptyPath();
  5048. let upserts = {};
  5049. let deletes = [];
  5050. data.forEach((value, path) => {
  5051. if (!parent.isImmediateParentOf(path)) {
  5052. // Insert the accumulated changes at this parent location
  5053. const fieldsMap = this.getFieldsMap(parent);
  5054. this.applyChanges(fieldsMap, upserts, deletes);
  5055. upserts = {};
  5056. deletes = [];
  5057. parent = path.popLast();
  5058. }
  5059. if (value) {
  5060. upserts[path.lastSegment()] = deepClone(value);
  5061. }
  5062. else {
  5063. deletes.push(path.lastSegment());
  5064. }
  5065. });
  5066. const fieldsMap = this.getFieldsMap(parent);
  5067. this.applyChanges(fieldsMap, upserts, deletes);
  5068. }
  5069. /**
  5070. * Removes the field at the specified path. If there is no field at the
  5071. * specified path, nothing is changed.
  5072. *
  5073. * @param path - The field path to remove.
  5074. */
  5075. delete(path) {
  5076. const nestedValue = this.field(path.popLast());
  5077. if (isMapValue(nestedValue) && nestedValue.mapValue.fields) {
  5078. delete nestedValue.mapValue.fields[path.lastSegment()];
  5079. }
  5080. }
  5081. isEqual(other) {
  5082. return valueEquals(this.value, other.value);
  5083. }
  5084. /**
  5085. * Returns the map that contains the leaf element of `path`. If the parent
  5086. * entry does not yet exist, or if it is not a map, a new map will be created.
  5087. */
  5088. getFieldsMap(path) {
  5089. let current = this.value;
  5090. if (!current.mapValue.fields) {
  5091. current.mapValue = { fields: {} };
  5092. }
  5093. for (let i = 0; i < path.length; ++i) {
  5094. let next = current.mapValue.fields[path.get(i)];
  5095. if (!isMapValue(next) || !next.mapValue.fields) {
  5096. next = { mapValue: { fields: {} } };
  5097. current.mapValue.fields[path.get(i)] = next;
  5098. }
  5099. current = next;
  5100. }
  5101. return current.mapValue.fields;
  5102. }
  5103. /**
  5104. * Modifies `fieldsMap` by adding, replacing or deleting the specified
  5105. * entries.
  5106. */
  5107. applyChanges(fieldsMap, inserts, deletes) {
  5108. forEach(inserts, (key, val) => (fieldsMap[key] = val));
  5109. for (const field of deletes) {
  5110. delete fieldsMap[field];
  5111. }
  5112. }
  5113. clone() {
  5114. return new ObjectValue(deepClone(this.value));
  5115. }
  5116. }
  5117. /**
  5118. * Returns a FieldMask built from all fields in a MapValue.
  5119. */
  5120. function extractFieldMask(value) {
  5121. const fields = [];
  5122. forEach(value.fields, (key, value) => {
  5123. const currentPath = new FieldPath$1([key]);
  5124. if (isMapValue(value)) {
  5125. const nestedMask = extractFieldMask(value.mapValue);
  5126. const nestedFields = nestedMask.fields;
  5127. if (nestedFields.length === 0) {
  5128. // Preserve the empty map by adding it to the FieldMask.
  5129. fields.push(currentPath);
  5130. }
  5131. else {
  5132. // For nested and non-empty ObjectValues, add the FieldPath of the
  5133. // leaf nodes.
  5134. for (const nestedPath of nestedFields) {
  5135. fields.push(currentPath.child(nestedPath));
  5136. }
  5137. }
  5138. }
  5139. else {
  5140. // For nested and non-empty ObjectValues, add the FieldPath of the leaf
  5141. // nodes.
  5142. fields.push(currentPath);
  5143. }
  5144. });
  5145. return new FieldMask(fields);
  5146. }
  5147. /**
  5148. * @license
  5149. * Copyright 2017 Google LLC
  5150. *
  5151. * Licensed under the Apache License, Version 2.0 (the "License");
  5152. * you may not use this file except in compliance with the License.
  5153. * You may obtain a copy of the License at
  5154. *
  5155. * http://www.apache.org/licenses/LICENSE-2.0
  5156. *
  5157. * Unless required by applicable law or agreed to in writing, software
  5158. * distributed under the License is distributed on an "AS IS" BASIS,
  5159. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5160. * See the License for the specific language governing permissions and
  5161. * limitations under the License.
  5162. */
  5163. /**
  5164. * Represents a document in Firestore with a key, version, data and whether it
  5165. * has local mutations applied to it.
  5166. *
  5167. * Documents can transition between states via `convertToFoundDocument()`,
  5168. * `convertToNoDocument()` and `convertToUnknownDocument()`. If a document does
  5169. * not transition to one of these states even after all mutations have been
  5170. * applied, `isValidDocument()` returns false and the document should be removed
  5171. * from all views.
  5172. */
  5173. class MutableDocument {
  5174. constructor(key, documentType, version, readTime, createTime, data, documentState) {
  5175. this.key = key;
  5176. this.documentType = documentType;
  5177. this.version = version;
  5178. this.readTime = readTime;
  5179. this.createTime = createTime;
  5180. this.data = data;
  5181. this.documentState = documentState;
  5182. }
  5183. /**
  5184. * Creates a document with no known version or data, but which can serve as
  5185. * base document for mutations.
  5186. */
  5187. static newInvalidDocument(documentKey) {
  5188. return new MutableDocument(documentKey, 0 /* DocumentType.INVALID */,
  5189. /* version */ SnapshotVersion.min(),
  5190. /* readTime */ SnapshotVersion.min(),
  5191. /* createTime */ SnapshotVersion.min(), ObjectValue.empty(), 0 /* DocumentState.SYNCED */);
  5192. }
  5193. /**
  5194. * Creates a new document that is known to exist with the given data at the
  5195. * given version.
  5196. */
  5197. static newFoundDocument(documentKey, version, createTime, value) {
  5198. return new MutableDocument(documentKey, 1 /* DocumentType.FOUND_DOCUMENT */,
  5199. /* version */ version,
  5200. /* readTime */ SnapshotVersion.min(),
  5201. /* createTime */ createTime, value, 0 /* DocumentState.SYNCED */);
  5202. }
  5203. /** Creates a new document that is known to not exist at the given version. */
  5204. static newNoDocument(documentKey, version) {
  5205. return new MutableDocument(documentKey, 2 /* DocumentType.NO_DOCUMENT */,
  5206. /* version */ version,
  5207. /* readTime */ SnapshotVersion.min(),
  5208. /* createTime */ SnapshotVersion.min(), ObjectValue.empty(), 0 /* DocumentState.SYNCED */);
  5209. }
  5210. /**
  5211. * Creates a new document that is known to exist at the given version but
  5212. * whose data is not known (e.g. a document that was updated without a known
  5213. * base document).
  5214. */
  5215. static newUnknownDocument(documentKey, version) {
  5216. return new MutableDocument(documentKey, 3 /* DocumentType.UNKNOWN_DOCUMENT */,
  5217. /* version */ version,
  5218. /* readTime */ SnapshotVersion.min(),
  5219. /* createTime */ SnapshotVersion.min(), ObjectValue.empty(), 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */);
  5220. }
  5221. /**
  5222. * Changes the document type to indicate that it exists and that its version
  5223. * and data are known.
  5224. */
  5225. convertToFoundDocument(version, value) {
  5226. // If a document is switching state from being an invalid or deleted
  5227. // document to a valid (FOUND_DOCUMENT) document, either due to receiving an
  5228. // update from Watch or due to applying a local set mutation on top
  5229. // of a deleted document, our best guess about its createTime would be the
  5230. // version at which the document transitioned to a FOUND_DOCUMENT.
  5231. if (this.createTime.isEqual(SnapshotVersion.min()) &&
  5232. (this.documentType === 2 /* DocumentType.NO_DOCUMENT */ ||
  5233. this.documentType === 0 /* DocumentType.INVALID */)) {
  5234. this.createTime = version;
  5235. }
  5236. this.version = version;
  5237. this.documentType = 1 /* DocumentType.FOUND_DOCUMENT */;
  5238. this.data = value;
  5239. this.documentState = 0 /* DocumentState.SYNCED */;
  5240. return this;
  5241. }
  5242. /**
  5243. * Changes the document type to indicate that it doesn't exist at the given
  5244. * version.
  5245. */
  5246. convertToNoDocument(version) {
  5247. this.version = version;
  5248. this.documentType = 2 /* DocumentType.NO_DOCUMENT */;
  5249. this.data = ObjectValue.empty();
  5250. this.documentState = 0 /* DocumentState.SYNCED */;
  5251. return this;
  5252. }
  5253. /**
  5254. * Changes the document type to indicate that it exists at a given version but
  5255. * that its data is not known (e.g. a document that was updated without a known
  5256. * base document).
  5257. */
  5258. convertToUnknownDocument(version) {
  5259. this.version = version;
  5260. this.documentType = 3 /* DocumentType.UNKNOWN_DOCUMENT */;
  5261. this.data = ObjectValue.empty();
  5262. this.documentState = 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */;
  5263. return this;
  5264. }
  5265. setHasCommittedMutations() {
  5266. this.documentState = 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */;
  5267. return this;
  5268. }
  5269. setHasLocalMutations() {
  5270. this.documentState = 1 /* DocumentState.HAS_LOCAL_MUTATIONS */;
  5271. this.version = SnapshotVersion.min();
  5272. return this;
  5273. }
  5274. setReadTime(readTime) {
  5275. this.readTime = readTime;
  5276. return this;
  5277. }
  5278. get hasLocalMutations() {
  5279. return this.documentState === 1 /* DocumentState.HAS_LOCAL_MUTATIONS */;
  5280. }
  5281. get hasCommittedMutations() {
  5282. return this.documentState === 2 /* DocumentState.HAS_COMMITTED_MUTATIONS */;
  5283. }
  5284. get hasPendingWrites() {
  5285. return this.hasLocalMutations || this.hasCommittedMutations;
  5286. }
  5287. isValidDocument() {
  5288. return this.documentType !== 0 /* DocumentType.INVALID */;
  5289. }
  5290. isFoundDocument() {
  5291. return this.documentType === 1 /* DocumentType.FOUND_DOCUMENT */;
  5292. }
  5293. isNoDocument() {
  5294. return this.documentType === 2 /* DocumentType.NO_DOCUMENT */;
  5295. }
  5296. isUnknownDocument() {
  5297. return this.documentType === 3 /* DocumentType.UNKNOWN_DOCUMENT */;
  5298. }
  5299. isEqual(other) {
  5300. return (other instanceof MutableDocument &&
  5301. this.key.isEqual(other.key) &&
  5302. this.version.isEqual(other.version) &&
  5303. this.documentType === other.documentType &&
  5304. this.documentState === other.documentState &&
  5305. this.data.isEqual(other.data));
  5306. }
  5307. mutableCopy() {
  5308. return new MutableDocument(this.key, this.documentType, this.version, this.readTime, this.createTime, this.data.clone(), this.documentState);
  5309. }
  5310. toString() {
  5311. return (`Document(${this.key}, ${this.version}, ${JSON.stringify(this.data.value)}, ` +
  5312. `{createTime: ${this.createTime}}), ` +
  5313. `{documentType: ${this.documentType}}), ` +
  5314. `{documentState: ${this.documentState}})`);
  5315. }
  5316. }
  5317. /**
  5318. * Compares the value for field `field` in the provided documents. Throws if
  5319. * the field does not exist in both documents.
  5320. */
  5321. function compareDocumentsByField(field, d1, d2) {
  5322. const v1 = d1.data.field(field);
  5323. const v2 = d2.data.field(field);
  5324. if (v1 !== null && v2 !== null) {
  5325. return valueCompare(v1, v2);
  5326. }
  5327. else {
  5328. return fail();
  5329. }
  5330. }
  5331. /**
  5332. * @license
  5333. * Copyright 2022 Google LLC
  5334. *
  5335. * Licensed under the Apache License, Version 2.0 (the "License");
  5336. * you may not use this file except in compliance with the License.
  5337. * You may obtain a copy of the License at
  5338. *
  5339. * http://www.apache.org/licenses/LICENSE-2.0
  5340. *
  5341. * Unless required by applicable law or agreed to in writing, software
  5342. * distributed under the License is distributed on an "AS IS" BASIS,
  5343. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5344. * See the License for the specific language governing permissions and
  5345. * limitations under the License.
  5346. */
  5347. /**
  5348. * Represents a bound of a query.
  5349. *
  5350. * The bound is specified with the given components representing a position and
  5351. * whether it's just before or just after the position (relative to whatever the
  5352. * query order is).
  5353. *
  5354. * The position represents a logical index position for a query. It's a prefix
  5355. * of values for the (potentially implicit) order by clauses of a query.
  5356. *
  5357. * Bound provides a function to determine whether a document comes before or
  5358. * after a bound. This is influenced by whether the position is just before or
  5359. * just after the provided values.
  5360. */
  5361. class Bound {
  5362. constructor(position, inclusive) {
  5363. this.position = position;
  5364. this.inclusive = inclusive;
  5365. }
  5366. }
  5367. function boundCompareToDocument(bound, orderBy, doc) {
  5368. let comparison = 0;
  5369. for (let i = 0; i < bound.position.length; i++) {
  5370. const orderByComponent = orderBy[i];
  5371. const component = bound.position[i];
  5372. if (orderByComponent.field.isKeyField()) {
  5373. comparison = DocumentKey.comparator(DocumentKey.fromName(component.referenceValue), doc.key);
  5374. }
  5375. else {
  5376. const docValue = doc.data.field(orderByComponent.field);
  5377. comparison = valueCompare(component, docValue);
  5378. }
  5379. if (orderByComponent.dir === "desc" /* Direction.DESCENDING */) {
  5380. comparison = comparison * -1;
  5381. }
  5382. if (comparison !== 0) {
  5383. break;
  5384. }
  5385. }
  5386. return comparison;
  5387. }
  5388. /**
  5389. * Returns true if a document sorts after a bound using the provided sort
  5390. * order.
  5391. */
  5392. function boundSortsAfterDocument(bound, orderBy, doc) {
  5393. const comparison = boundCompareToDocument(bound, orderBy, doc);
  5394. return bound.inclusive ? comparison >= 0 : comparison > 0;
  5395. }
  5396. /**
  5397. * Returns true if a document sorts before a bound using the provided sort
  5398. * order.
  5399. */
  5400. function boundSortsBeforeDocument(bound, orderBy, doc) {
  5401. const comparison = boundCompareToDocument(bound, orderBy, doc);
  5402. return bound.inclusive ? comparison <= 0 : comparison < 0;
  5403. }
  5404. function boundEquals(left, right) {
  5405. if (left === null) {
  5406. return right === null;
  5407. }
  5408. else if (right === null) {
  5409. return false;
  5410. }
  5411. if (left.inclusive !== right.inclusive ||
  5412. left.position.length !== right.position.length) {
  5413. return false;
  5414. }
  5415. for (let i = 0; i < left.position.length; i++) {
  5416. const leftPosition = left.position[i];
  5417. const rightPosition = right.position[i];
  5418. if (!valueEquals(leftPosition, rightPosition)) {
  5419. return false;
  5420. }
  5421. }
  5422. return true;
  5423. }
  5424. /**
  5425. * @license
  5426. * Copyright 2022 Google LLC
  5427. *
  5428. * Licensed under the Apache License, Version 2.0 (the "License");
  5429. * you may not use this file except in compliance with the License.
  5430. * You may obtain a copy of the License at
  5431. *
  5432. * http://www.apache.org/licenses/LICENSE-2.0
  5433. *
  5434. * Unless required by applicable law or agreed to in writing, software
  5435. * distributed under the License is distributed on an "AS IS" BASIS,
  5436. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5437. * See the License for the specific language governing permissions and
  5438. * limitations under the License.
  5439. */
  5440. /**
  5441. * An ordering on a field, in some Direction. Direction defaults to ASCENDING.
  5442. */
  5443. class OrderBy {
  5444. constructor(field, dir = "asc" /* Direction.ASCENDING */) {
  5445. this.field = field;
  5446. this.dir = dir;
  5447. }
  5448. }
  5449. function canonifyOrderBy(orderBy) {
  5450. // TODO(b/29183165): Make this collision robust.
  5451. return orderBy.field.canonicalString() + orderBy.dir;
  5452. }
  5453. function stringifyOrderBy(orderBy) {
  5454. return `${orderBy.field.canonicalString()} (${orderBy.dir})`;
  5455. }
  5456. function orderByEquals(left, right) {
  5457. return left.dir === right.dir && left.field.isEqual(right.field);
  5458. }
  5459. /**
  5460. * @license
  5461. * Copyright 2022 Google LLC
  5462. *
  5463. * Licensed under the Apache License, Version 2.0 (the "License");
  5464. * you may not use this file except in compliance with the License.
  5465. * You may obtain a copy of the License at
  5466. *
  5467. * http://www.apache.org/licenses/LICENSE-2.0
  5468. *
  5469. * Unless required by applicable law or agreed to in writing, software
  5470. * distributed under the License is distributed on an "AS IS" BASIS,
  5471. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5472. * See the License for the specific language governing permissions and
  5473. * limitations under the License.
  5474. */
  5475. class Filter {
  5476. }
  5477. class FieldFilter extends Filter {
  5478. constructor(field, op, value) {
  5479. super();
  5480. this.field = field;
  5481. this.op = op;
  5482. this.value = value;
  5483. }
  5484. /**
  5485. * Creates a filter based on the provided arguments.
  5486. */
  5487. static create(field, op, value) {
  5488. if (field.isKeyField()) {
  5489. if (op === "in" /* Operator.IN */ || op === "not-in" /* Operator.NOT_IN */) {
  5490. return this.createKeyFieldInFilter(field, op, value);
  5491. }
  5492. else {
  5493. return new KeyFieldFilter(field, op, value);
  5494. }
  5495. }
  5496. else if (op === "array-contains" /* Operator.ARRAY_CONTAINS */) {
  5497. return new ArrayContainsFilter(field, value);
  5498. }
  5499. else if (op === "in" /* Operator.IN */) {
  5500. return new InFilter(field, value);
  5501. }
  5502. else if (op === "not-in" /* Operator.NOT_IN */) {
  5503. return new NotInFilter(field, value);
  5504. }
  5505. else if (op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  5506. return new ArrayContainsAnyFilter(field, value);
  5507. }
  5508. else {
  5509. return new FieldFilter(field, op, value);
  5510. }
  5511. }
  5512. static createKeyFieldInFilter(field, op, value) {
  5513. return op === "in" /* Operator.IN */
  5514. ? new KeyFieldInFilter(field, value)
  5515. : new KeyFieldNotInFilter(field, value);
  5516. }
  5517. matches(doc) {
  5518. const other = doc.data.field(this.field);
  5519. // Types do not have to match in NOT_EQUAL filters.
  5520. if (this.op === "!=" /* Operator.NOT_EQUAL */) {
  5521. return (other !== null &&
  5522. this.matchesComparison(valueCompare(other, this.value)));
  5523. }
  5524. // Only compare types with matching backend order (such as double and int).
  5525. return (other !== null &&
  5526. typeOrder(this.value) === typeOrder(other) &&
  5527. this.matchesComparison(valueCompare(other, this.value)));
  5528. }
  5529. matchesComparison(comparison) {
  5530. switch (this.op) {
  5531. case "<" /* Operator.LESS_THAN */:
  5532. return comparison < 0;
  5533. case "<=" /* Operator.LESS_THAN_OR_EQUAL */:
  5534. return comparison <= 0;
  5535. case "==" /* Operator.EQUAL */:
  5536. return comparison === 0;
  5537. case "!=" /* Operator.NOT_EQUAL */:
  5538. return comparison !== 0;
  5539. case ">" /* Operator.GREATER_THAN */:
  5540. return comparison > 0;
  5541. case ">=" /* Operator.GREATER_THAN_OR_EQUAL */:
  5542. return comparison >= 0;
  5543. default:
  5544. return fail();
  5545. }
  5546. }
  5547. isInequality() {
  5548. return ([
  5549. "<" /* Operator.LESS_THAN */,
  5550. "<=" /* Operator.LESS_THAN_OR_EQUAL */,
  5551. ">" /* Operator.GREATER_THAN */,
  5552. ">=" /* Operator.GREATER_THAN_OR_EQUAL */,
  5553. "!=" /* Operator.NOT_EQUAL */,
  5554. "not-in" /* Operator.NOT_IN */
  5555. ].indexOf(this.op) >= 0);
  5556. }
  5557. getFlattenedFilters() {
  5558. return [this];
  5559. }
  5560. getFilters() {
  5561. return [this];
  5562. }
  5563. getFirstInequalityField() {
  5564. if (this.isInequality()) {
  5565. return this.field;
  5566. }
  5567. return null;
  5568. }
  5569. }
  5570. class CompositeFilter extends Filter {
  5571. constructor(filters, op) {
  5572. super();
  5573. this.filters = filters;
  5574. this.op = op;
  5575. this.memoizedFlattenedFilters = null;
  5576. }
  5577. /**
  5578. * Creates a filter based on the provided arguments.
  5579. */
  5580. static create(filters, op) {
  5581. return new CompositeFilter(filters, op);
  5582. }
  5583. matches(doc) {
  5584. if (compositeFilterIsConjunction(this)) {
  5585. // For conjunctions, all filters must match, so return false if any filter doesn't match.
  5586. return this.filters.find(filter => !filter.matches(doc)) === undefined;
  5587. }
  5588. else {
  5589. // For disjunctions, at least one filter should match.
  5590. return this.filters.find(filter => filter.matches(doc)) !== undefined;
  5591. }
  5592. }
  5593. getFlattenedFilters() {
  5594. if (this.memoizedFlattenedFilters !== null) {
  5595. return this.memoizedFlattenedFilters;
  5596. }
  5597. this.memoizedFlattenedFilters = this.filters.reduce((result, subfilter) => {
  5598. return result.concat(subfilter.getFlattenedFilters());
  5599. }, []);
  5600. return this.memoizedFlattenedFilters;
  5601. }
  5602. // Returns a mutable copy of `this.filters`
  5603. getFilters() {
  5604. return Object.assign([], this.filters);
  5605. }
  5606. getFirstInequalityField() {
  5607. const found = this.findFirstMatchingFilter(filter => filter.isInequality());
  5608. if (found !== null) {
  5609. return found.field;
  5610. }
  5611. return null;
  5612. }
  5613. // Performs a depth-first search to find and return the first FieldFilter in the composite filter
  5614. // that satisfies the predicate. Returns `null` if none of the FieldFilters satisfy the
  5615. // predicate.
  5616. findFirstMatchingFilter(predicate) {
  5617. for (const fieldFilter of this.getFlattenedFilters()) {
  5618. if (predicate(fieldFilter)) {
  5619. return fieldFilter;
  5620. }
  5621. }
  5622. return null;
  5623. }
  5624. }
  5625. function compositeFilterIsConjunction(compositeFilter) {
  5626. return compositeFilter.op === "and" /* CompositeOperator.AND */;
  5627. }
  5628. function compositeFilterIsDisjunction(compositeFilter) {
  5629. return compositeFilter.op === "or" /* CompositeOperator.OR */;
  5630. }
  5631. /**
  5632. * Returns true if this filter is a conjunction of field filters only. Returns false otherwise.
  5633. */
  5634. function compositeFilterIsFlatConjunction(compositeFilter) {
  5635. return (compositeFilterIsFlat(compositeFilter) &&
  5636. compositeFilterIsConjunction(compositeFilter));
  5637. }
  5638. /**
  5639. * Returns true if this filter does not contain any composite filters. Returns false otherwise.
  5640. */
  5641. function compositeFilterIsFlat(compositeFilter) {
  5642. for (const filter of compositeFilter.filters) {
  5643. if (filter instanceof CompositeFilter) {
  5644. return false;
  5645. }
  5646. }
  5647. return true;
  5648. }
  5649. function canonifyFilter(filter) {
  5650. if (filter instanceof FieldFilter) {
  5651. // TODO(b/29183165): Technically, this won't be unique if two values have
  5652. // the same description, such as the int 3 and the string "3". So we should
  5653. // add the types in here somehow, too.
  5654. return (filter.field.canonicalString() +
  5655. filter.op.toString() +
  5656. canonicalId(filter.value));
  5657. }
  5658. else if (compositeFilterIsFlatConjunction(filter)) {
  5659. // Older SDK versions use an implicit AND operation between their filters.
  5660. // In the new SDK versions, the developer may use an explicit AND filter.
  5661. // To stay consistent with the old usages, we add a special case to ensure
  5662. // the canonical ID for these two are the same. For example:
  5663. // `col.whereEquals("a", 1).whereEquals("b", 2)` should have the same
  5664. // canonical ID as `col.where(and(equals("a",1), equals("b",2)))`.
  5665. return filter.filters.map(filter => canonifyFilter(filter)).join(',');
  5666. }
  5667. else {
  5668. // filter instanceof CompositeFilter
  5669. const canonicalIdsString = filter.filters
  5670. .map(filter => canonifyFilter(filter))
  5671. .join(',');
  5672. return `${filter.op}(${canonicalIdsString})`;
  5673. }
  5674. }
  5675. function filterEquals(f1, f2) {
  5676. if (f1 instanceof FieldFilter) {
  5677. return fieldFilterEquals(f1, f2);
  5678. }
  5679. else if (f1 instanceof CompositeFilter) {
  5680. return compositeFilterEquals(f1, f2);
  5681. }
  5682. else {
  5683. fail();
  5684. }
  5685. }
  5686. function fieldFilterEquals(f1, f2) {
  5687. return (f2 instanceof FieldFilter &&
  5688. f1.op === f2.op &&
  5689. f1.field.isEqual(f2.field) &&
  5690. valueEquals(f1.value, f2.value));
  5691. }
  5692. function compositeFilterEquals(f1, f2) {
  5693. if (f2 instanceof CompositeFilter &&
  5694. f1.op === f2.op &&
  5695. f1.filters.length === f2.filters.length) {
  5696. const subFiltersMatch = f1.filters.reduce((result, f1Filter, index) => result && filterEquals(f1Filter, f2.filters[index]), true);
  5697. return subFiltersMatch;
  5698. }
  5699. return false;
  5700. }
  5701. /**
  5702. * Returns a new composite filter that contains all filter from
  5703. * `compositeFilter` plus all the given filters in `otherFilters`.
  5704. */
  5705. function compositeFilterWithAddedFilters(compositeFilter, otherFilters) {
  5706. const mergedFilters = compositeFilter.filters.concat(otherFilters);
  5707. return CompositeFilter.create(mergedFilters, compositeFilter.op);
  5708. }
  5709. /** Returns a debug description for `filter`. */
  5710. function stringifyFilter(filter) {
  5711. if (filter instanceof FieldFilter) {
  5712. return stringifyFieldFilter(filter);
  5713. }
  5714. else if (filter instanceof CompositeFilter) {
  5715. return stringifyCompositeFilter(filter);
  5716. }
  5717. else {
  5718. return 'Filter';
  5719. }
  5720. }
  5721. function stringifyCompositeFilter(filter) {
  5722. return (filter.op.toString() +
  5723. ` {` +
  5724. filter.getFilters().map(stringifyFilter).join(' ,') +
  5725. '}');
  5726. }
  5727. function stringifyFieldFilter(filter) {
  5728. return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(filter.value)}`;
  5729. }
  5730. /** Filter that matches on key fields (i.e. '__name__'). */
  5731. class KeyFieldFilter extends FieldFilter {
  5732. constructor(field, op, value) {
  5733. super(field, op, value);
  5734. this.key = DocumentKey.fromName(value.referenceValue);
  5735. }
  5736. matches(doc) {
  5737. const comparison = DocumentKey.comparator(doc.key, this.key);
  5738. return this.matchesComparison(comparison);
  5739. }
  5740. }
  5741. /** Filter that matches on key fields within an array. */
  5742. class KeyFieldInFilter extends FieldFilter {
  5743. constructor(field, value) {
  5744. super(field, "in" /* Operator.IN */, value);
  5745. this.keys = extractDocumentKeysFromArrayValue("in" /* Operator.IN */, value);
  5746. }
  5747. matches(doc) {
  5748. return this.keys.some(key => key.isEqual(doc.key));
  5749. }
  5750. }
  5751. /** Filter that matches on key fields not present within an array. */
  5752. class KeyFieldNotInFilter extends FieldFilter {
  5753. constructor(field, value) {
  5754. super(field, "not-in" /* Operator.NOT_IN */, value);
  5755. this.keys = extractDocumentKeysFromArrayValue("not-in" /* Operator.NOT_IN */, value);
  5756. }
  5757. matches(doc) {
  5758. return !this.keys.some(key => key.isEqual(doc.key));
  5759. }
  5760. }
  5761. function extractDocumentKeysFromArrayValue(op, value) {
  5762. var _a;
  5763. return (((_a = value.arrayValue) === null || _a === void 0 ? void 0 : _a.values) || []).map(v => {
  5764. return DocumentKey.fromName(v.referenceValue);
  5765. });
  5766. }
  5767. /** A Filter that implements the array-contains operator. */
  5768. class ArrayContainsFilter extends FieldFilter {
  5769. constructor(field, value) {
  5770. super(field, "array-contains" /* Operator.ARRAY_CONTAINS */, value);
  5771. }
  5772. matches(doc) {
  5773. const other = doc.data.field(this.field);
  5774. return isArray(other) && arrayValueContains(other.arrayValue, this.value);
  5775. }
  5776. }
  5777. /** A Filter that implements the IN operator. */
  5778. class InFilter extends FieldFilter {
  5779. constructor(field, value) {
  5780. super(field, "in" /* Operator.IN */, value);
  5781. }
  5782. matches(doc) {
  5783. const other = doc.data.field(this.field);
  5784. return other !== null && arrayValueContains(this.value.arrayValue, other);
  5785. }
  5786. }
  5787. /** A Filter that implements the not-in operator. */
  5788. class NotInFilter extends FieldFilter {
  5789. constructor(field, value) {
  5790. super(field, "not-in" /* Operator.NOT_IN */, value);
  5791. }
  5792. matches(doc) {
  5793. if (arrayValueContains(this.value.arrayValue, { nullValue: 'NULL_VALUE' })) {
  5794. return false;
  5795. }
  5796. const other = doc.data.field(this.field);
  5797. return other !== null && !arrayValueContains(this.value.arrayValue, other);
  5798. }
  5799. }
  5800. /** A Filter that implements the array-contains-any operator. */
  5801. class ArrayContainsAnyFilter extends FieldFilter {
  5802. constructor(field, value) {
  5803. super(field, "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */, value);
  5804. }
  5805. matches(doc) {
  5806. const other = doc.data.field(this.field);
  5807. if (!isArray(other) || !other.arrayValue.values) {
  5808. return false;
  5809. }
  5810. return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
  5811. }
  5812. }
  5813. /**
  5814. * @license
  5815. * Copyright 2019 Google LLC
  5816. *
  5817. * Licensed under the Apache License, Version 2.0 (the "License");
  5818. * you may not use this file except in compliance with the License.
  5819. * You may obtain a copy of the License at
  5820. *
  5821. * http://www.apache.org/licenses/LICENSE-2.0
  5822. *
  5823. * Unless required by applicable law or agreed to in writing, software
  5824. * distributed under the License is distributed on an "AS IS" BASIS,
  5825. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5826. * See the License for the specific language governing permissions and
  5827. * limitations under the License.
  5828. */
  5829. // Visible for testing
  5830. class TargetImpl {
  5831. constructor(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
  5832. this.path = path;
  5833. this.collectionGroup = collectionGroup;
  5834. this.orderBy = orderBy;
  5835. this.filters = filters;
  5836. this.limit = limit;
  5837. this.startAt = startAt;
  5838. this.endAt = endAt;
  5839. this.memoizedCanonicalId = null;
  5840. }
  5841. }
  5842. /**
  5843. * Initializes a Target with a path and optional additional query constraints.
  5844. * Path must currently be empty if this is a collection group query.
  5845. *
  5846. * NOTE: you should always construct `Target` from `Query.toTarget` instead of
  5847. * using this factory method, because `Query` provides an implicit `orderBy`
  5848. * property.
  5849. */
  5850. function newTarget(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
  5851. return new TargetImpl(path, collectionGroup, orderBy, filters, limit, startAt, endAt);
  5852. }
  5853. function canonifyTarget(target) {
  5854. const targetImpl = debugCast(target);
  5855. if (targetImpl.memoizedCanonicalId === null) {
  5856. let str = targetImpl.path.canonicalString();
  5857. if (targetImpl.collectionGroup !== null) {
  5858. str += '|cg:' + targetImpl.collectionGroup;
  5859. }
  5860. str += '|f:';
  5861. str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
  5862. str += '|ob:';
  5863. str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
  5864. if (!isNullOrUndefined(targetImpl.limit)) {
  5865. str += '|l:';
  5866. str += targetImpl.limit;
  5867. }
  5868. if (targetImpl.startAt) {
  5869. str += '|lb:';
  5870. str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
  5871. str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
  5872. }
  5873. if (targetImpl.endAt) {
  5874. str += '|ub:';
  5875. str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
  5876. str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
  5877. }
  5878. targetImpl.memoizedCanonicalId = str;
  5879. }
  5880. return targetImpl.memoizedCanonicalId;
  5881. }
  5882. function stringifyTarget(target) {
  5883. let str = target.path.canonicalString();
  5884. if (target.collectionGroup !== null) {
  5885. str += ' collectionGroup=' + target.collectionGroup;
  5886. }
  5887. if (target.filters.length > 0) {
  5888. str += `, filters: [${target.filters
  5889. .map(f => stringifyFilter(f))
  5890. .join(', ')}]`;
  5891. }
  5892. if (!isNullOrUndefined(target.limit)) {
  5893. str += ', limit: ' + target.limit;
  5894. }
  5895. if (target.orderBy.length > 0) {
  5896. str += `, orderBy: [${target.orderBy
  5897. .map(o => stringifyOrderBy(o))
  5898. .join(', ')}]`;
  5899. }
  5900. if (target.startAt) {
  5901. str += ', startAt: ';
  5902. str += target.startAt.inclusive ? 'b:' : 'a:';
  5903. str += target.startAt.position.map(p => canonicalId(p)).join(',');
  5904. }
  5905. if (target.endAt) {
  5906. str += ', endAt: ';
  5907. str += target.endAt.inclusive ? 'a:' : 'b:';
  5908. str += target.endAt.position.map(p => canonicalId(p)).join(',');
  5909. }
  5910. return `Target(${str})`;
  5911. }
  5912. function targetEquals(left, right) {
  5913. if (left.limit !== right.limit) {
  5914. return false;
  5915. }
  5916. if (left.orderBy.length !== right.orderBy.length) {
  5917. return false;
  5918. }
  5919. for (let i = 0; i < left.orderBy.length; i++) {
  5920. if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {
  5921. return false;
  5922. }
  5923. }
  5924. if (left.filters.length !== right.filters.length) {
  5925. return false;
  5926. }
  5927. for (let i = 0; i < left.filters.length; i++) {
  5928. if (!filterEquals(left.filters[i], right.filters[i])) {
  5929. return false;
  5930. }
  5931. }
  5932. if (left.collectionGroup !== right.collectionGroup) {
  5933. return false;
  5934. }
  5935. if (!left.path.isEqual(right.path)) {
  5936. return false;
  5937. }
  5938. if (!boundEquals(left.startAt, right.startAt)) {
  5939. return false;
  5940. }
  5941. return boundEquals(left.endAt, right.endAt);
  5942. }
  5943. function targetIsDocumentTarget(target) {
  5944. return (DocumentKey.isDocumentKey(target.path) &&
  5945. target.collectionGroup === null &&
  5946. target.filters.length === 0);
  5947. }
  5948. /** Returns the field filters that target the given field path. */
  5949. function targetGetFieldFiltersForPath(target, path) {
  5950. return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
  5951. }
  5952. /**
  5953. * Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
  5954. * filters. Returns `null` if there are no such filters.
  5955. */
  5956. function targetGetArrayValues(target, fieldIndex) {
  5957. const segment = fieldIndexGetArraySegment(fieldIndex);
  5958. if (segment === undefined) {
  5959. return null;
  5960. }
  5961. for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
  5962. switch (fieldFilter.op) {
  5963. case "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */:
  5964. return fieldFilter.value.arrayValue.values || [];
  5965. case "array-contains" /* Operator.ARRAY_CONTAINS */:
  5966. return [fieldFilter.value];
  5967. // Remaining filters are not array filters.
  5968. }
  5969. }
  5970. return null;
  5971. }
  5972. /**
  5973. * Returns the list of values that are used in != or NOT_IN filters. Returns
  5974. * `null` if there are no such filters.
  5975. */
  5976. function targetGetNotInValues(target, fieldIndex) {
  5977. const values = new Map();
  5978. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  5979. for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
  5980. switch (fieldFilter.op) {
  5981. case "==" /* Operator.EQUAL */:
  5982. case "in" /* Operator.IN */:
  5983. // Encode equality prefix, which is encoded in the index value before
  5984. // the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
  5985. // `value != 'ab'`).
  5986. values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
  5987. break;
  5988. case "not-in" /* Operator.NOT_IN */:
  5989. case "!=" /* Operator.NOT_EQUAL */:
  5990. // NotIn/NotEqual is always a suffix. There cannot be any remaining
  5991. // segments and hence we can return early here.
  5992. values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
  5993. return Array.from(values.values());
  5994. // Remaining filters cannot be used as notIn bounds.
  5995. }
  5996. }
  5997. }
  5998. return null;
  5999. }
  6000. /**
  6001. * Returns a lower bound of field values that can be used as a starting point to
  6002. * scan the index defined by `fieldIndex`. Returns `MIN_VALUE` if no lower bound
  6003. * exists.
  6004. */
  6005. function targetGetLowerBound(target, fieldIndex) {
  6006. const values = [];
  6007. let inclusive = true;
  6008. // For each segment, retrieve a lower bound if there is a suitable filter or
  6009. // startAt.
  6010. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  6011. const segmentBound = segment.kind === 0 /* IndexKind.ASCENDING */
  6012. ? targetGetAscendingBound(target, segment.fieldPath, target.startAt)
  6013. : targetGetDescendingBound(target, segment.fieldPath, target.startAt);
  6014. values.push(segmentBound.value);
  6015. inclusive && (inclusive = segmentBound.inclusive);
  6016. }
  6017. return new Bound(values, inclusive);
  6018. }
  6019. /**
  6020. * Returns an upper bound of field values that can be used as an ending point
  6021. * when scanning the index defined by `fieldIndex`. Returns `MAX_VALUE` if no
  6022. * upper bound exists.
  6023. */
  6024. function targetGetUpperBound(target, fieldIndex) {
  6025. const values = [];
  6026. let inclusive = true;
  6027. // For each segment, retrieve an upper bound if there is a suitable filter or
  6028. // endAt.
  6029. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  6030. const segmentBound = segment.kind === 0 /* IndexKind.ASCENDING */
  6031. ? targetGetDescendingBound(target, segment.fieldPath, target.endAt)
  6032. : targetGetAscendingBound(target, segment.fieldPath, target.endAt);
  6033. values.push(segmentBound.value);
  6034. inclusive && (inclusive = segmentBound.inclusive);
  6035. }
  6036. return new Bound(values, inclusive);
  6037. }
  6038. /**
  6039. * Returns the value to use as the lower bound for ascending index segment at
  6040. * the provided `fieldPath` (or the upper bound for an descending segment).
  6041. */
  6042. function targetGetAscendingBound(target, fieldPath, bound) {
  6043. let value = MIN_VALUE;
  6044. let inclusive = true;
  6045. // Process all filters to find a value for the current field segment
  6046. for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
  6047. let filterValue = MIN_VALUE;
  6048. let filterInclusive = true;
  6049. switch (fieldFilter.op) {
  6050. case "<" /* Operator.LESS_THAN */:
  6051. case "<=" /* Operator.LESS_THAN_OR_EQUAL */:
  6052. filterValue = valuesGetLowerBound(fieldFilter.value);
  6053. break;
  6054. case "==" /* Operator.EQUAL */:
  6055. case "in" /* Operator.IN */:
  6056. case ">=" /* Operator.GREATER_THAN_OR_EQUAL */:
  6057. filterValue = fieldFilter.value;
  6058. break;
  6059. case ">" /* Operator.GREATER_THAN */:
  6060. filterValue = fieldFilter.value;
  6061. filterInclusive = false;
  6062. break;
  6063. case "!=" /* Operator.NOT_EQUAL */:
  6064. case "not-in" /* Operator.NOT_IN */:
  6065. filterValue = MIN_VALUE;
  6066. break;
  6067. // Remaining filters cannot be used as lower bounds.
  6068. }
  6069. if (lowerBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) < 0) {
  6070. value = filterValue;
  6071. inclusive = filterInclusive;
  6072. }
  6073. }
  6074. // If there is an additional bound, compare the values against the existing
  6075. // range to see if we can narrow the scope.
  6076. if (bound !== null) {
  6077. for (let i = 0; i < target.orderBy.length; ++i) {
  6078. const orderBy = target.orderBy[i];
  6079. if (orderBy.field.isEqual(fieldPath)) {
  6080. const cursorValue = bound.position[i];
  6081. if (lowerBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) < 0) {
  6082. value = cursorValue;
  6083. inclusive = bound.inclusive;
  6084. }
  6085. break;
  6086. }
  6087. }
  6088. }
  6089. return { value, inclusive };
  6090. }
  6091. /**
  6092. * Returns the value to use as the upper bound for ascending index segment at
  6093. * the provided `fieldPath` (or the lower bound for a descending segment).
  6094. */
  6095. function targetGetDescendingBound(target, fieldPath, bound) {
  6096. let value = MAX_VALUE;
  6097. let inclusive = true;
  6098. // Process all filters to find a value for the current field segment
  6099. for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
  6100. let filterValue = MAX_VALUE;
  6101. let filterInclusive = true;
  6102. switch (fieldFilter.op) {
  6103. case ">=" /* Operator.GREATER_THAN_OR_EQUAL */:
  6104. case ">" /* Operator.GREATER_THAN */:
  6105. filterValue = valuesGetUpperBound(fieldFilter.value);
  6106. filterInclusive = false;
  6107. break;
  6108. case "==" /* Operator.EQUAL */:
  6109. case "in" /* Operator.IN */:
  6110. case "<=" /* Operator.LESS_THAN_OR_EQUAL */:
  6111. filterValue = fieldFilter.value;
  6112. break;
  6113. case "<" /* Operator.LESS_THAN */:
  6114. filterValue = fieldFilter.value;
  6115. filterInclusive = false;
  6116. break;
  6117. case "!=" /* Operator.NOT_EQUAL */:
  6118. case "not-in" /* Operator.NOT_IN */:
  6119. filterValue = MAX_VALUE;
  6120. break;
  6121. // Remaining filters cannot be used as upper bounds.
  6122. }
  6123. if (upperBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) > 0) {
  6124. value = filterValue;
  6125. inclusive = filterInclusive;
  6126. }
  6127. }
  6128. // If there is an additional bound, compare the values against the existing
  6129. // range to see if we can narrow the scope.
  6130. if (bound !== null) {
  6131. for (let i = 0; i < target.orderBy.length; ++i) {
  6132. const orderBy = target.orderBy[i];
  6133. if (orderBy.field.isEqual(fieldPath)) {
  6134. const cursorValue = bound.position[i];
  6135. if (upperBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) > 0) {
  6136. value = cursorValue;
  6137. inclusive = bound.inclusive;
  6138. }
  6139. break;
  6140. }
  6141. }
  6142. }
  6143. return { value, inclusive };
  6144. }
  6145. /** Returns the number of segments of a perfect index for this target. */
  6146. function targetGetSegmentCount(target) {
  6147. let fields = new SortedSet(FieldPath$1.comparator);
  6148. let hasArraySegment = false;
  6149. for (const filter of target.filters) {
  6150. for (const subFilter of filter.getFlattenedFilters()) {
  6151. // __name__ is not an explicit segment of any index, so we don't need to
  6152. // count it.
  6153. if (subFilter.field.isKeyField()) {
  6154. continue;
  6155. }
  6156. // ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filters must be counted separately.
  6157. // For instance, it is possible to have an index for "a ARRAY a ASC". Even
  6158. // though these are on the same field, they should be counted as two
  6159. // separate segments in an index.
  6160. if (subFilter.op === "array-contains" /* Operator.ARRAY_CONTAINS */ ||
  6161. subFilter.op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  6162. hasArraySegment = true;
  6163. }
  6164. else {
  6165. fields = fields.add(subFilter.field);
  6166. }
  6167. }
  6168. }
  6169. for (const orderBy of target.orderBy) {
  6170. // __name__ is not an explicit segment of any index, so we don't need to
  6171. // count it.
  6172. if (!orderBy.field.isKeyField()) {
  6173. fields = fields.add(orderBy.field);
  6174. }
  6175. }
  6176. return fields.size + (hasArraySegment ? 1 : 0);
  6177. }
  6178. function targetHasLimit(target) {
  6179. return target.limit !== null;
  6180. }
  6181. /**
  6182. * @license
  6183. * Copyright 2017 Google LLC
  6184. *
  6185. * Licensed under the Apache License, Version 2.0 (the "License");
  6186. * you may not use this file except in compliance with the License.
  6187. * You may obtain a copy of the License at
  6188. *
  6189. * http://www.apache.org/licenses/LICENSE-2.0
  6190. *
  6191. * Unless required by applicable law or agreed to in writing, software
  6192. * distributed under the License is distributed on an "AS IS" BASIS,
  6193. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6194. * See the License for the specific language governing permissions and
  6195. * limitations under the License.
  6196. */
  6197. /**
  6198. * Query encapsulates all the query attributes we support in the SDK. It can
  6199. * be run against the LocalStore, as well as be converted to a `Target` to
  6200. * query the RemoteStore results.
  6201. *
  6202. * Visible for testing.
  6203. */
  6204. class QueryImpl {
  6205. /**
  6206. * Initializes a Query with a path and optional additional query constraints.
  6207. * Path must currently be empty if this is a collection group query.
  6208. */
  6209. constructor(path, collectionGroup = null, explicitOrderBy = [], filters = [], limit = null, limitType = "F" /* LimitType.First */, startAt = null, endAt = null) {
  6210. this.path = path;
  6211. this.collectionGroup = collectionGroup;
  6212. this.explicitOrderBy = explicitOrderBy;
  6213. this.filters = filters;
  6214. this.limit = limit;
  6215. this.limitType = limitType;
  6216. this.startAt = startAt;
  6217. this.endAt = endAt;
  6218. this.memoizedOrderBy = null;
  6219. // The corresponding `Target` of this `Query` instance.
  6220. this.memoizedTarget = null;
  6221. if (this.startAt) ;
  6222. if (this.endAt) ;
  6223. }
  6224. }
  6225. /** Creates a new Query instance with the options provided. */
  6226. function newQuery(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt) {
  6227. return new QueryImpl(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt);
  6228. }
  6229. /** Creates a new Query for a query that matches all documents at `path` */
  6230. function newQueryForPath(path) {
  6231. return new QueryImpl(path);
  6232. }
  6233. /**
  6234. * Helper to convert a collection group query into a collection query at a
  6235. * specific path. This is used when executing collection group queries, since
  6236. * we have to split the query into a set of collection queries at multiple
  6237. * paths.
  6238. */
  6239. function asCollectionQueryAtPath(query, path) {
  6240. return new QueryImpl(path,
  6241. /*collectionGroup=*/ null, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
  6242. }
  6243. /**
  6244. * Returns true if this query does not specify any query constraints that
  6245. * could remove results.
  6246. */
  6247. function queryMatchesAllDocuments(query) {
  6248. return (query.filters.length === 0 &&
  6249. query.limit === null &&
  6250. query.startAt == null &&
  6251. query.endAt == null &&
  6252. (query.explicitOrderBy.length === 0 ||
  6253. (query.explicitOrderBy.length === 1 &&
  6254. query.explicitOrderBy[0].field.isKeyField())));
  6255. }
  6256. function getFirstOrderByField(query) {
  6257. return query.explicitOrderBy.length > 0
  6258. ? query.explicitOrderBy[0].field
  6259. : null;
  6260. }
  6261. function getInequalityFilterField(query) {
  6262. for (const filter of query.filters) {
  6263. const result = filter.getFirstInequalityField();
  6264. if (result !== null) {
  6265. return result;
  6266. }
  6267. }
  6268. return null;
  6269. }
  6270. /**
  6271. * Creates a new Query for a collection group query that matches all documents
  6272. * within the provided collection group.
  6273. */
  6274. function newQueryForCollectionGroup(collectionId) {
  6275. return new QueryImpl(ResourcePath.emptyPath(), collectionId);
  6276. }
  6277. /**
  6278. * Returns whether the query matches a single document by path (rather than a
  6279. * collection).
  6280. */
  6281. function isDocumentQuery$1(query) {
  6282. return (DocumentKey.isDocumentKey(query.path) &&
  6283. query.collectionGroup === null &&
  6284. query.filters.length === 0);
  6285. }
  6286. /**
  6287. * Returns whether the query matches a collection group rather than a specific
  6288. * collection.
  6289. */
  6290. function isCollectionGroupQuery(query) {
  6291. return query.collectionGroup !== null;
  6292. }
  6293. /**
  6294. * Returns the implicit order by constraint that is used to execute the Query,
  6295. * which can be different from the order by constraints the user provided (e.g.
  6296. * the SDK and backend always orders by `__name__`).
  6297. */
  6298. function queryOrderBy(query) {
  6299. const queryImpl = debugCast(query);
  6300. if (queryImpl.memoizedOrderBy === null) {
  6301. queryImpl.memoizedOrderBy = [];
  6302. const inequalityField = getInequalityFilterField(queryImpl);
  6303. const firstOrderByField = getFirstOrderByField(queryImpl);
  6304. if (inequalityField !== null && firstOrderByField === null) {
  6305. // In order to implicitly add key ordering, we must also add the
  6306. // inequality filter field for it to be a valid query.
  6307. // Note that the default inequality field and key ordering is ascending.
  6308. if (!inequalityField.isKeyField()) {
  6309. queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
  6310. }
  6311. queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), "asc" /* Direction.ASCENDING */));
  6312. }
  6313. else {
  6314. let foundKeyOrdering = false;
  6315. for (const orderBy of queryImpl.explicitOrderBy) {
  6316. queryImpl.memoizedOrderBy.push(orderBy);
  6317. if (orderBy.field.isKeyField()) {
  6318. foundKeyOrdering = true;
  6319. }
  6320. }
  6321. if (!foundKeyOrdering) {
  6322. // The order of the implicit key ordering always matches the last
  6323. // explicit order by
  6324. const lastDirection = queryImpl.explicitOrderBy.length > 0
  6325. ? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
  6326. .dir
  6327. : "asc" /* Direction.ASCENDING */;
  6328. queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), lastDirection));
  6329. }
  6330. }
  6331. }
  6332. return queryImpl.memoizedOrderBy;
  6333. }
  6334. /**
  6335. * Converts this `Query` instance to it's corresponding `Target` representation.
  6336. */
  6337. function queryToTarget(query) {
  6338. const queryImpl = debugCast(query);
  6339. if (!queryImpl.memoizedTarget) {
  6340. if (queryImpl.limitType === "F" /* LimitType.First */) {
  6341. queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, queryOrderBy(queryImpl), queryImpl.filters, queryImpl.limit, queryImpl.startAt, queryImpl.endAt);
  6342. }
  6343. else {
  6344. // Flip the orderBy directions since we want the last results
  6345. const orderBys = [];
  6346. for (const orderBy of queryOrderBy(queryImpl)) {
  6347. const dir = orderBy.dir === "desc" /* Direction.DESCENDING */
  6348. ? "asc" /* Direction.ASCENDING */
  6349. : "desc" /* Direction.DESCENDING */;
  6350. orderBys.push(new OrderBy(orderBy.field, dir));
  6351. }
  6352. // We need to swap the cursors to match the now-flipped query ordering.
  6353. const startAt = queryImpl.endAt
  6354. ? new Bound(queryImpl.endAt.position, queryImpl.endAt.inclusive)
  6355. : null;
  6356. const endAt = queryImpl.startAt
  6357. ? new Bound(queryImpl.startAt.position, queryImpl.startAt.inclusive)
  6358. : null;
  6359. // Now return as a LimitType.First query.
  6360. queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
  6361. }
  6362. }
  6363. return queryImpl.memoizedTarget;
  6364. }
  6365. function queryWithAddedFilter(query, filter) {
  6366. filter.getFirstInequalityField();
  6367. getInequalityFilterField(query);
  6368. const newFilters = query.filters.concat([filter]);
  6369. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), newFilters, query.limit, query.limitType, query.startAt, query.endAt);
  6370. }
  6371. function queryWithAddedOrderBy(query, orderBy) {
  6372. // TODO(dimond): validate that orderBy does not list the same key twice.
  6373. const newOrderBy = query.explicitOrderBy.concat([orderBy]);
  6374. return new QueryImpl(query.path, query.collectionGroup, newOrderBy, query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
  6375. }
  6376. function queryWithLimit(query, limit, limitType) {
  6377. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), limit, limitType, query.startAt, query.endAt);
  6378. }
  6379. function queryWithStartAt(query, bound) {
  6380. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, bound, query.endAt);
  6381. }
  6382. function queryWithEndAt(query, bound) {
  6383. return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, bound);
  6384. }
  6385. function queryEquals(left, right) {
  6386. return (targetEquals(queryToTarget(left), queryToTarget(right)) &&
  6387. left.limitType === right.limitType);
  6388. }
  6389. // TODO(b/29183165): This is used to get a unique string from a query to, for
  6390. // example, use as a dictionary key, but the implementation is subject to
  6391. // collisions. Make it collision-free.
  6392. function canonifyQuery(query) {
  6393. return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;
  6394. }
  6395. function stringifyQuery(query) {
  6396. return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${query.limitType})`;
  6397. }
  6398. /** Returns whether `doc` matches the constraints of `query`. */
  6399. function queryMatches(query, doc) {
  6400. return (doc.isFoundDocument() &&
  6401. queryMatchesPathAndCollectionGroup(query, doc) &&
  6402. queryMatchesOrderBy(query, doc) &&
  6403. queryMatchesFilters(query, doc) &&
  6404. queryMatchesBounds(query, doc));
  6405. }
  6406. function queryMatchesPathAndCollectionGroup(query, doc) {
  6407. const docPath = doc.key.path;
  6408. if (query.collectionGroup !== null) {
  6409. // NOTE: this.path is currently always empty since we don't expose Collection
  6410. // Group queries rooted at a document path yet.
  6411. return (doc.key.hasCollectionId(query.collectionGroup) &&
  6412. query.path.isPrefixOf(docPath));
  6413. }
  6414. else if (DocumentKey.isDocumentKey(query.path)) {
  6415. // exact match for document queries
  6416. return query.path.isEqual(docPath);
  6417. }
  6418. else {
  6419. // shallow ancestor queries by default
  6420. return query.path.isImmediateParentOf(docPath);
  6421. }
  6422. }
  6423. /**
  6424. * A document must have a value for every ordering clause in order to show up
  6425. * in the results.
  6426. */
  6427. function queryMatchesOrderBy(query, doc) {
  6428. // We must use `queryOrderBy()` to get the list of all orderBys (both implicit and explicit).
  6429. // Note that for OR queries, orderBy applies to all disjunction terms and implicit orderBys must
  6430. // be taken into account. For example, the query "a > 1 || b==1" has an implicit "orderBy a" due
  6431. // to the inequality, and is evaluated as "a > 1 orderBy a || b==1 orderBy a".
  6432. // A document with content of {b:1} matches the filters, but does not match the orderBy because
  6433. // it's missing the field 'a'.
  6434. for (const orderBy of queryOrderBy(query)) {
  6435. // order by key always matches
  6436. if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
  6437. return false;
  6438. }
  6439. }
  6440. return true;
  6441. }
  6442. function queryMatchesFilters(query, doc) {
  6443. for (const filter of query.filters) {
  6444. if (!filter.matches(doc)) {
  6445. return false;
  6446. }
  6447. }
  6448. return true;
  6449. }
  6450. /** Makes sure a document is within the bounds, if provided. */
  6451. function queryMatchesBounds(query, doc) {
  6452. if (query.startAt &&
  6453. !boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
  6454. return false;
  6455. }
  6456. if (query.endAt &&
  6457. !boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
  6458. return false;
  6459. }
  6460. return true;
  6461. }
  6462. /**
  6463. * Returns the collection group that this query targets.
  6464. *
  6465. * PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
  6466. * synchronization for query results.
  6467. */
  6468. function queryCollectionGroup(query) {
  6469. return (query.collectionGroup ||
  6470. (query.path.length % 2 === 1
  6471. ? query.path.lastSegment()
  6472. : query.path.get(query.path.length - 2)));
  6473. }
  6474. /**
  6475. * Returns a new comparator function that can be used to compare two documents
  6476. * based on the Query's ordering constraint.
  6477. */
  6478. function newQueryComparator(query) {
  6479. return (d1, d2) => {
  6480. let comparedOnKeyField = false;
  6481. for (const orderBy of queryOrderBy(query)) {
  6482. const comp = compareDocs(orderBy, d1, d2);
  6483. if (comp !== 0) {
  6484. return comp;
  6485. }
  6486. comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();
  6487. }
  6488. return 0;
  6489. };
  6490. }
  6491. function compareDocs(orderBy, d1, d2) {
  6492. const comparison = orderBy.field.isKeyField()
  6493. ? DocumentKey.comparator(d1.key, d2.key)
  6494. : compareDocumentsByField(orderBy.field, d1, d2);
  6495. switch (orderBy.dir) {
  6496. case "asc" /* Direction.ASCENDING */:
  6497. return comparison;
  6498. case "desc" /* Direction.DESCENDING */:
  6499. return -1 * comparison;
  6500. default:
  6501. return fail();
  6502. }
  6503. }
  6504. /**
  6505. * @license
  6506. * Copyright 2017 Google LLC
  6507. *
  6508. * Licensed under the Apache License, Version 2.0 (the "License");
  6509. * you may not use this file except in compliance with the License.
  6510. * You may obtain a copy of the License at
  6511. *
  6512. * http://www.apache.org/licenses/LICENSE-2.0
  6513. *
  6514. * Unless required by applicable law or agreed to in writing, software
  6515. * distributed under the License is distributed on an "AS IS" BASIS,
  6516. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6517. * See the License for the specific language governing permissions and
  6518. * limitations under the License.
  6519. */
  6520. /**
  6521. * A map implementation that uses objects as keys. Objects must have an
  6522. * associated equals function and must be immutable. Entries in the map are
  6523. * stored together with the key being produced from the mapKeyFn. This map
  6524. * automatically handles collisions of keys.
  6525. */
  6526. class ObjectMap {
  6527. constructor(mapKeyFn, equalsFn) {
  6528. this.mapKeyFn = mapKeyFn;
  6529. this.equalsFn = equalsFn;
  6530. /**
  6531. * The inner map for a key/value pair. Due to the possibility of collisions we
  6532. * keep a list of entries that we do a linear search through to find an actual
  6533. * match. Note that collisions should be rare, so we still expect near
  6534. * constant time lookups in practice.
  6535. */
  6536. this.inner = {};
  6537. /** The number of entries stored in the map */
  6538. this.innerSize = 0;
  6539. }
  6540. /** Get a value for this key, or undefined if it does not exist. */
  6541. get(key) {
  6542. const id = this.mapKeyFn(key);
  6543. const matches = this.inner[id];
  6544. if (matches === undefined) {
  6545. return undefined;
  6546. }
  6547. for (const [otherKey, value] of matches) {
  6548. if (this.equalsFn(otherKey, key)) {
  6549. return value;
  6550. }
  6551. }
  6552. return undefined;
  6553. }
  6554. has(key) {
  6555. return this.get(key) !== undefined;
  6556. }
  6557. /** Put this key and value in the map. */
  6558. set(key, value) {
  6559. const id = this.mapKeyFn(key);
  6560. const matches = this.inner[id];
  6561. if (matches === undefined) {
  6562. this.inner[id] = [[key, value]];
  6563. this.innerSize++;
  6564. return;
  6565. }
  6566. for (let i = 0; i < matches.length; i++) {
  6567. if (this.equalsFn(matches[i][0], key)) {
  6568. // This is updating an existing entry and does not increase `innerSize`.
  6569. matches[i] = [key, value];
  6570. return;
  6571. }
  6572. }
  6573. matches.push([key, value]);
  6574. this.innerSize++;
  6575. }
  6576. /**
  6577. * Remove this key from the map. Returns a boolean if anything was deleted.
  6578. */
  6579. delete(key) {
  6580. const id = this.mapKeyFn(key);
  6581. const matches = this.inner[id];
  6582. if (matches === undefined) {
  6583. return false;
  6584. }
  6585. for (let i = 0; i < matches.length; i++) {
  6586. if (this.equalsFn(matches[i][0], key)) {
  6587. if (matches.length === 1) {
  6588. delete this.inner[id];
  6589. }
  6590. else {
  6591. matches.splice(i, 1);
  6592. }
  6593. this.innerSize--;
  6594. return true;
  6595. }
  6596. }
  6597. return false;
  6598. }
  6599. forEach(fn) {
  6600. forEach(this.inner, (_, entries) => {
  6601. for (const [k, v] of entries) {
  6602. fn(k, v);
  6603. }
  6604. });
  6605. }
  6606. isEmpty() {
  6607. return isEmpty(this.inner);
  6608. }
  6609. size() {
  6610. return this.innerSize;
  6611. }
  6612. }
  6613. /**
  6614. * @license
  6615. * Copyright 2017 Google LLC
  6616. *
  6617. * Licensed under the Apache License, Version 2.0 (the "License");
  6618. * you may not use this file except in compliance with the License.
  6619. * You may obtain a copy of the License at
  6620. *
  6621. * http://www.apache.org/licenses/LICENSE-2.0
  6622. *
  6623. * Unless required by applicable law or agreed to in writing, software
  6624. * distributed under the License is distributed on an "AS IS" BASIS,
  6625. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6626. * See the License for the specific language governing permissions and
  6627. * limitations under the License.
  6628. */
  6629. const EMPTY_MUTABLE_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
  6630. function mutableDocumentMap() {
  6631. return EMPTY_MUTABLE_DOCUMENT_MAP;
  6632. }
  6633. const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
  6634. function documentMap(...docs) {
  6635. let map = EMPTY_DOCUMENT_MAP;
  6636. for (const doc of docs) {
  6637. map = map.insert(doc.key, doc);
  6638. }
  6639. return map;
  6640. }
  6641. function newOverlayedDocumentMap() {
  6642. return newDocumentKeyMap();
  6643. }
  6644. function convertOverlayedDocumentMapToDocumentMap(collection) {
  6645. let documents = EMPTY_DOCUMENT_MAP;
  6646. collection.forEach((k, v) => (documents = documents.insert(k, v.overlayedDocument)));
  6647. return documents;
  6648. }
  6649. function newOverlayMap() {
  6650. return newDocumentKeyMap();
  6651. }
  6652. function newMutationMap() {
  6653. return newDocumentKeyMap();
  6654. }
  6655. function newDocumentKeyMap() {
  6656. return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
  6657. }
  6658. const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
  6659. function documentVersionMap() {
  6660. return EMPTY_DOCUMENT_VERSION_MAP;
  6661. }
  6662. const EMPTY_DOCUMENT_KEY_SET = new SortedSet(DocumentKey.comparator);
  6663. function documentKeySet(...keys) {
  6664. let set = EMPTY_DOCUMENT_KEY_SET;
  6665. for (const key of keys) {
  6666. set = set.add(key);
  6667. }
  6668. return set;
  6669. }
  6670. const EMPTY_TARGET_ID_SET = new SortedSet(primitiveComparator);
  6671. function targetIdSet() {
  6672. return EMPTY_TARGET_ID_SET;
  6673. }
  6674. /**
  6675. * @license
  6676. * Copyright 2020 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. /**
  6691. * Returns an DoubleValue for `value` that is encoded based the serializer's
  6692. * `useProto3Json` setting.
  6693. */
  6694. function toDouble(serializer, value) {
  6695. if (serializer.useProto3Json) {
  6696. if (isNaN(value)) {
  6697. return { doubleValue: 'NaN' };
  6698. }
  6699. else if (value === Infinity) {
  6700. return { doubleValue: 'Infinity' };
  6701. }
  6702. else if (value === -Infinity) {
  6703. return { doubleValue: '-Infinity' };
  6704. }
  6705. }
  6706. return { doubleValue: isNegativeZero(value) ? '-0' : value };
  6707. }
  6708. /**
  6709. * Returns an IntegerValue for `value`.
  6710. */
  6711. function toInteger(value) {
  6712. return { integerValue: '' + value };
  6713. }
  6714. /**
  6715. * Returns a value for a number that's appropriate to put into a proto.
  6716. * The return value is an IntegerValue if it can safely represent the value,
  6717. * otherwise a DoubleValue is returned.
  6718. */
  6719. function toNumber(serializer, value) {
  6720. return isSafeInteger(value) ? toInteger(value) : toDouble(serializer, value);
  6721. }
  6722. /**
  6723. * @license
  6724. * Copyright 2018 Google LLC
  6725. *
  6726. * Licensed under the Apache License, Version 2.0 (the "License");
  6727. * you may not use this file except in compliance with the License.
  6728. * You may obtain a copy of the License at
  6729. *
  6730. * http://www.apache.org/licenses/LICENSE-2.0
  6731. *
  6732. * Unless required by applicable law or agreed to in writing, software
  6733. * distributed under the License is distributed on an "AS IS" BASIS,
  6734. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6735. * See the License for the specific language governing permissions and
  6736. * limitations under the License.
  6737. */
  6738. /** Used to represent a field transform on a mutation. */
  6739. class TransformOperation {
  6740. constructor() {
  6741. // Make sure that the structural type of `TransformOperation` is unique.
  6742. // See https://github.com/microsoft/TypeScript/issues/5451
  6743. this._ = undefined;
  6744. }
  6745. }
  6746. /**
  6747. * Computes the local transform result against the provided `previousValue`,
  6748. * optionally using the provided localWriteTime.
  6749. */
  6750. function applyTransformOperationToLocalView(transform, previousValue, localWriteTime) {
  6751. if (transform instanceof ServerTimestampTransform) {
  6752. return serverTimestamp$1(localWriteTime, previousValue);
  6753. }
  6754. else if (transform instanceof ArrayUnionTransformOperation) {
  6755. return applyArrayUnionTransformOperation(transform, previousValue);
  6756. }
  6757. else if (transform instanceof ArrayRemoveTransformOperation) {
  6758. return applyArrayRemoveTransformOperation(transform, previousValue);
  6759. }
  6760. else {
  6761. return applyNumericIncrementTransformOperationToLocalView(transform, previousValue);
  6762. }
  6763. }
  6764. /**
  6765. * Computes a final transform result after the transform has been acknowledged
  6766. * by the server, potentially using the server-provided transformResult.
  6767. */
  6768. function applyTransformOperationToRemoteDocument(transform, previousValue, transformResult) {
  6769. // The server just sends null as the transform result for array operations,
  6770. // so we have to calculate a result the same as we do for local
  6771. // applications.
  6772. if (transform instanceof ArrayUnionTransformOperation) {
  6773. return applyArrayUnionTransformOperation(transform, previousValue);
  6774. }
  6775. else if (transform instanceof ArrayRemoveTransformOperation) {
  6776. return applyArrayRemoveTransformOperation(transform, previousValue);
  6777. }
  6778. return transformResult;
  6779. }
  6780. /**
  6781. * If this transform operation is not idempotent, returns the base value to
  6782. * persist for this transform. If a base value is returned, the transform
  6783. * operation is always applied to this base value, even if document has
  6784. * already been updated.
  6785. *
  6786. * Base values provide consistent behavior for non-idempotent transforms and
  6787. * allow us to return the same latency-compensated value even if the backend
  6788. * has already applied the transform operation. The base value is null for
  6789. * idempotent transforms, as they can be re-played even if the backend has
  6790. * already applied them.
  6791. *
  6792. * @returns a base value to store along with the mutation, or null for
  6793. * idempotent transforms.
  6794. */
  6795. function computeTransformOperationBaseValue(transform, previousValue) {
  6796. if (transform instanceof NumericIncrementTransformOperation) {
  6797. return isNumber(previousValue) ? previousValue : { integerValue: 0 };
  6798. }
  6799. return null;
  6800. }
  6801. function transformOperationEquals(left, right) {
  6802. if (left instanceof ArrayUnionTransformOperation &&
  6803. right instanceof ArrayUnionTransformOperation) {
  6804. return arrayEquals(left.elements, right.elements, valueEquals);
  6805. }
  6806. else if (left instanceof ArrayRemoveTransformOperation &&
  6807. right instanceof ArrayRemoveTransformOperation) {
  6808. return arrayEquals(left.elements, right.elements, valueEquals);
  6809. }
  6810. else if (left instanceof NumericIncrementTransformOperation &&
  6811. right instanceof NumericIncrementTransformOperation) {
  6812. return valueEquals(left.operand, right.operand);
  6813. }
  6814. return (left instanceof ServerTimestampTransform &&
  6815. right instanceof ServerTimestampTransform);
  6816. }
  6817. /** Transforms a value into a server-generated timestamp. */
  6818. class ServerTimestampTransform extends TransformOperation {
  6819. }
  6820. /** Transforms an array value via a union operation. */
  6821. class ArrayUnionTransformOperation extends TransformOperation {
  6822. constructor(elements) {
  6823. super();
  6824. this.elements = elements;
  6825. }
  6826. }
  6827. function applyArrayUnionTransformOperation(transform, previousValue) {
  6828. const values = coercedFieldValuesArray(previousValue);
  6829. for (const toUnion of transform.elements) {
  6830. if (!values.some(element => valueEquals(element, toUnion))) {
  6831. values.push(toUnion);
  6832. }
  6833. }
  6834. return { arrayValue: { values } };
  6835. }
  6836. /** Transforms an array value via a remove operation. */
  6837. class ArrayRemoveTransformOperation extends TransformOperation {
  6838. constructor(elements) {
  6839. super();
  6840. this.elements = elements;
  6841. }
  6842. }
  6843. function applyArrayRemoveTransformOperation(transform, previousValue) {
  6844. let values = coercedFieldValuesArray(previousValue);
  6845. for (const toRemove of transform.elements) {
  6846. values = values.filter(element => !valueEquals(element, toRemove));
  6847. }
  6848. return { arrayValue: { values } };
  6849. }
  6850. /**
  6851. * Implements the backend semantics for locally computed NUMERIC_ADD (increment)
  6852. * transforms. Converts all field values to integers or doubles, but unlike the
  6853. * backend does not cap integer values at 2^63. Instead, JavaScript number
  6854. * arithmetic is used and precision loss can occur for values greater than 2^53.
  6855. */
  6856. class NumericIncrementTransformOperation extends TransformOperation {
  6857. constructor(serializer, operand) {
  6858. super();
  6859. this.serializer = serializer;
  6860. this.operand = operand;
  6861. }
  6862. }
  6863. function applyNumericIncrementTransformOperationToLocalView(transform, previousValue) {
  6864. // PORTING NOTE: Since JavaScript's integer arithmetic is limited to 53 bit
  6865. // precision and resolves overflows by reducing precision, we do not
  6866. // manually cap overflows at 2^63.
  6867. const baseValue = computeTransformOperationBaseValue(transform, previousValue);
  6868. const sum = asNumber(baseValue) + asNumber(transform.operand);
  6869. if (isInteger(baseValue) && isInteger(transform.operand)) {
  6870. return toInteger(sum);
  6871. }
  6872. else {
  6873. return toDouble(transform.serializer, sum);
  6874. }
  6875. }
  6876. function asNumber(value) {
  6877. return normalizeNumber(value.integerValue || value.doubleValue);
  6878. }
  6879. function coercedFieldValuesArray(value) {
  6880. return isArray(value) && value.arrayValue.values
  6881. ? value.arrayValue.values.slice()
  6882. : [];
  6883. }
  6884. /**
  6885. * @license
  6886. * Copyright 2017 Google LLC
  6887. *
  6888. * Licensed under the Apache License, Version 2.0 (the "License");
  6889. * you may not use this file except in compliance with the License.
  6890. * You may obtain a copy of the License at
  6891. *
  6892. * http://www.apache.org/licenses/LICENSE-2.0
  6893. *
  6894. * Unless required by applicable law or agreed to in writing, software
  6895. * distributed under the License is distributed on an "AS IS" BASIS,
  6896. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6897. * See the License for the specific language governing permissions and
  6898. * limitations under the License.
  6899. */
  6900. /** A field path and the TransformOperation to perform upon it. */
  6901. class FieldTransform {
  6902. constructor(field, transform) {
  6903. this.field = field;
  6904. this.transform = transform;
  6905. }
  6906. }
  6907. function fieldTransformEquals(left, right) {
  6908. return (left.field.isEqual(right.field) &&
  6909. transformOperationEquals(left.transform, right.transform));
  6910. }
  6911. function fieldTransformsAreEqual(left, right) {
  6912. if (left === undefined && right === undefined) {
  6913. return true;
  6914. }
  6915. if (left && right) {
  6916. return arrayEquals(left, right, (l, r) => fieldTransformEquals(l, r));
  6917. }
  6918. return false;
  6919. }
  6920. /** The result of successfully applying a mutation to the backend. */
  6921. class MutationResult {
  6922. constructor(
  6923. /**
  6924. * The version at which the mutation was committed:
  6925. *
  6926. * - For most operations, this is the updateTime in the WriteResult.
  6927. * - For deletes, the commitTime of the WriteResponse (because deletes are
  6928. * not stored and have no updateTime).
  6929. *
  6930. * Note that these versions can be different: No-op writes will not change
  6931. * the updateTime even though the commitTime advances.
  6932. */
  6933. version,
  6934. /**
  6935. * The resulting fields returned from the backend after a mutation
  6936. * containing field transforms has been committed. Contains one FieldValue
  6937. * for each FieldTransform that was in the mutation.
  6938. *
  6939. * Will be empty if the mutation did not contain any field transforms.
  6940. */
  6941. transformResults) {
  6942. this.version = version;
  6943. this.transformResults = transformResults;
  6944. }
  6945. }
  6946. /**
  6947. * Encodes a precondition for a mutation. This follows the model that the
  6948. * backend accepts with the special case of an explicit "empty" precondition
  6949. * (meaning no precondition).
  6950. */
  6951. class Precondition {
  6952. constructor(updateTime, exists) {
  6953. this.updateTime = updateTime;
  6954. this.exists = exists;
  6955. }
  6956. /** Creates a new empty Precondition. */
  6957. static none() {
  6958. return new Precondition();
  6959. }
  6960. /** Creates a new Precondition with an exists flag. */
  6961. static exists(exists) {
  6962. return new Precondition(undefined, exists);
  6963. }
  6964. /** Creates a new Precondition based on a version a document exists at. */
  6965. static updateTime(version) {
  6966. return new Precondition(version);
  6967. }
  6968. /** Returns whether this Precondition is empty. */
  6969. get isNone() {
  6970. return this.updateTime === undefined && this.exists === undefined;
  6971. }
  6972. isEqual(other) {
  6973. return (this.exists === other.exists &&
  6974. (this.updateTime
  6975. ? !!other.updateTime && this.updateTime.isEqual(other.updateTime)
  6976. : !other.updateTime));
  6977. }
  6978. }
  6979. /** Returns true if the preconditions is valid for the given document. */
  6980. function preconditionIsValidForDocument(precondition, document) {
  6981. if (precondition.updateTime !== undefined) {
  6982. return (document.isFoundDocument() &&
  6983. document.version.isEqual(precondition.updateTime));
  6984. }
  6985. else if (precondition.exists !== undefined) {
  6986. return precondition.exists === document.isFoundDocument();
  6987. }
  6988. else {
  6989. return true;
  6990. }
  6991. }
  6992. /**
  6993. * A mutation describes a self-contained change to a document. Mutations can
  6994. * create, replace, delete, and update subsets of documents.
  6995. *
  6996. * Mutations not only act on the value of the document but also its version.
  6997. *
  6998. * For local mutations (mutations that haven't been committed yet), we preserve
  6999. * the existing version for Set and Patch mutations. For Delete mutations, we
  7000. * reset the version to 0.
  7001. *
  7002. * Here's the expected transition table.
  7003. *
  7004. * MUTATION APPLIED TO RESULTS IN
  7005. *
  7006. * SetMutation Document(v3) Document(v3)
  7007. * SetMutation NoDocument(v3) Document(v0)
  7008. * SetMutation InvalidDocument(v0) Document(v0)
  7009. * PatchMutation Document(v3) Document(v3)
  7010. * PatchMutation NoDocument(v3) NoDocument(v3)
  7011. * PatchMutation InvalidDocument(v0) UnknownDocument(v3)
  7012. * DeleteMutation Document(v3) NoDocument(v0)
  7013. * DeleteMutation NoDocument(v3) NoDocument(v0)
  7014. * DeleteMutation InvalidDocument(v0) NoDocument(v0)
  7015. *
  7016. * For acknowledged mutations, we use the updateTime of the WriteResponse as
  7017. * the resulting version for Set and Patch mutations. As deletes have no
  7018. * explicit update time, we use the commitTime of the WriteResponse for
  7019. * Delete mutations.
  7020. *
  7021. * If a mutation is acknowledged by the backend but fails the precondition check
  7022. * locally, we transition to an `UnknownDocument` and rely on Watch to send us
  7023. * the updated version.
  7024. *
  7025. * Field transforms are used only with Patch and Set Mutations. We use the
  7026. * `updateTransforms` message to store transforms, rather than the `transforms`s
  7027. * messages.
  7028. *
  7029. * ## Subclassing Notes
  7030. *
  7031. * Every type of mutation needs to implement its own applyToRemoteDocument() and
  7032. * applyToLocalView() to implement the actual behavior of applying the mutation
  7033. * to some source document (see `setMutationApplyToRemoteDocument()` for an
  7034. * example).
  7035. */
  7036. class Mutation {
  7037. }
  7038. /**
  7039. * A utility method to calculate a `Mutation` representing the overlay from the
  7040. * final state of the document, and a `FieldMask` representing the fields that
  7041. * are mutated by the local mutations.
  7042. */
  7043. function calculateOverlayMutation(doc, mask) {
  7044. if (!doc.hasLocalMutations || (mask && mask.fields.length === 0)) {
  7045. return null;
  7046. }
  7047. // mask is null when sets or deletes are applied to the current document.
  7048. if (mask === null) {
  7049. if (doc.isNoDocument()) {
  7050. return new DeleteMutation(doc.key, Precondition.none());
  7051. }
  7052. else {
  7053. return new SetMutation(doc.key, doc.data, Precondition.none());
  7054. }
  7055. }
  7056. else {
  7057. const docValue = doc.data;
  7058. const patchValue = ObjectValue.empty();
  7059. let maskSet = new SortedSet(FieldPath$1.comparator);
  7060. for (let path of mask.fields) {
  7061. if (!maskSet.has(path)) {
  7062. let value = docValue.field(path);
  7063. // If we are deleting a nested field, we take the immediate parent as
  7064. // the mask used to construct the resulting mutation.
  7065. // Justification: Nested fields can create parent fields implicitly. If
  7066. // only a leaf entry is deleted in later mutations, the parent field
  7067. // should still remain, but we may have lost this information.
  7068. // Consider mutation (foo.bar 1), then mutation (foo.bar delete()).
  7069. // This leaves the final result (foo, {}). Despite the fact that `doc`
  7070. // has the correct result, `foo` is not in `mask`, and the resulting
  7071. // mutation would miss `foo`.
  7072. if (value === null && path.length > 1) {
  7073. path = path.popLast();
  7074. value = docValue.field(path);
  7075. }
  7076. if (value === null) {
  7077. patchValue.delete(path);
  7078. }
  7079. else {
  7080. patchValue.set(path, value);
  7081. }
  7082. maskSet = maskSet.add(path);
  7083. }
  7084. }
  7085. return new PatchMutation(doc.key, patchValue, new FieldMask(maskSet.toArray()), Precondition.none());
  7086. }
  7087. }
  7088. /**
  7089. * Applies this mutation to the given document for the purposes of computing a
  7090. * new remote document. If the input document doesn't match the expected state
  7091. * (e.g. it is invalid or outdated), the document type may transition to
  7092. * unknown.
  7093. *
  7094. * @param mutation - The mutation to apply.
  7095. * @param document - The document to mutate. The input document can be an
  7096. * invalid document if the client has no knowledge of the pre-mutation state
  7097. * of the document.
  7098. * @param mutationResult - The result of applying the mutation from the backend.
  7099. */
  7100. function mutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7101. if (mutation instanceof SetMutation) {
  7102. setMutationApplyToRemoteDocument(mutation, document, mutationResult);
  7103. }
  7104. else if (mutation instanceof PatchMutation) {
  7105. patchMutationApplyToRemoteDocument(mutation, document, mutationResult);
  7106. }
  7107. else {
  7108. deleteMutationApplyToRemoteDocument(mutation, document, mutationResult);
  7109. }
  7110. }
  7111. /**
  7112. * Applies this mutation to the given document for the purposes of computing
  7113. * the new local view of a document. If the input document doesn't match the
  7114. * expected state, the document is not modified.
  7115. *
  7116. * @param mutation - The mutation to apply.
  7117. * @param document - The document to mutate. The input document can be an
  7118. * invalid document if the client has no knowledge of the pre-mutation state
  7119. * of the document.
  7120. * @param previousMask - The fields that have been updated before applying this mutation.
  7121. * @param localWriteTime - A timestamp indicating the local write time of the
  7122. * batch this mutation is a part of.
  7123. * @returns A `FieldMask` representing the fields that are changed by applying this mutation.
  7124. */
  7125. function mutationApplyToLocalView(mutation, document, previousMask, localWriteTime) {
  7126. if (mutation instanceof SetMutation) {
  7127. return setMutationApplyToLocalView(mutation, document, previousMask, localWriteTime);
  7128. }
  7129. else if (mutation instanceof PatchMutation) {
  7130. return patchMutationApplyToLocalView(mutation, document, previousMask, localWriteTime);
  7131. }
  7132. else {
  7133. return deleteMutationApplyToLocalView(mutation, document, previousMask);
  7134. }
  7135. }
  7136. /**
  7137. * If this mutation is not idempotent, returns the base value to persist with
  7138. * this mutation. If a base value is returned, the mutation is always applied
  7139. * to this base value, even if document has already been updated.
  7140. *
  7141. * The base value is a sparse object that consists of only the document
  7142. * fields for which this mutation contains a non-idempotent transformation
  7143. * (e.g. a numeric increment). The provided value guarantees consistent
  7144. * behavior for non-idempotent transforms and allow us to return the same
  7145. * latency-compensated value even if the backend has already applied the
  7146. * mutation. The base value is null for idempotent mutations, as they can be
  7147. * re-played even if the backend has already applied them.
  7148. *
  7149. * @returns a base value to store along with the mutation, or null for
  7150. * idempotent mutations.
  7151. */
  7152. function mutationExtractBaseValue(mutation, document) {
  7153. let baseObject = null;
  7154. for (const fieldTransform of mutation.fieldTransforms) {
  7155. const existingValue = document.data.field(fieldTransform.field);
  7156. const coercedValue = computeTransformOperationBaseValue(fieldTransform.transform, existingValue || null);
  7157. if (coercedValue != null) {
  7158. if (baseObject === null) {
  7159. baseObject = ObjectValue.empty();
  7160. }
  7161. baseObject.set(fieldTransform.field, coercedValue);
  7162. }
  7163. }
  7164. return baseObject ? baseObject : null;
  7165. }
  7166. function mutationEquals(left, right) {
  7167. if (left.type !== right.type) {
  7168. return false;
  7169. }
  7170. if (!left.key.isEqual(right.key)) {
  7171. return false;
  7172. }
  7173. if (!left.precondition.isEqual(right.precondition)) {
  7174. return false;
  7175. }
  7176. if (!fieldTransformsAreEqual(left.fieldTransforms, right.fieldTransforms)) {
  7177. return false;
  7178. }
  7179. if (left.type === 0 /* MutationType.Set */) {
  7180. return left.value.isEqual(right.value);
  7181. }
  7182. if (left.type === 1 /* MutationType.Patch */) {
  7183. return (left.data.isEqual(right.data) &&
  7184. left.fieldMask.isEqual(right.fieldMask));
  7185. }
  7186. return true;
  7187. }
  7188. /**
  7189. * A mutation that creates or replaces the document at the given key with the
  7190. * object value contents.
  7191. */
  7192. class SetMutation extends Mutation {
  7193. constructor(key, value, precondition, fieldTransforms = []) {
  7194. super();
  7195. this.key = key;
  7196. this.value = value;
  7197. this.precondition = precondition;
  7198. this.fieldTransforms = fieldTransforms;
  7199. this.type = 0 /* MutationType.Set */;
  7200. }
  7201. getFieldMask() {
  7202. return null;
  7203. }
  7204. }
  7205. function setMutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7206. // Unlike setMutationApplyToLocalView, if we're applying a mutation to a
  7207. // remote document the server has accepted the mutation so the precondition
  7208. // must have held.
  7209. const newData = mutation.value.clone();
  7210. const transformResults = serverTransformResults(mutation.fieldTransforms, document, mutationResult.transformResults);
  7211. newData.setAll(transformResults);
  7212. document
  7213. .convertToFoundDocument(mutationResult.version, newData)
  7214. .setHasCommittedMutations();
  7215. }
  7216. function setMutationApplyToLocalView(mutation, document, previousMask, localWriteTime) {
  7217. if (!preconditionIsValidForDocument(mutation.precondition, document)) {
  7218. // The mutation failed to apply (e.g. a document ID created with add()
  7219. // caused a name collision).
  7220. return previousMask;
  7221. }
  7222. const newData = mutation.value.clone();
  7223. const transformResults = localTransformResults(mutation.fieldTransforms, localWriteTime, document);
  7224. newData.setAll(transformResults);
  7225. document
  7226. .convertToFoundDocument(document.version, newData)
  7227. .setHasLocalMutations();
  7228. return null; // SetMutation overwrites all fields.
  7229. }
  7230. /**
  7231. * A mutation that modifies fields of the document at the given key with the
  7232. * given values. The values are applied through a field mask:
  7233. *
  7234. * * When a field is in both the mask and the values, the corresponding field
  7235. * is updated.
  7236. * * When a field is in neither the mask nor the values, the corresponding
  7237. * field is unmodified.
  7238. * * When a field is in the mask but not in the values, the corresponding field
  7239. * is deleted.
  7240. * * When a field is not in the mask but is in the values, the values map is
  7241. * ignored.
  7242. */
  7243. class PatchMutation extends Mutation {
  7244. constructor(key, data, fieldMask, precondition, fieldTransforms = []) {
  7245. super();
  7246. this.key = key;
  7247. this.data = data;
  7248. this.fieldMask = fieldMask;
  7249. this.precondition = precondition;
  7250. this.fieldTransforms = fieldTransforms;
  7251. this.type = 1 /* MutationType.Patch */;
  7252. }
  7253. getFieldMask() {
  7254. return this.fieldMask;
  7255. }
  7256. }
  7257. function patchMutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7258. if (!preconditionIsValidForDocument(mutation.precondition, document)) {
  7259. // Since the mutation was not rejected, we know that the precondition
  7260. // matched on the backend. We therefore must not have the expected version
  7261. // of the document in our cache and convert to an UnknownDocument with a
  7262. // known updateTime.
  7263. document.convertToUnknownDocument(mutationResult.version);
  7264. return;
  7265. }
  7266. const transformResults = serverTransformResults(mutation.fieldTransforms, document, mutationResult.transformResults);
  7267. const newData = document.data;
  7268. newData.setAll(getPatch(mutation));
  7269. newData.setAll(transformResults);
  7270. document
  7271. .convertToFoundDocument(mutationResult.version, newData)
  7272. .setHasCommittedMutations();
  7273. }
  7274. function patchMutationApplyToLocalView(mutation, document, previousMask, localWriteTime) {
  7275. if (!preconditionIsValidForDocument(mutation.precondition, document)) {
  7276. return previousMask;
  7277. }
  7278. const transformResults = localTransformResults(mutation.fieldTransforms, localWriteTime, document);
  7279. const newData = document.data;
  7280. newData.setAll(getPatch(mutation));
  7281. newData.setAll(transformResults);
  7282. document
  7283. .convertToFoundDocument(document.version, newData)
  7284. .setHasLocalMutations();
  7285. if (previousMask === null) {
  7286. return null;
  7287. }
  7288. return previousMask
  7289. .unionWith(mutation.fieldMask.fields)
  7290. .unionWith(mutation.fieldTransforms.map(transform => transform.field));
  7291. }
  7292. /**
  7293. * Returns a FieldPath/Value map with the content of the PatchMutation.
  7294. */
  7295. function getPatch(mutation) {
  7296. const result = new Map();
  7297. mutation.fieldMask.fields.forEach(fieldPath => {
  7298. if (!fieldPath.isEmpty()) {
  7299. const newValue = mutation.data.field(fieldPath);
  7300. result.set(fieldPath, newValue);
  7301. }
  7302. });
  7303. return result;
  7304. }
  7305. /**
  7306. * Creates a list of "transform results" (a transform result is a field value
  7307. * representing the result of applying a transform) for use after a mutation
  7308. * containing transforms has been acknowledged by the server.
  7309. *
  7310. * @param fieldTransforms - The field transforms to apply the result to.
  7311. * @param mutableDocument - The current state of the document after applying all
  7312. * previous mutations.
  7313. * @param serverTransformResults - The transform results received by the server.
  7314. * @returns The transform results list.
  7315. */
  7316. function serverTransformResults(fieldTransforms, mutableDocument, serverTransformResults) {
  7317. const transformResults = new Map();
  7318. hardAssert(fieldTransforms.length === serverTransformResults.length);
  7319. for (let i = 0; i < serverTransformResults.length; i++) {
  7320. const fieldTransform = fieldTransforms[i];
  7321. const transform = fieldTransform.transform;
  7322. const previousValue = mutableDocument.data.field(fieldTransform.field);
  7323. transformResults.set(fieldTransform.field, applyTransformOperationToRemoteDocument(transform, previousValue, serverTransformResults[i]));
  7324. }
  7325. return transformResults;
  7326. }
  7327. /**
  7328. * Creates a list of "transform results" (a transform result is a field value
  7329. * representing the result of applying a transform) for use when applying a
  7330. * transform locally.
  7331. *
  7332. * @param fieldTransforms - The field transforms to apply the result to.
  7333. * @param localWriteTime - The local time of the mutation (used to
  7334. * generate ServerTimestampValues).
  7335. * @param mutableDocument - The document to apply transforms on.
  7336. * @returns The transform results list.
  7337. */
  7338. function localTransformResults(fieldTransforms, localWriteTime, mutableDocument) {
  7339. const transformResults = new Map();
  7340. for (const fieldTransform of fieldTransforms) {
  7341. const transform = fieldTransform.transform;
  7342. const previousValue = mutableDocument.data.field(fieldTransform.field);
  7343. transformResults.set(fieldTransform.field, applyTransformOperationToLocalView(transform, previousValue, localWriteTime));
  7344. }
  7345. return transformResults;
  7346. }
  7347. /** A mutation that deletes the document at the given key. */
  7348. class DeleteMutation extends Mutation {
  7349. constructor(key, precondition) {
  7350. super();
  7351. this.key = key;
  7352. this.precondition = precondition;
  7353. this.type = 2 /* MutationType.Delete */;
  7354. this.fieldTransforms = [];
  7355. }
  7356. getFieldMask() {
  7357. return null;
  7358. }
  7359. }
  7360. function deleteMutationApplyToRemoteDocument(mutation, document, mutationResult) {
  7361. // Unlike applyToLocalView, if we're applying a mutation to a remote
  7362. // document the server has accepted the mutation so the precondition must
  7363. // have held.
  7364. document
  7365. .convertToNoDocument(mutationResult.version)
  7366. .setHasCommittedMutations();
  7367. }
  7368. function deleteMutationApplyToLocalView(mutation, document, previousMask) {
  7369. if (preconditionIsValidForDocument(mutation.precondition, document)) {
  7370. document.convertToNoDocument(document.version).setHasLocalMutations();
  7371. return null;
  7372. }
  7373. return previousMask;
  7374. }
  7375. /**
  7376. * A mutation that verifies the existence of the document at the given key with
  7377. * the provided precondition.
  7378. *
  7379. * The `verify` operation is only used in Transactions, and this class serves
  7380. * primarily to facilitate serialization into protos.
  7381. */
  7382. class VerifyMutation extends Mutation {
  7383. constructor(key, precondition) {
  7384. super();
  7385. this.key = key;
  7386. this.precondition = precondition;
  7387. this.type = 3 /* MutationType.Verify */;
  7388. this.fieldTransforms = [];
  7389. }
  7390. getFieldMask() {
  7391. return null;
  7392. }
  7393. }
  7394. /**
  7395. * @license
  7396. * Copyright 2017 Google LLC
  7397. *
  7398. * Licensed under the Apache License, Version 2.0 (the "License");
  7399. * you may not use this file except in compliance with the License.
  7400. * You may obtain a copy of the License at
  7401. *
  7402. * http://www.apache.org/licenses/LICENSE-2.0
  7403. *
  7404. * Unless required by applicable law or agreed to in writing, software
  7405. * distributed under the License is distributed on an "AS IS" BASIS,
  7406. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7407. * See the License for the specific language governing permissions and
  7408. * limitations under the License.
  7409. */
  7410. /**
  7411. * A batch of mutations that will be sent as one unit to the backend.
  7412. */
  7413. class MutationBatch {
  7414. /**
  7415. * @param batchId - The unique ID of this mutation batch.
  7416. * @param localWriteTime - The original write time of this mutation.
  7417. * @param baseMutations - Mutations that are used to populate the base
  7418. * values when this mutation is applied locally. This can be used to locally
  7419. * overwrite values that are persisted in the remote document cache. Base
  7420. * mutations are never sent to the backend.
  7421. * @param mutations - The user-provided mutations in this mutation batch.
  7422. * User-provided mutations are applied both locally and remotely on the
  7423. * backend.
  7424. */
  7425. constructor(batchId, localWriteTime, baseMutations, mutations) {
  7426. this.batchId = batchId;
  7427. this.localWriteTime = localWriteTime;
  7428. this.baseMutations = baseMutations;
  7429. this.mutations = mutations;
  7430. }
  7431. /**
  7432. * Applies all the mutations in this MutationBatch to the specified document
  7433. * to compute the state of the remote document
  7434. *
  7435. * @param document - The document to apply mutations to.
  7436. * @param batchResult - The result of applying the MutationBatch to the
  7437. * backend.
  7438. */
  7439. applyToRemoteDocument(document, batchResult) {
  7440. const mutationResults = batchResult.mutationResults;
  7441. for (let i = 0; i < this.mutations.length; i++) {
  7442. const mutation = this.mutations[i];
  7443. if (mutation.key.isEqual(document.key)) {
  7444. const mutationResult = mutationResults[i];
  7445. mutationApplyToRemoteDocument(mutation, document, mutationResult);
  7446. }
  7447. }
  7448. }
  7449. /**
  7450. * Computes the local view of a document given all the mutations in this
  7451. * batch.
  7452. *
  7453. * @param document - The document to apply mutations to.
  7454. * @param mutatedFields - Fields that have been updated before applying this mutation batch.
  7455. * @returns A `FieldMask` representing all the fields that are mutated.
  7456. */
  7457. applyToLocalView(document, mutatedFields) {
  7458. // First, apply the base state. This allows us to apply non-idempotent
  7459. // transform against a consistent set of values.
  7460. for (const mutation of this.baseMutations) {
  7461. if (mutation.key.isEqual(document.key)) {
  7462. mutatedFields = mutationApplyToLocalView(mutation, document, mutatedFields, this.localWriteTime);
  7463. }
  7464. }
  7465. // Second, apply all user-provided mutations.
  7466. for (const mutation of this.mutations) {
  7467. if (mutation.key.isEqual(document.key)) {
  7468. mutatedFields = mutationApplyToLocalView(mutation, document, mutatedFields, this.localWriteTime);
  7469. }
  7470. }
  7471. return mutatedFields;
  7472. }
  7473. /**
  7474. * Computes the local view for all provided documents given the mutations in
  7475. * this batch. Returns a `DocumentKey` to `Mutation` map which can be used to
  7476. * replace all the mutation applications.
  7477. */
  7478. applyToLocalDocumentSet(documentMap, documentsWithoutRemoteVersion) {
  7479. // TODO(mrschmidt): This implementation is O(n^2). If we apply the mutations
  7480. // directly (as done in `applyToLocalView()`), we can reduce the complexity
  7481. // to O(n).
  7482. const overlays = newMutationMap();
  7483. this.mutations.forEach(m => {
  7484. const overlayedDocument = documentMap.get(m.key);
  7485. // TODO(mutabledocuments): This method should take a MutableDocumentMap
  7486. // and we should remove this cast.
  7487. const mutableDocument = overlayedDocument.overlayedDocument;
  7488. let mutatedFields = this.applyToLocalView(mutableDocument, overlayedDocument.mutatedFields);
  7489. // Set mutatedFields to null if the document is only from local mutations.
  7490. // This creates a Set or Delete mutation, instead of trying to create a
  7491. // patch mutation as the overlay.
  7492. mutatedFields = documentsWithoutRemoteVersion.has(m.key)
  7493. ? null
  7494. : mutatedFields;
  7495. const overlay = calculateOverlayMutation(mutableDocument, mutatedFields);
  7496. if (overlay !== null) {
  7497. overlays.set(m.key, overlay);
  7498. }
  7499. if (!mutableDocument.isValidDocument()) {
  7500. mutableDocument.convertToNoDocument(SnapshotVersion.min());
  7501. }
  7502. });
  7503. return overlays;
  7504. }
  7505. keys() {
  7506. return this.mutations.reduce((keys, m) => keys.add(m.key), documentKeySet());
  7507. }
  7508. isEqual(other) {
  7509. return (this.batchId === other.batchId &&
  7510. arrayEquals(this.mutations, other.mutations, (l, r) => mutationEquals(l, r)) &&
  7511. arrayEquals(this.baseMutations, other.baseMutations, (l, r) => mutationEquals(l, r)));
  7512. }
  7513. }
  7514. /** The result of applying a mutation batch to the backend. */
  7515. class MutationBatchResult {
  7516. constructor(batch, commitVersion, mutationResults,
  7517. /**
  7518. * A pre-computed mapping from each mutated document to the resulting
  7519. * version.
  7520. */
  7521. docVersions) {
  7522. this.batch = batch;
  7523. this.commitVersion = commitVersion;
  7524. this.mutationResults = mutationResults;
  7525. this.docVersions = docVersions;
  7526. }
  7527. /**
  7528. * Creates a new MutationBatchResult for the given batch and results. There
  7529. * must be one result for each mutation in the batch. This static factory
  7530. * caches a document=&gt;version mapping (docVersions).
  7531. */
  7532. static from(batch, commitVersion, results) {
  7533. hardAssert(batch.mutations.length === results.length);
  7534. let versionMap = documentVersionMap();
  7535. const mutations = batch.mutations;
  7536. for (let i = 0; i < mutations.length; i++) {
  7537. versionMap = versionMap.insert(mutations[i].key, results[i].version);
  7538. }
  7539. return new MutationBatchResult(batch, commitVersion, results, versionMap);
  7540. }
  7541. }
  7542. /**
  7543. * @license
  7544. * Copyright 2022 Google LLC
  7545. *
  7546. * Licensed under the Apache License, Version 2.0 (the "License");
  7547. * you may not use this file except in compliance with the License.
  7548. * You may obtain a copy of the License at
  7549. *
  7550. * http://www.apache.org/licenses/LICENSE-2.0
  7551. *
  7552. * Unless required by applicable law or agreed to in writing, software
  7553. * distributed under the License is distributed on an "AS IS" BASIS,
  7554. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7555. * See the License for the specific language governing permissions and
  7556. * limitations under the License.
  7557. */
  7558. /**
  7559. * Representation of an overlay computed by Firestore.
  7560. *
  7561. * Holds information about a mutation and the largest batch id in Firestore when
  7562. * the mutation was created.
  7563. */
  7564. class Overlay {
  7565. constructor(largestBatchId, mutation) {
  7566. this.largestBatchId = largestBatchId;
  7567. this.mutation = mutation;
  7568. }
  7569. getKey() {
  7570. return this.mutation.key;
  7571. }
  7572. isEqual(other) {
  7573. return other !== null && this.mutation === other.mutation;
  7574. }
  7575. toString() {
  7576. return `Overlay{
  7577. largestBatchId: ${this.largestBatchId},
  7578. mutation: ${this.mutation.toString()}
  7579. }`;
  7580. }
  7581. }
  7582. /**
  7583. * @license
  7584. * Copyright 2017 Google LLC
  7585. *
  7586. * Licensed under the Apache License, Version 2.0 (the "License");
  7587. * you may not use this file except in compliance with the License.
  7588. * You may obtain a copy of the License at
  7589. *
  7590. * http://www.apache.org/licenses/LICENSE-2.0
  7591. *
  7592. * Unless required by applicable law or agreed to in writing, software
  7593. * distributed under the License is distributed on an "AS IS" BASIS,
  7594. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7595. * See the License for the specific language governing permissions and
  7596. * limitations under the License.
  7597. */
  7598. class ExistenceFilter {
  7599. constructor(count, unchangedNames) {
  7600. this.count = count;
  7601. this.unchangedNames = unchangedNames;
  7602. }
  7603. }
  7604. /**
  7605. * @license
  7606. * Copyright 2017 Google LLC
  7607. *
  7608. * Licensed under the Apache License, Version 2.0 (the "License");
  7609. * you may not use this file except in compliance with the License.
  7610. * You may obtain a copy of the License at
  7611. *
  7612. * http://www.apache.org/licenses/LICENSE-2.0
  7613. *
  7614. * Unless required by applicable law or agreed to in writing, software
  7615. * distributed under the License is distributed on an "AS IS" BASIS,
  7616. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7617. * See the License for the specific language governing permissions and
  7618. * limitations under the License.
  7619. */
  7620. /**
  7621. * Error Codes describing the different ways GRPC can fail. These are copied
  7622. * directly from GRPC's sources here:
  7623. *
  7624. * https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h
  7625. *
  7626. * Important! The names of these identifiers matter because the string forms
  7627. * are used for reverse lookups from the webchannel stream. Do NOT change the
  7628. * names of these identifiers or change this into a const enum.
  7629. */
  7630. var RpcCode;
  7631. (function (RpcCode) {
  7632. RpcCode[RpcCode["OK"] = 0] = "OK";
  7633. RpcCode[RpcCode["CANCELLED"] = 1] = "CANCELLED";
  7634. RpcCode[RpcCode["UNKNOWN"] = 2] = "UNKNOWN";
  7635. RpcCode[RpcCode["INVALID_ARGUMENT"] = 3] = "INVALID_ARGUMENT";
  7636. RpcCode[RpcCode["DEADLINE_EXCEEDED"] = 4] = "DEADLINE_EXCEEDED";
  7637. RpcCode[RpcCode["NOT_FOUND"] = 5] = "NOT_FOUND";
  7638. RpcCode[RpcCode["ALREADY_EXISTS"] = 6] = "ALREADY_EXISTS";
  7639. RpcCode[RpcCode["PERMISSION_DENIED"] = 7] = "PERMISSION_DENIED";
  7640. RpcCode[RpcCode["UNAUTHENTICATED"] = 16] = "UNAUTHENTICATED";
  7641. RpcCode[RpcCode["RESOURCE_EXHAUSTED"] = 8] = "RESOURCE_EXHAUSTED";
  7642. RpcCode[RpcCode["FAILED_PRECONDITION"] = 9] = "FAILED_PRECONDITION";
  7643. RpcCode[RpcCode["ABORTED"] = 10] = "ABORTED";
  7644. RpcCode[RpcCode["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE";
  7645. RpcCode[RpcCode["UNIMPLEMENTED"] = 12] = "UNIMPLEMENTED";
  7646. RpcCode[RpcCode["INTERNAL"] = 13] = "INTERNAL";
  7647. RpcCode[RpcCode["UNAVAILABLE"] = 14] = "UNAVAILABLE";
  7648. RpcCode[RpcCode["DATA_LOSS"] = 15] = "DATA_LOSS";
  7649. })(RpcCode || (RpcCode = {}));
  7650. /**
  7651. * Determines whether an error code represents a permanent error when received
  7652. * in response to a non-write operation.
  7653. *
  7654. * See isPermanentWriteError for classifying write errors.
  7655. */
  7656. function isPermanentError(code) {
  7657. switch (code) {
  7658. case Code.OK:
  7659. return fail();
  7660. case Code.CANCELLED:
  7661. case Code.UNKNOWN:
  7662. case Code.DEADLINE_EXCEEDED:
  7663. case Code.RESOURCE_EXHAUSTED:
  7664. case Code.INTERNAL:
  7665. case Code.UNAVAILABLE:
  7666. // Unauthenticated means something went wrong with our token and we need
  7667. // to retry with new credentials which will happen automatically.
  7668. case Code.UNAUTHENTICATED:
  7669. return false;
  7670. case Code.INVALID_ARGUMENT:
  7671. case Code.NOT_FOUND:
  7672. case Code.ALREADY_EXISTS:
  7673. case Code.PERMISSION_DENIED:
  7674. case Code.FAILED_PRECONDITION:
  7675. // Aborted might be retried in some scenarios, but that is dependant on
  7676. // the context and should handled individually by the calling code.
  7677. // See https://cloud.google.com/apis/design/errors.
  7678. case Code.ABORTED:
  7679. case Code.OUT_OF_RANGE:
  7680. case Code.UNIMPLEMENTED:
  7681. case Code.DATA_LOSS:
  7682. return true;
  7683. default:
  7684. return fail();
  7685. }
  7686. }
  7687. /**
  7688. * Determines whether an error code represents a permanent error when received
  7689. * in response to a write operation.
  7690. *
  7691. * Write operations must be handled specially because as of b/119437764, ABORTED
  7692. * errors on the write stream should be retried too (even though ABORTED errors
  7693. * are not generally retryable).
  7694. *
  7695. * Note that during the initial handshake on the write stream an ABORTED error
  7696. * signals that we should discard our stream token (i.e. it is permanent). This
  7697. * means a handshake error should be classified with isPermanentError, above.
  7698. */
  7699. function isPermanentWriteError(code) {
  7700. return isPermanentError(code) && code !== Code.ABORTED;
  7701. }
  7702. /**
  7703. * Maps an error Code from GRPC status code number, like 0, 1, or 14. These
  7704. * are not the same as HTTP status codes.
  7705. *
  7706. * @returns The Code equivalent to the given GRPC status code. Fails if there
  7707. * is no match.
  7708. */
  7709. function mapCodeFromRpcCode(code) {
  7710. if (code === undefined) {
  7711. // This shouldn't normally happen, but in certain error cases (like trying
  7712. // to send invalid proto messages) we may get an error with no GRPC code.
  7713. logError('GRPC error has no .code');
  7714. return Code.UNKNOWN;
  7715. }
  7716. switch (code) {
  7717. case RpcCode.OK:
  7718. return Code.OK;
  7719. case RpcCode.CANCELLED:
  7720. return Code.CANCELLED;
  7721. case RpcCode.UNKNOWN:
  7722. return Code.UNKNOWN;
  7723. case RpcCode.DEADLINE_EXCEEDED:
  7724. return Code.DEADLINE_EXCEEDED;
  7725. case RpcCode.RESOURCE_EXHAUSTED:
  7726. return Code.RESOURCE_EXHAUSTED;
  7727. case RpcCode.INTERNAL:
  7728. return Code.INTERNAL;
  7729. case RpcCode.UNAVAILABLE:
  7730. return Code.UNAVAILABLE;
  7731. case RpcCode.UNAUTHENTICATED:
  7732. return Code.UNAUTHENTICATED;
  7733. case RpcCode.INVALID_ARGUMENT:
  7734. return Code.INVALID_ARGUMENT;
  7735. case RpcCode.NOT_FOUND:
  7736. return Code.NOT_FOUND;
  7737. case RpcCode.ALREADY_EXISTS:
  7738. return Code.ALREADY_EXISTS;
  7739. case RpcCode.PERMISSION_DENIED:
  7740. return Code.PERMISSION_DENIED;
  7741. case RpcCode.FAILED_PRECONDITION:
  7742. return Code.FAILED_PRECONDITION;
  7743. case RpcCode.ABORTED:
  7744. return Code.ABORTED;
  7745. case RpcCode.OUT_OF_RANGE:
  7746. return Code.OUT_OF_RANGE;
  7747. case RpcCode.UNIMPLEMENTED:
  7748. return Code.UNIMPLEMENTED;
  7749. case RpcCode.DATA_LOSS:
  7750. return Code.DATA_LOSS;
  7751. default:
  7752. return fail();
  7753. }
  7754. }
  7755. /**
  7756. * @license
  7757. * Copyright 2023 Google LLC
  7758. *
  7759. * Licensed under the Apache License, Version 2.0 (the "License");
  7760. * you may not use this file except in compliance with the License.
  7761. * You may obtain a copy of the License at
  7762. *
  7763. * http://www.apache.org/licenses/LICENSE-2.0
  7764. *
  7765. * Unless required by applicable law or agreed to in writing, software
  7766. * distributed under the License is distributed on an "AS IS" BASIS,
  7767. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7768. * See the License for the specific language governing permissions and
  7769. * limitations under the License.
  7770. */
  7771. /**
  7772. * An error encountered while decoding base64 string.
  7773. */
  7774. class Base64DecodeError extends Error {
  7775. constructor() {
  7776. super(...arguments);
  7777. this.name = 'Base64DecodeError';
  7778. }
  7779. }
  7780. /**
  7781. * @license
  7782. * Copyright 2023 Google LLC
  7783. *
  7784. * Licensed under the Apache License, Version 2.0 (the "License");
  7785. * you may not use this file except in compliance with the License.
  7786. * You may obtain a copy of the License at
  7787. *
  7788. * http://www.apache.org/licenses/LICENSE-2.0
  7789. *
  7790. * Unless required by applicable law or agreed to in writing, software
  7791. * distributed under the License is distributed on an "AS IS" BASIS,
  7792. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7793. * See the License for the specific language governing permissions and
  7794. * limitations under the License.
  7795. */
  7796. /**
  7797. * Manages "testing hooks", hooks into the internals of the SDK to verify
  7798. * internal state and events during integration tests. Do not use this class
  7799. * except for testing purposes.
  7800. *
  7801. * There are two ways to retrieve the global singleton instance of this class:
  7802. * 1. The `instance` property, which returns null if the global singleton
  7803. * instance has not been created. Use this property if the caller should
  7804. * "do nothing" if there are no testing hooks registered, such as when
  7805. * delivering an event to notify registered callbacks.
  7806. * 2. The `getOrCreateInstance()` method, which creates the global singleton
  7807. * instance if it has not been created. Use this method if the instance is
  7808. * needed to, for example, register a callback.
  7809. *
  7810. * @internal
  7811. */
  7812. class TestingHooks {
  7813. constructor() {
  7814. this.onExistenceFilterMismatchCallbacks = new Map();
  7815. }
  7816. /**
  7817. * Returns the singleton instance of this class, or null if it has not been
  7818. * initialized.
  7819. */
  7820. static get instance() {
  7821. return gTestingHooksSingletonInstance;
  7822. }
  7823. /**
  7824. * Returns the singleton instance of this class, creating it if is has never
  7825. * been created before.
  7826. */
  7827. static getOrCreateInstance() {
  7828. if (gTestingHooksSingletonInstance === null) {
  7829. gTestingHooksSingletonInstance = new TestingHooks();
  7830. }
  7831. return gTestingHooksSingletonInstance;
  7832. }
  7833. /**
  7834. * Registers a callback to be notified when an existence filter mismatch
  7835. * occurs in the Watch listen stream.
  7836. *
  7837. * The relative order in which callbacks are notified is unspecified; do not
  7838. * rely on any particular ordering. If a given callback is registered multiple
  7839. * times then it will be notified multiple times, once per registration.
  7840. *
  7841. * @param callback the callback to invoke upon existence filter mismatch.
  7842. *
  7843. * @return a function that, when called, unregisters the given callback; only
  7844. * the first invocation of the returned function does anything; all subsequent
  7845. * invocations do nothing.
  7846. */
  7847. onExistenceFilterMismatch(callback) {
  7848. const key = Symbol();
  7849. this.onExistenceFilterMismatchCallbacks.set(key, callback);
  7850. return () => this.onExistenceFilterMismatchCallbacks.delete(key);
  7851. }
  7852. /**
  7853. * Invokes all currently-registered `onExistenceFilterMismatch` callbacks.
  7854. * @param info Information about the existence filter mismatch.
  7855. */
  7856. notifyOnExistenceFilterMismatch(info) {
  7857. this.onExistenceFilterMismatchCallbacks.forEach(callback => callback(info));
  7858. }
  7859. }
  7860. /** The global singleton instance of `TestingHooks`. */
  7861. let gTestingHooksSingletonInstance = null;
  7862. /**
  7863. * @license
  7864. * Copyright 2023 Google LLC
  7865. *
  7866. * Licensed under the Apache License, Version 2.0 (the "License");
  7867. * you may not use this file except in compliance with the License.
  7868. * You may obtain a copy of the License at
  7869. *
  7870. * http://www.apache.org/licenses/LICENSE-2.0
  7871. *
  7872. * Unless required by applicable law or agreed to in writing, software
  7873. * distributed under the License is distributed on an "AS IS" BASIS,
  7874. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7875. * See the License for the specific language governing permissions and
  7876. * limitations under the License.
  7877. */
  7878. /**
  7879. * An instance of the Platform's 'TextEncoder' implementation.
  7880. */
  7881. function newTextEncoder() {
  7882. return new TextEncoder();
  7883. }
  7884. /**
  7885. * An instance of the Platform's 'TextDecoder' implementation.
  7886. */
  7887. function newTextDecoder() {
  7888. return new TextDecoder('utf-8');
  7889. }
  7890. /**
  7891. * @license
  7892. * Copyright 2022 Google LLC
  7893. *
  7894. * Licensed under the Apache License, Version 2.0 (the "License");
  7895. * you may not use this file except in compliance with the License.
  7896. * You may obtain a copy of the License at
  7897. *
  7898. * http://www.apache.org/licenses/LICENSE-2.0
  7899. *
  7900. * Unless required by applicable law or agreed to in writing, software
  7901. * distributed under the License is distributed on an "AS IS" BASIS,
  7902. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7903. * See the License for the specific language governing permissions and
  7904. * limitations under the License.
  7905. */
  7906. const MAX_64_BIT_UNSIGNED_INTEGER = new Integer([0xffffffff, 0xffffffff], 0);
  7907. // Hash a string using md5 hashing algorithm.
  7908. function getMd5HashValue(value) {
  7909. const encodedValue = newTextEncoder().encode(value);
  7910. const md5 = new Md5();
  7911. md5.update(encodedValue);
  7912. return new Uint8Array(md5.digest());
  7913. }
  7914. // Interpret the 16 bytes array as two 64-bit unsigned integers, encoded using
  7915. // 2’s complement using little endian.
  7916. function get64BitUints(Bytes) {
  7917. const dataView = new DataView(Bytes.buffer);
  7918. const chunk1 = dataView.getUint32(0, /* littleEndian= */ true);
  7919. const chunk2 = dataView.getUint32(4, /* littleEndian= */ true);
  7920. const chunk3 = dataView.getUint32(8, /* littleEndian= */ true);
  7921. const chunk4 = dataView.getUint32(12, /* littleEndian= */ true);
  7922. const integer1 = new Integer([chunk1, chunk2], 0);
  7923. const integer2 = new Integer([chunk3, chunk4], 0);
  7924. return [integer1, integer2];
  7925. }
  7926. class BloomFilter {
  7927. constructor(bitmap, padding, hashCount) {
  7928. this.bitmap = bitmap;
  7929. this.padding = padding;
  7930. this.hashCount = hashCount;
  7931. if (padding < 0 || padding >= 8) {
  7932. throw new BloomFilterError(`Invalid padding: ${padding}`);
  7933. }
  7934. if (hashCount < 0) {
  7935. throw new BloomFilterError(`Invalid hash count: ${hashCount}`);
  7936. }
  7937. if (bitmap.length > 0 && this.hashCount === 0) {
  7938. // Only empty bloom filter can have 0 hash count.
  7939. throw new BloomFilterError(`Invalid hash count: ${hashCount}`);
  7940. }
  7941. if (bitmap.length === 0 && padding !== 0) {
  7942. // Empty bloom filter should have 0 padding.
  7943. throw new BloomFilterError(`Invalid padding when bitmap length is 0: ${padding}`);
  7944. }
  7945. this.bitCount = bitmap.length * 8 - padding;
  7946. // Set the bit count in Integer to avoid repetition in mightContain().
  7947. this.bitCountInInteger = Integer.fromNumber(this.bitCount);
  7948. }
  7949. // Calculate the ith hash value based on the hashed 64bit integers,
  7950. // and calculate its corresponding bit index in the bitmap to be checked.
  7951. getBitIndex(num1, num2, hashIndex) {
  7952. // Calculate hashed value h(i) = h1 + (i * h2).
  7953. let hashValue = num1.add(num2.multiply(Integer.fromNumber(hashIndex)));
  7954. // Wrap if hash value overflow 64bit.
  7955. if (hashValue.compare(MAX_64_BIT_UNSIGNED_INTEGER) === 1) {
  7956. hashValue = new Integer([hashValue.getBits(0), hashValue.getBits(1)], 0);
  7957. }
  7958. return hashValue.modulo(this.bitCountInInteger).toNumber();
  7959. }
  7960. // Return whether the bit on the given index in the bitmap is set to 1.
  7961. isBitSet(index) {
  7962. // To retrieve bit n, calculate: (bitmap[n / 8] & (0x01 << (n % 8))).
  7963. const byte = this.bitmap[Math.floor(index / 8)];
  7964. const offset = index % 8;
  7965. return (byte & (0x01 << offset)) !== 0;
  7966. }
  7967. mightContain(value) {
  7968. // Empty bitmap should always return false on membership check.
  7969. if (this.bitCount === 0) {
  7970. return false;
  7971. }
  7972. const md5HashedValue = getMd5HashValue(value);
  7973. const [hash1, hash2] = get64BitUints(md5HashedValue);
  7974. for (let i = 0; i < this.hashCount; i++) {
  7975. const index = this.getBitIndex(hash1, hash2, i);
  7976. if (!this.isBitSet(index)) {
  7977. return false;
  7978. }
  7979. }
  7980. return true;
  7981. }
  7982. /** Create bloom filter for testing purposes only. */
  7983. static create(bitCount, hashCount, contains) {
  7984. const padding = bitCount % 8 === 0 ? 0 : 8 - (bitCount % 8);
  7985. const bitmap = new Uint8Array(Math.ceil(bitCount / 8));
  7986. const bloomFilter = new BloomFilter(bitmap, padding, hashCount);
  7987. contains.forEach(item => bloomFilter.insert(item));
  7988. return bloomFilter;
  7989. }
  7990. insert(value) {
  7991. if (this.bitCount === 0) {
  7992. return;
  7993. }
  7994. const md5HashedValue = getMd5HashValue(value);
  7995. const [hash1, hash2] = get64BitUints(md5HashedValue);
  7996. for (let i = 0; i < this.hashCount; i++) {
  7997. const index = this.getBitIndex(hash1, hash2, i);
  7998. this.setBit(index);
  7999. }
  8000. }
  8001. setBit(index) {
  8002. const indexOfByte = Math.floor(index / 8);
  8003. const offset = index % 8;
  8004. this.bitmap[indexOfByte] |= 0x01 << offset;
  8005. }
  8006. }
  8007. class BloomFilterError extends Error {
  8008. constructor() {
  8009. super(...arguments);
  8010. this.name = 'BloomFilterError';
  8011. }
  8012. }
  8013. /**
  8014. * @license
  8015. * Copyright 2017 Google LLC
  8016. *
  8017. * Licensed under the Apache License, Version 2.0 (the "License");
  8018. * you may not use this file except in compliance with the License.
  8019. * You may obtain a copy of the License at
  8020. *
  8021. * http://www.apache.org/licenses/LICENSE-2.0
  8022. *
  8023. * Unless required by applicable law or agreed to in writing, software
  8024. * distributed under the License is distributed on an "AS IS" BASIS,
  8025. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8026. * See the License for the specific language governing permissions and
  8027. * limitations under the License.
  8028. */
  8029. /**
  8030. * An event from the RemoteStore. It is split into targetChanges (changes to the
  8031. * state or the set of documents in our watched targets) and documentUpdates
  8032. * (changes to the actual documents).
  8033. */
  8034. class RemoteEvent {
  8035. constructor(
  8036. /**
  8037. * The snapshot version this event brings us up to, or MIN if not set.
  8038. */
  8039. snapshotVersion,
  8040. /**
  8041. * A map from target to changes to the target. See TargetChange.
  8042. */
  8043. targetChanges,
  8044. /**
  8045. * A map of targets that is known to be inconsistent, and the purpose for
  8046. * re-listening. Listens for these targets should be re-established without
  8047. * resume tokens.
  8048. */
  8049. targetMismatches,
  8050. /**
  8051. * A set of which documents have changed or been deleted, along with the
  8052. * doc's new values (if not deleted).
  8053. */
  8054. documentUpdates,
  8055. /**
  8056. * A set of which document updates are due only to limbo resolution targets.
  8057. */
  8058. resolvedLimboDocuments) {
  8059. this.snapshotVersion = snapshotVersion;
  8060. this.targetChanges = targetChanges;
  8061. this.targetMismatches = targetMismatches;
  8062. this.documentUpdates = documentUpdates;
  8063. this.resolvedLimboDocuments = resolvedLimboDocuments;
  8064. }
  8065. /**
  8066. * HACK: Views require RemoteEvents in order to determine whether the view is
  8067. * CURRENT, but secondary tabs don't receive remote events. So this method is
  8068. * used to create a synthesized RemoteEvent that can be used to apply a
  8069. * CURRENT status change to a View, for queries executed in a different tab.
  8070. */
  8071. // PORTING NOTE: Multi-tab only
  8072. static createSynthesizedRemoteEventForCurrentChange(targetId, current, resumeToken) {
  8073. const targetChanges = new Map();
  8074. targetChanges.set(targetId, TargetChange.createSynthesizedTargetChangeForCurrentChange(targetId, current, resumeToken));
  8075. return new RemoteEvent(SnapshotVersion.min(), targetChanges, new SortedMap(primitiveComparator), mutableDocumentMap(), documentKeySet());
  8076. }
  8077. }
  8078. /**
  8079. * A TargetChange specifies the set of changes for a specific target as part of
  8080. * a RemoteEvent. These changes track which documents are added, modified or
  8081. * removed, as well as the target's resume token and whether the target is
  8082. * marked CURRENT.
  8083. * The actual changes *to* documents are not part of the TargetChange since
  8084. * documents may be part of multiple targets.
  8085. */
  8086. class TargetChange {
  8087. constructor(
  8088. /**
  8089. * An opaque, server-assigned token that allows watching a query to be resumed
  8090. * after disconnecting without retransmitting all the data that matches the
  8091. * query. The resume token essentially identifies a point in time from which
  8092. * the server should resume sending results.
  8093. */
  8094. resumeToken,
  8095. /**
  8096. * The "current" (synced) status of this target. Note that "current"
  8097. * has special meaning in the RPC protocol that implies that a target is
  8098. * both up-to-date and consistent with the rest of the watch stream.
  8099. */
  8100. current,
  8101. /**
  8102. * The set of documents that were newly assigned to this target as part of
  8103. * this remote event.
  8104. */
  8105. addedDocuments,
  8106. /**
  8107. * The set of documents that were already assigned to this target but received
  8108. * an update during this remote event.
  8109. */
  8110. modifiedDocuments,
  8111. /**
  8112. * The set of documents that were removed from this target as part of this
  8113. * remote event.
  8114. */
  8115. removedDocuments) {
  8116. this.resumeToken = resumeToken;
  8117. this.current = current;
  8118. this.addedDocuments = addedDocuments;
  8119. this.modifiedDocuments = modifiedDocuments;
  8120. this.removedDocuments = removedDocuments;
  8121. }
  8122. /**
  8123. * This method is used to create a synthesized TargetChanges that can be used to
  8124. * apply a CURRENT status change to a View (for queries executed in a different
  8125. * tab) or for new queries (to raise snapshots with correct CURRENT status).
  8126. */
  8127. static createSynthesizedTargetChangeForCurrentChange(targetId, current, resumeToken) {
  8128. return new TargetChange(resumeToken, current, documentKeySet(), documentKeySet(), documentKeySet());
  8129. }
  8130. }
  8131. /**
  8132. * @license
  8133. * Copyright 2017 Google LLC
  8134. *
  8135. * Licensed under the Apache License, Version 2.0 (the "License");
  8136. * you may not use this file except in compliance with the License.
  8137. * You may obtain a copy of the License at
  8138. *
  8139. * http://www.apache.org/licenses/LICENSE-2.0
  8140. *
  8141. * Unless required by applicable law or agreed to in writing, software
  8142. * distributed under the License is distributed on an "AS IS" BASIS,
  8143. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8144. * See the License for the specific language governing permissions and
  8145. * limitations under the License.
  8146. */
  8147. /**
  8148. * Represents a changed document and a list of target ids to which this change
  8149. * applies.
  8150. *
  8151. * If document has been deleted NoDocument will be provided.
  8152. */
  8153. class DocumentWatchChange {
  8154. constructor(
  8155. /** The new document applies to all of these targets. */
  8156. updatedTargetIds,
  8157. /** The new document is removed from all of these targets. */
  8158. removedTargetIds,
  8159. /** The key of the document for this change. */
  8160. key,
  8161. /**
  8162. * The new document or NoDocument if it was deleted. Is null if the
  8163. * document went out of view without the server sending a new document.
  8164. */
  8165. newDoc) {
  8166. this.updatedTargetIds = updatedTargetIds;
  8167. this.removedTargetIds = removedTargetIds;
  8168. this.key = key;
  8169. this.newDoc = newDoc;
  8170. }
  8171. }
  8172. class ExistenceFilterChange {
  8173. constructor(targetId, existenceFilter) {
  8174. this.targetId = targetId;
  8175. this.existenceFilter = existenceFilter;
  8176. }
  8177. }
  8178. class WatchTargetChange {
  8179. constructor(
  8180. /** What kind of change occurred to the watch target. */
  8181. state,
  8182. /** The target IDs that were added/removed/set. */
  8183. targetIds,
  8184. /**
  8185. * An opaque, server-assigned token that allows watching a target to be
  8186. * resumed after disconnecting without retransmitting all the data that
  8187. * matches the target. The resume token essentially identifies a point in
  8188. * time from which the server should resume sending results.
  8189. */
  8190. resumeToken = ByteString.EMPTY_BYTE_STRING,
  8191. /** An RPC error indicating why the watch failed. */
  8192. cause = null) {
  8193. this.state = state;
  8194. this.targetIds = targetIds;
  8195. this.resumeToken = resumeToken;
  8196. this.cause = cause;
  8197. }
  8198. }
  8199. /** Tracks the internal state of a Watch target. */
  8200. class TargetState {
  8201. constructor() {
  8202. /**
  8203. * The number of pending responses (adds or removes) that we are waiting on.
  8204. * We only consider targets active that have no pending responses.
  8205. */
  8206. this.pendingResponses = 0;
  8207. /**
  8208. * Keeps track of the document changes since the last raised snapshot.
  8209. *
  8210. * These changes are continuously updated as we receive document updates and
  8211. * always reflect the current set of changes against the last issued snapshot.
  8212. */
  8213. this.documentChanges = snapshotChangesMap();
  8214. /** See public getters for explanations of these fields. */
  8215. this._resumeToken = ByteString.EMPTY_BYTE_STRING;
  8216. this._current = false;
  8217. /**
  8218. * Whether this target state should be included in the next snapshot. We
  8219. * initialize to true so that newly-added targets are included in the next
  8220. * RemoteEvent.
  8221. */
  8222. this._hasPendingChanges = true;
  8223. }
  8224. /**
  8225. * Whether this target has been marked 'current'.
  8226. *
  8227. * 'Current' has special meaning in the RPC protocol: It implies that the
  8228. * Watch backend has sent us all changes up to the point at which the target
  8229. * was added and that the target is consistent with the rest of the watch
  8230. * stream.
  8231. */
  8232. get current() {
  8233. return this._current;
  8234. }
  8235. /** The last resume token sent to us for this target. */
  8236. get resumeToken() {
  8237. return this._resumeToken;
  8238. }
  8239. /** Whether this target has pending target adds or target removes. */
  8240. get isPending() {
  8241. return this.pendingResponses !== 0;
  8242. }
  8243. /** Whether we have modified any state that should trigger a snapshot. */
  8244. get hasPendingChanges() {
  8245. return this._hasPendingChanges;
  8246. }
  8247. /**
  8248. * Applies the resume token to the TargetChange, but only when it has a new
  8249. * value. Empty resumeTokens are discarded.
  8250. */
  8251. updateResumeToken(resumeToken) {
  8252. if (resumeToken.approximateByteSize() > 0) {
  8253. this._hasPendingChanges = true;
  8254. this._resumeToken = resumeToken;
  8255. }
  8256. }
  8257. /**
  8258. * Creates a target change from the current set of changes.
  8259. *
  8260. * To reset the document changes after raising this snapshot, call
  8261. * `clearPendingChanges()`.
  8262. */
  8263. toTargetChange() {
  8264. let addedDocuments = documentKeySet();
  8265. let modifiedDocuments = documentKeySet();
  8266. let removedDocuments = documentKeySet();
  8267. this.documentChanges.forEach((key, changeType) => {
  8268. switch (changeType) {
  8269. case 0 /* ChangeType.Added */:
  8270. addedDocuments = addedDocuments.add(key);
  8271. break;
  8272. case 2 /* ChangeType.Modified */:
  8273. modifiedDocuments = modifiedDocuments.add(key);
  8274. break;
  8275. case 1 /* ChangeType.Removed */:
  8276. removedDocuments = removedDocuments.add(key);
  8277. break;
  8278. default:
  8279. fail();
  8280. }
  8281. });
  8282. return new TargetChange(this._resumeToken, this._current, addedDocuments, modifiedDocuments, removedDocuments);
  8283. }
  8284. /**
  8285. * Resets the document changes and sets `hasPendingChanges` to false.
  8286. */
  8287. clearPendingChanges() {
  8288. this._hasPendingChanges = false;
  8289. this.documentChanges = snapshotChangesMap();
  8290. }
  8291. addDocumentChange(key, changeType) {
  8292. this._hasPendingChanges = true;
  8293. this.documentChanges = this.documentChanges.insert(key, changeType);
  8294. }
  8295. removeDocumentChange(key) {
  8296. this._hasPendingChanges = true;
  8297. this.documentChanges = this.documentChanges.remove(key);
  8298. }
  8299. recordPendingTargetRequest() {
  8300. this.pendingResponses += 1;
  8301. }
  8302. recordTargetResponse() {
  8303. this.pendingResponses -= 1;
  8304. }
  8305. markCurrent() {
  8306. this._hasPendingChanges = true;
  8307. this._current = true;
  8308. }
  8309. }
  8310. const LOG_TAG$g = 'WatchChangeAggregator';
  8311. /**
  8312. * A helper class to accumulate watch changes into a RemoteEvent.
  8313. */
  8314. class WatchChangeAggregator {
  8315. constructor(metadataProvider) {
  8316. this.metadataProvider = metadataProvider;
  8317. /** The internal state of all tracked targets. */
  8318. this.targetStates = new Map();
  8319. /** Keeps track of the documents to update since the last raised snapshot. */
  8320. this.pendingDocumentUpdates = mutableDocumentMap();
  8321. /** A mapping of document keys to their set of target IDs. */
  8322. this.pendingDocumentTargetMapping = documentTargetMap();
  8323. /**
  8324. * A map of targets with existence filter mismatches. These targets are
  8325. * known to be inconsistent and their listens needs to be re-established by
  8326. * RemoteStore.
  8327. */
  8328. this.pendingTargetResets = new SortedMap(primitiveComparator);
  8329. }
  8330. /**
  8331. * Processes and adds the DocumentWatchChange to the current set of changes.
  8332. */
  8333. handleDocumentChange(docChange) {
  8334. for (const targetId of docChange.updatedTargetIds) {
  8335. if (docChange.newDoc && docChange.newDoc.isFoundDocument()) {
  8336. this.addDocumentToTarget(targetId, docChange.newDoc);
  8337. }
  8338. else {
  8339. this.removeDocumentFromTarget(targetId, docChange.key, docChange.newDoc);
  8340. }
  8341. }
  8342. for (const targetId of docChange.removedTargetIds) {
  8343. this.removeDocumentFromTarget(targetId, docChange.key, docChange.newDoc);
  8344. }
  8345. }
  8346. /** Processes and adds the WatchTargetChange to the current set of changes. */
  8347. handleTargetChange(targetChange) {
  8348. this.forEachTarget(targetChange, targetId => {
  8349. const targetState = this.ensureTargetState(targetId);
  8350. switch (targetChange.state) {
  8351. case 0 /* WatchTargetChangeState.NoChange */:
  8352. if (this.isActiveTarget(targetId)) {
  8353. targetState.updateResumeToken(targetChange.resumeToken);
  8354. }
  8355. break;
  8356. case 1 /* WatchTargetChangeState.Added */:
  8357. // We need to decrement the number of pending acks needed from watch
  8358. // for this targetId.
  8359. targetState.recordTargetResponse();
  8360. if (!targetState.isPending) {
  8361. // We have a freshly added target, so we need to reset any state
  8362. // that we had previously. This can happen e.g. when remove and add
  8363. // back a target for existence filter mismatches.
  8364. targetState.clearPendingChanges();
  8365. }
  8366. targetState.updateResumeToken(targetChange.resumeToken);
  8367. break;
  8368. case 2 /* WatchTargetChangeState.Removed */:
  8369. // We need to keep track of removed targets to we can post-filter and
  8370. // remove any target changes.
  8371. // We need to decrement the number of pending acks needed from watch
  8372. // for this targetId.
  8373. targetState.recordTargetResponse();
  8374. if (!targetState.isPending) {
  8375. this.removeTarget(targetId);
  8376. }
  8377. break;
  8378. case 3 /* WatchTargetChangeState.Current */:
  8379. if (this.isActiveTarget(targetId)) {
  8380. targetState.markCurrent();
  8381. targetState.updateResumeToken(targetChange.resumeToken);
  8382. }
  8383. break;
  8384. case 4 /* WatchTargetChangeState.Reset */:
  8385. if (this.isActiveTarget(targetId)) {
  8386. // Reset the target and synthesizes removes for all existing
  8387. // documents. The backend will re-add any documents that still
  8388. // match the target before it sends the next global snapshot.
  8389. this.resetTarget(targetId);
  8390. targetState.updateResumeToken(targetChange.resumeToken);
  8391. }
  8392. break;
  8393. default:
  8394. fail();
  8395. }
  8396. });
  8397. }
  8398. /**
  8399. * Iterates over all targetIds that the watch change applies to: either the
  8400. * targetIds explicitly listed in the change or the targetIds of all currently
  8401. * active targets.
  8402. */
  8403. forEachTarget(targetChange, fn) {
  8404. if (targetChange.targetIds.length > 0) {
  8405. targetChange.targetIds.forEach(fn);
  8406. }
  8407. else {
  8408. this.targetStates.forEach((_, targetId) => {
  8409. if (this.isActiveTarget(targetId)) {
  8410. fn(targetId);
  8411. }
  8412. });
  8413. }
  8414. }
  8415. /**
  8416. * Handles existence filters and synthesizes deletes for filter mismatches.
  8417. * Targets that are invalidated by filter mismatches are added to
  8418. * `pendingTargetResets`.
  8419. */
  8420. handleExistenceFilter(watchChange) {
  8421. var _a;
  8422. const targetId = watchChange.targetId;
  8423. const expectedCount = watchChange.existenceFilter.count;
  8424. const targetData = this.targetDataForActiveTarget(targetId);
  8425. if (targetData) {
  8426. const target = targetData.target;
  8427. if (targetIsDocumentTarget(target)) {
  8428. if (expectedCount === 0) {
  8429. // The existence filter told us the document does not exist. We deduce
  8430. // that this document does not exist and apply a deleted document to
  8431. // our updates. Without applying this deleted document there might be
  8432. // another query that will raise this document as part of a snapshot
  8433. // until it is resolved, essentially exposing inconsistency between
  8434. // queries.
  8435. const key = new DocumentKey(target.path);
  8436. this.removeDocumentFromTarget(targetId, key, MutableDocument.newNoDocument(key, SnapshotVersion.min()));
  8437. }
  8438. else {
  8439. hardAssert(expectedCount === 1);
  8440. }
  8441. }
  8442. else {
  8443. const currentSize = this.getCurrentDocumentCountForTarget(targetId);
  8444. // Existence filter mismatch. Mark the documents as being in limbo, and
  8445. // raise a snapshot with `isFromCache:true`.
  8446. if (currentSize !== expectedCount) {
  8447. // Apply bloom filter to identify and mark removed documents.
  8448. const status = this.applyBloomFilter(watchChange, currentSize);
  8449. if (status !== 0 /* BloomFilterApplicationStatus.Success */) {
  8450. // If bloom filter application fails, we reset the mapping and
  8451. // trigger re-run of the query.
  8452. this.resetTarget(targetId);
  8453. const purpose = status === 2 /* BloomFilterApplicationStatus.FalsePositive */
  8454. ? "TargetPurposeExistenceFilterMismatchBloom" /* TargetPurpose.ExistenceFilterMismatchBloom */
  8455. : "TargetPurposeExistenceFilterMismatch" /* TargetPurpose.ExistenceFilterMismatch */;
  8456. this.pendingTargetResets = this.pendingTargetResets.insert(targetId, purpose);
  8457. }
  8458. (_a = TestingHooks.instance) === null || _a === void 0 ? void 0 : _a.notifyOnExistenceFilterMismatch(createExistenceFilterMismatchInfoForTestingHooks(status, currentSize, watchChange.existenceFilter));
  8459. }
  8460. }
  8461. }
  8462. }
  8463. /**
  8464. * Apply bloom filter to remove the deleted documents, and return the
  8465. * application status.
  8466. */
  8467. applyBloomFilter(watchChange, currentCount) {
  8468. const { unchangedNames, count: expectedCount } = watchChange.existenceFilter;
  8469. if (!unchangedNames || !unchangedNames.bits) {
  8470. return 1 /* BloomFilterApplicationStatus.Skipped */;
  8471. }
  8472. const { bits: { bitmap = '', padding = 0 }, hashCount = 0 } = unchangedNames;
  8473. let normalizedBitmap;
  8474. try {
  8475. normalizedBitmap = normalizeByteString(bitmap).toUint8Array();
  8476. }
  8477. catch (err) {
  8478. if (err instanceof Base64DecodeError) {
  8479. logWarn('Decoding the base64 bloom filter in existence filter failed (' +
  8480. err.message +
  8481. '); ignoring the bloom filter and falling back to full re-query.');
  8482. return 1 /* BloomFilterApplicationStatus.Skipped */;
  8483. }
  8484. else {
  8485. throw err;
  8486. }
  8487. }
  8488. let bloomFilter;
  8489. try {
  8490. // BloomFilter throws error if the inputs are invalid.
  8491. bloomFilter = new BloomFilter(normalizedBitmap, padding, hashCount);
  8492. }
  8493. catch (err) {
  8494. if (err instanceof BloomFilterError) {
  8495. logWarn('BloomFilter error: ', err);
  8496. }
  8497. else {
  8498. logWarn('Applying bloom filter failed: ', err);
  8499. }
  8500. return 1 /* BloomFilterApplicationStatus.Skipped */;
  8501. }
  8502. if (bloomFilter.bitCount === 0) {
  8503. return 1 /* BloomFilterApplicationStatus.Skipped */;
  8504. }
  8505. const removedDocumentCount = this.filterRemovedDocuments(watchChange.targetId, bloomFilter);
  8506. if (expectedCount !== currentCount - removedDocumentCount) {
  8507. return 2 /* BloomFilterApplicationStatus.FalsePositive */;
  8508. }
  8509. return 0 /* BloomFilterApplicationStatus.Success */;
  8510. }
  8511. /**
  8512. * Filter out removed documents based on bloom filter membership result and
  8513. * return number of documents removed.
  8514. */
  8515. filterRemovedDocuments(targetId, bloomFilter) {
  8516. const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);
  8517. let removalCount = 0;
  8518. existingKeys.forEach(key => {
  8519. const databaseId = this.metadataProvider.getDatabaseId();
  8520. const documentPath = `projects/${databaseId.projectId}/databases/${databaseId.database}/documents/${key.path.canonicalString()}`;
  8521. if (!bloomFilter.mightContain(documentPath)) {
  8522. this.removeDocumentFromTarget(targetId, key, /*updatedDocument=*/ null);
  8523. removalCount++;
  8524. }
  8525. });
  8526. return removalCount;
  8527. }
  8528. /**
  8529. * Converts the currently accumulated state into a remote event at the
  8530. * provided snapshot version. Resets the accumulated changes before returning.
  8531. */
  8532. createRemoteEvent(snapshotVersion) {
  8533. const targetChanges = new Map();
  8534. this.targetStates.forEach((targetState, targetId) => {
  8535. const targetData = this.targetDataForActiveTarget(targetId);
  8536. if (targetData) {
  8537. if (targetState.current && targetIsDocumentTarget(targetData.target)) {
  8538. // Document queries for document that don't exist can produce an empty
  8539. // result set. To update our local cache, we synthesize a document
  8540. // delete if we have not previously received the document. This
  8541. // resolves the limbo state of the document, removing it from
  8542. // limboDocumentRefs.
  8543. //
  8544. // TODO(dimond): Ideally we would have an explicit lookup target
  8545. // instead resulting in an explicit delete message and we could
  8546. // remove this special logic.
  8547. const key = new DocumentKey(targetData.target.path);
  8548. if (this.pendingDocumentUpdates.get(key) === null &&
  8549. !this.targetContainsDocument(targetId, key)) {
  8550. this.removeDocumentFromTarget(targetId, key, MutableDocument.newNoDocument(key, snapshotVersion));
  8551. }
  8552. }
  8553. if (targetState.hasPendingChanges) {
  8554. targetChanges.set(targetId, targetState.toTargetChange());
  8555. targetState.clearPendingChanges();
  8556. }
  8557. }
  8558. });
  8559. let resolvedLimboDocuments = documentKeySet();
  8560. // We extract the set of limbo-only document updates as the GC logic
  8561. // special-cases documents that do not appear in the target cache.
  8562. //
  8563. // TODO(gsoltis): Expand on this comment once GC is available in the JS
  8564. // client.
  8565. this.pendingDocumentTargetMapping.forEach((key, targets) => {
  8566. let isOnlyLimboTarget = true;
  8567. targets.forEachWhile(targetId => {
  8568. const targetData = this.targetDataForActiveTarget(targetId);
  8569. if (targetData &&
  8570. targetData.purpose !== "TargetPurposeLimboResolution" /* TargetPurpose.LimboResolution */) {
  8571. isOnlyLimboTarget = false;
  8572. return false;
  8573. }
  8574. return true;
  8575. });
  8576. if (isOnlyLimboTarget) {
  8577. resolvedLimboDocuments = resolvedLimboDocuments.add(key);
  8578. }
  8579. });
  8580. this.pendingDocumentUpdates.forEach((_, doc) => doc.setReadTime(snapshotVersion));
  8581. const remoteEvent = new RemoteEvent(snapshotVersion, targetChanges, this.pendingTargetResets, this.pendingDocumentUpdates, resolvedLimboDocuments);
  8582. this.pendingDocumentUpdates = mutableDocumentMap();
  8583. this.pendingDocumentTargetMapping = documentTargetMap();
  8584. this.pendingTargetResets = new SortedMap(primitiveComparator);
  8585. return remoteEvent;
  8586. }
  8587. /**
  8588. * Adds the provided document to the internal list of document updates and
  8589. * its document key to the given target's mapping.
  8590. */
  8591. // Visible for testing.
  8592. addDocumentToTarget(targetId, document) {
  8593. if (!this.isActiveTarget(targetId)) {
  8594. return;
  8595. }
  8596. const changeType = this.targetContainsDocument(targetId, document.key)
  8597. ? 2 /* ChangeType.Modified */
  8598. : 0 /* ChangeType.Added */;
  8599. const targetState = this.ensureTargetState(targetId);
  8600. targetState.addDocumentChange(document.key, changeType);
  8601. this.pendingDocumentUpdates = this.pendingDocumentUpdates.insert(document.key, document);
  8602. this.pendingDocumentTargetMapping =
  8603. this.pendingDocumentTargetMapping.insert(document.key, this.ensureDocumentTargetMapping(document.key).add(targetId));
  8604. }
  8605. /**
  8606. * Removes the provided document from the target mapping. If the
  8607. * document no longer matches the target, but the document's state is still
  8608. * known (e.g. we know that the document was deleted or we received the change
  8609. * that caused the filter mismatch), the new document can be provided
  8610. * to update the remote document cache.
  8611. */
  8612. // Visible for testing.
  8613. removeDocumentFromTarget(targetId, key, updatedDocument) {
  8614. if (!this.isActiveTarget(targetId)) {
  8615. return;
  8616. }
  8617. const targetState = this.ensureTargetState(targetId);
  8618. if (this.targetContainsDocument(targetId, key)) {
  8619. targetState.addDocumentChange(key, 1 /* ChangeType.Removed */);
  8620. }
  8621. else {
  8622. // The document may have entered and left the target before we raised a
  8623. // snapshot, so we can just ignore the change.
  8624. targetState.removeDocumentChange(key);
  8625. }
  8626. this.pendingDocumentTargetMapping =
  8627. this.pendingDocumentTargetMapping.insert(key, this.ensureDocumentTargetMapping(key).delete(targetId));
  8628. if (updatedDocument) {
  8629. this.pendingDocumentUpdates = this.pendingDocumentUpdates.insert(key, updatedDocument);
  8630. }
  8631. }
  8632. removeTarget(targetId) {
  8633. this.targetStates.delete(targetId);
  8634. }
  8635. /**
  8636. * Returns the current count of documents in the target. This includes both
  8637. * the number of documents that the LocalStore considers to be part of the
  8638. * target as well as any accumulated changes.
  8639. */
  8640. getCurrentDocumentCountForTarget(targetId) {
  8641. const targetState = this.ensureTargetState(targetId);
  8642. const targetChange = targetState.toTargetChange();
  8643. return (this.metadataProvider.getRemoteKeysForTarget(targetId).size +
  8644. targetChange.addedDocuments.size -
  8645. targetChange.removedDocuments.size);
  8646. }
  8647. /**
  8648. * Increment the number of acks needed from watch before we can consider the
  8649. * server to be 'in-sync' with the client's active targets.
  8650. */
  8651. recordPendingTargetRequest(targetId) {
  8652. // For each request we get we need to record we need a response for it.
  8653. const targetState = this.ensureTargetState(targetId);
  8654. targetState.recordPendingTargetRequest();
  8655. }
  8656. ensureTargetState(targetId) {
  8657. let result = this.targetStates.get(targetId);
  8658. if (!result) {
  8659. result = new TargetState();
  8660. this.targetStates.set(targetId, result);
  8661. }
  8662. return result;
  8663. }
  8664. ensureDocumentTargetMapping(key) {
  8665. let targetMapping = this.pendingDocumentTargetMapping.get(key);
  8666. if (!targetMapping) {
  8667. targetMapping = new SortedSet(primitiveComparator);
  8668. this.pendingDocumentTargetMapping =
  8669. this.pendingDocumentTargetMapping.insert(key, targetMapping);
  8670. }
  8671. return targetMapping;
  8672. }
  8673. /**
  8674. * Verifies that the user is still interested in this target (by calling
  8675. * `getTargetDataForTarget()`) and that we are not waiting for pending ADDs
  8676. * from watch.
  8677. */
  8678. isActiveTarget(targetId) {
  8679. const targetActive = this.targetDataForActiveTarget(targetId) !== null;
  8680. if (!targetActive) {
  8681. logDebug(LOG_TAG$g, 'Detected inactive target', targetId);
  8682. }
  8683. return targetActive;
  8684. }
  8685. /**
  8686. * Returns the TargetData for an active target (i.e. a target that the user
  8687. * is still interested in that has no outstanding target change requests).
  8688. */
  8689. targetDataForActiveTarget(targetId) {
  8690. const targetState = this.targetStates.get(targetId);
  8691. return targetState && targetState.isPending
  8692. ? null
  8693. : this.metadataProvider.getTargetDataForTarget(targetId);
  8694. }
  8695. /**
  8696. * Resets the state of a Watch target to its initial state (e.g. sets
  8697. * 'current' to false, clears the resume token and removes its target mapping
  8698. * from all documents).
  8699. */
  8700. resetTarget(targetId) {
  8701. this.targetStates.set(targetId, new TargetState());
  8702. // Trigger removal for any documents currently mapped to this target.
  8703. // These removals will be part of the initial snapshot if Watch does not
  8704. // resend these documents.
  8705. const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);
  8706. existingKeys.forEach(key => {
  8707. this.removeDocumentFromTarget(targetId, key, /*updatedDocument=*/ null);
  8708. });
  8709. }
  8710. /**
  8711. * Returns whether the LocalStore considers the document to be part of the
  8712. * specified target.
  8713. */
  8714. targetContainsDocument(targetId, key) {
  8715. const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);
  8716. return existingKeys.has(key);
  8717. }
  8718. }
  8719. function documentTargetMap() {
  8720. return new SortedMap(DocumentKey.comparator);
  8721. }
  8722. function snapshotChangesMap() {
  8723. return new SortedMap(DocumentKey.comparator);
  8724. }
  8725. function createExistenceFilterMismatchInfoForTestingHooks(status, localCacheCount, existenceFilter) {
  8726. var _a, _b, _c, _d, _e, _f;
  8727. const result = {
  8728. localCacheCount,
  8729. existenceFilterCount: existenceFilter.count
  8730. };
  8731. const unchangedNames = existenceFilter.unchangedNames;
  8732. if (unchangedNames) {
  8733. result.bloomFilter = {
  8734. applied: status === 0 /* BloomFilterApplicationStatus.Success */,
  8735. hashCount: (_a = unchangedNames === null || unchangedNames === void 0 ? void 0 : unchangedNames.hashCount) !== null && _a !== void 0 ? _a : 0,
  8736. bitmapLength: (_d = (_c = (_b = unchangedNames === null || unchangedNames === void 0 ? void 0 : unchangedNames.bits) === null || _b === void 0 ? void 0 : _b.bitmap) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0,
  8737. padding: (_f = (_e = unchangedNames === null || unchangedNames === void 0 ? void 0 : unchangedNames.bits) === null || _e === void 0 ? void 0 : _e.padding) !== null && _f !== void 0 ? _f : 0
  8738. };
  8739. }
  8740. return result;
  8741. }
  8742. /**
  8743. * @license
  8744. * Copyright 2017 Google LLC
  8745. *
  8746. * Licensed under the Apache License, Version 2.0 (the "License");
  8747. * you may not use this file except in compliance with the License.
  8748. * You may obtain a copy of the License at
  8749. *
  8750. * http://www.apache.org/licenses/LICENSE-2.0
  8751. *
  8752. * Unless required by applicable law or agreed to in writing, software
  8753. * distributed under the License is distributed on an "AS IS" BASIS,
  8754. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8755. * See the License for the specific language governing permissions and
  8756. * limitations under the License.
  8757. */
  8758. const DIRECTIONS = (() => {
  8759. const dirs = {};
  8760. dirs["asc" /* Direction.ASCENDING */] = 'ASCENDING';
  8761. dirs["desc" /* Direction.DESCENDING */] = 'DESCENDING';
  8762. return dirs;
  8763. })();
  8764. const OPERATORS = (() => {
  8765. const ops = {};
  8766. ops["<" /* Operator.LESS_THAN */] = 'LESS_THAN';
  8767. ops["<=" /* Operator.LESS_THAN_OR_EQUAL */] = 'LESS_THAN_OR_EQUAL';
  8768. ops[">" /* Operator.GREATER_THAN */] = 'GREATER_THAN';
  8769. ops[">=" /* Operator.GREATER_THAN_OR_EQUAL */] = 'GREATER_THAN_OR_EQUAL';
  8770. ops["==" /* Operator.EQUAL */] = 'EQUAL';
  8771. ops["!=" /* Operator.NOT_EQUAL */] = 'NOT_EQUAL';
  8772. ops["array-contains" /* Operator.ARRAY_CONTAINS */] = 'ARRAY_CONTAINS';
  8773. ops["in" /* Operator.IN */] = 'IN';
  8774. ops["not-in" /* Operator.NOT_IN */] = 'NOT_IN';
  8775. ops["array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */] = 'ARRAY_CONTAINS_ANY';
  8776. return ops;
  8777. })();
  8778. const COMPOSITE_OPERATORS = (() => {
  8779. const ops = {};
  8780. ops["and" /* CompositeOperator.AND */] = 'AND';
  8781. ops["or" /* CompositeOperator.OR */] = 'OR';
  8782. return ops;
  8783. })();
  8784. function assertPresent(value, description) {
  8785. }
  8786. /**
  8787. * This class generates JsonObject values for the Datastore API suitable for
  8788. * sending to either GRPC stub methods or via the JSON/HTTP REST API.
  8789. *
  8790. * The serializer supports both Protobuf.js and Proto3 JSON formats. By
  8791. * setting `useProto3Json` to true, the serializer will use the Proto3 JSON
  8792. * format.
  8793. *
  8794. * For a description of the Proto3 JSON format check
  8795. * https://developers.google.com/protocol-buffers/docs/proto3#json
  8796. *
  8797. * TODO(klimt): We can remove the databaseId argument if we keep the full
  8798. * resource name in documents.
  8799. */
  8800. class JsonProtoSerializer {
  8801. constructor(databaseId, useProto3Json) {
  8802. this.databaseId = databaseId;
  8803. this.useProto3Json = useProto3Json;
  8804. }
  8805. }
  8806. function fromRpcStatus(status) {
  8807. const code = status.code === undefined ? Code.UNKNOWN : mapCodeFromRpcCode(status.code);
  8808. return new FirestoreError(code, status.message || '');
  8809. }
  8810. /**
  8811. * Returns a value for a number (or null) that's appropriate to put into
  8812. * a google.protobuf.Int32Value proto.
  8813. * DO NOT USE THIS FOR ANYTHING ELSE.
  8814. * This method cheats. It's typed as returning "number" because that's what
  8815. * our generated proto interfaces say Int32Value must be. But GRPC actually
  8816. * expects a { value: <number> } struct.
  8817. */
  8818. function toInt32Proto(serializer, val) {
  8819. if (serializer.useProto3Json || isNullOrUndefined(val)) {
  8820. return val;
  8821. }
  8822. else {
  8823. return { value: val };
  8824. }
  8825. }
  8826. /**
  8827. * Returns a number (or null) from a google.protobuf.Int32Value proto.
  8828. */
  8829. function fromInt32Proto(val) {
  8830. let result;
  8831. if (typeof val === 'object') {
  8832. result = val.value;
  8833. }
  8834. else {
  8835. result = val;
  8836. }
  8837. return isNullOrUndefined(result) ? null : result;
  8838. }
  8839. /**
  8840. * Returns a value for a Date that's appropriate to put into a proto.
  8841. */
  8842. function toTimestamp(serializer, timestamp) {
  8843. if (serializer.useProto3Json) {
  8844. // Serialize to ISO-8601 date format, but with full nano resolution.
  8845. // Since JS Date has only millis, let's only use it for the seconds and
  8846. // then manually add the fractions to the end.
  8847. const jsDateStr = new Date(timestamp.seconds * 1000).toISOString();
  8848. // Remove .xxx frac part and Z in the end.
  8849. const strUntilSeconds = jsDateStr.replace(/\.\d*/, '').replace('Z', '');
  8850. // Pad the fraction out to 9 digits (nanos).
  8851. const nanoStr = ('000000000' + timestamp.nanoseconds).slice(-9);
  8852. return `${strUntilSeconds}.${nanoStr}Z`;
  8853. }
  8854. else {
  8855. return {
  8856. seconds: '' + timestamp.seconds,
  8857. nanos: timestamp.nanoseconds
  8858. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  8859. };
  8860. }
  8861. }
  8862. function fromTimestamp(date) {
  8863. const timestamp = normalizeTimestamp(date);
  8864. return new Timestamp(timestamp.seconds, timestamp.nanos);
  8865. }
  8866. /**
  8867. * Returns a value for bytes that's appropriate to put in a proto.
  8868. *
  8869. * Visible for testing.
  8870. */
  8871. function toBytes(serializer, bytes) {
  8872. if (serializer.useProto3Json) {
  8873. return bytes.toBase64();
  8874. }
  8875. else {
  8876. return bytes.toUint8Array();
  8877. }
  8878. }
  8879. /**
  8880. * Returns a ByteString based on the proto string value.
  8881. */
  8882. function fromBytes(serializer, value) {
  8883. if (serializer.useProto3Json) {
  8884. hardAssert(value === undefined || typeof value === 'string');
  8885. return ByteString.fromBase64String(value ? value : '');
  8886. }
  8887. else {
  8888. hardAssert(value === undefined || value instanceof Uint8Array);
  8889. return ByteString.fromUint8Array(value ? value : new Uint8Array());
  8890. }
  8891. }
  8892. function toVersion(serializer, version) {
  8893. return toTimestamp(serializer, version.toTimestamp());
  8894. }
  8895. function fromVersion(version) {
  8896. hardAssert(!!version);
  8897. return SnapshotVersion.fromTimestamp(fromTimestamp(version));
  8898. }
  8899. function toResourceName(databaseId, path) {
  8900. return fullyQualifiedPrefixPath(databaseId)
  8901. .child('documents')
  8902. .child(path)
  8903. .canonicalString();
  8904. }
  8905. function fromResourceName(name) {
  8906. const resource = ResourcePath.fromString(name);
  8907. hardAssert(isValidResourceName(resource));
  8908. return resource;
  8909. }
  8910. function toName(serializer, key) {
  8911. return toResourceName(serializer.databaseId, key.path);
  8912. }
  8913. function fromName(serializer, name) {
  8914. const resource = fromResourceName(name);
  8915. if (resource.get(1) !== serializer.databaseId.projectId) {
  8916. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Tried to deserialize key from different project: ' +
  8917. resource.get(1) +
  8918. ' vs ' +
  8919. serializer.databaseId.projectId);
  8920. }
  8921. if (resource.get(3) !== serializer.databaseId.database) {
  8922. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Tried to deserialize key from different database: ' +
  8923. resource.get(3) +
  8924. ' vs ' +
  8925. serializer.databaseId.database);
  8926. }
  8927. return new DocumentKey(extractLocalPathFromResourceName(resource));
  8928. }
  8929. function toQueryPath(serializer, path) {
  8930. return toResourceName(serializer.databaseId, path);
  8931. }
  8932. function fromQueryPath(name) {
  8933. const resourceName = fromResourceName(name);
  8934. // In v1beta1 queries for collections at the root did not have a trailing
  8935. // "/documents". In v1 all resource paths contain "/documents". Preserve the
  8936. // ability to read the v1beta1 form for compatibility with queries persisted
  8937. // in the local target cache.
  8938. if (resourceName.length === 4) {
  8939. return ResourcePath.emptyPath();
  8940. }
  8941. return extractLocalPathFromResourceName(resourceName);
  8942. }
  8943. function getEncodedDatabaseId(serializer) {
  8944. const path = new ResourcePath([
  8945. 'projects',
  8946. serializer.databaseId.projectId,
  8947. 'databases',
  8948. serializer.databaseId.database
  8949. ]);
  8950. return path.canonicalString();
  8951. }
  8952. function fullyQualifiedPrefixPath(databaseId) {
  8953. return new ResourcePath([
  8954. 'projects',
  8955. databaseId.projectId,
  8956. 'databases',
  8957. databaseId.database
  8958. ]);
  8959. }
  8960. function extractLocalPathFromResourceName(resourceName) {
  8961. hardAssert(resourceName.length > 4 && resourceName.get(4) === 'documents');
  8962. return resourceName.popFirst(5);
  8963. }
  8964. /** Creates a Document proto from key and fields (but no create/update time) */
  8965. function toMutationDocument(serializer, key, fields) {
  8966. return {
  8967. name: toName(serializer, key),
  8968. fields: fields.value.mapValue.fields
  8969. };
  8970. }
  8971. function toDocument(serializer, document) {
  8972. return {
  8973. name: toName(serializer, document.key),
  8974. fields: document.data.value.mapValue.fields,
  8975. updateTime: toTimestamp(serializer, document.version.toTimestamp()),
  8976. createTime: toTimestamp(serializer, document.createTime.toTimestamp())
  8977. };
  8978. }
  8979. function fromDocument(serializer, document, hasCommittedMutations) {
  8980. const key = fromName(serializer, document.name);
  8981. const version = fromVersion(document.updateTime);
  8982. // If we read a document from persistence that is missing createTime, it's due
  8983. // to older SDK versions not storing this information. In such cases, we'll
  8984. // set the createTime to zero. This can be removed in the long term.
  8985. const createTime = document.createTime
  8986. ? fromVersion(document.createTime)
  8987. : SnapshotVersion.min();
  8988. const data = new ObjectValue({ mapValue: { fields: document.fields } });
  8989. const result = MutableDocument.newFoundDocument(key, version, createTime, data);
  8990. if (hasCommittedMutations) {
  8991. result.setHasCommittedMutations();
  8992. }
  8993. return hasCommittedMutations ? result.setHasCommittedMutations() : result;
  8994. }
  8995. function fromFound(serializer, doc) {
  8996. hardAssert(!!doc.found);
  8997. assertPresent(doc.found.name);
  8998. assertPresent(doc.found.updateTime);
  8999. const key = fromName(serializer, doc.found.name);
  9000. const version = fromVersion(doc.found.updateTime);
  9001. const createTime = doc.found.createTime
  9002. ? fromVersion(doc.found.createTime)
  9003. : SnapshotVersion.min();
  9004. const data = new ObjectValue({ mapValue: { fields: doc.found.fields } });
  9005. return MutableDocument.newFoundDocument(key, version, createTime, data);
  9006. }
  9007. function fromMissing(serializer, result) {
  9008. hardAssert(!!result.missing);
  9009. hardAssert(!!result.readTime);
  9010. const key = fromName(serializer, result.missing);
  9011. const version = fromVersion(result.readTime);
  9012. return MutableDocument.newNoDocument(key, version);
  9013. }
  9014. function fromBatchGetDocumentsResponse(serializer, result) {
  9015. if ('found' in result) {
  9016. return fromFound(serializer, result);
  9017. }
  9018. else if ('missing' in result) {
  9019. return fromMissing(serializer, result);
  9020. }
  9021. return fail();
  9022. }
  9023. function fromWatchChange(serializer, change) {
  9024. let watchChange;
  9025. if ('targetChange' in change) {
  9026. assertPresent(change.targetChange);
  9027. // proto3 default value is unset in JSON (undefined), so use 'NO_CHANGE'
  9028. // if unset
  9029. const state = fromWatchTargetChangeState(change.targetChange.targetChangeType || 'NO_CHANGE');
  9030. const targetIds = change.targetChange.targetIds || [];
  9031. const resumeToken = fromBytes(serializer, change.targetChange.resumeToken);
  9032. const causeProto = change.targetChange.cause;
  9033. const cause = causeProto && fromRpcStatus(causeProto);
  9034. watchChange = new WatchTargetChange(state, targetIds, resumeToken, cause || null);
  9035. }
  9036. else if ('documentChange' in change) {
  9037. assertPresent(change.documentChange);
  9038. const entityChange = change.documentChange;
  9039. assertPresent(entityChange.document);
  9040. assertPresent(entityChange.document.name);
  9041. assertPresent(entityChange.document.updateTime);
  9042. const key = fromName(serializer, entityChange.document.name);
  9043. const version = fromVersion(entityChange.document.updateTime);
  9044. const createTime = entityChange.document.createTime
  9045. ? fromVersion(entityChange.document.createTime)
  9046. : SnapshotVersion.min();
  9047. const data = new ObjectValue({
  9048. mapValue: { fields: entityChange.document.fields }
  9049. });
  9050. const doc = MutableDocument.newFoundDocument(key, version, createTime, data);
  9051. const updatedTargetIds = entityChange.targetIds || [];
  9052. const removedTargetIds = entityChange.removedTargetIds || [];
  9053. watchChange = new DocumentWatchChange(updatedTargetIds, removedTargetIds, doc.key, doc);
  9054. }
  9055. else if ('documentDelete' in change) {
  9056. assertPresent(change.documentDelete);
  9057. const docDelete = change.documentDelete;
  9058. assertPresent(docDelete.document);
  9059. const key = fromName(serializer, docDelete.document);
  9060. const version = docDelete.readTime
  9061. ? fromVersion(docDelete.readTime)
  9062. : SnapshotVersion.min();
  9063. const doc = MutableDocument.newNoDocument(key, version);
  9064. const removedTargetIds = docDelete.removedTargetIds || [];
  9065. watchChange = new DocumentWatchChange([], removedTargetIds, doc.key, doc);
  9066. }
  9067. else if ('documentRemove' in change) {
  9068. assertPresent(change.documentRemove);
  9069. const docRemove = change.documentRemove;
  9070. assertPresent(docRemove.document);
  9071. const key = fromName(serializer, docRemove.document);
  9072. const removedTargetIds = docRemove.removedTargetIds || [];
  9073. watchChange = new DocumentWatchChange([], removedTargetIds, key, null);
  9074. }
  9075. else if ('filter' in change) {
  9076. // TODO(dimond): implement existence filter parsing with strategy.
  9077. assertPresent(change.filter);
  9078. const filter = change.filter;
  9079. assertPresent(filter.targetId);
  9080. const { count = 0, unchangedNames } = filter;
  9081. const existenceFilter = new ExistenceFilter(count, unchangedNames);
  9082. const targetId = filter.targetId;
  9083. watchChange = new ExistenceFilterChange(targetId, existenceFilter);
  9084. }
  9085. else {
  9086. return fail();
  9087. }
  9088. return watchChange;
  9089. }
  9090. function fromWatchTargetChangeState(state) {
  9091. if (state === 'NO_CHANGE') {
  9092. return 0 /* WatchTargetChangeState.NoChange */;
  9093. }
  9094. else if (state === 'ADD') {
  9095. return 1 /* WatchTargetChangeState.Added */;
  9096. }
  9097. else if (state === 'REMOVE') {
  9098. return 2 /* WatchTargetChangeState.Removed */;
  9099. }
  9100. else if (state === 'CURRENT') {
  9101. return 3 /* WatchTargetChangeState.Current */;
  9102. }
  9103. else if (state === 'RESET') {
  9104. return 4 /* WatchTargetChangeState.Reset */;
  9105. }
  9106. else {
  9107. return fail();
  9108. }
  9109. }
  9110. function versionFromListenResponse(change) {
  9111. // We have only reached a consistent snapshot for the entire stream if there
  9112. // is a read_time set and it applies to all targets (i.e. the list of
  9113. // targets is empty). The backend is guaranteed to send such responses.
  9114. if (!('targetChange' in change)) {
  9115. return SnapshotVersion.min();
  9116. }
  9117. const targetChange = change.targetChange;
  9118. if (targetChange.targetIds && targetChange.targetIds.length) {
  9119. return SnapshotVersion.min();
  9120. }
  9121. if (!targetChange.readTime) {
  9122. return SnapshotVersion.min();
  9123. }
  9124. return fromVersion(targetChange.readTime);
  9125. }
  9126. function toMutation(serializer, mutation) {
  9127. let result;
  9128. if (mutation instanceof SetMutation) {
  9129. result = {
  9130. update: toMutationDocument(serializer, mutation.key, mutation.value)
  9131. };
  9132. }
  9133. else if (mutation instanceof DeleteMutation) {
  9134. result = { delete: toName(serializer, mutation.key) };
  9135. }
  9136. else if (mutation instanceof PatchMutation) {
  9137. result = {
  9138. update: toMutationDocument(serializer, mutation.key, mutation.data),
  9139. updateMask: toDocumentMask(mutation.fieldMask)
  9140. };
  9141. }
  9142. else if (mutation instanceof VerifyMutation) {
  9143. result = {
  9144. verify: toName(serializer, mutation.key)
  9145. };
  9146. }
  9147. else {
  9148. return fail();
  9149. }
  9150. if (mutation.fieldTransforms.length > 0) {
  9151. result.updateTransforms = mutation.fieldTransforms.map(transform => toFieldTransform(serializer, transform));
  9152. }
  9153. if (!mutation.precondition.isNone) {
  9154. result.currentDocument = toPrecondition(serializer, mutation.precondition);
  9155. }
  9156. return result;
  9157. }
  9158. function fromMutation(serializer, proto) {
  9159. const precondition = proto.currentDocument
  9160. ? fromPrecondition(proto.currentDocument)
  9161. : Precondition.none();
  9162. const fieldTransforms = proto.updateTransforms
  9163. ? proto.updateTransforms.map(transform => fromFieldTransform(serializer, transform))
  9164. : [];
  9165. if (proto.update) {
  9166. assertPresent(proto.update.name);
  9167. const key = fromName(serializer, proto.update.name);
  9168. const value = new ObjectValue({
  9169. mapValue: { fields: proto.update.fields }
  9170. });
  9171. if (proto.updateMask) {
  9172. const fieldMask = fromDocumentMask(proto.updateMask);
  9173. return new PatchMutation(key, value, fieldMask, precondition, fieldTransforms);
  9174. }
  9175. else {
  9176. return new SetMutation(key, value, precondition, fieldTransforms);
  9177. }
  9178. }
  9179. else if (proto.delete) {
  9180. const key = fromName(serializer, proto.delete);
  9181. return new DeleteMutation(key, precondition);
  9182. }
  9183. else if (proto.verify) {
  9184. const key = fromName(serializer, proto.verify);
  9185. return new VerifyMutation(key, precondition);
  9186. }
  9187. else {
  9188. return fail();
  9189. }
  9190. }
  9191. function toPrecondition(serializer, precondition) {
  9192. if (precondition.updateTime !== undefined) {
  9193. return {
  9194. updateTime: toVersion(serializer, precondition.updateTime)
  9195. };
  9196. }
  9197. else if (precondition.exists !== undefined) {
  9198. return { exists: precondition.exists };
  9199. }
  9200. else {
  9201. return fail();
  9202. }
  9203. }
  9204. function fromPrecondition(precondition) {
  9205. if (precondition.updateTime !== undefined) {
  9206. return Precondition.updateTime(fromVersion(precondition.updateTime));
  9207. }
  9208. else if (precondition.exists !== undefined) {
  9209. return Precondition.exists(precondition.exists);
  9210. }
  9211. else {
  9212. return Precondition.none();
  9213. }
  9214. }
  9215. function fromWriteResult(proto, commitTime) {
  9216. // NOTE: Deletes don't have an updateTime.
  9217. let version = proto.updateTime
  9218. ? fromVersion(proto.updateTime)
  9219. : fromVersion(commitTime);
  9220. if (version.isEqual(SnapshotVersion.min())) {
  9221. // The Firestore Emulator currently returns an update time of 0 for
  9222. // deletes of non-existing documents (rather than null). This breaks the
  9223. // test "get deleted doc while offline with source=cache" as NoDocuments
  9224. // with version 0 are filtered by IndexedDb's RemoteDocumentCache.
  9225. // TODO(#2149): Remove this when Emulator is fixed
  9226. version = fromVersion(commitTime);
  9227. }
  9228. return new MutationResult(version, proto.transformResults || []);
  9229. }
  9230. function fromWriteResults(protos, commitTime) {
  9231. if (protos && protos.length > 0) {
  9232. hardAssert(commitTime !== undefined);
  9233. return protos.map(proto => fromWriteResult(proto, commitTime));
  9234. }
  9235. else {
  9236. return [];
  9237. }
  9238. }
  9239. function toFieldTransform(serializer, fieldTransform) {
  9240. const transform = fieldTransform.transform;
  9241. if (transform instanceof ServerTimestampTransform) {
  9242. return {
  9243. fieldPath: fieldTransform.field.canonicalString(),
  9244. setToServerValue: 'REQUEST_TIME'
  9245. };
  9246. }
  9247. else if (transform instanceof ArrayUnionTransformOperation) {
  9248. return {
  9249. fieldPath: fieldTransform.field.canonicalString(),
  9250. appendMissingElements: {
  9251. values: transform.elements
  9252. }
  9253. };
  9254. }
  9255. else if (transform instanceof ArrayRemoveTransformOperation) {
  9256. return {
  9257. fieldPath: fieldTransform.field.canonicalString(),
  9258. removeAllFromArray: {
  9259. values: transform.elements
  9260. }
  9261. };
  9262. }
  9263. else if (transform instanceof NumericIncrementTransformOperation) {
  9264. return {
  9265. fieldPath: fieldTransform.field.canonicalString(),
  9266. increment: transform.operand
  9267. };
  9268. }
  9269. else {
  9270. throw fail();
  9271. }
  9272. }
  9273. function fromFieldTransform(serializer, proto) {
  9274. let transform = null;
  9275. if ('setToServerValue' in proto) {
  9276. hardAssert(proto.setToServerValue === 'REQUEST_TIME');
  9277. transform = new ServerTimestampTransform();
  9278. }
  9279. else if ('appendMissingElements' in proto) {
  9280. const values = proto.appendMissingElements.values || [];
  9281. transform = new ArrayUnionTransformOperation(values);
  9282. }
  9283. else if ('removeAllFromArray' in proto) {
  9284. const values = proto.removeAllFromArray.values || [];
  9285. transform = new ArrayRemoveTransformOperation(values);
  9286. }
  9287. else if ('increment' in proto) {
  9288. transform = new NumericIncrementTransformOperation(serializer, proto.increment);
  9289. }
  9290. else {
  9291. fail();
  9292. }
  9293. const fieldPath = FieldPath$1.fromServerFormat(proto.fieldPath);
  9294. return new FieldTransform(fieldPath, transform);
  9295. }
  9296. function toDocumentsTarget(serializer, target) {
  9297. return { documents: [toQueryPath(serializer, target.path)] };
  9298. }
  9299. function fromDocumentsTarget(documentsTarget) {
  9300. const count = documentsTarget.documents.length;
  9301. hardAssert(count === 1);
  9302. const name = documentsTarget.documents[0];
  9303. return queryToTarget(newQueryForPath(fromQueryPath(name)));
  9304. }
  9305. function toQueryTarget(serializer, target) {
  9306. // Dissect the path into parent, collectionId, and optional key filter.
  9307. const result = { structuredQuery: {} };
  9308. const path = target.path;
  9309. if (target.collectionGroup !== null) {
  9310. result.parent = toQueryPath(serializer, path);
  9311. result.structuredQuery.from = [
  9312. {
  9313. collectionId: target.collectionGroup,
  9314. allDescendants: true
  9315. }
  9316. ];
  9317. }
  9318. else {
  9319. result.parent = toQueryPath(serializer, path.popLast());
  9320. result.structuredQuery.from = [{ collectionId: path.lastSegment() }];
  9321. }
  9322. const where = toFilters(target.filters);
  9323. if (where) {
  9324. result.structuredQuery.where = where;
  9325. }
  9326. const orderBy = toOrder(target.orderBy);
  9327. if (orderBy) {
  9328. result.structuredQuery.orderBy = orderBy;
  9329. }
  9330. const limit = toInt32Proto(serializer, target.limit);
  9331. if (limit !== null) {
  9332. result.structuredQuery.limit = limit;
  9333. }
  9334. if (target.startAt) {
  9335. result.structuredQuery.startAt = toStartAtCursor(target.startAt);
  9336. }
  9337. if (target.endAt) {
  9338. result.structuredQuery.endAt = toEndAtCursor(target.endAt);
  9339. }
  9340. return result;
  9341. }
  9342. function toRunAggregationQueryRequest(serializer, target, aggregates) {
  9343. const queryTarget = toQueryTarget(serializer, target);
  9344. const aliasMap = {};
  9345. const aggregations = [];
  9346. let aggregationNum = 0;
  9347. aggregates.forEach(aggregate => {
  9348. // Map all client-side aliases to a unique short-form
  9349. // alias. This avoids issues with client-side aliases that
  9350. // exceed the 1500-byte string size limit.
  9351. const serverAlias = `aggregate_${aggregationNum++}`;
  9352. aliasMap[serverAlias] = aggregate.alias;
  9353. if (aggregate.aggregateType === 'count') {
  9354. aggregations.push({
  9355. alias: serverAlias,
  9356. count: {}
  9357. });
  9358. }
  9359. else if (aggregate.aggregateType === 'avg') {
  9360. aggregations.push({
  9361. alias: serverAlias,
  9362. avg: {
  9363. field: toFieldPathReference(aggregate.fieldPath)
  9364. }
  9365. });
  9366. }
  9367. else if (aggregate.aggregateType === 'sum') {
  9368. aggregations.push({
  9369. alias: serverAlias,
  9370. sum: {
  9371. field: toFieldPathReference(aggregate.fieldPath)
  9372. }
  9373. });
  9374. }
  9375. });
  9376. return {
  9377. request: {
  9378. structuredAggregationQuery: {
  9379. aggregations,
  9380. structuredQuery: queryTarget.structuredQuery
  9381. },
  9382. parent: queryTarget.parent
  9383. },
  9384. aliasMap
  9385. };
  9386. }
  9387. function convertQueryTargetToQuery(target) {
  9388. let path = fromQueryPath(target.parent);
  9389. const query = target.structuredQuery;
  9390. const fromCount = query.from ? query.from.length : 0;
  9391. let collectionGroup = null;
  9392. if (fromCount > 0) {
  9393. hardAssert(fromCount === 1);
  9394. const from = query.from[0];
  9395. if (from.allDescendants) {
  9396. collectionGroup = from.collectionId;
  9397. }
  9398. else {
  9399. path = path.child(from.collectionId);
  9400. }
  9401. }
  9402. let filterBy = [];
  9403. if (query.where) {
  9404. filterBy = fromFilters(query.where);
  9405. }
  9406. let orderBy = [];
  9407. if (query.orderBy) {
  9408. orderBy = fromOrder(query.orderBy);
  9409. }
  9410. let limit = null;
  9411. if (query.limit) {
  9412. limit = fromInt32Proto(query.limit);
  9413. }
  9414. let startAt = null;
  9415. if (query.startAt) {
  9416. startAt = fromStartAtCursor(query.startAt);
  9417. }
  9418. let endAt = null;
  9419. if (query.endAt) {
  9420. endAt = fromEndAtCursor(query.endAt);
  9421. }
  9422. return newQuery(path, collectionGroup, orderBy, filterBy, limit, "F" /* LimitType.First */, startAt, endAt);
  9423. }
  9424. function fromQueryTarget(target) {
  9425. return queryToTarget(convertQueryTargetToQuery(target));
  9426. }
  9427. function toListenRequestLabels(serializer, targetData) {
  9428. const value = toLabel(targetData.purpose);
  9429. if (value == null) {
  9430. return null;
  9431. }
  9432. else {
  9433. return {
  9434. 'goog-listen-tags': value
  9435. };
  9436. }
  9437. }
  9438. function toLabel(purpose) {
  9439. switch (purpose) {
  9440. case "TargetPurposeListen" /* TargetPurpose.Listen */:
  9441. return null;
  9442. case "TargetPurposeExistenceFilterMismatch" /* TargetPurpose.ExistenceFilterMismatch */:
  9443. return 'existence-filter-mismatch';
  9444. case "TargetPurposeExistenceFilterMismatchBloom" /* TargetPurpose.ExistenceFilterMismatchBloom */:
  9445. return 'existence-filter-mismatch-bloom';
  9446. case "TargetPurposeLimboResolution" /* TargetPurpose.LimboResolution */:
  9447. return 'limbo-document';
  9448. default:
  9449. return fail();
  9450. }
  9451. }
  9452. function toTarget(serializer, targetData) {
  9453. let result;
  9454. const target = targetData.target;
  9455. if (targetIsDocumentTarget(target)) {
  9456. result = { documents: toDocumentsTarget(serializer, target) };
  9457. }
  9458. else {
  9459. result = { query: toQueryTarget(serializer, target) };
  9460. }
  9461. result.targetId = targetData.targetId;
  9462. if (targetData.resumeToken.approximateByteSize() > 0) {
  9463. result.resumeToken = toBytes(serializer, targetData.resumeToken);
  9464. const expectedCount = toInt32Proto(serializer, targetData.expectedCount);
  9465. if (expectedCount !== null) {
  9466. result.expectedCount = expectedCount;
  9467. }
  9468. }
  9469. else if (targetData.snapshotVersion.compareTo(SnapshotVersion.min()) > 0) {
  9470. // TODO(wuandy): Consider removing above check because it is most likely true.
  9471. // Right now, many tests depend on this behaviour though (leaving min() out
  9472. // of serialization).
  9473. result.readTime = toTimestamp(serializer, targetData.snapshotVersion.toTimestamp());
  9474. const expectedCount = toInt32Proto(serializer, targetData.expectedCount);
  9475. if (expectedCount !== null) {
  9476. result.expectedCount = expectedCount;
  9477. }
  9478. }
  9479. return result;
  9480. }
  9481. function toFilters(filters) {
  9482. if (filters.length === 0) {
  9483. return;
  9484. }
  9485. return toFilter(CompositeFilter.create(filters, "and" /* CompositeOperator.AND */));
  9486. }
  9487. function fromFilters(filter) {
  9488. const result = fromFilter(filter);
  9489. if (result instanceof CompositeFilter &&
  9490. compositeFilterIsFlatConjunction(result)) {
  9491. return result.getFilters();
  9492. }
  9493. return [result];
  9494. }
  9495. function fromFilter(filter) {
  9496. if (filter.unaryFilter !== undefined) {
  9497. return fromUnaryFilter(filter);
  9498. }
  9499. else if (filter.fieldFilter !== undefined) {
  9500. return fromFieldFilter(filter);
  9501. }
  9502. else if (filter.compositeFilter !== undefined) {
  9503. return fromCompositeFilter(filter);
  9504. }
  9505. else {
  9506. return fail();
  9507. }
  9508. }
  9509. function toOrder(orderBys) {
  9510. if (orderBys.length === 0) {
  9511. return;
  9512. }
  9513. return orderBys.map(order => toPropertyOrder(order));
  9514. }
  9515. function fromOrder(orderBys) {
  9516. return orderBys.map(order => fromPropertyOrder(order));
  9517. }
  9518. function toStartAtCursor(cursor) {
  9519. return {
  9520. before: cursor.inclusive,
  9521. values: cursor.position
  9522. };
  9523. }
  9524. function toEndAtCursor(cursor) {
  9525. return {
  9526. before: !cursor.inclusive,
  9527. values: cursor.position
  9528. };
  9529. }
  9530. function fromStartAtCursor(cursor) {
  9531. const inclusive = !!cursor.before;
  9532. const position = cursor.values || [];
  9533. return new Bound(position, inclusive);
  9534. }
  9535. function fromEndAtCursor(cursor) {
  9536. const inclusive = !cursor.before;
  9537. const position = cursor.values || [];
  9538. return new Bound(position, inclusive);
  9539. }
  9540. // visible for testing
  9541. function toDirection(dir) {
  9542. return DIRECTIONS[dir];
  9543. }
  9544. // visible for testing
  9545. function fromDirection(dir) {
  9546. switch (dir) {
  9547. case 'ASCENDING':
  9548. return "asc" /* Direction.ASCENDING */;
  9549. case 'DESCENDING':
  9550. return "desc" /* Direction.DESCENDING */;
  9551. default:
  9552. return undefined;
  9553. }
  9554. }
  9555. // visible for testing
  9556. function toOperatorName(op) {
  9557. return OPERATORS[op];
  9558. }
  9559. function toCompositeOperatorName(op) {
  9560. return COMPOSITE_OPERATORS[op];
  9561. }
  9562. function fromOperatorName(op) {
  9563. switch (op) {
  9564. case 'EQUAL':
  9565. return "==" /* Operator.EQUAL */;
  9566. case 'NOT_EQUAL':
  9567. return "!=" /* Operator.NOT_EQUAL */;
  9568. case 'GREATER_THAN':
  9569. return ">" /* Operator.GREATER_THAN */;
  9570. case 'GREATER_THAN_OR_EQUAL':
  9571. return ">=" /* Operator.GREATER_THAN_OR_EQUAL */;
  9572. case 'LESS_THAN':
  9573. return "<" /* Operator.LESS_THAN */;
  9574. case 'LESS_THAN_OR_EQUAL':
  9575. return "<=" /* Operator.LESS_THAN_OR_EQUAL */;
  9576. case 'ARRAY_CONTAINS':
  9577. return "array-contains" /* Operator.ARRAY_CONTAINS */;
  9578. case 'IN':
  9579. return "in" /* Operator.IN */;
  9580. case 'NOT_IN':
  9581. return "not-in" /* Operator.NOT_IN */;
  9582. case 'ARRAY_CONTAINS_ANY':
  9583. return "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */;
  9584. case 'OPERATOR_UNSPECIFIED':
  9585. return fail();
  9586. default:
  9587. return fail();
  9588. }
  9589. }
  9590. function fromCompositeOperatorName(op) {
  9591. switch (op) {
  9592. case 'AND':
  9593. return "and" /* CompositeOperator.AND */;
  9594. case 'OR':
  9595. return "or" /* CompositeOperator.OR */;
  9596. default:
  9597. return fail();
  9598. }
  9599. }
  9600. function toFieldPathReference(path) {
  9601. return { fieldPath: path.canonicalString() };
  9602. }
  9603. function fromFieldPathReference(fieldReference) {
  9604. return FieldPath$1.fromServerFormat(fieldReference.fieldPath);
  9605. }
  9606. // visible for testing
  9607. function toPropertyOrder(orderBy) {
  9608. return {
  9609. field: toFieldPathReference(orderBy.field),
  9610. direction: toDirection(orderBy.dir)
  9611. };
  9612. }
  9613. function fromPropertyOrder(orderBy) {
  9614. return new OrderBy(fromFieldPathReference(orderBy.field), fromDirection(orderBy.direction));
  9615. }
  9616. // visible for testing
  9617. function toFilter(filter) {
  9618. if (filter instanceof FieldFilter) {
  9619. return toUnaryOrFieldFilter(filter);
  9620. }
  9621. else if (filter instanceof CompositeFilter) {
  9622. return toCompositeFilter(filter);
  9623. }
  9624. else {
  9625. return fail();
  9626. }
  9627. }
  9628. function toCompositeFilter(filter) {
  9629. const protos = filter.getFilters().map(filter => toFilter(filter));
  9630. if (protos.length === 1) {
  9631. return protos[0];
  9632. }
  9633. return {
  9634. compositeFilter: {
  9635. op: toCompositeOperatorName(filter.op),
  9636. filters: protos
  9637. }
  9638. };
  9639. }
  9640. function toUnaryOrFieldFilter(filter) {
  9641. if (filter.op === "==" /* Operator.EQUAL */) {
  9642. if (isNanValue(filter.value)) {
  9643. return {
  9644. unaryFilter: {
  9645. field: toFieldPathReference(filter.field),
  9646. op: 'IS_NAN'
  9647. }
  9648. };
  9649. }
  9650. else if (isNullValue(filter.value)) {
  9651. return {
  9652. unaryFilter: {
  9653. field: toFieldPathReference(filter.field),
  9654. op: 'IS_NULL'
  9655. }
  9656. };
  9657. }
  9658. }
  9659. else if (filter.op === "!=" /* Operator.NOT_EQUAL */) {
  9660. if (isNanValue(filter.value)) {
  9661. return {
  9662. unaryFilter: {
  9663. field: toFieldPathReference(filter.field),
  9664. op: 'IS_NOT_NAN'
  9665. }
  9666. };
  9667. }
  9668. else if (isNullValue(filter.value)) {
  9669. return {
  9670. unaryFilter: {
  9671. field: toFieldPathReference(filter.field),
  9672. op: 'IS_NOT_NULL'
  9673. }
  9674. };
  9675. }
  9676. }
  9677. return {
  9678. fieldFilter: {
  9679. field: toFieldPathReference(filter.field),
  9680. op: toOperatorName(filter.op),
  9681. value: filter.value
  9682. }
  9683. };
  9684. }
  9685. function fromUnaryFilter(filter) {
  9686. switch (filter.unaryFilter.op) {
  9687. case 'IS_NAN':
  9688. const nanField = fromFieldPathReference(filter.unaryFilter.field);
  9689. return FieldFilter.create(nanField, "==" /* Operator.EQUAL */, {
  9690. doubleValue: NaN
  9691. });
  9692. case 'IS_NULL':
  9693. const nullField = fromFieldPathReference(filter.unaryFilter.field);
  9694. return FieldFilter.create(nullField, "==" /* Operator.EQUAL */, {
  9695. nullValue: 'NULL_VALUE'
  9696. });
  9697. case 'IS_NOT_NAN':
  9698. const notNanField = fromFieldPathReference(filter.unaryFilter.field);
  9699. return FieldFilter.create(notNanField, "!=" /* Operator.NOT_EQUAL */, {
  9700. doubleValue: NaN
  9701. });
  9702. case 'IS_NOT_NULL':
  9703. const notNullField = fromFieldPathReference(filter.unaryFilter.field);
  9704. return FieldFilter.create(notNullField, "!=" /* Operator.NOT_EQUAL */, {
  9705. nullValue: 'NULL_VALUE'
  9706. });
  9707. case 'OPERATOR_UNSPECIFIED':
  9708. return fail();
  9709. default:
  9710. return fail();
  9711. }
  9712. }
  9713. function fromFieldFilter(filter) {
  9714. return FieldFilter.create(fromFieldPathReference(filter.fieldFilter.field), fromOperatorName(filter.fieldFilter.op), filter.fieldFilter.value);
  9715. }
  9716. function fromCompositeFilter(filter) {
  9717. return CompositeFilter.create(filter.compositeFilter.filters.map(filter => fromFilter(filter)), fromCompositeOperatorName(filter.compositeFilter.op));
  9718. }
  9719. function toDocumentMask(fieldMask) {
  9720. const canonicalFields = [];
  9721. fieldMask.fields.forEach(field => canonicalFields.push(field.canonicalString()));
  9722. return {
  9723. fieldPaths: canonicalFields
  9724. };
  9725. }
  9726. function fromDocumentMask(proto) {
  9727. const paths = proto.fieldPaths || [];
  9728. return new FieldMask(paths.map(path => FieldPath$1.fromServerFormat(path)));
  9729. }
  9730. function isValidResourceName(path) {
  9731. // Resource names have at least 4 components (project ID, database ID)
  9732. return (path.length >= 4 &&
  9733. path.get(0) === 'projects' &&
  9734. path.get(2) === 'databases');
  9735. }
  9736. /**
  9737. * @license
  9738. * Copyright 2017 Google LLC
  9739. *
  9740. * Licensed under the Apache License, Version 2.0 (the "License");
  9741. * you may not use this file except in compliance with the License.
  9742. * You may obtain a copy of the License at
  9743. *
  9744. * http://www.apache.org/licenses/LICENSE-2.0
  9745. *
  9746. * Unless required by applicable law or agreed to in writing, software
  9747. * distributed under the License is distributed on an "AS IS" BASIS,
  9748. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9749. * See the License for the specific language governing permissions and
  9750. * limitations under the License.
  9751. */
  9752. /**
  9753. * An immutable set of metadata that the local store tracks for each target.
  9754. */
  9755. class TargetData {
  9756. constructor(
  9757. /** The target being listened to. */
  9758. target,
  9759. /**
  9760. * The target ID to which the target corresponds; Assigned by the
  9761. * LocalStore for user listens and by the SyncEngine for limbo watches.
  9762. */
  9763. targetId,
  9764. /** The purpose of the target. */
  9765. purpose,
  9766. /**
  9767. * The sequence number of the last transaction during which this target data
  9768. * was modified.
  9769. */
  9770. sequenceNumber,
  9771. /** The latest snapshot version seen for this target. */
  9772. snapshotVersion = SnapshotVersion.min(),
  9773. /**
  9774. * The maximum snapshot version at which the associated view
  9775. * contained no limbo documents.
  9776. */
  9777. lastLimboFreeSnapshotVersion = SnapshotVersion.min(),
  9778. /**
  9779. * An opaque, server-assigned token that allows watching a target to be
  9780. * resumed after disconnecting without retransmitting all the data that
  9781. * matches the target. The resume token essentially identifies a point in
  9782. * time from which the server should resume sending results.
  9783. */
  9784. resumeToken = ByteString.EMPTY_BYTE_STRING,
  9785. /**
  9786. * The number of documents that last matched the query at the resume token or
  9787. * read time. Documents are counted only when making a listen request with
  9788. * resume token or read time, otherwise, keep it null.
  9789. */
  9790. expectedCount = null) {
  9791. this.target = target;
  9792. this.targetId = targetId;
  9793. this.purpose = purpose;
  9794. this.sequenceNumber = sequenceNumber;
  9795. this.snapshotVersion = snapshotVersion;
  9796. this.lastLimboFreeSnapshotVersion = lastLimboFreeSnapshotVersion;
  9797. this.resumeToken = resumeToken;
  9798. this.expectedCount = expectedCount;
  9799. }
  9800. /** Creates a new target data instance with an updated sequence number. */
  9801. withSequenceNumber(sequenceNumber) {
  9802. return new TargetData(this.target, this.targetId, this.purpose, sequenceNumber, this.snapshotVersion, this.lastLimboFreeSnapshotVersion, this.resumeToken, this.expectedCount);
  9803. }
  9804. /**
  9805. * Creates a new target data instance with an updated resume token and
  9806. * snapshot version.
  9807. */
  9808. withResumeToken(resumeToken, snapshotVersion) {
  9809. return new TargetData(this.target, this.targetId, this.purpose, this.sequenceNumber, snapshotVersion, this.lastLimboFreeSnapshotVersion, resumeToken,
  9810. /* expectedCount= */ null);
  9811. }
  9812. /**
  9813. * Creates a new target data instance with an updated expected count.
  9814. */
  9815. withExpectedCount(expectedCount) {
  9816. return new TargetData(this.target, this.targetId, this.purpose, this.sequenceNumber, this.snapshotVersion, this.lastLimboFreeSnapshotVersion, this.resumeToken, expectedCount);
  9817. }
  9818. /**
  9819. * Creates a new target data instance with an updated last limbo free
  9820. * snapshot version number.
  9821. */
  9822. withLastLimboFreeSnapshotVersion(lastLimboFreeSnapshotVersion) {
  9823. return new TargetData(this.target, this.targetId, this.purpose, this.sequenceNumber, this.snapshotVersion, lastLimboFreeSnapshotVersion, this.resumeToken, this.expectedCount);
  9824. }
  9825. }
  9826. /**
  9827. * @license
  9828. * Copyright 2017 Google LLC
  9829. *
  9830. * Licensed under the Apache License, Version 2.0 (the "License");
  9831. * you may not use this file except in compliance with the License.
  9832. * You may obtain a copy of the License at
  9833. *
  9834. * http://www.apache.org/licenses/LICENSE-2.0
  9835. *
  9836. * Unless required by applicable law or agreed to in writing, software
  9837. * distributed under the License is distributed on an "AS IS" BASIS,
  9838. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9839. * See the License for the specific language governing permissions and
  9840. * limitations under the License.
  9841. */
  9842. /** Serializer for values stored in the LocalStore. */
  9843. class LocalSerializer {
  9844. constructor(remoteSerializer) {
  9845. this.remoteSerializer = remoteSerializer;
  9846. }
  9847. }
  9848. /** Decodes a remote document from storage locally to a Document. */
  9849. function fromDbRemoteDocument(localSerializer, remoteDoc) {
  9850. let doc;
  9851. if (remoteDoc.document) {
  9852. doc = fromDocument(localSerializer.remoteSerializer, remoteDoc.document, !!remoteDoc.hasCommittedMutations);
  9853. }
  9854. else if (remoteDoc.noDocument) {
  9855. const key = DocumentKey.fromSegments(remoteDoc.noDocument.path);
  9856. const version = fromDbTimestamp(remoteDoc.noDocument.readTime);
  9857. doc = MutableDocument.newNoDocument(key, version);
  9858. if (remoteDoc.hasCommittedMutations) {
  9859. doc.setHasCommittedMutations();
  9860. }
  9861. }
  9862. else if (remoteDoc.unknownDocument) {
  9863. const key = DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
  9864. const version = fromDbTimestamp(remoteDoc.unknownDocument.version);
  9865. doc = MutableDocument.newUnknownDocument(key, version);
  9866. }
  9867. else {
  9868. return fail();
  9869. }
  9870. if (remoteDoc.readTime) {
  9871. doc.setReadTime(fromDbTimestampKey(remoteDoc.readTime));
  9872. }
  9873. return doc;
  9874. }
  9875. /** Encodes a document for storage locally. */
  9876. function toDbRemoteDocument(localSerializer, document) {
  9877. const key = document.key;
  9878. const remoteDoc = {
  9879. prefixPath: key.getCollectionPath().popLast().toArray(),
  9880. collectionGroup: key.collectionGroup,
  9881. documentId: key.path.lastSegment(),
  9882. readTime: toDbTimestampKey(document.readTime),
  9883. hasCommittedMutations: document.hasCommittedMutations
  9884. };
  9885. if (document.isFoundDocument()) {
  9886. remoteDoc.document = toDocument(localSerializer.remoteSerializer, document);
  9887. }
  9888. else if (document.isNoDocument()) {
  9889. remoteDoc.noDocument = {
  9890. path: key.path.toArray(),
  9891. readTime: toDbTimestamp(document.version)
  9892. };
  9893. }
  9894. else if (document.isUnknownDocument()) {
  9895. remoteDoc.unknownDocument = {
  9896. path: key.path.toArray(),
  9897. version: toDbTimestamp(document.version)
  9898. };
  9899. }
  9900. else {
  9901. return fail();
  9902. }
  9903. return remoteDoc;
  9904. }
  9905. function toDbTimestampKey(snapshotVersion) {
  9906. const timestamp = snapshotVersion.toTimestamp();
  9907. return [timestamp.seconds, timestamp.nanoseconds];
  9908. }
  9909. function fromDbTimestampKey(dbTimestampKey) {
  9910. const timestamp = new Timestamp(dbTimestampKey[0], dbTimestampKey[1]);
  9911. return SnapshotVersion.fromTimestamp(timestamp);
  9912. }
  9913. function toDbTimestamp(snapshotVersion) {
  9914. const timestamp = snapshotVersion.toTimestamp();
  9915. return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
  9916. }
  9917. function fromDbTimestamp(dbTimestamp) {
  9918. const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
  9919. return SnapshotVersion.fromTimestamp(timestamp);
  9920. }
  9921. /** Encodes a batch of mutations into a DbMutationBatch for local storage. */
  9922. function toDbMutationBatch(localSerializer, userId, batch) {
  9923. const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
  9924. const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
  9925. return {
  9926. userId,
  9927. batchId: batch.batchId,
  9928. localWriteTimeMs: batch.localWriteTime.toMillis(),
  9929. baseMutations: serializedBaseMutations,
  9930. mutations: serializedMutations
  9931. };
  9932. }
  9933. /** Decodes a DbMutationBatch into a MutationBatch */
  9934. function fromDbMutationBatch(localSerializer, dbBatch) {
  9935. const baseMutations = (dbBatch.baseMutations || []).map(m => fromMutation(localSerializer.remoteSerializer, m));
  9936. // Squash old transform mutations into existing patch or set mutations.
  9937. // The replacement of representing `transforms` with `update_transforms`
  9938. // on the SDK means that old `transform` mutations stored in IndexedDB need
  9939. // to be updated to `update_transforms`.
  9940. // TODO(b/174608374): Remove this code once we perform a schema migration.
  9941. for (let i = 0; i < dbBatch.mutations.length - 1; ++i) {
  9942. const currentMutation = dbBatch.mutations[i];
  9943. const hasTransform = i + 1 < dbBatch.mutations.length &&
  9944. dbBatch.mutations[i + 1].transform !== undefined;
  9945. if (hasTransform) {
  9946. const transformMutation = dbBatch.mutations[i + 1];
  9947. currentMutation.updateTransforms =
  9948. transformMutation.transform.fieldTransforms;
  9949. dbBatch.mutations.splice(i + 1, 1);
  9950. ++i;
  9951. }
  9952. }
  9953. const mutations = dbBatch.mutations.map(m => fromMutation(localSerializer.remoteSerializer, m));
  9954. const timestamp = Timestamp.fromMillis(dbBatch.localWriteTimeMs);
  9955. return new MutationBatch(dbBatch.batchId, timestamp, baseMutations, mutations);
  9956. }
  9957. /** Decodes a DbTarget into TargetData */
  9958. function fromDbTarget(dbTarget) {
  9959. const version = fromDbTimestamp(dbTarget.readTime);
  9960. const lastLimboFreeSnapshotVersion = dbTarget.lastLimboFreeSnapshotVersion !== undefined
  9961. ? fromDbTimestamp(dbTarget.lastLimboFreeSnapshotVersion)
  9962. : SnapshotVersion.min();
  9963. let target;
  9964. if (isDocumentQuery(dbTarget.query)) {
  9965. target = fromDocumentsTarget(dbTarget.query);
  9966. }
  9967. else {
  9968. target = fromQueryTarget(dbTarget.query);
  9969. }
  9970. return new TargetData(target, dbTarget.targetId, "TargetPurposeListen" /* TargetPurpose.Listen */, dbTarget.lastListenSequenceNumber, version, lastLimboFreeSnapshotVersion, ByteString.fromBase64String(dbTarget.resumeToken));
  9971. }
  9972. /** Encodes TargetData into a DbTarget for storage locally. */
  9973. function toDbTarget(localSerializer, targetData) {
  9974. const dbTimestamp = toDbTimestamp(targetData.snapshotVersion);
  9975. const dbLastLimboFreeTimestamp = toDbTimestamp(targetData.lastLimboFreeSnapshotVersion);
  9976. let queryProto;
  9977. if (targetIsDocumentTarget(targetData.target)) {
  9978. queryProto = toDocumentsTarget(localSerializer.remoteSerializer, targetData.target);
  9979. }
  9980. else {
  9981. queryProto = toQueryTarget(localSerializer.remoteSerializer, targetData.target);
  9982. }
  9983. // We can't store the resumeToken as a ByteString in IndexedDb, so we
  9984. // convert it to a base64 string for storage.
  9985. const resumeToken = targetData.resumeToken.toBase64();
  9986. // lastListenSequenceNumber is always 0 until we do real GC.
  9987. return {
  9988. targetId: targetData.targetId,
  9989. canonicalId: canonifyTarget(targetData.target),
  9990. readTime: dbTimestamp,
  9991. resumeToken,
  9992. lastListenSequenceNumber: targetData.sequenceNumber,
  9993. lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
  9994. query: queryProto
  9995. };
  9996. }
  9997. /**
  9998. * A helper function for figuring out what kind of query has been stored.
  9999. */
  10000. function isDocumentQuery(dbQuery) {
  10001. return dbQuery.documents !== undefined;
  10002. }
  10003. /** Encodes a DbBundle to a BundleMetadata object. */
  10004. function fromDbBundle(dbBundle) {
  10005. return {
  10006. id: dbBundle.bundleId,
  10007. createTime: fromDbTimestamp(dbBundle.createTime),
  10008. version: dbBundle.version
  10009. };
  10010. }
  10011. /** Encodes a BundleMetadata to a DbBundle. */
  10012. function toDbBundle(metadata) {
  10013. return {
  10014. bundleId: metadata.id,
  10015. createTime: toDbTimestamp(fromVersion(metadata.createTime)),
  10016. version: metadata.version
  10017. };
  10018. }
  10019. /** Encodes a DbNamedQuery to a NamedQuery. */
  10020. function fromDbNamedQuery(dbNamedQuery) {
  10021. return {
  10022. name: dbNamedQuery.name,
  10023. query: fromBundledQuery(dbNamedQuery.bundledQuery),
  10024. readTime: fromDbTimestamp(dbNamedQuery.readTime)
  10025. };
  10026. }
  10027. /** Encodes a NamedQuery from a bundle proto to a DbNamedQuery. */
  10028. function toDbNamedQuery(query) {
  10029. return {
  10030. name: query.name,
  10031. readTime: toDbTimestamp(fromVersion(query.readTime)),
  10032. bundledQuery: query.bundledQuery
  10033. };
  10034. }
  10035. /**
  10036. * Encodes a `BundledQuery` from bundle proto to a Query object.
  10037. *
  10038. * This reconstructs the original query used to build the bundle being loaded,
  10039. * including features exists only in SDKs (for example: limit-to-last).
  10040. */
  10041. function fromBundledQuery(bundledQuery) {
  10042. const query = convertQueryTargetToQuery({
  10043. parent: bundledQuery.parent,
  10044. structuredQuery: bundledQuery.structuredQuery
  10045. });
  10046. if (bundledQuery.limitType === 'LAST') {
  10047. return queryWithLimit(query, query.limit, "L" /* LimitType.Last */);
  10048. }
  10049. return query;
  10050. }
  10051. /** Encodes a NamedQuery proto object to a NamedQuery model object. */
  10052. function fromProtoNamedQuery(namedQuery) {
  10053. return {
  10054. name: namedQuery.name,
  10055. query: fromBundledQuery(namedQuery.bundledQuery),
  10056. readTime: fromVersion(namedQuery.readTime)
  10057. };
  10058. }
  10059. /** Decodes a BundleMetadata proto into a BundleMetadata object. */
  10060. function fromBundleMetadata(metadata) {
  10061. return {
  10062. id: metadata.id,
  10063. version: metadata.version,
  10064. createTime: fromVersion(metadata.createTime)
  10065. };
  10066. }
  10067. /** Encodes a DbDocumentOverlay object to an Overlay model object. */
  10068. function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
  10069. return new Overlay(dbDocumentOverlay.largestBatchId, fromMutation(localSerializer.remoteSerializer, dbDocumentOverlay.overlayMutation));
  10070. }
  10071. /** Decodes an Overlay model object into a DbDocumentOverlay object. */
  10072. function toDbDocumentOverlay(localSerializer, userId, overlay) {
  10073. const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
  10074. return {
  10075. userId,
  10076. collectionPath,
  10077. documentId,
  10078. collectionGroup: overlay.mutation.key.getCollectionGroup(),
  10079. largestBatchId: overlay.largestBatchId,
  10080. overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
  10081. };
  10082. }
  10083. /**
  10084. * Returns the DbDocumentOverlayKey corresponding to the given user and
  10085. * document key.
  10086. */
  10087. function toDbDocumentOverlayKey(userId, docKey) {
  10088. const docId = docKey.path.lastSegment();
  10089. const collectionPath = encodeResourcePath(docKey.path.popLast());
  10090. return [userId, collectionPath, docId];
  10091. }
  10092. function toDbIndexConfiguration(index) {
  10093. return {
  10094. indexId: index.indexId,
  10095. collectionGroup: index.collectionGroup,
  10096. fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
  10097. };
  10098. }
  10099. function fromDbIndexConfiguration(index, state) {
  10100. const decodedState = state
  10101. ? new IndexState(state.sequenceNumber, new IndexOffset(fromDbTimestamp(state.readTime), new DocumentKey(decodeResourcePath(state.documentKey)), state.largestBatchId))
  10102. : IndexState.empty();
  10103. const decodedSegments = index.fields.map(([fieldPath, kind]) => new IndexSegment(FieldPath$1.fromServerFormat(fieldPath), kind));
  10104. return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
  10105. }
  10106. function toDbIndexState(indexId, user, sequenceNumber, offset) {
  10107. return {
  10108. indexId,
  10109. uid: user.uid || '',
  10110. sequenceNumber,
  10111. readTime: toDbTimestamp(offset.readTime),
  10112. documentKey: encodeResourcePath(offset.documentKey.path),
  10113. largestBatchId: offset.largestBatchId
  10114. };
  10115. }
  10116. /**
  10117. * @license
  10118. * Copyright 2020 Google LLC
  10119. *
  10120. * Licensed under the Apache License, Version 2.0 (the "License");
  10121. * you may not use this file except in compliance with the License.
  10122. * You may obtain a copy of the License at
  10123. *
  10124. * http://www.apache.org/licenses/LICENSE-2.0
  10125. *
  10126. * Unless required by applicable law or agreed to in writing, software
  10127. * distributed under the License is distributed on an "AS IS" BASIS,
  10128. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10129. * See the License for the specific language governing permissions and
  10130. * limitations under the License.
  10131. */
  10132. class IndexedDbBundleCache {
  10133. getBundleMetadata(transaction, bundleId) {
  10134. return bundlesStore(transaction)
  10135. .get(bundleId)
  10136. .next(bundle => {
  10137. if (bundle) {
  10138. return fromDbBundle(bundle);
  10139. }
  10140. return undefined;
  10141. });
  10142. }
  10143. saveBundleMetadata(transaction, bundleMetadata) {
  10144. return bundlesStore(transaction).put(toDbBundle(bundleMetadata));
  10145. }
  10146. getNamedQuery(transaction, queryName) {
  10147. return namedQueriesStore(transaction)
  10148. .get(queryName)
  10149. .next(query => {
  10150. if (query) {
  10151. return fromDbNamedQuery(query);
  10152. }
  10153. return undefined;
  10154. });
  10155. }
  10156. saveNamedQuery(transaction, query) {
  10157. return namedQueriesStore(transaction).put(toDbNamedQuery(query));
  10158. }
  10159. }
  10160. /**
  10161. * Helper to get a typed SimpleDbStore for the bundles object store.
  10162. */
  10163. function bundlesStore(txn) {
  10164. return getStore(txn, DbBundleStore);
  10165. }
  10166. /**
  10167. * Helper to get a typed SimpleDbStore for the namedQueries object store.
  10168. */
  10169. function namedQueriesStore(txn) {
  10170. return getStore(txn, DbNamedQueryStore);
  10171. }
  10172. /**
  10173. * @license
  10174. * Copyright 2022 Google LLC
  10175. *
  10176. * Licensed under the Apache License, Version 2.0 (the "License");
  10177. * you may not use this file except in compliance with the License.
  10178. * You may obtain a copy of the License at
  10179. *
  10180. * http://www.apache.org/licenses/LICENSE-2.0
  10181. *
  10182. * Unless required by applicable law or agreed to in writing, software
  10183. * distributed under the License is distributed on an "AS IS" BASIS,
  10184. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10185. * See the License for the specific language governing permissions and
  10186. * limitations under the License.
  10187. */
  10188. /**
  10189. * Implementation of DocumentOverlayCache using IndexedDb.
  10190. */
  10191. class IndexedDbDocumentOverlayCache {
  10192. /**
  10193. * @param serializer - The document serializer.
  10194. * @param userId - The userId for which we are accessing overlays.
  10195. */
  10196. constructor(serializer, userId) {
  10197. this.serializer = serializer;
  10198. this.userId = userId;
  10199. }
  10200. static forUser(serializer, user) {
  10201. const userId = user.uid || '';
  10202. return new IndexedDbDocumentOverlayCache(serializer, userId);
  10203. }
  10204. getOverlay(transaction, key) {
  10205. return documentOverlayStore(transaction)
  10206. .get(toDbDocumentOverlayKey(this.userId, key))
  10207. .next(dbOverlay => {
  10208. if (dbOverlay) {
  10209. return fromDbDocumentOverlay(this.serializer, dbOverlay);
  10210. }
  10211. return null;
  10212. });
  10213. }
  10214. getOverlays(transaction, keys) {
  10215. const result = newOverlayMap();
  10216. return PersistencePromise.forEach(keys, (key) => {
  10217. return this.getOverlay(transaction, key).next(overlay => {
  10218. if (overlay !== null) {
  10219. result.set(key, overlay);
  10220. }
  10221. });
  10222. }).next(() => result);
  10223. }
  10224. saveOverlays(transaction, largestBatchId, overlays) {
  10225. const promises = [];
  10226. overlays.forEach((_, mutation) => {
  10227. const overlay = new Overlay(largestBatchId, mutation);
  10228. promises.push(this.saveOverlay(transaction, overlay));
  10229. });
  10230. return PersistencePromise.waitFor(promises);
  10231. }
  10232. removeOverlaysForBatchId(transaction, documentKeys, batchId) {
  10233. const collectionPaths = new Set();
  10234. // Get the set of unique collection paths.
  10235. documentKeys.forEach(key => collectionPaths.add(encodeResourcePath(key.getCollectionPath())));
  10236. const promises = [];
  10237. collectionPaths.forEach(collectionPath => {
  10238. const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
  10239. /*lowerOpen=*/ false,
  10240. /*upperOpen=*/ true);
  10241. promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
  10242. });
  10243. return PersistencePromise.waitFor(promises);
  10244. }
  10245. getOverlaysForCollection(transaction, collection, sinceBatchId) {
  10246. const result = newOverlayMap();
  10247. const collectionPath = encodeResourcePath(collection);
  10248. // We want batch IDs larger than `sinceBatchId`, and so the lower bound
  10249. // is not inclusive.
  10250. const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
  10251. /*lowerOpen=*/ true);
  10252. return documentOverlayStore(transaction)
  10253. .loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
  10254. .next(dbOverlays => {
  10255. for (const dbOverlay of dbOverlays) {
  10256. const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
  10257. result.set(overlay.getKey(), overlay);
  10258. }
  10259. return result;
  10260. });
  10261. }
  10262. getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
  10263. const result = newOverlayMap();
  10264. let currentBatchId = undefined;
  10265. // We want batch IDs larger than `sinceBatchId`, and so the lower bound
  10266. // is not inclusive.
  10267. const range = IDBKeyRange.bound([this.userId, collectionGroup, sinceBatchId], [this.userId, collectionGroup, Number.POSITIVE_INFINITY],
  10268. /*lowerOpen=*/ true);
  10269. return documentOverlayStore(transaction)
  10270. .iterate({
  10271. index: DbDocumentOverlayCollectionGroupOverlayIndex,
  10272. range
  10273. }, (_, dbOverlay, control) => {
  10274. // We do not want to return partial batch overlays, even if the size
  10275. // of the result set exceeds the given `count` argument. Therefore, we
  10276. // continue to aggregate results even after the result size exceeds
  10277. // `count` if there are more overlays from the `currentBatchId`.
  10278. const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
  10279. if (result.size() < count ||
  10280. overlay.largestBatchId === currentBatchId) {
  10281. result.set(overlay.getKey(), overlay);
  10282. currentBatchId = overlay.largestBatchId;
  10283. }
  10284. else {
  10285. control.done();
  10286. }
  10287. })
  10288. .next(() => result);
  10289. }
  10290. saveOverlay(transaction, overlay) {
  10291. return documentOverlayStore(transaction).put(toDbDocumentOverlay(this.serializer, this.userId, overlay));
  10292. }
  10293. }
  10294. /**
  10295. * Helper to get a typed SimpleDbStore for the document overlay object store.
  10296. */
  10297. function documentOverlayStore(txn) {
  10298. return getStore(txn, DbDocumentOverlayStore);
  10299. }
  10300. /**
  10301. * @license
  10302. * Copyright 2021 Google LLC
  10303. *
  10304. * Licensed under the Apache License, Version 2.0 (the "License");
  10305. * you may not use this file except in compliance with the License.
  10306. * You may obtain a copy of the License at
  10307. *
  10308. * http://www.apache.org/licenses/LICENSE-2.0
  10309. *
  10310. * Unless required by applicable law or agreed to in writing, software
  10311. * distributed under the License is distributed on an "AS IS" BASIS,
  10312. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10313. * See the License for the specific language governing permissions and
  10314. * limitations under the License.
  10315. */
  10316. // Note: This code is copied from the backend. Code that is not used by
  10317. // Firestore was removed.
  10318. const INDEX_TYPE_NULL = 5;
  10319. const INDEX_TYPE_BOOLEAN = 10;
  10320. const INDEX_TYPE_NAN = 13;
  10321. const INDEX_TYPE_NUMBER = 15;
  10322. const INDEX_TYPE_TIMESTAMP = 20;
  10323. const INDEX_TYPE_STRING = 25;
  10324. const INDEX_TYPE_BLOB = 30;
  10325. const INDEX_TYPE_REFERENCE = 37;
  10326. const INDEX_TYPE_GEOPOINT = 45;
  10327. const INDEX_TYPE_ARRAY = 50;
  10328. const INDEX_TYPE_MAP = 55;
  10329. const INDEX_TYPE_REFERENCE_SEGMENT = 60;
  10330. // A terminator that indicates that a truncatable value was not truncated.
  10331. // This must be smaller than all other type labels.
  10332. const NOT_TRUNCATED = 2;
  10333. /** Firestore index value writer. */
  10334. class FirestoreIndexValueWriter {
  10335. constructor() { }
  10336. // The write methods below short-circuit writing terminators for values
  10337. // containing a (terminating) truncated value.
  10338. //
  10339. // As an example, consider the resulting encoding for:
  10340. //
  10341. // ["bar", [2, "foo"]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TERM, TERM, TERM)
  10342. // ["bar", [2, truncated("foo")]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TRUNC)
  10343. // ["bar", truncated(["foo"])] -> (STRING, "bar", TERM, ARRAY. STRING, "foo", TERM, TRUNC)
  10344. /** Writes an index value. */
  10345. writeIndexValue(value, encoder) {
  10346. this.writeIndexValueAux(value, encoder);
  10347. // Write separator to split index values
  10348. // (see go/firestore-storage-format#encodings).
  10349. encoder.writeInfinity();
  10350. }
  10351. writeIndexValueAux(indexValue, encoder) {
  10352. if ('nullValue' in indexValue) {
  10353. this.writeValueTypeLabel(encoder, INDEX_TYPE_NULL);
  10354. }
  10355. else if ('booleanValue' in indexValue) {
  10356. this.writeValueTypeLabel(encoder, INDEX_TYPE_BOOLEAN);
  10357. encoder.writeNumber(indexValue.booleanValue ? 1 : 0);
  10358. }
  10359. else if ('integerValue' in indexValue) {
  10360. this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
  10361. encoder.writeNumber(normalizeNumber(indexValue.integerValue));
  10362. }
  10363. else if ('doubleValue' in indexValue) {
  10364. const n = normalizeNumber(indexValue.doubleValue);
  10365. if (isNaN(n)) {
  10366. this.writeValueTypeLabel(encoder, INDEX_TYPE_NAN);
  10367. }
  10368. else {
  10369. this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
  10370. if (isNegativeZero(n)) {
  10371. // -0.0, 0 and 0.0 are all considered the same
  10372. encoder.writeNumber(0.0);
  10373. }
  10374. else {
  10375. encoder.writeNumber(n);
  10376. }
  10377. }
  10378. }
  10379. else if ('timestampValue' in indexValue) {
  10380. const timestamp = indexValue.timestampValue;
  10381. this.writeValueTypeLabel(encoder, INDEX_TYPE_TIMESTAMP);
  10382. if (typeof timestamp === 'string') {
  10383. encoder.writeString(timestamp);
  10384. }
  10385. else {
  10386. encoder.writeString(`${timestamp.seconds || ''}`);
  10387. encoder.writeNumber(timestamp.nanos || 0);
  10388. }
  10389. }
  10390. else if ('stringValue' in indexValue) {
  10391. this.writeIndexString(indexValue.stringValue, encoder);
  10392. this.writeTruncationMarker(encoder);
  10393. }
  10394. else if ('bytesValue' in indexValue) {
  10395. this.writeValueTypeLabel(encoder, INDEX_TYPE_BLOB);
  10396. encoder.writeBytes(normalizeByteString(indexValue.bytesValue));
  10397. this.writeTruncationMarker(encoder);
  10398. }
  10399. else if ('referenceValue' in indexValue) {
  10400. this.writeIndexEntityRef(indexValue.referenceValue, encoder);
  10401. }
  10402. else if ('geoPointValue' in indexValue) {
  10403. const geoPoint = indexValue.geoPointValue;
  10404. this.writeValueTypeLabel(encoder, INDEX_TYPE_GEOPOINT);
  10405. encoder.writeNumber(geoPoint.latitude || 0);
  10406. encoder.writeNumber(geoPoint.longitude || 0);
  10407. }
  10408. else if ('mapValue' in indexValue) {
  10409. if (isMaxValue(indexValue)) {
  10410. this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER);
  10411. }
  10412. else {
  10413. this.writeIndexMap(indexValue.mapValue, encoder);
  10414. this.writeTruncationMarker(encoder);
  10415. }
  10416. }
  10417. else if ('arrayValue' in indexValue) {
  10418. this.writeIndexArray(indexValue.arrayValue, encoder);
  10419. this.writeTruncationMarker(encoder);
  10420. }
  10421. else {
  10422. fail();
  10423. }
  10424. }
  10425. writeIndexString(stringIndexValue, encoder) {
  10426. this.writeValueTypeLabel(encoder, INDEX_TYPE_STRING);
  10427. this.writeUnlabeledIndexString(stringIndexValue, encoder);
  10428. }
  10429. writeUnlabeledIndexString(stringIndexValue, encoder) {
  10430. encoder.writeString(stringIndexValue);
  10431. }
  10432. writeIndexMap(mapIndexValue, encoder) {
  10433. const map = mapIndexValue.fields || {};
  10434. this.writeValueTypeLabel(encoder, INDEX_TYPE_MAP);
  10435. for (const key of Object.keys(map)) {
  10436. this.writeIndexString(key, encoder);
  10437. this.writeIndexValueAux(map[key], encoder);
  10438. }
  10439. }
  10440. writeIndexArray(arrayIndexValue, encoder) {
  10441. const values = arrayIndexValue.values || [];
  10442. this.writeValueTypeLabel(encoder, INDEX_TYPE_ARRAY);
  10443. for (const element of values) {
  10444. this.writeIndexValueAux(element, encoder);
  10445. }
  10446. }
  10447. writeIndexEntityRef(referenceValue, encoder) {
  10448. this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE);
  10449. const path = DocumentKey.fromName(referenceValue).path;
  10450. path.forEach(segment => {
  10451. this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE_SEGMENT);
  10452. this.writeUnlabeledIndexString(segment, encoder);
  10453. });
  10454. }
  10455. writeValueTypeLabel(encoder, typeOrder) {
  10456. encoder.writeNumber(typeOrder);
  10457. }
  10458. writeTruncationMarker(encoder) {
  10459. // While the SDK does not implement truncation, the truncation marker is
  10460. // used to terminate all variable length values (which are strings, bytes,
  10461. // references, arrays and maps).
  10462. encoder.writeNumber(NOT_TRUNCATED);
  10463. }
  10464. }
  10465. FirestoreIndexValueWriter.INSTANCE = new FirestoreIndexValueWriter();
  10466. /**
  10467. * @license
  10468. * Copyright 2021 Google LLC
  10469. *
  10470. * Licensed under the Apache License, Version 2.0 (the "License");
  10471. * you may not use this file except in compliance with the License.
  10472. * You may obtain a copy of the License at
  10473. *
  10474. * http://www.apache.org/licenses/LICENSE-2.0
  10475. *
  10476. * Unless required by applicable law | agreed to in writing, software
  10477. * distributed under the License is distributed on an "AS IS" BASIS,
  10478. * WITHOUT WARRANTIES | CONDITIONS OF ANY KIND, either express | implied.
  10479. * See the License for the specific language governing permissions and
  10480. * limitations under the License.
  10481. */
  10482. /** These constants are taken from the backend. */
  10483. const MIN_SURROGATE = '\uD800';
  10484. const MAX_SURROGATE = '\uDBFF';
  10485. const ESCAPE1 = 0x00;
  10486. const NULL_BYTE = 0xff; // Combined with ESCAPE1
  10487. const SEPARATOR = 0x01; // Combined with ESCAPE1
  10488. const ESCAPE2 = 0xff;
  10489. const INFINITY = 0xff; // Combined with ESCAPE2
  10490. const FF_BYTE = 0x00; // Combined with ESCAPE2
  10491. const LONG_SIZE = 64;
  10492. const BYTE_SIZE = 8;
  10493. /**
  10494. * The default size of the buffer. This is arbitrary, but likely larger than
  10495. * most index values so that less copies of the underlying buffer will be made.
  10496. * For large values, a single copy will made to double the buffer length.
  10497. */
  10498. const DEFAULT_BUFFER_SIZE = 1024;
  10499. /** Converts a JavaScript number to a byte array (using big endian encoding). */
  10500. function doubleToLongBits(value) {
  10501. const dv = new DataView(new ArrayBuffer(8));
  10502. dv.setFloat64(0, value, /* littleEndian= */ false);
  10503. return new Uint8Array(dv.buffer);
  10504. }
  10505. /**
  10506. * Counts the number of zeros in a byte.
  10507. *
  10508. * Visible for testing.
  10509. */
  10510. function numberOfLeadingZerosInByte(x) {
  10511. if (x === 0) {
  10512. return 8;
  10513. }
  10514. let zeros = 0;
  10515. if (x >> 4 === 0) {
  10516. // Test if the first four bits are zero.
  10517. zeros += 4;
  10518. x = x << 4;
  10519. }
  10520. if (x >> 6 === 0) {
  10521. // Test if the first two (or next two) bits are zero.
  10522. zeros += 2;
  10523. x = x << 2;
  10524. }
  10525. if (x >> 7 === 0) {
  10526. // Test if the remaining bit is zero.
  10527. zeros += 1;
  10528. }
  10529. return zeros;
  10530. }
  10531. /** Counts the number of leading zeros in the given byte array. */
  10532. function numberOfLeadingZeros(bytes) {
  10533. let leadingZeros = 0;
  10534. for (let i = 0; i < 8; ++i) {
  10535. const zeros = numberOfLeadingZerosInByte(bytes[i] & 0xff);
  10536. leadingZeros += zeros;
  10537. if (zeros !== 8) {
  10538. break;
  10539. }
  10540. }
  10541. return leadingZeros;
  10542. }
  10543. /**
  10544. * Returns the number of bytes required to store "value". Leading zero bytes
  10545. * are skipped.
  10546. */
  10547. function unsignedNumLength(value) {
  10548. // This is just the number of bytes for the unsigned representation of the number.
  10549. const numBits = LONG_SIZE - numberOfLeadingZeros(value);
  10550. return Math.ceil(numBits / BYTE_SIZE);
  10551. }
  10552. /**
  10553. * OrderedCodeWriter is a minimal-allocation implementation of the writing
  10554. * behavior defined by the backend.
  10555. *
  10556. * The code is ported from its Java counterpart.
  10557. */
  10558. class OrderedCodeWriter {
  10559. constructor() {
  10560. this.buffer = new Uint8Array(DEFAULT_BUFFER_SIZE);
  10561. this.position = 0;
  10562. }
  10563. writeBytesAscending(value) {
  10564. const it = value[Symbol.iterator]();
  10565. let byte = it.next();
  10566. while (!byte.done) {
  10567. this.writeByteAscending(byte.value);
  10568. byte = it.next();
  10569. }
  10570. this.writeSeparatorAscending();
  10571. }
  10572. writeBytesDescending(value) {
  10573. const it = value[Symbol.iterator]();
  10574. let byte = it.next();
  10575. while (!byte.done) {
  10576. this.writeByteDescending(byte.value);
  10577. byte = it.next();
  10578. }
  10579. this.writeSeparatorDescending();
  10580. }
  10581. /** Writes utf8 bytes into this byte sequence, ascending. */
  10582. writeUtf8Ascending(sequence) {
  10583. for (const c of sequence) {
  10584. const charCode = c.charCodeAt(0);
  10585. if (charCode < 0x80) {
  10586. this.writeByteAscending(charCode);
  10587. }
  10588. else if (charCode < 0x800) {
  10589. this.writeByteAscending((0x0f << 6) | (charCode >>> 6));
  10590. this.writeByteAscending(0x80 | (0x3f & charCode));
  10591. }
  10592. else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
  10593. this.writeByteAscending((0x0f << 5) | (charCode >>> 12));
  10594. this.writeByteAscending(0x80 | (0x3f & (charCode >>> 6)));
  10595. this.writeByteAscending(0x80 | (0x3f & charCode));
  10596. }
  10597. else {
  10598. const codePoint = c.codePointAt(0);
  10599. this.writeByteAscending((0x0f << 4) | (codePoint >>> 18));
  10600. this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 12)));
  10601. this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 6)));
  10602. this.writeByteAscending(0x80 | (0x3f & codePoint));
  10603. }
  10604. }
  10605. this.writeSeparatorAscending();
  10606. }
  10607. /** Writes utf8 bytes into this byte sequence, descending */
  10608. writeUtf8Descending(sequence) {
  10609. for (const c of sequence) {
  10610. const charCode = c.charCodeAt(0);
  10611. if (charCode < 0x80) {
  10612. this.writeByteDescending(charCode);
  10613. }
  10614. else if (charCode < 0x800) {
  10615. this.writeByteDescending((0x0f << 6) | (charCode >>> 6));
  10616. this.writeByteDescending(0x80 | (0x3f & charCode));
  10617. }
  10618. else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
  10619. this.writeByteDescending((0x0f << 5) | (charCode >>> 12));
  10620. this.writeByteDescending(0x80 | (0x3f & (charCode >>> 6)));
  10621. this.writeByteDescending(0x80 | (0x3f & charCode));
  10622. }
  10623. else {
  10624. const codePoint = c.codePointAt(0);
  10625. this.writeByteDescending((0x0f << 4) | (codePoint >>> 18));
  10626. this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 12)));
  10627. this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 6)));
  10628. this.writeByteDescending(0x80 | (0x3f & codePoint));
  10629. }
  10630. }
  10631. this.writeSeparatorDescending();
  10632. }
  10633. writeNumberAscending(val) {
  10634. // Values are encoded with a single byte length prefix, followed by the
  10635. // actual value in big-endian format with leading 0 bytes dropped.
  10636. const value = this.toOrderedBits(val);
  10637. const len = unsignedNumLength(value);
  10638. this.ensureAvailable(1 + len);
  10639. this.buffer[this.position++] = len & 0xff; // Write the length
  10640. for (let i = value.length - len; i < value.length; ++i) {
  10641. this.buffer[this.position++] = value[i] & 0xff;
  10642. }
  10643. }
  10644. writeNumberDescending(val) {
  10645. // Values are encoded with a single byte length prefix, followed by the
  10646. // inverted value in big-endian format with leading 0 bytes dropped.
  10647. const value = this.toOrderedBits(val);
  10648. const len = unsignedNumLength(value);
  10649. this.ensureAvailable(1 + len);
  10650. this.buffer[this.position++] = ~(len & 0xff); // Write the length
  10651. for (let i = value.length - len; i < value.length; ++i) {
  10652. this.buffer[this.position++] = ~(value[i] & 0xff);
  10653. }
  10654. }
  10655. /**
  10656. * Writes the "infinity" byte sequence that sorts after all other byte
  10657. * sequences written in ascending order.
  10658. */
  10659. writeInfinityAscending() {
  10660. this.writeEscapedByteAscending(ESCAPE2);
  10661. this.writeEscapedByteAscending(INFINITY);
  10662. }
  10663. /**
  10664. * Writes the "infinity" byte sequence that sorts before all other byte
  10665. * sequences written in descending order.
  10666. */
  10667. writeInfinityDescending() {
  10668. this.writeEscapedByteDescending(ESCAPE2);
  10669. this.writeEscapedByteDescending(INFINITY);
  10670. }
  10671. /**
  10672. * Resets the buffer such that it is the same as when it was newly
  10673. * constructed.
  10674. */
  10675. reset() {
  10676. this.position = 0;
  10677. }
  10678. seed(encodedBytes) {
  10679. this.ensureAvailable(encodedBytes.length);
  10680. this.buffer.set(encodedBytes, this.position);
  10681. this.position += encodedBytes.length;
  10682. }
  10683. /** Makes a copy of the encoded bytes in this buffer. */
  10684. encodedBytes() {
  10685. return this.buffer.slice(0, this.position);
  10686. }
  10687. /**
  10688. * Encodes `val` into an encoding so that the order matches the IEEE 754
  10689. * floating-point comparison results with the following exceptions:
  10690. * -0.0 < 0.0
  10691. * all non-NaN < NaN
  10692. * NaN = NaN
  10693. */
  10694. toOrderedBits(val) {
  10695. const value = doubleToLongBits(val);
  10696. // Check if the first bit is set. We use a bit mask since value[0] is
  10697. // encoded as a number from 0 to 255.
  10698. const isNegative = (value[0] & 0x80) !== 0;
  10699. // Revert the two complement to get natural ordering
  10700. value[0] ^= isNegative ? 0xff : 0x80;
  10701. for (let i = 1; i < value.length; ++i) {
  10702. value[i] ^= isNegative ? 0xff : 0x00;
  10703. }
  10704. return value;
  10705. }
  10706. /** Writes a single byte ascending to the buffer. */
  10707. writeByteAscending(b) {
  10708. const masked = b & 0xff;
  10709. if (masked === ESCAPE1) {
  10710. this.writeEscapedByteAscending(ESCAPE1);
  10711. this.writeEscapedByteAscending(NULL_BYTE);
  10712. }
  10713. else if (masked === ESCAPE2) {
  10714. this.writeEscapedByteAscending(ESCAPE2);
  10715. this.writeEscapedByteAscending(FF_BYTE);
  10716. }
  10717. else {
  10718. this.writeEscapedByteAscending(masked);
  10719. }
  10720. }
  10721. /** Writes a single byte descending to the buffer. */
  10722. writeByteDescending(b) {
  10723. const masked = b & 0xff;
  10724. if (masked === ESCAPE1) {
  10725. this.writeEscapedByteDescending(ESCAPE1);
  10726. this.writeEscapedByteDescending(NULL_BYTE);
  10727. }
  10728. else if (masked === ESCAPE2) {
  10729. this.writeEscapedByteDescending(ESCAPE2);
  10730. this.writeEscapedByteDescending(FF_BYTE);
  10731. }
  10732. else {
  10733. this.writeEscapedByteDescending(b);
  10734. }
  10735. }
  10736. writeSeparatorAscending() {
  10737. this.writeEscapedByteAscending(ESCAPE1);
  10738. this.writeEscapedByteAscending(SEPARATOR);
  10739. }
  10740. writeSeparatorDescending() {
  10741. this.writeEscapedByteDescending(ESCAPE1);
  10742. this.writeEscapedByteDescending(SEPARATOR);
  10743. }
  10744. writeEscapedByteAscending(b) {
  10745. this.ensureAvailable(1);
  10746. this.buffer[this.position++] = b;
  10747. }
  10748. writeEscapedByteDescending(b) {
  10749. this.ensureAvailable(1);
  10750. this.buffer[this.position++] = ~b;
  10751. }
  10752. ensureAvailable(bytes) {
  10753. const minCapacity = bytes + this.position;
  10754. if (minCapacity <= this.buffer.length) {
  10755. return;
  10756. }
  10757. // Try doubling.
  10758. let newLength = this.buffer.length * 2;
  10759. // Still not big enough? Just allocate the right size.
  10760. if (newLength < minCapacity) {
  10761. newLength = minCapacity;
  10762. }
  10763. // Create the new buffer.
  10764. const newBuffer = new Uint8Array(newLength);
  10765. newBuffer.set(this.buffer); // copy old data
  10766. this.buffer = newBuffer;
  10767. }
  10768. }
  10769. class AscendingIndexByteEncoder {
  10770. constructor(orderedCode) {
  10771. this.orderedCode = orderedCode;
  10772. }
  10773. writeBytes(value) {
  10774. this.orderedCode.writeBytesAscending(value);
  10775. }
  10776. writeString(value) {
  10777. this.orderedCode.writeUtf8Ascending(value);
  10778. }
  10779. writeNumber(value) {
  10780. this.orderedCode.writeNumberAscending(value);
  10781. }
  10782. writeInfinity() {
  10783. this.orderedCode.writeInfinityAscending();
  10784. }
  10785. }
  10786. class DescendingIndexByteEncoder {
  10787. constructor(orderedCode) {
  10788. this.orderedCode = orderedCode;
  10789. }
  10790. writeBytes(value) {
  10791. this.orderedCode.writeBytesDescending(value);
  10792. }
  10793. writeString(value) {
  10794. this.orderedCode.writeUtf8Descending(value);
  10795. }
  10796. writeNumber(value) {
  10797. this.orderedCode.writeNumberDescending(value);
  10798. }
  10799. writeInfinity() {
  10800. this.orderedCode.writeInfinityDescending();
  10801. }
  10802. }
  10803. /**
  10804. * Implements `DirectionalIndexByteEncoder` using `OrderedCodeWriter` for the
  10805. * actual encoding.
  10806. */
  10807. class IndexByteEncoder {
  10808. constructor() {
  10809. this.orderedCode = new OrderedCodeWriter();
  10810. this.ascending = new AscendingIndexByteEncoder(this.orderedCode);
  10811. this.descending = new DescendingIndexByteEncoder(this.orderedCode);
  10812. }
  10813. seed(encodedBytes) {
  10814. this.orderedCode.seed(encodedBytes);
  10815. }
  10816. forKind(kind) {
  10817. return kind === 0 /* IndexKind.ASCENDING */ ? this.ascending : this.descending;
  10818. }
  10819. encodedBytes() {
  10820. return this.orderedCode.encodedBytes();
  10821. }
  10822. reset() {
  10823. this.orderedCode.reset();
  10824. }
  10825. }
  10826. /**
  10827. * @license
  10828. * Copyright 2022 Google LLC
  10829. *
  10830. * Licensed under the Apache License, Version 2.0 (the "License");
  10831. * you may not use this file except in compliance with the License.
  10832. * You may obtain a copy of the License at
  10833. *
  10834. * http://www.apache.org/licenses/LICENSE-2.0
  10835. *
  10836. * Unless required by applicable law or agreed to in writing, software
  10837. * distributed under the License is distributed on an "AS IS" BASIS,
  10838. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10839. * See the License for the specific language governing permissions and
  10840. * limitations under the License.
  10841. */
  10842. /** Represents an index entry saved by the SDK in persisted storage. */
  10843. class IndexEntry {
  10844. constructor(indexId, documentKey, arrayValue, directionalValue) {
  10845. this.indexId = indexId;
  10846. this.documentKey = documentKey;
  10847. this.arrayValue = arrayValue;
  10848. this.directionalValue = directionalValue;
  10849. }
  10850. /**
  10851. * Returns an IndexEntry entry that sorts immediately after the current
  10852. * directional value.
  10853. */
  10854. successor() {
  10855. const currentLength = this.directionalValue.length;
  10856. const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
  10857. ? currentLength + 1
  10858. : currentLength;
  10859. const successor = new Uint8Array(newLength);
  10860. successor.set(this.directionalValue, 0);
  10861. if (newLength !== currentLength) {
  10862. successor.set([0], this.directionalValue.length);
  10863. }
  10864. else {
  10865. ++successor[successor.length - 1];
  10866. }
  10867. return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
  10868. }
  10869. }
  10870. function indexEntryComparator(left, right) {
  10871. let cmp = left.indexId - right.indexId;
  10872. if (cmp !== 0) {
  10873. return cmp;
  10874. }
  10875. cmp = compareByteArrays(left.arrayValue, right.arrayValue);
  10876. if (cmp !== 0) {
  10877. return cmp;
  10878. }
  10879. cmp = compareByteArrays(left.directionalValue, right.directionalValue);
  10880. if (cmp !== 0) {
  10881. return cmp;
  10882. }
  10883. return DocumentKey.comparator(left.documentKey, right.documentKey);
  10884. }
  10885. function compareByteArrays(left, right) {
  10886. for (let i = 0; i < left.length && i < right.length; ++i) {
  10887. const compare = left[i] - right[i];
  10888. if (compare !== 0) {
  10889. return compare;
  10890. }
  10891. }
  10892. return left.length - right.length;
  10893. }
  10894. /**
  10895. * @license
  10896. * Copyright 2022 Google LLC
  10897. *
  10898. * Licensed under the Apache License, Version 2.0 (the "License");
  10899. * you may not use this file except in compliance with the License.
  10900. * You may obtain a copy of the License at
  10901. *
  10902. * http://www.apache.org/licenses/LICENSE-2.0
  10903. *
  10904. * Unless required by applicable law or agreed to in writing, software
  10905. * distributed under the License is distributed on an "AS IS" BASIS,
  10906. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10907. * See the License for the specific language governing permissions and
  10908. * limitations under the License.
  10909. */
  10910. /**
  10911. * A light query planner for Firestore.
  10912. *
  10913. * This class matches a `FieldIndex` against a Firestore Query `Target`. It
  10914. * determines whether a given index can be used to serve the specified target.
  10915. *
  10916. * The following table showcases some possible index configurations:
  10917. *
  10918. * Query | Index
  10919. * -----------------------------------------------------------------------------
  10920. * where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
  10921. * where('a', '==', 'a').where('b', '==', 'b') | a ASC
  10922. * where('a', '==', 'a').where('b', '==', 'b') | b DESC
  10923. * where('a', '>=', 'a').orderBy('a') | a ASC
  10924. * where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
  10925. * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
  10926. * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
  10927. * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
  10928. * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
  10929. */
  10930. class TargetIndexMatcher {
  10931. constructor(target) {
  10932. this.collectionId =
  10933. target.collectionGroup != null
  10934. ? target.collectionGroup
  10935. : target.path.lastSegment();
  10936. this.orderBys = target.orderBy;
  10937. this.equalityFilters = [];
  10938. for (const filter of target.filters) {
  10939. const fieldFilter = filter;
  10940. if (fieldFilter.isInequality()) {
  10941. this.inequalityFilter = fieldFilter;
  10942. }
  10943. else {
  10944. this.equalityFilters.push(fieldFilter);
  10945. }
  10946. }
  10947. }
  10948. /**
  10949. * Returns whether the index can be used to serve the TargetIndexMatcher's
  10950. * target.
  10951. *
  10952. * An index is considered capable of serving the target when:
  10953. * - The target uses all index segments for its filters and orderBy clauses.
  10954. * The target can have additional filter and orderBy clauses, but not
  10955. * fewer.
  10956. * - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
  10957. * have a corresponding `CONTAINS` segment.
  10958. * - All directional index segments can be mapped to the target as a series of
  10959. * equality filters, a single inequality filter and a series of orderBy
  10960. * clauses.
  10961. * - The segments that represent the equality filters may appear out of order.
  10962. * - The optional segment for the inequality filter must appear after all
  10963. * equality segments.
  10964. * - The segments that represent that orderBy clause of the target must appear
  10965. * in order after all equality and inequality segments. Single orderBy
  10966. * clauses cannot be skipped, but a continuous orderBy suffix may be
  10967. * omitted.
  10968. */
  10969. servedByIndex(index) {
  10970. hardAssert(index.collectionGroup === this.collectionId);
  10971. // If there is an array element, find a matching filter.
  10972. const arraySegment = fieldIndexGetArraySegment(index);
  10973. if (arraySegment !== undefined &&
  10974. !this.hasMatchingEqualityFilter(arraySegment)) {
  10975. return false;
  10976. }
  10977. const segments = fieldIndexGetDirectionalSegments(index);
  10978. let equalitySegments = new Set();
  10979. let segmentIndex = 0;
  10980. let orderBysIndex = 0;
  10981. // Process all equalities first. Equalities can appear out of order.
  10982. for (; segmentIndex < segments.length; ++segmentIndex) {
  10983. // We attempt to greedily match all segments to equality filters. If a
  10984. // filter matches an index segment, we can mark the segment as used.
  10985. if (this.hasMatchingEqualityFilter(segments[segmentIndex])) {
  10986. equalitySegments = equalitySegments.add(segments[segmentIndex].fieldPath.canonicalString());
  10987. }
  10988. else {
  10989. // If we cannot find a matching filter, we need to verify whether the
  10990. // remaining segments map to the target's inequality and its orderBy
  10991. // clauses.
  10992. break;
  10993. }
  10994. }
  10995. // If we already have processed all segments, all segments are used to serve
  10996. // the equality filters and we do not need to map any segments to the
  10997. // target's inequality and orderBy clauses.
  10998. if (segmentIndex === segments.length) {
  10999. return true;
  11000. }
  11001. if (this.inequalityFilter !== undefined) {
  11002. // If there is an inequality filter and the field was not in one of the
  11003. // equality filters above, the next segment must match both the filter
  11004. // and the first orderBy clause.
  11005. if (!equalitySegments.has(this.inequalityFilter.field.canonicalString())) {
  11006. const segment = segments[segmentIndex];
  11007. if (!this.matchesFilter(this.inequalityFilter, segment) ||
  11008. !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
  11009. return false;
  11010. }
  11011. }
  11012. ++segmentIndex;
  11013. }
  11014. // All remaining segments need to represent the prefix of the target's
  11015. // orderBy.
  11016. for (; segmentIndex < segments.length; ++segmentIndex) {
  11017. const segment = segments[segmentIndex];
  11018. if (orderBysIndex >= this.orderBys.length ||
  11019. !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
  11020. return false;
  11021. }
  11022. }
  11023. return true;
  11024. }
  11025. hasMatchingEqualityFilter(segment) {
  11026. for (const filter of this.equalityFilters) {
  11027. if (this.matchesFilter(filter, segment)) {
  11028. return true;
  11029. }
  11030. }
  11031. return false;
  11032. }
  11033. matchesFilter(filter, segment) {
  11034. if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
  11035. return false;
  11036. }
  11037. const isArrayOperator = filter.op === "array-contains" /* Operator.ARRAY_CONTAINS */ ||
  11038. filter.op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */;
  11039. return (segment.kind === 2 /* IndexKind.CONTAINS */) === isArrayOperator;
  11040. }
  11041. matchesOrderBy(orderBy, segment) {
  11042. if (!orderBy.field.isEqual(segment.fieldPath)) {
  11043. return false;
  11044. }
  11045. return ((segment.kind === 0 /* IndexKind.ASCENDING */ &&
  11046. orderBy.dir === "asc" /* Direction.ASCENDING */) ||
  11047. (segment.kind === 1 /* IndexKind.DESCENDING */ &&
  11048. orderBy.dir === "desc" /* Direction.DESCENDING */));
  11049. }
  11050. }
  11051. /**
  11052. * @license
  11053. * Copyright 2022 Google LLC
  11054. *
  11055. * Licensed under the Apache License, Version 2.0 (the "License");
  11056. * you may not use this file except in compliance with the License.
  11057. * You may obtain a copy of the License at
  11058. *
  11059. * http://www.apache.org/licenses/LICENSE-2.0
  11060. *
  11061. * Unless required by applicable law or agreed to in writing, software
  11062. * distributed under the License is distributed on an "AS IS" BASIS,
  11063. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11064. * See the License for the specific language governing permissions and
  11065. * limitations under the License.
  11066. */
  11067. /**
  11068. * Provides utility functions that help with boolean logic transformations needed for handling
  11069. * complex filters used in queries.
  11070. */
  11071. /**
  11072. * The `in` filter is only a syntactic sugar over a disjunction of equalities. For instance: `a in
  11073. * [1,2,3]` is in fact `a==1 || a==2 || a==3`. This method expands any `in` filter in the given
  11074. * input into a disjunction of equality filters and returns the expanded filter.
  11075. */
  11076. function computeInExpansion(filter) {
  11077. var _a, _b;
  11078. hardAssert(filter instanceof FieldFilter || filter instanceof CompositeFilter);
  11079. if (filter instanceof FieldFilter) {
  11080. if (filter instanceof InFilter) {
  11081. 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))) || [];
  11082. return CompositeFilter.create(expandedFilters, "or" /* CompositeOperator.OR */);
  11083. }
  11084. else {
  11085. // We have reached other kinds of field filters.
  11086. return filter;
  11087. }
  11088. }
  11089. // We have a composite filter.
  11090. const expandedFilters = filter.filters.map(subfilter => computeInExpansion(subfilter));
  11091. return CompositeFilter.create(expandedFilters, filter.op);
  11092. }
  11093. /**
  11094. * Given a composite filter, returns the list of terms in its disjunctive normal form.
  11095. *
  11096. * <p>Each element in the return value is one term of the resulting DNF. For instance: For the
  11097. * input: (A || B) && C, the DNF form is: (A && C) || (B && C), and the return value is a list
  11098. * with two elements: a composite filter that performs (A && C), and a composite filter that
  11099. * performs (B && C).
  11100. *
  11101. * @param filter the composite filter to calculate DNF transform for.
  11102. * @return the terms in the DNF transform.
  11103. */
  11104. function getDnfTerms(filter) {
  11105. if (filter.getFilters().length === 0) {
  11106. return [];
  11107. }
  11108. const result = computeDistributedNormalForm(computeInExpansion(filter));
  11109. hardAssert(isDisjunctiveNormalForm(result));
  11110. if (isSingleFieldFilter(result) || isFlatConjunction(result)) {
  11111. return [result];
  11112. }
  11113. return result.getFilters();
  11114. }
  11115. /** Returns true if the given filter is a single field filter. e.g. (a == 10). */
  11116. function isSingleFieldFilter(filter) {
  11117. return filter instanceof FieldFilter;
  11118. }
  11119. /**
  11120. * Returns true if the given filter is the conjunction of one or more field filters. e.g. (a == 10
  11121. * && b == 20)
  11122. */
  11123. function isFlatConjunction(filter) {
  11124. return (filter instanceof CompositeFilter &&
  11125. compositeFilterIsFlatConjunction(filter));
  11126. }
  11127. /**
  11128. * Returns whether or not the given filter is in disjunctive normal form (DNF).
  11129. *
  11130. * <p>In boolean logic, a disjunctive normal form (DNF) is a canonical normal form of a logical
  11131. * formula consisting of a disjunction of conjunctions; it can also be described as an OR of ANDs.
  11132. *
  11133. * <p>For more info, visit: https://en.wikipedia.org/wiki/Disjunctive_normal_form
  11134. */
  11135. function isDisjunctiveNormalForm(filter) {
  11136. return (isSingleFieldFilter(filter) ||
  11137. isFlatConjunction(filter) ||
  11138. isDisjunctionOfFieldFiltersAndFlatConjunctions(filter));
  11139. }
  11140. /**
  11141. * Returns true if the given filter is the disjunction of one or more "flat conjunctions" and
  11142. * field filters. e.g. (a == 10) || (b==20 && c==30)
  11143. */
  11144. function isDisjunctionOfFieldFiltersAndFlatConjunctions(filter) {
  11145. if (filter instanceof CompositeFilter) {
  11146. if (compositeFilterIsDisjunction(filter)) {
  11147. for (const subFilter of filter.getFilters()) {
  11148. if (!isSingleFieldFilter(subFilter) && !isFlatConjunction(subFilter)) {
  11149. return false;
  11150. }
  11151. }
  11152. return true;
  11153. }
  11154. }
  11155. return false;
  11156. }
  11157. function computeDistributedNormalForm(filter) {
  11158. hardAssert(filter instanceof FieldFilter || filter instanceof CompositeFilter);
  11159. if (filter instanceof FieldFilter) {
  11160. return filter;
  11161. }
  11162. if (filter.filters.length === 1) {
  11163. return computeDistributedNormalForm(filter.filters[0]);
  11164. }
  11165. // Compute DNF for each of the subfilters first
  11166. const result = filter.filters.map(subfilter => computeDistributedNormalForm(subfilter));
  11167. let newFilter = CompositeFilter.create(result, filter.op);
  11168. newFilter = applyAssociation(newFilter);
  11169. if (isDisjunctiveNormalForm(newFilter)) {
  11170. return newFilter;
  11171. }
  11172. hardAssert(newFilter instanceof CompositeFilter);
  11173. hardAssert(compositeFilterIsConjunction(newFilter));
  11174. hardAssert(newFilter.filters.length > 1);
  11175. return newFilter.filters.reduce((runningResult, filter) => applyDistribution(runningResult, filter));
  11176. }
  11177. function applyDistribution(lhs, rhs) {
  11178. hardAssert(lhs instanceof FieldFilter || lhs instanceof CompositeFilter);
  11179. hardAssert(rhs instanceof FieldFilter || rhs instanceof CompositeFilter);
  11180. let result;
  11181. if (lhs instanceof FieldFilter) {
  11182. if (rhs instanceof FieldFilter) {
  11183. // FieldFilter FieldFilter
  11184. result = applyDistributionFieldFilters(lhs, rhs);
  11185. }
  11186. else {
  11187. // FieldFilter CompositeFilter
  11188. result = applyDistributionFieldAndCompositeFilters(lhs, rhs);
  11189. }
  11190. }
  11191. else {
  11192. if (rhs instanceof FieldFilter) {
  11193. // CompositeFilter FieldFilter
  11194. result = applyDistributionFieldAndCompositeFilters(rhs, lhs);
  11195. }
  11196. else {
  11197. // CompositeFilter CompositeFilter
  11198. result = applyDistributionCompositeFilters(lhs, rhs);
  11199. }
  11200. }
  11201. return applyAssociation(result);
  11202. }
  11203. function applyDistributionFieldFilters(lhs, rhs) {
  11204. // Conjunction distribution for two field filters is the conjunction of them.
  11205. return CompositeFilter.create([lhs, rhs], "and" /* CompositeOperator.AND */);
  11206. }
  11207. function applyDistributionCompositeFilters(lhs, rhs) {
  11208. hardAssert(lhs.filters.length > 0 && rhs.filters.length > 0);
  11209. // There are four cases:
  11210. // (A & B) & (C & D) --> (A & B & C & D)
  11211. // (A & B) & (C | D) --> (A & B & C) | (A & B & D)
  11212. // (A | B) & (C & D) --> (C & D & A) | (C & D & B)
  11213. // (A | B) & (C | D) --> (A & C) | (A & D) | (B & C) | (B & D)
  11214. // Case 1 is a merge.
  11215. if (compositeFilterIsConjunction(lhs) && compositeFilterIsConjunction(rhs)) {
  11216. return compositeFilterWithAddedFilters(lhs, rhs.getFilters());
  11217. }
  11218. // Case 2,3,4 all have at least one side (lhs or rhs) that is a disjunction. In all three cases
  11219. // we should take each element of the disjunction and distribute it over the other side, and
  11220. // return the disjunction of the distribution results.
  11221. const disjunctionSide = compositeFilterIsDisjunction(lhs) ? lhs : rhs;
  11222. const otherSide = compositeFilterIsDisjunction(lhs) ? rhs : lhs;
  11223. const results = disjunctionSide.filters.map(subfilter => applyDistribution(subfilter, otherSide));
  11224. return CompositeFilter.create(results, "or" /* CompositeOperator.OR */);
  11225. }
  11226. function applyDistributionFieldAndCompositeFilters(fieldFilter, compositeFilter) {
  11227. // There are two cases:
  11228. // A & (B & C) --> (A & B & C)
  11229. // A & (B | C) --> (A & B) | (A & C)
  11230. if (compositeFilterIsConjunction(compositeFilter)) {
  11231. // Case 1
  11232. return compositeFilterWithAddedFilters(compositeFilter, fieldFilter.getFilters());
  11233. }
  11234. else {
  11235. // Case 2
  11236. const newFilters = compositeFilter.filters.map(subfilter => applyDistribution(fieldFilter, subfilter));
  11237. return CompositeFilter.create(newFilters, "or" /* CompositeOperator.OR */);
  11238. }
  11239. }
  11240. /**
  11241. * Applies the associativity property to the given filter and returns the resulting filter.
  11242. *
  11243. * <ul>
  11244. * <li>A | (B | C) == (A | B) | C == (A | B | C)
  11245. * <li>A & (B & C) == (A & B) & C == (A & B & C)
  11246. * </ul>
  11247. *
  11248. * <p>For more info, visit: https://en.wikipedia.org/wiki/Associative_property#Propositional_logic
  11249. */
  11250. function applyAssociation(filter) {
  11251. hardAssert(filter instanceof FieldFilter || filter instanceof CompositeFilter);
  11252. if (filter instanceof FieldFilter) {
  11253. return filter;
  11254. }
  11255. const filters = filter.getFilters();
  11256. // If the composite filter only contains 1 filter, apply associativity to it.
  11257. if (filters.length === 1) {
  11258. return applyAssociation(filters[0]);
  11259. }
  11260. // Associativity applied to a flat composite filter results is itself.
  11261. if (compositeFilterIsFlat(filter)) {
  11262. return filter;
  11263. }
  11264. // First apply associativity to all subfilters. This will in turn recursively apply
  11265. // associativity to all nested composite filters and field filters.
  11266. const updatedFilters = filters.map(subfilter => applyAssociation(subfilter));
  11267. // For composite subfilters that perform the same kind of logical operation as `compositeFilter`
  11268. // take out their filters and add them to `compositeFilter`. For example:
  11269. // compositeFilter = (A | (B | C | D))
  11270. // compositeSubfilter = (B | C | D)
  11271. // Result: (A | B | C | D)
  11272. // Note that the `compositeSubfilter` has been eliminated, and its filters (B, C, D) have been
  11273. // added to the top-level "compositeFilter".
  11274. const newSubfilters = [];
  11275. updatedFilters.forEach(subfilter => {
  11276. if (subfilter instanceof FieldFilter) {
  11277. newSubfilters.push(subfilter);
  11278. }
  11279. else if (subfilter instanceof CompositeFilter) {
  11280. if (subfilter.op === filter.op) {
  11281. // compositeFilter: (A | (B | C))
  11282. // compositeSubfilter: (B | C)
  11283. // Result: (A | B | C)
  11284. newSubfilters.push(...subfilter.filters);
  11285. }
  11286. else {
  11287. // compositeFilter: (A | (B & C))
  11288. // compositeSubfilter: (B & C)
  11289. // Result: (A | (B & C))
  11290. newSubfilters.push(subfilter);
  11291. }
  11292. }
  11293. });
  11294. if (newSubfilters.length === 1) {
  11295. return newSubfilters[0];
  11296. }
  11297. return CompositeFilter.create(newSubfilters, filter.op);
  11298. }
  11299. /**
  11300. * @license
  11301. * Copyright 2019 Google LLC
  11302. *
  11303. * Licensed under the Apache License, Version 2.0 (the "License");
  11304. * you may not use this file except in compliance with the License.
  11305. * You may obtain a copy of the License at
  11306. *
  11307. * http://www.apache.org/licenses/LICENSE-2.0
  11308. *
  11309. * Unless required by applicable law or agreed to in writing, software
  11310. * distributed under the License is distributed on an "AS IS" BASIS,
  11311. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11312. * See the License for the specific language governing permissions and
  11313. * limitations under the License.
  11314. */
  11315. /**
  11316. * An in-memory implementation of IndexManager.
  11317. */
  11318. class MemoryIndexManager {
  11319. constructor() {
  11320. this.collectionParentIndex = new MemoryCollectionParentIndex();
  11321. }
  11322. addToCollectionParentIndex(transaction, collectionPath) {
  11323. this.collectionParentIndex.add(collectionPath);
  11324. return PersistencePromise.resolve();
  11325. }
  11326. getCollectionParents(transaction, collectionId) {
  11327. return PersistencePromise.resolve(this.collectionParentIndex.getEntries(collectionId));
  11328. }
  11329. addFieldIndex(transaction, index) {
  11330. // Field indices are not supported with memory persistence.
  11331. return PersistencePromise.resolve();
  11332. }
  11333. deleteFieldIndex(transaction, index) {
  11334. // Field indices are not supported with memory persistence.
  11335. return PersistencePromise.resolve();
  11336. }
  11337. getDocumentsMatchingTarget(transaction, target) {
  11338. // Field indices are not supported with memory persistence.
  11339. return PersistencePromise.resolve(null);
  11340. }
  11341. getIndexType(transaction, target) {
  11342. // Field indices are not supported with memory persistence.
  11343. return PersistencePromise.resolve(0 /* IndexType.NONE */);
  11344. }
  11345. getFieldIndexes(transaction, collectionGroup) {
  11346. // Field indices are not supported with memory persistence.
  11347. return PersistencePromise.resolve([]);
  11348. }
  11349. getNextCollectionGroupToUpdate(transaction) {
  11350. // Field indices are not supported with memory persistence.
  11351. return PersistencePromise.resolve(null);
  11352. }
  11353. getMinOffset(transaction, target) {
  11354. return PersistencePromise.resolve(IndexOffset.min());
  11355. }
  11356. getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
  11357. return PersistencePromise.resolve(IndexOffset.min());
  11358. }
  11359. updateCollectionGroup(transaction, collectionGroup, offset) {
  11360. // Field indices are not supported with memory persistence.
  11361. return PersistencePromise.resolve();
  11362. }
  11363. updateIndexEntries(transaction, documents) {
  11364. // Field indices are not supported with memory persistence.
  11365. return PersistencePromise.resolve();
  11366. }
  11367. }
  11368. /**
  11369. * Internal implementation of the collection-parent index exposed by MemoryIndexManager.
  11370. * Also used for in-memory caching by IndexedDbIndexManager and initial index population
  11371. * in indexeddb_schema.ts
  11372. */
  11373. class MemoryCollectionParentIndex {
  11374. constructor() {
  11375. this.index = {};
  11376. }
  11377. // Returns false if the entry already existed.
  11378. add(collectionPath) {
  11379. const collectionId = collectionPath.lastSegment();
  11380. const parentPath = collectionPath.popLast();
  11381. const existingParents = this.index[collectionId] ||
  11382. new SortedSet(ResourcePath.comparator);
  11383. const added = !existingParents.has(parentPath);
  11384. this.index[collectionId] = existingParents.add(parentPath);
  11385. return added;
  11386. }
  11387. has(collectionPath) {
  11388. const collectionId = collectionPath.lastSegment();
  11389. const parentPath = collectionPath.popLast();
  11390. const existingParents = this.index[collectionId];
  11391. return existingParents && existingParents.has(parentPath);
  11392. }
  11393. getEntries(collectionId) {
  11394. const parentPaths = this.index[collectionId] ||
  11395. new SortedSet(ResourcePath.comparator);
  11396. return parentPaths.toArray();
  11397. }
  11398. }
  11399. /**
  11400. * @license
  11401. * Copyright 2019 Google LLC
  11402. *
  11403. * Licensed under the Apache License, Version 2.0 (the "License");
  11404. * you may not use this file except in compliance with the License.
  11405. * You may obtain a copy of the License at
  11406. *
  11407. * http://www.apache.org/licenses/LICENSE-2.0
  11408. *
  11409. * Unless required by applicable law or agreed to in writing, software
  11410. * distributed under the License is distributed on an "AS IS" BASIS,
  11411. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11412. * See the License for the specific language governing permissions and
  11413. * limitations under the License.
  11414. */
  11415. const LOG_TAG$f = 'IndexedDbIndexManager';
  11416. const EMPTY_VALUE = new Uint8Array(0);
  11417. /**
  11418. * A persisted implementation of IndexManager.
  11419. *
  11420. * PORTING NOTE: Unlike iOS and Android, the Web SDK does not memoize index
  11421. * data as it supports multi-tab access.
  11422. */
  11423. class IndexedDbIndexManager {
  11424. constructor(user, databaseId) {
  11425. this.user = user;
  11426. this.databaseId = databaseId;
  11427. /**
  11428. * An in-memory copy of the index entries we've already written since the SDK
  11429. * launched. Used to avoid re-writing the same entry repeatedly.
  11430. *
  11431. * This is *NOT* a complete cache of what's in persistence and so can never be
  11432. * used to satisfy reads.
  11433. */
  11434. this.collectionParentsCache = new MemoryCollectionParentIndex();
  11435. /**
  11436. * Maps from a target to its equivalent list of sub-targets. Each sub-target
  11437. * contains only one term from the target's disjunctive normal form (DNF).
  11438. */
  11439. this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
  11440. this.uid = user.uid || '';
  11441. }
  11442. /**
  11443. * Adds a new entry to the collection parent index.
  11444. *
  11445. * Repeated calls for the same collectionPath should be avoided within a
  11446. * transaction as IndexedDbIndexManager only caches writes once a transaction
  11447. * has been committed.
  11448. */
  11449. addToCollectionParentIndex(transaction, collectionPath) {
  11450. if (!this.collectionParentsCache.has(collectionPath)) {
  11451. const collectionId = collectionPath.lastSegment();
  11452. const parentPath = collectionPath.popLast();
  11453. transaction.addOnCommittedListener(() => {
  11454. // Add the collection to the in memory cache only if the transaction was
  11455. // successfully committed.
  11456. this.collectionParentsCache.add(collectionPath);
  11457. });
  11458. const collectionParent = {
  11459. collectionId,
  11460. parent: encodeResourcePath(parentPath)
  11461. };
  11462. return collectionParentsStore(transaction).put(collectionParent);
  11463. }
  11464. return PersistencePromise.resolve();
  11465. }
  11466. getCollectionParents(transaction, collectionId) {
  11467. const parentPaths = [];
  11468. const range = IDBKeyRange.bound([collectionId, ''], [immediateSuccessor(collectionId), ''],
  11469. /*lowerOpen=*/ false,
  11470. /*upperOpen=*/ true);
  11471. return collectionParentsStore(transaction)
  11472. .loadAll(range)
  11473. .next(entries => {
  11474. for (const entry of entries) {
  11475. // This collectionId guard shouldn't be necessary (and isn't as long
  11476. // as we're running in a real browser), but there's a bug in
  11477. // indexeddbshim that breaks our range in our tests running in node:
  11478. // https://github.com/axemclion/IndexedDBShim/issues/334
  11479. if (entry.collectionId !== collectionId) {
  11480. break;
  11481. }
  11482. parentPaths.push(decodeResourcePath(entry.parent));
  11483. }
  11484. return parentPaths;
  11485. });
  11486. }
  11487. addFieldIndex(transaction, index) {
  11488. // TODO(indexing): Verify that the auto-incrementing index ID works in
  11489. // Safari & Firefox.
  11490. const indexes = indexConfigurationStore(transaction);
  11491. const dbIndex = toDbIndexConfiguration(index);
  11492. delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
  11493. const result = indexes.add(dbIndex);
  11494. if (index.indexState) {
  11495. const states = indexStateStore(transaction);
  11496. return result.next(indexId => {
  11497. states.put(toDbIndexState(indexId, this.user, index.indexState.sequenceNumber, index.indexState.offset));
  11498. });
  11499. }
  11500. else {
  11501. return result.next();
  11502. }
  11503. }
  11504. deleteFieldIndex(transaction, index) {
  11505. const indexes = indexConfigurationStore(transaction);
  11506. const states = indexStateStore(transaction);
  11507. const entries = indexEntriesStore(transaction);
  11508. return indexes
  11509. .delete(index.indexId)
  11510. .next(() => states.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
  11511. /*lowerOpen=*/ false,
  11512. /*upperOpen=*/ true)))
  11513. .next(() => entries.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
  11514. /*lowerOpen=*/ false,
  11515. /*upperOpen=*/ true)));
  11516. }
  11517. getDocumentsMatchingTarget(transaction, target) {
  11518. const indexEntries = indexEntriesStore(transaction);
  11519. let canServeTarget = true;
  11520. const indexes = new Map();
  11521. return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
  11522. return this.getFieldIndex(transaction, subTarget).next(index => {
  11523. canServeTarget && (canServeTarget = !!index);
  11524. indexes.set(subTarget, index);
  11525. });
  11526. }).next(() => {
  11527. if (!canServeTarget) {
  11528. return PersistencePromise.resolve(null);
  11529. }
  11530. else {
  11531. let existingKeys = documentKeySet();
  11532. const result = [];
  11533. return PersistencePromise.forEach(indexes, (index, subTarget) => {
  11534. logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
  11535. const arrayValues = targetGetArrayValues(subTarget, index);
  11536. const notInValues = targetGetNotInValues(subTarget, index);
  11537. const lowerBound = targetGetLowerBound(subTarget, index);
  11538. const upperBound = targetGetUpperBound(subTarget, index);
  11539. const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
  11540. const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
  11541. const notInEncoded = this.encodeValues(index, subTarget, notInValues);
  11542. const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, lowerBound.inclusive, upperBoundEncoded, upperBound.inclusive, notInEncoded);
  11543. return PersistencePromise.forEach(indexRanges, (indexRange) => {
  11544. return indexEntries
  11545. .loadFirst(indexRange, target.limit)
  11546. .next(entries => {
  11547. entries.forEach(entry => {
  11548. const documentKey = DocumentKey.fromSegments(entry.documentKey);
  11549. if (!existingKeys.has(documentKey)) {
  11550. existingKeys = existingKeys.add(documentKey);
  11551. result.push(documentKey);
  11552. }
  11553. });
  11554. });
  11555. });
  11556. }).next(() => result);
  11557. }
  11558. });
  11559. }
  11560. getSubTargets(target) {
  11561. let subTargets = this.targetToDnfSubTargets.get(target);
  11562. if (subTargets) {
  11563. return subTargets;
  11564. }
  11565. if (target.filters.length === 0) {
  11566. subTargets = [target];
  11567. }
  11568. else {
  11569. // There is an implicit AND operation between all the filters stored in the target
  11570. const dnf = getDnfTerms(CompositeFilter.create(target.filters, "and" /* CompositeOperator.AND */));
  11571. subTargets = dnf.map(term => newTarget(target.path, target.collectionGroup, target.orderBy, term.getFilters(), target.limit, target.startAt, target.endAt));
  11572. }
  11573. this.targetToDnfSubTargets.set(target, subTargets);
  11574. return subTargets;
  11575. }
  11576. /**
  11577. * Constructs a key range query on `DbIndexEntryStore` that unions all
  11578. * bounds.
  11579. */
  11580. generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
  11581. // The number of total index scans we union together. This is similar to a
  11582. // distributed normal form, but adapted for array values. We create a single
  11583. // index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
  11584. // combined with the values from the query bounds.
  11585. const totalScans = (arrayValues != null ? arrayValues.length : 1) *
  11586. Math.max(lowerBounds.length, upperBounds.length);
  11587. const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
  11588. const indexRanges = [];
  11589. for (let i = 0; i < totalScans; ++i) {
  11590. const arrayValue = arrayValues
  11591. ? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
  11592. : EMPTY_VALUE;
  11593. const lowerBound = this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive);
  11594. const upperBound = this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive);
  11595. const notInBound = notInValues.map(notIn => this.generateLowerBound(indexId, arrayValue, notIn,
  11596. /* inclusive= */ true));
  11597. indexRanges.push(...this.createRange(lowerBound, upperBound, notInBound));
  11598. }
  11599. return indexRanges;
  11600. }
  11601. /** Generates the lower bound for `arrayValue` and `directionalValue`. */
  11602. generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
  11603. const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
  11604. return inclusive ? entry : entry.successor();
  11605. }
  11606. /** Generates the upper bound for `arrayValue` and `directionalValue`. */
  11607. generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
  11608. const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
  11609. return inclusive ? entry.successor() : entry;
  11610. }
  11611. getFieldIndex(transaction, target) {
  11612. const targetIndexMatcher = new TargetIndexMatcher(target);
  11613. const collectionGroup = target.collectionGroup != null
  11614. ? target.collectionGroup
  11615. : target.path.lastSegment();
  11616. return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
  11617. // Return the index with the most number of segments.
  11618. let index = null;
  11619. for (const candidate of indexes) {
  11620. const matches = targetIndexMatcher.servedByIndex(candidate);
  11621. if (matches &&
  11622. (!index || candidate.fields.length > index.fields.length)) {
  11623. index = candidate;
  11624. }
  11625. }
  11626. return index;
  11627. });
  11628. }
  11629. getIndexType(transaction, target) {
  11630. let indexType = 2 /* IndexType.FULL */;
  11631. const subTargets = this.getSubTargets(target);
  11632. return PersistencePromise.forEach(subTargets, (target) => {
  11633. return this.getFieldIndex(transaction, target).next(index => {
  11634. if (!index) {
  11635. indexType = 0 /* IndexType.NONE */;
  11636. }
  11637. else if (indexType !== 0 /* IndexType.NONE */ &&
  11638. index.fields.length < targetGetSegmentCount(target)) {
  11639. indexType = 1 /* IndexType.PARTIAL */;
  11640. }
  11641. });
  11642. }).next(() => {
  11643. // OR queries have more than one sub-target (one sub-target per DNF term). We currently consider
  11644. // OR queries that have a `limit` to have a partial index. For such queries we perform sorting
  11645. // and apply the limit in memory as a post-processing step.
  11646. if (targetHasLimit(target) &&
  11647. subTargets.length > 1 &&
  11648. indexType === 2 /* IndexType.FULL */) {
  11649. return 1 /* IndexType.PARTIAL */;
  11650. }
  11651. return indexType;
  11652. });
  11653. }
  11654. /**
  11655. * Returns the byte encoded form of the directional values in the field index.
  11656. * Returns `null` if the document does not have all fields specified in the
  11657. * index.
  11658. */
  11659. encodeDirectionalElements(fieldIndex, document) {
  11660. const encoder = new IndexByteEncoder();
  11661. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  11662. const field = document.data.field(segment.fieldPath);
  11663. if (field == null) {
  11664. return null;
  11665. }
  11666. const directionalEncoder = encoder.forKind(segment.kind);
  11667. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
  11668. }
  11669. return encoder.encodedBytes();
  11670. }
  11671. /** Encodes a single value to the ascending index format. */
  11672. encodeSingleElement(value) {
  11673. const encoder = new IndexByteEncoder();
  11674. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* IndexKind.ASCENDING */));
  11675. return encoder.encodedBytes();
  11676. }
  11677. /**
  11678. * Returns an encoded form of the document key that sorts based on the key
  11679. * ordering of the field index.
  11680. */
  11681. encodeDirectionalKey(fieldIndex, documentKey) {
  11682. const encoder = new IndexByteEncoder();
  11683. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(refValue(this.databaseId, documentKey), encoder.forKind(fieldIndexGetKeyOrder(fieldIndex)));
  11684. return encoder.encodedBytes();
  11685. }
  11686. /**
  11687. * Encodes the given field values according to the specification in `target`.
  11688. * For IN queries, a list of possible values is returned.
  11689. */
  11690. encodeValues(fieldIndex, target, values) {
  11691. if (values === null) {
  11692. return [];
  11693. }
  11694. let encoders = [];
  11695. encoders.push(new IndexByteEncoder());
  11696. let valueIdx = 0;
  11697. for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
  11698. const value = values[valueIdx++];
  11699. for (const encoder of encoders) {
  11700. if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
  11701. encoders = this.expandIndexValues(encoders, segment, value);
  11702. }
  11703. else {
  11704. const directionalEncoder = encoder.forKind(segment.kind);
  11705. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
  11706. }
  11707. }
  11708. }
  11709. return this.getEncodedBytes(encoders);
  11710. }
  11711. /**
  11712. * Encodes the given bounds according to the specification in `target`. For IN
  11713. * queries, a list of possible values is returned.
  11714. */
  11715. encodeBound(fieldIndex, target, bound) {
  11716. return this.encodeValues(fieldIndex, target, bound.position);
  11717. }
  11718. /** Returns the byte representation for the provided encoders. */
  11719. getEncodedBytes(encoders) {
  11720. const result = [];
  11721. for (let i = 0; i < encoders.length; ++i) {
  11722. result[i] = encoders[i].encodedBytes();
  11723. }
  11724. return result;
  11725. }
  11726. /**
  11727. * Creates a separate encoder for each element of an array.
  11728. *
  11729. * The method appends each value to all existing encoders (e.g. filter("a",
  11730. * "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
  11731. * list of new encoders is returned.
  11732. */
  11733. expandIndexValues(encoders, segment, value) {
  11734. const prefixes = [...encoders];
  11735. const results = [];
  11736. for (const arrayElement of value.arrayValue.values || []) {
  11737. for (const prefix of prefixes) {
  11738. const clonedEncoder = new IndexByteEncoder();
  11739. clonedEncoder.seed(prefix.encodedBytes());
  11740. FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
  11741. results.push(clonedEncoder);
  11742. }
  11743. }
  11744. return results;
  11745. }
  11746. isInFilter(target, fieldPath) {
  11747. return !!target.filters.find(f => f instanceof FieldFilter &&
  11748. f.field.isEqual(fieldPath) &&
  11749. (f.op === "in" /* Operator.IN */ || f.op === "not-in" /* Operator.NOT_IN */));
  11750. }
  11751. getFieldIndexes(transaction, collectionGroup) {
  11752. const indexes = indexConfigurationStore(transaction);
  11753. const states = indexStateStore(transaction);
  11754. return (collectionGroup
  11755. ? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
  11756. : indexes.loadAll()).next(indexConfigs => {
  11757. const result = [];
  11758. return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
  11759. return states
  11760. .get([indexConfig.indexId, this.uid])
  11761. .next(indexState => {
  11762. result.push(fromDbIndexConfiguration(indexConfig, indexState));
  11763. });
  11764. }).next(() => result);
  11765. });
  11766. }
  11767. getNextCollectionGroupToUpdate(transaction) {
  11768. return this.getFieldIndexes(transaction).next(indexes => {
  11769. if (indexes.length === 0) {
  11770. return null;
  11771. }
  11772. indexes.sort((l, r) => {
  11773. const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
  11774. return cmp !== 0
  11775. ? cmp
  11776. : primitiveComparator(l.collectionGroup, r.collectionGroup);
  11777. });
  11778. return indexes[0].collectionGroup;
  11779. });
  11780. }
  11781. updateCollectionGroup(transaction, collectionGroup, offset) {
  11782. const indexes = indexConfigurationStore(transaction);
  11783. const states = indexStateStore(transaction);
  11784. return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
  11785. .loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
  11786. .next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
  11787. }
  11788. updateIndexEntries(transaction, documents) {
  11789. // Porting Note: `getFieldIndexes()` on Web does not cache index lookups as
  11790. // it could be used across different IndexedDB transactions. As any cached
  11791. // data might be invalidated by other multi-tab clients, we can only trust
  11792. // data within a single IndexedDB transaction. We therefore add a cache
  11793. // here.
  11794. const memoizedIndexes = new Map();
  11795. return PersistencePromise.forEach(documents, (key, doc) => {
  11796. const memoizedCollectionIndexes = memoizedIndexes.get(key.collectionGroup);
  11797. const fieldIndexes = memoizedCollectionIndexes
  11798. ? PersistencePromise.resolve(memoizedCollectionIndexes)
  11799. : this.getFieldIndexes(transaction, key.collectionGroup);
  11800. return fieldIndexes.next(fieldIndexes => {
  11801. memoizedIndexes.set(key.collectionGroup, fieldIndexes);
  11802. return PersistencePromise.forEach(fieldIndexes, (fieldIndex) => {
  11803. return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
  11804. const newEntries = this.computeIndexEntries(doc, fieldIndex);
  11805. if (!existingEntries.isEqual(newEntries)) {
  11806. return this.updateEntries(transaction, doc, fieldIndex, existingEntries, newEntries);
  11807. }
  11808. return PersistencePromise.resolve();
  11809. });
  11810. });
  11811. });
  11812. });
  11813. }
  11814. addIndexEntry(transaction, document, fieldIndex, indexEntry) {
  11815. const indexEntries = indexEntriesStore(transaction);
  11816. return indexEntries.put({
  11817. indexId: indexEntry.indexId,
  11818. uid: this.uid,
  11819. arrayValue: indexEntry.arrayValue,
  11820. directionalValue: indexEntry.directionalValue,
  11821. orderedDocumentKey: this.encodeDirectionalKey(fieldIndex, document.key),
  11822. documentKey: document.key.path.toArray()
  11823. });
  11824. }
  11825. deleteIndexEntry(transaction, document, fieldIndex, indexEntry) {
  11826. const indexEntries = indexEntriesStore(transaction);
  11827. return indexEntries.delete([
  11828. indexEntry.indexId,
  11829. this.uid,
  11830. indexEntry.arrayValue,
  11831. indexEntry.directionalValue,
  11832. this.encodeDirectionalKey(fieldIndex, document.key),
  11833. document.key.path.toArray()
  11834. ]);
  11835. }
  11836. getExistingIndexEntries(transaction, documentKey, fieldIndex) {
  11837. const indexEntries = indexEntriesStore(transaction);
  11838. let results = new SortedSet(indexEntryComparator);
  11839. return indexEntries
  11840. .iterate({
  11841. index: DbIndexEntryDocumentKeyIndex,
  11842. range: IDBKeyRange.only([
  11843. fieldIndex.indexId,
  11844. this.uid,
  11845. this.encodeDirectionalKey(fieldIndex, documentKey)
  11846. ])
  11847. }, (_, entry) => {
  11848. results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
  11849. })
  11850. .next(() => results);
  11851. }
  11852. /** Creates the index entries for the given document. */
  11853. computeIndexEntries(document, fieldIndex) {
  11854. let results = new SortedSet(indexEntryComparator);
  11855. const directionalValue = this.encodeDirectionalElements(fieldIndex, document);
  11856. if (directionalValue == null) {
  11857. return results;
  11858. }
  11859. const arraySegment = fieldIndexGetArraySegment(fieldIndex);
  11860. if (arraySegment != null) {
  11861. const value = document.data.field(arraySegment.fieldPath);
  11862. if (isArray(value)) {
  11863. for (const arrayValue of value.arrayValue.values || []) {
  11864. results = results.add(new IndexEntry(fieldIndex.indexId, document.key, this.encodeSingleElement(arrayValue), directionalValue));
  11865. }
  11866. }
  11867. }
  11868. else {
  11869. results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
  11870. }
  11871. return results;
  11872. }
  11873. /**
  11874. * Updates the index entries for the provided document by deleting entries
  11875. * that are no longer referenced in `newEntries` and adding all newly added
  11876. * entries.
  11877. */
  11878. updateEntries(transaction, document, fieldIndex, existingEntries, newEntries) {
  11879. logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
  11880. const promises = [];
  11881. diffSortedSets(existingEntries, newEntries, indexEntryComparator,
  11882. /* onAdd= */ entry => {
  11883. promises.push(this.addIndexEntry(transaction, document, fieldIndex, entry));
  11884. },
  11885. /* onRemove= */ entry => {
  11886. promises.push(this.deleteIndexEntry(transaction, document, fieldIndex, entry));
  11887. });
  11888. return PersistencePromise.waitFor(promises);
  11889. }
  11890. getNextSequenceNumber(transaction) {
  11891. let nextSequenceNumber = 1;
  11892. const states = indexStateStore(transaction);
  11893. return states
  11894. .iterate({
  11895. index: DbIndexStateSequenceNumberIndex,
  11896. reverse: true,
  11897. range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
  11898. }, (_, state, controller) => {
  11899. controller.done();
  11900. nextSequenceNumber = state.sequenceNumber + 1;
  11901. })
  11902. .next(() => nextSequenceNumber);
  11903. }
  11904. /**
  11905. * Returns a new set of IDB ranges that splits the existing range and excludes
  11906. * any values that match the `notInValue` from these ranges. As an example,
  11907. * '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
  11908. */
  11909. createRange(lower, upper, notInValues) {
  11910. // The notIn values need to be sorted and unique so that we can return a
  11911. // sorted set of non-overlapping ranges.
  11912. notInValues = notInValues
  11913. .sort((l, r) => indexEntryComparator(l, r))
  11914. .filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
  11915. const bounds = [];
  11916. bounds.push(lower);
  11917. for (const notInValue of notInValues) {
  11918. const cmpToLower = indexEntryComparator(notInValue, lower);
  11919. const cmpToUpper = indexEntryComparator(notInValue, upper);
  11920. if (cmpToLower === 0) {
  11921. // `notInValue` is the lower bound. We therefore need to raise the bound
  11922. // to the next value.
  11923. bounds[0] = lower.successor();
  11924. }
  11925. else if (cmpToLower > 0 && cmpToUpper < 0) {
  11926. // `notInValue` is in the middle of the range
  11927. bounds.push(notInValue);
  11928. bounds.push(notInValue.successor());
  11929. }
  11930. else if (cmpToUpper > 0) {
  11931. // `notInValue` (and all following values) are out of the range
  11932. break;
  11933. }
  11934. }
  11935. bounds.push(upper);
  11936. const ranges = [];
  11937. for (let i = 0; i < bounds.length; i += 2) {
  11938. // If we encounter two bounds that will create an unmatchable key range,
  11939. // then we return an empty set of key ranges.
  11940. if (this.isRangeMatchable(bounds[i], bounds[i + 1])) {
  11941. return [];
  11942. }
  11943. const lowerBound = [
  11944. bounds[i].indexId,
  11945. this.uid,
  11946. bounds[i].arrayValue,
  11947. bounds[i].directionalValue,
  11948. EMPTY_VALUE,
  11949. []
  11950. ];
  11951. const upperBound = [
  11952. bounds[i + 1].indexId,
  11953. this.uid,
  11954. bounds[i + 1].arrayValue,
  11955. bounds[i + 1].directionalValue,
  11956. EMPTY_VALUE,
  11957. []
  11958. ];
  11959. ranges.push(IDBKeyRange.bound(lowerBound, upperBound));
  11960. }
  11961. return ranges;
  11962. }
  11963. isRangeMatchable(lowerBound, upperBound) {
  11964. // If lower bound is greater than the upper bound, then the key
  11965. // range can never be matched.
  11966. return indexEntryComparator(lowerBound, upperBound) > 0;
  11967. }
  11968. getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
  11969. return this.getFieldIndexes(transaction, collectionGroup).next(getMinOffsetFromFieldIndexes);
  11970. }
  11971. getMinOffset(transaction, target) {
  11972. return PersistencePromise.mapArray(this.getSubTargets(target), (subTarget) => this.getFieldIndex(transaction, subTarget).next(index => index ? index : fail())).next(getMinOffsetFromFieldIndexes);
  11973. }
  11974. }
  11975. /**
  11976. * Helper to get a typed SimpleDbStore for the collectionParents
  11977. * document store.
  11978. */
  11979. function collectionParentsStore(txn) {
  11980. return getStore(txn, DbCollectionParentStore);
  11981. }
  11982. /**
  11983. * Helper to get a typed SimpleDbStore for the index entry object store.
  11984. */
  11985. function indexEntriesStore(txn) {
  11986. return getStore(txn, DbIndexEntryStore);
  11987. }
  11988. /**
  11989. * Helper to get a typed SimpleDbStore for the index configuration object store.
  11990. */
  11991. function indexConfigurationStore(txn) {
  11992. return getStore(txn, DbIndexConfigurationStore);
  11993. }
  11994. /**
  11995. * Helper to get a typed SimpleDbStore for the index state object store.
  11996. */
  11997. function indexStateStore(txn) {
  11998. return getStore(txn, DbIndexStateStore);
  11999. }
  12000. function getMinOffsetFromFieldIndexes(fieldIndexes) {
  12001. hardAssert(fieldIndexes.length !== 0);
  12002. let minOffset = fieldIndexes[0].indexState.offset;
  12003. let maxBatchId = minOffset.largestBatchId;
  12004. for (let i = 1; i < fieldIndexes.length; i++) {
  12005. const newOffset = fieldIndexes[i].indexState.offset;
  12006. if (indexOffsetComparator(newOffset, minOffset) < 0) {
  12007. minOffset = newOffset;
  12008. }
  12009. if (maxBatchId < newOffset.largestBatchId) {
  12010. maxBatchId = newOffset.largestBatchId;
  12011. }
  12012. }
  12013. return new IndexOffset(minOffset.readTime, minOffset.documentKey, maxBatchId);
  12014. }
  12015. /**
  12016. * @license
  12017. * Copyright 2020 Google LLC
  12018. *
  12019. * Licensed under the Apache License, Version 2.0 (the "License");
  12020. * you may not use this file except in compliance with the License.
  12021. * You may obtain a copy of the License at
  12022. *
  12023. * http://www.apache.org/licenses/LICENSE-2.0
  12024. *
  12025. * Unless required by applicable law or agreed to in writing, software
  12026. * distributed under the License is distributed on an "AS IS" BASIS,
  12027. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12028. * See the License for the specific language governing permissions and
  12029. * limitations under the License.
  12030. */
  12031. /**
  12032. * Delete a mutation batch and the associated document mutations.
  12033. * @returns A PersistencePromise of the document mutations that were removed.
  12034. */
  12035. function removeMutationBatch(txn, userId, batch) {
  12036. const mutationStore = txn.store(DbMutationBatchStore);
  12037. const indexTxn = txn.store(DbDocumentMutationStore);
  12038. const promises = [];
  12039. const range = IDBKeyRange.only(batch.batchId);
  12040. let numDeleted = 0;
  12041. const removePromise = mutationStore.iterate({ range }, (key, value, control) => {
  12042. numDeleted++;
  12043. return control.delete();
  12044. });
  12045. promises.push(removePromise.next(() => {
  12046. hardAssert(numDeleted === 1);
  12047. }));
  12048. const removedDocuments = [];
  12049. for (const mutation of batch.mutations) {
  12050. const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
  12051. promises.push(indexTxn.delete(indexKey));
  12052. removedDocuments.push(mutation.key);
  12053. }
  12054. return PersistencePromise.waitFor(promises).next(() => removedDocuments);
  12055. }
  12056. /**
  12057. * Returns an approximate size for the given document.
  12058. */
  12059. function dbDocumentSize(doc) {
  12060. if (!doc) {
  12061. return 0;
  12062. }
  12063. let value;
  12064. if (doc.document) {
  12065. value = doc.document;
  12066. }
  12067. else if (doc.unknownDocument) {
  12068. value = doc.unknownDocument;
  12069. }
  12070. else if (doc.noDocument) {
  12071. value = doc.noDocument;
  12072. }
  12073. else {
  12074. throw fail();
  12075. }
  12076. return JSON.stringify(value).length;
  12077. }
  12078. /**
  12079. * @license
  12080. * Copyright 2017 Google LLC
  12081. *
  12082. * Licensed under the Apache License, Version 2.0 (the "License");
  12083. * you may not use this file except in compliance with the License.
  12084. * You may obtain a copy of the License at
  12085. *
  12086. * http://www.apache.org/licenses/LICENSE-2.0
  12087. *
  12088. * Unless required by applicable law or agreed to in writing, software
  12089. * distributed under the License is distributed on an "AS IS" BASIS,
  12090. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12091. * See the License for the specific language governing permissions and
  12092. * limitations under the License.
  12093. */
  12094. /** A mutation queue for a specific user, backed by IndexedDB. */
  12095. class IndexedDbMutationQueue {
  12096. constructor(
  12097. /**
  12098. * The normalized userId (e.g. null UID => "" userId) used to store /
  12099. * retrieve mutations.
  12100. */
  12101. userId, serializer, indexManager, referenceDelegate) {
  12102. this.userId = userId;
  12103. this.serializer = serializer;
  12104. this.indexManager = indexManager;
  12105. this.referenceDelegate = referenceDelegate;
  12106. /**
  12107. * Caches the document keys for pending mutation batches. If the mutation
  12108. * has been removed from IndexedDb, the cached value may continue to
  12109. * be used to retrieve the batch's document keys. To remove a cached value
  12110. * locally, `removeCachedMutationKeys()` should be invoked either directly
  12111. * or through `removeMutationBatches()`.
  12112. *
  12113. * With multi-tab, when the primary client acknowledges or rejects a mutation,
  12114. * this cache is used by secondary clients to invalidate the local
  12115. * view of the documents that were previously affected by the mutation.
  12116. */
  12117. // PORTING NOTE: Multi-tab only.
  12118. this.documentKeysByBatchId = {};
  12119. }
  12120. /**
  12121. * Creates a new mutation queue for the given user.
  12122. * @param user - The user for which to create a mutation queue.
  12123. * @param serializer - The serializer to use when persisting to IndexedDb.
  12124. */
  12125. static forUser(user, serializer, indexManager, referenceDelegate) {
  12126. // TODO(mcg): Figure out what constraints there are on userIDs
  12127. // In particular, are there any reserved characters? are empty ids allowed?
  12128. // For the moment store these together in the same mutations table assuming
  12129. // that empty userIDs aren't allowed.
  12130. hardAssert(user.uid !== '');
  12131. const userId = user.isAuthenticated() ? user.uid : '';
  12132. return new IndexedDbMutationQueue(userId, serializer, indexManager, referenceDelegate);
  12133. }
  12134. checkEmpty(transaction) {
  12135. let empty = true;
  12136. const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
  12137. return mutationsStore(transaction)
  12138. .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
  12139. empty = false;
  12140. control.done();
  12141. })
  12142. .next(() => empty);
  12143. }
  12144. addMutationBatch(transaction, localWriteTime, baseMutations, mutations) {
  12145. const documentStore = documentMutationsStore(transaction);
  12146. const mutationStore = mutationsStore(transaction);
  12147. // The IndexedDb implementation in Chrome (and Firefox) does not handle
  12148. // compound indices that include auto-generated keys correctly. To ensure
  12149. // that the index entry is added correctly in all browsers, we perform two
  12150. // writes: The first write is used to retrieve the next auto-generated Batch
  12151. // ID, and the second write populates the index and stores the actual
  12152. // mutation batch.
  12153. // See: https://bugs.chromium.org/p/chromium/issues/detail?id=701972
  12154. // We write an empty object to obtain key
  12155. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  12156. return mutationStore.add({}).next(batchId => {
  12157. hardAssert(typeof batchId === 'number');
  12158. const batch = new MutationBatch(batchId, localWriteTime, baseMutations, mutations);
  12159. const dbBatch = toDbMutationBatch(this.serializer, this.userId, batch);
  12160. const promises = [];
  12161. let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
  12162. for (const mutation of mutations) {
  12163. const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
  12164. collectionParents = collectionParents.add(mutation.key.path.popLast());
  12165. promises.push(mutationStore.put(dbBatch));
  12166. promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
  12167. }
  12168. collectionParents.forEach(parent => {
  12169. promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
  12170. });
  12171. transaction.addOnCommittedListener(() => {
  12172. this.documentKeysByBatchId[batchId] = batch.keys();
  12173. });
  12174. return PersistencePromise.waitFor(promises).next(() => batch);
  12175. });
  12176. }
  12177. lookupMutationBatch(transaction, batchId) {
  12178. return mutationsStore(transaction)
  12179. .get(batchId)
  12180. .next(dbBatch => {
  12181. if (dbBatch) {
  12182. hardAssert(dbBatch.userId === this.userId);
  12183. return fromDbMutationBatch(this.serializer, dbBatch);
  12184. }
  12185. return null;
  12186. });
  12187. }
  12188. /**
  12189. * Returns the document keys for the mutation batch with the given batchId.
  12190. * For primary clients, this method returns `null` after
  12191. * `removeMutationBatches()` has been called. Secondary clients return a
  12192. * cached result until `removeCachedMutationKeys()` is invoked.
  12193. */
  12194. // PORTING NOTE: Multi-tab only.
  12195. lookupMutationKeys(transaction, batchId) {
  12196. if (this.documentKeysByBatchId[batchId]) {
  12197. return PersistencePromise.resolve(this.documentKeysByBatchId[batchId]);
  12198. }
  12199. else {
  12200. return this.lookupMutationBatch(transaction, batchId).next(batch => {
  12201. if (batch) {
  12202. const keys = batch.keys();
  12203. this.documentKeysByBatchId[batchId] = keys;
  12204. return keys;
  12205. }
  12206. else {
  12207. return null;
  12208. }
  12209. });
  12210. }
  12211. }
  12212. getNextMutationBatchAfterBatchId(transaction, batchId) {
  12213. const nextBatchId = batchId + 1;
  12214. const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
  12215. let foundBatch = null;
  12216. return mutationsStore(transaction)
  12217. .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
  12218. if (dbBatch.userId === this.userId) {
  12219. hardAssert(dbBatch.batchId >= nextBatchId);
  12220. foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
  12221. }
  12222. control.done();
  12223. })
  12224. .next(() => foundBatch);
  12225. }
  12226. getHighestUnacknowledgedBatchId(transaction) {
  12227. const range = IDBKeyRange.upperBound([
  12228. this.userId,
  12229. Number.POSITIVE_INFINITY
  12230. ]);
  12231. let batchId = BATCHID_UNKNOWN;
  12232. return mutationsStore(transaction)
  12233. .iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
  12234. batchId = dbBatch.batchId;
  12235. control.done();
  12236. })
  12237. .next(() => batchId);
  12238. }
  12239. getAllMutationBatches(transaction) {
  12240. const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
  12241. return mutationsStore(transaction)
  12242. .loadAll(DbMutationBatchUserMutationsIndex, range)
  12243. .next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
  12244. }
  12245. getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
  12246. // Scan the document-mutation index starting with a prefix starting with
  12247. // the given documentKey.
  12248. const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
  12249. const indexStart = IDBKeyRange.lowerBound(indexPrefix);
  12250. const results = [];
  12251. return documentMutationsStore(transaction)
  12252. .iterate({ range: indexStart }, (indexKey, _, control) => {
  12253. const [userID, encodedPath, batchId] = indexKey;
  12254. // Only consider rows matching exactly the specific key of
  12255. // interest. Note that because we order by path first, and we
  12256. // order terminators before path separators, we'll encounter all
  12257. // the index rows for documentKey contiguously. In particular, all
  12258. // the rows for documentKey will occur before any rows for
  12259. // documents nested in a subcollection beneath documentKey so we
  12260. // can stop as soon as we hit any such row.
  12261. const path = decodeResourcePath(encodedPath);
  12262. if (userID !== this.userId || !documentKey.path.isEqual(path)) {
  12263. control.done();
  12264. return;
  12265. }
  12266. // Look up the mutation batch in the store.
  12267. return mutationsStore(transaction)
  12268. .get(batchId)
  12269. .next(mutation => {
  12270. if (!mutation) {
  12271. throw fail();
  12272. }
  12273. hardAssert(mutation.userId === this.userId);
  12274. results.push(fromDbMutationBatch(this.serializer, mutation));
  12275. });
  12276. })
  12277. .next(() => results);
  12278. }
  12279. getAllMutationBatchesAffectingDocumentKeys(transaction, documentKeys) {
  12280. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  12281. const promises = [];
  12282. documentKeys.forEach(documentKey => {
  12283. const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
  12284. const range = IDBKeyRange.lowerBound(indexStart);
  12285. const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
  12286. const [userID, encodedPath, batchID] = indexKey;
  12287. // Only consider rows matching exactly the specific key of
  12288. // interest. Note that because we order by path first, and we
  12289. // order terminators before path separators, we'll encounter all
  12290. // the index rows for documentKey contiguously. In particular, all
  12291. // the rows for documentKey will occur before any rows for
  12292. // documents nested in a subcollection beneath documentKey so we
  12293. // can stop as soon as we hit any such row.
  12294. const path = decodeResourcePath(encodedPath);
  12295. if (userID !== this.userId || !documentKey.path.isEqual(path)) {
  12296. control.done();
  12297. return;
  12298. }
  12299. uniqueBatchIDs = uniqueBatchIDs.add(batchID);
  12300. });
  12301. promises.push(promise);
  12302. });
  12303. return PersistencePromise.waitFor(promises).next(() => this.lookupMutationBatches(transaction, uniqueBatchIDs));
  12304. }
  12305. getAllMutationBatchesAffectingQuery(transaction, query) {
  12306. const queryPath = query.path;
  12307. const immediateChildrenLength = queryPath.length + 1;
  12308. // TODO(mcg): Actually implement a single-collection query
  12309. //
  12310. // This is actually executing an ancestor query, traversing the whole
  12311. // subtree below the collection which can be horrifically inefficient for
  12312. // some structures. The right way to solve this is to implement the full
  12313. // value index, but that's not in the cards in the near future so this is
  12314. // the best we can do for the moment.
  12315. //
  12316. // Since we don't yet index the actual properties in the mutations, our
  12317. // current approach is to just return all mutation batches that affect
  12318. // documents in the collection being queried.
  12319. const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
  12320. const indexStart = IDBKeyRange.lowerBound(indexPrefix);
  12321. // Collect up unique batchIDs encountered during a scan of the index. Use a
  12322. // SortedSet to accumulate batch IDs so they can be traversed in order in a
  12323. // scan of the main table.
  12324. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  12325. return documentMutationsStore(transaction)
  12326. .iterate({ range: indexStart }, (indexKey, _, control) => {
  12327. const [userID, encodedPath, batchID] = indexKey;
  12328. const path = decodeResourcePath(encodedPath);
  12329. if (userID !== this.userId || !queryPath.isPrefixOf(path)) {
  12330. control.done();
  12331. return;
  12332. }
  12333. // Rows with document keys more than one segment longer than the
  12334. // query path can't be matches. For example, a query on 'rooms'
  12335. // can't match the document /rooms/abc/messages/xyx.
  12336. // TODO(mcg): we'll need a different scanner when we implement
  12337. // ancestor queries.
  12338. if (path.length !== immediateChildrenLength) {
  12339. return;
  12340. }
  12341. uniqueBatchIDs = uniqueBatchIDs.add(batchID);
  12342. })
  12343. .next(() => this.lookupMutationBatches(transaction, uniqueBatchIDs));
  12344. }
  12345. lookupMutationBatches(transaction, batchIDs) {
  12346. const results = [];
  12347. const promises = [];
  12348. // TODO(rockwood): Implement this using iterate.
  12349. batchIDs.forEach(batchId => {
  12350. promises.push(mutationsStore(transaction)
  12351. .get(batchId)
  12352. .next(mutation => {
  12353. if (mutation === null) {
  12354. throw fail();
  12355. }
  12356. hardAssert(mutation.userId === this.userId);
  12357. results.push(fromDbMutationBatch(this.serializer, mutation));
  12358. }));
  12359. });
  12360. return PersistencePromise.waitFor(promises).next(() => results);
  12361. }
  12362. removeMutationBatch(transaction, batch) {
  12363. return removeMutationBatch(transaction.simpleDbTransaction, this.userId, batch).next(removedDocuments => {
  12364. transaction.addOnCommittedListener(() => {
  12365. this.removeCachedMutationKeys(batch.batchId);
  12366. });
  12367. return PersistencePromise.forEach(removedDocuments, (key) => {
  12368. return this.referenceDelegate.markPotentiallyOrphaned(transaction, key);
  12369. });
  12370. });
  12371. }
  12372. /**
  12373. * Clears the cached keys for a mutation batch. This method should be
  12374. * called by secondary clients after they process mutation updates.
  12375. *
  12376. * Note that this method does not have to be called from primary clients as
  12377. * the corresponding cache entries are cleared when an acknowledged or
  12378. * rejected batch is removed from the mutation queue.
  12379. */
  12380. // PORTING NOTE: Multi-tab only
  12381. removeCachedMutationKeys(batchId) {
  12382. delete this.documentKeysByBatchId[batchId];
  12383. }
  12384. performConsistencyCheck(txn) {
  12385. return this.checkEmpty(txn).next(empty => {
  12386. if (!empty) {
  12387. return PersistencePromise.resolve();
  12388. }
  12389. // Verify that there are no entries in the documentMutations index if
  12390. // the queue is empty.
  12391. const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
  12392. const danglingMutationReferences = [];
  12393. return documentMutationsStore(txn)
  12394. .iterate({ range: startRange }, (key, _, control) => {
  12395. const userID = key[0];
  12396. if (userID !== this.userId) {
  12397. control.done();
  12398. return;
  12399. }
  12400. else {
  12401. const path = decodeResourcePath(key[1]);
  12402. danglingMutationReferences.push(path);
  12403. }
  12404. })
  12405. .next(() => {
  12406. hardAssert(danglingMutationReferences.length === 0);
  12407. });
  12408. });
  12409. }
  12410. containsKey(txn, key) {
  12411. return mutationQueueContainsKey(txn, this.userId, key);
  12412. }
  12413. // PORTING NOTE: Multi-tab only (state is held in memory in other clients).
  12414. /** Returns the mutation queue's metadata from IndexedDb. */
  12415. getMutationQueueMetadata(transaction) {
  12416. return mutationQueuesStore(transaction)
  12417. .get(this.userId)
  12418. .next((metadata) => {
  12419. return (metadata || {
  12420. userId: this.userId,
  12421. lastAcknowledgedBatchId: BATCHID_UNKNOWN,
  12422. lastStreamToken: ''
  12423. });
  12424. });
  12425. }
  12426. }
  12427. /**
  12428. * @returns true if the mutation queue for the given user contains a pending
  12429. * mutation for the given key.
  12430. */
  12431. function mutationQueueContainsKey(txn, userId, key) {
  12432. const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
  12433. const encodedPath = indexKey[1];
  12434. const startRange = IDBKeyRange.lowerBound(indexKey);
  12435. let containsKey = false;
  12436. return documentMutationsStore(txn)
  12437. .iterate({ range: startRange, keysOnly: true }, (key, value, control) => {
  12438. const [userID, keyPath, /*batchID*/ _] = key;
  12439. if (userID === userId && keyPath === encodedPath) {
  12440. containsKey = true;
  12441. }
  12442. control.done();
  12443. })
  12444. .next(() => containsKey);
  12445. }
  12446. /** Returns true if any mutation queue contains the given document. */
  12447. function mutationQueuesContainKey(txn, docKey) {
  12448. let found = false;
  12449. return mutationQueuesStore(txn)
  12450. .iterateSerial(userId => {
  12451. return mutationQueueContainsKey(txn, userId, docKey).next(containsKey => {
  12452. if (containsKey) {
  12453. found = true;
  12454. }
  12455. return PersistencePromise.resolve(!containsKey);
  12456. });
  12457. })
  12458. .next(() => found);
  12459. }
  12460. /**
  12461. * Helper to get a typed SimpleDbStore for the mutations object store.
  12462. */
  12463. function mutationsStore(txn) {
  12464. return getStore(txn, DbMutationBatchStore);
  12465. }
  12466. /**
  12467. * Helper to get a typed SimpleDbStore for the mutationQueues object store.
  12468. */
  12469. function documentMutationsStore(txn) {
  12470. return getStore(txn, DbDocumentMutationStore);
  12471. }
  12472. /**
  12473. * Helper to get a typed SimpleDbStore for the mutationQueues object store.
  12474. */
  12475. function mutationQueuesStore(txn) {
  12476. return getStore(txn, DbMutationQueueStore);
  12477. }
  12478. /**
  12479. * @license
  12480. * Copyright 2017 Google LLC
  12481. *
  12482. * Licensed under the Apache License, Version 2.0 (the "License");
  12483. * you may not use this file except in compliance with the License.
  12484. * You may obtain a copy of the License at
  12485. *
  12486. * http://www.apache.org/licenses/LICENSE-2.0
  12487. *
  12488. * Unless required by applicable law or agreed to in writing, software
  12489. * distributed under the License is distributed on an "AS IS" BASIS,
  12490. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12491. * See the License for the specific language governing permissions and
  12492. * limitations under the License.
  12493. */
  12494. /** Offset to ensure non-overlapping target ids. */
  12495. const OFFSET = 2;
  12496. /**
  12497. * Generates monotonically increasing target IDs for sending targets to the
  12498. * watch stream.
  12499. *
  12500. * The client constructs two generators, one for the target cache, and one for
  12501. * for the sync engine (to generate limbo documents targets). These
  12502. * generators produce non-overlapping IDs (by using even and odd IDs
  12503. * respectively).
  12504. *
  12505. * By separating the target ID space, the query cache can generate target IDs
  12506. * that persist across client restarts, while sync engine can independently
  12507. * generate in-memory target IDs that are transient and can be reused after a
  12508. * restart.
  12509. */
  12510. class TargetIdGenerator {
  12511. constructor(lastId) {
  12512. this.lastId = lastId;
  12513. }
  12514. next() {
  12515. this.lastId += OFFSET;
  12516. return this.lastId;
  12517. }
  12518. static forTargetCache() {
  12519. // The target cache generator must return '2' in its first call to `next()`
  12520. // as there is no differentiation in the protocol layer between an unset
  12521. // number and the number '0'. If we were to sent a target with target ID
  12522. // '0', the backend would consider it unset and replace it with its own ID.
  12523. return new TargetIdGenerator(2 - OFFSET);
  12524. }
  12525. static forSyncEngine() {
  12526. // Sync engine assigns target IDs for limbo document detection.
  12527. return new TargetIdGenerator(1 - OFFSET);
  12528. }
  12529. }
  12530. /**
  12531. * @license
  12532. * Copyright 2017 Google LLC
  12533. *
  12534. * Licensed under the Apache License, Version 2.0 (the "License");
  12535. * you may not use this file except in compliance with the License.
  12536. * You may obtain a copy of the License at
  12537. *
  12538. * http://www.apache.org/licenses/LICENSE-2.0
  12539. *
  12540. * Unless required by applicable law or agreed to in writing, software
  12541. * distributed under the License is distributed on an "AS IS" BASIS,
  12542. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12543. * See the License for the specific language governing permissions and
  12544. * limitations under the License.
  12545. */
  12546. class IndexedDbTargetCache {
  12547. constructor(referenceDelegate, serializer) {
  12548. this.referenceDelegate = referenceDelegate;
  12549. this.serializer = serializer;
  12550. }
  12551. // PORTING NOTE: We don't cache global metadata for the target cache, since
  12552. // some of it (in particular `highestTargetId`) can be modified by secondary
  12553. // tabs. We could perhaps be more granular (and e.g. still cache
  12554. // `lastRemoteSnapshotVersion` in memory) but for simplicity we currently go
  12555. // to IndexedDb whenever we need to read metadata. We can revisit if it turns
  12556. // out to have a meaningful performance impact.
  12557. allocateTargetId(transaction) {
  12558. return this.retrieveMetadata(transaction).next(metadata => {
  12559. const targetIdGenerator = new TargetIdGenerator(metadata.highestTargetId);
  12560. metadata.highestTargetId = targetIdGenerator.next();
  12561. return this.saveMetadata(transaction, metadata).next(() => metadata.highestTargetId);
  12562. });
  12563. }
  12564. getLastRemoteSnapshotVersion(transaction) {
  12565. return this.retrieveMetadata(transaction).next(metadata => {
  12566. return SnapshotVersion.fromTimestamp(new Timestamp(metadata.lastRemoteSnapshotVersion.seconds, metadata.lastRemoteSnapshotVersion.nanoseconds));
  12567. });
  12568. }
  12569. getHighestSequenceNumber(transaction) {
  12570. return this.retrieveMetadata(transaction).next(targetGlobal => targetGlobal.highestListenSequenceNumber);
  12571. }
  12572. setTargetsMetadata(transaction, highestListenSequenceNumber, lastRemoteSnapshotVersion) {
  12573. return this.retrieveMetadata(transaction).next(metadata => {
  12574. metadata.highestListenSequenceNumber = highestListenSequenceNumber;
  12575. if (lastRemoteSnapshotVersion) {
  12576. metadata.lastRemoteSnapshotVersion =
  12577. lastRemoteSnapshotVersion.toTimestamp();
  12578. }
  12579. if (highestListenSequenceNumber > metadata.highestListenSequenceNumber) {
  12580. metadata.highestListenSequenceNumber = highestListenSequenceNumber;
  12581. }
  12582. return this.saveMetadata(transaction, metadata);
  12583. });
  12584. }
  12585. addTargetData(transaction, targetData) {
  12586. return this.saveTargetData(transaction, targetData).next(() => {
  12587. return this.retrieveMetadata(transaction).next(metadata => {
  12588. metadata.targetCount += 1;
  12589. this.updateMetadataFromTargetData(targetData, metadata);
  12590. return this.saveMetadata(transaction, metadata);
  12591. });
  12592. });
  12593. }
  12594. updateTargetData(transaction, targetData) {
  12595. return this.saveTargetData(transaction, targetData);
  12596. }
  12597. removeTargetData(transaction, targetData) {
  12598. return this.removeMatchingKeysForTargetId(transaction, targetData.targetId)
  12599. .next(() => targetsStore(transaction).delete(targetData.targetId))
  12600. .next(() => this.retrieveMetadata(transaction))
  12601. .next(metadata => {
  12602. hardAssert(metadata.targetCount > 0);
  12603. metadata.targetCount -= 1;
  12604. return this.saveMetadata(transaction, metadata);
  12605. });
  12606. }
  12607. /**
  12608. * Drops any targets with sequence number less than or equal to the upper bound, excepting those
  12609. * present in `activeTargetIds`. Document associations for the removed targets are also removed.
  12610. * Returns the number of targets removed.
  12611. */
  12612. removeTargets(txn, upperBound, activeTargetIds) {
  12613. let count = 0;
  12614. const promises = [];
  12615. return targetsStore(txn)
  12616. .iterate((key, value) => {
  12617. const targetData = fromDbTarget(value);
  12618. if (targetData.sequenceNumber <= upperBound &&
  12619. activeTargetIds.get(targetData.targetId) === null) {
  12620. count++;
  12621. promises.push(this.removeTargetData(txn, targetData));
  12622. }
  12623. })
  12624. .next(() => PersistencePromise.waitFor(promises))
  12625. .next(() => count);
  12626. }
  12627. /**
  12628. * Call provided function with each `TargetData` that we have cached.
  12629. */
  12630. forEachTarget(txn, f) {
  12631. return targetsStore(txn).iterate((key, value) => {
  12632. const targetData = fromDbTarget(value);
  12633. f(targetData);
  12634. });
  12635. }
  12636. retrieveMetadata(transaction) {
  12637. return globalTargetStore(transaction)
  12638. .get(DbTargetGlobalKey)
  12639. .next(metadata => {
  12640. hardAssert(metadata !== null);
  12641. return metadata;
  12642. });
  12643. }
  12644. saveMetadata(transaction, metadata) {
  12645. return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
  12646. }
  12647. saveTargetData(transaction, targetData) {
  12648. return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
  12649. }
  12650. /**
  12651. * In-place updates the provided metadata to account for values in the given
  12652. * TargetData. Saving is done separately. Returns true if there were any
  12653. * changes to the metadata.
  12654. */
  12655. updateMetadataFromTargetData(targetData, metadata) {
  12656. let updated = false;
  12657. if (targetData.targetId > metadata.highestTargetId) {
  12658. metadata.highestTargetId = targetData.targetId;
  12659. updated = true;
  12660. }
  12661. if (targetData.sequenceNumber > metadata.highestListenSequenceNumber) {
  12662. metadata.highestListenSequenceNumber = targetData.sequenceNumber;
  12663. updated = true;
  12664. }
  12665. return updated;
  12666. }
  12667. getTargetCount(transaction) {
  12668. return this.retrieveMetadata(transaction).next(metadata => metadata.targetCount);
  12669. }
  12670. getTargetData(transaction, target) {
  12671. // Iterating by the canonicalId may yield more than one result because
  12672. // canonicalId values are not required to be unique per target. This query
  12673. // depends on the queryTargets index to be efficient.
  12674. const canonicalId = canonifyTarget(target);
  12675. const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
  12676. let result = null;
  12677. return targetsStore(transaction)
  12678. .iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
  12679. const found = fromDbTarget(value);
  12680. // After finding a potential match, check that the target is
  12681. // actually equal to the requested target.
  12682. if (targetEquals(target, found.target)) {
  12683. result = found;
  12684. control.done();
  12685. }
  12686. })
  12687. .next(() => result);
  12688. }
  12689. addMatchingKeys(txn, keys, targetId) {
  12690. // PORTING NOTE: The reverse index (documentsTargets) is maintained by
  12691. // IndexedDb.
  12692. const promises = [];
  12693. const store = documentTargetStore(txn);
  12694. keys.forEach(key => {
  12695. const path = encodeResourcePath(key.path);
  12696. promises.push(store.put({ targetId, path }));
  12697. promises.push(this.referenceDelegate.addReference(txn, targetId, key));
  12698. });
  12699. return PersistencePromise.waitFor(promises);
  12700. }
  12701. removeMatchingKeys(txn, keys, targetId) {
  12702. // PORTING NOTE: The reverse index (documentsTargets) is maintained by
  12703. // IndexedDb.
  12704. const store = documentTargetStore(txn);
  12705. return PersistencePromise.forEach(keys, (key) => {
  12706. const path = encodeResourcePath(key.path);
  12707. return PersistencePromise.waitFor([
  12708. store.delete([targetId, path]),
  12709. this.referenceDelegate.removeReference(txn, targetId, key)
  12710. ]);
  12711. });
  12712. }
  12713. removeMatchingKeysForTargetId(txn, targetId) {
  12714. const store = documentTargetStore(txn);
  12715. const range = IDBKeyRange.bound([targetId], [targetId + 1],
  12716. /*lowerOpen=*/ false,
  12717. /*upperOpen=*/ true);
  12718. return store.delete(range);
  12719. }
  12720. getMatchingKeysForTargetId(txn, targetId) {
  12721. const range = IDBKeyRange.bound([targetId], [targetId + 1],
  12722. /*lowerOpen=*/ false,
  12723. /*upperOpen=*/ true);
  12724. const store = documentTargetStore(txn);
  12725. let result = documentKeySet();
  12726. return store
  12727. .iterate({ range, keysOnly: true }, (key, _, control) => {
  12728. const path = decodeResourcePath(key[1]);
  12729. const docKey = new DocumentKey(path);
  12730. result = result.add(docKey);
  12731. })
  12732. .next(() => result);
  12733. }
  12734. containsKey(txn, key) {
  12735. const path = encodeResourcePath(key.path);
  12736. const range = IDBKeyRange.bound([path], [immediateSuccessor(path)],
  12737. /*lowerOpen=*/ false,
  12738. /*upperOpen=*/ true);
  12739. let count = 0;
  12740. return documentTargetStore(txn)
  12741. .iterate({
  12742. index: DbTargetDocumentDocumentTargetsIndex,
  12743. keysOnly: true,
  12744. range
  12745. }, ([targetId, path], _, control) => {
  12746. // Having a sentinel row for a document does not count as containing that document;
  12747. // For the target cache, containing the document means the document is part of some
  12748. // target.
  12749. if (targetId !== 0) {
  12750. count++;
  12751. control.done();
  12752. }
  12753. })
  12754. .next(() => count > 0);
  12755. }
  12756. /**
  12757. * Looks up a TargetData entry by target ID.
  12758. *
  12759. * @param targetId - The target ID of the TargetData entry to look up.
  12760. * @returns The cached TargetData entry, or null if the cache has no entry for
  12761. * the target.
  12762. */
  12763. // PORTING NOTE: Multi-tab only.
  12764. getTargetDataForTarget(transaction, targetId) {
  12765. return targetsStore(transaction)
  12766. .get(targetId)
  12767. .next(found => {
  12768. if (found) {
  12769. return fromDbTarget(found);
  12770. }
  12771. else {
  12772. return null;
  12773. }
  12774. });
  12775. }
  12776. }
  12777. /**
  12778. * Helper to get a typed SimpleDbStore for the queries object store.
  12779. */
  12780. function targetsStore(txn) {
  12781. return getStore(txn, DbTargetStore);
  12782. }
  12783. /**
  12784. * Helper to get a typed SimpleDbStore for the target globals object store.
  12785. */
  12786. function globalTargetStore(txn) {
  12787. return getStore(txn, DbTargetGlobalStore);
  12788. }
  12789. /**
  12790. * Helper to get a typed SimpleDbStore for the document target object store.
  12791. */
  12792. function documentTargetStore(txn) {
  12793. return getStore(txn, DbTargetDocumentStore);
  12794. }
  12795. /**
  12796. * @license
  12797. * Copyright 2018 Google LLC
  12798. *
  12799. * Licensed under the Apache License, Version 2.0 (the "License");
  12800. * you may not use this file except in compliance with the License.
  12801. * You may obtain a copy of the License at
  12802. *
  12803. * http://www.apache.org/licenses/LICENSE-2.0
  12804. *
  12805. * Unless required by applicable law or agreed to in writing, software
  12806. * distributed under the License is distributed on an "AS IS" BASIS,
  12807. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12808. * See the License for the specific language governing permissions and
  12809. * limitations under the License.
  12810. */
  12811. const GC_DID_NOT_RUN = {
  12812. didRun: false,
  12813. sequenceNumbersCollected: 0,
  12814. targetsRemoved: 0,
  12815. documentsRemoved: 0
  12816. };
  12817. const LRU_COLLECTION_DISABLED = -1;
  12818. const LRU_DEFAULT_CACHE_SIZE_BYTES = 40 * 1024 * 1024;
  12819. class LruParams {
  12820. constructor(
  12821. // When we attempt to collect, we will only do so if the cache size is greater than this
  12822. // threshold. Passing `COLLECTION_DISABLED` here will cause collection to always be skipped.
  12823. cacheSizeCollectionThreshold,
  12824. // The percentage of sequence numbers that we will attempt to collect
  12825. percentileToCollect,
  12826. // A cap on the total number of sequence numbers that will be collected. This prevents
  12827. // us from collecting a huge number of sequence numbers if the cache has grown very large.
  12828. maximumSequenceNumbersToCollect) {
  12829. this.cacheSizeCollectionThreshold = cacheSizeCollectionThreshold;
  12830. this.percentileToCollect = percentileToCollect;
  12831. this.maximumSequenceNumbersToCollect = maximumSequenceNumbersToCollect;
  12832. }
  12833. static withCacheSize(cacheSize) {
  12834. return new LruParams(cacheSize, LruParams.DEFAULT_COLLECTION_PERCENTILE, LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT);
  12835. }
  12836. }
  12837. LruParams.DEFAULT_COLLECTION_PERCENTILE = 10;
  12838. LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT = 1000;
  12839. LruParams.DEFAULT = new LruParams(LRU_DEFAULT_CACHE_SIZE_BYTES, LruParams.DEFAULT_COLLECTION_PERCENTILE, LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT);
  12840. LruParams.DISABLED = new LruParams(LRU_COLLECTION_DISABLED, 0, 0);
  12841. /**
  12842. * @license
  12843. * Copyright 2020 Google LLC
  12844. *
  12845. * Licensed under the Apache License, Version 2.0 (the "License");
  12846. * you may not use this file except in compliance with the License.
  12847. * You may obtain a copy of the License at
  12848. *
  12849. * http://www.apache.org/licenses/LICENSE-2.0
  12850. *
  12851. * Unless required by applicable law or agreed to in writing, software
  12852. * distributed under the License is distributed on an "AS IS" BASIS,
  12853. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12854. * See the License for the specific language governing permissions and
  12855. * limitations under the License.
  12856. */
  12857. const LOG_TAG$e = 'LruGarbageCollector';
  12858. const LRU_MINIMUM_CACHE_SIZE_BYTES = 1 * 1024 * 1024;
  12859. /** How long we wait to try running LRU GC after SDK initialization. */
  12860. const INITIAL_GC_DELAY_MS = 1 * 60 * 1000;
  12861. /** Minimum amount of time between GC checks, after the first one. */
  12862. const REGULAR_GC_DELAY_MS = 5 * 60 * 1000;
  12863. function bufferEntryComparator([aSequence, aIndex], [bSequence, bIndex]) {
  12864. const seqCmp = primitiveComparator(aSequence, bSequence);
  12865. if (seqCmp === 0) {
  12866. // This order doesn't matter, but we can bias against churn by sorting
  12867. // entries created earlier as less than newer entries.
  12868. return primitiveComparator(aIndex, bIndex);
  12869. }
  12870. else {
  12871. return seqCmp;
  12872. }
  12873. }
  12874. /**
  12875. * Used to calculate the nth sequence number. Keeps a rolling buffer of the
  12876. * lowest n values passed to `addElement`, and finally reports the largest of
  12877. * them in `maxValue`.
  12878. */
  12879. class RollingSequenceNumberBuffer {
  12880. constructor(maxElements) {
  12881. this.maxElements = maxElements;
  12882. this.buffer = new SortedSet(bufferEntryComparator);
  12883. this.previousIndex = 0;
  12884. }
  12885. nextIndex() {
  12886. return ++this.previousIndex;
  12887. }
  12888. addElement(sequenceNumber) {
  12889. const entry = [sequenceNumber, this.nextIndex()];
  12890. if (this.buffer.size < this.maxElements) {
  12891. this.buffer = this.buffer.add(entry);
  12892. }
  12893. else {
  12894. const highestValue = this.buffer.last();
  12895. if (bufferEntryComparator(entry, highestValue) < 0) {
  12896. this.buffer = this.buffer.delete(highestValue).add(entry);
  12897. }
  12898. }
  12899. }
  12900. get maxValue() {
  12901. // Guaranteed to be non-empty. If we decide we are not collecting any
  12902. // sequence numbers, nthSequenceNumber below short-circuits. If we have
  12903. // decided that we are collecting n sequence numbers, it's because n is some
  12904. // percentage of the existing sequence numbers. That means we should never
  12905. // be in a situation where we are collecting sequence numbers but don't
  12906. // actually have any.
  12907. return this.buffer.last()[0];
  12908. }
  12909. }
  12910. /**
  12911. * This class is responsible for the scheduling of LRU garbage collection. It handles checking
  12912. * whether or not GC is enabled, as well as which delay to use before the next run.
  12913. */
  12914. class LruScheduler {
  12915. constructor(garbageCollector, asyncQueue, localStore) {
  12916. this.garbageCollector = garbageCollector;
  12917. this.asyncQueue = asyncQueue;
  12918. this.localStore = localStore;
  12919. this.gcTask = null;
  12920. }
  12921. start() {
  12922. if (this.garbageCollector.params.cacheSizeCollectionThreshold !==
  12923. LRU_COLLECTION_DISABLED) {
  12924. this.scheduleGC(INITIAL_GC_DELAY_MS);
  12925. }
  12926. }
  12927. stop() {
  12928. if (this.gcTask) {
  12929. this.gcTask.cancel();
  12930. this.gcTask = null;
  12931. }
  12932. }
  12933. get started() {
  12934. return this.gcTask !== null;
  12935. }
  12936. scheduleGC(delay) {
  12937. logDebug(LOG_TAG$e, `Garbage collection scheduled in ${delay}ms`);
  12938. this.gcTask = this.asyncQueue.enqueueAfterDelay("lru_garbage_collection" /* TimerId.LruGarbageCollection */, delay, async () => {
  12939. this.gcTask = null;
  12940. try {
  12941. await this.localStore.collectGarbage(this.garbageCollector);
  12942. }
  12943. catch (e) {
  12944. if (isIndexedDbTransactionError(e)) {
  12945. logDebug(LOG_TAG$e, 'Ignoring IndexedDB error during garbage collection: ', e);
  12946. }
  12947. else {
  12948. await ignoreIfPrimaryLeaseLoss(e);
  12949. }
  12950. }
  12951. await this.scheduleGC(REGULAR_GC_DELAY_MS);
  12952. });
  12953. }
  12954. }
  12955. /**
  12956. * Implements the steps for LRU garbage collection.
  12957. */
  12958. class LruGarbageCollectorImpl {
  12959. constructor(delegate, params) {
  12960. this.delegate = delegate;
  12961. this.params = params;
  12962. }
  12963. calculateTargetCount(txn, percentile) {
  12964. return this.delegate.getSequenceNumberCount(txn).next(targetCount => {
  12965. return Math.floor((percentile / 100.0) * targetCount);
  12966. });
  12967. }
  12968. nthSequenceNumber(txn, n) {
  12969. if (n === 0) {
  12970. return PersistencePromise.resolve(ListenSequence.INVALID);
  12971. }
  12972. const buffer = new RollingSequenceNumberBuffer(n);
  12973. return this.delegate
  12974. .forEachTarget(txn, target => buffer.addElement(target.sequenceNumber))
  12975. .next(() => {
  12976. return this.delegate.forEachOrphanedDocumentSequenceNumber(txn, sequenceNumber => buffer.addElement(sequenceNumber));
  12977. })
  12978. .next(() => buffer.maxValue);
  12979. }
  12980. removeTargets(txn, upperBound, activeTargetIds) {
  12981. return this.delegate.removeTargets(txn, upperBound, activeTargetIds);
  12982. }
  12983. removeOrphanedDocuments(txn, upperBound) {
  12984. return this.delegate.removeOrphanedDocuments(txn, upperBound);
  12985. }
  12986. collect(txn, activeTargetIds) {
  12987. if (this.params.cacheSizeCollectionThreshold === LRU_COLLECTION_DISABLED) {
  12988. logDebug('LruGarbageCollector', 'Garbage collection skipped; disabled');
  12989. return PersistencePromise.resolve(GC_DID_NOT_RUN);
  12990. }
  12991. return this.getCacheSize(txn).next(cacheSize => {
  12992. if (cacheSize < this.params.cacheSizeCollectionThreshold) {
  12993. logDebug('LruGarbageCollector', `Garbage collection skipped; Cache size ${cacheSize} ` +
  12994. `is lower than threshold ${this.params.cacheSizeCollectionThreshold}`);
  12995. return GC_DID_NOT_RUN;
  12996. }
  12997. else {
  12998. return this.runGarbageCollection(txn, activeTargetIds);
  12999. }
  13000. });
  13001. }
  13002. getCacheSize(txn) {
  13003. return this.delegate.getCacheSize(txn);
  13004. }
  13005. runGarbageCollection(txn, activeTargetIds) {
  13006. let upperBoundSequenceNumber;
  13007. let sequenceNumbersToCollect, targetsRemoved;
  13008. // Timestamps for various pieces of the process
  13009. let countedTargetsTs, foundUpperBoundTs, removedTargetsTs, removedDocumentsTs;
  13010. const startTs = Date.now();
  13011. return this.calculateTargetCount(txn, this.params.percentileToCollect)
  13012. .next(sequenceNumbers => {
  13013. // Cap at the configured max
  13014. if (sequenceNumbers > this.params.maximumSequenceNumbersToCollect) {
  13015. logDebug('LruGarbageCollector', 'Capping sequence numbers to collect down ' +
  13016. `to the maximum of ${this.params.maximumSequenceNumbersToCollect} ` +
  13017. `from ${sequenceNumbers}`);
  13018. sequenceNumbersToCollect =
  13019. this.params.maximumSequenceNumbersToCollect;
  13020. }
  13021. else {
  13022. sequenceNumbersToCollect = sequenceNumbers;
  13023. }
  13024. countedTargetsTs = Date.now();
  13025. return this.nthSequenceNumber(txn, sequenceNumbersToCollect);
  13026. })
  13027. .next(upperBound => {
  13028. upperBoundSequenceNumber = upperBound;
  13029. foundUpperBoundTs = Date.now();
  13030. return this.removeTargets(txn, upperBoundSequenceNumber, activeTargetIds);
  13031. })
  13032. .next(numTargetsRemoved => {
  13033. targetsRemoved = numTargetsRemoved;
  13034. removedTargetsTs = Date.now();
  13035. return this.removeOrphanedDocuments(txn, upperBoundSequenceNumber);
  13036. })
  13037. .next(documentsRemoved => {
  13038. removedDocumentsTs = Date.now();
  13039. if (getLogLevel() <= LogLevel.DEBUG) {
  13040. const desc = 'LRU Garbage Collection\n' +
  13041. `\tCounted targets in ${countedTargetsTs - startTs}ms\n` +
  13042. `\tDetermined least recently used ${sequenceNumbersToCollect} in ` +
  13043. `${foundUpperBoundTs - countedTargetsTs}ms\n` +
  13044. `\tRemoved ${targetsRemoved} targets in ` +
  13045. `${removedTargetsTs - foundUpperBoundTs}ms\n` +
  13046. `\tRemoved ${documentsRemoved} documents in ` +
  13047. `${removedDocumentsTs - removedTargetsTs}ms\n` +
  13048. `Total Duration: ${removedDocumentsTs - startTs}ms`;
  13049. logDebug('LruGarbageCollector', desc);
  13050. }
  13051. return PersistencePromise.resolve({
  13052. didRun: true,
  13053. sequenceNumbersCollected: sequenceNumbersToCollect,
  13054. targetsRemoved,
  13055. documentsRemoved
  13056. });
  13057. });
  13058. }
  13059. }
  13060. function newLruGarbageCollector(delegate, params) {
  13061. return new LruGarbageCollectorImpl(delegate, params);
  13062. }
  13063. /**
  13064. * @license
  13065. * Copyright 2020 Google LLC
  13066. *
  13067. * Licensed under the Apache License, Version 2.0 (the "License");
  13068. * you may not use this file except in compliance with the License.
  13069. * You may obtain a copy of the License at
  13070. *
  13071. * http://www.apache.org/licenses/LICENSE-2.0
  13072. *
  13073. * Unless required by applicable law or agreed to in writing, software
  13074. * distributed under the License is distributed on an "AS IS" BASIS,
  13075. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13076. * See the License for the specific language governing permissions and
  13077. * limitations under the License.
  13078. */
  13079. /** Provides LRU functionality for IndexedDB persistence. */
  13080. class IndexedDbLruDelegateImpl {
  13081. constructor(db, params) {
  13082. this.db = db;
  13083. this.garbageCollector = newLruGarbageCollector(this, params);
  13084. }
  13085. getSequenceNumberCount(txn) {
  13086. const docCountPromise = this.orphanedDocumentCount(txn);
  13087. const targetCountPromise = this.db.getTargetCache().getTargetCount(txn);
  13088. return targetCountPromise.next(targetCount => docCountPromise.next(docCount => targetCount + docCount));
  13089. }
  13090. orphanedDocumentCount(txn) {
  13091. let orphanedCount = 0;
  13092. return this.forEachOrphanedDocumentSequenceNumber(txn, _ => {
  13093. orphanedCount++;
  13094. }).next(() => orphanedCount);
  13095. }
  13096. forEachTarget(txn, f) {
  13097. return this.db.getTargetCache().forEachTarget(txn, f);
  13098. }
  13099. forEachOrphanedDocumentSequenceNumber(txn, f) {
  13100. return this.forEachOrphanedDocument(txn, (docKey, sequenceNumber) => f(sequenceNumber));
  13101. }
  13102. addReference(txn, targetId, key) {
  13103. return writeSentinelKey(txn, key);
  13104. }
  13105. removeReference(txn, targetId, key) {
  13106. return writeSentinelKey(txn, key);
  13107. }
  13108. removeTargets(txn, upperBound, activeTargetIds) {
  13109. return this.db.getTargetCache().removeTargets(txn, upperBound, activeTargetIds);
  13110. }
  13111. markPotentiallyOrphaned(txn, key) {
  13112. return writeSentinelKey(txn, key);
  13113. }
  13114. /**
  13115. * Returns true if anything would prevent this document from being garbage
  13116. * collected, given that the document in question is not present in any
  13117. * targets and has a sequence number less than or equal to the upper bound for
  13118. * the collection run.
  13119. */
  13120. isPinned(txn, docKey) {
  13121. return mutationQueuesContainKey(txn, docKey);
  13122. }
  13123. removeOrphanedDocuments(txn, upperBound) {
  13124. const documentCache = this.db.getRemoteDocumentCache();
  13125. const changeBuffer = documentCache.newChangeBuffer();
  13126. const promises = [];
  13127. let documentCount = 0;
  13128. const iteration = this.forEachOrphanedDocument(txn, (docKey, sequenceNumber) => {
  13129. if (sequenceNumber <= upperBound) {
  13130. const p = this.isPinned(txn, docKey).next(isPinned => {
  13131. if (!isPinned) {
  13132. documentCount++;
  13133. // Our size accounting requires us to read all documents before
  13134. // removing them.
  13135. return changeBuffer.getEntry(txn, docKey).next(() => {
  13136. changeBuffer.removeEntry(docKey, SnapshotVersion.min());
  13137. return documentTargetStore(txn).delete(sentinelKey$1(docKey));
  13138. });
  13139. }
  13140. });
  13141. promises.push(p);
  13142. }
  13143. });
  13144. return iteration
  13145. .next(() => PersistencePromise.waitFor(promises))
  13146. .next(() => changeBuffer.apply(txn))
  13147. .next(() => documentCount);
  13148. }
  13149. removeTarget(txn, targetData) {
  13150. const updated = targetData.withSequenceNumber(txn.currentSequenceNumber);
  13151. return this.db.getTargetCache().updateTargetData(txn, updated);
  13152. }
  13153. updateLimboDocument(txn, key) {
  13154. return writeSentinelKey(txn, key);
  13155. }
  13156. /**
  13157. * Call provided function for each document in the cache that is 'orphaned'. Orphaned
  13158. * means not a part of any target, so the only entry in the target-document index for
  13159. * that document will be the sentinel row (targetId 0), which will also have the sequence
  13160. * number for the last time the document was accessed.
  13161. */
  13162. forEachOrphanedDocument(txn, f) {
  13163. const store = documentTargetStore(txn);
  13164. let nextToReport = ListenSequence.INVALID;
  13165. let nextPath;
  13166. return store
  13167. .iterate({
  13168. index: DbTargetDocumentDocumentTargetsIndex
  13169. }, ([targetId, docKey], { path, sequenceNumber }) => {
  13170. if (targetId === 0) {
  13171. // if nextToReport is valid, report it, this is a new key so the
  13172. // last one must not be a member of any targets.
  13173. if (nextToReport !== ListenSequence.INVALID) {
  13174. f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
  13175. }
  13176. // set nextToReport to be this sequence number. It's the next one we
  13177. // might report, if we don't find any targets for this document.
  13178. // Note that the sequence number must be defined when the targetId
  13179. // is 0.
  13180. nextToReport = sequenceNumber;
  13181. nextPath = path;
  13182. }
  13183. else {
  13184. // set nextToReport to be invalid, we know we don't need to report
  13185. // this one since we found a target for it.
  13186. nextToReport = ListenSequence.INVALID;
  13187. }
  13188. })
  13189. .next(() => {
  13190. // Since we report sequence numbers after getting to the next key, we
  13191. // need to check if the last key we iterated over was an orphaned
  13192. // document and report it.
  13193. if (nextToReport !== ListenSequence.INVALID) {
  13194. f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
  13195. }
  13196. });
  13197. }
  13198. getCacheSize(txn) {
  13199. return this.db.getRemoteDocumentCache().getSize(txn);
  13200. }
  13201. }
  13202. function sentinelKey$1(key) {
  13203. return [0, encodeResourcePath(key.path)];
  13204. }
  13205. /**
  13206. * @returns A value suitable for writing a sentinel row in the target-document
  13207. * store.
  13208. */
  13209. function sentinelRow(key, sequenceNumber) {
  13210. return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
  13211. }
  13212. function writeSentinelKey(txn, key) {
  13213. return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
  13214. }
  13215. /**
  13216. * @license
  13217. * Copyright 2017 Google LLC
  13218. *
  13219. * Licensed under the Apache License, Version 2.0 (the "License");
  13220. * you may not use this file except in compliance with the License.
  13221. * You may obtain a copy of the License at
  13222. *
  13223. * http://www.apache.org/licenses/LICENSE-2.0
  13224. *
  13225. * Unless required by applicable law or agreed to in writing, software
  13226. * distributed under the License is distributed on an "AS IS" BASIS,
  13227. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13228. * See the License for the specific language governing permissions and
  13229. * limitations under the License.
  13230. */
  13231. /**
  13232. * An in-memory buffer of entries to be written to a RemoteDocumentCache.
  13233. * It can be used to batch up a set of changes to be written to the cache, but
  13234. * additionally supports reading entries back with the `getEntry()` method,
  13235. * falling back to the underlying RemoteDocumentCache if no entry is
  13236. * buffered.
  13237. *
  13238. * Entries added to the cache *must* be read first. This is to facilitate
  13239. * calculating the size delta of the pending changes.
  13240. *
  13241. * PORTING NOTE: This class was implemented then removed from other platforms.
  13242. * If byte-counting ends up being needed on the other platforms, consider
  13243. * porting this class as part of that implementation work.
  13244. */
  13245. class RemoteDocumentChangeBuffer {
  13246. constructor() {
  13247. // A mapping of document key to the new cache entry that should be written.
  13248. this.changes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
  13249. this.changesApplied = false;
  13250. }
  13251. /**
  13252. * Buffers a `RemoteDocumentCache.addEntry()` call.
  13253. *
  13254. * You can only modify documents that have already been retrieved via
  13255. * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
  13256. */
  13257. addEntry(document) {
  13258. this.assertNotApplied();
  13259. this.changes.set(document.key, document);
  13260. }
  13261. /**
  13262. * Buffers a `RemoteDocumentCache.removeEntry()` call.
  13263. *
  13264. * You can only remove documents that have already been retrieved via
  13265. * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
  13266. */
  13267. removeEntry(key, readTime) {
  13268. this.assertNotApplied();
  13269. this.changes.set(key, MutableDocument.newInvalidDocument(key).setReadTime(readTime));
  13270. }
  13271. /**
  13272. * Looks up an entry in the cache. The buffered changes will first be checked,
  13273. * and if no buffered change applies, this will forward to
  13274. * `RemoteDocumentCache.getEntry()`.
  13275. *
  13276. * @param transaction - The transaction in which to perform any persistence
  13277. * operations.
  13278. * @param documentKey - The key of the entry to look up.
  13279. * @returns The cached document or an invalid document if we have nothing
  13280. * cached.
  13281. */
  13282. getEntry(transaction, documentKey) {
  13283. this.assertNotApplied();
  13284. const bufferedEntry = this.changes.get(documentKey);
  13285. if (bufferedEntry !== undefined) {
  13286. return PersistencePromise.resolve(bufferedEntry);
  13287. }
  13288. else {
  13289. return this.getFromCache(transaction, documentKey);
  13290. }
  13291. }
  13292. /**
  13293. * Looks up several entries in the cache, forwarding to
  13294. * `RemoteDocumentCache.getEntry()`.
  13295. *
  13296. * @param transaction - The transaction in which to perform any persistence
  13297. * operations.
  13298. * @param documentKeys - The keys of the entries to look up.
  13299. * @returns A map of cached documents, indexed by key. If an entry cannot be
  13300. * found, the corresponding key will be mapped to an invalid document.
  13301. */
  13302. getEntries(transaction, documentKeys) {
  13303. return this.getAllFromCache(transaction, documentKeys);
  13304. }
  13305. /**
  13306. * Applies buffered changes to the underlying RemoteDocumentCache, using
  13307. * the provided transaction.
  13308. */
  13309. apply(transaction) {
  13310. this.assertNotApplied();
  13311. this.changesApplied = true;
  13312. return this.applyChanges(transaction);
  13313. }
  13314. /** Helper to assert this.changes is not null */
  13315. assertNotApplied() {
  13316. }
  13317. }
  13318. /**
  13319. * @license
  13320. * Copyright 2017 Google LLC
  13321. *
  13322. * Licensed under the Apache License, Version 2.0 (the "License");
  13323. * you may not use this file except in compliance with the License.
  13324. * You may obtain a copy of the License at
  13325. *
  13326. * http://www.apache.org/licenses/LICENSE-2.0
  13327. *
  13328. * Unless required by applicable law or agreed to in writing, software
  13329. * distributed under the License is distributed on an "AS IS" BASIS,
  13330. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13331. * See the License for the specific language governing permissions and
  13332. * limitations under the License.
  13333. */
  13334. /**
  13335. * The RemoteDocumentCache for IndexedDb. To construct, invoke
  13336. * `newIndexedDbRemoteDocumentCache()`.
  13337. */
  13338. class IndexedDbRemoteDocumentCacheImpl {
  13339. constructor(serializer) {
  13340. this.serializer = serializer;
  13341. }
  13342. setIndexManager(indexManager) {
  13343. this.indexManager = indexManager;
  13344. }
  13345. /**
  13346. * Adds the supplied entries to the cache.
  13347. *
  13348. * All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer
  13349. * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
  13350. */
  13351. addEntry(transaction, key, doc) {
  13352. const documentStore = remoteDocumentsStore(transaction);
  13353. return documentStore.put(doc);
  13354. }
  13355. /**
  13356. * Removes a document from the cache.
  13357. *
  13358. * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
  13359. * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
  13360. */
  13361. removeEntry(transaction, documentKey, readTime) {
  13362. const store = remoteDocumentsStore(transaction);
  13363. return store.delete(dbReadTimeKey(documentKey, readTime));
  13364. }
  13365. /**
  13366. * Updates the current cache size.
  13367. *
  13368. * Callers to `addEntry()` and `removeEntry()` *must* call this afterwards to update the
  13369. * cache's metadata.
  13370. */
  13371. updateMetadata(transaction, sizeDelta) {
  13372. return this.getMetadata(transaction).next(metadata => {
  13373. metadata.byteSize += sizeDelta;
  13374. return this.setMetadata(transaction, metadata);
  13375. });
  13376. }
  13377. getEntry(transaction, documentKey) {
  13378. let doc = MutableDocument.newInvalidDocument(documentKey);
  13379. return remoteDocumentsStore(transaction)
  13380. .iterate({
  13381. index: DbRemoteDocumentDocumentKeyIndex,
  13382. range: IDBKeyRange.only(dbKey(documentKey))
  13383. }, (_, dbRemoteDoc) => {
  13384. doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
  13385. })
  13386. .next(() => doc);
  13387. }
  13388. /**
  13389. * Looks up an entry in the cache.
  13390. *
  13391. * @param documentKey - The key of the entry to look up.
  13392. * @returns The cached document entry and its size.
  13393. */
  13394. getSizedEntry(transaction, documentKey) {
  13395. let result = {
  13396. size: 0,
  13397. document: MutableDocument.newInvalidDocument(documentKey)
  13398. };
  13399. return remoteDocumentsStore(transaction)
  13400. .iterate({
  13401. index: DbRemoteDocumentDocumentKeyIndex,
  13402. range: IDBKeyRange.only(dbKey(documentKey))
  13403. }, (_, dbRemoteDoc) => {
  13404. result = {
  13405. document: this.maybeDecodeDocument(documentKey, dbRemoteDoc),
  13406. size: dbDocumentSize(dbRemoteDoc)
  13407. };
  13408. })
  13409. .next(() => result);
  13410. }
  13411. getEntries(transaction, documentKeys) {
  13412. let results = mutableDocumentMap();
  13413. return this.forEachDbEntry(transaction, documentKeys, (key, dbRemoteDoc) => {
  13414. const doc = this.maybeDecodeDocument(key, dbRemoteDoc);
  13415. results = results.insert(key, doc);
  13416. }).next(() => results);
  13417. }
  13418. /**
  13419. * Looks up several entries in the cache.
  13420. *
  13421. * @param documentKeys - The set of keys entries to look up.
  13422. * @returns A map of documents indexed by key and a map of sizes indexed by
  13423. * key (zero if the document does not exist).
  13424. */
  13425. getSizedEntries(transaction, documentKeys) {
  13426. let results = mutableDocumentMap();
  13427. let sizeMap = new SortedMap(DocumentKey.comparator);
  13428. return this.forEachDbEntry(transaction, documentKeys, (key, dbRemoteDoc) => {
  13429. const doc = this.maybeDecodeDocument(key, dbRemoteDoc);
  13430. results = results.insert(key, doc);
  13431. sizeMap = sizeMap.insert(key, dbDocumentSize(dbRemoteDoc));
  13432. }).next(() => {
  13433. return { documents: results, sizeMap };
  13434. });
  13435. }
  13436. forEachDbEntry(transaction, documentKeys, callback) {
  13437. if (documentKeys.isEmpty()) {
  13438. return PersistencePromise.resolve();
  13439. }
  13440. let sortedKeys = new SortedSet(dbKeyComparator);
  13441. documentKeys.forEach(e => (sortedKeys = sortedKeys.add(e)));
  13442. const range = IDBKeyRange.bound(dbKey(sortedKeys.first()), dbKey(sortedKeys.last()));
  13443. const keyIter = sortedKeys.getIterator();
  13444. let nextKey = keyIter.getNext();
  13445. return remoteDocumentsStore(transaction)
  13446. .iterate({ index: DbRemoteDocumentDocumentKeyIndex, range }, (_, dbRemoteDoc, control) => {
  13447. const potentialKey = DocumentKey.fromSegments([
  13448. ...dbRemoteDoc.prefixPath,
  13449. dbRemoteDoc.collectionGroup,
  13450. dbRemoteDoc.documentId
  13451. ]);
  13452. // Go through keys not found in cache.
  13453. while (nextKey && dbKeyComparator(nextKey, potentialKey) < 0) {
  13454. callback(nextKey, null);
  13455. nextKey = keyIter.getNext();
  13456. }
  13457. if (nextKey && nextKey.isEqual(potentialKey)) {
  13458. // Key found in cache.
  13459. callback(nextKey, dbRemoteDoc);
  13460. nextKey = keyIter.hasNext() ? keyIter.getNext() : null;
  13461. }
  13462. // Skip to the next key (if there is one).
  13463. if (nextKey) {
  13464. control.skip(dbKey(nextKey));
  13465. }
  13466. else {
  13467. control.done();
  13468. }
  13469. })
  13470. .next(() => {
  13471. // The rest of the keys are not in the cache. One case where `iterate`
  13472. // above won't go through them is when the cache is empty.
  13473. while (nextKey) {
  13474. callback(nextKey, null);
  13475. nextKey = keyIter.hasNext() ? keyIter.getNext() : null;
  13476. }
  13477. });
  13478. }
  13479. getDocumentsMatchingQuery(transaction, query, offset, mutatedDocs) {
  13480. const collection = query.path;
  13481. const startKey = [
  13482. collection.popLast().toArray(),
  13483. collection.lastSegment(),
  13484. toDbTimestampKey(offset.readTime),
  13485. offset.documentKey.path.isEmpty()
  13486. ? ''
  13487. : offset.documentKey.path.lastSegment()
  13488. ];
  13489. const endKey = [
  13490. collection.popLast().toArray(),
  13491. collection.lastSegment(),
  13492. [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
  13493. ''
  13494. ];
  13495. return remoteDocumentsStore(transaction)
  13496. .loadAll(IDBKeyRange.bound(startKey, endKey, true))
  13497. .next(dbRemoteDocs => {
  13498. let results = mutableDocumentMap();
  13499. for (const dbRemoteDoc of dbRemoteDocs) {
  13500. const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
  13501. if (document.isFoundDocument() &&
  13502. (queryMatches(query, document) || mutatedDocs.has(document.key))) {
  13503. // Either the document matches the given query, or it is mutated.
  13504. results = results.insert(document.key, document);
  13505. }
  13506. }
  13507. return results;
  13508. });
  13509. }
  13510. getAllFromCollectionGroup(transaction, collectionGroup, offset, limit) {
  13511. let results = mutableDocumentMap();
  13512. const startKey = dbCollectionGroupKey(collectionGroup, offset);
  13513. const endKey = dbCollectionGroupKey(collectionGroup, IndexOffset.max());
  13514. return remoteDocumentsStore(transaction)
  13515. .iterate({
  13516. index: DbRemoteDocumentCollectionGroupIndex,
  13517. range: IDBKeyRange.bound(startKey, endKey, true)
  13518. }, (_, dbRemoteDoc, control) => {
  13519. const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
  13520. results = results.insert(document.key, document);
  13521. if (results.size === limit) {
  13522. control.done();
  13523. }
  13524. })
  13525. .next(() => results);
  13526. }
  13527. newChangeBuffer(options) {
  13528. return new IndexedDbRemoteDocumentChangeBuffer(this, !!options && options.trackRemovals);
  13529. }
  13530. getSize(txn) {
  13531. return this.getMetadata(txn).next(metadata => metadata.byteSize);
  13532. }
  13533. getMetadata(txn) {
  13534. return documentGlobalStore(txn)
  13535. .get(DbRemoteDocumentGlobalKey)
  13536. .next(metadata => {
  13537. hardAssert(!!metadata);
  13538. return metadata;
  13539. });
  13540. }
  13541. setMetadata(txn, metadata) {
  13542. return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
  13543. }
  13544. /**
  13545. * Decodes `dbRemoteDoc` and returns the document (or an invalid document if
  13546. * the document corresponds to the format used for sentinel deletes).
  13547. */
  13548. maybeDecodeDocument(documentKey, dbRemoteDoc) {
  13549. if (dbRemoteDoc) {
  13550. const doc = fromDbRemoteDocument(this.serializer, dbRemoteDoc);
  13551. // Whether the document is a sentinel removal and should only be used in the
  13552. // `getNewDocumentChanges()`
  13553. const isSentinelRemoval = doc.isNoDocument() && doc.version.isEqual(SnapshotVersion.min());
  13554. if (!isSentinelRemoval) {
  13555. return doc;
  13556. }
  13557. }
  13558. return MutableDocument.newInvalidDocument(documentKey);
  13559. }
  13560. }
  13561. /** Creates a new IndexedDbRemoteDocumentCache. */
  13562. function newIndexedDbRemoteDocumentCache(serializer) {
  13563. return new IndexedDbRemoteDocumentCacheImpl(serializer);
  13564. }
  13565. /**
  13566. * Handles the details of adding and updating documents in the IndexedDbRemoteDocumentCache.
  13567. *
  13568. * Unlike the MemoryRemoteDocumentChangeBuffer, the IndexedDb implementation computes the size
  13569. * delta for all submitted changes. This avoids having to re-read all documents from IndexedDb
  13570. * when we apply the changes.
  13571. */
  13572. class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
  13573. /**
  13574. * @param documentCache - The IndexedDbRemoteDocumentCache to apply the changes to.
  13575. * @param trackRemovals - Whether to create sentinel deletes that can be tracked by
  13576. * `getNewDocumentChanges()`.
  13577. */
  13578. constructor(documentCache, trackRemovals) {
  13579. super();
  13580. this.documentCache = documentCache;
  13581. this.trackRemovals = trackRemovals;
  13582. // A map of document sizes and read times prior to applying the changes in
  13583. // this buffer.
  13584. this.documentStates = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
  13585. }
  13586. applyChanges(transaction) {
  13587. const promises = [];
  13588. let sizeDelta = 0;
  13589. let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
  13590. this.changes.forEach((key, documentChange) => {
  13591. const previousDoc = this.documentStates.get(key);
  13592. promises.push(this.documentCache.removeEntry(transaction, key, previousDoc.readTime));
  13593. if (documentChange.isValidDocument()) {
  13594. const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
  13595. collectionParents = collectionParents.add(key.path.popLast());
  13596. const size = dbDocumentSize(doc);
  13597. sizeDelta += size - previousDoc.size;
  13598. promises.push(this.documentCache.addEntry(transaction, key, doc));
  13599. }
  13600. else {
  13601. sizeDelta -= previousDoc.size;
  13602. if (this.trackRemovals) {
  13603. // In order to track removals, we store a "sentinel delete" in the
  13604. // RemoteDocumentCache. This entry is represented by a NoDocument
  13605. // with a version of 0 and ignored by `maybeDecodeDocument()` but
  13606. // preserved in `getNewDocumentChanges()`.
  13607. const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
  13608. promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
  13609. }
  13610. }
  13611. });
  13612. collectionParents.forEach(parent => {
  13613. promises.push(this.documentCache.indexManager.addToCollectionParentIndex(transaction, parent));
  13614. });
  13615. promises.push(this.documentCache.updateMetadata(transaction, sizeDelta));
  13616. return PersistencePromise.waitFor(promises);
  13617. }
  13618. getFromCache(transaction, documentKey) {
  13619. // Record the size of everything we load from the cache so we can compute a delta later.
  13620. return this.documentCache
  13621. .getSizedEntry(transaction, documentKey)
  13622. .next(getResult => {
  13623. this.documentStates.set(documentKey, {
  13624. size: getResult.size,
  13625. readTime: getResult.document.readTime
  13626. });
  13627. return getResult.document;
  13628. });
  13629. }
  13630. getAllFromCache(transaction, documentKeys) {
  13631. // Record the size of everything we load from the cache so we can compute
  13632. // a delta later.
  13633. return this.documentCache
  13634. .getSizedEntries(transaction, documentKeys)
  13635. .next(({ documents, sizeMap }) => {
  13636. // Note: `getAllFromCache` returns two maps instead of a single map from
  13637. // keys to `DocumentSizeEntry`s. This is to allow returning the
  13638. // `MutableDocumentMap` directly, without a conversion.
  13639. sizeMap.forEach((documentKey, size) => {
  13640. this.documentStates.set(documentKey, {
  13641. size,
  13642. readTime: documents.get(documentKey).readTime
  13643. });
  13644. });
  13645. return documents;
  13646. });
  13647. }
  13648. }
  13649. function documentGlobalStore(txn) {
  13650. return getStore(txn, DbRemoteDocumentGlobalStore);
  13651. }
  13652. /**
  13653. * Helper to get a typed SimpleDbStore for the remoteDocuments object store.
  13654. */
  13655. function remoteDocumentsStore(txn) {
  13656. return getStore(txn, DbRemoteDocumentStore);
  13657. }
  13658. /**
  13659. * Returns a key that can be used for document lookups on the
  13660. * `DbRemoteDocumentDocumentKeyIndex` index.
  13661. */
  13662. function dbKey(documentKey) {
  13663. const path = documentKey.path.toArray();
  13664. return [
  13665. /* prefix path */ path.slice(0, path.length - 2),
  13666. /* collection id */ path[path.length - 2],
  13667. /* document id */ path[path.length - 1]
  13668. ];
  13669. }
  13670. /**
  13671. * Returns a key that can be used for document lookups via the primary key of
  13672. * the DbRemoteDocument object store.
  13673. */
  13674. function dbReadTimeKey(documentKey, readTime) {
  13675. const path = documentKey.path.toArray();
  13676. return [
  13677. /* prefix path */ path.slice(0, path.length - 2),
  13678. /* collection id */ path[path.length - 2],
  13679. toDbTimestampKey(readTime),
  13680. /* document id */ path[path.length - 1]
  13681. ];
  13682. }
  13683. /**
  13684. * Returns a key that can be used for document lookups on the
  13685. * `DbRemoteDocumentDocumentCollectionGroupIndex` index.
  13686. */
  13687. function dbCollectionGroupKey(collectionGroup, offset) {
  13688. const path = offset.documentKey.path.toArray();
  13689. return [
  13690. /* collection id */ collectionGroup,
  13691. toDbTimestampKey(offset.readTime),
  13692. /* prefix path */ path.slice(0, path.length - 2),
  13693. /* document id */ path.length > 0 ? path[path.length - 1] : ''
  13694. ];
  13695. }
  13696. /**
  13697. * Comparator that compares document keys according to the primary key sorting
  13698. * used by the `DbRemoteDocumentDocument` store (by prefix path, collection id
  13699. * and then document ID).
  13700. *
  13701. * Visible for testing.
  13702. */
  13703. function dbKeyComparator(l, r) {
  13704. const left = l.path.toArray();
  13705. const right = r.path.toArray();
  13706. // The ordering is based on https://chromium.googlesource.com/chromium/blink/+/fe5c21fef94dae71c1c3344775b8d8a7f7e6d9ec/Source/modules/indexeddb/IDBKey.cpp#74
  13707. let cmp = 0;
  13708. for (let i = 0; i < left.length - 2 && i < right.length - 2; ++i) {
  13709. cmp = primitiveComparator(left[i], right[i]);
  13710. if (cmp) {
  13711. return cmp;
  13712. }
  13713. }
  13714. cmp = primitiveComparator(left.length, right.length);
  13715. if (cmp) {
  13716. return cmp;
  13717. }
  13718. cmp = primitiveComparator(left[left.length - 2], right[right.length - 2]);
  13719. if (cmp) {
  13720. return cmp;
  13721. }
  13722. return primitiveComparator(left[left.length - 1], right[right.length - 1]);
  13723. }
  13724. /**
  13725. * @license
  13726. * Copyright 2017 Google LLC
  13727. *
  13728. * Licensed under the Apache License, Version 2.0 (the "License");
  13729. * you may not use this file except in compliance with the License.
  13730. * You may obtain a copy of the License at
  13731. *
  13732. * http://www.apache.org/licenses/LICENSE-2.0
  13733. *
  13734. * Unless required by applicable law or agreed to in writing, software
  13735. * distributed under the License is distributed on an "AS IS" BASIS,
  13736. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13737. * See the License for the specific language governing permissions and
  13738. * limitations under the License.
  13739. */
  13740. /**
  13741. * Schema Version for the Web client:
  13742. * 1. Initial version including Mutation Queue, Query Cache, and Remote
  13743. * Document Cache
  13744. * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
  13745. * longer required because migration 3 unconditionally clears it.
  13746. * 3. Dropped and re-created Query Cache to deal with cache corruption related
  13747. * to limbo resolution. Addresses
  13748. * https://github.com/firebase/firebase-ios-sdk/issues/1548
  13749. * 4. Multi-Tab Support.
  13750. * 5. Removal of held write acks.
  13751. * 6. Create document global for tracking document cache size.
  13752. * 7. Ensure every cached document has a sentinel row with a sequence number.
  13753. * 8. Add collection-parent index for Collection Group queries.
  13754. * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
  13755. * an auto-incrementing ID. This is required for Index-Free queries.
  13756. * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
  13757. * 11. Add bundles and named_queries for bundle support.
  13758. * 12. Add document overlays.
  13759. * 13. Rewrite the keys of the remote document cache to allow for efficient
  13760. * document lookup via `getAll()`.
  13761. * 14. Add overlays.
  13762. * 15. Add indexing support.
  13763. */
  13764. const SCHEMA_VERSION = 15;
  13765. /**
  13766. * @license
  13767. * Copyright 2022 Google LLC
  13768. *
  13769. * Licensed under the Apache License, Version 2.0 (the "License");
  13770. * you may not use this file except in compliance with the License.
  13771. * You may obtain a copy of the License at
  13772. *
  13773. * http://www.apache.org/licenses/LICENSE-2.0
  13774. *
  13775. * Unless required by applicable law or agreed to in writing, software
  13776. * distributed under the License is distributed on an "AS IS" BASIS,
  13777. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13778. * See the License for the specific language governing permissions and
  13779. * limitations under the License.
  13780. */
  13781. /**
  13782. * Represents a local view (overlay) of a document, and the fields that are
  13783. * locally mutated.
  13784. */
  13785. class OverlayedDocument {
  13786. constructor(overlayedDocument,
  13787. /**
  13788. * The fields that are locally mutated by patch mutations.
  13789. *
  13790. * If the overlayed document is from set or delete mutations, this is `null`.
  13791. * If there is no overlay (mutation) for the document, this is an empty `FieldMask`.
  13792. */
  13793. mutatedFields) {
  13794. this.overlayedDocument = overlayedDocument;
  13795. this.mutatedFields = mutatedFields;
  13796. }
  13797. }
  13798. /**
  13799. * @license
  13800. * Copyright 2017 Google LLC
  13801. *
  13802. * Licensed under the Apache License, Version 2.0 (the "License");
  13803. * you may not use this file except in compliance with the License.
  13804. * You may obtain a copy of the License at
  13805. *
  13806. * http://www.apache.org/licenses/LICENSE-2.0
  13807. *
  13808. * Unless required by applicable law or agreed to in writing, software
  13809. * distributed under the License is distributed on an "AS IS" BASIS,
  13810. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13811. * See the License for the specific language governing permissions and
  13812. * limitations under the License.
  13813. */
  13814. /**
  13815. * A readonly view of the local state of all documents we're tracking (i.e. we
  13816. * have a cached version in remoteDocumentCache or local mutations for the
  13817. * document). The view is computed by applying the mutations in the
  13818. * MutationQueue to the RemoteDocumentCache.
  13819. */
  13820. class LocalDocumentsView {
  13821. constructor(remoteDocumentCache, mutationQueue, documentOverlayCache, indexManager) {
  13822. this.remoteDocumentCache = remoteDocumentCache;
  13823. this.mutationQueue = mutationQueue;
  13824. this.documentOverlayCache = documentOverlayCache;
  13825. this.indexManager = indexManager;
  13826. }
  13827. /**
  13828. * Get the local view of the document identified by `key`.
  13829. *
  13830. * @returns Local view of the document or null if we don't have any cached
  13831. * state for it.
  13832. */
  13833. getDocument(transaction, key) {
  13834. let overlay = null;
  13835. return this.documentOverlayCache
  13836. .getOverlay(transaction, key)
  13837. .next(value => {
  13838. overlay = value;
  13839. return this.remoteDocumentCache.getEntry(transaction, key);
  13840. })
  13841. .next(document => {
  13842. if (overlay !== null) {
  13843. mutationApplyToLocalView(overlay.mutation, document, FieldMask.empty(), Timestamp.now());
  13844. }
  13845. return document;
  13846. });
  13847. }
  13848. /**
  13849. * Gets the local view of the documents identified by `keys`.
  13850. *
  13851. * If we don't have cached state for a document in `keys`, a NoDocument will
  13852. * be stored for that key in the resulting set.
  13853. */
  13854. getDocuments(transaction, keys) {
  13855. return this.remoteDocumentCache
  13856. .getEntries(transaction, keys)
  13857. .next(docs => this.getLocalViewOfDocuments(transaction, docs, documentKeySet()).next(() => docs));
  13858. }
  13859. /**
  13860. * Similar to `getDocuments`, but creates the local view from the given
  13861. * `baseDocs` without retrieving documents from the local store.
  13862. *
  13863. * @param transaction - The transaction this operation is scoped to.
  13864. * @param docs - The documents to apply local mutations to get the local views.
  13865. * @param existenceStateChanged - The set of document keys whose existence state
  13866. * is changed. This is useful to determine if some documents overlay needs
  13867. * to be recalculated.
  13868. */
  13869. getLocalViewOfDocuments(transaction, docs, existenceStateChanged = documentKeySet()) {
  13870. const overlays = newOverlayMap();
  13871. return this.populateOverlays(transaction, overlays, docs).next(() => {
  13872. return this.computeViews(transaction, docs, overlays, existenceStateChanged).next(computeViewsResult => {
  13873. let result = documentMap();
  13874. computeViewsResult.forEach((documentKey, overlayedDocument) => {
  13875. result = result.insert(documentKey, overlayedDocument.overlayedDocument);
  13876. });
  13877. return result;
  13878. });
  13879. });
  13880. }
  13881. /**
  13882. * Gets the overlayed documents for the given document map, which will include
  13883. * the local view of those documents and a `FieldMask` indicating which fields
  13884. * are mutated locally, `null` if overlay is a Set or Delete mutation.
  13885. */
  13886. getOverlayedDocuments(transaction, docs) {
  13887. const overlays = newOverlayMap();
  13888. return this.populateOverlays(transaction, overlays, docs).next(() => this.computeViews(transaction, docs, overlays, documentKeySet()));
  13889. }
  13890. /**
  13891. * Fetches the overlays for {@code docs} and adds them to provided overlay map
  13892. * if the map does not already contain an entry for the given document key.
  13893. */
  13894. populateOverlays(transaction, overlays, docs) {
  13895. const missingOverlays = [];
  13896. docs.forEach(key => {
  13897. if (!overlays.has(key)) {
  13898. missingOverlays.push(key);
  13899. }
  13900. });
  13901. return this.documentOverlayCache
  13902. .getOverlays(transaction, missingOverlays)
  13903. .next(result => {
  13904. result.forEach((key, val) => {
  13905. overlays.set(key, val);
  13906. });
  13907. });
  13908. }
  13909. /**
  13910. * Computes the local view for the given documents.
  13911. *
  13912. * @param docs - The documents to compute views for. It also has the base
  13913. * version of the documents.
  13914. * @param overlays - The overlays that need to be applied to the given base
  13915. * version of the documents.
  13916. * @param existenceStateChanged - A set of documents whose existence states
  13917. * might have changed. This is used to determine if we need to re-calculate
  13918. * overlays from mutation queues.
  13919. * @return A map represents the local documents view.
  13920. */
  13921. computeViews(transaction, docs, overlays, existenceStateChanged) {
  13922. let recalculateDocuments = mutableDocumentMap();
  13923. const mutatedFields = newDocumentKeyMap();
  13924. const results = newOverlayedDocumentMap();
  13925. docs.forEach((_, doc) => {
  13926. const overlay = overlays.get(doc.key);
  13927. // Recalculate an overlay if the document's existence state changed due to
  13928. // a remote event *and* the overlay is a PatchMutation. This is because
  13929. // document existence state can change if some patch mutation's
  13930. // preconditions are met.
  13931. // NOTE: we recalculate when `overlay` is undefined as well, because there
  13932. // might be a patch mutation whose precondition does not match before the
  13933. // change (hence overlay is undefined), but would now match.
  13934. if (existenceStateChanged.has(doc.key) &&
  13935. (overlay === undefined || overlay.mutation instanceof PatchMutation)) {
  13936. recalculateDocuments = recalculateDocuments.insert(doc.key, doc);
  13937. }
  13938. else if (overlay !== undefined) {
  13939. mutatedFields.set(doc.key, overlay.mutation.getFieldMask());
  13940. mutationApplyToLocalView(overlay.mutation, doc, overlay.mutation.getFieldMask(), Timestamp.now());
  13941. }
  13942. else {
  13943. // no overlay exists
  13944. // Using EMPTY to indicate there is no overlay for the document.
  13945. mutatedFields.set(doc.key, FieldMask.empty());
  13946. }
  13947. });
  13948. return this.recalculateAndSaveOverlays(transaction, recalculateDocuments).next(recalculatedFields => {
  13949. recalculatedFields.forEach((documentKey, mask) => mutatedFields.set(documentKey, mask));
  13950. docs.forEach((documentKey, document) => {
  13951. var _a;
  13952. return results.set(documentKey, new OverlayedDocument(document, (_a = mutatedFields.get(documentKey)) !== null && _a !== void 0 ? _a : null));
  13953. });
  13954. return results;
  13955. });
  13956. }
  13957. recalculateAndSaveOverlays(transaction, docs) {
  13958. const masks = newDocumentKeyMap();
  13959. // A reverse lookup map from batch id to the documents within that batch.
  13960. let documentsByBatchId = new SortedMap((key1, key2) => key1 - key2);
  13961. let processed = documentKeySet();
  13962. return this.mutationQueue
  13963. .getAllMutationBatchesAffectingDocumentKeys(transaction, docs)
  13964. .next(batches => {
  13965. for (const batch of batches) {
  13966. batch.keys().forEach(key => {
  13967. const baseDoc = docs.get(key);
  13968. if (baseDoc === null) {
  13969. return;
  13970. }
  13971. let mask = masks.get(key) || FieldMask.empty();
  13972. mask = batch.applyToLocalView(baseDoc, mask);
  13973. masks.set(key, mask);
  13974. const newSet = (documentsByBatchId.get(batch.batchId) || documentKeySet()).add(key);
  13975. documentsByBatchId = documentsByBatchId.insert(batch.batchId, newSet);
  13976. });
  13977. }
  13978. })
  13979. .next(() => {
  13980. const promises = [];
  13981. // Iterate in descending order of batch IDs, and skip documents that are
  13982. // already saved.
  13983. const iter = documentsByBatchId.getReverseIterator();
  13984. while (iter.hasNext()) {
  13985. const entry = iter.getNext();
  13986. const batchId = entry.key;
  13987. const keys = entry.value;
  13988. const overlays = newMutationMap();
  13989. keys.forEach(key => {
  13990. if (!processed.has(key)) {
  13991. const overlayMutation = calculateOverlayMutation(docs.get(key), masks.get(key));
  13992. if (overlayMutation !== null) {
  13993. overlays.set(key, overlayMutation);
  13994. }
  13995. processed = processed.add(key);
  13996. }
  13997. });
  13998. promises.push(this.documentOverlayCache.saveOverlays(transaction, batchId, overlays));
  13999. }
  14000. return PersistencePromise.waitFor(promises);
  14001. })
  14002. .next(() => masks);
  14003. }
  14004. /**
  14005. * Recalculates overlays by reading the documents from remote document cache
  14006. * first, and saves them after they are calculated.
  14007. */
  14008. recalculateAndSaveOverlaysForDocumentKeys(transaction, documentKeys) {
  14009. return this.remoteDocumentCache
  14010. .getEntries(transaction, documentKeys)
  14011. .next(docs => this.recalculateAndSaveOverlays(transaction, docs));
  14012. }
  14013. /**
  14014. * Performs a query against the local view of all documents.
  14015. *
  14016. * @param transaction - The persistence transaction.
  14017. * @param query - The query to match documents against.
  14018. * @param offset - Read time and key to start scanning by (exclusive).
  14019. */
  14020. getDocumentsMatchingQuery(transaction, query, offset) {
  14021. if (isDocumentQuery$1(query)) {
  14022. return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
  14023. }
  14024. else if (isCollectionGroupQuery(query)) {
  14025. return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, offset);
  14026. }
  14027. else {
  14028. return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
  14029. }
  14030. }
  14031. /**
  14032. * Given a collection group, returns the next documents that follow the provided offset, along
  14033. * with an updated batch ID.
  14034. *
  14035. * <p>The documents returned by this method are ordered by remote version from the provided
  14036. * offset. If there are no more remote documents after the provided offset, documents with
  14037. * mutations in order of batch id from the offset are returned. Since all documents in a batch are
  14038. * returned together, the total number of documents returned can exceed {@code count}.
  14039. *
  14040. * @param transaction
  14041. * @param collectionGroup The collection group for the documents.
  14042. * @param offset The offset to index into.
  14043. * @param count The number of documents to return
  14044. * @return A LocalWriteResult with the documents that follow the provided offset and the last processed batch id.
  14045. */
  14046. getNextDocuments(transaction, collectionGroup, offset, count) {
  14047. return this.remoteDocumentCache
  14048. .getAllFromCollectionGroup(transaction, collectionGroup, offset, count)
  14049. .next((originalDocs) => {
  14050. const overlaysPromise = count - originalDocs.size > 0
  14051. ? this.documentOverlayCache.getOverlaysForCollectionGroup(transaction, collectionGroup, offset.largestBatchId, count - originalDocs.size)
  14052. : PersistencePromise.resolve(newOverlayMap());
  14053. // The callsite will use the largest batch ID together with the latest read time to create
  14054. // a new index offset. Since we only process batch IDs if all remote documents have been read,
  14055. // no overlay will increase the overall read time. This is why we only need to special case
  14056. // the batch id.
  14057. let largestBatchId = INITIAL_LARGEST_BATCH_ID;
  14058. let modifiedDocs = originalDocs;
  14059. return overlaysPromise.next(overlays => {
  14060. return PersistencePromise.forEach(overlays, (key, overlay) => {
  14061. if (largestBatchId < overlay.largestBatchId) {
  14062. largestBatchId = overlay.largestBatchId;
  14063. }
  14064. if (originalDocs.get(key)) {
  14065. return PersistencePromise.resolve();
  14066. }
  14067. return this.remoteDocumentCache
  14068. .getEntry(transaction, key)
  14069. .next(doc => {
  14070. modifiedDocs = modifiedDocs.insert(key, doc);
  14071. });
  14072. })
  14073. .next(() => this.populateOverlays(transaction, overlays, originalDocs))
  14074. .next(() => this.computeViews(transaction, modifiedDocs, overlays, documentKeySet()))
  14075. .next(localDocs => ({
  14076. batchId: largestBatchId,
  14077. changes: convertOverlayedDocumentMapToDocumentMap(localDocs)
  14078. }));
  14079. });
  14080. });
  14081. }
  14082. getDocumentsMatchingDocumentQuery(transaction, docPath) {
  14083. // Just do a simple document lookup.
  14084. return this.getDocument(transaction, new DocumentKey(docPath)).next(document => {
  14085. let result = documentMap();
  14086. if (document.isFoundDocument()) {
  14087. result = result.insert(document.key, document);
  14088. }
  14089. return result;
  14090. });
  14091. }
  14092. getDocumentsMatchingCollectionGroupQuery(transaction, query, offset) {
  14093. const collectionId = query.collectionGroup;
  14094. let results = documentMap();
  14095. return this.indexManager
  14096. .getCollectionParents(transaction, collectionId)
  14097. .next(parents => {
  14098. // Perform a collection query against each parent that contains the
  14099. // collectionId and aggregate the results.
  14100. return PersistencePromise.forEach(parents, (parent) => {
  14101. const collectionQuery = asCollectionQueryAtPath(query, parent.child(collectionId));
  14102. return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, offset).next(r => {
  14103. r.forEach((key, doc) => {
  14104. results = results.insert(key, doc);
  14105. });
  14106. });
  14107. }).next(() => results);
  14108. });
  14109. }
  14110. getDocumentsMatchingCollectionQuery(transaction, query, offset) {
  14111. // Query the remote documents and overlay mutations.
  14112. let overlays;
  14113. return this.documentOverlayCache
  14114. .getOverlaysForCollection(transaction, query.path, offset.largestBatchId)
  14115. .next(result => {
  14116. overlays = result;
  14117. return this.remoteDocumentCache.getDocumentsMatchingQuery(transaction, query, offset, overlays);
  14118. })
  14119. .next(remoteDocuments => {
  14120. // As documents might match the query because of their overlay we need to
  14121. // include documents for all overlays in the initial document set.
  14122. overlays.forEach((_, overlay) => {
  14123. const key = overlay.getKey();
  14124. if (remoteDocuments.get(key) === null) {
  14125. remoteDocuments = remoteDocuments.insert(key, MutableDocument.newInvalidDocument(key));
  14126. }
  14127. });
  14128. // Apply the overlays and match against the query.
  14129. let results = documentMap();
  14130. remoteDocuments.forEach((key, document) => {
  14131. const overlay = overlays.get(key);
  14132. if (overlay !== undefined) {
  14133. mutationApplyToLocalView(overlay.mutation, document, FieldMask.empty(), Timestamp.now());
  14134. }
  14135. // Finally, insert the documents that still match the query
  14136. if (queryMatches(query, document)) {
  14137. results = results.insert(key, document);
  14138. }
  14139. });
  14140. return results;
  14141. });
  14142. }
  14143. }
  14144. /**
  14145. * @license
  14146. * Copyright 2020 Google LLC
  14147. *
  14148. * Licensed under the Apache License, Version 2.0 (the "License");
  14149. * you may not use this file except in compliance with the License.
  14150. * You may obtain a copy of the License at
  14151. *
  14152. * http://www.apache.org/licenses/LICENSE-2.0
  14153. *
  14154. * Unless required by applicable law or agreed to in writing, software
  14155. * distributed under the License is distributed on an "AS IS" BASIS,
  14156. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14157. * See the License for the specific language governing permissions and
  14158. * limitations under the License.
  14159. */
  14160. class MemoryBundleCache {
  14161. constructor(serializer) {
  14162. this.serializer = serializer;
  14163. this.bundles = new Map();
  14164. this.namedQueries = new Map();
  14165. }
  14166. getBundleMetadata(transaction, bundleId) {
  14167. return PersistencePromise.resolve(this.bundles.get(bundleId));
  14168. }
  14169. saveBundleMetadata(transaction, bundleMetadata) {
  14170. this.bundles.set(bundleMetadata.id, fromBundleMetadata(bundleMetadata));
  14171. return PersistencePromise.resolve();
  14172. }
  14173. getNamedQuery(transaction, queryName) {
  14174. return PersistencePromise.resolve(this.namedQueries.get(queryName));
  14175. }
  14176. saveNamedQuery(transaction, query) {
  14177. this.namedQueries.set(query.name, fromProtoNamedQuery(query));
  14178. return PersistencePromise.resolve();
  14179. }
  14180. }
  14181. /**
  14182. * @license
  14183. * Copyright 2022 Google LLC
  14184. *
  14185. * Licensed under the Apache License, Version 2.0 (the "License");
  14186. * you may not use this file except in compliance with the License.
  14187. * You may obtain a copy of the License at
  14188. *
  14189. * http://www.apache.org/licenses/LICENSE-2.0
  14190. *
  14191. * Unless required by applicable law or agreed to in writing, software
  14192. * distributed under the License is distributed on an "AS IS" BASIS,
  14193. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14194. * See the License for the specific language governing permissions and
  14195. * limitations under the License.
  14196. */
  14197. /**
  14198. * An in-memory implementation of DocumentOverlayCache.
  14199. */
  14200. class MemoryDocumentOverlayCache {
  14201. constructor() {
  14202. // A map sorted by DocumentKey, whose value is a pair of the largest batch id
  14203. // for the overlay and the overlay itself.
  14204. this.overlays = new SortedMap(DocumentKey.comparator);
  14205. this.overlayByBatchId = new Map();
  14206. }
  14207. getOverlay(transaction, key) {
  14208. return PersistencePromise.resolve(this.overlays.get(key));
  14209. }
  14210. getOverlays(transaction, keys) {
  14211. const result = newOverlayMap();
  14212. return PersistencePromise.forEach(keys, (key) => {
  14213. return this.getOverlay(transaction, key).next(overlay => {
  14214. if (overlay !== null) {
  14215. result.set(key, overlay);
  14216. }
  14217. });
  14218. }).next(() => result);
  14219. }
  14220. saveOverlays(transaction, largestBatchId, overlays) {
  14221. overlays.forEach((_, mutation) => {
  14222. this.saveOverlay(transaction, largestBatchId, mutation);
  14223. });
  14224. return PersistencePromise.resolve();
  14225. }
  14226. removeOverlaysForBatchId(transaction, documentKeys, batchId) {
  14227. const keys = this.overlayByBatchId.get(batchId);
  14228. if (keys !== undefined) {
  14229. keys.forEach(key => (this.overlays = this.overlays.remove(key)));
  14230. this.overlayByBatchId.delete(batchId);
  14231. }
  14232. return PersistencePromise.resolve();
  14233. }
  14234. getOverlaysForCollection(transaction, collection, sinceBatchId) {
  14235. const result = newOverlayMap();
  14236. const immediateChildrenPathLength = collection.length + 1;
  14237. const prefix = new DocumentKey(collection.child(''));
  14238. const iter = this.overlays.getIteratorFrom(prefix);
  14239. while (iter.hasNext()) {
  14240. const entry = iter.getNext();
  14241. const overlay = entry.value;
  14242. const key = overlay.getKey();
  14243. if (!collection.isPrefixOf(key.path)) {
  14244. break;
  14245. }
  14246. // Documents from sub-collections
  14247. if (key.path.length !== immediateChildrenPathLength) {
  14248. continue;
  14249. }
  14250. if (overlay.largestBatchId > sinceBatchId) {
  14251. result.set(overlay.getKey(), overlay);
  14252. }
  14253. }
  14254. return PersistencePromise.resolve(result);
  14255. }
  14256. getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
  14257. let batchIdToOverlays = new SortedMap((key1, key2) => key1 - key2);
  14258. const iter = this.overlays.getIterator();
  14259. while (iter.hasNext()) {
  14260. const entry = iter.getNext();
  14261. const overlay = entry.value;
  14262. const key = overlay.getKey();
  14263. if (key.getCollectionGroup() !== collectionGroup) {
  14264. continue;
  14265. }
  14266. if (overlay.largestBatchId > sinceBatchId) {
  14267. let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
  14268. if (overlaysForBatchId === null) {
  14269. overlaysForBatchId = newOverlayMap();
  14270. batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
  14271. }
  14272. overlaysForBatchId.set(overlay.getKey(), overlay);
  14273. }
  14274. }
  14275. const result = newOverlayMap();
  14276. const batchIter = batchIdToOverlays.getIterator();
  14277. while (batchIter.hasNext()) {
  14278. const entry = batchIter.getNext();
  14279. const overlays = entry.value;
  14280. overlays.forEach((key, overlay) => result.set(key, overlay));
  14281. if (result.size() >= count) {
  14282. break;
  14283. }
  14284. }
  14285. return PersistencePromise.resolve(result);
  14286. }
  14287. saveOverlay(transaction, largestBatchId, mutation) {
  14288. // Remove the association of the overlay to its batch id.
  14289. const existing = this.overlays.get(mutation.key);
  14290. if (existing !== null) {
  14291. const newSet = this.overlayByBatchId
  14292. .get(existing.largestBatchId)
  14293. .delete(mutation.key);
  14294. this.overlayByBatchId.set(existing.largestBatchId, newSet);
  14295. }
  14296. this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
  14297. // Create the association of this overlay to the given largestBatchId.
  14298. let batch = this.overlayByBatchId.get(largestBatchId);
  14299. if (batch === undefined) {
  14300. batch = documentKeySet();
  14301. this.overlayByBatchId.set(largestBatchId, batch);
  14302. }
  14303. this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
  14304. }
  14305. }
  14306. /**
  14307. * @license
  14308. * Copyright 2017 Google LLC
  14309. *
  14310. * Licensed under the Apache License, Version 2.0 (the "License");
  14311. * you may not use this file except in compliance with the License.
  14312. * You may obtain a copy of the License at
  14313. *
  14314. * http://www.apache.org/licenses/LICENSE-2.0
  14315. *
  14316. * Unless required by applicable law or agreed to in writing, software
  14317. * distributed under the License is distributed on an "AS IS" BASIS,
  14318. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14319. * See the License for the specific language governing permissions and
  14320. * limitations under the License.
  14321. */
  14322. /**
  14323. * A collection of references to a document from some kind of numbered entity
  14324. * (either a target ID or batch ID). As references are added to or removed from
  14325. * the set corresponding events are emitted to a registered garbage collector.
  14326. *
  14327. * Each reference is represented by a DocumentReference object. Each of them
  14328. * contains enough information to uniquely identify the reference. They are all
  14329. * stored primarily in a set sorted by key. A document is considered garbage if
  14330. * there's no references in that set (this can be efficiently checked thanks to
  14331. * sorting by key).
  14332. *
  14333. * ReferenceSet also keeps a secondary set that contains references sorted by
  14334. * IDs. This one is used to efficiently implement removal of all references by
  14335. * some target ID.
  14336. */
  14337. class ReferenceSet {
  14338. constructor() {
  14339. // A set of outstanding references to a document sorted by key.
  14340. this.refsByKey = new SortedSet(DocReference.compareByKey);
  14341. // A set of outstanding references to a document sorted by target id.
  14342. this.refsByTarget = new SortedSet(DocReference.compareByTargetId);
  14343. }
  14344. /** Returns true if the reference set contains no references. */
  14345. isEmpty() {
  14346. return this.refsByKey.isEmpty();
  14347. }
  14348. /** Adds a reference to the given document key for the given ID. */
  14349. addReference(key, id) {
  14350. const ref = new DocReference(key, id);
  14351. this.refsByKey = this.refsByKey.add(ref);
  14352. this.refsByTarget = this.refsByTarget.add(ref);
  14353. }
  14354. /** Add references to the given document keys for the given ID. */
  14355. addReferences(keys, id) {
  14356. keys.forEach(key => this.addReference(key, id));
  14357. }
  14358. /**
  14359. * Removes a reference to the given document key for the given
  14360. * ID.
  14361. */
  14362. removeReference(key, id) {
  14363. this.removeRef(new DocReference(key, id));
  14364. }
  14365. removeReferences(keys, id) {
  14366. keys.forEach(key => this.removeReference(key, id));
  14367. }
  14368. /**
  14369. * Clears all references with a given ID. Calls removeRef() for each key
  14370. * removed.
  14371. */
  14372. removeReferencesForId(id) {
  14373. const emptyKey = new DocumentKey(new ResourcePath([]));
  14374. const startRef = new DocReference(emptyKey, id);
  14375. const endRef = new DocReference(emptyKey, id + 1);
  14376. const keys = [];
  14377. this.refsByTarget.forEachInRange([startRef, endRef], ref => {
  14378. this.removeRef(ref);
  14379. keys.push(ref.key);
  14380. });
  14381. return keys;
  14382. }
  14383. removeAllReferences() {
  14384. this.refsByKey.forEach(ref => this.removeRef(ref));
  14385. }
  14386. removeRef(ref) {
  14387. this.refsByKey = this.refsByKey.delete(ref);
  14388. this.refsByTarget = this.refsByTarget.delete(ref);
  14389. }
  14390. referencesForId(id) {
  14391. const emptyKey = new DocumentKey(new ResourcePath([]));
  14392. const startRef = new DocReference(emptyKey, id);
  14393. const endRef = new DocReference(emptyKey, id + 1);
  14394. let keys = documentKeySet();
  14395. this.refsByTarget.forEachInRange([startRef, endRef], ref => {
  14396. keys = keys.add(ref.key);
  14397. });
  14398. return keys;
  14399. }
  14400. containsKey(key) {
  14401. const ref = new DocReference(key, 0);
  14402. const firstRef = this.refsByKey.firstAfterOrEqual(ref);
  14403. return firstRef !== null && key.isEqual(firstRef.key);
  14404. }
  14405. }
  14406. class DocReference {
  14407. constructor(key, targetOrBatchId) {
  14408. this.key = key;
  14409. this.targetOrBatchId = targetOrBatchId;
  14410. }
  14411. /** Compare by key then by ID */
  14412. static compareByKey(left, right) {
  14413. return (DocumentKey.comparator(left.key, right.key) ||
  14414. primitiveComparator(left.targetOrBatchId, right.targetOrBatchId));
  14415. }
  14416. /** Compare by ID then by key */
  14417. static compareByTargetId(left, right) {
  14418. return (primitiveComparator(left.targetOrBatchId, right.targetOrBatchId) ||
  14419. DocumentKey.comparator(left.key, right.key));
  14420. }
  14421. }
  14422. /**
  14423. * @license
  14424. * Copyright 2017 Google LLC
  14425. *
  14426. * Licensed under the Apache License, Version 2.0 (the "License");
  14427. * you may not use this file except in compliance with the License.
  14428. * You may obtain a copy of the License at
  14429. *
  14430. * http://www.apache.org/licenses/LICENSE-2.0
  14431. *
  14432. * Unless required by applicable law or agreed to in writing, software
  14433. * distributed under the License is distributed on an "AS IS" BASIS,
  14434. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14435. * See the License for the specific language governing permissions and
  14436. * limitations under the License.
  14437. */
  14438. class MemoryMutationQueue {
  14439. constructor(indexManager, referenceDelegate) {
  14440. this.indexManager = indexManager;
  14441. this.referenceDelegate = referenceDelegate;
  14442. /**
  14443. * The set of all mutations that have been sent but not yet been applied to
  14444. * the backend.
  14445. */
  14446. this.mutationQueue = [];
  14447. /** Next value to use when assigning sequential IDs to each mutation batch. */
  14448. this.nextBatchId = 1;
  14449. /** An ordered mapping between documents and the mutations batch IDs. */
  14450. this.batchesByDocumentKey = new SortedSet(DocReference.compareByKey);
  14451. }
  14452. checkEmpty(transaction) {
  14453. return PersistencePromise.resolve(this.mutationQueue.length === 0);
  14454. }
  14455. addMutationBatch(transaction, localWriteTime, baseMutations, mutations) {
  14456. const batchId = this.nextBatchId;
  14457. this.nextBatchId++;
  14458. if (this.mutationQueue.length > 0) {
  14459. this.mutationQueue[this.mutationQueue.length - 1];
  14460. }
  14461. const batch = new MutationBatch(batchId, localWriteTime, baseMutations, mutations);
  14462. this.mutationQueue.push(batch);
  14463. // Track references by document key and index collection parents.
  14464. for (const mutation of mutations) {
  14465. this.batchesByDocumentKey = this.batchesByDocumentKey.add(new DocReference(mutation.key, batchId));
  14466. this.indexManager.addToCollectionParentIndex(transaction, mutation.key.path.popLast());
  14467. }
  14468. return PersistencePromise.resolve(batch);
  14469. }
  14470. lookupMutationBatch(transaction, batchId) {
  14471. return PersistencePromise.resolve(this.findMutationBatch(batchId));
  14472. }
  14473. getNextMutationBatchAfterBatchId(transaction, batchId) {
  14474. const nextBatchId = batchId + 1;
  14475. // The requested batchId may still be out of range so normalize it to the
  14476. // start of the queue.
  14477. const rawIndex = this.indexOfBatchId(nextBatchId);
  14478. const index = rawIndex < 0 ? 0 : rawIndex;
  14479. return PersistencePromise.resolve(this.mutationQueue.length > index ? this.mutationQueue[index] : null);
  14480. }
  14481. getHighestUnacknowledgedBatchId() {
  14482. return PersistencePromise.resolve(this.mutationQueue.length === 0 ? BATCHID_UNKNOWN : this.nextBatchId - 1);
  14483. }
  14484. getAllMutationBatches(transaction) {
  14485. return PersistencePromise.resolve(this.mutationQueue.slice());
  14486. }
  14487. getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
  14488. const start = new DocReference(documentKey, 0);
  14489. const end = new DocReference(documentKey, Number.POSITIVE_INFINITY);
  14490. const result = [];
  14491. this.batchesByDocumentKey.forEachInRange([start, end], ref => {
  14492. const batch = this.findMutationBatch(ref.targetOrBatchId);
  14493. result.push(batch);
  14494. });
  14495. return PersistencePromise.resolve(result);
  14496. }
  14497. getAllMutationBatchesAffectingDocumentKeys(transaction, documentKeys) {
  14498. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  14499. documentKeys.forEach(documentKey => {
  14500. const start = new DocReference(documentKey, 0);
  14501. const end = new DocReference(documentKey, Number.POSITIVE_INFINITY);
  14502. this.batchesByDocumentKey.forEachInRange([start, end], ref => {
  14503. uniqueBatchIDs = uniqueBatchIDs.add(ref.targetOrBatchId);
  14504. });
  14505. });
  14506. return PersistencePromise.resolve(this.findMutationBatches(uniqueBatchIDs));
  14507. }
  14508. getAllMutationBatchesAffectingQuery(transaction, query) {
  14509. // Use the query path as a prefix for testing if a document matches the
  14510. // query.
  14511. const prefix = query.path;
  14512. const immediateChildrenPathLength = prefix.length + 1;
  14513. // Construct a document reference for actually scanning the index. Unlike
  14514. // the prefix the document key in this reference must have an even number of
  14515. // segments. The empty segment can be used a suffix of the query path
  14516. // because it precedes all other segments in an ordered traversal.
  14517. let startPath = prefix;
  14518. if (!DocumentKey.isDocumentKey(startPath)) {
  14519. startPath = startPath.child('');
  14520. }
  14521. const start = new DocReference(new DocumentKey(startPath), 0);
  14522. // Find unique batchIDs referenced by all documents potentially matching the
  14523. // query.
  14524. let uniqueBatchIDs = new SortedSet(primitiveComparator);
  14525. this.batchesByDocumentKey.forEachWhile(ref => {
  14526. const rowKeyPath = ref.key.path;
  14527. if (!prefix.isPrefixOf(rowKeyPath)) {
  14528. return false;
  14529. }
  14530. else {
  14531. // Rows with document keys more than one segment longer than the query
  14532. // path can't be matches. For example, a query on 'rooms' can't match
  14533. // the document /rooms/abc/messages/xyx.
  14534. // TODO(mcg): we'll need a different scanner when we implement
  14535. // ancestor queries.
  14536. if (rowKeyPath.length === immediateChildrenPathLength) {
  14537. uniqueBatchIDs = uniqueBatchIDs.add(ref.targetOrBatchId);
  14538. }
  14539. return true;
  14540. }
  14541. }, start);
  14542. return PersistencePromise.resolve(this.findMutationBatches(uniqueBatchIDs));
  14543. }
  14544. findMutationBatches(batchIDs) {
  14545. // Construct an array of matching batches, sorted by batchID to ensure that
  14546. // multiple mutations affecting the same document key are applied in order.
  14547. const result = [];
  14548. batchIDs.forEach(batchId => {
  14549. const batch = this.findMutationBatch(batchId);
  14550. if (batch !== null) {
  14551. result.push(batch);
  14552. }
  14553. });
  14554. return result;
  14555. }
  14556. removeMutationBatch(transaction, batch) {
  14557. // Find the position of the first batch for removal.
  14558. const batchIndex = this.indexOfExistingBatchId(batch.batchId, 'removed');
  14559. hardAssert(batchIndex === 0);
  14560. this.mutationQueue.shift();
  14561. let references = this.batchesByDocumentKey;
  14562. return PersistencePromise.forEach(batch.mutations, (mutation) => {
  14563. const ref = new DocReference(mutation.key, batch.batchId);
  14564. references = references.delete(ref);
  14565. return this.referenceDelegate.markPotentiallyOrphaned(transaction, mutation.key);
  14566. }).next(() => {
  14567. this.batchesByDocumentKey = references;
  14568. });
  14569. }
  14570. removeCachedMutationKeys(batchId) {
  14571. // No-op since the memory mutation queue does not maintain a separate cache.
  14572. }
  14573. containsKey(txn, key) {
  14574. const ref = new DocReference(key, 0);
  14575. const firstRef = this.batchesByDocumentKey.firstAfterOrEqual(ref);
  14576. return PersistencePromise.resolve(key.isEqual(firstRef && firstRef.key));
  14577. }
  14578. performConsistencyCheck(txn) {
  14579. if (this.mutationQueue.length === 0) ;
  14580. return PersistencePromise.resolve();
  14581. }
  14582. /**
  14583. * Finds the index of the given batchId in the mutation queue and asserts that
  14584. * the resulting index is within the bounds of the queue.
  14585. *
  14586. * @param batchId - The batchId to search for
  14587. * @param action - A description of what the caller is doing, phrased in passive
  14588. * form (e.g. "acknowledged" in a routine that acknowledges batches).
  14589. */
  14590. indexOfExistingBatchId(batchId, action) {
  14591. const index = this.indexOfBatchId(batchId);
  14592. return index;
  14593. }
  14594. /**
  14595. * Finds the index of the given batchId in the mutation queue. This operation
  14596. * is O(1).
  14597. *
  14598. * @returns The computed index of the batch with the given batchId, based on
  14599. * the state of the queue. Note this index can be negative if the requested
  14600. * batchId has already been remvoed from the queue or past the end of the
  14601. * queue if the batchId is larger than the last added batch.
  14602. */
  14603. indexOfBatchId(batchId) {
  14604. if (this.mutationQueue.length === 0) {
  14605. // As an index this is past the end of the queue
  14606. return 0;
  14607. }
  14608. // Examine the front of the queue to figure out the difference between the
  14609. // batchId and indexes in the array. Note that since the queue is ordered
  14610. // by batchId, if the first batch has a larger batchId then the requested
  14611. // batchId doesn't exist in the queue.
  14612. const firstBatchId = this.mutationQueue[0].batchId;
  14613. return batchId - firstBatchId;
  14614. }
  14615. /**
  14616. * A version of lookupMutationBatch that doesn't return a promise, this makes
  14617. * other functions that uses this code easier to read and more efficent.
  14618. */
  14619. findMutationBatch(batchId) {
  14620. const index = this.indexOfBatchId(batchId);
  14621. if (index < 0 || index >= this.mutationQueue.length) {
  14622. return null;
  14623. }
  14624. const batch = this.mutationQueue[index];
  14625. return batch;
  14626. }
  14627. }
  14628. /**
  14629. * @license
  14630. * Copyright 2017 Google LLC
  14631. *
  14632. * Licensed under the Apache License, Version 2.0 (the "License");
  14633. * you may not use this file except in compliance with the License.
  14634. * You may obtain a copy of the License at
  14635. *
  14636. * http://www.apache.org/licenses/LICENSE-2.0
  14637. *
  14638. * Unless required by applicable law or agreed to in writing, software
  14639. * distributed under the License is distributed on an "AS IS" BASIS,
  14640. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14641. * See the License for the specific language governing permissions and
  14642. * limitations under the License.
  14643. */
  14644. function documentEntryMap() {
  14645. return new SortedMap(DocumentKey.comparator);
  14646. }
  14647. /**
  14648. * The memory-only RemoteDocumentCache for IndexedDb. To construct, invoke
  14649. * `newMemoryRemoteDocumentCache()`.
  14650. */
  14651. class MemoryRemoteDocumentCacheImpl {
  14652. /**
  14653. * @param sizer - Used to assess the size of a document. For eager GC, this is
  14654. * expected to just return 0 to avoid unnecessarily doing the work of
  14655. * calculating the size.
  14656. */
  14657. constructor(sizer) {
  14658. this.sizer = sizer;
  14659. /** Underlying cache of documents and their read times. */
  14660. this.docs = documentEntryMap();
  14661. /** Size of all cached documents. */
  14662. this.size = 0;
  14663. }
  14664. setIndexManager(indexManager) {
  14665. this.indexManager = indexManager;
  14666. }
  14667. /**
  14668. * Adds the supplied entry to the cache and updates the cache size as appropriate.
  14669. *
  14670. * All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer
  14671. * returned by `newChangeBuffer()`.
  14672. */
  14673. addEntry(transaction, doc) {
  14674. const key = doc.key;
  14675. const entry = this.docs.get(key);
  14676. const previousSize = entry ? entry.size : 0;
  14677. const currentSize = this.sizer(doc);
  14678. this.docs = this.docs.insert(key, {
  14679. document: doc.mutableCopy(),
  14680. size: currentSize
  14681. });
  14682. this.size += currentSize - previousSize;
  14683. return this.indexManager.addToCollectionParentIndex(transaction, key.path.popLast());
  14684. }
  14685. /**
  14686. * Removes the specified entry from the cache and updates the cache size as appropriate.
  14687. *
  14688. * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
  14689. * returned by `newChangeBuffer()`.
  14690. */
  14691. removeEntry(documentKey) {
  14692. const entry = this.docs.get(documentKey);
  14693. if (entry) {
  14694. this.docs = this.docs.remove(documentKey);
  14695. this.size -= entry.size;
  14696. }
  14697. }
  14698. getEntry(transaction, documentKey) {
  14699. const entry = this.docs.get(documentKey);
  14700. return PersistencePromise.resolve(entry
  14701. ? entry.document.mutableCopy()
  14702. : MutableDocument.newInvalidDocument(documentKey));
  14703. }
  14704. getEntries(transaction, documentKeys) {
  14705. let results = mutableDocumentMap();
  14706. documentKeys.forEach(documentKey => {
  14707. const entry = this.docs.get(documentKey);
  14708. results = results.insert(documentKey, entry
  14709. ? entry.document.mutableCopy()
  14710. : MutableDocument.newInvalidDocument(documentKey));
  14711. });
  14712. return PersistencePromise.resolve(results);
  14713. }
  14714. getDocumentsMatchingQuery(transaction, query, offset, mutatedDocs) {
  14715. let results = mutableDocumentMap();
  14716. // Documents are ordered by key, so we can use a prefix scan to narrow down
  14717. // the documents we need to match the query against.
  14718. const collectionPath = query.path;
  14719. const prefix = new DocumentKey(collectionPath.child(''));
  14720. const iterator = this.docs.getIteratorFrom(prefix);
  14721. while (iterator.hasNext()) {
  14722. const { key, value: { document } } = iterator.getNext();
  14723. if (!collectionPath.isPrefixOf(key.path)) {
  14724. break;
  14725. }
  14726. if (key.path.length > collectionPath.length + 1) {
  14727. // Exclude entries from subcollections.
  14728. continue;
  14729. }
  14730. if (indexOffsetComparator(newIndexOffsetFromDocument(document), offset) <= 0) {
  14731. // The document sorts before the offset.
  14732. continue;
  14733. }
  14734. if (!mutatedDocs.has(document.key) && !queryMatches(query, document)) {
  14735. // The document cannot possibly match the query.
  14736. continue;
  14737. }
  14738. results = results.insert(document.key, document.mutableCopy());
  14739. }
  14740. return PersistencePromise.resolve(results);
  14741. }
  14742. getAllFromCollectionGroup(transaction, collectionGroup, offset, limti) {
  14743. // This method should only be called from the IndexBackfiller if persistence
  14744. // is enabled.
  14745. fail();
  14746. }
  14747. forEachDocumentKey(transaction, f) {
  14748. return PersistencePromise.forEach(this.docs, (key) => f(key));
  14749. }
  14750. newChangeBuffer(options) {
  14751. // `trackRemovals` is ignores since the MemoryRemoteDocumentCache keeps
  14752. // a separate changelog and does not need special handling for removals.
  14753. return new MemoryRemoteDocumentChangeBuffer(this);
  14754. }
  14755. getSize(txn) {
  14756. return PersistencePromise.resolve(this.size);
  14757. }
  14758. }
  14759. /**
  14760. * Creates a new memory-only RemoteDocumentCache.
  14761. *
  14762. * @param sizer - Used to assess the size of a document. For eager GC, this is
  14763. * expected to just return 0 to avoid unnecessarily doing the work of
  14764. * calculating the size.
  14765. */
  14766. function newMemoryRemoteDocumentCache(sizer) {
  14767. return new MemoryRemoteDocumentCacheImpl(sizer);
  14768. }
  14769. /**
  14770. * Handles the details of adding and updating documents in the MemoryRemoteDocumentCache.
  14771. */
  14772. class MemoryRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
  14773. constructor(documentCache) {
  14774. super();
  14775. this.documentCache = documentCache;
  14776. }
  14777. applyChanges(transaction) {
  14778. const promises = [];
  14779. this.changes.forEach((key, doc) => {
  14780. if (doc.isValidDocument()) {
  14781. promises.push(this.documentCache.addEntry(transaction, doc));
  14782. }
  14783. else {
  14784. this.documentCache.removeEntry(key);
  14785. }
  14786. });
  14787. return PersistencePromise.waitFor(promises);
  14788. }
  14789. getFromCache(transaction, documentKey) {
  14790. return this.documentCache.getEntry(transaction, documentKey);
  14791. }
  14792. getAllFromCache(transaction, documentKeys) {
  14793. return this.documentCache.getEntries(transaction, documentKeys);
  14794. }
  14795. }
  14796. /**
  14797. * @license
  14798. * Copyright 2017 Google LLC
  14799. *
  14800. * Licensed under the Apache License, Version 2.0 (the "License");
  14801. * you may not use this file except in compliance with the License.
  14802. * You may obtain a copy of the License at
  14803. *
  14804. * http://www.apache.org/licenses/LICENSE-2.0
  14805. *
  14806. * Unless required by applicable law or agreed to in writing, software
  14807. * distributed under the License is distributed on an "AS IS" BASIS,
  14808. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14809. * See the License for the specific language governing permissions and
  14810. * limitations under the License.
  14811. */
  14812. class MemoryTargetCache {
  14813. constructor(persistence) {
  14814. this.persistence = persistence;
  14815. /**
  14816. * Maps a target to the data about that target
  14817. */
  14818. this.targets = new ObjectMap(t => canonifyTarget(t), targetEquals);
  14819. /** The last received snapshot version. */
  14820. this.lastRemoteSnapshotVersion = SnapshotVersion.min();
  14821. /** The highest numbered target ID encountered. */
  14822. this.highestTargetId = 0;
  14823. /** The highest sequence number encountered. */
  14824. this.highestSequenceNumber = 0;
  14825. /**
  14826. * A ordered bidirectional mapping between documents and the remote target
  14827. * IDs.
  14828. */
  14829. this.references = new ReferenceSet();
  14830. this.targetCount = 0;
  14831. this.targetIdGenerator = TargetIdGenerator.forTargetCache();
  14832. }
  14833. forEachTarget(txn, f) {
  14834. this.targets.forEach((_, targetData) => f(targetData));
  14835. return PersistencePromise.resolve();
  14836. }
  14837. getLastRemoteSnapshotVersion(transaction) {
  14838. return PersistencePromise.resolve(this.lastRemoteSnapshotVersion);
  14839. }
  14840. getHighestSequenceNumber(transaction) {
  14841. return PersistencePromise.resolve(this.highestSequenceNumber);
  14842. }
  14843. allocateTargetId(transaction) {
  14844. this.highestTargetId = this.targetIdGenerator.next();
  14845. return PersistencePromise.resolve(this.highestTargetId);
  14846. }
  14847. setTargetsMetadata(transaction, highestListenSequenceNumber, lastRemoteSnapshotVersion) {
  14848. if (lastRemoteSnapshotVersion) {
  14849. this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;
  14850. }
  14851. if (highestListenSequenceNumber > this.highestSequenceNumber) {
  14852. this.highestSequenceNumber = highestListenSequenceNumber;
  14853. }
  14854. return PersistencePromise.resolve();
  14855. }
  14856. saveTargetData(targetData) {
  14857. this.targets.set(targetData.target, targetData);
  14858. const targetId = targetData.targetId;
  14859. if (targetId > this.highestTargetId) {
  14860. this.targetIdGenerator = new TargetIdGenerator(targetId);
  14861. this.highestTargetId = targetId;
  14862. }
  14863. if (targetData.sequenceNumber > this.highestSequenceNumber) {
  14864. this.highestSequenceNumber = targetData.sequenceNumber;
  14865. }
  14866. }
  14867. addTargetData(transaction, targetData) {
  14868. this.saveTargetData(targetData);
  14869. this.targetCount += 1;
  14870. return PersistencePromise.resolve();
  14871. }
  14872. updateTargetData(transaction, targetData) {
  14873. this.saveTargetData(targetData);
  14874. return PersistencePromise.resolve();
  14875. }
  14876. removeTargetData(transaction, targetData) {
  14877. this.targets.delete(targetData.target);
  14878. this.references.removeReferencesForId(targetData.targetId);
  14879. this.targetCount -= 1;
  14880. return PersistencePromise.resolve();
  14881. }
  14882. removeTargets(transaction, upperBound, activeTargetIds) {
  14883. let count = 0;
  14884. const removals = [];
  14885. this.targets.forEach((key, targetData) => {
  14886. if (targetData.sequenceNumber <= upperBound &&
  14887. activeTargetIds.get(targetData.targetId) === null) {
  14888. this.targets.delete(key);
  14889. removals.push(this.removeMatchingKeysForTargetId(transaction, targetData.targetId));
  14890. count++;
  14891. }
  14892. });
  14893. return PersistencePromise.waitFor(removals).next(() => count);
  14894. }
  14895. getTargetCount(transaction) {
  14896. return PersistencePromise.resolve(this.targetCount);
  14897. }
  14898. getTargetData(transaction, target) {
  14899. const targetData = this.targets.get(target) || null;
  14900. return PersistencePromise.resolve(targetData);
  14901. }
  14902. addMatchingKeys(txn, keys, targetId) {
  14903. this.references.addReferences(keys, targetId);
  14904. return PersistencePromise.resolve();
  14905. }
  14906. removeMatchingKeys(txn, keys, targetId) {
  14907. this.references.removeReferences(keys, targetId);
  14908. const referenceDelegate = this.persistence.referenceDelegate;
  14909. const promises = [];
  14910. if (referenceDelegate) {
  14911. keys.forEach(key => {
  14912. promises.push(referenceDelegate.markPotentiallyOrphaned(txn, key));
  14913. });
  14914. }
  14915. return PersistencePromise.waitFor(promises);
  14916. }
  14917. removeMatchingKeysForTargetId(txn, targetId) {
  14918. this.references.removeReferencesForId(targetId);
  14919. return PersistencePromise.resolve();
  14920. }
  14921. getMatchingKeysForTargetId(txn, targetId) {
  14922. const matchingKeys = this.references.referencesForId(targetId);
  14923. return PersistencePromise.resolve(matchingKeys);
  14924. }
  14925. containsKey(txn, key) {
  14926. return PersistencePromise.resolve(this.references.containsKey(key));
  14927. }
  14928. }
  14929. /**
  14930. * @license
  14931. * Copyright 2017 Google LLC
  14932. *
  14933. * Licensed under the Apache License, Version 2.0 (the "License");
  14934. * you may not use this file except in compliance with the License.
  14935. * You may obtain a copy of the License at
  14936. *
  14937. * http://www.apache.org/licenses/LICENSE-2.0
  14938. *
  14939. * Unless required by applicable law or agreed to in writing, software
  14940. * distributed under the License is distributed on an "AS IS" BASIS,
  14941. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14942. * See the License for the specific language governing permissions and
  14943. * limitations under the License.
  14944. */
  14945. const LOG_TAG$d = 'MemoryPersistence';
  14946. /**
  14947. * A memory-backed instance of Persistence. Data is stored only in RAM and
  14948. * not persisted across sessions.
  14949. */
  14950. class MemoryPersistence {
  14951. /**
  14952. * The constructor accepts a factory for creating a reference delegate. This
  14953. * allows both the delegate and this instance to have strong references to
  14954. * each other without having nullable fields that would then need to be
  14955. * checked or asserted on every access.
  14956. */
  14957. constructor(referenceDelegateFactory, serializer) {
  14958. this.mutationQueues = {};
  14959. this.overlays = {};
  14960. this.listenSequence = new ListenSequence(0);
  14961. this._started = false;
  14962. this._started = true;
  14963. this.referenceDelegate = referenceDelegateFactory(this);
  14964. this.targetCache = new MemoryTargetCache(this);
  14965. const sizer = (doc) => this.referenceDelegate.documentSize(doc);
  14966. this.indexManager = new MemoryIndexManager();
  14967. this.remoteDocumentCache = newMemoryRemoteDocumentCache(sizer);
  14968. this.serializer = new LocalSerializer(serializer);
  14969. this.bundleCache = new MemoryBundleCache(this.serializer);
  14970. }
  14971. start() {
  14972. return Promise.resolve();
  14973. }
  14974. shutdown() {
  14975. // No durable state to ensure is closed on shutdown.
  14976. this._started = false;
  14977. return Promise.resolve();
  14978. }
  14979. get started() {
  14980. return this._started;
  14981. }
  14982. setDatabaseDeletedListener() {
  14983. // No op.
  14984. }
  14985. setNetworkEnabled() {
  14986. // No op.
  14987. }
  14988. getIndexManager(user) {
  14989. // We do not currently support indices for memory persistence, so we can
  14990. // return the same shared instance of the memory index manager.
  14991. return this.indexManager;
  14992. }
  14993. getDocumentOverlayCache(user) {
  14994. let overlay = this.overlays[user.toKey()];
  14995. if (!overlay) {
  14996. overlay = new MemoryDocumentOverlayCache();
  14997. this.overlays[user.toKey()] = overlay;
  14998. }
  14999. return overlay;
  15000. }
  15001. getMutationQueue(user, indexManager) {
  15002. let queue = this.mutationQueues[user.toKey()];
  15003. if (!queue) {
  15004. queue = new MemoryMutationQueue(indexManager, this.referenceDelegate);
  15005. this.mutationQueues[user.toKey()] = queue;
  15006. }
  15007. return queue;
  15008. }
  15009. getTargetCache() {
  15010. return this.targetCache;
  15011. }
  15012. getRemoteDocumentCache() {
  15013. return this.remoteDocumentCache;
  15014. }
  15015. getBundleCache() {
  15016. return this.bundleCache;
  15017. }
  15018. runTransaction(action, mode, transactionOperation) {
  15019. logDebug(LOG_TAG$d, 'Starting transaction:', action);
  15020. const txn = new MemoryTransaction(this.listenSequence.next());
  15021. this.referenceDelegate.onTransactionStarted();
  15022. return transactionOperation(txn)
  15023. .next(result => {
  15024. return this.referenceDelegate
  15025. .onTransactionCommitted(txn)
  15026. .next(() => result);
  15027. })
  15028. .toPromise()
  15029. .then(result => {
  15030. txn.raiseOnCommittedEvent();
  15031. return result;
  15032. });
  15033. }
  15034. mutationQueuesContainKey(transaction, key) {
  15035. return PersistencePromise.or(Object.values(this.mutationQueues).map(queue => () => queue.containsKey(transaction, key)));
  15036. }
  15037. }
  15038. /**
  15039. * Memory persistence is not actually transactional, but future implementations
  15040. * may have transaction-scoped state.
  15041. */
  15042. class MemoryTransaction extends PersistenceTransaction {
  15043. constructor(currentSequenceNumber) {
  15044. super();
  15045. this.currentSequenceNumber = currentSequenceNumber;
  15046. }
  15047. }
  15048. class MemoryEagerDelegate {
  15049. constructor(persistence) {
  15050. this.persistence = persistence;
  15051. /** Tracks all documents that are active in Query views. */
  15052. this.localViewReferences = new ReferenceSet();
  15053. /** The list of documents that are potentially GCed after each transaction. */
  15054. this._orphanedDocuments = null;
  15055. }
  15056. static factory(persistence) {
  15057. return new MemoryEagerDelegate(persistence);
  15058. }
  15059. get orphanedDocuments() {
  15060. if (!this._orphanedDocuments) {
  15061. throw fail();
  15062. }
  15063. else {
  15064. return this._orphanedDocuments;
  15065. }
  15066. }
  15067. addReference(txn, targetId, key) {
  15068. this.localViewReferences.addReference(key, targetId);
  15069. this.orphanedDocuments.delete(key.toString());
  15070. return PersistencePromise.resolve();
  15071. }
  15072. removeReference(txn, targetId, key) {
  15073. this.localViewReferences.removeReference(key, targetId);
  15074. this.orphanedDocuments.add(key.toString());
  15075. return PersistencePromise.resolve();
  15076. }
  15077. markPotentiallyOrphaned(txn, key) {
  15078. this.orphanedDocuments.add(key.toString());
  15079. return PersistencePromise.resolve();
  15080. }
  15081. removeTarget(txn, targetData) {
  15082. const orphaned = this.localViewReferences.removeReferencesForId(targetData.targetId);
  15083. orphaned.forEach(key => this.orphanedDocuments.add(key.toString()));
  15084. const cache = this.persistence.getTargetCache();
  15085. return cache
  15086. .getMatchingKeysForTargetId(txn, targetData.targetId)
  15087. .next(keys => {
  15088. keys.forEach(key => this.orphanedDocuments.add(key.toString()));
  15089. })
  15090. .next(() => cache.removeTargetData(txn, targetData));
  15091. }
  15092. onTransactionStarted() {
  15093. this._orphanedDocuments = new Set();
  15094. }
  15095. onTransactionCommitted(txn) {
  15096. // Remove newly orphaned documents.
  15097. const cache = this.persistence.getRemoteDocumentCache();
  15098. const changeBuffer = cache.newChangeBuffer();
  15099. return PersistencePromise.forEach(this.orphanedDocuments, (path) => {
  15100. const key = DocumentKey.fromPath(path);
  15101. return this.isReferenced(txn, key).next(isReferenced => {
  15102. if (!isReferenced) {
  15103. changeBuffer.removeEntry(key, SnapshotVersion.min());
  15104. }
  15105. });
  15106. }).next(() => {
  15107. this._orphanedDocuments = null;
  15108. return changeBuffer.apply(txn);
  15109. });
  15110. }
  15111. updateLimboDocument(txn, key) {
  15112. return this.isReferenced(txn, key).next(isReferenced => {
  15113. if (isReferenced) {
  15114. this.orphanedDocuments.delete(key.toString());
  15115. }
  15116. else {
  15117. this.orphanedDocuments.add(key.toString());
  15118. }
  15119. });
  15120. }
  15121. documentSize(doc) {
  15122. // For eager GC, we don't care about the document size, there are no size thresholds.
  15123. return 0;
  15124. }
  15125. isReferenced(txn, key) {
  15126. return PersistencePromise.or([
  15127. () => PersistencePromise.resolve(this.localViewReferences.containsKey(key)),
  15128. () => this.persistence.getTargetCache().containsKey(txn, key),
  15129. () => this.persistence.mutationQueuesContainKey(txn, key)
  15130. ]);
  15131. }
  15132. }
  15133. class MemoryLruDelegate {
  15134. constructor(persistence, lruParams) {
  15135. this.persistence = persistence;
  15136. this.orphanedSequenceNumbers = new ObjectMap(k => encodeResourcePath(k.path), (l, r) => l.isEqual(r));
  15137. this.garbageCollector = newLruGarbageCollector(this, lruParams);
  15138. }
  15139. static factory(persistence, lruParams) {
  15140. return new MemoryLruDelegate(persistence, lruParams);
  15141. }
  15142. // No-ops, present so memory persistence doesn't have to care which delegate
  15143. // it has.
  15144. onTransactionStarted() { }
  15145. onTransactionCommitted(txn) {
  15146. return PersistencePromise.resolve();
  15147. }
  15148. forEachTarget(txn, f) {
  15149. return this.persistence.getTargetCache().forEachTarget(txn, f);
  15150. }
  15151. getSequenceNumberCount(txn) {
  15152. const docCountPromise = this.orphanedDocumentCount(txn);
  15153. const targetCountPromise = this.persistence
  15154. .getTargetCache()
  15155. .getTargetCount(txn);
  15156. return targetCountPromise.next(targetCount => docCountPromise.next(docCount => targetCount + docCount));
  15157. }
  15158. orphanedDocumentCount(txn) {
  15159. let orphanedCount = 0;
  15160. return this.forEachOrphanedDocumentSequenceNumber(txn, _ => {
  15161. orphanedCount++;
  15162. }).next(() => orphanedCount);
  15163. }
  15164. forEachOrphanedDocumentSequenceNumber(txn, f) {
  15165. return PersistencePromise.forEach(this.orphanedSequenceNumbers, (key, sequenceNumber) => {
  15166. // Pass in the exact sequence number as the upper bound so we know it won't be pinned by
  15167. // being too recent.
  15168. return this.isPinned(txn, key, sequenceNumber).next(isPinned => {
  15169. if (!isPinned) {
  15170. return f(sequenceNumber);
  15171. }
  15172. else {
  15173. return PersistencePromise.resolve();
  15174. }
  15175. });
  15176. });
  15177. }
  15178. removeTargets(txn, upperBound, activeTargetIds) {
  15179. return this.persistence
  15180. .getTargetCache()
  15181. .removeTargets(txn, upperBound, activeTargetIds);
  15182. }
  15183. removeOrphanedDocuments(txn, upperBound) {
  15184. let count = 0;
  15185. const cache = this.persistence.getRemoteDocumentCache();
  15186. const changeBuffer = cache.newChangeBuffer();
  15187. const p = cache.forEachDocumentKey(txn, key => {
  15188. return this.isPinned(txn, key, upperBound).next(isPinned => {
  15189. if (!isPinned) {
  15190. count++;
  15191. changeBuffer.removeEntry(key, SnapshotVersion.min());
  15192. }
  15193. });
  15194. });
  15195. return p.next(() => changeBuffer.apply(txn)).next(() => count);
  15196. }
  15197. markPotentiallyOrphaned(txn, key) {
  15198. this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);
  15199. return PersistencePromise.resolve();
  15200. }
  15201. removeTarget(txn, targetData) {
  15202. const updated = targetData.withSequenceNumber(txn.currentSequenceNumber);
  15203. return this.persistence.getTargetCache().updateTargetData(txn, updated);
  15204. }
  15205. addReference(txn, targetId, key) {
  15206. this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);
  15207. return PersistencePromise.resolve();
  15208. }
  15209. removeReference(txn, targetId, key) {
  15210. this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);
  15211. return PersistencePromise.resolve();
  15212. }
  15213. updateLimboDocument(txn, key) {
  15214. this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);
  15215. return PersistencePromise.resolve();
  15216. }
  15217. documentSize(document) {
  15218. let documentSize = document.key.toString().length;
  15219. if (document.isFoundDocument()) {
  15220. documentSize += estimateByteSize(document.data.value);
  15221. }
  15222. return documentSize;
  15223. }
  15224. isPinned(txn, key, upperBound) {
  15225. return PersistencePromise.or([
  15226. () => this.persistence.mutationQueuesContainKey(txn, key),
  15227. () => this.persistence.getTargetCache().containsKey(txn, key),
  15228. () => {
  15229. const orphanedAt = this.orphanedSequenceNumbers.get(key);
  15230. return PersistencePromise.resolve(orphanedAt !== undefined && orphanedAt > upperBound);
  15231. }
  15232. ]);
  15233. }
  15234. getCacheSize(txn) {
  15235. return this.persistence.getRemoteDocumentCache().getSize(txn);
  15236. }
  15237. }
  15238. /**
  15239. * @license
  15240. * Copyright 2020 Google LLC
  15241. *
  15242. * Licensed under the Apache License, Version 2.0 (the "License");
  15243. * you may not use this file except in compliance with the License.
  15244. * You may obtain a copy of the License at
  15245. *
  15246. * http://www.apache.org/licenses/LICENSE-2.0
  15247. *
  15248. * Unless required by applicable law or agreed to in writing, software
  15249. * distributed under the License is distributed on an "AS IS" BASIS,
  15250. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15251. * See the License for the specific language governing permissions and
  15252. * limitations under the License.
  15253. */
  15254. /** Performs database creation and schema upgrades. */
  15255. class SchemaConverter {
  15256. constructor(serializer) {
  15257. this.serializer = serializer;
  15258. }
  15259. /**
  15260. * Performs database creation and schema upgrades.
  15261. *
  15262. * Note that in production, this method is only ever used to upgrade the schema
  15263. * to SCHEMA_VERSION. Different values of toVersion are only used for testing
  15264. * and local feature development.
  15265. */
  15266. createOrUpgrade(db, txn, fromVersion, toVersion) {
  15267. const simpleDbTransaction = new SimpleDbTransaction('createOrUpgrade', txn);
  15268. if (fromVersion < 1 && toVersion >= 1) {
  15269. createPrimaryClientStore(db);
  15270. createMutationQueue(db);
  15271. createQueryCache(db);
  15272. createLegacyRemoteDocumentCache(db);
  15273. }
  15274. // Migration 2 to populate the targetGlobal object no longer needed since
  15275. // migration 3 unconditionally clears it.
  15276. let p = PersistencePromise.resolve();
  15277. if (fromVersion < 3 && toVersion >= 3) {
  15278. // Brand new clients don't need to drop and recreate--only clients that
  15279. // potentially have corrupt data.
  15280. if (fromVersion !== 0) {
  15281. dropQueryCache(db);
  15282. createQueryCache(db);
  15283. }
  15284. p = p.next(() => writeEmptyTargetGlobalEntry(simpleDbTransaction));
  15285. }
  15286. if (fromVersion < 4 && toVersion >= 4) {
  15287. if (fromVersion !== 0) {
  15288. // Schema version 3 uses auto-generated keys to generate globally unique
  15289. // mutation batch IDs (this was previously ensured internally by the
  15290. // client). To migrate to the new schema, we have to read all mutations
  15291. // and write them back out. We preserve the existing batch IDs to guarantee
  15292. // consistency with other object stores. Any further mutation batch IDs will
  15293. // be auto-generated.
  15294. p = p.next(() => upgradeMutationBatchSchemaAndMigrateData(db, simpleDbTransaction));
  15295. }
  15296. p = p.next(() => {
  15297. createClientMetadataStore(db);
  15298. });
  15299. }
  15300. if (fromVersion < 5 && toVersion >= 5) {
  15301. p = p.next(() => this.removeAcknowledgedMutations(simpleDbTransaction));
  15302. }
  15303. if (fromVersion < 6 && toVersion >= 6) {
  15304. p = p.next(() => {
  15305. createDocumentGlobalStore(db);
  15306. return this.addDocumentGlobal(simpleDbTransaction);
  15307. });
  15308. }
  15309. if (fromVersion < 7 && toVersion >= 7) {
  15310. p = p.next(() => this.ensureSequenceNumbers(simpleDbTransaction));
  15311. }
  15312. if (fromVersion < 8 && toVersion >= 8) {
  15313. p = p.next(() => this.createCollectionParentIndex(db, simpleDbTransaction));
  15314. }
  15315. if (fromVersion < 9 && toVersion >= 9) {
  15316. p = p.next(() => {
  15317. // Multi-Tab used to manage its own changelog, but this has been moved
  15318. // to the DbRemoteDocument object store itself. Since the previous change
  15319. // log only contained transient data, we can drop its object store.
  15320. dropRemoteDocumentChangesStore(db);
  15321. // Note: Schema version 9 used to create a read time index for the
  15322. // RemoteDocumentCache. This is now done with schema version 13.
  15323. });
  15324. }
  15325. if (fromVersion < 10 && toVersion >= 10) {
  15326. p = p.next(() => this.rewriteCanonicalIds(simpleDbTransaction));
  15327. }
  15328. if (fromVersion < 11 && toVersion >= 11) {
  15329. p = p.next(() => {
  15330. createBundlesStore(db);
  15331. createNamedQueriesStore(db);
  15332. });
  15333. }
  15334. if (fromVersion < 12 && toVersion >= 12) {
  15335. p = p.next(() => {
  15336. createDocumentOverlayStore(db);
  15337. });
  15338. }
  15339. if (fromVersion < 13 && toVersion >= 13) {
  15340. p = p
  15341. .next(() => createRemoteDocumentCache(db))
  15342. .next(() => this.rewriteRemoteDocumentCache(db, simpleDbTransaction))
  15343. .next(() => db.deleteObjectStore(DbRemoteDocumentStore$1));
  15344. }
  15345. if (fromVersion < 14 && toVersion >= 14) {
  15346. p = p.next(() => this.runOverlayMigration(db, simpleDbTransaction));
  15347. }
  15348. if (fromVersion < 15 && toVersion >= 15) {
  15349. p = p.next(() => createFieldIndex(db));
  15350. }
  15351. return p;
  15352. }
  15353. addDocumentGlobal(txn) {
  15354. let byteSize = 0;
  15355. return txn
  15356. .store(DbRemoteDocumentStore$1)
  15357. .iterate((_, doc) => {
  15358. byteSize += dbDocumentSize(doc);
  15359. })
  15360. .next(() => {
  15361. const metadata = { byteSize };
  15362. return txn
  15363. .store(DbRemoteDocumentGlobalStore)
  15364. .put(DbRemoteDocumentGlobalKey, metadata);
  15365. });
  15366. }
  15367. removeAcknowledgedMutations(txn) {
  15368. const queuesStore = txn.store(DbMutationQueueStore);
  15369. const mutationsStore = txn.store(DbMutationBatchStore);
  15370. return queuesStore.loadAll().next(queues => {
  15371. return PersistencePromise.forEach(queues, (queue) => {
  15372. const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
  15373. return mutationsStore
  15374. .loadAll(DbMutationBatchUserMutationsIndex, range)
  15375. .next(dbBatches => {
  15376. return PersistencePromise.forEach(dbBatches, (dbBatch) => {
  15377. hardAssert(dbBatch.userId === queue.userId);
  15378. const batch = fromDbMutationBatch(this.serializer, dbBatch);
  15379. return removeMutationBatch(txn, queue.userId, batch).next(() => { });
  15380. });
  15381. });
  15382. });
  15383. });
  15384. }
  15385. /**
  15386. * Ensures that every document in the remote document cache has a corresponding sentinel row
  15387. * with a sequence number. Missing rows are given the most recently used sequence number.
  15388. */
  15389. ensureSequenceNumbers(txn) {
  15390. const documentTargetStore = txn.store(DbTargetDocumentStore);
  15391. const documentsStore = txn.store(DbRemoteDocumentStore$1);
  15392. const globalTargetStore = txn.store(DbTargetGlobalStore);
  15393. return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
  15394. const writeSentinelKey = (path) => {
  15395. return documentTargetStore.put({
  15396. targetId: 0,
  15397. path: encodeResourcePath(path),
  15398. sequenceNumber: metadata.highestListenSequenceNumber
  15399. });
  15400. };
  15401. const promises = [];
  15402. return documentsStore
  15403. .iterate((key, doc) => {
  15404. const path = new ResourcePath(key);
  15405. const docSentinelKey = sentinelKey(path);
  15406. promises.push(documentTargetStore.get(docSentinelKey).next(maybeSentinel => {
  15407. if (!maybeSentinel) {
  15408. return writeSentinelKey(path);
  15409. }
  15410. else {
  15411. return PersistencePromise.resolve();
  15412. }
  15413. }));
  15414. })
  15415. .next(() => PersistencePromise.waitFor(promises));
  15416. });
  15417. }
  15418. createCollectionParentIndex(db, txn) {
  15419. // Create the index.
  15420. db.createObjectStore(DbCollectionParentStore, {
  15421. keyPath: DbCollectionParentKeyPath
  15422. });
  15423. const collectionParentsStore = txn.store(DbCollectionParentStore);
  15424. // Helper to add an index entry iff we haven't already written it.
  15425. const cache = new MemoryCollectionParentIndex();
  15426. const addEntry = (collectionPath) => {
  15427. if (cache.add(collectionPath)) {
  15428. const collectionId = collectionPath.lastSegment();
  15429. const parentPath = collectionPath.popLast();
  15430. return collectionParentsStore.put({
  15431. collectionId,
  15432. parent: encodeResourcePath(parentPath)
  15433. });
  15434. }
  15435. };
  15436. // Index existing remote documents.
  15437. return txn
  15438. .store(DbRemoteDocumentStore$1)
  15439. .iterate({ keysOnly: true }, (pathSegments, _) => {
  15440. const path = new ResourcePath(pathSegments);
  15441. return addEntry(path.popLast());
  15442. })
  15443. .next(() => {
  15444. // Index existing mutations.
  15445. return txn
  15446. .store(DbDocumentMutationStore)
  15447. .iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
  15448. const path = decodeResourcePath(encodedPath);
  15449. return addEntry(path.popLast());
  15450. });
  15451. });
  15452. }
  15453. rewriteCanonicalIds(txn) {
  15454. const targetStore = txn.store(DbTargetStore);
  15455. return targetStore.iterate((key, originalDbTarget) => {
  15456. const originalTargetData = fromDbTarget(originalDbTarget);
  15457. const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
  15458. return targetStore.put(updatedDbTarget);
  15459. });
  15460. }
  15461. rewriteRemoteDocumentCache(db, transaction) {
  15462. const legacyRemoteDocumentStore = transaction.store(DbRemoteDocumentStore$1);
  15463. const writes = [];
  15464. return legacyRemoteDocumentStore
  15465. .iterate((_, legacyDocument) => {
  15466. const remoteDocumentStore = transaction.store(DbRemoteDocumentStore);
  15467. const path = extractKey(legacyDocument).path.toArray();
  15468. const dbRemoteDocument = {
  15469. prefixPath: path.slice(0, path.length - 2),
  15470. collectionGroup: path[path.length - 2],
  15471. documentId: path[path.length - 1],
  15472. readTime: legacyDocument.readTime || [0, 0],
  15473. unknownDocument: legacyDocument.unknownDocument,
  15474. noDocument: legacyDocument.noDocument,
  15475. document: legacyDocument.document,
  15476. hasCommittedMutations: !!legacyDocument.hasCommittedMutations
  15477. };
  15478. writes.push(remoteDocumentStore.put(dbRemoteDocument));
  15479. })
  15480. .next(() => PersistencePromise.waitFor(writes));
  15481. }
  15482. runOverlayMigration(db, transaction) {
  15483. const mutationsStore = transaction.store(DbMutationBatchStore);
  15484. const remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
  15485. const memoryPersistence = new MemoryPersistence(MemoryEagerDelegate.factory, this.serializer.remoteSerializer);
  15486. return mutationsStore.loadAll().next(dbBatches => {
  15487. const userToDocumentSet = new Map();
  15488. dbBatches.forEach(dbBatch => {
  15489. var _a;
  15490. let documentSet = (_a = userToDocumentSet.get(dbBatch.userId)) !== null && _a !== void 0 ? _a : documentKeySet();
  15491. const batch = fromDbMutationBatch(this.serializer, dbBatch);
  15492. batch.keys().forEach(key => (documentSet = documentSet.add(key)));
  15493. userToDocumentSet.set(dbBatch.userId, documentSet);
  15494. });
  15495. return PersistencePromise.forEach(userToDocumentSet, (allDocumentKeysForUser, userId) => {
  15496. const user = new User(userId);
  15497. const documentOverlayCache = IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
  15498. // NOTE: The index manager and the reference delegate are
  15499. // irrelevant for the purpose of recalculating and saving
  15500. // overlays. We can therefore simply use the memory
  15501. // implementation.
  15502. const indexManager = memoryPersistence.getIndexManager(user);
  15503. const mutationQueue = IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, memoryPersistence.referenceDelegate);
  15504. const localDocumentsView = new LocalDocumentsView(remoteDocumentCache, mutationQueue, documentOverlayCache, indexManager);
  15505. return localDocumentsView
  15506. .recalculateAndSaveOverlaysForDocumentKeys(new IndexedDbTransaction(transaction, ListenSequence.INVALID), allDocumentKeysForUser)
  15507. .next();
  15508. });
  15509. });
  15510. }
  15511. }
  15512. function sentinelKey(path) {
  15513. return [0, encodeResourcePath(path)];
  15514. }
  15515. function createPrimaryClientStore(db) {
  15516. db.createObjectStore(DbPrimaryClientStore);
  15517. }
  15518. function createMutationQueue(db) {
  15519. db.createObjectStore(DbMutationQueueStore, {
  15520. keyPath: DbMutationQueueKeyPath
  15521. });
  15522. const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
  15523. keyPath: DbMutationBatchKeyPath,
  15524. autoIncrement: true
  15525. });
  15526. mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
  15527. db.createObjectStore(DbDocumentMutationStore);
  15528. }
  15529. /**
  15530. * Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
  15531. * and rewrites all data.
  15532. */
  15533. function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
  15534. const v1MutationsStore = txn.store(DbMutationBatchStore);
  15535. return v1MutationsStore.loadAll().next(existingMutations => {
  15536. db.deleteObjectStore(DbMutationBatchStore);
  15537. const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
  15538. keyPath: DbMutationBatchKeyPath,
  15539. autoIncrement: true
  15540. });
  15541. mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
  15542. const v3MutationsStore = txn.store(DbMutationBatchStore);
  15543. const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
  15544. return PersistencePromise.waitFor(writeAll);
  15545. });
  15546. }
  15547. function createLegacyRemoteDocumentCache(db) {
  15548. db.createObjectStore(DbRemoteDocumentStore$1);
  15549. }
  15550. function createRemoteDocumentCache(db) {
  15551. const remoteDocumentStore = db.createObjectStore(DbRemoteDocumentStore, {
  15552. keyPath: DbRemoteDocumentKeyPath
  15553. });
  15554. remoteDocumentStore.createIndex(DbRemoteDocumentDocumentKeyIndex, DbRemoteDocumentDocumentKeyIndexPath);
  15555. remoteDocumentStore.createIndex(DbRemoteDocumentCollectionGroupIndex, DbRemoteDocumentCollectionGroupIndexPath);
  15556. }
  15557. function createDocumentGlobalStore(db) {
  15558. db.createObjectStore(DbRemoteDocumentGlobalStore);
  15559. }
  15560. function createQueryCache(db) {
  15561. const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
  15562. keyPath: DbTargetDocumentKeyPath
  15563. });
  15564. targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
  15565. const targetStore = db.createObjectStore(DbTargetStore, {
  15566. keyPath: DbTargetKeyPath
  15567. });
  15568. // NOTE: This is unique only because the TargetId is the suffix.
  15569. targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
  15570. db.createObjectStore(DbTargetGlobalStore);
  15571. }
  15572. function dropQueryCache(db) {
  15573. db.deleteObjectStore(DbTargetDocumentStore);
  15574. db.deleteObjectStore(DbTargetStore);
  15575. db.deleteObjectStore(DbTargetGlobalStore);
  15576. }
  15577. function dropRemoteDocumentChangesStore(db) {
  15578. if (db.objectStoreNames.contains('remoteDocumentChanges')) {
  15579. db.deleteObjectStore('remoteDocumentChanges');
  15580. }
  15581. }
  15582. /**
  15583. * Creates the target global singleton row.
  15584. *
  15585. * @param txn - The version upgrade transaction for indexeddb
  15586. */
  15587. function writeEmptyTargetGlobalEntry(txn) {
  15588. const globalStore = txn.store(DbTargetGlobalStore);
  15589. const metadata = {
  15590. highestTargetId: 0,
  15591. highestListenSequenceNumber: 0,
  15592. lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
  15593. targetCount: 0
  15594. };
  15595. return globalStore.put(DbTargetGlobalKey, metadata);
  15596. }
  15597. function createClientMetadataStore(db) {
  15598. db.createObjectStore(DbClientMetadataStore, {
  15599. keyPath: DbClientMetadataKeyPath
  15600. });
  15601. }
  15602. function createBundlesStore(db) {
  15603. db.createObjectStore(DbBundleStore, {
  15604. keyPath: DbBundleKeyPath
  15605. });
  15606. }
  15607. function createNamedQueriesStore(db) {
  15608. db.createObjectStore(DbNamedQueryStore, {
  15609. keyPath: DbNamedQueryKeyPath
  15610. });
  15611. }
  15612. function createFieldIndex(db) {
  15613. const indexConfigurationStore = db.createObjectStore(DbIndexConfigurationStore, {
  15614. keyPath: DbIndexConfigurationKeyPath,
  15615. autoIncrement: true
  15616. });
  15617. indexConfigurationStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
  15618. const indexStateStore = db.createObjectStore(DbIndexStateStore, {
  15619. keyPath: DbIndexStateKeyPath
  15620. });
  15621. indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
  15622. const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
  15623. keyPath: DbIndexEntryKeyPath
  15624. });
  15625. indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
  15626. }
  15627. function createDocumentOverlayStore(db) {
  15628. const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
  15629. keyPath: DbDocumentOverlayKeyPath
  15630. });
  15631. documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
  15632. documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
  15633. }
  15634. function extractKey(remoteDoc) {
  15635. if (remoteDoc.document) {
  15636. return new DocumentKey(ResourcePath.fromString(remoteDoc.document.name).popFirst(5));
  15637. }
  15638. else if (remoteDoc.noDocument) {
  15639. return DocumentKey.fromSegments(remoteDoc.noDocument.path);
  15640. }
  15641. else if (remoteDoc.unknownDocument) {
  15642. return DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
  15643. }
  15644. else {
  15645. return fail();
  15646. }
  15647. }
  15648. /**
  15649. * @license
  15650. * Copyright 2017 Google LLC
  15651. *
  15652. * Licensed under the Apache License, Version 2.0 (the "License");
  15653. * you may not use this file except in compliance with the License.
  15654. * You may obtain a copy of the License at
  15655. *
  15656. * http://www.apache.org/licenses/LICENSE-2.0
  15657. *
  15658. * Unless required by applicable law or agreed to in writing, software
  15659. * distributed under the License is distributed on an "AS IS" BASIS,
  15660. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15661. * See the License for the specific language governing permissions and
  15662. * limitations under the License.
  15663. */
  15664. const LOG_TAG$c = 'IndexedDbPersistence';
  15665. /**
  15666. * Oldest acceptable age in milliseconds for client metadata before the client
  15667. * is considered inactive and its associated data is garbage collected.
  15668. */
  15669. const MAX_CLIENT_AGE_MS = 30 * 60 * 1000; // 30 minutes
  15670. /**
  15671. * Oldest acceptable metadata age for clients that may participate in the
  15672. * primary lease election. Clients that have not updated their client metadata
  15673. * within 5 seconds are not eligible to receive a primary lease.
  15674. */
  15675. const MAX_PRIMARY_ELIGIBLE_AGE_MS = 5000;
  15676. /**
  15677. * The interval at which clients will update their metadata, including
  15678. * refreshing their primary lease if held or potentially trying to acquire it if
  15679. * not held.
  15680. *
  15681. * Primary clients may opportunistically refresh their metadata earlier
  15682. * if they're already performing an IndexedDB operation.
  15683. */
  15684. const CLIENT_METADATA_REFRESH_INTERVAL_MS = 4000;
  15685. /** User-facing error when the primary lease is required but not available. */
  15686. const PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG = 'Failed to obtain exclusive access to the persistence layer. To allow ' +
  15687. 'shared access, multi-tab synchronization has to be enabled in all tabs. ' +
  15688. 'If you are using `experimentalForceOwningTab:true`, make sure that only ' +
  15689. 'one tab has persistence enabled at any given time.';
  15690. const UNSUPPORTED_PLATFORM_ERROR_MSG = 'This platform is either missing IndexedDB or is known to have ' +
  15691. 'an incomplete implementation. Offline persistence has been disabled.';
  15692. // The format of the LocalStorage key that stores zombied client is:
  15693. // firestore_zombie_<persistence_prefix>_<instance_key>
  15694. const ZOMBIED_CLIENTS_KEY_PREFIX = 'firestore_zombie';
  15695. /**
  15696. * The name of the main (and currently only) IndexedDB database. This name is
  15697. * appended to the prefix provided to the IndexedDbPersistence constructor.
  15698. */
  15699. const MAIN_DATABASE = 'main';
  15700. /**
  15701. * An IndexedDB-backed instance of Persistence. Data is stored persistently
  15702. * across sessions.
  15703. *
  15704. * On Web only, the Firestore SDKs support shared access to its persistence
  15705. * layer. This allows multiple browser tabs to read and write to IndexedDb and
  15706. * to synchronize state even without network connectivity. Shared access is
  15707. * currently optional and not enabled unless all clients invoke
  15708. * `enablePersistence()` with `{synchronizeTabs:true}`.
  15709. *
  15710. * In multi-tab mode, if multiple clients are active at the same time, the SDK
  15711. * will designate one client as the “primary client”. An effort is made to pick
  15712. * a visible, network-connected and active client, and this client is
  15713. * responsible for letting other clients know about its presence. The primary
  15714. * client writes a unique client-generated identifier (the client ID) to
  15715. * IndexedDb’s “owner” store every 4 seconds. If the primary client fails to
  15716. * update this entry, another client can acquire the lease and take over as
  15717. * primary.
  15718. *
  15719. * Some persistence operations in the SDK are designated as primary-client only
  15720. * operations. This includes the acknowledgment of mutations and all updates of
  15721. * remote documents. The effects of these operations are written to persistence
  15722. * and then broadcast to other tabs via LocalStorage (see
  15723. * `WebStorageSharedClientState`), which then refresh their state from
  15724. * persistence.
  15725. *
  15726. * Similarly, the primary client listens to notifications sent by secondary
  15727. * clients to discover persistence changes written by secondary clients, such as
  15728. * the addition of new mutations and query targets.
  15729. *
  15730. * If multi-tab is not enabled and another tab already obtained the primary
  15731. * lease, IndexedDbPersistence enters a failed state and all subsequent
  15732. * operations will automatically fail.
  15733. *
  15734. * Additionally, there is an optimization so that when a tab is closed, the
  15735. * primary lease is released immediately (this is especially important to make
  15736. * sure that a refreshed tab is able to immediately re-acquire the primary
  15737. * lease). Unfortunately, IndexedDB cannot be reliably used in window.unload
  15738. * since it is an asynchronous API. So in addition to attempting to give up the
  15739. * lease, the leaseholder writes its client ID to a "zombiedClient" entry in
  15740. * LocalStorage which acts as an indicator that another tab should go ahead and
  15741. * take the primary lease immediately regardless of the current lease timestamp.
  15742. *
  15743. * TODO(b/114226234): Remove `synchronizeTabs` section when multi-tab is no
  15744. * longer optional.
  15745. */
  15746. class IndexedDbPersistence {
  15747. constructor(
  15748. /**
  15749. * Whether to synchronize the in-memory state of multiple tabs and share
  15750. * access to local persistence.
  15751. */
  15752. allowTabSynchronization, persistenceKey, clientId, lruParams, queue, window, document, serializer, sequenceNumberSyncer,
  15753. /**
  15754. * If set to true, forcefully obtains database access. Existing tabs will
  15755. * no longer be able to access IndexedDB.
  15756. */
  15757. forceOwningTab, schemaVersion = SCHEMA_VERSION) {
  15758. this.allowTabSynchronization = allowTabSynchronization;
  15759. this.persistenceKey = persistenceKey;
  15760. this.clientId = clientId;
  15761. this.queue = queue;
  15762. this.window = window;
  15763. this.document = document;
  15764. this.sequenceNumberSyncer = sequenceNumberSyncer;
  15765. this.forceOwningTab = forceOwningTab;
  15766. this.schemaVersion = schemaVersion;
  15767. this.listenSequence = null;
  15768. this._started = false;
  15769. this.isPrimary = false;
  15770. this.networkEnabled = true;
  15771. /** Our window.unload handler, if registered. */
  15772. this.windowUnloadHandler = null;
  15773. this.inForeground = false;
  15774. /** Our 'visibilitychange' listener if registered. */
  15775. this.documentVisibilityHandler = null;
  15776. /** The client metadata refresh task. */
  15777. this.clientMetadataRefresher = null;
  15778. /** The last time we garbage collected the client metadata object store. */
  15779. this.lastGarbageCollectionTime = Number.NEGATIVE_INFINITY;
  15780. /** A listener to notify on primary state changes. */
  15781. this.primaryStateListener = _ => Promise.resolve();
  15782. if (!IndexedDbPersistence.isAvailable()) {
  15783. throw new FirestoreError(Code.UNIMPLEMENTED, UNSUPPORTED_PLATFORM_ERROR_MSG);
  15784. }
  15785. this.referenceDelegate = new IndexedDbLruDelegateImpl(this, lruParams);
  15786. this.dbName = persistenceKey + MAIN_DATABASE;
  15787. this.serializer = new LocalSerializer(serializer);
  15788. this.simpleDb = new SimpleDb(this.dbName, this.schemaVersion, new SchemaConverter(this.serializer));
  15789. this.targetCache = new IndexedDbTargetCache(this.referenceDelegate, this.serializer);
  15790. this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
  15791. this.bundleCache = new IndexedDbBundleCache();
  15792. if (this.window && this.window.localStorage) {
  15793. this.webStorage = this.window.localStorage;
  15794. }
  15795. else {
  15796. this.webStorage = null;
  15797. if (forceOwningTab === false) {
  15798. logError(LOG_TAG$c, 'LocalStorage is unavailable. As a result, persistence may not work ' +
  15799. 'reliably. In particular enablePersistence() could fail immediately ' +
  15800. 'after refreshing the page.');
  15801. }
  15802. }
  15803. }
  15804. /**
  15805. * Attempt to start IndexedDb persistence.
  15806. *
  15807. * @returns Whether persistence was enabled.
  15808. */
  15809. start() {
  15810. // NOTE: This is expected to fail sometimes (in the case of another tab
  15811. // already having the persistence lock), so it's the first thing we should
  15812. // do.
  15813. return this.updateClientMetadataAndTryBecomePrimary()
  15814. .then(() => {
  15815. if (!this.isPrimary && !this.allowTabSynchronization) {
  15816. // Fail `start()` if `synchronizeTabs` is disabled and we cannot
  15817. // obtain the primary lease.
  15818. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG);
  15819. }
  15820. this.attachVisibilityHandler();
  15821. this.attachWindowUnloadHook();
  15822. this.scheduleClientMetadataAndPrimaryLeaseRefreshes();
  15823. return this.runTransaction('getHighestListenSequenceNumber', 'readonly', txn => this.targetCache.getHighestSequenceNumber(txn));
  15824. })
  15825. .then(highestListenSequenceNumber => {
  15826. this.listenSequence = new ListenSequence(highestListenSequenceNumber, this.sequenceNumberSyncer);
  15827. })
  15828. .then(() => {
  15829. this._started = true;
  15830. })
  15831. .catch(reason => {
  15832. this.simpleDb && this.simpleDb.close();
  15833. return Promise.reject(reason);
  15834. });
  15835. }
  15836. /**
  15837. * Registers a listener that gets called when the primary state of the
  15838. * instance changes. Upon registering, this listener is invoked immediately
  15839. * with the current primary state.
  15840. *
  15841. * PORTING NOTE: This is only used for Web multi-tab.
  15842. */
  15843. setPrimaryStateListener(primaryStateListener) {
  15844. this.primaryStateListener = async (primaryState) => {
  15845. if (this.started) {
  15846. return primaryStateListener(primaryState);
  15847. }
  15848. };
  15849. return primaryStateListener(this.isPrimary);
  15850. }
  15851. /**
  15852. * Registers a listener that gets called when the database receives a
  15853. * version change event indicating that it has deleted.
  15854. *
  15855. * PORTING NOTE: This is only used for Web multi-tab.
  15856. */
  15857. setDatabaseDeletedListener(databaseDeletedListener) {
  15858. this.simpleDb.setVersionChangeListener(async (event) => {
  15859. // Check if an attempt is made to delete IndexedDB.
  15860. if (event.newVersion === null) {
  15861. await databaseDeletedListener();
  15862. }
  15863. });
  15864. }
  15865. /**
  15866. * Adjusts the current network state in the client's metadata, potentially
  15867. * affecting the primary lease.
  15868. *
  15869. * PORTING NOTE: This is only used for Web multi-tab.
  15870. */
  15871. setNetworkEnabled(networkEnabled) {
  15872. if (this.networkEnabled !== networkEnabled) {
  15873. this.networkEnabled = networkEnabled;
  15874. // Schedule a primary lease refresh for immediate execution. The eventual
  15875. // lease update will be propagated via `primaryStateListener`.
  15876. this.queue.enqueueAndForget(async () => {
  15877. if (this.started) {
  15878. await this.updateClientMetadataAndTryBecomePrimary();
  15879. }
  15880. });
  15881. }
  15882. }
  15883. /**
  15884. * Updates the client metadata in IndexedDb and attempts to either obtain or
  15885. * extend the primary lease for the local client. Asynchronously notifies the
  15886. * primary state listener if the client either newly obtained or released its
  15887. * primary lease.
  15888. */
  15889. updateClientMetadataAndTryBecomePrimary() {
  15890. return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
  15891. const metadataStore = clientMetadataStore(txn);
  15892. return metadataStore
  15893. .put({
  15894. clientId: this.clientId,
  15895. updateTimeMs: Date.now(),
  15896. networkEnabled: this.networkEnabled,
  15897. inForeground: this.inForeground
  15898. })
  15899. .next(() => {
  15900. if (this.isPrimary) {
  15901. return this.verifyPrimaryLease(txn).next(success => {
  15902. if (!success) {
  15903. this.isPrimary = false;
  15904. this.queue.enqueueRetryable(() => this.primaryStateListener(false));
  15905. }
  15906. });
  15907. }
  15908. })
  15909. .next(() => this.canActAsPrimary(txn))
  15910. .next(canActAsPrimary => {
  15911. if (this.isPrimary && !canActAsPrimary) {
  15912. return this.releasePrimaryLeaseIfHeld(txn).next(() => false);
  15913. }
  15914. else if (canActAsPrimary) {
  15915. return this.acquireOrExtendPrimaryLease(txn).next(() => true);
  15916. }
  15917. else {
  15918. return /* canActAsPrimary= */ false;
  15919. }
  15920. });
  15921. })
  15922. .catch(e => {
  15923. if (isIndexedDbTransactionError(e)) {
  15924. logDebug(LOG_TAG$c, 'Failed to extend owner lease: ', e);
  15925. // Proceed with the existing state. Any subsequent access to
  15926. // IndexedDB will verify the lease.
  15927. return this.isPrimary;
  15928. }
  15929. if (!this.allowTabSynchronization) {
  15930. throw e;
  15931. }
  15932. logDebug(LOG_TAG$c, 'Releasing owner lease after error during lease refresh', e);
  15933. return /* isPrimary= */ false;
  15934. })
  15935. .then(isPrimary => {
  15936. if (this.isPrimary !== isPrimary) {
  15937. this.queue.enqueueRetryable(() => this.primaryStateListener(isPrimary));
  15938. }
  15939. this.isPrimary = isPrimary;
  15940. });
  15941. }
  15942. verifyPrimaryLease(txn) {
  15943. const store = primaryClientStore(txn);
  15944. return store.get(DbPrimaryClientKey).next(primaryClient => {
  15945. return PersistencePromise.resolve(this.isLocalClient(primaryClient));
  15946. });
  15947. }
  15948. removeClientMetadata(txn) {
  15949. const metadataStore = clientMetadataStore(txn);
  15950. return metadataStore.delete(this.clientId);
  15951. }
  15952. /**
  15953. * If the garbage collection threshold has passed, prunes the
  15954. * RemoteDocumentChanges and the ClientMetadata store based on the last update
  15955. * time of all clients.
  15956. */
  15957. async maybeGarbageCollectMultiClientState() {
  15958. if (this.isPrimary &&
  15959. !this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
  15960. this.lastGarbageCollectionTime = Date.now();
  15961. const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
  15962. const metadataStore = getStore(txn, DbClientMetadataStore);
  15963. return metadataStore.loadAll().next(existingClients => {
  15964. const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
  15965. const inactive = existingClients.filter(client => active.indexOf(client) === -1);
  15966. // Delete metadata for clients that are no longer considered active.
  15967. return PersistencePromise.forEach(inactive, (inactiveClient) => metadataStore.delete(inactiveClient.clientId)).next(() => inactive);
  15968. });
  15969. }).catch(() => {
  15970. // Ignore primary lease violations or any other type of error. The next
  15971. // primary will run `maybeGarbageCollectMultiClientState()` again.
  15972. // We don't use `ignoreIfPrimaryLeaseLoss()` since we don't want to depend
  15973. // on LocalStore.
  15974. return [];
  15975. });
  15976. // Delete potential leftover entries that may continue to mark the
  15977. // inactive clients as zombied in LocalStorage.
  15978. // Ideally we'd delete the IndexedDb and LocalStorage zombie entries for
  15979. // the client atomically, but we can't. So we opt to delete the IndexedDb
  15980. // entries first to avoid potentially reviving a zombied client.
  15981. if (this.webStorage) {
  15982. for (const inactiveClient of inactiveClients) {
  15983. this.webStorage.removeItem(this.zombiedClientLocalStorageKey(inactiveClient.clientId));
  15984. }
  15985. }
  15986. }
  15987. }
  15988. /**
  15989. * Schedules a recurring timer to update the client metadata and to either
  15990. * extend or acquire the primary lease if the client is eligible.
  15991. */
  15992. scheduleClientMetadataAndPrimaryLeaseRefreshes() {
  15993. this.clientMetadataRefresher = this.queue.enqueueAfterDelay("client_metadata_refresh" /* TimerId.ClientMetadataRefresh */, CLIENT_METADATA_REFRESH_INTERVAL_MS, () => {
  15994. return this.updateClientMetadataAndTryBecomePrimary()
  15995. .then(() => this.maybeGarbageCollectMultiClientState())
  15996. .then(() => this.scheduleClientMetadataAndPrimaryLeaseRefreshes());
  15997. });
  15998. }
  15999. /** Checks whether `client` is the local client. */
  16000. isLocalClient(client) {
  16001. return client ? client.ownerId === this.clientId : false;
  16002. }
  16003. /**
  16004. * Evaluate the state of all active clients and determine whether the local
  16005. * client is or can act as the holder of the primary lease. Returns whether
  16006. * the client is eligible for the lease, but does not actually acquire it.
  16007. * May return 'false' even if there is no active leaseholder and another
  16008. * (foreground) client should become leaseholder instead.
  16009. */
  16010. canActAsPrimary(txn) {
  16011. if (this.forceOwningTab) {
  16012. return PersistencePromise.resolve(true);
  16013. }
  16014. const store = primaryClientStore(txn);
  16015. return store
  16016. .get(DbPrimaryClientKey)
  16017. .next(currentPrimary => {
  16018. const currentLeaseIsValid = currentPrimary !== null &&
  16019. this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
  16020. !this.isClientZombied(currentPrimary.ownerId);
  16021. // A client is eligible for the primary lease if:
  16022. // - its network is enabled and the client's tab is in the foreground.
  16023. // - its network is enabled and no other client's tab is in the
  16024. // foreground.
  16025. // - every clients network is disabled and the client's tab is in the
  16026. // foreground.
  16027. // - every clients network is disabled and no other client's tab is in
  16028. // the foreground.
  16029. // - the `forceOwningTab` setting was passed in.
  16030. if (currentLeaseIsValid) {
  16031. if (this.isLocalClient(currentPrimary) && this.networkEnabled) {
  16032. return true;
  16033. }
  16034. if (!this.isLocalClient(currentPrimary)) {
  16035. if (!currentPrimary.allowTabSynchronization) {
  16036. // Fail the `canActAsPrimary` check if the current leaseholder has
  16037. // not opted into multi-tab synchronization. If this happens at
  16038. // client startup, we reject the Promise returned by
  16039. // `enablePersistence()` and the user can continue to use Firestore
  16040. // with in-memory persistence.
  16041. // If this fails during a lease refresh, we will instead block the
  16042. // AsyncQueue from executing further operations. Note that this is
  16043. // acceptable since mixing & matching different `synchronizeTabs`
  16044. // settings is not supported.
  16045. //
  16046. // TODO(b/114226234): Remove this check when `synchronizeTabs` can
  16047. // no longer be turned off.
  16048. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG);
  16049. }
  16050. return false;
  16051. }
  16052. }
  16053. if (this.networkEnabled && this.inForeground) {
  16054. return true;
  16055. }
  16056. return clientMetadataStore(txn)
  16057. .loadAll()
  16058. .next(existingClients => {
  16059. // Process all existing clients and determine whether at least one of
  16060. // them is better suited to obtain the primary lease.
  16061. const preferredCandidate = this.filterActiveClients(existingClients, MAX_PRIMARY_ELIGIBLE_AGE_MS).find(otherClient => {
  16062. if (this.clientId !== otherClient.clientId) {
  16063. const otherClientHasBetterNetworkState = !this.networkEnabled && otherClient.networkEnabled;
  16064. const otherClientHasBetterVisibility = !this.inForeground && otherClient.inForeground;
  16065. const otherClientHasSameNetworkState = this.networkEnabled === otherClient.networkEnabled;
  16066. if (otherClientHasBetterNetworkState ||
  16067. (otherClientHasBetterVisibility &&
  16068. otherClientHasSameNetworkState)) {
  16069. return true;
  16070. }
  16071. }
  16072. return false;
  16073. });
  16074. return preferredCandidate === undefined;
  16075. });
  16076. })
  16077. .next(canActAsPrimary => {
  16078. if (this.isPrimary !== canActAsPrimary) {
  16079. logDebug(LOG_TAG$c, `Client ${canActAsPrimary ? 'is' : 'is not'} eligible for a primary lease.`);
  16080. }
  16081. return canActAsPrimary;
  16082. });
  16083. }
  16084. async shutdown() {
  16085. // The shutdown() operations are idempotent and can be called even when
  16086. // start() aborted (e.g. because it couldn't acquire the persistence lease).
  16087. this._started = false;
  16088. this.markClientZombied();
  16089. if (this.clientMetadataRefresher) {
  16090. this.clientMetadataRefresher.cancel();
  16091. this.clientMetadataRefresher = null;
  16092. }
  16093. this.detachVisibilityHandler();
  16094. this.detachWindowUnloadHook();
  16095. // Use `SimpleDb.runTransaction` directly to avoid failing if another tab
  16096. // has obtained the primary lease.
  16097. await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
  16098. const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
  16099. return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
  16100. });
  16101. this.simpleDb.close();
  16102. // Remove the entry marking the client as zombied from LocalStorage since
  16103. // we successfully deleted its metadata from IndexedDb.
  16104. this.removeClientZombiedEntry();
  16105. }
  16106. /**
  16107. * Returns clients that are not zombied and have an updateTime within the
  16108. * provided threshold.
  16109. */
  16110. filterActiveClients(clients, activityThresholdMs) {
  16111. return clients.filter(client => this.isWithinAge(client.updateTimeMs, activityThresholdMs) &&
  16112. !this.isClientZombied(client.clientId));
  16113. }
  16114. /**
  16115. * Returns the IDs of the clients that are currently active. If multi-tab
  16116. * is not supported, returns an array that only contains the local client's
  16117. * ID.
  16118. *
  16119. * PORTING NOTE: This is only used for Web multi-tab.
  16120. */
  16121. getActiveClients() {
  16122. return this.runTransaction('getActiveClients', 'readonly', txn => {
  16123. return clientMetadataStore(txn)
  16124. .loadAll()
  16125. .next(clients => this.filterActiveClients(clients, MAX_CLIENT_AGE_MS).map(clientMetadata => clientMetadata.clientId));
  16126. });
  16127. }
  16128. get started() {
  16129. return this._started;
  16130. }
  16131. getMutationQueue(user, indexManager) {
  16132. return IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, this.referenceDelegate);
  16133. }
  16134. getTargetCache() {
  16135. return this.targetCache;
  16136. }
  16137. getRemoteDocumentCache() {
  16138. return this.remoteDocumentCache;
  16139. }
  16140. getIndexManager(user) {
  16141. return new IndexedDbIndexManager(user, this.serializer.remoteSerializer.databaseId);
  16142. }
  16143. getDocumentOverlayCache(user) {
  16144. return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
  16145. }
  16146. getBundleCache() {
  16147. return this.bundleCache;
  16148. }
  16149. runTransaction(action, mode, transactionOperation) {
  16150. logDebug(LOG_TAG$c, 'Starting transaction:', action);
  16151. const simpleDbMode = mode === 'readonly' ? 'readonly' : 'readwrite';
  16152. const objectStores = getObjectStores(this.schemaVersion);
  16153. let persistenceTransaction;
  16154. // Do all transactions as readwrite against all object stores, since we
  16155. // are the only reader/writer.
  16156. return this.simpleDb
  16157. .runTransaction(action, simpleDbMode, objectStores, simpleDbTxn => {
  16158. persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, this.listenSequence
  16159. ? this.listenSequence.next()
  16160. : ListenSequence.INVALID);
  16161. if (mode === 'readwrite-primary') {
  16162. // While we merely verify that we have (or can acquire) the lease
  16163. // immediately, we wait to extend the primary lease until after
  16164. // executing transactionOperation(). This ensures that even if the
  16165. // transactionOperation takes a long time, we'll use a recent
  16166. // leaseTimestampMs in the extended (or newly acquired) lease.
  16167. return this.verifyPrimaryLease(persistenceTransaction)
  16168. .next(holdsPrimaryLease => {
  16169. if (holdsPrimaryLease) {
  16170. return /* holdsPrimaryLease= */ true;
  16171. }
  16172. return this.canActAsPrimary(persistenceTransaction);
  16173. })
  16174. .next(holdsPrimaryLease => {
  16175. if (!holdsPrimaryLease) {
  16176. logError(`Failed to obtain primary lease for action '${action}'.`);
  16177. this.isPrimary = false;
  16178. this.queue.enqueueRetryable(() => this.primaryStateListener(false));
  16179. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_LOST_ERROR_MSG);
  16180. }
  16181. return transactionOperation(persistenceTransaction);
  16182. })
  16183. .next(result => {
  16184. return this.acquireOrExtendPrimaryLease(persistenceTransaction).next(() => result);
  16185. });
  16186. }
  16187. else {
  16188. return this.verifyAllowTabSynchronization(persistenceTransaction).next(() => transactionOperation(persistenceTransaction));
  16189. }
  16190. })
  16191. .then(result => {
  16192. persistenceTransaction.raiseOnCommittedEvent();
  16193. return result;
  16194. });
  16195. }
  16196. /**
  16197. * Verifies that the current tab is the primary leaseholder or alternatively
  16198. * that the leaseholder has opted into multi-tab synchronization.
  16199. */
  16200. // TODO(b/114226234): Remove this check when `synchronizeTabs` can no longer
  16201. // be turned off.
  16202. verifyAllowTabSynchronization(txn) {
  16203. const store = primaryClientStore(txn);
  16204. return store.get(DbPrimaryClientKey).next(currentPrimary => {
  16205. const currentLeaseIsValid = currentPrimary !== null &&
  16206. this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
  16207. !this.isClientZombied(currentPrimary.ownerId);
  16208. if (currentLeaseIsValid && !this.isLocalClient(currentPrimary)) {
  16209. if (!this.forceOwningTab &&
  16210. (!this.allowTabSynchronization ||
  16211. !currentPrimary.allowTabSynchronization)) {
  16212. throw new FirestoreError(Code.FAILED_PRECONDITION, PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG);
  16213. }
  16214. }
  16215. });
  16216. }
  16217. /**
  16218. * Obtains or extends the new primary lease for the local client. This
  16219. * method does not verify that the client is eligible for this lease.
  16220. */
  16221. acquireOrExtendPrimaryLease(txn) {
  16222. const newPrimary = {
  16223. ownerId: this.clientId,
  16224. allowTabSynchronization: this.allowTabSynchronization,
  16225. leaseTimestampMs: Date.now()
  16226. };
  16227. return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
  16228. }
  16229. static isAvailable() {
  16230. return SimpleDb.isAvailable();
  16231. }
  16232. /** Checks the primary lease and removes it if we are the current primary. */
  16233. releasePrimaryLeaseIfHeld(txn) {
  16234. const store = primaryClientStore(txn);
  16235. return store.get(DbPrimaryClientKey).next(primaryClient => {
  16236. if (this.isLocalClient(primaryClient)) {
  16237. logDebug(LOG_TAG$c, 'Releasing primary lease.');
  16238. return store.delete(DbPrimaryClientKey);
  16239. }
  16240. else {
  16241. return PersistencePromise.resolve();
  16242. }
  16243. });
  16244. }
  16245. /** Verifies that `updateTimeMs` is within `maxAgeMs`. */
  16246. isWithinAge(updateTimeMs, maxAgeMs) {
  16247. const now = Date.now();
  16248. const minAcceptable = now - maxAgeMs;
  16249. const maxAcceptable = now;
  16250. if (updateTimeMs < minAcceptable) {
  16251. return false;
  16252. }
  16253. else if (updateTimeMs > maxAcceptable) {
  16254. logError(`Detected an update time that is in the future: ${updateTimeMs} > ${maxAcceptable}`);
  16255. return false;
  16256. }
  16257. return true;
  16258. }
  16259. attachVisibilityHandler() {
  16260. if (this.document !== null &&
  16261. typeof this.document.addEventListener === 'function') {
  16262. this.documentVisibilityHandler = () => {
  16263. this.queue.enqueueAndForget(() => {
  16264. this.inForeground = this.document.visibilityState === 'visible';
  16265. return this.updateClientMetadataAndTryBecomePrimary();
  16266. });
  16267. };
  16268. this.document.addEventListener('visibilitychange', this.documentVisibilityHandler);
  16269. this.inForeground = this.document.visibilityState === 'visible';
  16270. }
  16271. }
  16272. detachVisibilityHandler() {
  16273. if (this.documentVisibilityHandler) {
  16274. this.document.removeEventListener('visibilitychange', this.documentVisibilityHandler);
  16275. this.documentVisibilityHandler = null;
  16276. }
  16277. }
  16278. /**
  16279. * Attaches a window.unload handler that will synchronously write our
  16280. * clientId to a "zombie client id" location in LocalStorage. This can be used
  16281. * by tabs trying to acquire the primary lease to determine that the lease
  16282. * is no longer valid even if the timestamp is recent. This is particularly
  16283. * important for the refresh case (so the tab correctly re-acquires the
  16284. * primary lease). LocalStorage is used for this rather than IndexedDb because
  16285. * it is a synchronous API and so can be used reliably from an unload
  16286. * handler.
  16287. */
  16288. attachWindowUnloadHook() {
  16289. var _a;
  16290. if (typeof ((_a = this.window) === null || _a === void 0 ? void 0 : _a.addEventListener) === 'function') {
  16291. this.windowUnloadHandler = () => {
  16292. // Note: In theory, this should be scheduled on the AsyncQueue since it
  16293. // accesses internal state. We execute this code directly during shutdown
  16294. // to make sure it gets a chance to run.
  16295. this.markClientZombied();
  16296. const safariIndexdbBugVersionRegex = /(?:Version|Mobile)\/1[456]/;
  16297. if (isSafari() &&
  16298. (navigator.appVersion.match(safariIndexdbBugVersionRegex) ||
  16299. navigator.userAgent.match(safariIndexdbBugVersionRegex))) {
  16300. // On Safari 14, 15, and 16, we do not run any cleanup actions as it might
  16301. // trigger a bug that prevents Safari from re-opening IndexedDB during
  16302. // the next page load.
  16303. // See https://bugs.webkit.org/show_bug.cgi?id=226547
  16304. this.queue.enterRestrictedMode(/* purgeExistingTasks= */ true);
  16305. }
  16306. this.queue.enqueueAndForget(() => {
  16307. // Attempt graceful shutdown (including releasing our primary lease),
  16308. // but there's no guarantee it will complete.
  16309. return this.shutdown();
  16310. });
  16311. };
  16312. this.window.addEventListener('pagehide', this.windowUnloadHandler);
  16313. }
  16314. }
  16315. detachWindowUnloadHook() {
  16316. if (this.windowUnloadHandler) {
  16317. this.window.removeEventListener('pagehide', this.windowUnloadHandler);
  16318. this.windowUnloadHandler = null;
  16319. }
  16320. }
  16321. /**
  16322. * Returns whether a client is "zombied" based on its LocalStorage entry.
  16323. * Clients become zombied when their tab closes without running all of the
  16324. * cleanup logic in `shutdown()`.
  16325. */
  16326. isClientZombied(clientId) {
  16327. var _a;
  16328. try {
  16329. const isZombied = ((_a = this.webStorage) === null || _a === void 0 ? void 0 : _a.getItem(this.zombiedClientLocalStorageKey(clientId))) !== null;
  16330. logDebug(LOG_TAG$c, `Client '${clientId}' ${isZombied ? 'is' : 'is not'} zombied in LocalStorage`);
  16331. return isZombied;
  16332. }
  16333. catch (e) {
  16334. // Gracefully handle if LocalStorage isn't working.
  16335. logError(LOG_TAG$c, 'Failed to get zombied client id.', e);
  16336. return false;
  16337. }
  16338. }
  16339. /**
  16340. * Record client as zombied (a client that had its tab closed). Zombied
  16341. * clients are ignored during primary tab selection.
  16342. */
  16343. markClientZombied() {
  16344. if (!this.webStorage) {
  16345. return;
  16346. }
  16347. try {
  16348. this.webStorage.setItem(this.zombiedClientLocalStorageKey(this.clientId), String(Date.now()));
  16349. }
  16350. catch (e) {
  16351. // Gracefully handle if LocalStorage isn't available / working.
  16352. logError('Failed to set zombie client id.', e);
  16353. }
  16354. }
  16355. /** Removes the zombied client entry if it exists. */
  16356. removeClientZombiedEntry() {
  16357. if (!this.webStorage) {
  16358. return;
  16359. }
  16360. try {
  16361. this.webStorage.removeItem(this.zombiedClientLocalStorageKey(this.clientId));
  16362. }
  16363. catch (e) {
  16364. // Ignore
  16365. }
  16366. }
  16367. zombiedClientLocalStorageKey(clientId) {
  16368. return `${ZOMBIED_CLIENTS_KEY_PREFIX}_${this.persistenceKey}_${clientId}`;
  16369. }
  16370. }
  16371. /**
  16372. * Helper to get a typed SimpleDbStore for the primary client object store.
  16373. */
  16374. function primaryClientStore(txn) {
  16375. return getStore(txn, DbPrimaryClientStore);
  16376. }
  16377. /**
  16378. * Helper to get a typed SimpleDbStore for the client metadata object store.
  16379. */
  16380. function clientMetadataStore(txn) {
  16381. return getStore(txn, DbClientMetadataStore);
  16382. }
  16383. /**
  16384. * Generates a string used as a prefix when storing data in IndexedDB and
  16385. * LocalStorage.
  16386. */
  16387. function indexedDbStoragePrefix(databaseId, persistenceKey) {
  16388. // Use two different prefix formats:
  16389. //
  16390. // * firestore / persistenceKey / projectID . databaseID / ...
  16391. // * firestore / persistenceKey / projectID / ...
  16392. //
  16393. // projectIDs are DNS-compatible names and cannot contain dots
  16394. // so there's no danger of collisions.
  16395. let database = databaseId.projectId;
  16396. if (!databaseId.isDefaultDatabase) {
  16397. database += '.' + databaseId.database;
  16398. }
  16399. return 'firestore/' + persistenceKey + '/' + database + '/';
  16400. }
  16401. async function indexedDbClearPersistence(persistenceKey) {
  16402. if (!SimpleDb.isAvailable()) {
  16403. return Promise.resolve();
  16404. }
  16405. const dbName = persistenceKey + MAIN_DATABASE;
  16406. await SimpleDb.delete(dbName);
  16407. }
  16408. /**
  16409. * @license
  16410. * Copyright 2017 Google LLC
  16411. *
  16412. * Licensed under the Apache License, Version 2.0 (the "License");
  16413. * you may not use this file except in compliance with the License.
  16414. * You may obtain a copy of the License at
  16415. *
  16416. * http://www.apache.org/licenses/LICENSE-2.0
  16417. *
  16418. * Unless required by applicable law or agreed to in writing, software
  16419. * distributed under the License is distributed on an "AS IS" BASIS,
  16420. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16421. * See the License for the specific language governing permissions and
  16422. * limitations under the License.
  16423. */
  16424. /**
  16425. * Compares two array for equality using comparator. The method computes the
  16426. * intersection and invokes `onAdd` for every element that is in `after` but not
  16427. * `before`. `onRemove` is invoked for every element in `before` but missing
  16428. * from `after`.
  16429. *
  16430. * The method creates a copy of both `before` and `after` and runs in O(n log
  16431. * n), where n is the size of the two lists.
  16432. *
  16433. * @param before - The elements that exist in the original array.
  16434. * @param after - The elements to diff against the original array.
  16435. * @param comparator - The comparator for the elements in before and after.
  16436. * @param onAdd - A function to invoke for every element that is part of `
  16437. * after` but not `before`.
  16438. * @param onRemove - A function to invoke for every element that is part of
  16439. * `before` but not `after`.
  16440. */
  16441. function diffArrays(before, after, comparator, onAdd, onRemove) {
  16442. before = [...before];
  16443. after = [...after];
  16444. before.sort(comparator);
  16445. after.sort(comparator);
  16446. const bLen = before.length;
  16447. const aLen = after.length;
  16448. let a = 0;
  16449. let b = 0;
  16450. while (a < aLen && b < bLen) {
  16451. const cmp = comparator(before[b], after[a]);
  16452. if (cmp < 0) {
  16453. // The element was removed if the next element in our ordered
  16454. // walkthrough is only in `before`.
  16455. onRemove(before[b++]);
  16456. }
  16457. else if (cmp > 0) {
  16458. // The element was added if the next element in our ordered walkthrough
  16459. // is only in `after`.
  16460. onAdd(after[a++]);
  16461. }
  16462. else {
  16463. a++;
  16464. b++;
  16465. }
  16466. }
  16467. while (a < aLen) {
  16468. onAdd(after[a++]);
  16469. }
  16470. while (b < bLen) {
  16471. onRemove(before[b++]);
  16472. }
  16473. }
  16474. /**
  16475. * @license
  16476. * Copyright 2020 Google LLC
  16477. *
  16478. * Licensed under the Apache License, Version 2.0 (the "License");
  16479. * you may not use this file except in compliance with the License.
  16480. * You may obtain a copy of the License at
  16481. *
  16482. * http://www.apache.org/licenses/LICENSE-2.0
  16483. *
  16484. * Unless required by applicable law or agreed to in writing, software
  16485. * distributed under the License is distributed on an "AS IS" BASIS,
  16486. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16487. * See the License for the specific language governing permissions and
  16488. * limitations under the License.
  16489. */
  16490. const LOG_TAG$b = 'LocalStore';
  16491. /**
  16492. * The maximum time to leave a resume token buffered without writing it out.
  16493. * This value is arbitrary: it's long enough to avoid several writes
  16494. * (possibly indefinitely if updates come more frequently than this) but
  16495. * short enough that restarting after crashing will still have a pretty
  16496. * recent resume token.
  16497. */
  16498. const RESUME_TOKEN_MAX_AGE_MICROS = 5 * 60 * 1e6;
  16499. /**
  16500. * Implements `LocalStore` interface.
  16501. *
  16502. * Note: some field defined in this class might have public access level, but
  16503. * the class is not exported so they are only accessible from this module.
  16504. * This is useful to implement optional features (like bundles) in free
  16505. * functions, such that they are tree-shakeable.
  16506. */
  16507. class LocalStoreImpl {
  16508. constructor(
  16509. /** Manages our in-memory or durable persistence. */
  16510. persistence, queryEngine, initialUser, serializer) {
  16511. this.persistence = persistence;
  16512. this.queryEngine = queryEngine;
  16513. this.serializer = serializer;
  16514. /**
  16515. * Maps a targetID to data about its target.
  16516. *
  16517. * PORTING NOTE: We are using an immutable data structure on Web to make re-runs
  16518. * of `applyRemoteEvent()` idempotent.
  16519. */
  16520. this.targetDataByTarget = new SortedMap(primitiveComparator);
  16521. /** Maps a target to its targetID. */
  16522. // TODO(wuandy): Evaluate if TargetId can be part of Target.
  16523. this.targetIdByTarget = new ObjectMap(t => canonifyTarget(t), targetEquals);
  16524. /**
  16525. * A per collection group index of the last read time processed by
  16526. * `getNewDocumentChanges()`.
  16527. *
  16528. * PORTING NOTE: This is only used for multi-tab synchronization.
  16529. */
  16530. this.collectionGroupReadTime = new Map();
  16531. this.remoteDocuments = persistence.getRemoteDocumentCache();
  16532. this.targetCache = persistence.getTargetCache();
  16533. this.bundleCache = persistence.getBundleCache();
  16534. this.initializeUserComponents(initialUser);
  16535. }
  16536. initializeUserComponents(user) {
  16537. // TODO(indexing): Add spec tests that test these components change after a
  16538. // user change
  16539. this.documentOverlayCache = this.persistence.getDocumentOverlayCache(user);
  16540. this.indexManager = this.persistence.getIndexManager(user);
  16541. this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
  16542. this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.documentOverlayCache, this.indexManager);
  16543. this.remoteDocuments.setIndexManager(this.indexManager);
  16544. this.queryEngine.initialize(this.localDocuments, this.indexManager);
  16545. }
  16546. collectGarbage(garbageCollector) {
  16547. return this.persistence.runTransaction('Collect garbage', 'readwrite-primary', txn => garbageCollector.collect(txn, this.targetDataByTarget));
  16548. }
  16549. }
  16550. function newLocalStore(
  16551. /** Manages our in-memory or durable persistence. */
  16552. persistence, queryEngine, initialUser, serializer) {
  16553. return new LocalStoreImpl(persistence, queryEngine, initialUser, serializer);
  16554. }
  16555. /**
  16556. * Tells the LocalStore that the currently authenticated user has changed.
  16557. *
  16558. * In response the local store switches the mutation queue to the new user and
  16559. * returns any resulting document changes.
  16560. */
  16561. // PORTING NOTE: Android and iOS only return the documents affected by the
  16562. // change.
  16563. async function localStoreHandleUserChange(localStore, user) {
  16564. const localStoreImpl = debugCast(localStore);
  16565. const result = await localStoreImpl.persistence.runTransaction('Handle user change', 'readonly', txn => {
  16566. // Swap out the mutation queue, grabbing the pending mutation batches
  16567. // before and after.
  16568. let oldBatches;
  16569. return localStoreImpl.mutationQueue
  16570. .getAllMutationBatches(txn)
  16571. .next(promisedOldBatches => {
  16572. oldBatches = promisedOldBatches;
  16573. localStoreImpl.initializeUserComponents(user);
  16574. return localStoreImpl.mutationQueue.getAllMutationBatches(txn);
  16575. })
  16576. .next(newBatches => {
  16577. const removedBatchIds = [];
  16578. const addedBatchIds = [];
  16579. // Union the old/new changed keys.
  16580. let changedKeys = documentKeySet();
  16581. for (const batch of oldBatches) {
  16582. removedBatchIds.push(batch.batchId);
  16583. for (const mutation of batch.mutations) {
  16584. changedKeys = changedKeys.add(mutation.key);
  16585. }
  16586. }
  16587. for (const batch of newBatches) {
  16588. addedBatchIds.push(batch.batchId);
  16589. for (const mutation of batch.mutations) {
  16590. changedKeys = changedKeys.add(mutation.key);
  16591. }
  16592. }
  16593. // Return the set of all (potentially) changed documents and the list
  16594. // of mutation batch IDs that were affected by change.
  16595. return localStoreImpl.localDocuments
  16596. .getDocuments(txn, changedKeys)
  16597. .next(affectedDocuments => {
  16598. return {
  16599. affectedDocuments,
  16600. removedBatchIds,
  16601. addedBatchIds
  16602. };
  16603. });
  16604. });
  16605. });
  16606. return result;
  16607. }
  16608. /* Accepts locally generated Mutations and commit them to storage. */
  16609. function localStoreWriteLocally(localStore, mutations) {
  16610. const localStoreImpl = debugCast(localStore);
  16611. const localWriteTime = Timestamp.now();
  16612. const keys = mutations.reduce((keys, m) => keys.add(m.key), documentKeySet());
  16613. let overlayedDocuments;
  16614. let mutationBatch;
  16615. return localStoreImpl.persistence
  16616. .runTransaction('Locally write mutations', 'readwrite', txn => {
  16617. // Figure out which keys do not have a remote version in the cache, this
  16618. // is needed to create the right overlay mutation: if no remote version
  16619. // presents, we do not need to create overlays as patch mutations.
  16620. // TODO(Overlay): Is there a better way to determine this? Using the
  16621. // document version does not work because local mutations set them back
  16622. // to 0.
  16623. let remoteDocs = mutableDocumentMap();
  16624. let docsWithoutRemoteVersion = documentKeySet();
  16625. return localStoreImpl.remoteDocuments
  16626. .getEntries(txn, keys)
  16627. .next(docs => {
  16628. remoteDocs = docs;
  16629. remoteDocs.forEach((key, doc) => {
  16630. if (!doc.isValidDocument()) {
  16631. docsWithoutRemoteVersion = docsWithoutRemoteVersion.add(key);
  16632. }
  16633. });
  16634. })
  16635. .next(() => {
  16636. // Load and apply all existing mutations. This lets us compute the
  16637. // current base state for all non-idempotent transforms before applying
  16638. // any additional user-provided writes.
  16639. return localStoreImpl.localDocuments.getOverlayedDocuments(txn, remoteDocs);
  16640. })
  16641. .next((docs) => {
  16642. overlayedDocuments = docs;
  16643. // For non-idempotent mutations (such as `FieldValue.increment()`),
  16644. // we record the base state in a separate patch mutation. This is
  16645. // later used to guarantee consistent values and prevents flicker
  16646. // even if the backend sends us an update that already includes our
  16647. // transform.
  16648. const baseMutations = [];
  16649. for (const mutation of mutations) {
  16650. const baseValue = mutationExtractBaseValue(mutation, overlayedDocuments.get(mutation.key).overlayedDocument);
  16651. if (baseValue != null) {
  16652. // NOTE: The base state should only be applied if there's some
  16653. // existing document to override, so use a Precondition of
  16654. // exists=true
  16655. baseMutations.push(new PatchMutation(mutation.key, baseValue, extractFieldMask(baseValue.value.mapValue), Precondition.exists(true)));
  16656. }
  16657. }
  16658. return localStoreImpl.mutationQueue.addMutationBatch(txn, localWriteTime, baseMutations, mutations);
  16659. })
  16660. .next(batch => {
  16661. mutationBatch = batch;
  16662. const overlays = batch.applyToLocalDocumentSet(overlayedDocuments, docsWithoutRemoteVersion);
  16663. return localStoreImpl.documentOverlayCache.saveOverlays(txn, batch.batchId, overlays);
  16664. });
  16665. })
  16666. .then(() => ({
  16667. batchId: mutationBatch.batchId,
  16668. changes: convertOverlayedDocumentMapToDocumentMap(overlayedDocuments)
  16669. }));
  16670. }
  16671. /**
  16672. * Acknowledges the given batch.
  16673. *
  16674. * On the happy path when a batch is acknowledged, the local store will
  16675. *
  16676. * + remove the batch from the mutation queue;
  16677. * + apply the changes to the remote document cache;
  16678. * + recalculate the latency compensated view implied by those changes (there
  16679. * may be mutations in the queue that affect the documents but haven't been
  16680. * acknowledged yet); and
  16681. * + give the changed documents back the sync engine
  16682. *
  16683. * @returns The resulting (modified) documents.
  16684. */
  16685. function localStoreAcknowledgeBatch(localStore, batchResult) {
  16686. const localStoreImpl = debugCast(localStore);
  16687. return localStoreImpl.persistence.runTransaction('Acknowledge batch', 'readwrite-primary', txn => {
  16688. const affected = batchResult.batch.keys();
  16689. const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
  16690. trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
  16691. });
  16692. return applyWriteToRemoteDocuments(localStoreImpl, txn, batchResult, documentBuffer)
  16693. .next(() => documentBuffer.apply(txn))
  16694. .next(() => localStoreImpl.mutationQueue.performConsistencyCheck(txn))
  16695. .next(() => localStoreImpl.documentOverlayCache.removeOverlaysForBatchId(txn, affected, batchResult.batch.batchId))
  16696. .next(() => localStoreImpl.localDocuments.recalculateAndSaveOverlaysForDocumentKeys(txn, getKeysWithTransformResults(batchResult)))
  16697. .next(() => localStoreImpl.localDocuments.getDocuments(txn, affected));
  16698. });
  16699. }
  16700. function getKeysWithTransformResults(batchResult) {
  16701. let result = documentKeySet();
  16702. for (let i = 0; i < batchResult.mutationResults.length; ++i) {
  16703. const mutationResult = batchResult.mutationResults[i];
  16704. if (mutationResult.transformResults.length > 0) {
  16705. result = result.add(batchResult.batch.mutations[i].key);
  16706. }
  16707. }
  16708. return result;
  16709. }
  16710. /**
  16711. * Removes mutations from the MutationQueue for the specified batch;
  16712. * LocalDocuments will be recalculated.
  16713. *
  16714. * @returns The resulting modified documents.
  16715. */
  16716. function localStoreRejectBatch(localStore, batchId) {
  16717. const localStoreImpl = debugCast(localStore);
  16718. return localStoreImpl.persistence.runTransaction('Reject batch', 'readwrite-primary', txn => {
  16719. let affectedKeys;
  16720. return localStoreImpl.mutationQueue
  16721. .lookupMutationBatch(txn, batchId)
  16722. .next((batch) => {
  16723. hardAssert(batch !== null);
  16724. affectedKeys = batch.keys();
  16725. return localStoreImpl.mutationQueue.removeMutationBatch(txn, batch);
  16726. })
  16727. .next(() => localStoreImpl.mutationQueue.performConsistencyCheck(txn))
  16728. .next(() => localStoreImpl.documentOverlayCache.removeOverlaysForBatchId(txn, affectedKeys, batchId))
  16729. .next(() => localStoreImpl.localDocuments.recalculateAndSaveOverlaysForDocumentKeys(txn, affectedKeys))
  16730. .next(() => localStoreImpl.localDocuments.getDocuments(txn, affectedKeys));
  16731. });
  16732. }
  16733. /**
  16734. * Returns the largest (latest) batch id in mutation queue that is pending
  16735. * server response.
  16736. *
  16737. * Returns `BATCHID_UNKNOWN` if the queue is empty.
  16738. */
  16739. function localStoreGetHighestUnacknowledgedBatchId(localStore) {
  16740. const localStoreImpl = debugCast(localStore);
  16741. return localStoreImpl.persistence.runTransaction('Get highest unacknowledged batch id', 'readonly', txn => localStoreImpl.mutationQueue.getHighestUnacknowledgedBatchId(txn));
  16742. }
  16743. /**
  16744. * Returns the last consistent snapshot processed (used by the RemoteStore to
  16745. * determine whether to buffer incoming snapshots from the backend).
  16746. */
  16747. function localStoreGetLastRemoteSnapshotVersion(localStore) {
  16748. const localStoreImpl = debugCast(localStore);
  16749. return localStoreImpl.persistence.runTransaction('Get last remote snapshot version', 'readonly', txn => localStoreImpl.targetCache.getLastRemoteSnapshotVersion(txn));
  16750. }
  16751. /**
  16752. * Updates the "ground-state" (remote) documents. We assume that the remote
  16753. * event reflects any write batches that have been acknowledged or rejected
  16754. * (i.e. we do not re-apply local mutations to updates from this event).
  16755. *
  16756. * LocalDocuments are re-calculated if there are remaining mutations in the
  16757. * queue.
  16758. */
  16759. function localStoreApplyRemoteEventToLocalCache(localStore, remoteEvent) {
  16760. const localStoreImpl = debugCast(localStore);
  16761. const remoteVersion = remoteEvent.snapshotVersion;
  16762. let newTargetDataByTargetMap = localStoreImpl.targetDataByTarget;
  16763. return localStoreImpl.persistence
  16764. .runTransaction('Apply remote event', 'readwrite-primary', txn => {
  16765. const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
  16766. trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
  16767. });
  16768. // Reset newTargetDataByTargetMap in case this transaction gets re-run.
  16769. newTargetDataByTargetMap = localStoreImpl.targetDataByTarget;
  16770. const promises = [];
  16771. remoteEvent.targetChanges.forEach((change, targetId) => {
  16772. const oldTargetData = newTargetDataByTargetMap.get(targetId);
  16773. if (!oldTargetData) {
  16774. return;
  16775. }
  16776. // Only update the remote keys if the target is still active. This
  16777. // ensures that we can persist the updated target data along with
  16778. // the updated assignment.
  16779. promises.push(localStoreImpl.targetCache
  16780. .removeMatchingKeys(txn, change.removedDocuments, targetId)
  16781. .next(() => {
  16782. return localStoreImpl.targetCache.addMatchingKeys(txn, change.addedDocuments, targetId);
  16783. }));
  16784. let newTargetData = oldTargetData.withSequenceNumber(txn.currentSequenceNumber);
  16785. if (remoteEvent.targetMismatches.get(targetId) !== null) {
  16786. newTargetData = newTargetData
  16787. .withResumeToken(ByteString.EMPTY_BYTE_STRING, SnapshotVersion.min())
  16788. .withLastLimboFreeSnapshotVersion(SnapshotVersion.min());
  16789. }
  16790. else if (change.resumeToken.approximateByteSize() > 0) {
  16791. newTargetData = newTargetData.withResumeToken(change.resumeToken, remoteVersion);
  16792. }
  16793. newTargetDataByTargetMap = newTargetDataByTargetMap.insert(targetId, newTargetData);
  16794. // Update the target data if there are target changes (or if
  16795. // sufficient time has passed since the last update).
  16796. if (shouldPersistTargetData(oldTargetData, newTargetData, change)) {
  16797. promises.push(localStoreImpl.targetCache.updateTargetData(txn, newTargetData));
  16798. }
  16799. });
  16800. let changedDocs = mutableDocumentMap();
  16801. let existenceChangedKeys = documentKeySet();
  16802. remoteEvent.documentUpdates.forEach(key => {
  16803. if (remoteEvent.resolvedLimboDocuments.has(key)) {
  16804. promises.push(localStoreImpl.persistence.referenceDelegate.updateLimboDocument(txn, key));
  16805. }
  16806. });
  16807. // Each loop iteration only affects its "own" doc, so it's safe to get all
  16808. // the remote documents in advance in a single call.
  16809. promises.push(populateDocumentChangeBuffer(txn, documentBuffer, remoteEvent.documentUpdates).next(result => {
  16810. changedDocs = result.changedDocuments;
  16811. existenceChangedKeys = result.existenceChangedKeys;
  16812. }));
  16813. // HACK: The only reason we allow a null snapshot version is so that we
  16814. // can synthesize remote events when we get permission denied errors while
  16815. // trying to resolve the state of a locally cached document that is in
  16816. // limbo.
  16817. if (!remoteVersion.isEqual(SnapshotVersion.min())) {
  16818. const updateRemoteVersion = localStoreImpl.targetCache
  16819. .getLastRemoteSnapshotVersion(txn)
  16820. .next(lastRemoteSnapshotVersion => {
  16821. return localStoreImpl.targetCache.setTargetsMetadata(txn, txn.currentSequenceNumber, remoteVersion);
  16822. });
  16823. promises.push(updateRemoteVersion);
  16824. }
  16825. return PersistencePromise.waitFor(promises)
  16826. .next(() => documentBuffer.apply(txn))
  16827. .next(() => localStoreImpl.localDocuments.getLocalViewOfDocuments(txn, changedDocs, existenceChangedKeys))
  16828. .next(() => changedDocs);
  16829. })
  16830. .then(changedDocs => {
  16831. localStoreImpl.targetDataByTarget = newTargetDataByTargetMap;
  16832. return changedDocs;
  16833. });
  16834. }
  16835. /**
  16836. * Populates document change buffer with documents from backend or a bundle.
  16837. * Returns the document changes resulting from applying those documents, and
  16838. * also a set of documents whose existence state are changed as a result.
  16839. *
  16840. * @param txn - Transaction to use to read existing documents from storage.
  16841. * @param documentBuffer - Document buffer to collect the resulted changes to be
  16842. * applied to storage.
  16843. * @param documents - Documents to be applied.
  16844. */
  16845. function populateDocumentChangeBuffer(txn, documentBuffer, documents) {
  16846. let updatedKeys = documentKeySet();
  16847. let existenceChangedKeys = documentKeySet();
  16848. documents.forEach(k => (updatedKeys = updatedKeys.add(k)));
  16849. return documentBuffer.getEntries(txn, updatedKeys).next(existingDocs => {
  16850. let changedDocuments = mutableDocumentMap();
  16851. documents.forEach((key, doc) => {
  16852. const existingDoc = existingDocs.get(key);
  16853. // Check if see if there is a existence state change for this document.
  16854. if (doc.isFoundDocument() !== existingDoc.isFoundDocument()) {
  16855. existenceChangedKeys = existenceChangedKeys.add(key);
  16856. }
  16857. // Note: The order of the steps below is important, since we want
  16858. // to ensure that rejected limbo resolutions (which fabricate
  16859. // NoDocuments with SnapshotVersion.min()) never add documents to
  16860. // cache.
  16861. if (doc.isNoDocument() && doc.version.isEqual(SnapshotVersion.min())) {
  16862. // NoDocuments with SnapshotVersion.min() are used in manufactured
  16863. // events. We remove these documents from cache since we lost
  16864. // access.
  16865. documentBuffer.removeEntry(key, doc.readTime);
  16866. changedDocuments = changedDocuments.insert(key, doc);
  16867. }
  16868. else if (!existingDoc.isValidDocument() ||
  16869. doc.version.compareTo(existingDoc.version) > 0 ||
  16870. (doc.version.compareTo(existingDoc.version) === 0 &&
  16871. existingDoc.hasPendingWrites)) {
  16872. documentBuffer.addEntry(doc);
  16873. changedDocuments = changedDocuments.insert(key, doc);
  16874. }
  16875. else {
  16876. logDebug(LOG_TAG$b, 'Ignoring outdated watch update for ', key, '. Current version:', existingDoc.version, ' Watch version:', doc.version);
  16877. }
  16878. });
  16879. return { changedDocuments, existenceChangedKeys };
  16880. });
  16881. }
  16882. /**
  16883. * Returns true if the newTargetData should be persisted during an update of
  16884. * an active target. TargetData should always be persisted when a target is
  16885. * being released and should not call this function.
  16886. *
  16887. * While the target is active, TargetData updates can be omitted when nothing
  16888. * about the target has changed except metadata like the resume token or
  16889. * snapshot version. Occasionally it's worth the extra write to prevent these
  16890. * values from getting too stale after a crash, but this doesn't have to be
  16891. * too frequent.
  16892. */
  16893. function shouldPersistTargetData(oldTargetData, newTargetData, change) {
  16894. // Always persist target data if we don't already have a resume token.
  16895. if (oldTargetData.resumeToken.approximateByteSize() === 0) {
  16896. return true;
  16897. }
  16898. // Don't allow resume token changes to be buffered indefinitely. This
  16899. // allows us to be reasonably up-to-date after a crash and avoids needing
  16900. // to loop over all active queries on shutdown. Especially in the browser
  16901. // we may not get time to do anything interesting while the current tab is
  16902. // closing.
  16903. const timeDelta = newTargetData.snapshotVersion.toMicroseconds() -
  16904. oldTargetData.snapshotVersion.toMicroseconds();
  16905. if (timeDelta >= RESUME_TOKEN_MAX_AGE_MICROS) {
  16906. return true;
  16907. }
  16908. // Otherwise if the only thing that has changed about a target is its resume
  16909. // token it's not worth persisting. Note that the RemoteStore keeps an
  16910. // in-memory view of the currently active targets which includes the current
  16911. // resume token, so stream failure or user changes will still use an
  16912. // up-to-date resume token regardless of what we do here.
  16913. const changes = change.addedDocuments.size +
  16914. change.modifiedDocuments.size +
  16915. change.removedDocuments.size;
  16916. return changes > 0;
  16917. }
  16918. /**
  16919. * Notifies local store of the changed views to locally pin documents.
  16920. */
  16921. async function localStoreNotifyLocalViewChanges(localStore, viewChanges) {
  16922. const localStoreImpl = debugCast(localStore);
  16923. try {
  16924. await localStoreImpl.persistence.runTransaction('notifyLocalViewChanges', 'readwrite', txn => {
  16925. return PersistencePromise.forEach(viewChanges, (viewChange) => {
  16926. 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)));
  16927. });
  16928. });
  16929. }
  16930. catch (e) {
  16931. if (isIndexedDbTransactionError(e)) {
  16932. // If `notifyLocalViewChanges` fails, we did not advance the sequence
  16933. // number for the documents that were included in this transaction.
  16934. // This might trigger them to be deleted earlier than they otherwise
  16935. // would have, but it should not invalidate the integrity of the data.
  16936. logDebug(LOG_TAG$b, 'Failed to update sequence numbers: ' + e);
  16937. }
  16938. else {
  16939. throw e;
  16940. }
  16941. }
  16942. for (const viewChange of viewChanges) {
  16943. const targetId = viewChange.targetId;
  16944. if (!viewChange.fromCache) {
  16945. const targetData = localStoreImpl.targetDataByTarget.get(targetId);
  16946. // Advance the last limbo free snapshot version
  16947. const lastLimboFreeSnapshotVersion = targetData.snapshotVersion;
  16948. const updatedTargetData = targetData.withLastLimboFreeSnapshotVersion(lastLimboFreeSnapshotVersion);
  16949. localStoreImpl.targetDataByTarget =
  16950. localStoreImpl.targetDataByTarget.insert(targetId, updatedTargetData);
  16951. // TODO(b/272564316): Apply the optimization done on other platforms.
  16952. // This is a problem for web because saving the updated targetData from
  16953. // non-primary client conflicts with what primary client saved.
  16954. }
  16955. }
  16956. }
  16957. /**
  16958. * Gets the mutation batch after the passed in batchId in the mutation queue
  16959. * or null if empty.
  16960. * @param afterBatchId - If provided, the batch to search after.
  16961. * @returns The next mutation or null if there wasn't one.
  16962. */
  16963. function localStoreGetNextMutationBatch(localStore, afterBatchId) {
  16964. const localStoreImpl = debugCast(localStore);
  16965. return localStoreImpl.persistence.runTransaction('Get next mutation batch', 'readonly', txn => {
  16966. if (afterBatchId === undefined) {
  16967. afterBatchId = BATCHID_UNKNOWN;
  16968. }
  16969. return localStoreImpl.mutationQueue.getNextMutationBatchAfterBatchId(txn, afterBatchId);
  16970. });
  16971. }
  16972. /**
  16973. * Reads the current value of a Document with a given key or null if not
  16974. * found - used for testing.
  16975. */
  16976. function localStoreReadDocument(localStore, key) {
  16977. const localStoreImpl = debugCast(localStore);
  16978. return localStoreImpl.persistence.runTransaction('read document', 'readonly', txn => localStoreImpl.localDocuments.getDocument(txn, key));
  16979. }
  16980. /**
  16981. * Assigns the given target an internal ID so that its results can be pinned so
  16982. * they don't get GC'd. A target must be allocated in the local store before
  16983. * the store can be used to manage its view.
  16984. *
  16985. * Allocating an already allocated `Target` will return the existing `TargetData`
  16986. * for that `Target`.
  16987. */
  16988. function localStoreAllocateTarget(localStore, target) {
  16989. const localStoreImpl = debugCast(localStore);
  16990. return localStoreImpl.persistence
  16991. .runTransaction('Allocate target', 'readwrite', txn => {
  16992. let targetData;
  16993. return localStoreImpl.targetCache
  16994. .getTargetData(txn, target)
  16995. .next((cached) => {
  16996. if (cached) {
  16997. // This target has been listened to previously, so reuse the
  16998. // previous targetID.
  16999. // TODO(mcg): freshen last accessed date?
  17000. targetData = cached;
  17001. return PersistencePromise.resolve(targetData);
  17002. }
  17003. else {
  17004. return localStoreImpl.targetCache
  17005. .allocateTargetId(txn)
  17006. .next(targetId => {
  17007. targetData = new TargetData(target, targetId, "TargetPurposeListen" /* TargetPurpose.Listen */, txn.currentSequenceNumber);
  17008. return localStoreImpl.targetCache
  17009. .addTargetData(txn, targetData)
  17010. .next(() => targetData);
  17011. });
  17012. }
  17013. });
  17014. })
  17015. .then(targetData => {
  17016. // If Multi-Tab is enabled, the existing target data may be newer than
  17017. // the in-memory data
  17018. const cachedTargetData = localStoreImpl.targetDataByTarget.get(targetData.targetId);
  17019. if (cachedTargetData === null ||
  17020. targetData.snapshotVersion.compareTo(cachedTargetData.snapshotVersion) >
  17021. 0) {
  17022. localStoreImpl.targetDataByTarget =
  17023. localStoreImpl.targetDataByTarget.insert(targetData.targetId, targetData);
  17024. localStoreImpl.targetIdByTarget.set(target, targetData.targetId);
  17025. }
  17026. return targetData;
  17027. });
  17028. }
  17029. /**
  17030. * Returns the TargetData as seen by the LocalStore, including updates that may
  17031. * have not yet been persisted to the TargetCache.
  17032. */
  17033. // Visible for testing.
  17034. function localStoreGetTargetData(localStore, transaction, target) {
  17035. const localStoreImpl = debugCast(localStore);
  17036. const targetId = localStoreImpl.targetIdByTarget.get(target);
  17037. if (targetId !== undefined) {
  17038. return PersistencePromise.resolve(localStoreImpl.targetDataByTarget.get(targetId));
  17039. }
  17040. else {
  17041. return localStoreImpl.targetCache.getTargetData(transaction, target);
  17042. }
  17043. }
  17044. /**
  17045. * Unpins all the documents associated with the given target. If
  17046. * `keepPersistedTargetData` is set to false and Eager GC enabled, the method
  17047. * directly removes the associated target data from the target cache.
  17048. *
  17049. * Releasing a non-existing `Target` is a no-op.
  17050. */
  17051. // PORTING NOTE: `keepPersistedTargetData` is multi-tab only.
  17052. async function localStoreReleaseTarget(localStore, targetId, keepPersistedTargetData) {
  17053. const localStoreImpl = debugCast(localStore);
  17054. const targetData = localStoreImpl.targetDataByTarget.get(targetId);
  17055. const mode = keepPersistedTargetData ? 'readwrite' : 'readwrite-primary';
  17056. try {
  17057. if (!keepPersistedTargetData) {
  17058. await localStoreImpl.persistence.runTransaction('Release target', mode, txn => {
  17059. return localStoreImpl.persistence.referenceDelegate.removeTarget(txn, targetData);
  17060. });
  17061. }
  17062. }
  17063. catch (e) {
  17064. if (isIndexedDbTransactionError(e)) {
  17065. // All `releaseTarget` does is record the final metadata state for the
  17066. // target, but we've been recording this periodically during target
  17067. // activity. If we lose this write this could cause a very slight
  17068. // difference in the order of target deletion during GC, but we
  17069. // don't define exact LRU semantics so this is acceptable.
  17070. logDebug(LOG_TAG$b, `Failed to update sequence numbers for target ${targetId}: ${e}`);
  17071. }
  17072. else {
  17073. throw e;
  17074. }
  17075. }
  17076. localStoreImpl.targetDataByTarget =
  17077. localStoreImpl.targetDataByTarget.remove(targetId);
  17078. localStoreImpl.targetIdByTarget.delete(targetData.target);
  17079. }
  17080. /**
  17081. * Runs the specified query against the local store and returns the results,
  17082. * potentially taking advantage of query data from previous executions (such
  17083. * as the set of remote keys).
  17084. *
  17085. * @param usePreviousResults - Whether results from previous executions can
  17086. * be used to optimize this query execution.
  17087. */
  17088. function localStoreExecuteQuery(localStore, query, usePreviousResults) {
  17089. const localStoreImpl = debugCast(localStore);
  17090. let lastLimboFreeSnapshotVersion = SnapshotVersion.min();
  17091. let remoteKeys = documentKeySet();
  17092. return localStoreImpl.persistence.runTransaction('Execute query', 'readonly', txn => {
  17093. return localStoreGetTargetData(localStoreImpl, txn, queryToTarget(query))
  17094. .next(targetData => {
  17095. if (targetData) {
  17096. lastLimboFreeSnapshotVersion =
  17097. targetData.lastLimboFreeSnapshotVersion;
  17098. return localStoreImpl.targetCache
  17099. .getMatchingKeysForTargetId(txn, targetData.targetId)
  17100. .next(result => {
  17101. remoteKeys = result;
  17102. });
  17103. }
  17104. })
  17105. .next(() => localStoreImpl.queryEngine.getDocumentsMatchingQuery(txn, query, usePreviousResults
  17106. ? lastLimboFreeSnapshotVersion
  17107. : SnapshotVersion.min(), usePreviousResults ? remoteKeys : documentKeySet()))
  17108. .next(documents => {
  17109. setMaxReadTime(localStoreImpl, queryCollectionGroup(query), documents);
  17110. return { documents, remoteKeys };
  17111. });
  17112. });
  17113. }
  17114. function applyWriteToRemoteDocuments(localStoreImpl, txn, batchResult, documentBuffer) {
  17115. const batch = batchResult.batch;
  17116. const docKeys = batch.keys();
  17117. let promiseChain = PersistencePromise.resolve();
  17118. docKeys.forEach(docKey => {
  17119. promiseChain = promiseChain
  17120. .next(() => documentBuffer.getEntry(txn, docKey))
  17121. .next(doc => {
  17122. const ackVersion = batchResult.docVersions.get(docKey);
  17123. hardAssert(ackVersion !== null);
  17124. if (doc.version.compareTo(ackVersion) < 0) {
  17125. batch.applyToRemoteDocument(doc, batchResult);
  17126. if (doc.isValidDocument()) {
  17127. // We use the commitVersion as the readTime rather than the
  17128. // document's updateTime since the updateTime is not advanced
  17129. // for updates that do not modify the underlying document.
  17130. doc.setReadTime(batchResult.commitVersion);
  17131. documentBuffer.addEntry(doc);
  17132. }
  17133. }
  17134. });
  17135. });
  17136. return promiseChain.next(() => localStoreImpl.mutationQueue.removeMutationBatch(txn, batch));
  17137. }
  17138. /** Returns the local view of the documents affected by a mutation batch. */
  17139. // PORTING NOTE: Multi-Tab only.
  17140. function localStoreLookupMutationDocuments(localStore, batchId) {
  17141. const localStoreImpl = debugCast(localStore);
  17142. const mutationQueueImpl = debugCast(localStoreImpl.mutationQueue);
  17143. return localStoreImpl.persistence.runTransaction('Lookup mutation documents', 'readonly', txn => {
  17144. return mutationQueueImpl.lookupMutationKeys(txn, batchId).next(keys => {
  17145. if (keys) {
  17146. return localStoreImpl.localDocuments.getDocuments(txn, keys);
  17147. }
  17148. else {
  17149. return PersistencePromise.resolve(null);
  17150. }
  17151. });
  17152. });
  17153. }
  17154. // PORTING NOTE: Multi-Tab only.
  17155. function localStoreRemoveCachedMutationBatchMetadata(localStore, batchId) {
  17156. const mutationQueueImpl = debugCast(debugCast(localStore, LocalStoreImpl).mutationQueue);
  17157. mutationQueueImpl.removeCachedMutationKeys(batchId);
  17158. }
  17159. // PORTING NOTE: Multi-Tab only.
  17160. function localStoreGetActiveClients(localStore) {
  17161. const persistenceImpl = debugCast(debugCast(localStore, LocalStoreImpl).persistence);
  17162. return persistenceImpl.getActiveClients();
  17163. }
  17164. // PORTING NOTE: Multi-Tab only.
  17165. function localStoreGetCachedTarget(localStore, targetId) {
  17166. const localStoreImpl = debugCast(localStore);
  17167. const targetCacheImpl = debugCast(localStoreImpl.targetCache);
  17168. const cachedTargetData = localStoreImpl.targetDataByTarget.get(targetId);
  17169. if (cachedTargetData) {
  17170. return Promise.resolve(cachedTargetData.target);
  17171. }
  17172. else {
  17173. return localStoreImpl.persistence.runTransaction('Get target data', 'readonly', txn => {
  17174. return targetCacheImpl
  17175. .getTargetDataForTarget(txn, targetId)
  17176. .next(targetData => (targetData ? targetData.target : null));
  17177. });
  17178. }
  17179. }
  17180. /**
  17181. * Returns the set of documents that have been updated since the last call.
  17182. * If this is the first call, returns the set of changes since client
  17183. * initialization. Further invocations will return document that have changed
  17184. * since the prior call.
  17185. */
  17186. // PORTING NOTE: Multi-Tab only.
  17187. function localStoreGetNewDocumentChanges(localStore, collectionGroup) {
  17188. const localStoreImpl = debugCast(localStore);
  17189. // Get the current maximum read time for the collection. This should always
  17190. // exist, but to reduce the chance for regressions we default to
  17191. // SnapshotVersion.Min()
  17192. // TODO(indexing): Consider removing the default value.
  17193. const readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
  17194. SnapshotVersion.min();
  17195. return localStoreImpl.persistence
  17196. .runTransaction('Get new document changes', 'readonly', txn => localStoreImpl.remoteDocuments.getAllFromCollectionGroup(txn, collectionGroup, newIndexOffsetSuccessorFromReadTime(readTime, INITIAL_LARGEST_BATCH_ID),
  17197. /* limit= */ Number.MAX_SAFE_INTEGER))
  17198. .then(changedDocs => {
  17199. setMaxReadTime(localStoreImpl, collectionGroup, changedDocs);
  17200. return changedDocs;
  17201. });
  17202. }
  17203. /** Sets the collection group's maximum read time from the given documents. */
  17204. // PORTING NOTE: Multi-Tab only.
  17205. function setMaxReadTime(localStoreImpl, collectionGroup, changedDocs) {
  17206. let readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
  17207. SnapshotVersion.min();
  17208. changedDocs.forEach((_, doc) => {
  17209. if (doc.readTime.compareTo(readTime) > 0) {
  17210. readTime = doc.readTime;
  17211. }
  17212. });
  17213. localStoreImpl.collectionGroupReadTime.set(collectionGroup, readTime);
  17214. }
  17215. /**
  17216. * Creates a new target using the given bundle name, which will be used to
  17217. * hold the keys of all documents from the bundle in query-document mappings.
  17218. * This ensures that the loaded documents do not get garbage collected
  17219. * right away.
  17220. */
  17221. function umbrellaTarget(bundleName) {
  17222. // It is OK that the path used for the query is not valid, because this will
  17223. // not be read and queried.
  17224. return queryToTarget(newQueryForPath(ResourcePath.fromString(`__bundle__/docs/${bundleName}`)));
  17225. }
  17226. /**
  17227. * Applies the documents from a bundle to the "ground-state" (remote)
  17228. * documents.
  17229. *
  17230. * LocalDocuments are re-calculated if there are remaining mutations in the
  17231. * queue.
  17232. */
  17233. async function localStoreApplyBundledDocuments(localStore, bundleConverter, documents, bundleName) {
  17234. const localStoreImpl = debugCast(localStore);
  17235. let documentKeys = documentKeySet();
  17236. let documentMap = mutableDocumentMap();
  17237. for (const bundleDoc of documents) {
  17238. const documentKey = bundleConverter.toDocumentKey(bundleDoc.metadata.name);
  17239. if (bundleDoc.document) {
  17240. documentKeys = documentKeys.add(documentKey);
  17241. }
  17242. const doc = bundleConverter.toMutableDocument(bundleDoc);
  17243. doc.setReadTime(bundleConverter.toSnapshotVersion(bundleDoc.metadata.readTime));
  17244. documentMap = documentMap.insert(documentKey, doc);
  17245. }
  17246. const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
  17247. trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
  17248. });
  17249. // Allocates a target to hold all document keys from the bundle, such that
  17250. // they will not get garbage collected right away.
  17251. const umbrellaTargetData = await localStoreAllocateTarget(localStoreImpl, umbrellaTarget(bundleName));
  17252. return localStoreImpl.persistence.runTransaction('Apply bundle documents', 'readwrite', txn => {
  17253. return populateDocumentChangeBuffer(txn, documentBuffer, documentMap)
  17254. .next(documentChangeResult => {
  17255. documentBuffer.apply(txn);
  17256. return documentChangeResult;
  17257. })
  17258. .next(documentChangeResult => {
  17259. return localStoreImpl.targetCache
  17260. .removeMatchingKeysForTargetId(txn, umbrellaTargetData.targetId)
  17261. .next(() => localStoreImpl.targetCache.addMatchingKeys(txn, documentKeys, umbrellaTargetData.targetId))
  17262. .next(() => localStoreImpl.localDocuments.getLocalViewOfDocuments(txn, documentChangeResult.changedDocuments, documentChangeResult.existenceChangedKeys))
  17263. .next(() => documentChangeResult.changedDocuments);
  17264. });
  17265. });
  17266. }
  17267. /**
  17268. * Returns a promise of a boolean to indicate if the given bundle has already
  17269. * been loaded and the create time is newer than the current loading bundle.
  17270. */
  17271. function localStoreHasNewerBundle(localStore, bundleMetadata) {
  17272. const localStoreImpl = debugCast(localStore);
  17273. const currentReadTime = fromVersion(bundleMetadata.createTime);
  17274. return localStoreImpl.persistence
  17275. .runTransaction('hasNewerBundle', 'readonly', transaction => {
  17276. return localStoreImpl.bundleCache.getBundleMetadata(transaction, bundleMetadata.id);
  17277. })
  17278. .then(cached => {
  17279. return !!cached && cached.createTime.compareTo(currentReadTime) >= 0;
  17280. });
  17281. }
  17282. /**
  17283. * Saves the given `BundleMetadata` to local persistence.
  17284. */
  17285. function localStoreSaveBundle(localStore, bundleMetadata) {
  17286. const localStoreImpl = debugCast(localStore);
  17287. return localStoreImpl.persistence.runTransaction('Save bundle', 'readwrite', transaction => {
  17288. return localStoreImpl.bundleCache.saveBundleMetadata(transaction, bundleMetadata);
  17289. });
  17290. }
  17291. /**
  17292. * Returns a promise of a `NamedQuery` associated with given query name. Promise
  17293. * resolves to undefined if no persisted data can be found.
  17294. */
  17295. function localStoreGetNamedQuery(localStore, queryName) {
  17296. const localStoreImpl = debugCast(localStore);
  17297. return localStoreImpl.persistence.runTransaction('Get named query', 'readonly', transaction => localStoreImpl.bundleCache.getNamedQuery(transaction, queryName));
  17298. }
  17299. /**
  17300. * Saves the given `NamedQuery` to local persistence.
  17301. */
  17302. async function localStoreSaveNamedQuery(localStore, query, documents = documentKeySet()) {
  17303. // Allocate a target for the named query such that it can be resumed
  17304. // from associated read time if users use it to listen.
  17305. // NOTE: this also means if no corresponding target exists, the new target
  17306. // will remain active and will not get collected, unless users happen to
  17307. // unlisten the query somehow.
  17308. const allocated = await localStoreAllocateTarget(localStore, queryToTarget(fromBundledQuery(query.bundledQuery)));
  17309. const localStoreImpl = debugCast(localStore);
  17310. return localStoreImpl.persistence.runTransaction('Save named query', 'readwrite', transaction => {
  17311. const readTime = fromVersion(query.readTime);
  17312. // Simply save the query itself if it is older than what the SDK already
  17313. // has.
  17314. if (allocated.snapshotVersion.compareTo(readTime) >= 0) {
  17315. return localStoreImpl.bundleCache.saveNamedQuery(transaction, query);
  17316. }
  17317. // Update existing target data because the query from the bundle is newer.
  17318. const newTargetData = allocated.withResumeToken(ByteString.EMPTY_BYTE_STRING, readTime);
  17319. localStoreImpl.targetDataByTarget =
  17320. localStoreImpl.targetDataByTarget.insert(newTargetData.targetId, newTargetData);
  17321. return localStoreImpl.targetCache
  17322. .updateTargetData(transaction, newTargetData)
  17323. .next(() => localStoreImpl.targetCache.removeMatchingKeysForTargetId(transaction, allocated.targetId))
  17324. .next(() => localStoreImpl.targetCache.addMatchingKeys(transaction, documents, allocated.targetId))
  17325. .next(() => localStoreImpl.bundleCache.saveNamedQuery(transaction, query));
  17326. });
  17327. }
  17328. async function localStoreConfigureFieldIndexes(localStore, newFieldIndexes) {
  17329. const localStoreImpl = debugCast(localStore);
  17330. const indexManager = localStoreImpl.indexManager;
  17331. const promises = [];
  17332. return localStoreImpl.persistence.runTransaction('Configure indexes', 'readwrite', transaction => indexManager
  17333. .getFieldIndexes(transaction)
  17334. .next(oldFieldIndexes => diffArrays(oldFieldIndexes, newFieldIndexes, fieldIndexSemanticComparator, fieldIndex => {
  17335. promises.push(indexManager.addFieldIndex(transaction, fieldIndex));
  17336. }, fieldIndex => {
  17337. promises.push(indexManager.deleteFieldIndex(transaction, fieldIndex));
  17338. }))
  17339. .next(() => PersistencePromise.waitFor(promises)));
  17340. }
  17341. /**
  17342. * @license
  17343. * Copyright 2019 Google LLC
  17344. *
  17345. * Licensed under the Apache License, Version 2.0 (the "License");
  17346. * you may not use this file except in compliance with the License.
  17347. * You may obtain a copy of the License at
  17348. *
  17349. * http://www.apache.org/licenses/LICENSE-2.0
  17350. *
  17351. * Unless required by applicable law or agreed to in writing, software
  17352. * distributed under the License is distributed on an "AS IS" BASIS,
  17353. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17354. * See the License for the specific language governing permissions and
  17355. * limitations under the License.
  17356. */
  17357. /**
  17358. * The Firestore query engine.
  17359. *
  17360. * Firestore queries can be executed in three modes. The Query Engine determines
  17361. * what mode to use based on what data is persisted. The mode only determines
  17362. * the runtime complexity of the query - the result set is equivalent across all
  17363. * implementations.
  17364. *
  17365. * The Query engine will use indexed-based execution if a user has configured
  17366. * any index that can be used to execute query (via `setIndexConfiguration()`).
  17367. * Otherwise, the engine will try to optimize the query by re-using a previously
  17368. * persisted query result. If that is not possible, the query will be executed
  17369. * via a full collection scan.
  17370. *
  17371. * Index-based execution is the default when available. The query engine
  17372. * supports partial indexed execution and merges the result from the index
  17373. * lookup with documents that have not yet been indexed. The index evaluation
  17374. * matches the backend's format and as such, the SDK can use indexing for all
  17375. * queries that the backend supports.
  17376. *
  17377. * If no index exists, the query engine tries to take advantage of the target
  17378. * document mapping in the TargetCache. These mappings exists for all queries
  17379. * that have been synced with the backend at least once and allow the query
  17380. * engine to only read documents that previously matched a query plus any
  17381. * documents that were edited after the query was last listened to.
  17382. *
  17383. * There are some cases when this optimization is not guaranteed to produce
  17384. * the same results as full collection scans. In these cases, query
  17385. * processing falls back to full scans. These cases are:
  17386. *
  17387. * - Limit queries where a document that matched the query previously no longer
  17388. * matches the query.
  17389. *
  17390. * - Limit queries where a document edit may cause the document to sort below
  17391. * another document that is in the local cache.
  17392. *
  17393. * - Queries that have never been CURRENT or free of limbo documents.
  17394. */
  17395. class QueryEngine {
  17396. constructor() {
  17397. this.initialized = false;
  17398. }
  17399. /** Sets the document view to query against. */
  17400. initialize(localDocuments, indexManager) {
  17401. this.localDocumentsView = localDocuments;
  17402. this.indexManager = indexManager;
  17403. this.initialized = true;
  17404. }
  17405. /** Returns all local documents matching the specified query. */
  17406. getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion, remoteKeys) {
  17407. return this.performQueryUsingIndex(transaction, query)
  17408. .next(result => result
  17409. ? result
  17410. : this.performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion))
  17411. .next(result => result ? result : this.executeFullCollectionScan(transaction, query));
  17412. }
  17413. /**
  17414. * Performs an indexed query that evaluates the query based on a collection's
  17415. * persisted index values. Returns `null` if an index is not available.
  17416. */
  17417. performQueryUsingIndex(transaction, query) {
  17418. if (queryMatchesAllDocuments(query)) {
  17419. // Queries that match all documents don't benefit from using
  17420. // key-based lookups. It is more efficient to scan all documents in a
  17421. // collection, rather than to perform individual lookups.
  17422. return PersistencePromise.resolve(null);
  17423. }
  17424. let target = queryToTarget(query);
  17425. return this.indexManager
  17426. .getIndexType(transaction, target)
  17427. .next(indexType => {
  17428. if (indexType === 0 /* IndexType.NONE */) {
  17429. // The target cannot be served from any index.
  17430. return null;
  17431. }
  17432. if (query.limit !== null && indexType === 1 /* IndexType.PARTIAL */) {
  17433. // We cannot apply a limit for targets that are served using a partial
  17434. // index. If a partial index will be used to serve the target, the
  17435. // query may return a superset of documents that match the target
  17436. // (e.g. if the index doesn't include all the target's filters), or
  17437. // may return the correct set of documents in the wrong order (e.g. if
  17438. // the index doesn't include a segment for one of the orderBys).
  17439. // Therefore, a limit should not be applied in such cases.
  17440. query = queryWithLimit(query, null, "F" /* LimitType.First */);
  17441. target = queryToTarget(query);
  17442. }
  17443. return this.indexManager
  17444. .getDocumentsMatchingTarget(transaction, target)
  17445. .next(keys => {
  17446. const sortedKeys = documentKeySet(...keys);
  17447. return this.localDocumentsView
  17448. .getDocuments(transaction, sortedKeys)
  17449. .next(indexedDocuments => {
  17450. return this.indexManager
  17451. .getMinOffset(transaction, target)
  17452. .next(offset => {
  17453. const previousResults = this.applyQuery(query, indexedDocuments);
  17454. if (this.needsRefill(query, previousResults, sortedKeys, offset.readTime)) {
  17455. // A limit query whose boundaries change due to local
  17456. // edits can be re-run against the cache by excluding the
  17457. // limit. This ensures that all documents that match the
  17458. // query's filters are included in the result set. The SDK
  17459. // can then apply the limit once all local edits are
  17460. // incorporated.
  17461. return this.performQueryUsingIndex(transaction, queryWithLimit(query, null, "F" /* LimitType.First */));
  17462. }
  17463. return this.appendRemainingResults(transaction, previousResults, query, offset);
  17464. });
  17465. });
  17466. });
  17467. });
  17468. }
  17469. /**
  17470. * Performs a query based on the target's persisted query mapping. Returns
  17471. * `null` if the mapping is not available or cannot be used.
  17472. */
  17473. performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion) {
  17474. if (queryMatchesAllDocuments(query)) {
  17475. // Queries that match all documents don't benefit from using
  17476. // key-based lookups. It is more efficient to scan all documents in a
  17477. // collection, rather than to perform individual lookups.
  17478. return this.executeFullCollectionScan(transaction, query);
  17479. }
  17480. // Queries that have never seen a snapshot without limbo free documents
  17481. // should also be run as a full collection scan.
  17482. if (lastLimboFreeSnapshotVersion.isEqual(SnapshotVersion.min())) {
  17483. return this.executeFullCollectionScan(transaction, query);
  17484. }
  17485. return this.localDocumentsView.getDocuments(transaction, remoteKeys).next(documents => {
  17486. const previousResults = this.applyQuery(query, documents);
  17487. if (this.needsRefill(query, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
  17488. return this.executeFullCollectionScan(transaction, query);
  17489. }
  17490. if (getLogLevel() <= LogLevel.DEBUG) {
  17491. logDebug('QueryEngine', 'Re-using previous result from %s to execute query: %s', lastLimboFreeSnapshotVersion.toString(), stringifyQuery(query));
  17492. }
  17493. // Retrieve all results for documents that were updated since the last
  17494. // limbo-document free remote snapshot.
  17495. return this.appendRemainingResults(transaction, previousResults, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID));
  17496. });
  17497. }
  17498. /** Applies the query filter and sorting to the provided documents. */
  17499. applyQuery(query, documents) {
  17500. // Sort the documents and re-apply the query filter since previously
  17501. // matching documents do not necessarily still match the query.
  17502. let queryResults = new SortedSet(newQueryComparator(query));
  17503. documents.forEach((_, maybeDoc) => {
  17504. if (queryMatches(query, maybeDoc)) {
  17505. queryResults = queryResults.add(maybeDoc);
  17506. }
  17507. });
  17508. return queryResults;
  17509. }
  17510. /**
  17511. * Determines if a limit query needs to be refilled from cache, making it
  17512. * ineligible for index-free execution.
  17513. *
  17514. * @param query - The query.
  17515. * @param sortedPreviousResults - The documents that matched the query when it
  17516. * was last synchronized, sorted by the query's comparator.
  17517. * @param remoteKeys - The document keys that matched the query at the last
  17518. * snapshot.
  17519. * @param limboFreeSnapshotVersion - The version of the snapshot when the
  17520. * query was last synchronized.
  17521. */
  17522. needsRefill(query, sortedPreviousResults, remoteKeys, limboFreeSnapshotVersion) {
  17523. if (query.limit === null) {
  17524. // Queries without limits do not need to be refilled.
  17525. return false;
  17526. }
  17527. if (remoteKeys.size !== sortedPreviousResults.size) {
  17528. // The query needs to be refilled if a previously matching document no
  17529. // longer matches.
  17530. return true;
  17531. }
  17532. // Limit queries are not eligible for index-free query execution if there is
  17533. // a potential that an older document from cache now sorts before a document
  17534. // that was previously part of the limit. This, however, can only happen if
  17535. // the document at the edge of the limit goes out of limit.
  17536. // If a document that is not the limit boundary sorts differently,
  17537. // the boundary of the limit itself did not change and documents from cache
  17538. // will continue to be "rejected" by this boundary. Therefore, we can ignore
  17539. // any modifications that don't affect the last document.
  17540. const docAtLimitEdge = query.limitType === "F" /* LimitType.First */
  17541. ? sortedPreviousResults.last()
  17542. : sortedPreviousResults.first();
  17543. if (!docAtLimitEdge) {
  17544. // We don't need to refill the query if there were already no documents.
  17545. return false;
  17546. }
  17547. return (docAtLimitEdge.hasPendingWrites ||
  17548. docAtLimitEdge.version.compareTo(limboFreeSnapshotVersion) > 0);
  17549. }
  17550. executeFullCollectionScan(transaction, query) {
  17551. if (getLogLevel() <= LogLevel.DEBUG) {
  17552. logDebug('QueryEngine', 'Using full collection scan to execute query:', stringifyQuery(query));
  17553. }
  17554. return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
  17555. }
  17556. /**
  17557. * Combines the results from an indexed execution with the remaining documents
  17558. * that have not yet been indexed.
  17559. */
  17560. appendRemainingResults(transaction, indexedResults, query, offset) {
  17561. // Retrieve all results for documents that were updated since the offset.
  17562. return this.localDocumentsView
  17563. .getDocumentsMatchingQuery(transaction, query, offset)
  17564. .next(remainingResults => {
  17565. // Merge with existing results
  17566. indexedResults.forEach(d => {
  17567. remainingResults = remainingResults.insert(d.key, d);
  17568. });
  17569. return remainingResults;
  17570. });
  17571. }
  17572. }
  17573. /**
  17574. * @license
  17575. * Copyright 2019 Google LLC
  17576. *
  17577. * Licensed under the Apache License, Version 2.0 (the "License");
  17578. * you may not use this file except in compliance with the License.
  17579. * You may obtain a copy of the License at
  17580. *
  17581. * http://www.apache.org/licenses/LICENSE-2.0
  17582. *
  17583. * Unless required by applicable law or agreed to in writing, software
  17584. * distributed under the License is distributed on an "AS IS" BASIS,
  17585. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17586. * See the License for the specific language governing permissions and
  17587. * limitations under the License.
  17588. */
  17589. // The format of the LocalStorage key that stores the client state is:
  17590. // firestore_clients_<persistence_prefix>_<instance_key>
  17591. const CLIENT_STATE_KEY_PREFIX = 'firestore_clients';
  17592. /** Assembles the key for a client state in WebStorage */
  17593. function createWebStorageClientStateKey(persistenceKey, clientId) {
  17594. return `${CLIENT_STATE_KEY_PREFIX}_${persistenceKey}_${clientId}`;
  17595. }
  17596. // The format of the WebStorage key that stores the mutation state is:
  17597. // firestore_mutations_<persistence_prefix>_<batch_id>
  17598. // (for unauthenticated users)
  17599. // or: firestore_mutations_<persistence_prefix>_<batch_id>_<user_uid>
  17600. //
  17601. // 'user_uid' is last to avoid needing to escape '_' characters that it might
  17602. // contain.
  17603. const MUTATION_BATCH_KEY_PREFIX = 'firestore_mutations';
  17604. /** Assembles the key for a mutation batch in WebStorage */
  17605. function createWebStorageMutationBatchKey(persistenceKey, user, batchId) {
  17606. let mutationKey = `${MUTATION_BATCH_KEY_PREFIX}_${persistenceKey}_${batchId}`;
  17607. if (user.isAuthenticated()) {
  17608. mutationKey += `_${user.uid}`;
  17609. }
  17610. return mutationKey;
  17611. }
  17612. // The format of the WebStorage key that stores a query target's metadata is:
  17613. // firestore_targets_<persistence_prefix>_<target_id>
  17614. const QUERY_TARGET_KEY_PREFIX = 'firestore_targets';
  17615. /** Assembles the key for a query state in WebStorage */
  17616. function createWebStorageQueryTargetMetadataKey(persistenceKey, targetId) {
  17617. return `${QUERY_TARGET_KEY_PREFIX}_${persistenceKey}_${targetId}`;
  17618. }
  17619. // The WebStorage prefix that stores the primary tab's online state. The
  17620. // format of the key is:
  17621. // firestore_online_state_<persistence_prefix>
  17622. const ONLINE_STATE_KEY_PREFIX = 'firestore_online_state';
  17623. /** Assembles the key for the online state of the primary tab. */
  17624. function createWebStorageOnlineStateKey(persistenceKey) {
  17625. return `${ONLINE_STATE_KEY_PREFIX}_${persistenceKey}`;
  17626. }
  17627. // The WebStorage prefix that plays as a event to indicate the remote documents
  17628. // might have changed due to some secondary tabs loading a bundle.
  17629. // format of the key is:
  17630. // firestore_bundle_loaded_v2_<persistenceKey>
  17631. // The version ending with "v2" stores the list of modified collection groups.
  17632. const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded_v2';
  17633. function createBundleLoadedKey(persistenceKey) {
  17634. return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
  17635. }
  17636. // The WebStorage key prefix for the key that stores the last sequence number allocated. The key
  17637. // looks like 'firestore_sequence_number_<persistence_prefix>'.
  17638. const SEQUENCE_NUMBER_KEY_PREFIX = 'firestore_sequence_number';
  17639. /** Assembles the key for the current sequence number. */
  17640. function createWebStorageSequenceNumberKey(persistenceKey) {
  17641. return `${SEQUENCE_NUMBER_KEY_PREFIX}_${persistenceKey}`;
  17642. }
  17643. /**
  17644. * @license
  17645. * Copyright 2018 Google LLC
  17646. *
  17647. * Licensed under the Apache License, Version 2.0 (the "License");
  17648. * you may not use this file except in compliance with the License.
  17649. * You may obtain a copy of the License at
  17650. *
  17651. * http://www.apache.org/licenses/LICENSE-2.0
  17652. *
  17653. * Unless required by applicable law or agreed to in writing, software
  17654. * distributed under the License is distributed on an "AS IS" BASIS,
  17655. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17656. * See the License for the specific language governing permissions and
  17657. * limitations under the License.
  17658. */
  17659. const LOG_TAG$a = 'SharedClientState';
  17660. /**
  17661. * Holds the state of a mutation batch, including its user ID, batch ID and
  17662. * whether the batch is 'pending', 'acknowledged' or 'rejected'.
  17663. */
  17664. // Visible for testing
  17665. class MutationMetadata {
  17666. constructor(user, batchId, state, error) {
  17667. this.user = user;
  17668. this.batchId = batchId;
  17669. this.state = state;
  17670. this.error = error;
  17671. }
  17672. /**
  17673. * Parses a MutationMetadata from its JSON representation in WebStorage.
  17674. * Logs a warning and returns null if the format of the data is not valid.
  17675. */
  17676. static fromWebStorageEntry(user, batchId, value) {
  17677. const mutationBatch = JSON.parse(value);
  17678. let validData = typeof mutationBatch === 'object' &&
  17679. ['pending', 'acknowledged', 'rejected'].indexOf(mutationBatch.state) !==
  17680. -1 &&
  17681. (mutationBatch.error === undefined ||
  17682. typeof mutationBatch.error === 'object');
  17683. let firestoreError = undefined;
  17684. if (validData && mutationBatch.error) {
  17685. validData =
  17686. typeof mutationBatch.error.message === 'string' &&
  17687. typeof mutationBatch.error.code === 'string';
  17688. if (validData) {
  17689. firestoreError = new FirestoreError(mutationBatch.error.code, mutationBatch.error.message);
  17690. }
  17691. }
  17692. if (validData) {
  17693. return new MutationMetadata(user, batchId, mutationBatch.state, firestoreError);
  17694. }
  17695. else {
  17696. logError(LOG_TAG$a, `Failed to parse mutation state for ID '${batchId}': ${value}`);
  17697. return null;
  17698. }
  17699. }
  17700. toWebStorageJSON() {
  17701. const batchMetadata = {
  17702. state: this.state,
  17703. updateTimeMs: Date.now() // Modify the existing value to trigger update.
  17704. };
  17705. if (this.error) {
  17706. batchMetadata.error = {
  17707. code: this.error.code,
  17708. message: this.error.message
  17709. };
  17710. }
  17711. return JSON.stringify(batchMetadata);
  17712. }
  17713. }
  17714. /**
  17715. * Holds the state of a query target, including its target ID and whether the
  17716. * target is 'not-current', 'current' or 'rejected'.
  17717. */
  17718. // Visible for testing
  17719. class QueryTargetMetadata {
  17720. constructor(targetId, state, error) {
  17721. this.targetId = targetId;
  17722. this.state = state;
  17723. this.error = error;
  17724. }
  17725. /**
  17726. * Parses a QueryTargetMetadata from its JSON representation in WebStorage.
  17727. * Logs a warning and returns null if the format of the data is not valid.
  17728. */
  17729. static fromWebStorageEntry(targetId, value) {
  17730. const targetState = JSON.parse(value);
  17731. let validData = typeof targetState === 'object' &&
  17732. ['not-current', 'current', 'rejected'].indexOf(targetState.state) !==
  17733. -1 &&
  17734. (targetState.error === undefined ||
  17735. typeof targetState.error === 'object');
  17736. let firestoreError = undefined;
  17737. if (validData && targetState.error) {
  17738. validData =
  17739. typeof targetState.error.message === 'string' &&
  17740. typeof targetState.error.code === 'string';
  17741. if (validData) {
  17742. firestoreError = new FirestoreError(targetState.error.code, targetState.error.message);
  17743. }
  17744. }
  17745. if (validData) {
  17746. return new QueryTargetMetadata(targetId, targetState.state, firestoreError);
  17747. }
  17748. else {
  17749. logError(LOG_TAG$a, `Failed to parse target state for ID '${targetId}': ${value}`);
  17750. return null;
  17751. }
  17752. }
  17753. toWebStorageJSON() {
  17754. const targetState = {
  17755. state: this.state,
  17756. updateTimeMs: Date.now() // Modify the existing value to trigger update.
  17757. };
  17758. if (this.error) {
  17759. targetState.error = {
  17760. code: this.error.code,
  17761. message: this.error.message
  17762. };
  17763. }
  17764. return JSON.stringify(targetState);
  17765. }
  17766. }
  17767. /**
  17768. * This class represents the immutable ClientState for a client read from
  17769. * WebStorage, containing the list of active query targets.
  17770. */
  17771. class RemoteClientState {
  17772. constructor(clientId, activeTargetIds) {
  17773. this.clientId = clientId;
  17774. this.activeTargetIds = activeTargetIds;
  17775. }
  17776. /**
  17777. * Parses a RemoteClientState from the JSON representation in WebStorage.
  17778. * Logs a warning and returns null if the format of the data is not valid.
  17779. */
  17780. static fromWebStorageEntry(clientId, value) {
  17781. const clientState = JSON.parse(value);
  17782. let validData = typeof clientState === 'object' &&
  17783. clientState.activeTargetIds instanceof Array;
  17784. let activeTargetIdsSet = targetIdSet();
  17785. for (let i = 0; validData && i < clientState.activeTargetIds.length; ++i) {
  17786. validData = isSafeInteger(clientState.activeTargetIds[i]);
  17787. activeTargetIdsSet = activeTargetIdsSet.add(clientState.activeTargetIds[i]);
  17788. }
  17789. if (validData) {
  17790. return new RemoteClientState(clientId, activeTargetIdsSet);
  17791. }
  17792. else {
  17793. logError(LOG_TAG$a, `Failed to parse client data for instance '${clientId}': ${value}`);
  17794. return null;
  17795. }
  17796. }
  17797. }
  17798. /**
  17799. * This class represents the online state for all clients participating in
  17800. * multi-tab. The online state is only written to by the primary client, and
  17801. * used in secondary clients to update their query views.
  17802. */
  17803. class SharedOnlineState {
  17804. constructor(clientId, onlineState) {
  17805. this.clientId = clientId;
  17806. this.onlineState = onlineState;
  17807. }
  17808. /**
  17809. * Parses a SharedOnlineState from its JSON representation in WebStorage.
  17810. * Logs a warning and returns null if the format of the data is not valid.
  17811. */
  17812. static fromWebStorageEntry(value) {
  17813. const onlineState = JSON.parse(value);
  17814. const validData = typeof onlineState === 'object' &&
  17815. ['Unknown', 'Online', 'Offline'].indexOf(onlineState.onlineState) !==
  17816. -1 &&
  17817. typeof onlineState.clientId === 'string';
  17818. if (validData) {
  17819. return new SharedOnlineState(onlineState.clientId, onlineState.onlineState);
  17820. }
  17821. else {
  17822. logError(LOG_TAG$a, `Failed to parse online state: ${value}`);
  17823. return null;
  17824. }
  17825. }
  17826. }
  17827. /**
  17828. * Metadata state of the local client. Unlike `RemoteClientState`, this class is
  17829. * mutable and keeps track of all pending mutations, which allows us to
  17830. * update the range of pending mutation batch IDs as new mutations are added or
  17831. * removed.
  17832. *
  17833. * The data in `LocalClientState` is not read from WebStorage and instead
  17834. * updated via its instance methods. The updated state can be serialized via
  17835. * `toWebStorageJSON()`.
  17836. */
  17837. // Visible for testing.
  17838. class LocalClientState {
  17839. constructor() {
  17840. this.activeTargetIds = targetIdSet();
  17841. }
  17842. addQueryTarget(targetId) {
  17843. this.activeTargetIds = this.activeTargetIds.add(targetId);
  17844. }
  17845. removeQueryTarget(targetId) {
  17846. this.activeTargetIds = this.activeTargetIds.delete(targetId);
  17847. }
  17848. /**
  17849. * Converts this entry into a JSON-encoded format we can use for WebStorage.
  17850. * Does not encode `clientId` as it is part of the key in WebStorage.
  17851. */
  17852. toWebStorageJSON() {
  17853. const data = {
  17854. activeTargetIds: this.activeTargetIds.toArray(),
  17855. updateTimeMs: Date.now() // Modify the existing value to trigger update.
  17856. };
  17857. return JSON.stringify(data);
  17858. }
  17859. }
  17860. /**
  17861. * `WebStorageSharedClientState` uses WebStorage (window.localStorage) as the
  17862. * backing store for the SharedClientState. It keeps track of all active
  17863. * clients and supports modifications of the local client's data.
  17864. */
  17865. class WebStorageSharedClientState {
  17866. constructor(window, queue, persistenceKey, localClientId, initialUser) {
  17867. this.window = window;
  17868. this.queue = queue;
  17869. this.persistenceKey = persistenceKey;
  17870. this.localClientId = localClientId;
  17871. this.syncEngine = null;
  17872. this.onlineStateHandler = null;
  17873. this.sequenceNumberHandler = null;
  17874. this.storageListener = this.handleWebStorageEvent.bind(this);
  17875. this.activeClients = new SortedMap(primitiveComparator);
  17876. this.started = false;
  17877. /**
  17878. * Captures WebStorage events that occur before `start()` is called. These
  17879. * events are replayed once `WebStorageSharedClientState` is started.
  17880. */
  17881. this.earlyEvents = [];
  17882. // Escape the special characters mentioned here:
  17883. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
  17884. const escapedPersistenceKey = persistenceKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  17885. this.storage = this.window.localStorage;
  17886. this.currentUser = initialUser;
  17887. this.localClientStorageKey = createWebStorageClientStateKey(this.persistenceKey, this.localClientId);
  17888. this.sequenceNumberKey = createWebStorageSequenceNumberKey(this.persistenceKey);
  17889. this.activeClients = this.activeClients.insert(this.localClientId, new LocalClientState());
  17890. this.clientStateKeyRe = new RegExp(`^${CLIENT_STATE_KEY_PREFIX}_${escapedPersistenceKey}_([^_]*)$`);
  17891. this.mutationBatchKeyRe = new RegExp(`^${MUTATION_BATCH_KEY_PREFIX}_${escapedPersistenceKey}_(\\d+)(?:_(.*))?$`);
  17892. this.queryTargetKeyRe = new RegExp(`^${QUERY_TARGET_KEY_PREFIX}_${escapedPersistenceKey}_(\\d+)$`);
  17893. this.onlineStateKey = createWebStorageOnlineStateKey(this.persistenceKey);
  17894. this.bundleLoadedKey = createBundleLoadedKey(this.persistenceKey);
  17895. // Rather than adding the storage observer during start(), we add the
  17896. // storage observer during initialization. This ensures that we collect
  17897. // events before other components populate their initial state (during their
  17898. // respective start() calls). Otherwise, we might for example miss a
  17899. // mutation that is added after LocalStore's start() processed the existing
  17900. // mutations but before we observe WebStorage events.
  17901. this.window.addEventListener('storage', this.storageListener);
  17902. }
  17903. /** Returns 'true' if WebStorage is available in the current environment. */
  17904. static isAvailable(window) {
  17905. return !!(window && window.localStorage);
  17906. }
  17907. async start() {
  17908. // Retrieve the list of existing clients to backfill the data in
  17909. // SharedClientState.
  17910. const existingClients = await this.syncEngine.getActiveClients();
  17911. for (const clientId of existingClients) {
  17912. if (clientId === this.localClientId) {
  17913. continue;
  17914. }
  17915. const storageItem = this.getItem(createWebStorageClientStateKey(this.persistenceKey, clientId));
  17916. if (storageItem) {
  17917. const clientState = RemoteClientState.fromWebStorageEntry(clientId, storageItem);
  17918. if (clientState) {
  17919. this.activeClients = this.activeClients.insert(clientState.clientId, clientState);
  17920. }
  17921. }
  17922. }
  17923. this.persistClientState();
  17924. // Check if there is an existing online state and call the callback handler
  17925. // if applicable.
  17926. const onlineStateJSON = this.storage.getItem(this.onlineStateKey);
  17927. if (onlineStateJSON) {
  17928. const onlineState = this.fromWebStorageOnlineState(onlineStateJSON);
  17929. if (onlineState) {
  17930. this.handleOnlineStateEvent(onlineState);
  17931. }
  17932. }
  17933. for (const event of this.earlyEvents) {
  17934. this.handleWebStorageEvent(event);
  17935. }
  17936. this.earlyEvents = [];
  17937. // Register a window unload hook to remove the client metadata entry from
  17938. // WebStorage even if `shutdown()` was not called.
  17939. this.window.addEventListener('pagehide', () => this.shutdown());
  17940. this.started = true;
  17941. }
  17942. writeSequenceNumber(sequenceNumber) {
  17943. this.setItem(this.sequenceNumberKey, JSON.stringify(sequenceNumber));
  17944. }
  17945. getAllActiveQueryTargets() {
  17946. return this.extractActiveQueryTargets(this.activeClients);
  17947. }
  17948. isActiveQueryTarget(targetId) {
  17949. let found = false;
  17950. this.activeClients.forEach((key, value) => {
  17951. if (value.activeTargetIds.has(targetId)) {
  17952. found = true;
  17953. }
  17954. });
  17955. return found;
  17956. }
  17957. addPendingMutation(batchId) {
  17958. this.persistMutationState(batchId, 'pending');
  17959. }
  17960. updateMutationState(batchId, state, error) {
  17961. this.persistMutationState(batchId, state, error);
  17962. // Once a final mutation result is observed by other clients, they no longer
  17963. // access the mutation's metadata entry. Since WebStorage replays events
  17964. // in order, it is safe to delete the entry right after updating it.
  17965. this.removeMutationState(batchId);
  17966. }
  17967. addLocalQueryTarget(targetId) {
  17968. let queryState = 'not-current';
  17969. // Lookup an existing query state if the target ID was already registered
  17970. // by another tab
  17971. if (this.isActiveQueryTarget(targetId)) {
  17972. const storageItem = this.storage.getItem(createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId));
  17973. if (storageItem) {
  17974. const metadata = QueryTargetMetadata.fromWebStorageEntry(targetId, storageItem);
  17975. if (metadata) {
  17976. queryState = metadata.state;
  17977. }
  17978. }
  17979. }
  17980. this.localClientState.addQueryTarget(targetId);
  17981. this.persistClientState();
  17982. return queryState;
  17983. }
  17984. removeLocalQueryTarget(targetId) {
  17985. this.localClientState.removeQueryTarget(targetId);
  17986. this.persistClientState();
  17987. }
  17988. isLocalQueryTarget(targetId) {
  17989. return this.localClientState.activeTargetIds.has(targetId);
  17990. }
  17991. clearQueryState(targetId) {
  17992. this.removeItem(createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId));
  17993. }
  17994. updateQueryState(targetId, state, error) {
  17995. this.persistQueryTargetState(targetId, state, error);
  17996. }
  17997. handleUserChange(user, removedBatchIds, addedBatchIds) {
  17998. removedBatchIds.forEach(batchId => {
  17999. this.removeMutationState(batchId);
  18000. });
  18001. this.currentUser = user;
  18002. addedBatchIds.forEach(batchId => {
  18003. this.addPendingMutation(batchId);
  18004. });
  18005. }
  18006. setOnlineState(onlineState) {
  18007. this.persistOnlineState(onlineState);
  18008. }
  18009. notifyBundleLoaded(collectionGroups) {
  18010. this.persistBundleLoadedState(collectionGroups);
  18011. }
  18012. shutdown() {
  18013. if (this.started) {
  18014. this.window.removeEventListener('storage', this.storageListener);
  18015. this.removeItem(this.localClientStorageKey);
  18016. this.started = false;
  18017. }
  18018. }
  18019. getItem(key) {
  18020. const value = this.storage.getItem(key);
  18021. logDebug(LOG_TAG$a, 'READ', key, value);
  18022. return value;
  18023. }
  18024. setItem(key, value) {
  18025. logDebug(LOG_TAG$a, 'SET', key, value);
  18026. this.storage.setItem(key, value);
  18027. }
  18028. removeItem(key) {
  18029. logDebug(LOG_TAG$a, 'REMOVE', key);
  18030. this.storage.removeItem(key);
  18031. }
  18032. handleWebStorageEvent(event) {
  18033. // Note: The function is typed to take Event to be interface-compatible with
  18034. // `Window.addEventListener`.
  18035. const storageEvent = event;
  18036. if (storageEvent.storageArea === this.storage) {
  18037. logDebug(LOG_TAG$a, 'EVENT', storageEvent.key, storageEvent.newValue);
  18038. if (storageEvent.key === this.localClientStorageKey) {
  18039. logError('Received WebStorage notification for local change. Another client might have ' +
  18040. 'garbage-collected our state');
  18041. return;
  18042. }
  18043. this.queue.enqueueRetryable(async () => {
  18044. if (!this.started) {
  18045. this.earlyEvents.push(storageEvent);
  18046. return;
  18047. }
  18048. if (storageEvent.key === null) {
  18049. return;
  18050. }
  18051. if (this.clientStateKeyRe.test(storageEvent.key)) {
  18052. if (storageEvent.newValue != null) {
  18053. const clientState = this.fromWebStorageClientState(storageEvent.key, storageEvent.newValue);
  18054. if (clientState) {
  18055. return this.handleClientStateEvent(clientState.clientId, clientState);
  18056. }
  18057. }
  18058. else {
  18059. const clientId = this.fromWebStorageClientStateKey(storageEvent.key);
  18060. return this.handleClientStateEvent(clientId, null);
  18061. }
  18062. }
  18063. else if (this.mutationBatchKeyRe.test(storageEvent.key)) {
  18064. if (storageEvent.newValue !== null) {
  18065. const mutationMetadata = this.fromWebStorageMutationMetadata(storageEvent.key, storageEvent.newValue);
  18066. if (mutationMetadata) {
  18067. return this.handleMutationBatchEvent(mutationMetadata);
  18068. }
  18069. }
  18070. }
  18071. else if (this.queryTargetKeyRe.test(storageEvent.key)) {
  18072. if (storageEvent.newValue !== null) {
  18073. const queryTargetMetadata = this.fromWebStorageQueryTargetMetadata(storageEvent.key, storageEvent.newValue);
  18074. if (queryTargetMetadata) {
  18075. return this.handleQueryTargetEvent(queryTargetMetadata);
  18076. }
  18077. }
  18078. }
  18079. else if (storageEvent.key === this.onlineStateKey) {
  18080. if (storageEvent.newValue !== null) {
  18081. const onlineState = this.fromWebStorageOnlineState(storageEvent.newValue);
  18082. if (onlineState) {
  18083. return this.handleOnlineStateEvent(onlineState);
  18084. }
  18085. }
  18086. }
  18087. else if (storageEvent.key === this.sequenceNumberKey) {
  18088. const sequenceNumber = fromWebStorageSequenceNumber(storageEvent.newValue);
  18089. if (sequenceNumber !== ListenSequence.INVALID) {
  18090. this.sequenceNumberHandler(sequenceNumber);
  18091. }
  18092. }
  18093. else if (storageEvent.key === this.bundleLoadedKey) {
  18094. const collectionGroups = this.fromWebStoreBundleLoadedState(storageEvent.newValue);
  18095. await Promise.all(collectionGroups.map(cg => this.syncEngine.synchronizeWithChangedDocuments(cg)));
  18096. }
  18097. });
  18098. }
  18099. }
  18100. get localClientState() {
  18101. return this.activeClients.get(this.localClientId);
  18102. }
  18103. persistClientState() {
  18104. this.setItem(this.localClientStorageKey, this.localClientState.toWebStorageJSON());
  18105. }
  18106. persistMutationState(batchId, state, error) {
  18107. const mutationState = new MutationMetadata(this.currentUser, batchId, state, error);
  18108. const mutationKey = createWebStorageMutationBatchKey(this.persistenceKey, this.currentUser, batchId);
  18109. this.setItem(mutationKey, mutationState.toWebStorageJSON());
  18110. }
  18111. removeMutationState(batchId) {
  18112. const mutationKey = createWebStorageMutationBatchKey(this.persistenceKey, this.currentUser, batchId);
  18113. this.removeItem(mutationKey);
  18114. }
  18115. persistOnlineState(onlineState) {
  18116. const entry = {
  18117. clientId: this.localClientId,
  18118. onlineState
  18119. };
  18120. this.storage.setItem(this.onlineStateKey, JSON.stringify(entry));
  18121. }
  18122. persistQueryTargetState(targetId, state, error) {
  18123. const targetKey = createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId);
  18124. const targetMetadata = new QueryTargetMetadata(targetId, state, error);
  18125. this.setItem(targetKey, targetMetadata.toWebStorageJSON());
  18126. }
  18127. persistBundleLoadedState(collectionGroups) {
  18128. const json = JSON.stringify(Array.from(collectionGroups));
  18129. this.setItem(this.bundleLoadedKey, json);
  18130. }
  18131. /**
  18132. * Parses a client state key in WebStorage. Returns null if the key does not
  18133. * match the expected key format.
  18134. */
  18135. fromWebStorageClientStateKey(key) {
  18136. const match = this.clientStateKeyRe.exec(key);
  18137. return match ? match[1] : null;
  18138. }
  18139. /**
  18140. * Parses a client state in WebStorage. Returns 'null' if the value could not
  18141. * be parsed.
  18142. */
  18143. fromWebStorageClientState(key, value) {
  18144. const clientId = this.fromWebStorageClientStateKey(key);
  18145. return RemoteClientState.fromWebStorageEntry(clientId, value);
  18146. }
  18147. /**
  18148. * Parses a mutation batch state in WebStorage. Returns 'null' if the value
  18149. * could not be parsed.
  18150. */
  18151. fromWebStorageMutationMetadata(key, value) {
  18152. const match = this.mutationBatchKeyRe.exec(key);
  18153. const batchId = Number(match[1]);
  18154. const userId = match[2] !== undefined ? match[2] : null;
  18155. return MutationMetadata.fromWebStorageEntry(new User(userId), batchId, value);
  18156. }
  18157. /**
  18158. * Parses a query target state from WebStorage. Returns 'null' if the value
  18159. * could not be parsed.
  18160. */
  18161. fromWebStorageQueryTargetMetadata(key, value) {
  18162. const match = this.queryTargetKeyRe.exec(key);
  18163. const targetId = Number(match[1]);
  18164. return QueryTargetMetadata.fromWebStorageEntry(targetId, value);
  18165. }
  18166. /**
  18167. * Parses an online state from WebStorage. Returns 'null' if the value
  18168. * could not be parsed.
  18169. */
  18170. fromWebStorageOnlineState(value) {
  18171. return SharedOnlineState.fromWebStorageEntry(value);
  18172. }
  18173. fromWebStoreBundleLoadedState(value) {
  18174. return JSON.parse(value);
  18175. }
  18176. async handleMutationBatchEvent(mutationBatch) {
  18177. if (mutationBatch.user.uid !== this.currentUser.uid) {
  18178. logDebug(LOG_TAG$a, `Ignoring mutation for non-active user ${mutationBatch.user.uid}`);
  18179. return;
  18180. }
  18181. return this.syncEngine.applyBatchState(mutationBatch.batchId, mutationBatch.state, mutationBatch.error);
  18182. }
  18183. handleQueryTargetEvent(targetMetadata) {
  18184. return this.syncEngine.applyTargetState(targetMetadata.targetId, targetMetadata.state, targetMetadata.error);
  18185. }
  18186. handleClientStateEvent(clientId, clientState) {
  18187. const updatedClients = clientState
  18188. ? this.activeClients.insert(clientId, clientState)
  18189. : this.activeClients.remove(clientId);
  18190. const existingTargets = this.extractActiveQueryTargets(this.activeClients);
  18191. const newTargets = this.extractActiveQueryTargets(updatedClients);
  18192. const addedTargets = [];
  18193. const removedTargets = [];
  18194. newTargets.forEach(targetId => {
  18195. if (!existingTargets.has(targetId)) {
  18196. addedTargets.push(targetId);
  18197. }
  18198. });
  18199. existingTargets.forEach(targetId => {
  18200. if (!newTargets.has(targetId)) {
  18201. removedTargets.push(targetId);
  18202. }
  18203. });
  18204. return this.syncEngine.applyActiveTargetsChange(addedTargets, removedTargets).then(() => {
  18205. this.activeClients = updatedClients;
  18206. });
  18207. }
  18208. handleOnlineStateEvent(onlineState) {
  18209. // We check whether the client that wrote this online state is still active
  18210. // by comparing its client ID to the list of clients kept active in
  18211. // IndexedDb. If a client does not update their IndexedDb client state
  18212. // within 5 seconds, it is considered inactive and we don't emit an online
  18213. // state event.
  18214. if (this.activeClients.get(onlineState.clientId)) {
  18215. this.onlineStateHandler(onlineState.onlineState);
  18216. }
  18217. }
  18218. extractActiveQueryTargets(clients) {
  18219. let activeTargets = targetIdSet();
  18220. clients.forEach((kev, value) => {
  18221. activeTargets = activeTargets.unionWith(value.activeTargetIds);
  18222. });
  18223. return activeTargets;
  18224. }
  18225. }
  18226. function fromWebStorageSequenceNumber(seqString) {
  18227. let sequenceNumber = ListenSequence.INVALID;
  18228. if (seqString != null) {
  18229. try {
  18230. const parsed = JSON.parse(seqString);
  18231. hardAssert(typeof parsed === 'number');
  18232. sequenceNumber = parsed;
  18233. }
  18234. catch (e) {
  18235. logError(LOG_TAG$a, 'Failed to read sequence number from WebStorage', e);
  18236. }
  18237. }
  18238. return sequenceNumber;
  18239. }
  18240. /**
  18241. * `MemorySharedClientState` is a simple implementation of SharedClientState for
  18242. * clients using memory persistence. The state in this class remains fully
  18243. * isolated and no synchronization is performed.
  18244. */
  18245. class MemorySharedClientState {
  18246. constructor() {
  18247. this.localState = new LocalClientState();
  18248. this.queryState = {};
  18249. this.onlineStateHandler = null;
  18250. this.sequenceNumberHandler = null;
  18251. }
  18252. addPendingMutation(batchId) {
  18253. // No op.
  18254. }
  18255. updateMutationState(batchId, state, error) {
  18256. // No op.
  18257. }
  18258. addLocalQueryTarget(targetId) {
  18259. this.localState.addQueryTarget(targetId);
  18260. return this.queryState[targetId] || 'not-current';
  18261. }
  18262. updateQueryState(targetId, state, error) {
  18263. this.queryState[targetId] = state;
  18264. }
  18265. removeLocalQueryTarget(targetId) {
  18266. this.localState.removeQueryTarget(targetId);
  18267. }
  18268. isLocalQueryTarget(targetId) {
  18269. return this.localState.activeTargetIds.has(targetId);
  18270. }
  18271. clearQueryState(targetId) {
  18272. delete this.queryState[targetId];
  18273. }
  18274. getAllActiveQueryTargets() {
  18275. return this.localState.activeTargetIds;
  18276. }
  18277. isActiveQueryTarget(targetId) {
  18278. return this.localState.activeTargetIds.has(targetId);
  18279. }
  18280. start() {
  18281. this.localState = new LocalClientState();
  18282. return Promise.resolve();
  18283. }
  18284. handleUserChange(user, removedBatchIds, addedBatchIds) {
  18285. // No op.
  18286. }
  18287. setOnlineState(onlineState) {
  18288. // No op.
  18289. }
  18290. shutdown() { }
  18291. writeSequenceNumber(sequenceNumber) { }
  18292. notifyBundleLoaded(collectionGroups) {
  18293. // No op.
  18294. }
  18295. }
  18296. /**
  18297. * @license
  18298. * Copyright 2019 Google LLC
  18299. *
  18300. * Licensed under the Apache License, Version 2.0 (the "License");
  18301. * you may not use this file except in compliance with the License.
  18302. * You may obtain a copy of the License at
  18303. *
  18304. * http://www.apache.org/licenses/LICENSE-2.0
  18305. *
  18306. * Unless required by applicable law or agreed to in writing, software
  18307. * distributed under the License is distributed on an "AS IS" BASIS,
  18308. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18309. * See the License for the specific language governing permissions and
  18310. * limitations under the License.
  18311. */
  18312. class NoopConnectivityMonitor {
  18313. addCallback(callback) {
  18314. // No-op.
  18315. }
  18316. shutdown() {
  18317. // No-op.
  18318. }
  18319. }
  18320. /**
  18321. * @license
  18322. * Copyright 2017 Google LLC
  18323. *
  18324. * Licensed under the Apache License, Version 2.0 (the "License");
  18325. * you may not use this file except in compliance with the License.
  18326. * You may obtain a copy of the License at
  18327. *
  18328. * http://www.apache.org/licenses/LICENSE-2.0
  18329. *
  18330. * Unless required by applicable law or agreed to in writing, software
  18331. * distributed under the License is distributed on an "AS IS" BASIS,
  18332. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18333. * See the License for the specific language governing permissions and
  18334. * limitations under the License.
  18335. */
  18336. /**
  18337. * Provides a simple helper class that implements the Stream interface to
  18338. * bridge to other implementations that are streams but do not implement the
  18339. * interface. The stream callbacks are invoked with the callOn... methods.
  18340. */
  18341. class StreamBridge {
  18342. constructor(args) {
  18343. this.sendFn = args.sendFn;
  18344. this.closeFn = args.closeFn;
  18345. }
  18346. onOpen(callback) {
  18347. this.wrappedOnOpen = callback;
  18348. }
  18349. onClose(callback) {
  18350. this.wrappedOnClose = callback;
  18351. }
  18352. onMessage(callback) {
  18353. this.wrappedOnMessage = callback;
  18354. }
  18355. close() {
  18356. this.closeFn();
  18357. }
  18358. send(msg) {
  18359. this.sendFn(msg);
  18360. }
  18361. callOnOpen() {
  18362. this.wrappedOnOpen();
  18363. }
  18364. callOnClose(err) {
  18365. this.wrappedOnClose(err);
  18366. }
  18367. callOnMessage(msg) {
  18368. this.wrappedOnMessage(msg);
  18369. }
  18370. }
  18371. /**
  18372. * @license
  18373. * Copyright 2023 Google LLC
  18374. *
  18375. * Licensed under the Apache License, Version 2.0 (the "License");
  18376. * you may not use this file except in compliance with the License.
  18377. * You may obtain a copy of the License at
  18378. *
  18379. * http://www.apache.org/licenses/LICENSE-2.0
  18380. *
  18381. * Unless required by applicable law or agreed to in writing, software
  18382. * distributed under the License is distributed on an "AS IS" BASIS,
  18383. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18384. * See the License for the specific language governing permissions and
  18385. * limitations under the License.
  18386. */
  18387. /**
  18388. * The value returned from the most recent invocation of
  18389. * `generateUniqueDebugId()`, or null if it has never been invoked.
  18390. */
  18391. let lastUniqueDebugId = null;
  18392. /**
  18393. * Generates and returns an initial value for `lastUniqueDebugId`.
  18394. *
  18395. * The returned value is randomly selected from a range of integers that are
  18396. * represented as 8 hexadecimal digits. This means that (within reason) any
  18397. * numbers generated by incrementing the returned number by 1 will also be
  18398. * represented by 8 hexadecimal digits. This leads to all "IDs" having the same
  18399. * length when converted to a hexadecimal string, making reading logs containing
  18400. * these IDs easier to follow. And since the return value is randomly selected
  18401. * it will help to differentiate between logs from different executions.
  18402. */
  18403. function generateInitialUniqueDebugId() {
  18404. const minResult = 0x10000000;
  18405. const maxResult = 0x90000000;
  18406. const resultRange = maxResult - minResult;
  18407. const resultOffset = Math.round(resultRange * Math.random());
  18408. return minResult + resultOffset;
  18409. }
  18410. /**
  18411. * Generates and returns a unique ID as a hexadecimal string.
  18412. *
  18413. * The returned ID is intended to be used in debug logging messages to help
  18414. * correlate log messages that may be spatially separated in the logs, but
  18415. * logically related. For example, a network connection could include the same
  18416. * "debug ID" string in all of its log messages to help trace a specific
  18417. * connection over time.
  18418. *
  18419. * @return the 10-character generated ID (e.g. "0xa1b2c3d4").
  18420. */
  18421. function generateUniqueDebugId() {
  18422. if (lastUniqueDebugId === null) {
  18423. lastUniqueDebugId = generateInitialUniqueDebugId();
  18424. }
  18425. else {
  18426. lastUniqueDebugId++;
  18427. }
  18428. return '0x' + lastUniqueDebugId.toString(16);
  18429. }
  18430. /**
  18431. * @license
  18432. * Copyright 2017 Google LLC
  18433. *
  18434. * Licensed under the Apache License, Version 2.0 (the "License");
  18435. * you may not use this file except in compliance with the License.
  18436. * You may obtain a copy of the License at
  18437. *
  18438. * http://www.apache.org/licenses/LICENSE-2.0
  18439. *
  18440. * Unless required by applicable law or agreed to in writing, software
  18441. * distributed under the License is distributed on an "AS IS" BASIS,
  18442. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18443. * See the License for the specific language governing permissions and
  18444. * limitations under the License.
  18445. */
  18446. /*
  18447. * Utilities for dealing with node.js-style APIs. See nodePromise for more
  18448. * details.
  18449. */
  18450. /**
  18451. * Creates a node-style callback that resolves or rejects a new Promise. The
  18452. * callback is passed to the given action which can then use the callback as
  18453. * a parameter to a node-style function.
  18454. *
  18455. * The intent is to directly bridge a node-style function (which takes a
  18456. * callback) into a Promise without manually converting between the node-style
  18457. * callback and the promise at each call.
  18458. *
  18459. * In effect it allows you to convert:
  18460. *
  18461. * @example
  18462. * new Promise((resolve: (value?: fs.Stats) => void,
  18463. * reject: (error?: any) => void) => {
  18464. * fs.stat(path, (error?: any, stat?: fs.Stats) => {
  18465. * if (error) {
  18466. * reject(error);
  18467. * } else {
  18468. * resolve(stat);
  18469. * }
  18470. * });
  18471. * });
  18472. *
  18473. * Into
  18474. * @example
  18475. * nodePromise((callback: NodeCallback<fs.Stats>) => {
  18476. * fs.stat(path, callback);
  18477. * });
  18478. *
  18479. * @param action - a function that takes a node-style callback as an argument
  18480. * and then uses that callback to invoke some node-style API.
  18481. * @returns a new Promise which will be rejected if the callback is given the
  18482. * first Error parameter or will resolve to the value given otherwise.
  18483. */
  18484. function nodePromise(action) {
  18485. return new Promise((resolve, reject) => {
  18486. action((error, value) => {
  18487. if (error) {
  18488. reject(error);
  18489. }
  18490. else {
  18491. resolve(value);
  18492. }
  18493. });
  18494. });
  18495. }
  18496. /**
  18497. * @license
  18498. * Copyright 2017 Google LLC
  18499. *
  18500. * Licensed under the Apache License, Version 2.0 (the "License");
  18501. * you may not use this file except in compliance with the License.
  18502. * You may obtain a copy of the License at
  18503. *
  18504. * http://www.apache.org/licenses/LICENSE-2.0
  18505. *
  18506. * Unless required by applicable law or agreed to in writing, software
  18507. * distributed under the License is distributed on an "AS IS" BASIS,
  18508. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18509. * See the License for the specific language governing permissions and
  18510. * limitations under the License.
  18511. */
  18512. // TODO: Fetch runtime version from grpc-js/package.json instead
  18513. // when there's a cleaner way to dynamic require JSON in both Node ESM and CJS
  18514. const grpcVersion = '1.7.3';
  18515. const LOG_TAG$9 = 'GrpcConnection';
  18516. const X_GOOG_API_CLIENT_VALUE = `gl-node/${process.versions.node} fire/${SDK_VERSION} grpc/${grpcVersion}`;
  18517. function createMetadata(databasePath, authToken, appCheckToken, appId) {
  18518. hardAssert(authToken === null || authToken.type === 'OAuth');
  18519. const metadata = new grpc.Metadata();
  18520. if (authToken) {
  18521. authToken.headers.forEach((value, key) => metadata.set(key, value));
  18522. }
  18523. if (appCheckToken) {
  18524. appCheckToken.headers.forEach((value, key) => metadata.set(key, value));
  18525. }
  18526. if (appId) {
  18527. metadata.set('X-Firebase-GMPID', appId);
  18528. }
  18529. metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
  18530. // These headers are used to improve routing and project isolation by the
  18531. // backend.
  18532. // TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
  18533. // released with cl/428820046. Currently blocked because Emulators are now built with Java
  18534. // 11 from Google3.
  18535. metadata.set('Google-Cloud-Resource-Prefix', databasePath);
  18536. metadata.set('x-goog-request-params', databasePath);
  18537. return metadata;
  18538. }
  18539. /**
  18540. * A Connection implemented by GRPC-Node.
  18541. */
  18542. class GrpcConnection {
  18543. constructor(protos, databaseInfo) {
  18544. this.databaseInfo = databaseInfo;
  18545. // We cache stubs for the most-recently-used token.
  18546. this.cachedStub = null;
  18547. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  18548. this.firestore = protos['google']['firestore']['v1'];
  18549. this.databasePath = `projects/${databaseInfo.databaseId.projectId}/databases/${databaseInfo.databaseId.database}`;
  18550. }
  18551. get shouldResourcePathBeIncludedInRequest() {
  18552. // Both `invokeRPC()` and `invokeStreamingRPC()` ignore their `path` arguments, and expect
  18553. // the "path" to be part of the given `request`.
  18554. return true;
  18555. }
  18556. ensureActiveStub() {
  18557. if (!this.cachedStub) {
  18558. logDebug(LOG_TAG$9, 'Creating Firestore stub.');
  18559. const credentials = this.databaseInfo.ssl
  18560. ? grpc.credentials.createSsl()
  18561. : grpc.credentials.createInsecure();
  18562. this.cachedStub = new this.firestore.Firestore(this.databaseInfo.host, credentials);
  18563. }
  18564. return this.cachedStub;
  18565. }
  18566. invokeRPC(rpcName, path, request, authToken, appCheckToken) {
  18567. const streamId = generateUniqueDebugId();
  18568. const stub = this.ensureActiveStub();
  18569. const metadata = createMetadata(this.databasePath, authToken, appCheckToken, this.databaseInfo.appId);
  18570. const jsonRequest = Object.assign({ database: this.databasePath }, request);
  18571. return nodePromise((callback) => {
  18572. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} invoked with request:`, request);
  18573. return stub[rpcName](jsonRequest, metadata, (grpcError, value) => {
  18574. if (grpcError) {
  18575. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} failed with error:`, grpcError);
  18576. callback(new FirestoreError(mapCodeFromRpcCode(grpcError.code), grpcError.message));
  18577. }
  18578. else {
  18579. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} completed with response:`, value);
  18580. callback(undefined, value);
  18581. }
  18582. });
  18583. });
  18584. }
  18585. invokeStreamingRPC(rpcName, path, request, authToken, appCheckToken, expectedResponseCount) {
  18586. const streamId = generateUniqueDebugId();
  18587. const results = [];
  18588. const responseDeferred = new Deferred();
  18589. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} invoked (streaming) with request:`, request);
  18590. const stub = this.ensureActiveStub();
  18591. const metadata = createMetadata(this.databasePath, authToken, appCheckToken, this.databaseInfo.appId);
  18592. const jsonRequest = Object.assign(Object.assign({}, request), { database: this.databasePath });
  18593. const stream = stub[rpcName](jsonRequest, metadata);
  18594. let callbackFired = false;
  18595. stream.on('data', (response) => {
  18596. logDebug(LOG_TAG$9, `RPC ${rpcName} ${streamId} received result:`, response);
  18597. results.push(response);
  18598. if (expectedResponseCount !== undefined &&
  18599. results.length === expectedResponseCount) {
  18600. callbackFired = true;
  18601. responseDeferred.resolve(results);
  18602. }
  18603. });
  18604. stream.on('end', () => {
  18605. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} completed.`);
  18606. if (!callbackFired) {
  18607. callbackFired = true;
  18608. responseDeferred.resolve(results);
  18609. }
  18610. });
  18611. stream.on('error', (grpcError) => {
  18612. logDebug(LOG_TAG$9, `RPC '${rpcName}' ${streamId} failed with error:`, grpcError);
  18613. const code = mapCodeFromRpcCode(grpcError.code);
  18614. responseDeferred.reject(new FirestoreError(code, grpcError.message));
  18615. });
  18616. return responseDeferred.promise;
  18617. }
  18618. // TODO(mikelehen): This "method" is a monster. Should be refactored.
  18619. openStream(rpcName, authToken, appCheckToken) {
  18620. const streamId = generateUniqueDebugId();
  18621. const stub = this.ensureActiveStub();
  18622. const metadata = createMetadata(this.databasePath, authToken, appCheckToken, this.databaseInfo.appId);
  18623. const grpcStream = stub[rpcName](metadata);
  18624. let closed = false;
  18625. const close = (err) => {
  18626. if (!closed) {
  18627. closed = true;
  18628. stream.callOnClose(err);
  18629. grpcStream.end();
  18630. }
  18631. };
  18632. const stream = new StreamBridge({
  18633. sendFn: (msg) => {
  18634. if (!closed) {
  18635. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} sending:`, msg);
  18636. try {
  18637. grpcStream.write(msg);
  18638. }
  18639. catch (e) {
  18640. // This probably means we didn't conform to the proto. Make sure to
  18641. // log the message we sent.
  18642. logError('Failure sending:', msg);
  18643. logError('Error:', e);
  18644. throw e;
  18645. }
  18646. }
  18647. else {
  18648. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} ` +
  18649. 'not sending because gRPC stream is closed:', msg);
  18650. }
  18651. },
  18652. closeFn: () => {
  18653. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} closed locally via close().`);
  18654. close();
  18655. }
  18656. });
  18657. grpcStream.on('data', (msg) => {
  18658. if (!closed) {
  18659. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} received:`, msg);
  18660. stream.callOnMessage(msg);
  18661. }
  18662. });
  18663. grpcStream.on('end', () => {
  18664. logDebug(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} ended.`);
  18665. close();
  18666. });
  18667. grpcStream.on('error', (grpcError) => {
  18668. if (!closed) {
  18669. logWarn(LOG_TAG$9, `RPC '${rpcName}' stream ${streamId} error. Code:`, grpcError.code, 'Message:', grpcError.message);
  18670. const code = mapCodeFromRpcCode(grpcError.code);
  18671. close(new FirestoreError(code, grpcError.message));
  18672. }
  18673. });
  18674. logDebug(LOG_TAG$9, `Opening RPC '${rpcName}' stream ${streamId} ` +
  18675. `to ${this.databaseInfo.host}`);
  18676. // TODO(dimond): Since grpc has no explicit open status (or does it?) we
  18677. // simulate an onOpen in the next loop after the stream had it's listeners
  18678. // registered
  18679. setTimeout(() => {
  18680. stream.callOnOpen();
  18681. }, 0);
  18682. return stream;
  18683. }
  18684. }
  18685. const nested = {
  18686. google: {
  18687. nested: {
  18688. protobuf: {
  18689. options: {
  18690. csharp_namespace: "Google.Protobuf.WellKnownTypes",
  18691. go_package: "github.com/golang/protobuf/ptypes/wrappers",
  18692. java_package: "com.google.protobuf",
  18693. java_outer_classname: "WrappersProto",
  18694. java_multiple_files: true,
  18695. objc_class_prefix: "GPB",
  18696. cc_enable_arenas: true,
  18697. optimize_for: "SPEED"
  18698. },
  18699. nested: {
  18700. Timestamp: {
  18701. fields: {
  18702. seconds: {
  18703. type: "int64",
  18704. id: 1
  18705. },
  18706. nanos: {
  18707. type: "int32",
  18708. id: 2
  18709. }
  18710. }
  18711. },
  18712. FileDescriptorSet: {
  18713. fields: {
  18714. file: {
  18715. rule: "repeated",
  18716. type: "FileDescriptorProto",
  18717. id: 1
  18718. }
  18719. }
  18720. },
  18721. FileDescriptorProto: {
  18722. fields: {
  18723. name: {
  18724. type: "string",
  18725. id: 1
  18726. },
  18727. "package": {
  18728. type: "string",
  18729. id: 2
  18730. },
  18731. dependency: {
  18732. rule: "repeated",
  18733. type: "string",
  18734. id: 3
  18735. },
  18736. publicDependency: {
  18737. rule: "repeated",
  18738. type: "int32",
  18739. id: 10,
  18740. options: {
  18741. packed: false
  18742. }
  18743. },
  18744. weakDependency: {
  18745. rule: "repeated",
  18746. type: "int32",
  18747. id: 11,
  18748. options: {
  18749. packed: false
  18750. }
  18751. },
  18752. messageType: {
  18753. rule: "repeated",
  18754. type: "DescriptorProto",
  18755. id: 4
  18756. },
  18757. enumType: {
  18758. rule: "repeated",
  18759. type: "EnumDescriptorProto",
  18760. id: 5
  18761. },
  18762. service: {
  18763. rule: "repeated",
  18764. type: "ServiceDescriptorProto",
  18765. id: 6
  18766. },
  18767. extension: {
  18768. rule: "repeated",
  18769. type: "FieldDescriptorProto",
  18770. id: 7
  18771. },
  18772. options: {
  18773. type: "FileOptions",
  18774. id: 8
  18775. },
  18776. sourceCodeInfo: {
  18777. type: "SourceCodeInfo",
  18778. id: 9
  18779. },
  18780. syntax: {
  18781. type: "string",
  18782. id: 12
  18783. }
  18784. }
  18785. },
  18786. DescriptorProto: {
  18787. fields: {
  18788. name: {
  18789. type: "string",
  18790. id: 1
  18791. },
  18792. field: {
  18793. rule: "repeated",
  18794. type: "FieldDescriptorProto",
  18795. id: 2
  18796. },
  18797. extension: {
  18798. rule: "repeated",
  18799. type: "FieldDescriptorProto",
  18800. id: 6
  18801. },
  18802. nestedType: {
  18803. rule: "repeated",
  18804. type: "DescriptorProto",
  18805. id: 3
  18806. },
  18807. enumType: {
  18808. rule: "repeated",
  18809. type: "EnumDescriptorProto",
  18810. id: 4
  18811. },
  18812. extensionRange: {
  18813. rule: "repeated",
  18814. type: "ExtensionRange",
  18815. id: 5
  18816. },
  18817. oneofDecl: {
  18818. rule: "repeated",
  18819. type: "OneofDescriptorProto",
  18820. id: 8
  18821. },
  18822. options: {
  18823. type: "MessageOptions",
  18824. id: 7
  18825. },
  18826. reservedRange: {
  18827. rule: "repeated",
  18828. type: "ReservedRange",
  18829. id: 9
  18830. },
  18831. reservedName: {
  18832. rule: "repeated",
  18833. type: "string",
  18834. id: 10
  18835. }
  18836. },
  18837. nested: {
  18838. ExtensionRange: {
  18839. fields: {
  18840. start: {
  18841. type: "int32",
  18842. id: 1
  18843. },
  18844. end: {
  18845. type: "int32",
  18846. id: 2
  18847. }
  18848. }
  18849. },
  18850. ReservedRange: {
  18851. fields: {
  18852. start: {
  18853. type: "int32",
  18854. id: 1
  18855. },
  18856. end: {
  18857. type: "int32",
  18858. id: 2
  18859. }
  18860. }
  18861. }
  18862. }
  18863. },
  18864. FieldDescriptorProto: {
  18865. fields: {
  18866. name: {
  18867. type: "string",
  18868. id: 1
  18869. },
  18870. number: {
  18871. type: "int32",
  18872. id: 3
  18873. },
  18874. label: {
  18875. type: "Label",
  18876. id: 4
  18877. },
  18878. type: {
  18879. type: "Type",
  18880. id: 5
  18881. },
  18882. typeName: {
  18883. type: "string",
  18884. id: 6
  18885. },
  18886. extendee: {
  18887. type: "string",
  18888. id: 2
  18889. },
  18890. defaultValue: {
  18891. type: "string",
  18892. id: 7
  18893. },
  18894. oneofIndex: {
  18895. type: "int32",
  18896. id: 9
  18897. },
  18898. jsonName: {
  18899. type: "string",
  18900. id: 10
  18901. },
  18902. options: {
  18903. type: "FieldOptions",
  18904. id: 8
  18905. }
  18906. },
  18907. nested: {
  18908. Type: {
  18909. values: {
  18910. TYPE_DOUBLE: 1,
  18911. TYPE_FLOAT: 2,
  18912. TYPE_INT64: 3,
  18913. TYPE_UINT64: 4,
  18914. TYPE_INT32: 5,
  18915. TYPE_FIXED64: 6,
  18916. TYPE_FIXED32: 7,
  18917. TYPE_BOOL: 8,
  18918. TYPE_STRING: 9,
  18919. TYPE_GROUP: 10,
  18920. TYPE_MESSAGE: 11,
  18921. TYPE_BYTES: 12,
  18922. TYPE_UINT32: 13,
  18923. TYPE_ENUM: 14,
  18924. TYPE_SFIXED32: 15,
  18925. TYPE_SFIXED64: 16,
  18926. TYPE_SINT32: 17,
  18927. TYPE_SINT64: 18
  18928. }
  18929. },
  18930. Label: {
  18931. values: {
  18932. LABEL_OPTIONAL: 1,
  18933. LABEL_REQUIRED: 2,
  18934. LABEL_REPEATED: 3
  18935. }
  18936. }
  18937. }
  18938. },
  18939. OneofDescriptorProto: {
  18940. fields: {
  18941. name: {
  18942. type: "string",
  18943. id: 1
  18944. },
  18945. options: {
  18946. type: "OneofOptions",
  18947. id: 2
  18948. }
  18949. }
  18950. },
  18951. EnumDescriptorProto: {
  18952. fields: {
  18953. name: {
  18954. type: "string",
  18955. id: 1
  18956. },
  18957. value: {
  18958. rule: "repeated",
  18959. type: "EnumValueDescriptorProto",
  18960. id: 2
  18961. },
  18962. options: {
  18963. type: "EnumOptions",
  18964. id: 3
  18965. }
  18966. }
  18967. },
  18968. EnumValueDescriptorProto: {
  18969. fields: {
  18970. name: {
  18971. type: "string",
  18972. id: 1
  18973. },
  18974. number: {
  18975. type: "int32",
  18976. id: 2
  18977. },
  18978. options: {
  18979. type: "EnumValueOptions",
  18980. id: 3
  18981. }
  18982. }
  18983. },
  18984. ServiceDescriptorProto: {
  18985. fields: {
  18986. name: {
  18987. type: "string",
  18988. id: 1
  18989. },
  18990. method: {
  18991. rule: "repeated",
  18992. type: "MethodDescriptorProto",
  18993. id: 2
  18994. },
  18995. options: {
  18996. type: "ServiceOptions",
  18997. id: 3
  18998. }
  18999. }
  19000. },
  19001. MethodDescriptorProto: {
  19002. fields: {
  19003. name: {
  19004. type: "string",
  19005. id: 1
  19006. },
  19007. inputType: {
  19008. type: "string",
  19009. id: 2
  19010. },
  19011. outputType: {
  19012. type: "string",
  19013. id: 3
  19014. },
  19015. options: {
  19016. type: "MethodOptions",
  19017. id: 4
  19018. },
  19019. clientStreaming: {
  19020. type: "bool",
  19021. id: 5
  19022. },
  19023. serverStreaming: {
  19024. type: "bool",
  19025. id: 6
  19026. }
  19027. }
  19028. },
  19029. FileOptions: {
  19030. fields: {
  19031. javaPackage: {
  19032. type: "string",
  19033. id: 1
  19034. },
  19035. javaOuterClassname: {
  19036. type: "string",
  19037. id: 8
  19038. },
  19039. javaMultipleFiles: {
  19040. type: "bool",
  19041. id: 10
  19042. },
  19043. javaGenerateEqualsAndHash: {
  19044. type: "bool",
  19045. id: 20,
  19046. options: {
  19047. deprecated: true
  19048. }
  19049. },
  19050. javaStringCheckUtf8: {
  19051. type: "bool",
  19052. id: 27
  19053. },
  19054. optimizeFor: {
  19055. type: "OptimizeMode",
  19056. id: 9,
  19057. options: {
  19058. "default": "SPEED"
  19059. }
  19060. },
  19061. goPackage: {
  19062. type: "string",
  19063. id: 11
  19064. },
  19065. ccGenericServices: {
  19066. type: "bool",
  19067. id: 16
  19068. },
  19069. javaGenericServices: {
  19070. type: "bool",
  19071. id: 17
  19072. },
  19073. pyGenericServices: {
  19074. type: "bool",
  19075. id: 18
  19076. },
  19077. deprecated: {
  19078. type: "bool",
  19079. id: 23
  19080. },
  19081. ccEnableArenas: {
  19082. type: "bool",
  19083. id: 31
  19084. },
  19085. objcClassPrefix: {
  19086. type: "string",
  19087. id: 36
  19088. },
  19089. csharpNamespace: {
  19090. type: "string",
  19091. id: 37
  19092. },
  19093. uninterpretedOption: {
  19094. rule: "repeated",
  19095. type: "UninterpretedOption",
  19096. id: 999
  19097. }
  19098. },
  19099. extensions: [
  19100. [
  19101. 1000,
  19102. 536870911
  19103. ]
  19104. ],
  19105. reserved: [
  19106. [
  19107. 38,
  19108. 38
  19109. ]
  19110. ],
  19111. nested: {
  19112. OptimizeMode: {
  19113. values: {
  19114. SPEED: 1,
  19115. CODE_SIZE: 2,
  19116. LITE_RUNTIME: 3
  19117. }
  19118. }
  19119. }
  19120. },
  19121. MessageOptions: {
  19122. fields: {
  19123. messageSetWireFormat: {
  19124. type: "bool",
  19125. id: 1
  19126. },
  19127. noStandardDescriptorAccessor: {
  19128. type: "bool",
  19129. id: 2
  19130. },
  19131. deprecated: {
  19132. type: "bool",
  19133. id: 3
  19134. },
  19135. mapEntry: {
  19136. type: "bool",
  19137. id: 7
  19138. },
  19139. uninterpretedOption: {
  19140. rule: "repeated",
  19141. type: "UninterpretedOption",
  19142. id: 999
  19143. }
  19144. },
  19145. extensions: [
  19146. [
  19147. 1000,
  19148. 536870911
  19149. ]
  19150. ],
  19151. reserved: [
  19152. [
  19153. 8,
  19154. 8
  19155. ]
  19156. ]
  19157. },
  19158. FieldOptions: {
  19159. fields: {
  19160. ctype: {
  19161. type: "CType",
  19162. id: 1,
  19163. options: {
  19164. "default": "STRING"
  19165. }
  19166. },
  19167. packed: {
  19168. type: "bool",
  19169. id: 2
  19170. },
  19171. jstype: {
  19172. type: "JSType",
  19173. id: 6,
  19174. options: {
  19175. "default": "JS_NORMAL"
  19176. }
  19177. },
  19178. lazy: {
  19179. type: "bool",
  19180. id: 5
  19181. },
  19182. deprecated: {
  19183. type: "bool",
  19184. id: 3
  19185. },
  19186. weak: {
  19187. type: "bool",
  19188. id: 10
  19189. },
  19190. uninterpretedOption: {
  19191. rule: "repeated",
  19192. type: "UninterpretedOption",
  19193. id: 999
  19194. }
  19195. },
  19196. extensions: [
  19197. [
  19198. 1000,
  19199. 536870911
  19200. ]
  19201. ],
  19202. reserved: [
  19203. [
  19204. 4,
  19205. 4
  19206. ]
  19207. ],
  19208. nested: {
  19209. CType: {
  19210. values: {
  19211. STRING: 0,
  19212. CORD: 1,
  19213. STRING_PIECE: 2
  19214. }
  19215. },
  19216. JSType: {
  19217. values: {
  19218. JS_NORMAL: 0,
  19219. JS_STRING: 1,
  19220. JS_NUMBER: 2
  19221. }
  19222. }
  19223. }
  19224. },
  19225. OneofOptions: {
  19226. fields: {
  19227. uninterpretedOption: {
  19228. rule: "repeated",
  19229. type: "UninterpretedOption",
  19230. id: 999
  19231. }
  19232. },
  19233. extensions: [
  19234. [
  19235. 1000,
  19236. 536870911
  19237. ]
  19238. ]
  19239. },
  19240. EnumOptions: {
  19241. fields: {
  19242. allowAlias: {
  19243. type: "bool",
  19244. id: 2
  19245. },
  19246. deprecated: {
  19247. type: "bool",
  19248. id: 3
  19249. },
  19250. uninterpretedOption: {
  19251. rule: "repeated",
  19252. type: "UninterpretedOption",
  19253. id: 999
  19254. }
  19255. },
  19256. extensions: [
  19257. [
  19258. 1000,
  19259. 536870911
  19260. ]
  19261. ]
  19262. },
  19263. EnumValueOptions: {
  19264. fields: {
  19265. deprecated: {
  19266. type: "bool",
  19267. id: 1
  19268. },
  19269. uninterpretedOption: {
  19270. rule: "repeated",
  19271. type: "UninterpretedOption",
  19272. id: 999
  19273. }
  19274. },
  19275. extensions: [
  19276. [
  19277. 1000,
  19278. 536870911
  19279. ]
  19280. ]
  19281. },
  19282. ServiceOptions: {
  19283. fields: {
  19284. deprecated: {
  19285. type: "bool",
  19286. id: 33
  19287. },
  19288. uninterpretedOption: {
  19289. rule: "repeated",
  19290. type: "UninterpretedOption",
  19291. id: 999
  19292. }
  19293. },
  19294. extensions: [
  19295. [
  19296. 1000,
  19297. 536870911
  19298. ]
  19299. ]
  19300. },
  19301. MethodOptions: {
  19302. fields: {
  19303. deprecated: {
  19304. type: "bool",
  19305. id: 33
  19306. },
  19307. uninterpretedOption: {
  19308. rule: "repeated",
  19309. type: "UninterpretedOption",
  19310. id: 999
  19311. }
  19312. },
  19313. extensions: [
  19314. [
  19315. 1000,
  19316. 536870911
  19317. ]
  19318. ]
  19319. },
  19320. UninterpretedOption: {
  19321. fields: {
  19322. name: {
  19323. rule: "repeated",
  19324. type: "NamePart",
  19325. id: 2
  19326. },
  19327. identifierValue: {
  19328. type: "string",
  19329. id: 3
  19330. },
  19331. positiveIntValue: {
  19332. type: "uint64",
  19333. id: 4
  19334. },
  19335. negativeIntValue: {
  19336. type: "int64",
  19337. id: 5
  19338. },
  19339. doubleValue: {
  19340. type: "double",
  19341. id: 6
  19342. },
  19343. stringValue: {
  19344. type: "bytes",
  19345. id: 7
  19346. },
  19347. aggregateValue: {
  19348. type: "string",
  19349. id: 8
  19350. }
  19351. },
  19352. nested: {
  19353. NamePart: {
  19354. fields: {
  19355. namePart: {
  19356. rule: "required",
  19357. type: "string",
  19358. id: 1
  19359. },
  19360. isExtension: {
  19361. rule: "required",
  19362. type: "bool",
  19363. id: 2
  19364. }
  19365. }
  19366. }
  19367. }
  19368. },
  19369. SourceCodeInfo: {
  19370. fields: {
  19371. location: {
  19372. rule: "repeated",
  19373. type: "Location",
  19374. id: 1
  19375. }
  19376. },
  19377. nested: {
  19378. Location: {
  19379. fields: {
  19380. path: {
  19381. rule: "repeated",
  19382. type: "int32",
  19383. id: 1
  19384. },
  19385. span: {
  19386. rule: "repeated",
  19387. type: "int32",
  19388. id: 2
  19389. },
  19390. leadingComments: {
  19391. type: "string",
  19392. id: 3
  19393. },
  19394. trailingComments: {
  19395. type: "string",
  19396. id: 4
  19397. },
  19398. leadingDetachedComments: {
  19399. rule: "repeated",
  19400. type: "string",
  19401. id: 6
  19402. }
  19403. }
  19404. }
  19405. }
  19406. },
  19407. GeneratedCodeInfo: {
  19408. fields: {
  19409. annotation: {
  19410. rule: "repeated",
  19411. type: "Annotation",
  19412. id: 1
  19413. }
  19414. },
  19415. nested: {
  19416. Annotation: {
  19417. fields: {
  19418. path: {
  19419. rule: "repeated",
  19420. type: "int32",
  19421. id: 1
  19422. },
  19423. sourceFile: {
  19424. type: "string",
  19425. id: 2
  19426. },
  19427. begin: {
  19428. type: "int32",
  19429. id: 3
  19430. },
  19431. end: {
  19432. type: "int32",
  19433. id: 4
  19434. }
  19435. }
  19436. }
  19437. }
  19438. },
  19439. Struct: {
  19440. fields: {
  19441. fields: {
  19442. keyType: "string",
  19443. type: "Value",
  19444. id: 1
  19445. }
  19446. }
  19447. },
  19448. Value: {
  19449. oneofs: {
  19450. kind: {
  19451. oneof: [
  19452. "nullValue",
  19453. "numberValue",
  19454. "stringValue",
  19455. "boolValue",
  19456. "structValue",
  19457. "listValue"
  19458. ]
  19459. }
  19460. },
  19461. fields: {
  19462. nullValue: {
  19463. type: "NullValue",
  19464. id: 1
  19465. },
  19466. numberValue: {
  19467. type: "double",
  19468. id: 2
  19469. },
  19470. stringValue: {
  19471. type: "string",
  19472. id: 3
  19473. },
  19474. boolValue: {
  19475. type: "bool",
  19476. id: 4
  19477. },
  19478. structValue: {
  19479. type: "Struct",
  19480. id: 5
  19481. },
  19482. listValue: {
  19483. type: "ListValue",
  19484. id: 6
  19485. }
  19486. }
  19487. },
  19488. NullValue: {
  19489. values: {
  19490. NULL_VALUE: 0
  19491. }
  19492. },
  19493. ListValue: {
  19494. fields: {
  19495. values: {
  19496. rule: "repeated",
  19497. type: "Value",
  19498. id: 1
  19499. }
  19500. }
  19501. },
  19502. Empty: {
  19503. fields: {
  19504. }
  19505. },
  19506. DoubleValue: {
  19507. fields: {
  19508. value: {
  19509. type: "double",
  19510. id: 1
  19511. }
  19512. }
  19513. },
  19514. FloatValue: {
  19515. fields: {
  19516. value: {
  19517. type: "float",
  19518. id: 1
  19519. }
  19520. }
  19521. },
  19522. Int64Value: {
  19523. fields: {
  19524. value: {
  19525. type: "int64",
  19526. id: 1
  19527. }
  19528. }
  19529. },
  19530. UInt64Value: {
  19531. fields: {
  19532. value: {
  19533. type: "uint64",
  19534. id: 1
  19535. }
  19536. }
  19537. },
  19538. Int32Value: {
  19539. fields: {
  19540. value: {
  19541. type: "int32",
  19542. id: 1
  19543. }
  19544. }
  19545. },
  19546. UInt32Value: {
  19547. fields: {
  19548. value: {
  19549. type: "uint32",
  19550. id: 1
  19551. }
  19552. }
  19553. },
  19554. BoolValue: {
  19555. fields: {
  19556. value: {
  19557. type: "bool",
  19558. id: 1
  19559. }
  19560. }
  19561. },
  19562. StringValue: {
  19563. fields: {
  19564. value: {
  19565. type: "string",
  19566. id: 1
  19567. }
  19568. }
  19569. },
  19570. BytesValue: {
  19571. fields: {
  19572. value: {
  19573. type: "bytes",
  19574. id: 1
  19575. }
  19576. }
  19577. },
  19578. Any: {
  19579. fields: {
  19580. typeUrl: {
  19581. type: "string",
  19582. id: 1
  19583. },
  19584. value: {
  19585. type: "bytes",
  19586. id: 2
  19587. }
  19588. }
  19589. }
  19590. }
  19591. },
  19592. firestore: {
  19593. nested: {
  19594. v1: {
  19595. options: {
  19596. csharp_namespace: "Google.Cloud.Firestore.V1",
  19597. go_package: "google.golang.org/genproto/googleapis/firestore/v1;firestore",
  19598. java_multiple_files: true,
  19599. java_outer_classname: "WriteProto",
  19600. java_package: "com.google.firestore.v1",
  19601. objc_class_prefix: "GCFS",
  19602. php_namespace: "Google\\Cloud\\Firestore\\V1",
  19603. ruby_package: "Google::Cloud::Firestore::V1"
  19604. },
  19605. nested: {
  19606. AggregationResult: {
  19607. fields: {
  19608. aggregateFields: {
  19609. keyType: "string",
  19610. type: "Value",
  19611. id: 2
  19612. }
  19613. }
  19614. },
  19615. BitSequence: {
  19616. fields: {
  19617. bitmap: {
  19618. type: "bytes",
  19619. id: 1
  19620. },
  19621. padding: {
  19622. type: "int32",
  19623. id: 2
  19624. }
  19625. }
  19626. },
  19627. BloomFilter: {
  19628. fields: {
  19629. bits: {
  19630. type: "BitSequence",
  19631. id: 1
  19632. },
  19633. hashCount: {
  19634. type: "int32",
  19635. id: 2
  19636. }
  19637. }
  19638. },
  19639. DocumentMask: {
  19640. fields: {
  19641. fieldPaths: {
  19642. rule: "repeated",
  19643. type: "string",
  19644. id: 1
  19645. }
  19646. }
  19647. },
  19648. Precondition: {
  19649. oneofs: {
  19650. conditionType: {
  19651. oneof: [
  19652. "exists",
  19653. "updateTime"
  19654. ]
  19655. }
  19656. },
  19657. fields: {
  19658. exists: {
  19659. type: "bool",
  19660. id: 1
  19661. },
  19662. updateTime: {
  19663. type: "google.protobuf.Timestamp",
  19664. id: 2
  19665. }
  19666. }
  19667. },
  19668. TransactionOptions: {
  19669. oneofs: {
  19670. mode: {
  19671. oneof: [
  19672. "readOnly",
  19673. "readWrite"
  19674. ]
  19675. }
  19676. },
  19677. fields: {
  19678. readOnly: {
  19679. type: "ReadOnly",
  19680. id: 2
  19681. },
  19682. readWrite: {
  19683. type: "ReadWrite",
  19684. id: 3
  19685. }
  19686. },
  19687. nested: {
  19688. ReadWrite: {
  19689. fields: {
  19690. retryTransaction: {
  19691. type: "bytes",
  19692. id: 1
  19693. }
  19694. }
  19695. },
  19696. ReadOnly: {
  19697. oneofs: {
  19698. consistencySelector: {
  19699. oneof: [
  19700. "readTime"
  19701. ]
  19702. }
  19703. },
  19704. fields: {
  19705. readTime: {
  19706. type: "google.protobuf.Timestamp",
  19707. id: 2
  19708. }
  19709. }
  19710. }
  19711. }
  19712. },
  19713. Document: {
  19714. fields: {
  19715. name: {
  19716. type: "string",
  19717. id: 1
  19718. },
  19719. fields: {
  19720. keyType: "string",
  19721. type: "Value",
  19722. id: 2
  19723. },
  19724. createTime: {
  19725. type: "google.protobuf.Timestamp",
  19726. id: 3
  19727. },
  19728. updateTime: {
  19729. type: "google.protobuf.Timestamp",
  19730. id: 4
  19731. }
  19732. }
  19733. },
  19734. Value: {
  19735. oneofs: {
  19736. valueType: {
  19737. oneof: [
  19738. "nullValue",
  19739. "booleanValue",
  19740. "integerValue",
  19741. "doubleValue",
  19742. "timestampValue",
  19743. "stringValue",
  19744. "bytesValue",
  19745. "referenceValue",
  19746. "geoPointValue",
  19747. "arrayValue",
  19748. "mapValue"
  19749. ]
  19750. }
  19751. },
  19752. fields: {
  19753. nullValue: {
  19754. type: "google.protobuf.NullValue",
  19755. id: 11
  19756. },
  19757. booleanValue: {
  19758. type: "bool",
  19759. id: 1
  19760. },
  19761. integerValue: {
  19762. type: "int64",
  19763. id: 2
  19764. },
  19765. doubleValue: {
  19766. type: "double",
  19767. id: 3
  19768. },
  19769. timestampValue: {
  19770. type: "google.protobuf.Timestamp",
  19771. id: 10
  19772. },
  19773. stringValue: {
  19774. type: "string",
  19775. id: 17
  19776. },
  19777. bytesValue: {
  19778. type: "bytes",
  19779. id: 18
  19780. },
  19781. referenceValue: {
  19782. type: "string",
  19783. id: 5
  19784. },
  19785. geoPointValue: {
  19786. type: "google.type.LatLng",
  19787. id: 8
  19788. },
  19789. arrayValue: {
  19790. type: "ArrayValue",
  19791. id: 9
  19792. },
  19793. mapValue: {
  19794. type: "MapValue",
  19795. id: 6
  19796. }
  19797. }
  19798. },
  19799. ArrayValue: {
  19800. fields: {
  19801. values: {
  19802. rule: "repeated",
  19803. type: "Value",
  19804. id: 1
  19805. }
  19806. }
  19807. },
  19808. MapValue: {
  19809. fields: {
  19810. fields: {
  19811. keyType: "string",
  19812. type: "Value",
  19813. id: 1
  19814. }
  19815. }
  19816. },
  19817. Firestore: {
  19818. options: {
  19819. "(google.api.default_host)": "firestore.googleapis.com",
  19820. "(google.api.oauth_scopes)": "https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/datastore"
  19821. },
  19822. methods: {
  19823. GetDocument: {
  19824. requestType: "GetDocumentRequest",
  19825. responseType: "Document",
  19826. options: {
  19827. "(google.api.http).get": "/v1/{name=projects/*/databases/*/documents/*/**}"
  19828. },
  19829. parsedOptions: [
  19830. {
  19831. "(google.api.http)": {
  19832. get: "/v1/{name=projects/*/databases/*/documents/*/**}"
  19833. }
  19834. }
  19835. ]
  19836. },
  19837. ListDocuments: {
  19838. requestType: "ListDocumentsRequest",
  19839. responseType: "ListDocumentsResponse",
  19840. options: {
  19841. "(google.api.http).get": "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}"
  19842. },
  19843. parsedOptions: [
  19844. {
  19845. "(google.api.http)": {
  19846. get: "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}"
  19847. }
  19848. }
  19849. ]
  19850. },
  19851. UpdateDocument: {
  19852. requestType: "UpdateDocumentRequest",
  19853. responseType: "Document",
  19854. options: {
  19855. "(google.api.http).patch": "/v1/{document.name=projects/*/databases/*/documents/*/**}",
  19856. "(google.api.http).body": "document",
  19857. "(google.api.method_signature)": "document,update_mask"
  19858. },
  19859. parsedOptions: [
  19860. {
  19861. "(google.api.http)": {
  19862. patch: "/v1/{document.name=projects/*/databases/*/documents/*/**}",
  19863. body: "document"
  19864. }
  19865. },
  19866. {
  19867. "(google.api.method_signature)": "document,update_mask"
  19868. }
  19869. ]
  19870. },
  19871. DeleteDocument: {
  19872. requestType: "DeleteDocumentRequest",
  19873. responseType: "google.protobuf.Empty",
  19874. options: {
  19875. "(google.api.http).delete": "/v1/{name=projects/*/databases/*/documents/*/**}",
  19876. "(google.api.method_signature)": "name"
  19877. },
  19878. parsedOptions: [
  19879. {
  19880. "(google.api.http)": {
  19881. "delete": "/v1/{name=projects/*/databases/*/documents/*/**}"
  19882. }
  19883. },
  19884. {
  19885. "(google.api.method_signature)": "name"
  19886. }
  19887. ]
  19888. },
  19889. BatchGetDocuments: {
  19890. requestType: "BatchGetDocumentsRequest",
  19891. responseType: "BatchGetDocumentsResponse",
  19892. responseStream: true,
  19893. options: {
  19894. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:batchGet",
  19895. "(google.api.http).body": "*"
  19896. },
  19897. parsedOptions: [
  19898. {
  19899. "(google.api.http)": {
  19900. post: "/v1/{database=projects/*/databases/*}/documents:batchGet",
  19901. body: "*"
  19902. }
  19903. }
  19904. ]
  19905. },
  19906. BeginTransaction: {
  19907. requestType: "BeginTransactionRequest",
  19908. responseType: "BeginTransactionResponse",
  19909. options: {
  19910. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:beginTransaction",
  19911. "(google.api.http).body": "*",
  19912. "(google.api.method_signature)": "database"
  19913. },
  19914. parsedOptions: [
  19915. {
  19916. "(google.api.http)": {
  19917. post: "/v1/{database=projects/*/databases/*}/documents:beginTransaction",
  19918. body: "*"
  19919. }
  19920. },
  19921. {
  19922. "(google.api.method_signature)": "database"
  19923. }
  19924. ]
  19925. },
  19926. Commit: {
  19927. requestType: "CommitRequest",
  19928. responseType: "CommitResponse",
  19929. options: {
  19930. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:commit",
  19931. "(google.api.http).body": "*",
  19932. "(google.api.method_signature)": "database,writes"
  19933. },
  19934. parsedOptions: [
  19935. {
  19936. "(google.api.http)": {
  19937. post: "/v1/{database=projects/*/databases/*}/documents:commit",
  19938. body: "*"
  19939. }
  19940. },
  19941. {
  19942. "(google.api.method_signature)": "database,writes"
  19943. }
  19944. ]
  19945. },
  19946. Rollback: {
  19947. requestType: "RollbackRequest",
  19948. responseType: "google.protobuf.Empty",
  19949. options: {
  19950. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:rollback",
  19951. "(google.api.http).body": "*",
  19952. "(google.api.method_signature)": "database,transaction"
  19953. },
  19954. parsedOptions: [
  19955. {
  19956. "(google.api.http)": {
  19957. post: "/v1/{database=projects/*/databases/*}/documents:rollback",
  19958. body: "*"
  19959. }
  19960. },
  19961. {
  19962. "(google.api.method_signature)": "database,transaction"
  19963. }
  19964. ]
  19965. },
  19966. RunQuery: {
  19967. requestType: "RunQueryRequest",
  19968. responseType: "RunQueryResponse",
  19969. responseStream: true,
  19970. options: {
  19971. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:runQuery",
  19972. "(google.api.http).body": "*",
  19973. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:runQuery",
  19974. "(google.api.http).additional_bindings.body": "*"
  19975. },
  19976. parsedOptions: [
  19977. {
  19978. "(google.api.http)": {
  19979. post: "/v1/{parent=projects/*/databases/*/documents}:runQuery",
  19980. body: "*",
  19981. additional_bindings: {
  19982. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:runQuery",
  19983. body: "*"
  19984. }
  19985. }
  19986. }
  19987. ]
  19988. },
  19989. RunAggregationQuery: {
  19990. requestType: "RunAggregationQueryRequest",
  19991. responseType: "RunAggregationQueryResponse",
  19992. responseStream: true,
  19993. options: {
  19994. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:runAggregationQuery",
  19995. "(google.api.http).body": "*",
  19996. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:runAggregationQuery",
  19997. "(google.api.http).additional_bindings.body": "*"
  19998. },
  19999. parsedOptions: [
  20000. {
  20001. "(google.api.http)": {
  20002. post: "/v1/{parent=projects/*/databases/*/documents}:runAggregationQuery",
  20003. body: "*",
  20004. additional_bindings: {
  20005. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:runAggregationQuery",
  20006. body: "*"
  20007. }
  20008. }
  20009. }
  20010. ]
  20011. },
  20012. PartitionQuery: {
  20013. requestType: "PartitionQueryRequest",
  20014. responseType: "PartitionQueryResponse",
  20015. options: {
  20016. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:partitionQuery",
  20017. "(google.api.http).body": "*",
  20018. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery",
  20019. "(google.api.http).additional_bindings.body": "*"
  20020. },
  20021. parsedOptions: [
  20022. {
  20023. "(google.api.http)": {
  20024. post: "/v1/{parent=projects/*/databases/*/documents}:partitionQuery",
  20025. body: "*",
  20026. additional_bindings: {
  20027. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery",
  20028. body: "*"
  20029. }
  20030. }
  20031. }
  20032. ]
  20033. },
  20034. Write: {
  20035. requestType: "WriteRequest",
  20036. requestStream: true,
  20037. responseType: "WriteResponse",
  20038. responseStream: true,
  20039. options: {
  20040. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:write",
  20041. "(google.api.http).body": "*"
  20042. },
  20043. parsedOptions: [
  20044. {
  20045. "(google.api.http)": {
  20046. post: "/v1/{database=projects/*/databases/*}/documents:write",
  20047. body: "*"
  20048. }
  20049. }
  20050. ]
  20051. },
  20052. Listen: {
  20053. requestType: "ListenRequest",
  20054. requestStream: true,
  20055. responseType: "ListenResponse",
  20056. responseStream: true,
  20057. options: {
  20058. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:listen",
  20059. "(google.api.http).body": "*"
  20060. },
  20061. parsedOptions: [
  20062. {
  20063. "(google.api.http)": {
  20064. post: "/v1/{database=projects/*/databases/*}/documents:listen",
  20065. body: "*"
  20066. }
  20067. }
  20068. ]
  20069. },
  20070. ListCollectionIds: {
  20071. requestType: "ListCollectionIdsRequest",
  20072. responseType: "ListCollectionIdsResponse",
  20073. options: {
  20074. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents}:listCollectionIds",
  20075. "(google.api.http).body": "*",
  20076. "(google.api.http).additional_bindings.post": "/v1/{parent=projects/*/databases/*/documents/*/**}:listCollectionIds",
  20077. "(google.api.http).additional_bindings.body": "*",
  20078. "(google.api.method_signature)": "parent"
  20079. },
  20080. parsedOptions: [
  20081. {
  20082. "(google.api.http)": {
  20083. post: "/v1/{parent=projects/*/databases/*/documents}:listCollectionIds",
  20084. body: "*",
  20085. additional_bindings: {
  20086. post: "/v1/{parent=projects/*/databases/*/documents/*/**}:listCollectionIds",
  20087. body: "*"
  20088. }
  20089. }
  20090. },
  20091. {
  20092. "(google.api.method_signature)": "parent"
  20093. }
  20094. ]
  20095. },
  20096. BatchWrite: {
  20097. requestType: "BatchWriteRequest",
  20098. responseType: "BatchWriteResponse",
  20099. options: {
  20100. "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:batchWrite",
  20101. "(google.api.http).body": "*"
  20102. },
  20103. parsedOptions: [
  20104. {
  20105. "(google.api.http)": {
  20106. post: "/v1/{database=projects/*/databases/*}/documents:batchWrite",
  20107. body: "*"
  20108. }
  20109. }
  20110. ]
  20111. },
  20112. CreateDocument: {
  20113. requestType: "CreateDocumentRequest",
  20114. responseType: "Document",
  20115. options: {
  20116. "(google.api.http).post": "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}",
  20117. "(google.api.http).body": "document"
  20118. },
  20119. parsedOptions: [
  20120. {
  20121. "(google.api.http)": {
  20122. post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}",
  20123. body: "document"
  20124. }
  20125. }
  20126. ]
  20127. }
  20128. }
  20129. },
  20130. GetDocumentRequest: {
  20131. oneofs: {
  20132. consistencySelector: {
  20133. oneof: [
  20134. "transaction",
  20135. "readTime"
  20136. ]
  20137. }
  20138. },
  20139. fields: {
  20140. name: {
  20141. type: "string",
  20142. id: 1,
  20143. options: {
  20144. "(google.api.field_behavior)": "REQUIRED"
  20145. }
  20146. },
  20147. mask: {
  20148. type: "DocumentMask",
  20149. id: 2
  20150. },
  20151. transaction: {
  20152. type: "bytes",
  20153. id: 3
  20154. },
  20155. readTime: {
  20156. type: "google.protobuf.Timestamp",
  20157. id: 5
  20158. }
  20159. }
  20160. },
  20161. ListDocumentsRequest: {
  20162. oneofs: {
  20163. consistencySelector: {
  20164. oneof: [
  20165. "transaction",
  20166. "readTime"
  20167. ]
  20168. }
  20169. },
  20170. fields: {
  20171. parent: {
  20172. type: "string",
  20173. id: 1,
  20174. options: {
  20175. "(google.api.field_behavior)": "REQUIRED"
  20176. }
  20177. },
  20178. collectionId: {
  20179. type: "string",
  20180. id: 2,
  20181. options: {
  20182. "(google.api.field_behavior)": "REQUIRED"
  20183. }
  20184. },
  20185. pageSize: {
  20186. type: "int32",
  20187. id: 3
  20188. },
  20189. pageToken: {
  20190. type: "string",
  20191. id: 4
  20192. },
  20193. orderBy: {
  20194. type: "string",
  20195. id: 6
  20196. },
  20197. mask: {
  20198. type: "DocumentMask",
  20199. id: 7
  20200. },
  20201. transaction: {
  20202. type: "bytes",
  20203. id: 8
  20204. },
  20205. readTime: {
  20206. type: "google.protobuf.Timestamp",
  20207. id: 10
  20208. },
  20209. showMissing: {
  20210. type: "bool",
  20211. id: 12
  20212. }
  20213. }
  20214. },
  20215. ListDocumentsResponse: {
  20216. fields: {
  20217. documents: {
  20218. rule: "repeated",
  20219. type: "Document",
  20220. id: 1
  20221. },
  20222. nextPageToken: {
  20223. type: "string",
  20224. id: 2
  20225. }
  20226. }
  20227. },
  20228. CreateDocumentRequest: {
  20229. fields: {
  20230. parent: {
  20231. type: "string",
  20232. id: 1,
  20233. options: {
  20234. "(google.api.field_behavior)": "REQUIRED"
  20235. }
  20236. },
  20237. collectionId: {
  20238. type: "string",
  20239. id: 2,
  20240. options: {
  20241. "(google.api.field_behavior)": "REQUIRED"
  20242. }
  20243. },
  20244. documentId: {
  20245. type: "string",
  20246. id: 3
  20247. },
  20248. document: {
  20249. type: "Document",
  20250. id: 4,
  20251. options: {
  20252. "(google.api.field_behavior)": "REQUIRED"
  20253. }
  20254. },
  20255. mask: {
  20256. type: "DocumentMask",
  20257. id: 5
  20258. }
  20259. }
  20260. },
  20261. UpdateDocumentRequest: {
  20262. fields: {
  20263. document: {
  20264. type: "Document",
  20265. id: 1,
  20266. options: {
  20267. "(google.api.field_behavior)": "REQUIRED"
  20268. }
  20269. },
  20270. updateMask: {
  20271. type: "DocumentMask",
  20272. id: 2
  20273. },
  20274. mask: {
  20275. type: "DocumentMask",
  20276. id: 3
  20277. },
  20278. currentDocument: {
  20279. type: "Precondition",
  20280. id: 4
  20281. }
  20282. }
  20283. },
  20284. DeleteDocumentRequest: {
  20285. fields: {
  20286. name: {
  20287. type: "string",
  20288. id: 1,
  20289. options: {
  20290. "(google.api.field_behavior)": "REQUIRED"
  20291. }
  20292. },
  20293. currentDocument: {
  20294. type: "Precondition",
  20295. id: 2
  20296. }
  20297. }
  20298. },
  20299. BatchGetDocumentsRequest: {
  20300. oneofs: {
  20301. consistencySelector: {
  20302. oneof: [
  20303. "transaction",
  20304. "newTransaction",
  20305. "readTime"
  20306. ]
  20307. }
  20308. },
  20309. fields: {
  20310. database: {
  20311. type: "string",
  20312. id: 1,
  20313. options: {
  20314. "(google.api.field_behavior)": "REQUIRED"
  20315. }
  20316. },
  20317. documents: {
  20318. rule: "repeated",
  20319. type: "string",
  20320. id: 2
  20321. },
  20322. mask: {
  20323. type: "DocumentMask",
  20324. id: 3
  20325. },
  20326. transaction: {
  20327. type: "bytes",
  20328. id: 4
  20329. },
  20330. newTransaction: {
  20331. type: "TransactionOptions",
  20332. id: 5
  20333. },
  20334. readTime: {
  20335. type: "google.protobuf.Timestamp",
  20336. id: 7
  20337. }
  20338. }
  20339. },
  20340. BatchGetDocumentsResponse: {
  20341. oneofs: {
  20342. result: {
  20343. oneof: [
  20344. "found",
  20345. "missing"
  20346. ]
  20347. }
  20348. },
  20349. fields: {
  20350. found: {
  20351. type: "Document",
  20352. id: 1
  20353. },
  20354. missing: {
  20355. type: "string",
  20356. id: 2
  20357. },
  20358. transaction: {
  20359. type: "bytes",
  20360. id: 3
  20361. },
  20362. readTime: {
  20363. type: "google.protobuf.Timestamp",
  20364. id: 4
  20365. }
  20366. }
  20367. },
  20368. BeginTransactionRequest: {
  20369. fields: {
  20370. database: {
  20371. type: "string",
  20372. id: 1,
  20373. options: {
  20374. "(google.api.field_behavior)": "REQUIRED"
  20375. }
  20376. },
  20377. options: {
  20378. type: "TransactionOptions",
  20379. id: 2
  20380. }
  20381. }
  20382. },
  20383. BeginTransactionResponse: {
  20384. fields: {
  20385. transaction: {
  20386. type: "bytes",
  20387. id: 1
  20388. }
  20389. }
  20390. },
  20391. CommitRequest: {
  20392. fields: {
  20393. database: {
  20394. type: "string",
  20395. id: 1,
  20396. options: {
  20397. "(google.api.field_behavior)": "REQUIRED"
  20398. }
  20399. },
  20400. writes: {
  20401. rule: "repeated",
  20402. type: "Write",
  20403. id: 2
  20404. },
  20405. transaction: {
  20406. type: "bytes",
  20407. id: 3
  20408. }
  20409. }
  20410. },
  20411. CommitResponse: {
  20412. fields: {
  20413. writeResults: {
  20414. rule: "repeated",
  20415. type: "WriteResult",
  20416. id: 1
  20417. },
  20418. commitTime: {
  20419. type: "google.protobuf.Timestamp",
  20420. id: 2
  20421. }
  20422. }
  20423. },
  20424. RollbackRequest: {
  20425. fields: {
  20426. database: {
  20427. type: "string",
  20428. id: 1,
  20429. options: {
  20430. "(google.api.field_behavior)": "REQUIRED"
  20431. }
  20432. },
  20433. transaction: {
  20434. type: "bytes",
  20435. id: 2,
  20436. options: {
  20437. "(google.api.field_behavior)": "REQUIRED"
  20438. }
  20439. }
  20440. }
  20441. },
  20442. RunQueryRequest: {
  20443. oneofs: {
  20444. queryType: {
  20445. oneof: [
  20446. "structuredQuery"
  20447. ]
  20448. },
  20449. consistencySelector: {
  20450. oneof: [
  20451. "transaction",
  20452. "newTransaction",
  20453. "readTime"
  20454. ]
  20455. }
  20456. },
  20457. fields: {
  20458. parent: {
  20459. type: "string",
  20460. id: 1,
  20461. options: {
  20462. "(google.api.field_behavior)": "REQUIRED"
  20463. }
  20464. },
  20465. structuredQuery: {
  20466. type: "StructuredQuery",
  20467. id: 2
  20468. },
  20469. transaction: {
  20470. type: "bytes",
  20471. id: 5
  20472. },
  20473. newTransaction: {
  20474. type: "TransactionOptions",
  20475. id: 6
  20476. },
  20477. readTime: {
  20478. type: "google.protobuf.Timestamp",
  20479. id: 7
  20480. }
  20481. }
  20482. },
  20483. RunQueryResponse: {
  20484. fields: {
  20485. transaction: {
  20486. type: "bytes",
  20487. id: 2
  20488. },
  20489. document: {
  20490. type: "Document",
  20491. id: 1
  20492. },
  20493. readTime: {
  20494. type: "google.protobuf.Timestamp",
  20495. id: 3
  20496. },
  20497. skippedResults: {
  20498. type: "int32",
  20499. id: 4
  20500. }
  20501. }
  20502. },
  20503. RunAggregationQueryRequest: {
  20504. oneofs: {
  20505. queryType: {
  20506. oneof: [
  20507. "structuredAggregationQuery"
  20508. ]
  20509. },
  20510. consistencySelector: {
  20511. oneof: [
  20512. "transaction",
  20513. "newTransaction",
  20514. "readTime"
  20515. ]
  20516. }
  20517. },
  20518. fields: {
  20519. parent: {
  20520. type: "string",
  20521. id: 1,
  20522. options: {
  20523. "(google.api.field_behavior)": "REQUIRED"
  20524. }
  20525. },
  20526. structuredAggregationQuery: {
  20527. type: "StructuredAggregationQuery",
  20528. id: 2
  20529. },
  20530. transaction: {
  20531. type: "bytes",
  20532. id: 4
  20533. },
  20534. newTransaction: {
  20535. type: "TransactionOptions",
  20536. id: 5
  20537. },
  20538. readTime: {
  20539. type: "google.protobuf.Timestamp",
  20540. id: 6
  20541. }
  20542. }
  20543. },
  20544. RunAggregationQueryResponse: {
  20545. fields: {
  20546. result: {
  20547. type: "AggregationResult",
  20548. id: 1
  20549. },
  20550. transaction: {
  20551. type: "bytes",
  20552. id: 2
  20553. },
  20554. readTime: {
  20555. type: "google.protobuf.Timestamp",
  20556. id: 3
  20557. }
  20558. }
  20559. },
  20560. PartitionQueryRequest: {
  20561. oneofs: {
  20562. queryType: {
  20563. oneof: [
  20564. "structuredQuery"
  20565. ]
  20566. }
  20567. },
  20568. fields: {
  20569. parent: {
  20570. type: "string",
  20571. id: 1,
  20572. options: {
  20573. "(google.api.field_behavior)": "REQUIRED"
  20574. }
  20575. },
  20576. structuredQuery: {
  20577. type: "StructuredQuery",
  20578. id: 2
  20579. },
  20580. partitionCount: {
  20581. type: "int64",
  20582. id: 3
  20583. },
  20584. pageToken: {
  20585. type: "string",
  20586. id: 4
  20587. },
  20588. pageSize: {
  20589. type: "int32",
  20590. id: 5
  20591. }
  20592. }
  20593. },
  20594. PartitionQueryResponse: {
  20595. fields: {
  20596. partitions: {
  20597. rule: "repeated",
  20598. type: "Cursor",
  20599. id: 1
  20600. },
  20601. nextPageToken: {
  20602. type: "string",
  20603. id: 2
  20604. }
  20605. }
  20606. },
  20607. WriteRequest: {
  20608. fields: {
  20609. database: {
  20610. type: "string",
  20611. id: 1,
  20612. options: {
  20613. "(google.api.field_behavior)": "REQUIRED"
  20614. }
  20615. },
  20616. streamId: {
  20617. type: "string",
  20618. id: 2
  20619. },
  20620. writes: {
  20621. rule: "repeated",
  20622. type: "Write",
  20623. id: 3
  20624. },
  20625. streamToken: {
  20626. type: "bytes",
  20627. id: 4
  20628. },
  20629. labels: {
  20630. keyType: "string",
  20631. type: "string",
  20632. id: 5
  20633. }
  20634. }
  20635. },
  20636. WriteResponse: {
  20637. fields: {
  20638. streamId: {
  20639. type: "string",
  20640. id: 1
  20641. },
  20642. streamToken: {
  20643. type: "bytes",
  20644. id: 2
  20645. },
  20646. writeResults: {
  20647. rule: "repeated",
  20648. type: "WriteResult",
  20649. id: 3
  20650. },
  20651. commitTime: {
  20652. type: "google.protobuf.Timestamp",
  20653. id: 4
  20654. }
  20655. }
  20656. },
  20657. ListenRequest: {
  20658. oneofs: {
  20659. targetChange: {
  20660. oneof: [
  20661. "addTarget",
  20662. "removeTarget"
  20663. ]
  20664. }
  20665. },
  20666. fields: {
  20667. database: {
  20668. type: "string",
  20669. id: 1,
  20670. options: {
  20671. "(google.api.field_behavior)": "REQUIRED"
  20672. }
  20673. },
  20674. addTarget: {
  20675. type: "Target",
  20676. id: 2
  20677. },
  20678. removeTarget: {
  20679. type: "int32",
  20680. id: 3
  20681. },
  20682. labels: {
  20683. keyType: "string",
  20684. type: "string",
  20685. id: 4
  20686. }
  20687. }
  20688. },
  20689. ListenResponse: {
  20690. oneofs: {
  20691. responseType: {
  20692. oneof: [
  20693. "targetChange",
  20694. "documentChange",
  20695. "documentDelete",
  20696. "documentRemove",
  20697. "filter"
  20698. ]
  20699. }
  20700. },
  20701. fields: {
  20702. targetChange: {
  20703. type: "TargetChange",
  20704. id: 2
  20705. },
  20706. documentChange: {
  20707. type: "DocumentChange",
  20708. id: 3
  20709. },
  20710. documentDelete: {
  20711. type: "DocumentDelete",
  20712. id: 4
  20713. },
  20714. documentRemove: {
  20715. type: "DocumentRemove",
  20716. id: 6
  20717. },
  20718. filter: {
  20719. type: "ExistenceFilter",
  20720. id: 5
  20721. }
  20722. }
  20723. },
  20724. Target: {
  20725. oneofs: {
  20726. targetType: {
  20727. oneof: [
  20728. "query",
  20729. "documents"
  20730. ]
  20731. },
  20732. resumeType: {
  20733. oneof: [
  20734. "resumeToken",
  20735. "readTime"
  20736. ]
  20737. }
  20738. },
  20739. fields: {
  20740. query: {
  20741. type: "QueryTarget",
  20742. id: 2
  20743. },
  20744. documents: {
  20745. type: "DocumentsTarget",
  20746. id: 3
  20747. },
  20748. resumeToken: {
  20749. type: "bytes",
  20750. id: 4
  20751. },
  20752. readTime: {
  20753. type: "google.protobuf.Timestamp",
  20754. id: 11
  20755. },
  20756. targetId: {
  20757. type: "int32",
  20758. id: 5
  20759. },
  20760. once: {
  20761. type: "bool",
  20762. id: 6
  20763. },
  20764. expectedCount: {
  20765. type: "google.protobuf.Int32Value",
  20766. id: 12
  20767. }
  20768. },
  20769. nested: {
  20770. DocumentsTarget: {
  20771. fields: {
  20772. documents: {
  20773. rule: "repeated",
  20774. type: "string",
  20775. id: 2
  20776. }
  20777. }
  20778. },
  20779. QueryTarget: {
  20780. oneofs: {
  20781. queryType: {
  20782. oneof: [
  20783. "structuredQuery"
  20784. ]
  20785. }
  20786. },
  20787. fields: {
  20788. parent: {
  20789. type: "string",
  20790. id: 1
  20791. },
  20792. structuredQuery: {
  20793. type: "StructuredQuery",
  20794. id: 2
  20795. }
  20796. }
  20797. }
  20798. }
  20799. },
  20800. TargetChange: {
  20801. fields: {
  20802. targetChangeType: {
  20803. type: "TargetChangeType",
  20804. id: 1
  20805. },
  20806. targetIds: {
  20807. rule: "repeated",
  20808. type: "int32",
  20809. id: 2
  20810. },
  20811. cause: {
  20812. type: "google.rpc.Status",
  20813. id: 3
  20814. },
  20815. resumeToken: {
  20816. type: "bytes",
  20817. id: 4
  20818. },
  20819. readTime: {
  20820. type: "google.protobuf.Timestamp",
  20821. id: 6
  20822. }
  20823. },
  20824. nested: {
  20825. TargetChangeType: {
  20826. values: {
  20827. NO_CHANGE: 0,
  20828. ADD: 1,
  20829. REMOVE: 2,
  20830. CURRENT: 3,
  20831. RESET: 4
  20832. }
  20833. }
  20834. }
  20835. },
  20836. ListCollectionIdsRequest: {
  20837. fields: {
  20838. parent: {
  20839. type: "string",
  20840. id: 1,
  20841. options: {
  20842. "(google.api.field_behavior)": "REQUIRED"
  20843. }
  20844. },
  20845. pageSize: {
  20846. type: "int32",
  20847. id: 2
  20848. },
  20849. pageToken: {
  20850. type: "string",
  20851. id: 3
  20852. }
  20853. }
  20854. },
  20855. ListCollectionIdsResponse: {
  20856. fields: {
  20857. collectionIds: {
  20858. rule: "repeated",
  20859. type: "string",
  20860. id: 1
  20861. },
  20862. nextPageToken: {
  20863. type: "string",
  20864. id: 2
  20865. }
  20866. }
  20867. },
  20868. BatchWriteRequest: {
  20869. fields: {
  20870. database: {
  20871. type: "string",
  20872. id: 1,
  20873. options: {
  20874. "(google.api.field_behavior)": "REQUIRED"
  20875. }
  20876. },
  20877. writes: {
  20878. rule: "repeated",
  20879. type: "Write",
  20880. id: 2
  20881. },
  20882. labels: {
  20883. keyType: "string",
  20884. type: "string",
  20885. id: 3
  20886. }
  20887. }
  20888. },
  20889. BatchWriteResponse: {
  20890. fields: {
  20891. writeResults: {
  20892. rule: "repeated",
  20893. type: "WriteResult",
  20894. id: 1
  20895. },
  20896. status: {
  20897. rule: "repeated",
  20898. type: "google.rpc.Status",
  20899. id: 2
  20900. }
  20901. }
  20902. },
  20903. StructuredQuery: {
  20904. fields: {
  20905. select: {
  20906. type: "Projection",
  20907. id: 1
  20908. },
  20909. from: {
  20910. rule: "repeated",
  20911. type: "CollectionSelector",
  20912. id: 2
  20913. },
  20914. where: {
  20915. type: "Filter",
  20916. id: 3
  20917. },
  20918. orderBy: {
  20919. rule: "repeated",
  20920. type: "Order",
  20921. id: 4
  20922. },
  20923. startAt: {
  20924. type: "Cursor",
  20925. id: 7
  20926. },
  20927. endAt: {
  20928. type: "Cursor",
  20929. id: 8
  20930. },
  20931. offset: {
  20932. type: "int32",
  20933. id: 6
  20934. },
  20935. limit: {
  20936. type: "google.protobuf.Int32Value",
  20937. id: 5
  20938. }
  20939. },
  20940. nested: {
  20941. CollectionSelector: {
  20942. fields: {
  20943. collectionId: {
  20944. type: "string",
  20945. id: 2
  20946. },
  20947. allDescendants: {
  20948. type: "bool",
  20949. id: 3
  20950. }
  20951. }
  20952. },
  20953. Filter: {
  20954. oneofs: {
  20955. filterType: {
  20956. oneof: [
  20957. "compositeFilter",
  20958. "fieldFilter",
  20959. "unaryFilter"
  20960. ]
  20961. }
  20962. },
  20963. fields: {
  20964. compositeFilter: {
  20965. type: "CompositeFilter",
  20966. id: 1
  20967. },
  20968. fieldFilter: {
  20969. type: "FieldFilter",
  20970. id: 2
  20971. },
  20972. unaryFilter: {
  20973. type: "UnaryFilter",
  20974. id: 3
  20975. }
  20976. }
  20977. },
  20978. CompositeFilter: {
  20979. fields: {
  20980. op: {
  20981. type: "Operator",
  20982. id: 1
  20983. },
  20984. filters: {
  20985. rule: "repeated",
  20986. type: "Filter",
  20987. id: 2
  20988. }
  20989. },
  20990. nested: {
  20991. Operator: {
  20992. values: {
  20993. OPERATOR_UNSPECIFIED: 0,
  20994. AND: 1,
  20995. OR: 2
  20996. }
  20997. }
  20998. }
  20999. },
  21000. FieldFilter: {
  21001. fields: {
  21002. field: {
  21003. type: "FieldReference",
  21004. id: 1
  21005. },
  21006. op: {
  21007. type: "Operator",
  21008. id: 2
  21009. },
  21010. value: {
  21011. type: "Value",
  21012. id: 3
  21013. }
  21014. },
  21015. nested: {
  21016. Operator: {
  21017. values: {
  21018. OPERATOR_UNSPECIFIED: 0,
  21019. LESS_THAN: 1,
  21020. LESS_THAN_OR_EQUAL: 2,
  21021. GREATER_THAN: 3,
  21022. GREATER_THAN_OR_EQUAL: 4,
  21023. EQUAL: 5,
  21024. NOT_EQUAL: 6,
  21025. ARRAY_CONTAINS: 7,
  21026. IN: 8,
  21027. ARRAY_CONTAINS_ANY: 9,
  21028. NOT_IN: 10
  21029. }
  21030. }
  21031. }
  21032. },
  21033. UnaryFilter: {
  21034. oneofs: {
  21035. operandType: {
  21036. oneof: [
  21037. "field"
  21038. ]
  21039. }
  21040. },
  21041. fields: {
  21042. op: {
  21043. type: "Operator",
  21044. id: 1
  21045. },
  21046. field: {
  21047. type: "FieldReference",
  21048. id: 2
  21049. }
  21050. },
  21051. nested: {
  21052. Operator: {
  21053. values: {
  21054. OPERATOR_UNSPECIFIED: 0,
  21055. IS_NAN: 2,
  21056. IS_NULL: 3,
  21057. IS_NOT_NAN: 4,
  21058. IS_NOT_NULL: 5
  21059. }
  21060. }
  21061. }
  21062. },
  21063. Order: {
  21064. fields: {
  21065. field: {
  21066. type: "FieldReference",
  21067. id: 1
  21068. },
  21069. direction: {
  21070. type: "Direction",
  21071. id: 2
  21072. }
  21073. }
  21074. },
  21075. FieldReference: {
  21076. fields: {
  21077. fieldPath: {
  21078. type: "string",
  21079. id: 2
  21080. }
  21081. }
  21082. },
  21083. Projection: {
  21084. fields: {
  21085. fields: {
  21086. rule: "repeated",
  21087. type: "FieldReference",
  21088. id: 2
  21089. }
  21090. }
  21091. },
  21092. Direction: {
  21093. values: {
  21094. DIRECTION_UNSPECIFIED: 0,
  21095. ASCENDING: 1,
  21096. DESCENDING: 2
  21097. }
  21098. }
  21099. }
  21100. },
  21101. StructuredAggregationQuery: {
  21102. oneofs: {
  21103. queryType: {
  21104. oneof: [
  21105. "structuredQuery"
  21106. ]
  21107. }
  21108. },
  21109. fields: {
  21110. structuredQuery: {
  21111. type: "StructuredQuery",
  21112. id: 1
  21113. },
  21114. aggregations: {
  21115. rule: "repeated",
  21116. type: "Aggregation",
  21117. id: 3
  21118. }
  21119. },
  21120. nested: {
  21121. Aggregation: {
  21122. oneofs: {
  21123. operator: {
  21124. oneof: [
  21125. "count",
  21126. "sum",
  21127. "avg"
  21128. ]
  21129. }
  21130. },
  21131. fields: {
  21132. count: {
  21133. type: "Count",
  21134. id: 1
  21135. },
  21136. sum: {
  21137. type: "Sum",
  21138. id: 2
  21139. },
  21140. avg: {
  21141. type: "Avg",
  21142. id: 3
  21143. },
  21144. alias: {
  21145. type: "string",
  21146. id: 7
  21147. }
  21148. },
  21149. nested: {
  21150. Count: {
  21151. fields: {
  21152. upTo: {
  21153. type: "google.protobuf.Int64Value",
  21154. id: 1
  21155. }
  21156. }
  21157. },
  21158. Sum: {
  21159. fields: {
  21160. field: {
  21161. type: "FieldReference",
  21162. id: 1
  21163. }
  21164. }
  21165. },
  21166. Avg: {
  21167. fields: {
  21168. field: {
  21169. type: "FieldReference",
  21170. id: 1
  21171. }
  21172. }
  21173. }
  21174. }
  21175. }
  21176. }
  21177. },
  21178. Cursor: {
  21179. fields: {
  21180. values: {
  21181. rule: "repeated",
  21182. type: "Value",
  21183. id: 1
  21184. },
  21185. before: {
  21186. type: "bool",
  21187. id: 2
  21188. }
  21189. }
  21190. },
  21191. Write: {
  21192. oneofs: {
  21193. operation: {
  21194. oneof: [
  21195. "update",
  21196. "delete",
  21197. "verify",
  21198. "transform"
  21199. ]
  21200. }
  21201. },
  21202. fields: {
  21203. update: {
  21204. type: "Document",
  21205. id: 1
  21206. },
  21207. "delete": {
  21208. type: "string",
  21209. id: 2
  21210. },
  21211. verify: {
  21212. type: "string",
  21213. id: 5
  21214. },
  21215. transform: {
  21216. type: "DocumentTransform",
  21217. id: 6
  21218. },
  21219. updateMask: {
  21220. type: "DocumentMask",
  21221. id: 3
  21222. },
  21223. updateTransforms: {
  21224. rule: "repeated",
  21225. type: "DocumentTransform.FieldTransform",
  21226. id: 7
  21227. },
  21228. currentDocument: {
  21229. type: "Precondition",
  21230. id: 4
  21231. }
  21232. }
  21233. },
  21234. DocumentTransform: {
  21235. fields: {
  21236. document: {
  21237. type: "string",
  21238. id: 1
  21239. },
  21240. fieldTransforms: {
  21241. rule: "repeated",
  21242. type: "FieldTransform",
  21243. id: 2
  21244. }
  21245. },
  21246. nested: {
  21247. FieldTransform: {
  21248. oneofs: {
  21249. transformType: {
  21250. oneof: [
  21251. "setToServerValue",
  21252. "increment",
  21253. "maximum",
  21254. "minimum",
  21255. "appendMissingElements",
  21256. "removeAllFromArray"
  21257. ]
  21258. }
  21259. },
  21260. fields: {
  21261. fieldPath: {
  21262. type: "string",
  21263. id: 1
  21264. },
  21265. setToServerValue: {
  21266. type: "ServerValue",
  21267. id: 2
  21268. },
  21269. increment: {
  21270. type: "Value",
  21271. id: 3
  21272. },
  21273. maximum: {
  21274. type: "Value",
  21275. id: 4
  21276. },
  21277. minimum: {
  21278. type: "Value",
  21279. id: 5
  21280. },
  21281. appendMissingElements: {
  21282. type: "ArrayValue",
  21283. id: 6
  21284. },
  21285. removeAllFromArray: {
  21286. type: "ArrayValue",
  21287. id: 7
  21288. }
  21289. },
  21290. nested: {
  21291. ServerValue: {
  21292. values: {
  21293. SERVER_VALUE_UNSPECIFIED: 0,
  21294. REQUEST_TIME: 1
  21295. }
  21296. }
  21297. }
  21298. }
  21299. }
  21300. },
  21301. WriteResult: {
  21302. fields: {
  21303. updateTime: {
  21304. type: "google.protobuf.Timestamp",
  21305. id: 1
  21306. },
  21307. transformResults: {
  21308. rule: "repeated",
  21309. type: "Value",
  21310. id: 2
  21311. }
  21312. }
  21313. },
  21314. DocumentChange: {
  21315. fields: {
  21316. document: {
  21317. type: "Document",
  21318. id: 1
  21319. },
  21320. targetIds: {
  21321. rule: "repeated",
  21322. type: "int32",
  21323. id: 5
  21324. },
  21325. removedTargetIds: {
  21326. rule: "repeated",
  21327. type: "int32",
  21328. id: 6
  21329. }
  21330. }
  21331. },
  21332. DocumentDelete: {
  21333. fields: {
  21334. document: {
  21335. type: "string",
  21336. id: 1
  21337. },
  21338. removedTargetIds: {
  21339. rule: "repeated",
  21340. type: "int32",
  21341. id: 6
  21342. },
  21343. readTime: {
  21344. type: "google.protobuf.Timestamp",
  21345. id: 4
  21346. }
  21347. }
  21348. },
  21349. DocumentRemove: {
  21350. fields: {
  21351. document: {
  21352. type: "string",
  21353. id: 1
  21354. },
  21355. removedTargetIds: {
  21356. rule: "repeated",
  21357. type: "int32",
  21358. id: 2
  21359. },
  21360. readTime: {
  21361. type: "google.protobuf.Timestamp",
  21362. id: 4
  21363. }
  21364. }
  21365. },
  21366. ExistenceFilter: {
  21367. fields: {
  21368. targetId: {
  21369. type: "int32",
  21370. id: 1
  21371. },
  21372. count: {
  21373. type: "int32",
  21374. id: 2
  21375. },
  21376. unchangedNames: {
  21377. type: "BloomFilter",
  21378. id: 3
  21379. }
  21380. }
  21381. }
  21382. }
  21383. }
  21384. }
  21385. },
  21386. api: {
  21387. options: {
  21388. go_package: "google.golang.org/genproto/googleapis/api/annotations;annotations",
  21389. java_multiple_files: true,
  21390. java_outer_classname: "HttpProto",
  21391. java_package: "com.google.api",
  21392. objc_class_prefix: "GAPI",
  21393. cc_enable_arenas: true
  21394. },
  21395. nested: {
  21396. http: {
  21397. type: "HttpRule",
  21398. id: 72295728,
  21399. extend: "google.protobuf.MethodOptions"
  21400. },
  21401. Http: {
  21402. fields: {
  21403. rules: {
  21404. rule: "repeated",
  21405. type: "HttpRule",
  21406. id: 1
  21407. }
  21408. }
  21409. },
  21410. HttpRule: {
  21411. oneofs: {
  21412. pattern: {
  21413. oneof: [
  21414. "get",
  21415. "put",
  21416. "post",
  21417. "delete",
  21418. "patch",
  21419. "custom"
  21420. ]
  21421. }
  21422. },
  21423. fields: {
  21424. get: {
  21425. type: "string",
  21426. id: 2
  21427. },
  21428. put: {
  21429. type: "string",
  21430. id: 3
  21431. },
  21432. post: {
  21433. type: "string",
  21434. id: 4
  21435. },
  21436. "delete": {
  21437. type: "string",
  21438. id: 5
  21439. },
  21440. patch: {
  21441. type: "string",
  21442. id: 6
  21443. },
  21444. custom: {
  21445. type: "CustomHttpPattern",
  21446. id: 8
  21447. },
  21448. selector: {
  21449. type: "string",
  21450. id: 1
  21451. },
  21452. body: {
  21453. type: "string",
  21454. id: 7
  21455. },
  21456. additionalBindings: {
  21457. rule: "repeated",
  21458. type: "HttpRule",
  21459. id: 11
  21460. }
  21461. }
  21462. },
  21463. CustomHttpPattern: {
  21464. fields: {
  21465. kind: {
  21466. type: "string",
  21467. id: 1
  21468. },
  21469. path: {
  21470. type: "string",
  21471. id: 2
  21472. }
  21473. }
  21474. },
  21475. methodSignature: {
  21476. rule: "repeated",
  21477. type: "string",
  21478. id: 1051,
  21479. extend: "google.protobuf.MethodOptions"
  21480. },
  21481. defaultHost: {
  21482. type: "string",
  21483. id: 1049,
  21484. extend: "google.protobuf.ServiceOptions"
  21485. },
  21486. oauthScopes: {
  21487. type: "string",
  21488. id: 1050,
  21489. extend: "google.protobuf.ServiceOptions"
  21490. },
  21491. fieldBehavior: {
  21492. rule: "repeated",
  21493. type: "google.api.FieldBehavior",
  21494. id: 1052,
  21495. extend: "google.protobuf.FieldOptions"
  21496. },
  21497. FieldBehavior: {
  21498. values: {
  21499. FIELD_BEHAVIOR_UNSPECIFIED: 0,
  21500. OPTIONAL: 1,
  21501. REQUIRED: 2,
  21502. OUTPUT_ONLY: 3,
  21503. INPUT_ONLY: 4,
  21504. IMMUTABLE: 5,
  21505. UNORDERED_LIST: 6,
  21506. NON_EMPTY_DEFAULT: 7
  21507. }
  21508. }
  21509. }
  21510. },
  21511. type: {
  21512. options: {
  21513. cc_enable_arenas: true,
  21514. go_package: "google.golang.org/genproto/googleapis/type/latlng;latlng",
  21515. java_multiple_files: true,
  21516. java_outer_classname: "LatLngProto",
  21517. java_package: "com.google.type",
  21518. objc_class_prefix: "GTP"
  21519. },
  21520. nested: {
  21521. LatLng: {
  21522. fields: {
  21523. latitude: {
  21524. type: "double",
  21525. id: 1
  21526. },
  21527. longitude: {
  21528. type: "double",
  21529. id: 2
  21530. }
  21531. }
  21532. }
  21533. }
  21534. },
  21535. rpc: {
  21536. options: {
  21537. cc_enable_arenas: true,
  21538. go_package: "google.golang.org/genproto/googleapis/rpc/status;status",
  21539. java_multiple_files: true,
  21540. java_outer_classname: "StatusProto",
  21541. java_package: "com.google.rpc",
  21542. objc_class_prefix: "RPC"
  21543. },
  21544. nested: {
  21545. Status: {
  21546. fields: {
  21547. code: {
  21548. type: "int32",
  21549. id: 1
  21550. },
  21551. message: {
  21552. type: "string",
  21553. id: 2
  21554. },
  21555. details: {
  21556. rule: "repeated",
  21557. type: "google.protobuf.Any",
  21558. id: 3
  21559. }
  21560. }
  21561. }
  21562. }
  21563. }
  21564. }
  21565. }
  21566. };
  21567. var protos = {
  21568. nested: nested
  21569. };
  21570. var protos$1 = /*#__PURE__*/Object.freeze({
  21571. __proto__: null,
  21572. nested: nested,
  21573. 'default': protos
  21574. });
  21575. /**
  21576. * @license
  21577. * Copyright 2020 Google LLC
  21578. *
  21579. * Licensed under the Apache License, Version 2.0 (the "License");
  21580. * you may not use this file except in compliance with the License.
  21581. * You may obtain a copy of the License at
  21582. *
  21583. * http://www.apache.org/licenses/LICENSE-2.0
  21584. *
  21585. * Unless required by applicable law or agreed to in writing, software
  21586. * distributed under the License is distributed on an "AS IS" BASIS,
  21587. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21588. * See the License for the specific language governing permissions and
  21589. * limitations under the License.
  21590. */
  21591. /** Used by tests so we can match @grpc/proto-loader behavior. */
  21592. const protoLoaderOptions = {
  21593. longs: String,
  21594. enums: String,
  21595. defaults: true,
  21596. oneofs: false
  21597. };
  21598. /**
  21599. * Loads the protocol buffer definitions for Firestore.
  21600. *
  21601. * @returns The GrpcObject representing our protos.
  21602. */
  21603. function loadProtos() {
  21604. const packageDefinition = protoLoader.fromJSON(protos$1, protoLoaderOptions);
  21605. return grpc.loadPackageDefinition(packageDefinition);
  21606. }
  21607. /**
  21608. * @license
  21609. * Copyright 2020 Google LLC
  21610. *
  21611. * Licensed under the Apache License, Version 2.0 (the "License");
  21612. * you may not use this file except in compliance with the License.
  21613. * You may obtain a copy of the License at
  21614. *
  21615. * http://www.apache.org/licenses/LICENSE-2.0
  21616. *
  21617. * Unless required by applicable law or agreed to in writing, software
  21618. * distributed under the License is distributed on an "AS IS" BASIS,
  21619. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21620. * See the License for the specific language governing permissions and
  21621. * limitations under the License.
  21622. */
  21623. /** Loads the GRPC stack */
  21624. function newConnection(databaseInfo) {
  21625. const protos = loadProtos();
  21626. return new GrpcConnection(protos, databaseInfo);
  21627. }
  21628. /** Return the Platform-specific connectivity monitor. */
  21629. function newConnectivityMonitor() {
  21630. return new NoopConnectivityMonitor();
  21631. }
  21632. /**
  21633. * @license
  21634. * Copyright 2020 Google LLC
  21635. *
  21636. * Licensed under the Apache License, Version 2.0 (the "License");
  21637. * you may not use this file except in compliance with the License.
  21638. * You may obtain a copy of the License at
  21639. *
  21640. * http://www.apache.org/licenses/LICENSE-2.0
  21641. *
  21642. * Unless required by applicable law or agreed to in writing, software
  21643. * distributed under the License is distributed on an "AS IS" BASIS,
  21644. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21645. * See the License for the specific language governing permissions and
  21646. * limitations under the License.
  21647. */
  21648. /** The Platform's 'window' implementation or null if not available. */
  21649. function getWindow() {
  21650. if (process.env.USE_MOCK_PERSISTENCE === 'YES') {
  21651. // eslint-disable-next-line no-restricted-globals
  21652. return window;
  21653. }
  21654. return null;
  21655. }
  21656. /** The Platform's 'document' implementation or null if not available. */
  21657. function getDocument() {
  21658. return null;
  21659. }
  21660. /**
  21661. * @license
  21662. * Copyright 2020 Google LLC
  21663. *
  21664. * Licensed under the Apache License, Version 2.0 (the "License");
  21665. * you may not use this file except in compliance with the License.
  21666. * You may obtain a copy of the License at
  21667. *
  21668. * http://www.apache.org/licenses/LICENSE-2.0
  21669. *
  21670. * Unless required by applicable law or agreed to in writing, software
  21671. * distributed under the License is distributed on an "AS IS" BASIS,
  21672. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21673. * See the License for the specific language governing permissions and
  21674. * limitations under the License.
  21675. */
  21676. function newSerializer(databaseId) {
  21677. return new JsonProtoSerializer(databaseId, /* useProto3Json= */ false);
  21678. }
  21679. /**
  21680. * @license
  21681. * Copyright 2017 Google LLC
  21682. *
  21683. * Licensed under the Apache License, Version 2.0 (the "License");
  21684. * you may not use this file except in compliance with the License.
  21685. * You may obtain a copy of the License at
  21686. *
  21687. * http://www.apache.org/licenses/LICENSE-2.0
  21688. *
  21689. * Unless required by applicable law or agreed to in writing, software
  21690. * distributed under the License is distributed on an "AS IS" BASIS,
  21691. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21692. * See the License for the specific language governing permissions and
  21693. * limitations under the License.
  21694. */
  21695. const LOG_TAG$8 = 'ExponentialBackoff';
  21696. /**
  21697. * Initial backoff time in milliseconds after an error.
  21698. * Set to 1s according to https://cloud.google.com/apis/design/errors.
  21699. */
  21700. const DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000;
  21701. const DEFAULT_BACKOFF_FACTOR = 1.5;
  21702. /** Maximum backoff time in milliseconds */
  21703. const DEFAULT_BACKOFF_MAX_DELAY_MS = 60 * 1000;
  21704. /**
  21705. * A helper for running delayed tasks following an exponential backoff curve
  21706. * between attempts.
  21707. *
  21708. * Each delay is made up of a "base" delay which follows the exponential
  21709. * backoff curve, and a +/- 50% "jitter" that is calculated and added to the
  21710. * base delay. This prevents clients from accidentally synchronizing their
  21711. * delays causing spikes of load to the backend.
  21712. */
  21713. class ExponentialBackoff {
  21714. constructor(
  21715. /**
  21716. * The AsyncQueue to run backoff operations on.
  21717. */
  21718. queue,
  21719. /**
  21720. * The ID to use when scheduling backoff operations on the AsyncQueue.
  21721. */
  21722. timerId,
  21723. /**
  21724. * The initial delay (used as the base delay on the first retry attempt).
  21725. * Note that jitter will still be applied, so the actual delay could be as
  21726. * little as 0.5*initialDelayMs.
  21727. */
  21728. initialDelayMs = DEFAULT_BACKOFF_INITIAL_DELAY_MS,
  21729. /**
  21730. * The multiplier to use to determine the extended base delay after each
  21731. * attempt.
  21732. */
  21733. backoffFactor = DEFAULT_BACKOFF_FACTOR,
  21734. /**
  21735. * The maximum base delay after which no further backoff is performed.
  21736. * Note that jitter will still be applied, so the actual delay could be as
  21737. * much as 1.5*maxDelayMs.
  21738. */
  21739. maxDelayMs = DEFAULT_BACKOFF_MAX_DELAY_MS) {
  21740. this.queue = queue;
  21741. this.timerId = timerId;
  21742. this.initialDelayMs = initialDelayMs;
  21743. this.backoffFactor = backoffFactor;
  21744. this.maxDelayMs = maxDelayMs;
  21745. this.currentBaseMs = 0;
  21746. this.timerPromise = null;
  21747. /** The last backoff attempt, as epoch milliseconds. */
  21748. this.lastAttemptTime = Date.now();
  21749. this.reset();
  21750. }
  21751. /**
  21752. * Resets the backoff delay.
  21753. *
  21754. * The very next backoffAndWait() will have no delay. If it is called again
  21755. * (i.e. due to an error), initialDelayMs (plus jitter) will be used, and
  21756. * subsequent ones will increase according to the backoffFactor.
  21757. */
  21758. reset() {
  21759. this.currentBaseMs = 0;
  21760. }
  21761. /**
  21762. * Resets the backoff delay to the maximum delay (e.g. for use after a
  21763. * RESOURCE_EXHAUSTED error).
  21764. */
  21765. resetToMax() {
  21766. this.currentBaseMs = this.maxDelayMs;
  21767. }
  21768. /**
  21769. * Returns a promise that resolves after currentDelayMs, and increases the
  21770. * delay for any subsequent attempts. If there was a pending backoff operation
  21771. * already, it will be canceled.
  21772. */
  21773. backoffAndRun(op) {
  21774. // Cancel any pending backoff operation.
  21775. this.cancel();
  21776. // First schedule using the current base (which may be 0 and should be
  21777. // honored as such).
  21778. const desiredDelayWithJitterMs = Math.floor(this.currentBaseMs + this.jitterDelayMs());
  21779. // Guard against lastAttemptTime being in the future due to a clock change.
  21780. const delaySoFarMs = Math.max(0, Date.now() - this.lastAttemptTime);
  21781. // Guard against the backoff delay already being past.
  21782. const remainingDelayMs = Math.max(0, desiredDelayWithJitterMs - delaySoFarMs);
  21783. if (remainingDelayMs > 0) {
  21784. logDebug(LOG_TAG$8, `Backing off for ${remainingDelayMs} ms ` +
  21785. `(base delay: ${this.currentBaseMs} ms, ` +
  21786. `delay with jitter: ${desiredDelayWithJitterMs} ms, ` +
  21787. `last attempt: ${delaySoFarMs} ms ago)`);
  21788. }
  21789. this.timerPromise = this.queue.enqueueAfterDelay(this.timerId, remainingDelayMs, () => {
  21790. this.lastAttemptTime = Date.now();
  21791. return op();
  21792. });
  21793. // Apply backoff factor to determine next delay and ensure it is within
  21794. // bounds.
  21795. this.currentBaseMs *= this.backoffFactor;
  21796. if (this.currentBaseMs < this.initialDelayMs) {
  21797. this.currentBaseMs = this.initialDelayMs;
  21798. }
  21799. if (this.currentBaseMs > this.maxDelayMs) {
  21800. this.currentBaseMs = this.maxDelayMs;
  21801. }
  21802. }
  21803. skipBackoff() {
  21804. if (this.timerPromise !== null) {
  21805. this.timerPromise.skipDelay();
  21806. this.timerPromise = null;
  21807. }
  21808. }
  21809. cancel() {
  21810. if (this.timerPromise !== null) {
  21811. this.timerPromise.cancel();
  21812. this.timerPromise = null;
  21813. }
  21814. }
  21815. /** Returns a random value in the range [-currentBaseMs/2, currentBaseMs/2] */
  21816. jitterDelayMs() {
  21817. return (Math.random() - 0.5) * this.currentBaseMs;
  21818. }
  21819. }
  21820. /**
  21821. * @license
  21822. * Copyright 2017 Google LLC
  21823. *
  21824. * Licensed under the Apache License, Version 2.0 (the "License");
  21825. * you may not use this file except in compliance with the License.
  21826. * You may obtain a copy of the License at
  21827. *
  21828. * http://www.apache.org/licenses/LICENSE-2.0
  21829. *
  21830. * Unless required by applicable law or agreed to in writing, software
  21831. * distributed under the License is distributed on an "AS IS" BASIS,
  21832. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21833. * See the License for the specific language governing permissions and
  21834. * limitations under the License.
  21835. */
  21836. const LOG_TAG$7 = 'PersistentStream';
  21837. /** The time a stream stays open after it is marked idle. */
  21838. const IDLE_TIMEOUT_MS = 60 * 1000;
  21839. /** The time a stream stays open until we consider it healthy. */
  21840. const HEALTHY_TIMEOUT_MS = 10 * 1000;
  21841. /**
  21842. * A PersistentStream is an abstract base class that represents a streaming RPC
  21843. * to the Firestore backend. It's built on top of the connections own support
  21844. * for streaming RPCs, and adds several critical features for our clients:
  21845. *
  21846. * - Exponential backoff on failure
  21847. * - Authentication via CredentialsProvider
  21848. * - Dispatching all callbacks into the shared worker queue
  21849. * - Closing idle streams after 60 seconds of inactivity
  21850. *
  21851. * Subclasses of PersistentStream implement serialization of models to and
  21852. * from the JSON representation of the protocol buffers for a specific
  21853. * streaming RPC.
  21854. *
  21855. * ## Starting and Stopping
  21856. *
  21857. * Streaming RPCs are stateful and need to be start()ed before messages can
  21858. * be sent and received. The PersistentStream will call the onOpen() function
  21859. * of the listener once the stream is ready to accept requests.
  21860. *
  21861. * Should a start() fail, PersistentStream will call the registered onClose()
  21862. * listener with a FirestoreError indicating what went wrong.
  21863. *
  21864. * A PersistentStream can be started and stopped repeatedly.
  21865. *
  21866. * Generic types:
  21867. * SendType: The type of the outgoing message of the underlying
  21868. * connection stream
  21869. * ReceiveType: The type of the incoming message of the underlying
  21870. * connection stream
  21871. * ListenerType: The type of the listener that will be used for callbacks
  21872. */
  21873. class PersistentStream {
  21874. constructor(queue, connectionTimerId, idleTimerId, healthTimerId, connection, authCredentialsProvider, appCheckCredentialsProvider, listener) {
  21875. this.queue = queue;
  21876. this.idleTimerId = idleTimerId;
  21877. this.healthTimerId = healthTimerId;
  21878. this.connection = connection;
  21879. this.authCredentialsProvider = authCredentialsProvider;
  21880. this.appCheckCredentialsProvider = appCheckCredentialsProvider;
  21881. this.listener = listener;
  21882. this.state = 0 /* PersistentStreamState.Initial */;
  21883. /**
  21884. * A close count that's incremented every time the stream is closed; used by
  21885. * getCloseGuardedDispatcher() to invalidate callbacks that happen after
  21886. * close.
  21887. */
  21888. this.closeCount = 0;
  21889. this.idleTimer = null;
  21890. this.healthCheck = null;
  21891. this.stream = null;
  21892. this.backoff = new ExponentialBackoff(queue, connectionTimerId);
  21893. }
  21894. /**
  21895. * Returns true if start() has been called and no error has occurred. True
  21896. * indicates the stream is open or in the process of opening (which
  21897. * encompasses respecting backoff, getting auth tokens, and starting the
  21898. * actual RPC). Use isOpen() to determine if the stream is open and ready for
  21899. * outbound requests.
  21900. */
  21901. isStarted() {
  21902. return (this.state === 1 /* PersistentStreamState.Starting */ ||
  21903. this.state === 5 /* PersistentStreamState.Backoff */ ||
  21904. this.isOpen());
  21905. }
  21906. /**
  21907. * Returns true if the underlying RPC is open (the onOpen() listener has been
  21908. * called) and the stream is ready for outbound requests.
  21909. */
  21910. isOpen() {
  21911. return (this.state === 2 /* PersistentStreamState.Open */ ||
  21912. this.state === 3 /* PersistentStreamState.Healthy */);
  21913. }
  21914. /**
  21915. * Starts the RPC. Only allowed if isStarted() returns false. The stream is
  21916. * not immediately ready for use: onOpen() will be invoked when the RPC is
  21917. * ready for outbound requests, at which point isOpen() will return true.
  21918. *
  21919. * When start returns, isStarted() will return true.
  21920. */
  21921. start() {
  21922. if (this.state === 4 /* PersistentStreamState.Error */) {
  21923. this.performBackoff();
  21924. return;
  21925. }
  21926. this.auth();
  21927. }
  21928. /**
  21929. * Stops the RPC. This call is idempotent and allowed regardless of the
  21930. * current isStarted() state.
  21931. *
  21932. * When stop returns, isStarted() and isOpen() will both return false.
  21933. */
  21934. async stop() {
  21935. if (this.isStarted()) {
  21936. await this.close(0 /* PersistentStreamState.Initial */);
  21937. }
  21938. }
  21939. /**
  21940. * After an error the stream will usually back off on the next attempt to
  21941. * start it. If the error warrants an immediate restart of the stream, the
  21942. * sender can use this to indicate that the receiver should not back off.
  21943. *
  21944. * Each error will call the onClose() listener. That function can decide to
  21945. * inhibit backoff if required.
  21946. */
  21947. inhibitBackoff() {
  21948. this.state = 0 /* PersistentStreamState.Initial */;
  21949. this.backoff.reset();
  21950. }
  21951. /**
  21952. * Marks this stream as idle. If no further actions are performed on the
  21953. * stream for one minute, the stream will automatically close itself and
  21954. * notify the stream's onClose() handler with Status.OK. The stream will then
  21955. * be in a !isStarted() state, requiring the caller to start the stream again
  21956. * before further use.
  21957. *
  21958. * Only streams that are in state 'Open' can be marked idle, as all other
  21959. * states imply pending network operations.
  21960. */
  21961. markIdle() {
  21962. // Starts the idle time if we are in state 'Open' and are not yet already
  21963. // running a timer (in which case the previous idle timeout still applies).
  21964. if (this.isOpen() && this.idleTimer === null) {
  21965. this.idleTimer = this.queue.enqueueAfterDelay(this.idleTimerId, IDLE_TIMEOUT_MS, () => this.handleIdleCloseTimer());
  21966. }
  21967. }
  21968. /** Sends a message to the underlying stream. */
  21969. sendRequest(msg) {
  21970. this.cancelIdleCheck();
  21971. this.stream.send(msg);
  21972. }
  21973. /** Called by the idle timer when the stream should close due to inactivity. */
  21974. async handleIdleCloseTimer() {
  21975. if (this.isOpen()) {
  21976. // When timing out an idle stream there's no reason to force the stream into backoff when
  21977. // it restarts so set the stream state to Initial instead of Error.
  21978. return this.close(0 /* PersistentStreamState.Initial */);
  21979. }
  21980. }
  21981. /** Marks the stream as active again. */
  21982. cancelIdleCheck() {
  21983. if (this.idleTimer) {
  21984. this.idleTimer.cancel();
  21985. this.idleTimer = null;
  21986. }
  21987. }
  21988. /** Cancels the health check delayed operation. */
  21989. cancelHealthCheck() {
  21990. if (this.healthCheck) {
  21991. this.healthCheck.cancel();
  21992. this.healthCheck = null;
  21993. }
  21994. }
  21995. /**
  21996. * Closes the stream and cleans up as necessary:
  21997. *
  21998. * * closes the underlying GRPC stream;
  21999. * * calls the onClose handler with the given 'error';
  22000. * * sets internal stream state to 'finalState';
  22001. * * adjusts the backoff timer based on the error
  22002. *
  22003. * A new stream can be opened by calling start().
  22004. *
  22005. * @param finalState - the intended state of the stream after closing.
  22006. * @param error - the error the connection was closed with.
  22007. */
  22008. async close(finalState, error) {
  22009. // Cancel any outstanding timers (they're guaranteed not to execute).
  22010. this.cancelIdleCheck();
  22011. this.cancelHealthCheck();
  22012. this.backoff.cancel();
  22013. // Invalidates any stream-related callbacks (e.g. from auth or the
  22014. // underlying stream), guaranteeing they won't execute.
  22015. this.closeCount++;
  22016. if (finalState !== 4 /* PersistentStreamState.Error */) {
  22017. // If this is an intentional close ensure we don't delay our next connection attempt.
  22018. this.backoff.reset();
  22019. }
  22020. else if (error && error.code === Code.RESOURCE_EXHAUSTED) {
  22021. // Log the error. (Probably either 'quota exceeded' or 'max queue length reached'.)
  22022. logError(error.toString());
  22023. logError('Using maximum backoff delay to prevent overloading the backend.');
  22024. this.backoff.resetToMax();
  22025. }
  22026. else if (error &&
  22027. error.code === Code.UNAUTHENTICATED &&
  22028. this.state !== 3 /* PersistentStreamState.Healthy */) {
  22029. // "unauthenticated" error means the token was rejected. This should rarely
  22030. // happen since both Auth and AppCheck ensure a sufficient TTL when we
  22031. // request a token. If a user manually resets their system clock this can
  22032. // fail, however. In this case, we should get a Code.UNAUTHENTICATED error
  22033. // before we received the first message and we need to invalidate the token
  22034. // to ensure that we fetch a new token.
  22035. this.authCredentialsProvider.invalidateToken();
  22036. this.appCheckCredentialsProvider.invalidateToken();
  22037. }
  22038. // Clean up the underlying stream because we are no longer interested in events.
  22039. if (this.stream !== null) {
  22040. this.tearDown();
  22041. this.stream.close();
  22042. this.stream = null;
  22043. }
  22044. // This state must be assigned before calling onClose() to allow the callback to
  22045. // inhibit backoff or otherwise manipulate the state in its non-started state.
  22046. this.state = finalState;
  22047. // Notify the listener that the stream closed.
  22048. await this.listener.onClose(error);
  22049. }
  22050. /**
  22051. * Can be overridden to perform additional cleanup before the stream is closed.
  22052. * Calling super.tearDown() is not required.
  22053. */
  22054. tearDown() { }
  22055. auth() {
  22056. this.state = 1 /* PersistentStreamState.Starting */;
  22057. const dispatchIfNotClosed = this.getCloseGuardedDispatcher(this.closeCount);
  22058. // TODO(mikelehen): Just use dispatchIfNotClosed, but see TODO below.
  22059. const closeCount = this.closeCount;
  22060. Promise.all([
  22061. this.authCredentialsProvider.getToken(),
  22062. this.appCheckCredentialsProvider.getToken()
  22063. ]).then(([authToken, appCheckToken]) => {
  22064. // Stream can be stopped while waiting for authentication.
  22065. // TODO(mikelehen): We really should just use dispatchIfNotClosed
  22066. // and let this dispatch onto the queue, but that opened a spec test can
  22067. // of worms that I don't want to deal with in this PR.
  22068. if (this.closeCount === closeCount) {
  22069. // Normally we'd have to schedule the callback on the AsyncQueue.
  22070. // However, the following calls are safe to be called outside the
  22071. // AsyncQueue since they don't chain asynchronous calls
  22072. this.startStream(authToken, appCheckToken);
  22073. }
  22074. }, (error) => {
  22075. dispatchIfNotClosed(() => {
  22076. const rpcError = new FirestoreError(Code.UNKNOWN, 'Fetching auth token failed: ' + error.message);
  22077. return this.handleStreamClose(rpcError);
  22078. });
  22079. });
  22080. }
  22081. startStream(authToken, appCheckToken) {
  22082. const dispatchIfNotClosed = this.getCloseGuardedDispatcher(this.closeCount);
  22083. this.stream = this.startRpc(authToken, appCheckToken);
  22084. this.stream.onOpen(() => {
  22085. dispatchIfNotClosed(() => {
  22086. this.state = 2 /* PersistentStreamState.Open */;
  22087. this.healthCheck = this.queue.enqueueAfterDelay(this.healthTimerId, HEALTHY_TIMEOUT_MS, () => {
  22088. if (this.isOpen()) {
  22089. this.state = 3 /* PersistentStreamState.Healthy */;
  22090. }
  22091. return Promise.resolve();
  22092. });
  22093. return this.listener.onOpen();
  22094. });
  22095. });
  22096. this.stream.onClose((error) => {
  22097. dispatchIfNotClosed(() => {
  22098. return this.handleStreamClose(error);
  22099. });
  22100. });
  22101. this.stream.onMessage((msg) => {
  22102. dispatchIfNotClosed(() => {
  22103. return this.onMessage(msg);
  22104. });
  22105. });
  22106. }
  22107. performBackoff() {
  22108. this.state = 5 /* PersistentStreamState.Backoff */;
  22109. this.backoff.backoffAndRun(async () => {
  22110. this.state = 0 /* PersistentStreamState.Initial */;
  22111. this.start();
  22112. });
  22113. }
  22114. // Visible for tests
  22115. handleStreamClose(error) {
  22116. logDebug(LOG_TAG$7, `close with error: ${error}`);
  22117. this.stream = null;
  22118. // In theory the stream could close cleanly, however, in our current model
  22119. // we never expect this to happen because if we stop a stream ourselves,
  22120. // this callback will never be called. To prevent cases where we retry
  22121. // without a backoff accidentally, we set the stream to error in all cases.
  22122. return this.close(4 /* PersistentStreamState.Error */, error);
  22123. }
  22124. /**
  22125. * Returns a "dispatcher" function that dispatches operations onto the
  22126. * AsyncQueue but only runs them if closeCount remains unchanged. This allows
  22127. * us to turn auth / stream callbacks into no-ops if the stream is closed /
  22128. * re-opened, etc.
  22129. */
  22130. getCloseGuardedDispatcher(startCloseCount) {
  22131. return (fn) => {
  22132. this.queue.enqueueAndForget(() => {
  22133. if (this.closeCount === startCloseCount) {
  22134. return fn();
  22135. }
  22136. else {
  22137. logDebug(LOG_TAG$7, 'stream callback skipped by getCloseGuardedDispatcher.');
  22138. return Promise.resolve();
  22139. }
  22140. });
  22141. };
  22142. }
  22143. }
  22144. /**
  22145. * A PersistentStream that implements the Listen RPC.
  22146. *
  22147. * Once the Listen stream has called the onOpen() listener, any number of
  22148. * listen() and unlisten() calls can be made to control what changes will be
  22149. * sent from the server for ListenResponses.
  22150. */
  22151. class PersistentListenStream extends PersistentStream {
  22152. constructor(queue, connection, authCredentials, appCheckCredentials, serializer, listener) {
  22153. super(queue, "listen_stream_connection_backoff" /* TimerId.ListenStreamConnectionBackoff */, "listen_stream_idle" /* TimerId.ListenStreamIdle */, "health_check_timeout" /* TimerId.HealthCheckTimeout */, connection, authCredentials, appCheckCredentials, listener);
  22154. this.serializer = serializer;
  22155. }
  22156. startRpc(authToken, appCheckToken) {
  22157. return this.connection.openStream('Listen', authToken, appCheckToken);
  22158. }
  22159. onMessage(watchChangeProto) {
  22160. // A successful response means the stream is healthy
  22161. this.backoff.reset();
  22162. const watchChange = fromWatchChange(this.serializer, watchChangeProto);
  22163. const snapshot = versionFromListenResponse(watchChangeProto);
  22164. return this.listener.onWatchChange(watchChange, snapshot);
  22165. }
  22166. /**
  22167. * Registers interest in the results of the given target. If the target
  22168. * includes a resumeToken it will be included in the request. Results that
  22169. * affect the target will be streamed back as WatchChange messages that
  22170. * reference the targetId.
  22171. */
  22172. watch(targetData) {
  22173. const request = {};
  22174. request.database = getEncodedDatabaseId(this.serializer);
  22175. request.addTarget = toTarget(this.serializer, targetData);
  22176. const labels = toListenRequestLabels(this.serializer, targetData);
  22177. if (labels) {
  22178. request.labels = labels;
  22179. }
  22180. this.sendRequest(request);
  22181. }
  22182. /**
  22183. * Unregisters interest in the results of the target associated with the
  22184. * given targetId.
  22185. */
  22186. unwatch(targetId) {
  22187. const request = {};
  22188. request.database = getEncodedDatabaseId(this.serializer);
  22189. request.removeTarget = targetId;
  22190. this.sendRequest(request);
  22191. }
  22192. }
  22193. /**
  22194. * A Stream that implements the Write RPC.
  22195. *
  22196. * The Write RPC requires the caller to maintain special streamToken
  22197. * state in between calls, to help the server understand which responses the
  22198. * client has processed by the time the next request is made. Every response
  22199. * will contain a streamToken; this value must be passed to the next
  22200. * request.
  22201. *
  22202. * After calling start() on this stream, the next request must be a handshake,
  22203. * containing whatever streamToken is on hand. Once a response to this
  22204. * request is received, all pending mutations may be submitted. When
  22205. * submitting multiple batches of mutations at the same time, it's
  22206. * okay to use the same streamToken for the calls to writeMutations.
  22207. *
  22208. * TODO(b/33271235): Use proto types
  22209. */
  22210. class PersistentWriteStream extends PersistentStream {
  22211. constructor(queue, connection, authCredentials, appCheckCredentials, serializer, listener) {
  22212. super(queue, "write_stream_connection_backoff" /* TimerId.WriteStreamConnectionBackoff */, "write_stream_idle" /* TimerId.WriteStreamIdle */, "health_check_timeout" /* TimerId.HealthCheckTimeout */, connection, authCredentials, appCheckCredentials, listener);
  22213. this.serializer = serializer;
  22214. this.handshakeComplete_ = false;
  22215. }
  22216. /**
  22217. * Tracks whether or not a handshake has been successfully exchanged and
  22218. * the stream is ready to accept mutations.
  22219. */
  22220. get handshakeComplete() {
  22221. return this.handshakeComplete_;
  22222. }
  22223. // Override of PersistentStream.start
  22224. start() {
  22225. this.handshakeComplete_ = false;
  22226. this.lastStreamToken = undefined;
  22227. super.start();
  22228. }
  22229. tearDown() {
  22230. if (this.handshakeComplete_) {
  22231. this.writeMutations([]);
  22232. }
  22233. }
  22234. startRpc(authToken, appCheckToken) {
  22235. return this.connection.openStream('Write', authToken, appCheckToken);
  22236. }
  22237. onMessage(responseProto) {
  22238. // Always capture the last stream token.
  22239. hardAssert(!!responseProto.streamToken);
  22240. this.lastStreamToken = responseProto.streamToken;
  22241. if (!this.handshakeComplete_) {
  22242. // The first response is always the handshake response
  22243. hardAssert(!responseProto.writeResults || responseProto.writeResults.length === 0);
  22244. this.handshakeComplete_ = true;
  22245. return this.listener.onHandshakeComplete();
  22246. }
  22247. else {
  22248. // A successful first write response means the stream is healthy,
  22249. // Note, that we could consider a successful handshake healthy, however,
  22250. // the write itself might be causing an error we want to back off from.
  22251. this.backoff.reset();
  22252. const results = fromWriteResults(responseProto.writeResults, responseProto.commitTime);
  22253. const commitVersion = fromVersion(responseProto.commitTime);
  22254. return this.listener.onMutationResult(commitVersion, results);
  22255. }
  22256. }
  22257. /**
  22258. * Sends an initial streamToken to the server, performing the handshake
  22259. * required to make the StreamingWrite RPC work. Subsequent
  22260. * calls should wait until onHandshakeComplete was called.
  22261. */
  22262. writeHandshake() {
  22263. // TODO(dimond): Support stream resumption. We intentionally do not set the
  22264. // stream token on the handshake, ignoring any stream token we might have.
  22265. const request = {};
  22266. request.database = getEncodedDatabaseId(this.serializer);
  22267. this.sendRequest(request);
  22268. }
  22269. /** Sends a group of mutations to the Firestore backend to apply. */
  22270. writeMutations(mutations) {
  22271. const request = {
  22272. streamToken: this.lastStreamToken,
  22273. writes: mutations.map(mutation => toMutation(this.serializer, mutation))
  22274. };
  22275. this.sendRequest(request);
  22276. }
  22277. }
  22278. /**
  22279. * @license
  22280. * Copyright 2017 Google LLC
  22281. *
  22282. * Licensed under the Apache License, Version 2.0 (the "License");
  22283. * you may not use this file except in compliance with the License.
  22284. * You may obtain a copy of the License at
  22285. *
  22286. * http://www.apache.org/licenses/LICENSE-2.0
  22287. *
  22288. * Unless required by applicable law or agreed to in writing, software
  22289. * distributed under the License is distributed on an "AS IS" BASIS,
  22290. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22291. * See the License for the specific language governing permissions and
  22292. * limitations under the License.
  22293. */
  22294. /**
  22295. * Datastore and its related methods are a wrapper around the external Google
  22296. * Cloud Datastore grpc API, which provides an interface that is more convenient
  22297. * for the rest of the client SDK architecture to consume.
  22298. */
  22299. class Datastore {
  22300. }
  22301. /**
  22302. * An implementation of Datastore that exposes additional state for internal
  22303. * consumption.
  22304. */
  22305. class DatastoreImpl extends Datastore {
  22306. constructor(authCredentials, appCheckCredentials, connection, serializer) {
  22307. super();
  22308. this.authCredentials = authCredentials;
  22309. this.appCheckCredentials = appCheckCredentials;
  22310. this.connection = connection;
  22311. this.serializer = serializer;
  22312. this.terminated = false;
  22313. }
  22314. verifyInitialized() {
  22315. if (this.terminated) {
  22316. throw new FirestoreError(Code.FAILED_PRECONDITION, 'The client has already been terminated.');
  22317. }
  22318. }
  22319. /** Invokes the provided RPC with auth and AppCheck tokens. */
  22320. invokeRPC(rpcName, path, request) {
  22321. this.verifyInitialized();
  22322. return Promise.all([
  22323. this.authCredentials.getToken(),
  22324. this.appCheckCredentials.getToken()
  22325. ])
  22326. .then(([authToken, appCheckToken]) => {
  22327. return this.connection.invokeRPC(rpcName, path, request, authToken, appCheckToken);
  22328. })
  22329. .catch((error) => {
  22330. if (error.name === 'FirebaseError') {
  22331. if (error.code === Code.UNAUTHENTICATED) {
  22332. this.authCredentials.invalidateToken();
  22333. this.appCheckCredentials.invalidateToken();
  22334. }
  22335. throw error;
  22336. }
  22337. else {
  22338. throw new FirestoreError(Code.UNKNOWN, error.toString());
  22339. }
  22340. });
  22341. }
  22342. /** Invokes the provided RPC with streamed results with auth and AppCheck tokens. */
  22343. invokeStreamingRPC(rpcName, path, request, expectedResponseCount) {
  22344. this.verifyInitialized();
  22345. return Promise.all([
  22346. this.authCredentials.getToken(),
  22347. this.appCheckCredentials.getToken()
  22348. ])
  22349. .then(([authToken, appCheckToken]) => {
  22350. return this.connection.invokeStreamingRPC(rpcName, path, request, authToken, appCheckToken, expectedResponseCount);
  22351. })
  22352. .catch((error) => {
  22353. if (error.name === 'FirebaseError') {
  22354. if (error.code === Code.UNAUTHENTICATED) {
  22355. this.authCredentials.invalidateToken();
  22356. this.appCheckCredentials.invalidateToken();
  22357. }
  22358. throw error;
  22359. }
  22360. else {
  22361. throw new FirestoreError(Code.UNKNOWN, error.toString());
  22362. }
  22363. });
  22364. }
  22365. terminate() {
  22366. this.terminated = true;
  22367. }
  22368. }
  22369. // TODO(firestorexp): Make sure there is only one Datastore instance per
  22370. // firestore-exp client.
  22371. function newDatastore(authCredentials, appCheckCredentials, connection, serializer) {
  22372. return new DatastoreImpl(authCredentials, appCheckCredentials, connection, serializer);
  22373. }
  22374. async function invokeCommitRpc(datastore, mutations) {
  22375. const datastoreImpl = debugCast(datastore);
  22376. const path = getEncodedDatabaseId(datastoreImpl.serializer) + '/documents';
  22377. const request = {
  22378. writes: mutations.map(m => toMutation(datastoreImpl.serializer, m))
  22379. };
  22380. await datastoreImpl.invokeRPC('Commit', path, request);
  22381. }
  22382. async function invokeBatchGetDocumentsRpc(datastore, keys) {
  22383. const datastoreImpl = debugCast(datastore);
  22384. const path = getEncodedDatabaseId(datastoreImpl.serializer) + '/documents';
  22385. const request = {
  22386. documents: keys.map(k => toName(datastoreImpl.serializer, k))
  22387. };
  22388. const response = await datastoreImpl.invokeStreamingRPC('BatchGetDocuments', path, request, keys.length);
  22389. const docs = new Map();
  22390. response.forEach(proto => {
  22391. const doc = fromBatchGetDocumentsResponse(datastoreImpl.serializer, proto);
  22392. docs.set(doc.key.toString(), doc);
  22393. });
  22394. const result = [];
  22395. keys.forEach(key => {
  22396. const doc = docs.get(key.toString());
  22397. hardAssert(!!doc);
  22398. result.push(doc);
  22399. });
  22400. return result;
  22401. }
  22402. async function invokeRunAggregationQueryRpc(datastore, query, aggregates) {
  22403. var _a;
  22404. const datastoreImpl = debugCast(datastore);
  22405. const { request, aliasMap } = toRunAggregationQueryRequest(datastoreImpl.serializer, queryToTarget(query), aggregates);
  22406. const parent = request.parent;
  22407. if (!datastoreImpl.connection.shouldResourcePathBeIncludedInRequest) {
  22408. delete request.parent;
  22409. }
  22410. const response = await datastoreImpl.invokeStreamingRPC('RunAggregationQuery', parent, request, /*expectedResponseCount=*/ 1);
  22411. // Omit RunAggregationQueryResponse that only contain readTimes.
  22412. const filteredResult = response.filter(proto => !!proto.result);
  22413. hardAssert(filteredResult.length === 1);
  22414. // Remap the short-form aliases that were sent to the server
  22415. // to the client-side aliases. Users will access the results
  22416. // using the client-side alias.
  22417. const unmappedAggregateFields = (_a = filteredResult[0].result) === null || _a === void 0 ? void 0 : _a.aggregateFields;
  22418. const remappedFields = Object.keys(unmappedAggregateFields).reduce((accumulator, key) => {
  22419. accumulator[aliasMap[key]] = unmappedAggregateFields[key];
  22420. return accumulator;
  22421. }, {});
  22422. return remappedFields;
  22423. }
  22424. function newPersistentWriteStream(datastore, queue, listener) {
  22425. const datastoreImpl = debugCast(datastore);
  22426. datastoreImpl.verifyInitialized();
  22427. return new PersistentWriteStream(queue, datastoreImpl.connection, datastoreImpl.authCredentials, datastoreImpl.appCheckCredentials, datastoreImpl.serializer, listener);
  22428. }
  22429. function newPersistentWatchStream(datastore, queue, listener) {
  22430. const datastoreImpl = debugCast(datastore);
  22431. datastoreImpl.verifyInitialized();
  22432. return new PersistentListenStream(queue, datastoreImpl.connection, datastoreImpl.authCredentials, datastoreImpl.appCheckCredentials, datastoreImpl.serializer, listener);
  22433. }
  22434. /**
  22435. * @license
  22436. * Copyright 2018 Google LLC
  22437. *
  22438. * Licensed under the Apache License, Version 2.0 (the "License");
  22439. * you may not use this file except in compliance with the License.
  22440. * You may obtain a copy of the License at
  22441. *
  22442. * http://www.apache.org/licenses/LICENSE-2.0
  22443. *
  22444. * Unless required by applicable law or agreed to in writing, software
  22445. * distributed under the License is distributed on an "AS IS" BASIS,
  22446. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22447. * See the License for the specific language governing permissions and
  22448. * limitations under the License.
  22449. */
  22450. const LOG_TAG$6 = 'OnlineStateTracker';
  22451. // To deal with transient failures, we allow multiple stream attempts before
  22452. // giving up and transitioning from OnlineState.Unknown to Offline.
  22453. // TODO(mikelehen): This used to be set to 2 as a mitigation for b/66228394.
  22454. // @jdimond thinks that bug is sufficiently fixed so that we can set this back
  22455. // to 1. If that works okay, we could potentially remove this logic entirely.
  22456. const MAX_WATCH_STREAM_FAILURES = 1;
  22457. // To deal with stream attempts that don't succeed or fail in a timely manner,
  22458. // we have a timeout for OnlineState to reach Online or Offline.
  22459. // If the timeout is reached, we transition to Offline rather than waiting
  22460. // indefinitely.
  22461. const ONLINE_STATE_TIMEOUT_MS = 10 * 1000;
  22462. /**
  22463. * A component used by the RemoteStore to track the OnlineState (that is,
  22464. * whether or not the client as a whole should be considered to be online or
  22465. * offline), implementing the appropriate heuristics.
  22466. *
  22467. * In particular, when the client is trying to connect to the backend, we
  22468. * allow up to MAX_WATCH_STREAM_FAILURES within ONLINE_STATE_TIMEOUT_MS for
  22469. * a connection to succeed. If we have too many failures or the timeout elapses,
  22470. * then we set the OnlineState to Offline, and the client will behave as if
  22471. * it is offline (get()s will return cached data, etc.).
  22472. */
  22473. class OnlineStateTracker {
  22474. constructor(asyncQueue, onlineStateHandler) {
  22475. this.asyncQueue = asyncQueue;
  22476. this.onlineStateHandler = onlineStateHandler;
  22477. /** The current OnlineState. */
  22478. this.state = "Unknown" /* OnlineState.Unknown */;
  22479. /**
  22480. * A count of consecutive failures to open the stream. If it reaches the
  22481. * maximum defined by MAX_WATCH_STREAM_FAILURES, we'll set the OnlineState to
  22482. * Offline.
  22483. */
  22484. this.watchStreamFailures = 0;
  22485. /**
  22486. * A timer that elapses after ONLINE_STATE_TIMEOUT_MS, at which point we
  22487. * transition from OnlineState.Unknown to OnlineState.Offline without waiting
  22488. * for the stream to actually fail (MAX_WATCH_STREAM_FAILURES times).
  22489. */
  22490. this.onlineStateTimer = null;
  22491. /**
  22492. * Whether the client should log a warning message if it fails to connect to
  22493. * the backend (initially true, cleared after a successful stream, or if we've
  22494. * logged the message already).
  22495. */
  22496. this.shouldWarnClientIsOffline = true;
  22497. }
  22498. /**
  22499. * Called by RemoteStore when a watch stream is started (including on each
  22500. * backoff attempt).
  22501. *
  22502. * If this is the first attempt, it sets the OnlineState to Unknown and starts
  22503. * the onlineStateTimer.
  22504. */
  22505. handleWatchStreamStart() {
  22506. if (this.watchStreamFailures === 0) {
  22507. this.setAndBroadcast("Unknown" /* OnlineState.Unknown */);
  22508. this.onlineStateTimer = this.asyncQueue.enqueueAfterDelay("online_state_timeout" /* TimerId.OnlineStateTimeout */, ONLINE_STATE_TIMEOUT_MS, () => {
  22509. this.onlineStateTimer = null;
  22510. this.logClientOfflineWarningIfNecessary(`Backend didn't respond within ${ONLINE_STATE_TIMEOUT_MS / 1000} ` +
  22511. `seconds.`);
  22512. this.setAndBroadcast("Offline" /* OnlineState.Offline */);
  22513. // NOTE: handleWatchStreamFailure() will continue to increment
  22514. // watchStreamFailures even though we are already marked Offline,
  22515. // but this is non-harmful.
  22516. return Promise.resolve();
  22517. });
  22518. }
  22519. }
  22520. /**
  22521. * Updates our OnlineState as appropriate after the watch stream reports a
  22522. * failure. The first failure moves us to the 'Unknown' state. We then may
  22523. * allow multiple failures (based on MAX_WATCH_STREAM_FAILURES) before we
  22524. * actually transition to the 'Offline' state.
  22525. */
  22526. handleWatchStreamFailure(error) {
  22527. if (this.state === "Online" /* OnlineState.Online */) {
  22528. this.setAndBroadcast("Unknown" /* OnlineState.Unknown */);
  22529. }
  22530. else {
  22531. this.watchStreamFailures++;
  22532. if (this.watchStreamFailures >= MAX_WATCH_STREAM_FAILURES) {
  22533. this.clearOnlineStateTimer();
  22534. this.logClientOfflineWarningIfNecessary(`Connection failed ${MAX_WATCH_STREAM_FAILURES} ` +
  22535. `times. Most recent error: ${error.toString()}`);
  22536. this.setAndBroadcast("Offline" /* OnlineState.Offline */);
  22537. }
  22538. }
  22539. }
  22540. /**
  22541. * Explicitly sets the OnlineState to the specified state.
  22542. *
  22543. * Note that this resets our timers / failure counters, etc. used by our
  22544. * Offline heuristics, so must not be used in place of
  22545. * handleWatchStreamStart() and handleWatchStreamFailure().
  22546. */
  22547. set(newState) {
  22548. this.clearOnlineStateTimer();
  22549. this.watchStreamFailures = 0;
  22550. if (newState === "Online" /* OnlineState.Online */) {
  22551. // We've connected to watch at least once. Don't warn the developer
  22552. // about being offline going forward.
  22553. this.shouldWarnClientIsOffline = false;
  22554. }
  22555. this.setAndBroadcast(newState);
  22556. }
  22557. setAndBroadcast(newState) {
  22558. if (newState !== this.state) {
  22559. this.state = newState;
  22560. this.onlineStateHandler(newState);
  22561. }
  22562. }
  22563. logClientOfflineWarningIfNecessary(details) {
  22564. const message = `Could not reach Cloud Firestore backend. ${details}\n` +
  22565. `This typically indicates that your device does not have a healthy ` +
  22566. `Internet connection at the moment. The client will operate in offline ` +
  22567. `mode until it is able to successfully connect to the backend.`;
  22568. if (this.shouldWarnClientIsOffline) {
  22569. logError(message);
  22570. this.shouldWarnClientIsOffline = false;
  22571. }
  22572. else {
  22573. logDebug(LOG_TAG$6, message);
  22574. }
  22575. }
  22576. clearOnlineStateTimer() {
  22577. if (this.onlineStateTimer !== null) {
  22578. this.onlineStateTimer.cancel();
  22579. this.onlineStateTimer = null;
  22580. }
  22581. }
  22582. }
  22583. /**
  22584. * @license
  22585. * Copyright 2017 Google LLC
  22586. *
  22587. * Licensed under the Apache License, Version 2.0 (the "License");
  22588. * you may not use this file except in compliance with the License.
  22589. * You may obtain a copy of the License at
  22590. *
  22591. * http://www.apache.org/licenses/LICENSE-2.0
  22592. *
  22593. * Unless required by applicable law or agreed to in writing, software
  22594. * distributed under the License is distributed on an "AS IS" BASIS,
  22595. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22596. * See the License for the specific language governing permissions and
  22597. * limitations under the License.
  22598. */
  22599. const LOG_TAG$5 = 'RemoteStore';
  22600. // TODO(b/35853402): Negotiate this with the stream.
  22601. const MAX_PENDING_WRITES = 10;
  22602. class RemoteStoreImpl {
  22603. constructor(
  22604. /**
  22605. * The local store, used to fill the write pipeline with outbound mutations.
  22606. */
  22607. localStore,
  22608. /** The client-side proxy for interacting with the backend. */
  22609. datastore, asyncQueue, onlineStateHandler, connectivityMonitor) {
  22610. this.localStore = localStore;
  22611. this.datastore = datastore;
  22612. this.asyncQueue = asyncQueue;
  22613. this.remoteSyncer = {};
  22614. /**
  22615. * A list of up to MAX_PENDING_WRITES writes that we have fetched from the
  22616. * LocalStore via fillWritePipeline() and have or will send to the write
  22617. * stream.
  22618. *
  22619. * Whenever writePipeline.length > 0 the RemoteStore will attempt to start or
  22620. * restart the write stream. When the stream is established the writes in the
  22621. * pipeline will be sent in order.
  22622. *
  22623. * Writes remain in writePipeline until they are acknowledged by the backend
  22624. * and thus will automatically be re-sent if the stream is interrupted /
  22625. * restarted before they're acknowledged.
  22626. *
  22627. * Write responses from the backend are linked to their originating request
  22628. * purely based on order, and so we can just shift() writes from the front of
  22629. * the writePipeline as we receive responses.
  22630. */
  22631. this.writePipeline = [];
  22632. /**
  22633. * A mapping of watched targets that the client cares about tracking and the
  22634. * user has explicitly called a 'listen' for this target.
  22635. *
  22636. * These targets may or may not have been sent to or acknowledged by the
  22637. * server. On re-establishing the listen stream, these targets should be sent
  22638. * to the server. The targets removed with unlistens are removed eagerly
  22639. * without waiting for confirmation from the listen stream.
  22640. */
  22641. this.listenTargets = new Map();
  22642. /**
  22643. * A set of reasons for why the RemoteStore may be offline. If empty, the
  22644. * RemoteStore may start its network connections.
  22645. */
  22646. this.offlineCauses = new Set();
  22647. /**
  22648. * Event handlers that get called when the network is disabled or enabled.
  22649. *
  22650. * PORTING NOTE: These functions are used on the Web client to create the
  22651. * underlying streams (to support tree-shakeable streams). On Android and iOS,
  22652. * the streams are created during construction of RemoteStore.
  22653. */
  22654. this.onNetworkStatusChange = [];
  22655. this.connectivityMonitor = connectivityMonitor;
  22656. this.connectivityMonitor.addCallback((_) => {
  22657. asyncQueue.enqueueAndForget(async () => {
  22658. // Porting Note: Unlike iOS, `restartNetwork()` is called even when the
  22659. // network becomes unreachable as we don't have any other way to tear
  22660. // down our streams.
  22661. if (canUseNetwork(this)) {
  22662. logDebug(LOG_TAG$5, 'Restarting streams for network reachability change.');
  22663. await restartNetwork(this);
  22664. }
  22665. });
  22666. });
  22667. this.onlineStateTracker = new OnlineStateTracker(asyncQueue, onlineStateHandler);
  22668. }
  22669. }
  22670. function newRemoteStore(localStore, datastore, asyncQueue, onlineStateHandler, connectivityMonitor) {
  22671. return new RemoteStoreImpl(localStore, datastore, asyncQueue, onlineStateHandler, connectivityMonitor);
  22672. }
  22673. /** Re-enables the network. Idempotent. */
  22674. function remoteStoreEnableNetwork(remoteStore) {
  22675. const remoteStoreImpl = debugCast(remoteStore);
  22676. remoteStoreImpl.offlineCauses.delete(0 /* OfflineCause.UserDisabled */);
  22677. return enableNetworkInternal(remoteStoreImpl);
  22678. }
  22679. async function enableNetworkInternal(remoteStoreImpl) {
  22680. if (canUseNetwork(remoteStoreImpl)) {
  22681. for (const networkStatusHandler of remoteStoreImpl.onNetworkStatusChange) {
  22682. await networkStatusHandler(/* enabled= */ true);
  22683. }
  22684. }
  22685. }
  22686. /**
  22687. * Temporarily disables the network. The network can be re-enabled using
  22688. * enableNetwork().
  22689. */
  22690. async function remoteStoreDisableNetwork(remoteStore) {
  22691. const remoteStoreImpl = debugCast(remoteStore);
  22692. remoteStoreImpl.offlineCauses.add(0 /* OfflineCause.UserDisabled */);
  22693. await disableNetworkInternal(remoteStoreImpl);
  22694. // Set the OnlineState to Offline so get()s return from cache, etc.
  22695. remoteStoreImpl.onlineStateTracker.set("Offline" /* OnlineState.Offline */);
  22696. }
  22697. async function disableNetworkInternal(remoteStoreImpl) {
  22698. for (const networkStatusHandler of remoteStoreImpl.onNetworkStatusChange) {
  22699. await networkStatusHandler(/* enabled= */ false);
  22700. }
  22701. }
  22702. async function remoteStoreShutdown(remoteStore) {
  22703. const remoteStoreImpl = debugCast(remoteStore);
  22704. logDebug(LOG_TAG$5, 'RemoteStore shutting down.');
  22705. remoteStoreImpl.offlineCauses.add(5 /* OfflineCause.Shutdown */);
  22706. await disableNetworkInternal(remoteStoreImpl);
  22707. remoteStoreImpl.connectivityMonitor.shutdown();
  22708. // Set the OnlineState to Unknown (rather than Offline) to avoid potentially
  22709. // triggering spurious listener events with cached data, etc.
  22710. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22711. }
  22712. /**
  22713. * Starts new listen for the given target. Uses resume token if provided. It
  22714. * is a no-op if the target of given `TargetData` is already being listened to.
  22715. */
  22716. function remoteStoreListen(remoteStore, targetData) {
  22717. const remoteStoreImpl = debugCast(remoteStore);
  22718. if (remoteStoreImpl.listenTargets.has(targetData.targetId)) {
  22719. return;
  22720. }
  22721. // Mark this as something the client is currently listening for.
  22722. remoteStoreImpl.listenTargets.set(targetData.targetId, targetData);
  22723. if (shouldStartWatchStream(remoteStoreImpl)) {
  22724. // The listen will be sent in onWatchStreamOpen
  22725. startWatchStream(remoteStoreImpl);
  22726. }
  22727. else if (ensureWatchStream(remoteStoreImpl).isOpen()) {
  22728. sendWatchRequest(remoteStoreImpl, targetData);
  22729. }
  22730. }
  22731. /**
  22732. * Removes the listen from server. It is a no-op if the given target id is
  22733. * not being listened to.
  22734. */
  22735. function remoteStoreUnlisten(remoteStore, targetId) {
  22736. const remoteStoreImpl = debugCast(remoteStore);
  22737. const watchStream = ensureWatchStream(remoteStoreImpl);
  22738. remoteStoreImpl.listenTargets.delete(targetId);
  22739. if (watchStream.isOpen()) {
  22740. sendUnwatchRequest(remoteStoreImpl, targetId);
  22741. }
  22742. if (remoteStoreImpl.listenTargets.size === 0) {
  22743. if (watchStream.isOpen()) {
  22744. watchStream.markIdle();
  22745. }
  22746. else if (canUseNetwork(remoteStoreImpl)) {
  22747. // Revert to OnlineState.Unknown if the watch stream is not open and we
  22748. // have no listeners, since without any listens to send we cannot
  22749. // confirm if the stream is healthy and upgrade to OnlineState.Online.
  22750. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22751. }
  22752. }
  22753. }
  22754. /**
  22755. * We need to increment the the expected number of pending responses we're due
  22756. * from watch so we wait for the ack to process any messages from this target.
  22757. */
  22758. function sendWatchRequest(remoteStoreImpl, targetData) {
  22759. remoteStoreImpl.watchChangeAggregator.recordPendingTargetRequest(targetData.targetId);
  22760. if (targetData.resumeToken.approximateByteSize() > 0 ||
  22761. targetData.snapshotVersion.compareTo(SnapshotVersion.min()) > 0) {
  22762. const expectedCount = remoteStoreImpl.remoteSyncer.getRemoteKeysForTarget(targetData.targetId).size;
  22763. targetData = targetData.withExpectedCount(expectedCount);
  22764. }
  22765. ensureWatchStream(remoteStoreImpl).watch(targetData);
  22766. }
  22767. /**
  22768. * We need to increment the expected number of pending responses we're due
  22769. * from watch so we wait for the removal on the server before we process any
  22770. * messages from this target.
  22771. */
  22772. function sendUnwatchRequest(remoteStoreImpl, targetId) {
  22773. remoteStoreImpl.watchChangeAggregator.recordPendingTargetRequest(targetId);
  22774. ensureWatchStream(remoteStoreImpl).unwatch(targetId);
  22775. }
  22776. function startWatchStream(remoteStoreImpl) {
  22777. remoteStoreImpl.watchChangeAggregator = new WatchChangeAggregator({
  22778. getRemoteKeysForTarget: targetId => remoteStoreImpl.remoteSyncer.getRemoteKeysForTarget(targetId),
  22779. getTargetDataForTarget: targetId => remoteStoreImpl.listenTargets.get(targetId) || null,
  22780. getDatabaseId: () => remoteStoreImpl.datastore.serializer.databaseId
  22781. });
  22782. ensureWatchStream(remoteStoreImpl).start();
  22783. remoteStoreImpl.onlineStateTracker.handleWatchStreamStart();
  22784. }
  22785. /**
  22786. * Returns whether the watch stream should be started because it's necessary
  22787. * and has not yet been started.
  22788. */
  22789. function shouldStartWatchStream(remoteStoreImpl) {
  22790. return (canUseNetwork(remoteStoreImpl) &&
  22791. !ensureWatchStream(remoteStoreImpl).isStarted() &&
  22792. remoteStoreImpl.listenTargets.size > 0);
  22793. }
  22794. function canUseNetwork(remoteStore) {
  22795. const remoteStoreImpl = debugCast(remoteStore);
  22796. return remoteStoreImpl.offlineCauses.size === 0;
  22797. }
  22798. function cleanUpWatchStreamState(remoteStoreImpl) {
  22799. remoteStoreImpl.watchChangeAggregator = undefined;
  22800. }
  22801. async function onWatchStreamOpen(remoteStoreImpl) {
  22802. remoteStoreImpl.listenTargets.forEach((targetData, targetId) => {
  22803. sendWatchRequest(remoteStoreImpl, targetData);
  22804. });
  22805. }
  22806. async function onWatchStreamClose(remoteStoreImpl, error) {
  22807. cleanUpWatchStreamState(remoteStoreImpl);
  22808. // If we still need the watch stream, retry the connection.
  22809. if (shouldStartWatchStream(remoteStoreImpl)) {
  22810. remoteStoreImpl.onlineStateTracker.handleWatchStreamFailure(error);
  22811. startWatchStream(remoteStoreImpl);
  22812. }
  22813. else {
  22814. // No need to restart watch stream because there are no active targets.
  22815. // The online state is set to unknown because there is no active attempt
  22816. // at establishing a connection
  22817. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  22818. }
  22819. }
  22820. async function onWatchStreamChange(remoteStoreImpl, watchChange, snapshotVersion) {
  22821. // Mark the client as online since we got a message from the server
  22822. remoteStoreImpl.onlineStateTracker.set("Online" /* OnlineState.Online */);
  22823. if (watchChange instanceof WatchTargetChange &&
  22824. watchChange.state === 2 /* WatchTargetChangeState.Removed */ &&
  22825. watchChange.cause) {
  22826. // There was an error on a target, don't wait for a consistent snapshot
  22827. // to raise events
  22828. try {
  22829. await handleTargetError(remoteStoreImpl, watchChange);
  22830. }
  22831. catch (e) {
  22832. logDebug(LOG_TAG$5, 'Failed to remove targets %s: %s ', watchChange.targetIds.join(','), e);
  22833. await disableNetworkUntilRecovery(remoteStoreImpl, e);
  22834. }
  22835. return;
  22836. }
  22837. if (watchChange instanceof DocumentWatchChange) {
  22838. remoteStoreImpl.watchChangeAggregator.handleDocumentChange(watchChange);
  22839. }
  22840. else if (watchChange instanceof ExistenceFilterChange) {
  22841. remoteStoreImpl.watchChangeAggregator.handleExistenceFilter(watchChange);
  22842. }
  22843. else {
  22844. remoteStoreImpl.watchChangeAggregator.handleTargetChange(watchChange);
  22845. }
  22846. if (!snapshotVersion.isEqual(SnapshotVersion.min())) {
  22847. try {
  22848. const lastRemoteSnapshotVersion = await localStoreGetLastRemoteSnapshotVersion(remoteStoreImpl.localStore);
  22849. if (snapshotVersion.compareTo(lastRemoteSnapshotVersion) >= 0) {
  22850. // We have received a target change with a global snapshot if the snapshot
  22851. // version is not equal to SnapshotVersion.min().
  22852. await raiseWatchSnapshot(remoteStoreImpl, snapshotVersion);
  22853. }
  22854. }
  22855. catch (e) {
  22856. logDebug(LOG_TAG$5, 'Failed to raise snapshot:', e);
  22857. await disableNetworkUntilRecovery(remoteStoreImpl, e);
  22858. }
  22859. }
  22860. }
  22861. /**
  22862. * Recovery logic for IndexedDB errors that takes the network offline until
  22863. * `op` succeeds. Retries are scheduled with backoff using
  22864. * `enqueueRetryable()`. If `op()` is not provided, IndexedDB access is
  22865. * validated via a generic operation.
  22866. *
  22867. * The returned Promise is resolved once the network is disabled and before
  22868. * any retry attempt.
  22869. */
  22870. async function disableNetworkUntilRecovery(remoteStoreImpl, e, op) {
  22871. if (isIndexedDbTransactionError(e)) {
  22872. remoteStoreImpl.offlineCauses.add(1 /* OfflineCause.IndexedDbFailed */);
  22873. // Disable network and raise offline snapshots
  22874. await disableNetworkInternal(remoteStoreImpl);
  22875. remoteStoreImpl.onlineStateTracker.set("Offline" /* OnlineState.Offline */);
  22876. if (!op) {
  22877. // Use a simple read operation to determine if IndexedDB recovered.
  22878. // Ideally, we would expose a health check directly on SimpleDb, but
  22879. // RemoteStore only has access to persistence through LocalStore.
  22880. op = () => localStoreGetLastRemoteSnapshotVersion(remoteStoreImpl.localStore);
  22881. }
  22882. // Probe IndexedDB periodically and re-enable network
  22883. remoteStoreImpl.asyncQueue.enqueueRetryable(async () => {
  22884. logDebug(LOG_TAG$5, 'Retrying IndexedDB access');
  22885. await op();
  22886. remoteStoreImpl.offlineCauses.delete(1 /* OfflineCause.IndexedDbFailed */);
  22887. await enableNetworkInternal(remoteStoreImpl);
  22888. });
  22889. }
  22890. else {
  22891. throw e;
  22892. }
  22893. }
  22894. /**
  22895. * Executes `op`. If `op` fails, takes the network offline until `op`
  22896. * succeeds. Returns after the first attempt.
  22897. */
  22898. function executeWithRecovery(remoteStoreImpl, op) {
  22899. return op().catch(e => disableNetworkUntilRecovery(remoteStoreImpl, e, op));
  22900. }
  22901. /**
  22902. * Takes a batch of changes from the Datastore, repackages them as a
  22903. * RemoteEvent, and passes that on to the listener, which is typically the
  22904. * SyncEngine.
  22905. */
  22906. function raiseWatchSnapshot(remoteStoreImpl, snapshotVersion) {
  22907. const remoteEvent = remoteStoreImpl.watchChangeAggregator.createRemoteEvent(snapshotVersion);
  22908. // Update in-memory resume tokens. LocalStore will update the
  22909. // persistent view of these when applying the completed RemoteEvent.
  22910. remoteEvent.targetChanges.forEach((change, targetId) => {
  22911. if (change.resumeToken.approximateByteSize() > 0) {
  22912. const targetData = remoteStoreImpl.listenTargets.get(targetId);
  22913. // A watched target might have been removed already.
  22914. if (targetData) {
  22915. remoteStoreImpl.listenTargets.set(targetId, targetData.withResumeToken(change.resumeToken, snapshotVersion));
  22916. }
  22917. }
  22918. });
  22919. // Re-establish listens for the targets that have been invalidated by
  22920. // existence filter mismatches.
  22921. remoteEvent.targetMismatches.forEach((targetId, targetPurpose) => {
  22922. const targetData = remoteStoreImpl.listenTargets.get(targetId);
  22923. if (!targetData) {
  22924. // A watched target might have been removed already.
  22925. return;
  22926. }
  22927. // Clear the resume token for the target, since we're in a known mismatch
  22928. // state.
  22929. remoteStoreImpl.listenTargets.set(targetId, targetData.withResumeToken(ByteString.EMPTY_BYTE_STRING, targetData.snapshotVersion));
  22930. // Cause a hard reset by unwatching and rewatching immediately, but
  22931. // deliberately don't send a resume token so that we get a full update.
  22932. sendUnwatchRequest(remoteStoreImpl, targetId);
  22933. // Mark the target we send as being on behalf of an existence filter
  22934. // mismatch, but don't actually retain that in listenTargets. This ensures
  22935. // that we flag the first re-listen this way without impacting future
  22936. // listens of this target (that might happen e.g. on reconnect).
  22937. const requestTargetData = new TargetData(targetData.target, targetId, targetPurpose, targetData.sequenceNumber);
  22938. sendWatchRequest(remoteStoreImpl, requestTargetData);
  22939. });
  22940. return remoteStoreImpl.remoteSyncer.applyRemoteEvent(remoteEvent);
  22941. }
  22942. /** Handles an error on a target */
  22943. async function handleTargetError(remoteStoreImpl, watchChange) {
  22944. const error = watchChange.cause;
  22945. for (const targetId of watchChange.targetIds) {
  22946. // A watched target might have been removed already.
  22947. if (remoteStoreImpl.listenTargets.has(targetId)) {
  22948. await remoteStoreImpl.remoteSyncer.rejectListen(targetId, error);
  22949. remoteStoreImpl.listenTargets.delete(targetId);
  22950. remoteStoreImpl.watchChangeAggregator.removeTarget(targetId);
  22951. }
  22952. }
  22953. }
  22954. /**
  22955. * Attempts to fill our write pipeline with writes from the LocalStore.
  22956. *
  22957. * Called internally to bootstrap or refill the write pipeline and by
  22958. * SyncEngine whenever there are new mutations to process.
  22959. *
  22960. * Starts the write stream if necessary.
  22961. */
  22962. async function fillWritePipeline(remoteStore) {
  22963. const remoteStoreImpl = debugCast(remoteStore);
  22964. const writeStream = ensureWriteStream(remoteStoreImpl);
  22965. let lastBatchIdRetrieved = remoteStoreImpl.writePipeline.length > 0
  22966. ? remoteStoreImpl.writePipeline[remoteStoreImpl.writePipeline.length - 1]
  22967. .batchId
  22968. : BATCHID_UNKNOWN;
  22969. while (canAddToWritePipeline(remoteStoreImpl)) {
  22970. try {
  22971. const batch = await localStoreGetNextMutationBatch(remoteStoreImpl.localStore, lastBatchIdRetrieved);
  22972. if (batch === null) {
  22973. if (remoteStoreImpl.writePipeline.length === 0) {
  22974. writeStream.markIdle();
  22975. }
  22976. break;
  22977. }
  22978. else {
  22979. lastBatchIdRetrieved = batch.batchId;
  22980. addToWritePipeline(remoteStoreImpl, batch);
  22981. }
  22982. }
  22983. catch (e) {
  22984. await disableNetworkUntilRecovery(remoteStoreImpl, e);
  22985. }
  22986. }
  22987. if (shouldStartWriteStream(remoteStoreImpl)) {
  22988. startWriteStream(remoteStoreImpl);
  22989. }
  22990. }
  22991. /**
  22992. * Returns true if we can add to the write pipeline (i.e. the network is
  22993. * enabled and the write pipeline is not full).
  22994. */
  22995. function canAddToWritePipeline(remoteStoreImpl) {
  22996. return (canUseNetwork(remoteStoreImpl) &&
  22997. remoteStoreImpl.writePipeline.length < MAX_PENDING_WRITES);
  22998. }
  22999. /**
  23000. * Queues additional writes to be sent to the write stream, sending them
  23001. * immediately if the write stream is established.
  23002. */
  23003. function addToWritePipeline(remoteStoreImpl, batch) {
  23004. remoteStoreImpl.writePipeline.push(batch);
  23005. const writeStream = ensureWriteStream(remoteStoreImpl);
  23006. if (writeStream.isOpen() && writeStream.handshakeComplete) {
  23007. writeStream.writeMutations(batch.mutations);
  23008. }
  23009. }
  23010. function shouldStartWriteStream(remoteStoreImpl) {
  23011. return (canUseNetwork(remoteStoreImpl) &&
  23012. !ensureWriteStream(remoteStoreImpl).isStarted() &&
  23013. remoteStoreImpl.writePipeline.length > 0);
  23014. }
  23015. function startWriteStream(remoteStoreImpl) {
  23016. ensureWriteStream(remoteStoreImpl).start();
  23017. }
  23018. async function onWriteStreamOpen(remoteStoreImpl) {
  23019. ensureWriteStream(remoteStoreImpl).writeHandshake();
  23020. }
  23021. async function onWriteHandshakeComplete(remoteStoreImpl) {
  23022. const writeStream = ensureWriteStream(remoteStoreImpl);
  23023. // Send the write pipeline now that the stream is established.
  23024. for (const batch of remoteStoreImpl.writePipeline) {
  23025. writeStream.writeMutations(batch.mutations);
  23026. }
  23027. }
  23028. async function onMutationResult(remoteStoreImpl, commitVersion, results) {
  23029. const batch = remoteStoreImpl.writePipeline.shift();
  23030. const success = MutationBatchResult.from(batch, commitVersion, results);
  23031. await executeWithRecovery(remoteStoreImpl, () => remoteStoreImpl.remoteSyncer.applySuccessfulWrite(success));
  23032. // It's possible that with the completion of this mutation another
  23033. // slot has freed up.
  23034. await fillWritePipeline(remoteStoreImpl);
  23035. }
  23036. async function onWriteStreamClose(remoteStoreImpl, error) {
  23037. // If the write stream closed after the write handshake completes, a write
  23038. // operation failed and we fail the pending operation.
  23039. if (error && ensureWriteStream(remoteStoreImpl).handshakeComplete) {
  23040. // This error affects the actual write.
  23041. await handleWriteError(remoteStoreImpl, error);
  23042. }
  23043. // The write stream might have been started by refilling the write
  23044. // pipeline for failed writes
  23045. if (shouldStartWriteStream(remoteStoreImpl)) {
  23046. startWriteStream(remoteStoreImpl);
  23047. }
  23048. }
  23049. async function handleWriteError(remoteStoreImpl, error) {
  23050. // Only handle permanent errors here. If it's transient, just let the retry
  23051. // logic kick in.
  23052. if (isPermanentWriteError(error.code)) {
  23053. // This was a permanent error, the request itself was the problem
  23054. // so it's not going to succeed if we resend it.
  23055. const batch = remoteStoreImpl.writePipeline.shift();
  23056. // In this case it's also unlikely that the server itself is melting
  23057. // down -- this was just a bad request so inhibit backoff on the next
  23058. // restart.
  23059. ensureWriteStream(remoteStoreImpl).inhibitBackoff();
  23060. await executeWithRecovery(remoteStoreImpl, () => remoteStoreImpl.remoteSyncer.rejectFailedWrite(batch.batchId, error));
  23061. // It's possible that with the completion of this mutation
  23062. // another slot has freed up.
  23063. await fillWritePipeline(remoteStoreImpl);
  23064. }
  23065. }
  23066. async function restartNetwork(remoteStore) {
  23067. const remoteStoreImpl = debugCast(remoteStore);
  23068. remoteStoreImpl.offlineCauses.add(4 /* OfflineCause.ConnectivityChange */);
  23069. await disableNetworkInternal(remoteStoreImpl);
  23070. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  23071. remoteStoreImpl.offlineCauses.delete(4 /* OfflineCause.ConnectivityChange */);
  23072. await enableNetworkInternal(remoteStoreImpl);
  23073. }
  23074. async function remoteStoreHandleCredentialChange(remoteStore, user) {
  23075. const remoteStoreImpl = debugCast(remoteStore);
  23076. remoteStoreImpl.asyncQueue.verifyOperationInProgress();
  23077. logDebug(LOG_TAG$5, 'RemoteStore received new credentials');
  23078. const usesNetwork = canUseNetwork(remoteStoreImpl);
  23079. // Tear down and re-create our network streams. This will ensure we get a
  23080. // fresh auth token for the new user and re-fill the write pipeline with
  23081. // new mutations from the LocalStore (since mutations are per-user).
  23082. remoteStoreImpl.offlineCauses.add(3 /* OfflineCause.CredentialChange */);
  23083. await disableNetworkInternal(remoteStoreImpl);
  23084. if (usesNetwork) {
  23085. // Don't set the network status to Unknown if we are offline.
  23086. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  23087. }
  23088. await remoteStoreImpl.remoteSyncer.handleCredentialChange(user);
  23089. remoteStoreImpl.offlineCauses.delete(3 /* OfflineCause.CredentialChange */);
  23090. await enableNetworkInternal(remoteStoreImpl);
  23091. }
  23092. /**
  23093. * Toggles the network state when the client gains or loses its primary lease.
  23094. */
  23095. async function remoteStoreApplyPrimaryState(remoteStore, isPrimary) {
  23096. const remoteStoreImpl = debugCast(remoteStore);
  23097. if (isPrimary) {
  23098. remoteStoreImpl.offlineCauses.delete(2 /* OfflineCause.IsSecondary */);
  23099. await enableNetworkInternal(remoteStoreImpl);
  23100. }
  23101. else if (!isPrimary) {
  23102. remoteStoreImpl.offlineCauses.add(2 /* OfflineCause.IsSecondary */);
  23103. await disableNetworkInternal(remoteStoreImpl);
  23104. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  23105. }
  23106. }
  23107. /**
  23108. * If not yet initialized, registers the WatchStream and its network state
  23109. * callback with `remoteStoreImpl`. Returns the existing stream if one is
  23110. * already available.
  23111. *
  23112. * PORTING NOTE: On iOS and Android, the WatchStream gets registered on startup.
  23113. * This is not done on Web to allow it to be tree-shaken.
  23114. */
  23115. function ensureWatchStream(remoteStoreImpl) {
  23116. if (!remoteStoreImpl.watchStream) {
  23117. // Create stream (but note that it is not started yet).
  23118. remoteStoreImpl.watchStream = newPersistentWatchStream(remoteStoreImpl.datastore, remoteStoreImpl.asyncQueue, {
  23119. onOpen: onWatchStreamOpen.bind(null, remoteStoreImpl),
  23120. onClose: onWatchStreamClose.bind(null, remoteStoreImpl),
  23121. onWatchChange: onWatchStreamChange.bind(null, remoteStoreImpl)
  23122. });
  23123. remoteStoreImpl.onNetworkStatusChange.push(async (enabled) => {
  23124. if (enabled) {
  23125. remoteStoreImpl.watchStream.inhibitBackoff();
  23126. if (shouldStartWatchStream(remoteStoreImpl)) {
  23127. startWatchStream(remoteStoreImpl);
  23128. }
  23129. else {
  23130. remoteStoreImpl.onlineStateTracker.set("Unknown" /* OnlineState.Unknown */);
  23131. }
  23132. }
  23133. else {
  23134. await remoteStoreImpl.watchStream.stop();
  23135. cleanUpWatchStreamState(remoteStoreImpl);
  23136. }
  23137. });
  23138. }
  23139. return remoteStoreImpl.watchStream;
  23140. }
  23141. /**
  23142. * If not yet initialized, registers the WriteStream and its network state
  23143. * callback with `remoteStoreImpl`. Returns the existing stream if one is
  23144. * already available.
  23145. *
  23146. * PORTING NOTE: On iOS and Android, the WriteStream gets registered on startup.
  23147. * This is not done on Web to allow it to be tree-shaken.
  23148. */
  23149. function ensureWriteStream(remoteStoreImpl) {
  23150. if (!remoteStoreImpl.writeStream) {
  23151. // Create stream (but note that it is not started yet).
  23152. remoteStoreImpl.writeStream = newPersistentWriteStream(remoteStoreImpl.datastore, remoteStoreImpl.asyncQueue, {
  23153. onOpen: onWriteStreamOpen.bind(null, remoteStoreImpl),
  23154. onClose: onWriteStreamClose.bind(null, remoteStoreImpl),
  23155. onHandshakeComplete: onWriteHandshakeComplete.bind(null, remoteStoreImpl),
  23156. onMutationResult: onMutationResult.bind(null, remoteStoreImpl)
  23157. });
  23158. remoteStoreImpl.onNetworkStatusChange.push(async (enabled) => {
  23159. if (enabled) {
  23160. remoteStoreImpl.writeStream.inhibitBackoff();
  23161. // This will start the write stream if necessary.
  23162. await fillWritePipeline(remoteStoreImpl);
  23163. }
  23164. else {
  23165. await remoteStoreImpl.writeStream.stop();
  23166. if (remoteStoreImpl.writePipeline.length > 0) {
  23167. logDebug(LOG_TAG$5, `Stopping write stream with ${remoteStoreImpl.writePipeline.length} pending writes`);
  23168. remoteStoreImpl.writePipeline = [];
  23169. }
  23170. }
  23171. });
  23172. }
  23173. return remoteStoreImpl.writeStream;
  23174. }
  23175. /**
  23176. * @license
  23177. * Copyright 2017 Google LLC
  23178. *
  23179. * Licensed under the Apache License, Version 2.0 (the "License");
  23180. * you may not use this file except in compliance with the License.
  23181. * You may obtain a copy of the License at
  23182. *
  23183. * http://www.apache.org/licenses/LICENSE-2.0
  23184. *
  23185. * Unless required by applicable law or agreed to in writing, software
  23186. * distributed under the License is distributed on an "AS IS" BASIS,
  23187. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23188. * See the License for the specific language governing permissions and
  23189. * limitations under the License.
  23190. */
  23191. const LOG_TAG$4 = 'AsyncQueue';
  23192. /**
  23193. * Represents an operation scheduled to be run in the future on an AsyncQueue.
  23194. *
  23195. * It is created via DelayedOperation.createAndSchedule().
  23196. *
  23197. * Supports cancellation (via cancel()) and early execution (via skipDelay()).
  23198. *
  23199. * Note: We implement `PromiseLike` instead of `Promise`, as the `Promise` type
  23200. * in newer versions of TypeScript defines `finally`, which is not available in
  23201. * IE.
  23202. */
  23203. class DelayedOperation {
  23204. constructor(asyncQueue, timerId, targetTimeMs, op, removalCallback) {
  23205. this.asyncQueue = asyncQueue;
  23206. this.timerId = timerId;
  23207. this.targetTimeMs = targetTimeMs;
  23208. this.op = op;
  23209. this.removalCallback = removalCallback;
  23210. this.deferred = new Deferred();
  23211. this.then = this.deferred.promise.then.bind(this.deferred.promise);
  23212. // It's normal for the deferred promise to be canceled (due to cancellation)
  23213. // and so we attach a dummy catch callback to avoid
  23214. // 'UnhandledPromiseRejectionWarning' log spam.
  23215. this.deferred.promise.catch(err => { });
  23216. }
  23217. /**
  23218. * Creates and returns a DelayedOperation that has been scheduled to be
  23219. * executed on the provided asyncQueue after the provided delayMs.
  23220. *
  23221. * @param asyncQueue - The queue to schedule the operation on.
  23222. * @param id - A Timer ID identifying the type of operation this is.
  23223. * @param delayMs - The delay (ms) before the operation should be scheduled.
  23224. * @param op - The operation to run.
  23225. * @param removalCallback - A callback to be called synchronously once the
  23226. * operation is executed or canceled, notifying the AsyncQueue to remove it
  23227. * from its delayedOperations list.
  23228. * PORTING NOTE: This exists to prevent making removeDelayedOperation() and
  23229. * the DelayedOperation class public.
  23230. */
  23231. static createAndSchedule(asyncQueue, timerId, delayMs, op, removalCallback) {
  23232. const targetTime = Date.now() + delayMs;
  23233. const delayedOp = new DelayedOperation(asyncQueue, timerId, targetTime, op, removalCallback);
  23234. delayedOp.start(delayMs);
  23235. return delayedOp;
  23236. }
  23237. /**
  23238. * Starts the timer. This is called immediately after construction by
  23239. * createAndSchedule().
  23240. */
  23241. start(delayMs) {
  23242. this.timerHandle = setTimeout(() => this.handleDelayElapsed(), delayMs);
  23243. }
  23244. /**
  23245. * Queues the operation to run immediately (if it hasn't already been run or
  23246. * canceled).
  23247. */
  23248. skipDelay() {
  23249. return this.handleDelayElapsed();
  23250. }
  23251. /**
  23252. * Cancels the operation if it hasn't already been executed or canceled. The
  23253. * promise will be rejected.
  23254. *
  23255. * As long as the operation has not yet been run, calling cancel() provides a
  23256. * guarantee that the operation will not be run.
  23257. */
  23258. cancel(reason) {
  23259. if (this.timerHandle !== null) {
  23260. this.clearTimeout();
  23261. this.deferred.reject(new FirestoreError(Code.CANCELLED, 'Operation cancelled' + (reason ? ': ' + reason : '')));
  23262. }
  23263. }
  23264. handleDelayElapsed() {
  23265. this.asyncQueue.enqueueAndForget(() => {
  23266. if (this.timerHandle !== null) {
  23267. this.clearTimeout();
  23268. return this.op().then(result => {
  23269. return this.deferred.resolve(result);
  23270. });
  23271. }
  23272. else {
  23273. return Promise.resolve();
  23274. }
  23275. });
  23276. }
  23277. clearTimeout() {
  23278. if (this.timerHandle !== null) {
  23279. this.removalCallback(this);
  23280. clearTimeout(this.timerHandle);
  23281. this.timerHandle = null;
  23282. }
  23283. }
  23284. }
  23285. /**
  23286. * Returns a FirestoreError that can be surfaced to the user if the provided
  23287. * error is an IndexedDbTransactionError. Re-throws the error otherwise.
  23288. */
  23289. function wrapInUserErrorIfRecoverable(e, msg) {
  23290. logError(LOG_TAG$4, `${msg}: ${e}`);
  23291. if (isIndexedDbTransactionError(e)) {
  23292. return new FirestoreError(Code.UNAVAILABLE, `${msg}: ${e}`);
  23293. }
  23294. else {
  23295. throw e;
  23296. }
  23297. }
  23298. /**
  23299. * @license
  23300. * Copyright 2017 Google LLC
  23301. *
  23302. * Licensed under the Apache License, Version 2.0 (the "License");
  23303. * you may not use this file except in compliance with the License.
  23304. * You may obtain a copy of the License at
  23305. *
  23306. * http://www.apache.org/licenses/LICENSE-2.0
  23307. *
  23308. * Unless required by applicable law or agreed to in writing, software
  23309. * distributed under the License is distributed on an "AS IS" BASIS,
  23310. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23311. * See the License for the specific language governing permissions and
  23312. * limitations under the License.
  23313. */
  23314. /**
  23315. * DocumentSet is an immutable (copy-on-write) collection that holds documents
  23316. * in order specified by the provided comparator. We always add a document key
  23317. * comparator on top of what is provided to guarantee document equality based on
  23318. * the key.
  23319. */
  23320. class DocumentSet {
  23321. /** The default ordering is by key if the comparator is omitted */
  23322. constructor(comp) {
  23323. // We are adding document key comparator to the end as it's the only
  23324. // guaranteed unique property of a document.
  23325. if (comp) {
  23326. this.comparator = (d1, d2) => comp(d1, d2) || DocumentKey.comparator(d1.key, d2.key);
  23327. }
  23328. else {
  23329. this.comparator = (d1, d2) => DocumentKey.comparator(d1.key, d2.key);
  23330. }
  23331. this.keyedMap = documentMap();
  23332. this.sortedSet = new SortedMap(this.comparator);
  23333. }
  23334. /**
  23335. * Returns an empty copy of the existing DocumentSet, using the same
  23336. * comparator.
  23337. */
  23338. static emptySet(oldSet) {
  23339. return new DocumentSet(oldSet.comparator);
  23340. }
  23341. has(key) {
  23342. return this.keyedMap.get(key) != null;
  23343. }
  23344. get(key) {
  23345. return this.keyedMap.get(key);
  23346. }
  23347. first() {
  23348. return this.sortedSet.minKey();
  23349. }
  23350. last() {
  23351. return this.sortedSet.maxKey();
  23352. }
  23353. isEmpty() {
  23354. return this.sortedSet.isEmpty();
  23355. }
  23356. /**
  23357. * Returns the index of the provided key in the document set, or -1 if the
  23358. * document key is not present in the set;
  23359. */
  23360. indexOf(key) {
  23361. const doc = this.keyedMap.get(key);
  23362. return doc ? this.sortedSet.indexOf(doc) : -1;
  23363. }
  23364. get size() {
  23365. return this.sortedSet.size;
  23366. }
  23367. /** Iterates documents in order defined by "comparator" */
  23368. forEach(cb) {
  23369. this.sortedSet.inorderTraversal((k, v) => {
  23370. cb(k);
  23371. return false;
  23372. });
  23373. }
  23374. /** Inserts or updates a document with the same key */
  23375. add(doc) {
  23376. // First remove the element if we have it.
  23377. const set = this.delete(doc.key);
  23378. return set.copy(set.keyedMap.insert(doc.key, doc), set.sortedSet.insert(doc, null));
  23379. }
  23380. /** Deletes a document with a given key */
  23381. delete(key) {
  23382. const doc = this.get(key);
  23383. if (!doc) {
  23384. return this;
  23385. }
  23386. return this.copy(this.keyedMap.remove(key), this.sortedSet.remove(doc));
  23387. }
  23388. isEqual(other) {
  23389. if (!(other instanceof DocumentSet)) {
  23390. return false;
  23391. }
  23392. if (this.size !== other.size) {
  23393. return false;
  23394. }
  23395. const thisIt = this.sortedSet.getIterator();
  23396. const otherIt = other.sortedSet.getIterator();
  23397. while (thisIt.hasNext()) {
  23398. const thisDoc = thisIt.getNext().key;
  23399. const otherDoc = otherIt.getNext().key;
  23400. if (!thisDoc.isEqual(otherDoc)) {
  23401. return false;
  23402. }
  23403. }
  23404. return true;
  23405. }
  23406. toString() {
  23407. const docStrings = [];
  23408. this.forEach(doc => {
  23409. docStrings.push(doc.toString());
  23410. });
  23411. if (docStrings.length === 0) {
  23412. return 'DocumentSet ()';
  23413. }
  23414. else {
  23415. return 'DocumentSet (\n ' + docStrings.join(' \n') + '\n)';
  23416. }
  23417. }
  23418. copy(keyedMap, sortedSet) {
  23419. const newSet = new DocumentSet();
  23420. newSet.comparator = this.comparator;
  23421. newSet.keyedMap = keyedMap;
  23422. newSet.sortedSet = sortedSet;
  23423. return newSet;
  23424. }
  23425. }
  23426. /**
  23427. * @license
  23428. * Copyright 2017 Google LLC
  23429. *
  23430. * Licensed under the Apache License, Version 2.0 (the "License");
  23431. * you may not use this file except in compliance with the License.
  23432. * You may obtain a copy of the License at
  23433. *
  23434. * http://www.apache.org/licenses/LICENSE-2.0
  23435. *
  23436. * Unless required by applicable law or agreed to in writing, software
  23437. * distributed under the License is distributed on an "AS IS" BASIS,
  23438. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23439. * See the License for the specific language governing permissions and
  23440. * limitations under the License.
  23441. */
  23442. /**
  23443. * DocumentChangeSet keeps track of a set of changes to docs in a query, merging
  23444. * duplicate events for the same doc.
  23445. */
  23446. class DocumentChangeSet {
  23447. constructor() {
  23448. this.changeMap = new SortedMap(DocumentKey.comparator);
  23449. }
  23450. track(change) {
  23451. const key = change.doc.key;
  23452. const oldChange = this.changeMap.get(key);
  23453. if (!oldChange) {
  23454. this.changeMap = this.changeMap.insert(key, change);
  23455. return;
  23456. }
  23457. // Merge the new change with the existing change.
  23458. if (change.type !== 0 /* ChangeType.Added */ &&
  23459. oldChange.type === 3 /* ChangeType.Metadata */) {
  23460. this.changeMap = this.changeMap.insert(key, change);
  23461. }
  23462. else if (change.type === 3 /* ChangeType.Metadata */ &&
  23463. oldChange.type !== 1 /* ChangeType.Removed */) {
  23464. this.changeMap = this.changeMap.insert(key, {
  23465. type: oldChange.type,
  23466. doc: change.doc
  23467. });
  23468. }
  23469. else if (change.type === 2 /* ChangeType.Modified */ &&
  23470. oldChange.type === 2 /* ChangeType.Modified */) {
  23471. this.changeMap = this.changeMap.insert(key, {
  23472. type: 2 /* ChangeType.Modified */,
  23473. doc: change.doc
  23474. });
  23475. }
  23476. else if (change.type === 2 /* ChangeType.Modified */ &&
  23477. oldChange.type === 0 /* ChangeType.Added */) {
  23478. this.changeMap = this.changeMap.insert(key, {
  23479. type: 0 /* ChangeType.Added */,
  23480. doc: change.doc
  23481. });
  23482. }
  23483. else if (change.type === 1 /* ChangeType.Removed */ &&
  23484. oldChange.type === 0 /* ChangeType.Added */) {
  23485. this.changeMap = this.changeMap.remove(key);
  23486. }
  23487. else if (change.type === 1 /* ChangeType.Removed */ &&
  23488. oldChange.type === 2 /* ChangeType.Modified */) {
  23489. this.changeMap = this.changeMap.insert(key, {
  23490. type: 1 /* ChangeType.Removed */,
  23491. doc: oldChange.doc
  23492. });
  23493. }
  23494. else if (change.type === 0 /* ChangeType.Added */ &&
  23495. oldChange.type === 1 /* ChangeType.Removed */) {
  23496. this.changeMap = this.changeMap.insert(key, {
  23497. type: 2 /* ChangeType.Modified */,
  23498. doc: change.doc
  23499. });
  23500. }
  23501. else {
  23502. // This includes these cases, which don't make sense:
  23503. // Added->Added
  23504. // Removed->Removed
  23505. // Modified->Added
  23506. // Removed->Modified
  23507. // Metadata->Added
  23508. // Removed->Metadata
  23509. fail();
  23510. }
  23511. }
  23512. getChanges() {
  23513. const changes = [];
  23514. this.changeMap.inorderTraversal((key, change) => {
  23515. changes.push(change);
  23516. });
  23517. return changes;
  23518. }
  23519. }
  23520. class ViewSnapshot {
  23521. constructor(query, docs, oldDocs, docChanges, mutatedKeys, fromCache, syncStateChanged, excludesMetadataChanges, hasCachedResults) {
  23522. this.query = query;
  23523. this.docs = docs;
  23524. this.oldDocs = oldDocs;
  23525. this.docChanges = docChanges;
  23526. this.mutatedKeys = mutatedKeys;
  23527. this.fromCache = fromCache;
  23528. this.syncStateChanged = syncStateChanged;
  23529. this.excludesMetadataChanges = excludesMetadataChanges;
  23530. this.hasCachedResults = hasCachedResults;
  23531. }
  23532. /** Returns a view snapshot as if all documents in the snapshot were added. */
  23533. static fromInitialDocuments(query, documents, mutatedKeys, fromCache, hasCachedResults) {
  23534. const changes = [];
  23535. documents.forEach(doc => {
  23536. changes.push({ type: 0 /* ChangeType.Added */, doc });
  23537. });
  23538. return new ViewSnapshot(query, documents, DocumentSet.emptySet(documents), changes, mutatedKeys, fromCache,
  23539. /* syncStateChanged= */ true,
  23540. /* excludesMetadataChanges= */ false, hasCachedResults);
  23541. }
  23542. get hasPendingWrites() {
  23543. return !this.mutatedKeys.isEmpty();
  23544. }
  23545. isEqual(other) {
  23546. if (this.fromCache !== other.fromCache ||
  23547. this.hasCachedResults !== other.hasCachedResults ||
  23548. this.syncStateChanged !== other.syncStateChanged ||
  23549. !this.mutatedKeys.isEqual(other.mutatedKeys) ||
  23550. !queryEquals(this.query, other.query) ||
  23551. !this.docs.isEqual(other.docs) ||
  23552. !this.oldDocs.isEqual(other.oldDocs)) {
  23553. return false;
  23554. }
  23555. const changes = this.docChanges;
  23556. const otherChanges = other.docChanges;
  23557. if (changes.length !== otherChanges.length) {
  23558. return false;
  23559. }
  23560. for (let i = 0; i < changes.length; i++) {
  23561. if (changes[i].type !== otherChanges[i].type ||
  23562. !changes[i].doc.isEqual(otherChanges[i].doc)) {
  23563. return false;
  23564. }
  23565. }
  23566. return true;
  23567. }
  23568. }
  23569. /**
  23570. * @license
  23571. * Copyright 2017 Google LLC
  23572. *
  23573. * Licensed under the Apache License, Version 2.0 (the "License");
  23574. * you may not use this file except in compliance with the License.
  23575. * You may obtain a copy of the License at
  23576. *
  23577. * http://www.apache.org/licenses/LICENSE-2.0
  23578. *
  23579. * Unless required by applicable law or agreed to in writing, software
  23580. * distributed under the License is distributed on an "AS IS" BASIS,
  23581. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23582. * See the License for the specific language governing permissions and
  23583. * limitations under the License.
  23584. */
  23585. /**
  23586. * Holds the listeners and the last received ViewSnapshot for a query being
  23587. * tracked by EventManager.
  23588. */
  23589. class QueryListenersInfo {
  23590. constructor() {
  23591. this.viewSnap = undefined;
  23592. this.listeners = [];
  23593. }
  23594. }
  23595. function newEventManager() {
  23596. return new EventManagerImpl();
  23597. }
  23598. class EventManagerImpl {
  23599. constructor() {
  23600. this.queries = new ObjectMap(q => canonifyQuery(q), queryEquals);
  23601. this.onlineState = "Unknown" /* OnlineState.Unknown */;
  23602. this.snapshotsInSyncListeners = new Set();
  23603. }
  23604. }
  23605. async function eventManagerListen(eventManager, listener) {
  23606. const eventManagerImpl = debugCast(eventManager);
  23607. const query = listener.query;
  23608. let firstListen = false;
  23609. let queryInfo = eventManagerImpl.queries.get(query);
  23610. if (!queryInfo) {
  23611. firstListen = true;
  23612. queryInfo = new QueryListenersInfo();
  23613. }
  23614. if (firstListen) {
  23615. try {
  23616. queryInfo.viewSnap = await eventManagerImpl.onListen(query);
  23617. }
  23618. catch (e) {
  23619. const firestoreError = wrapInUserErrorIfRecoverable(e, `Initialization of query '${stringifyQuery(listener.query)}' failed`);
  23620. listener.onError(firestoreError);
  23621. return;
  23622. }
  23623. }
  23624. eventManagerImpl.queries.set(query, queryInfo);
  23625. queryInfo.listeners.push(listener);
  23626. // Run global snapshot listeners if a consistent snapshot has been emitted.
  23627. listener.applyOnlineStateChange(eventManagerImpl.onlineState);
  23628. if (queryInfo.viewSnap) {
  23629. const raisedEvent = listener.onViewSnapshot(queryInfo.viewSnap);
  23630. if (raisedEvent) {
  23631. raiseSnapshotsInSyncEvent(eventManagerImpl);
  23632. }
  23633. }
  23634. }
  23635. async function eventManagerUnlisten(eventManager, listener) {
  23636. const eventManagerImpl = debugCast(eventManager);
  23637. const query = listener.query;
  23638. let lastListen = false;
  23639. const queryInfo = eventManagerImpl.queries.get(query);
  23640. if (queryInfo) {
  23641. const i = queryInfo.listeners.indexOf(listener);
  23642. if (i >= 0) {
  23643. queryInfo.listeners.splice(i, 1);
  23644. lastListen = queryInfo.listeners.length === 0;
  23645. }
  23646. }
  23647. if (lastListen) {
  23648. eventManagerImpl.queries.delete(query);
  23649. return eventManagerImpl.onUnlisten(query);
  23650. }
  23651. }
  23652. function eventManagerOnWatchChange(eventManager, viewSnaps) {
  23653. const eventManagerImpl = debugCast(eventManager);
  23654. let raisedEvent = false;
  23655. for (const viewSnap of viewSnaps) {
  23656. const query = viewSnap.query;
  23657. const queryInfo = eventManagerImpl.queries.get(query);
  23658. if (queryInfo) {
  23659. for (const listener of queryInfo.listeners) {
  23660. if (listener.onViewSnapshot(viewSnap)) {
  23661. raisedEvent = true;
  23662. }
  23663. }
  23664. queryInfo.viewSnap = viewSnap;
  23665. }
  23666. }
  23667. if (raisedEvent) {
  23668. raiseSnapshotsInSyncEvent(eventManagerImpl);
  23669. }
  23670. }
  23671. function eventManagerOnWatchError(eventManager, query, error) {
  23672. const eventManagerImpl = debugCast(eventManager);
  23673. const queryInfo = eventManagerImpl.queries.get(query);
  23674. if (queryInfo) {
  23675. for (const listener of queryInfo.listeners) {
  23676. listener.onError(error);
  23677. }
  23678. }
  23679. // Remove all listeners. NOTE: We don't need to call syncEngine.unlisten()
  23680. // after an error.
  23681. eventManagerImpl.queries.delete(query);
  23682. }
  23683. function eventManagerOnOnlineStateChange(eventManager, onlineState) {
  23684. const eventManagerImpl = debugCast(eventManager);
  23685. eventManagerImpl.onlineState = onlineState;
  23686. let raisedEvent = false;
  23687. eventManagerImpl.queries.forEach((_, queryInfo) => {
  23688. for (const listener of queryInfo.listeners) {
  23689. // Run global snapshot listeners if a consistent snapshot has been emitted.
  23690. if (listener.applyOnlineStateChange(onlineState)) {
  23691. raisedEvent = true;
  23692. }
  23693. }
  23694. });
  23695. if (raisedEvent) {
  23696. raiseSnapshotsInSyncEvent(eventManagerImpl);
  23697. }
  23698. }
  23699. function addSnapshotsInSyncListener(eventManager, observer) {
  23700. const eventManagerImpl = debugCast(eventManager);
  23701. eventManagerImpl.snapshotsInSyncListeners.add(observer);
  23702. // Immediately fire an initial event, indicating all existing listeners
  23703. // are in-sync.
  23704. observer.next();
  23705. }
  23706. function removeSnapshotsInSyncListener(eventManager, observer) {
  23707. const eventManagerImpl = debugCast(eventManager);
  23708. eventManagerImpl.snapshotsInSyncListeners.delete(observer);
  23709. }
  23710. // Call all global snapshot listeners that have been set.
  23711. function raiseSnapshotsInSyncEvent(eventManagerImpl) {
  23712. eventManagerImpl.snapshotsInSyncListeners.forEach(observer => {
  23713. observer.next();
  23714. });
  23715. }
  23716. /**
  23717. * QueryListener takes a series of internal view snapshots and determines
  23718. * when to raise the event.
  23719. *
  23720. * It uses an Observer to dispatch events.
  23721. */
  23722. class QueryListener {
  23723. constructor(query, queryObserver, options) {
  23724. this.query = query;
  23725. this.queryObserver = queryObserver;
  23726. /**
  23727. * Initial snapshots (e.g. from cache) may not be propagated to the wrapped
  23728. * observer. This flag is set to true once we've actually raised an event.
  23729. */
  23730. this.raisedInitialEvent = false;
  23731. this.snap = null;
  23732. this.onlineState = "Unknown" /* OnlineState.Unknown */;
  23733. this.options = options || {};
  23734. }
  23735. /**
  23736. * Applies the new ViewSnapshot to this listener, raising a user-facing event
  23737. * if applicable (depending on what changed, whether the user has opted into
  23738. * metadata-only changes, etc.). Returns true if a user-facing event was
  23739. * indeed raised.
  23740. */
  23741. onViewSnapshot(snap) {
  23742. if (!this.options.includeMetadataChanges) {
  23743. // Remove the metadata only changes.
  23744. const docChanges = [];
  23745. for (const docChange of snap.docChanges) {
  23746. if (docChange.type !== 3 /* ChangeType.Metadata */) {
  23747. docChanges.push(docChange);
  23748. }
  23749. }
  23750. snap = new ViewSnapshot(snap.query, snap.docs, snap.oldDocs, docChanges, snap.mutatedKeys, snap.fromCache, snap.syncStateChanged,
  23751. /* excludesMetadataChanges= */ true, snap.hasCachedResults);
  23752. }
  23753. let raisedEvent = false;
  23754. if (!this.raisedInitialEvent) {
  23755. if (this.shouldRaiseInitialEvent(snap, this.onlineState)) {
  23756. this.raiseInitialEvent(snap);
  23757. raisedEvent = true;
  23758. }
  23759. }
  23760. else if (this.shouldRaiseEvent(snap)) {
  23761. this.queryObserver.next(snap);
  23762. raisedEvent = true;
  23763. }
  23764. this.snap = snap;
  23765. return raisedEvent;
  23766. }
  23767. onError(error) {
  23768. this.queryObserver.error(error);
  23769. }
  23770. /** Returns whether a snapshot was raised. */
  23771. applyOnlineStateChange(onlineState) {
  23772. this.onlineState = onlineState;
  23773. let raisedEvent = false;
  23774. if (this.snap &&
  23775. !this.raisedInitialEvent &&
  23776. this.shouldRaiseInitialEvent(this.snap, onlineState)) {
  23777. this.raiseInitialEvent(this.snap);
  23778. raisedEvent = true;
  23779. }
  23780. return raisedEvent;
  23781. }
  23782. shouldRaiseInitialEvent(snap, onlineState) {
  23783. // Always raise the first event when we're synced
  23784. if (!snap.fromCache) {
  23785. return true;
  23786. }
  23787. // NOTE: We consider OnlineState.Unknown as online (it should become Offline
  23788. // or Online if we wait long enough).
  23789. const maybeOnline = onlineState !== "Offline" /* OnlineState.Offline */;
  23790. // Don't raise the event if we're online, aren't synced yet (checked
  23791. // above) and are waiting for a sync.
  23792. if (this.options.waitForSyncWhenOnline && maybeOnline) {
  23793. return false;
  23794. }
  23795. // Raise data from cache if we have any documents, have cached results before,
  23796. // or we are offline.
  23797. return (!snap.docs.isEmpty() ||
  23798. snap.hasCachedResults ||
  23799. onlineState === "Offline" /* OnlineState.Offline */);
  23800. }
  23801. shouldRaiseEvent(snap) {
  23802. // We don't need to handle includeDocumentMetadataChanges here because
  23803. // the Metadata only changes have already been stripped out if needed.
  23804. // At this point the only changes we will see are the ones we should
  23805. // propagate.
  23806. if (snap.docChanges.length > 0) {
  23807. return true;
  23808. }
  23809. const hasPendingWritesChanged = this.snap && this.snap.hasPendingWrites !== snap.hasPendingWrites;
  23810. if (snap.syncStateChanged || hasPendingWritesChanged) {
  23811. return this.options.includeMetadataChanges === true;
  23812. }
  23813. // Generally we should have hit one of the cases above, but it's possible
  23814. // to get here if there were only metadata docChanges and they got
  23815. // stripped out.
  23816. return false;
  23817. }
  23818. raiseInitialEvent(snap) {
  23819. snap = ViewSnapshot.fromInitialDocuments(snap.query, snap.docs, snap.mutatedKeys, snap.fromCache, snap.hasCachedResults);
  23820. this.raisedInitialEvent = true;
  23821. this.queryObserver.next(snap);
  23822. }
  23823. }
  23824. /**
  23825. * @license
  23826. * Copyright 2017 Google LLC
  23827. *
  23828. * Licensed under the Apache License, Version 2.0 (the "License");
  23829. * you may not use this file except in compliance with the License.
  23830. * You may obtain a copy of the License at
  23831. *
  23832. * http://www.apache.org/licenses/LICENSE-2.0
  23833. *
  23834. * Unless required by applicable law or agreed to in writing, software
  23835. * distributed under the License is distributed on an "AS IS" BASIS,
  23836. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23837. * See the License for the specific language governing permissions and
  23838. * limitations under the License.
  23839. */
  23840. /**
  23841. * A set of changes to what documents are currently in view and out of view for
  23842. * a given query. These changes are sent to the LocalStore by the View (via
  23843. * the SyncEngine) and are used to pin / unpin documents as appropriate.
  23844. */
  23845. class LocalViewChanges {
  23846. constructor(targetId, fromCache, addedKeys, removedKeys) {
  23847. this.targetId = targetId;
  23848. this.fromCache = fromCache;
  23849. this.addedKeys = addedKeys;
  23850. this.removedKeys = removedKeys;
  23851. }
  23852. static fromSnapshot(targetId, viewSnapshot) {
  23853. let addedKeys = documentKeySet();
  23854. let removedKeys = documentKeySet();
  23855. for (const docChange of viewSnapshot.docChanges) {
  23856. switch (docChange.type) {
  23857. case 0 /* ChangeType.Added */:
  23858. addedKeys = addedKeys.add(docChange.doc.key);
  23859. break;
  23860. case 1 /* ChangeType.Removed */:
  23861. removedKeys = removedKeys.add(docChange.doc.key);
  23862. break;
  23863. // do nothing
  23864. }
  23865. }
  23866. return new LocalViewChanges(targetId, viewSnapshot.fromCache, addedKeys, removedKeys);
  23867. }
  23868. }
  23869. /**
  23870. * @license
  23871. * Copyright 2020 Google LLC
  23872. *
  23873. * Licensed under the Apache License, Version 2.0 (the "License");
  23874. * you may not use this file except in compliance with the License.
  23875. * You may obtain a copy of the License at
  23876. *
  23877. * http://www.apache.org/licenses/LICENSE-2.0
  23878. *
  23879. * Unless required by applicable law or agreed to in writing, software
  23880. * distributed under the License is distributed on an "AS IS" BASIS,
  23881. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23882. * See the License for the specific language governing permissions and
  23883. * limitations under the License.
  23884. */
  23885. /**
  23886. * Helper to convert objects from bundles to model objects in the SDK.
  23887. */
  23888. class BundleConverterImpl {
  23889. constructor(serializer) {
  23890. this.serializer = serializer;
  23891. }
  23892. toDocumentKey(name) {
  23893. return fromName(this.serializer, name);
  23894. }
  23895. /**
  23896. * Converts a BundleDocument to a MutableDocument.
  23897. */
  23898. toMutableDocument(bundledDoc) {
  23899. if (bundledDoc.metadata.exists) {
  23900. return fromDocument(this.serializer, bundledDoc.document, false);
  23901. }
  23902. else {
  23903. return MutableDocument.newNoDocument(this.toDocumentKey(bundledDoc.metadata.name), this.toSnapshotVersion(bundledDoc.metadata.readTime));
  23904. }
  23905. }
  23906. toSnapshotVersion(time) {
  23907. return fromVersion(time);
  23908. }
  23909. }
  23910. /**
  23911. * A class to process the elements from a bundle, load them into local
  23912. * storage and provide progress update while loading.
  23913. */
  23914. class BundleLoader {
  23915. constructor(bundleMetadata, localStore, serializer) {
  23916. this.bundleMetadata = bundleMetadata;
  23917. this.localStore = localStore;
  23918. this.serializer = serializer;
  23919. /** Batched queries to be saved into storage */
  23920. this.queries = [];
  23921. /** Batched documents to be saved into storage */
  23922. this.documents = [];
  23923. /** The collection groups affected by this bundle. */
  23924. this.collectionGroups = new Set();
  23925. this.progress = bundleInitialProgress(bundleMetadata);
  23926. }
  23927. /**
  23928. * Adds an element from the bundle to the loader.
  23929. *
  23930. * Returns a new progress if adding the element leads to a new progress,
  23931. * otherwise returns null.
  23932. */
  23933. addSizedElement(element) {
  23934. this.progress.bytesLoaded += element.byteLength;
  23935. let documentsLoaded = this.progress.documentsLoaded;
  23936. if (element.payload.namedQuery) {
  23937. this.queries.push(element.payload.namedQuery);
  23938. }
  23939. else if (element.payload.documentMetadata) {
  23940. this.documents.push({ metadata: element.payload.documentMetadata });
  23941. if (!element.payload.documentMetadata.exists) {
  23942. ++documentsLoaded;
  23943. }
  23944. const path = ResourcePath.fromString(element.payload.documentMetadata.name);
  23945. this.collectionGroups.add(path.get(path.length - 2));
  23946. }
  23947. else if (element.payload.document) {
  23948. this.documents[this.documents.length - 1].document =
  23949. element.payload.document;
  23950. ++documentsLoaded;
  23951. }
  23952. if (documentsLoaded !== this.progress.documentsLoaded) {
  23953. this.progress.documentsLoaded = documentsLoaded;
  23954. return Object.assign({}, this.progress);
  23955. }
  23956. return null;
  23957. }
  23958. getQueryDocumentMapping(documents) {
  23959. const queryDocumentMap = new Map();
  23960. const bundleConverter = new BundleConverterImpl(this.serializer);
  23961. for (const bundleDoc of documents) {
  23962. if (bundleDoc.metadata.queries) {
  23963. const documentKey = bundleConverter.toDocumentKey(bundleDoc.metadata.name);
  23964. for (const queryName of bundleDoc.metadata.queries) {
  23965. const documentKeys = (queryDocumentMap.get(queryName) || documentKeySet()).add(documentKey);
  23966. queryDocumentMap.set(queryName, documentKeys);
  23967. }
  23968. }
  23969. }
  23970. return queryDocumentMap;
  23971. }
  23972. /**
  23973. * Update the progress to 'Success' and return the updated progress.
  23974. */
  23975. async complete() {
  23976. const changedDocs = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
  23977. const queryDocumentMap = this.getQueryDocumentMapping(this.documents);
  23978. for (const q of this.queries) {
  23979. await localStoreSaveNamedQuery(this.localStore, q, queryDocumentMap.get(q.name));
  23980. }
  23981. this.progress.taskState = 'Success';
  23982. return {
  23983. progress: this.progress,
  23984. changedCollectionGroups: this.collectionGroups,
  23985. changedDocs
  23986. };
  23987. }
  23988. }
  23989. /**
  23990. * Returns a `LoadBundleTaskProgress` representing the initial progress of
  23991. * loading a bundle.
  23992. */
  23993. function bundleInitialProgress(metadata) {
  23994. return {
  23995. taskState: 'Running',
  23996. documentsLoaded: 0,
  23997. bytesLoaded: 0,
  23998. totalDocuments: metadata.totalDocuments,
  23999. totalBytes: metadata.totalBytes
  24000. };
  24001. }
  24002. /**
  24003. * Returns a `LoadBundleTaskProgress` representing the progress that the loading
  24004. * has succeeded.
  24005. */
  24006. function bundleSuccessProgress(metadata) {
  24007. return {
  24008. taskState: 'Success',
  24009. documentsLoaded: metadata.totalDocuments,
  24010. bytesLoaded: metadata.totalBytes,
  24011. totalDocuments: metadata.totalDocuments,
  24012. totalBytes: metadata.totalBytes
  24013. };
  24014. }
  24015. /**
  24016. * @license
  24017. * Copyright 2017 Google LLC
  24018. *
  24019. * Licensed under the Apache License, Version 2.0 (the "License");
  24020. * you may not use this file except in compliance with the License.
  24021. * You may obtain a copy of the License at
  24022. *
  24023. * http://www.apache.org/licenses/LICENSE-2.0
  24024. *
  24025. * Unless required by applicable law or agreed to in writing, software
  24026. * distributed under the License is distributed on an "AS IS" BASIS,
  24027. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24028. * See the License for the specific language governing permissions and
  24029. * limitations under the License.
  24030. */
  24031. class AddedLimboDocument {
  24032. constructor(key) {
  24033. this.key = key;
  24034. }
  24035. }
  24036. class RemovedLimboDocument {
  24037. constructor(key) {
  24038. this.key = key;
  24039. }
  24040. }
  24041. /**
  24042. * View is responsible for computing the final merged truth of what docs are in
  24043. * a query. It gets notified of local and remote changes to docs, and applies
  24044. * the query filters and limits to determine the most correct possible results.
  24045. */
  24046. class View {
  24047. constructor(query,
  24048. /** Documents included in the remote target */
  24049. _syncedDocuments) {
  24050. this.query = query;
  24051. this._syncedDocuments = _syncedDocuments;
  24052. this.syncState = null;
  24053. this.hasCachedResults = false;
  24054. /**
  24055. * A flag whether the view is current with the backend. A view is considered
  24056. * current after it has seen the current flag from the backend and did not
  24057. * lose consistency within the watch stream (e.g. because of an existence
  24058. * filter mismatch).
  24059. */
  24060. this.current = false;
  24061. /** Documents in the view but not in the remote target */
  24062. this.limboDocuments = documentKeySet();
  24063. /** Document Keys that have local changes */
  24064. this.mutatedKeys = documentKeySet();
  24065. this.docComparator = newQueryComparator(query);
  24066. this.documentSet = new DocumentSet(this.docComparator);
  24067. }
  24068. /**
  24069. * The set of remote documents that the server has told us belongs to the target associated with
  24070. * this view.
  24071. */
  24072. get syncedDocuments() {
  24073. return this._syncedDocuments;
  24074. }
  24075. /**
  24076. * Iterates over a set of doc changes, applies the query limit, and computes
  24077. * what the new results should be, what the changes were, and whether we may
  24078. * need to go back to the local cache for more results. Does not make any
  24079. * changes to the view.
  24080. * @param docChanges - The doc changes to apply to this view.
  24081. * @param previousChanges - If this is being called with a refill, then start
  24082. * with this set of docs and changes instead of the current view.
  24083. * @returns a new set of docs, changes, and refill flag.
  24084. */
  24085. computeDocChanges(docChanges, previousChanges) {
  24086. const changeSet = previousChanges
  24087. ? previousChanges.changeSet
  24088. : new DocumentChangeSet();
  24089. const oldDocumentSet = previousChanges
  24090. ? previousChanges.documentSet
  24091. : this.documentSet;
  24092. let newMutatedKeys = previousChanges
  24093. ? previousChanges.mutatedKeys
  24094. : this.mutatedKeys;
  24095. let newDocumentSet = oldDocumentSet;
  24096. let needsRefill = false;
  24097. // Track the last doc in a (full) limit. This is necessary, because some
  24098. // update (a delete, or an update moving a doc past the old limit) might
  24099. // mean there is some other document in the local cache that either should
  24100. // come (1) between the old last limit doc and the new last document, in the
  24101. // case of updates, or (2) after the new last document, in the case of
  24102. // deletes. So we keep this doc at the old limit to compare the updates to.
  24103. //
  24104. // Note that this should never get used in a refill (when previousChanges is
  24105. // set), because there will only be adds -- no deletes or updates.
  24106. const lastDocInLimit = this.query.limitType === "F" /* LimitType.First */ &&
  24107. oldDocumentSet.size === this.query.limit
  24108. ? oldDocumentSet.last()
  24109. : null;
  24110. const firstDocInLimit = this.query.limitType === "L" /* LimitType.Last */ &&
  24111. oldDocumentSet.size === this.query.limit
  24112. ? oldDocumentSet.first()
  24113. : null;
  24114. docChanges.inorderTraversal((key, entry) => {
  24115. const oldDoc = oldDocumentSet.get(key);
  24116. const newDoc = queryMatches(this.query, entry) ? entry : null;
  24117. const oldDocHadPendingMutations = oldDoc
  24118. ? this.mutatedKeys.has(oldDoc.key)
  24119. : false;
  24120. const newDocHasPendingMutations = newDoc
  24121. ? newDoc.hasLocalMutations ||
  24122. // We only consider committed mutations for documents that were
  24123. // mutated during the lifetime of the view.
  24124. (this.mutatedKeys.has(newDoc.key) && newDoc.hasCommittedMutations)
  24125. : false;
  24126. let changeApplied = false;
  24127. // Calculate change
  24128. if (oldDoc && newDoc) {
  24129. const docsEqual = oldDoc.data.isEqual(newDoc.data);
  24130. if (!docsEqual) {
  24131. if (!this.shouldWaitForSyncedDocument(oldDoc, newDoc)) {
  24132. changeSet.track({
  24133. type: 2 /* ChangeType.Modified */,
  24134. doc: newDoc
  24135. });
  24136. changeApplied = true;
  24137. if ((lastDocInLimit &&
  24138. this.docComparator(newDoc, lastDocInLimit) > 0) ||
  24139. (firstDocInLimit &&
  24140. this.docComparator(newDoc, firstDocInLimit) < 0)) {
  24141. // This doc moved from inside the limit to outside the limit.
  24142. // That means there may be some other doc in the local cache
  24143. // that should be included instead.
  24144. needsRefill = true;
  24145. }
  24146. }
  24147. }
  24148. else if (oldDocHadPendingMutations !== newDocHasPendingMutations) {
  24149. changeSet.track({ type: 3 /* ChangeType.Metadata */, doc: newDoc });
  24150. changeApplied = true;
  24151. }
  24152. }
  24153. else if (!oldDoc && newDoc) {
  24154. changeSet.track({ type: 0 /* ChangeType.Added */, doc: newDoc });
  24155. changeApplied = true;
  24156. }
  24157. else if (oldDoc && !newDoc) {
  24158. changeSet.track({ type: 1 /* ChangeType.Removed */, doc: oldDoc });
  24159. changeApplied = true;
  24160. if (lastDocInLimit || firstDocInLimit) {
  24161. // A doc was removed from a full limit query. We'll need to
  24162. // requery from the local cache to see if we know about some other
  24163. // doc that should be in the results.
  24164. needsRefill = true;
  24165. }
  24166. }
  24167. if (changeApplied) {
  24168. if (newDoc) {
  24169. newDocumentSet = newDocumentSet.add(newDoc);
  24170. if (newDocHasPendingMutations) {
  24171. newMutatedKeys = newMutatedKeys.add(key);
  24172. }
  24173. else {
  24174. newMutatedKeys = newMutatedKeys.delete(key);
  24175. }
  24176. }
  24177. else {
  24178. newDocumentSet = newDocumentSet.delete(key);
  24179. newMutatedKeys = newMutatedKeys.delete(key);
  24180. }
  24181. }
  24182. });
  24183. // Drop documents out to meet limit/limitToLast requirement.
  24184. if (this.query.limit !== null) {
  24185. while (newDocumentSet.size > this.query.limit) {
  24186. const oldDoc = this.query.limitType === "F" /* LimitType.First */
  24187. ? newDocumentSet.last()
  24188. : newDocumentSet.first();
  24189. newDocumentSet = newDocumentSet.delete(oldDoc.key);
  24190. newMutatedKeys = newMutatedKeys.delete(oldDoc.key);
  24191. changeSet.track({ type: 1 /* ChangeType.Removed */, doc: oldDoc });
  24192. }
  24193. }
  24194. return {
  24195. documentSet: newDocumentSet,
  24196. changeSet,
  24197. needsRefill,
  24198. mutatedKeys: newMutatedKeys
  24199. };
  24200. }
  24201. shouldWaitForSyncedDocument(oldDoc, newDoc) {
  24202. // We suppress the initial change event for documents that were modified as
  24203. // part of a write acknowledgment (e.g. when the value of a server transform
  24204. // is applied) as Watch will send us the same document again.
  24205. // By suppressing the event, we only raise two user visible events (one with
  24206. // `hasPendingWrites` and the final state of the document) instead of three
  24207. // (one with `hasPendingWrites`, the modified document with
  24208. // `hasPendingWrites` and the final state of the document).
  24209. return (oldDoc.hasLocalMutations &&
  24210. newDoc.hasCommittedMutations &&
  24211. !newDoc.hasLocalMutations);
  24212. }
  24213. /**
  24214. * Updates the view with the given ViewDocumentChanges and optionally updates
  24215. * limbo docs and sync state from the provided target change.
  24216. * @param docChanges - The set of changes to make to the view's docs.
  24217. * @param updateLimboDocuments - Whether to update limbo documents based on
  24218. * this change.
  24219. * @param targetChange - A target change to apply for computing limbo docs and
  24220. * sync state.
  24221. * @returns A new ViewChange with the given docs, changes, and sync state.
  24222. */
  24223. // PORTING NOTE: The iOS/Android clients always compute limbo document changes.
  24224. applyChanges(docChanges, updateLimboDocuments, targetChange) {
  24225. const oldDocs = this.documentSet;
  24226. this.documentSet = docChanges.documentSet;
  24227. this.mutatedKeys = docChanges.mutatedKeys;
  24228. // Sort changes based on type and query comparator
  24229. const changes = docChanges.changeSet.getChanges();
  24230. changes.sort((c1, c2) => {
  24231. return (compareChangeType(c1.type, c2.type) ||
  24232. this.docComparator(c1.doc, c2.doc));
  24233. });
  24234. this.applyTargetChange(targetChange);
  24235. const limboChanges = updateLimboDocuments
  24236. ? this.updateLimboDocuments()
  24237. : [];
  24238. const synced = this.limboDocuments.size === 0 && this.current;
  24239. const newSyncState = synced ? 1 /* SyncState.Synced */ : 0 /* SyncState.Local */;
  24240. const syncStateChanged = newSyncState !== this.syncState;
  24241. this.syncState = newSyncState;
  24242. if (changes.length === 0 && !syncStateChanged) {
  24243. // no changes
  24244. return { limboChanges };
  24245. }
  24246. else {
  24247. const snap = new ViewSnapshot(this.query, docChanges.documentSet, oldDocs, changes, docChanges.mutatedKeys, newSyncState === 0 /* SyncState.Local */, syncStateChanged,
  24248. /* excludesMetadataChanges= */ false, targetChange
  24249. ? targetChange.resumeToken.approximateByteSize() > 0
  24250. : false);
  24251. return {
  24252. snapshot: snap,
  24253. limboChanges
  24254. };
  24255. }
  24256. }
  24257. /**
  24258. * Applies an OnlineState change to the view, potentially generating a
  24259. * ViewChange if the view's syncState changes as a result.
  24260. */
  24261. applyOnlineStateChange(onlineState) {
  24262. if (this.current && onlineState === "Offline" /* OnlineState.Offline */) {
  24263. // If we're offline, set `current` to false and then call applyChanges()
  24264. // to refresh our syncState and generate a ViewChange as appropriate. We
  24265. // are guaranteed to get a new TargetChange that sets `current` back to
  24266. // true once the client is back online.
  24267. this.current = false;
  24268. return this.applyChanges({
  24269. documentSet: this.documentSet,
  24270. changeSet: new DocumentChangeSet(),
  24271. mutatedKeys: this.mutatedKeys,
  24272. needsRefill: false
  24273. },
  24274. /* updateLimboDocuments= */ false);
  24275. }
  24276. else {
  24277. // No effect, just return a no-op ViewChange.
  24278. return { limboChanges: [] };
  24279. }
  24280. }
  24281. /**
  24282. * Returns whether the doc for the given key should be in limbo.
  24283. */
  24284. shouldBeInLimbo(key) {
  24285. // If the remote end says it's part of this query, it's not in limbo.
  24286. if (this._syncedDocuments.has(key)) {
  24287. return false;
  24288. }
  24289. // The local store doesn't think it's a result, so it shouldn't be in limbo.
  24290. if (!this.documentSet.has(key)) {
  24291. return false;
  24292. }
  24293. // If there are local changes to the doc, they might explain why the server
  24294. // doesn't know that it's part of the query. So don't put it in limbo.
  24295. // TODO(klimt): Ideally, we would only consider changes that might actually
  24296. // affect this specific query.
  24297. if (this.documentSet.get(key).hasLocalMutations) {
  24298. return false;
  24299. }
  24300. // Everything else is in limbo.
  24301. return true;
  24302. }
  24303. /**
  24304. * Updates syncedDocuments, current, and limbo docs based on the given change.
  24305. * Returns the list of changes to which docs are in limbo.
  24306. */
  24307. applyTargetChange(targetChange) {
  24308. if (targetChange) {
  24309. targetChange.addedDocuments.forEach(key => (this._syncedDocuments = this._syncedDocuments.add(key)));
  24310. targetChange.modifiedDocuments.forEach(key => {
  24311. });
  24312. targetChange.removedDocuments.forEach(key => (this._syncedDocuments = this._syncedDocuments.delete(key)));
  24313. this.current = targetChange.current;
  24314. }
  24315. }
  24316. updateLimboDocuments() {
  24317. // We can only determine limbo documents when we're in-sync with the server.
  24318. if (!this.current) {
  24319. return [];
  24320. }
  24321. // TODO(klimt): Do this incrementally so that it's not quadratic when
  24322. // updating many documents.
  24323. const oldLimboDocuments = this.limboDocuments;
  24324. this.limboDocuments = documentKeySet();
  24325. this.documentSet.forEach(doc => {
  24326. if (this.shouldBeInLimbo(doc.key)) {
  24327. this.limboDocuments = this.limboDocuments.add(doc.key);
  24328. }
  24329. });
  24330. // Diff the new limbo docs with the old limbo docs.
  24331. const changes = [];
  24332. oldLimboDocuments.forEach(key => {
  24333. if (!this.limboDocuments.has(key)) {
  24334. changes.push(new RemovedLimboDocument(key));
  24335. }
  24336. });
  24337. this.limboDocuments.forEach(key => {
  24338. if (!oldLimboDocuments.has(key)) {
  24339. changes.push(new AddedLimboDocument(key));
  24340. }
  24341. });
  24342. return changes;
  24343. }
  24344. /**
  24345. * Update the in-memory state of the current view with the state read from
  24346. * persistence.
  24347. *
  24348. * We update the query view whenever a client's primary status changes:
  24349. * - When a client transitions from primary to secondary, it can miss
  24350. * LocalStorage updates and its query views may temporarily not be
  24351. * synchronized with the state on disk.
  24352. * - For secondary to primary transitions, the client needs to update the list
  24353. * of `syncedDocuments` since secondary clients update their query views
  24354. * based purely on synthesized RemoteEvents.
  24355. *
  24356. * @param queryResult.documents - The documents that match the query according
  24357. * to the LocalStore.
  24358. * @param queryResult.remoteKeys - The keys of the documents that match the
  24359. * query according to the backend.
  24360. *
  24361. * @returns The ViewChange that resulted from this synchronization.
  24362. */
  24363. // PORTING NOTE: Multi-tab only.
  24364. synchronizeWithPersistedState(queryResult) {
  24365. this._syncedDocuments = queryResult.remoteKeys;
  24366. this.limboDocuments = documentKeySet();
  24367. const docChanges = this.computeDocChanges(queryResult.documents);
  24368. return this.applyChanges(docChanges, /*updateLimboDocuments=*/ true);
  24369. }
  24370. /**
  24371. * Returns a view snapshot as if this query was just listened to. Contains
  24372. * a document add for every existing document and the `fromCache` and
  24373. * `hasPendingWrites` status of the already established view.
  24374. */
  24375. // PORTING NOTE: Multi-tab only.
  24376. computeInitialSnapshot() {
  24377. return ViewSnapshot.fromInitialDocuments(this.query, this.documentSet, this.mutatedKeys, this.syncState === 0 /* SyncState.Local */, this.hasCachedResults);
  24378. }
  24379. }
  24380. function compareChangeType(c1, c2) {
  24381. const order = (change) => {
  24382. switch (change) {
  24383. case 0 /* ChangeType.Added */:
  24384. return 1;
  24385. case 2 /* ChangeType.Modified */:
  24386. return 2;
  24387. case 3 /* ChangeType.Metadata */:
  24388. // A metadata change is converted to a modified change at the public
  24389. // api layer. Since we sort by document key and then change type,
  24390. // metadata and modified changes must be sorted equivalently.
  24391. return 2;
  24392. case 1 /* ChangeType.Removed */:
  24393. return 0;
  24394. default:
  24395. return fail();
  24396. }
  24397. };
  24398. return order(c1) - order(c2);
  24399. }
  24400. /**
  24401. * @license
  24402. * Copyright 2020 Google LLC
  24403. *
  24404. * Licensed under the Apache License, Version 2.0 (the "License");
  24405. * you may not use this file except in compliance with the License.
  24406. * You may obtain a copy of the License at
  24407. *
  24408. * http://www.apache.org/licenses/LICENSE-2.0
  24409. *
  24410. * Unless required by applicable law or agreed to in writing, software
  24411. * distributed under the License is distributed on an "AS IS" BASIS,
  24412. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24413. * See the License for the specific language governing permissions and
  24414. * limitations under the License.
  24415. */
  24416. const LOG_TAG$3 = 'SyncEngine';
  24417. /**
  24418. * QueryView contains all of the data that SyncEngine needs to keep track of for
  24419. * a particular query.
  24420. */
  24421. class QueryView {
  24422. constructor(
  24423. /**
  24424. * The query itself.
  24425. */
  24426. query,
  24427. /**
  24428. * The target number created by the client that is used in the watch
  24429. * stream to identify this query.
  24430. */
  24431. targetId,
  24432. /**
  24433. * The view is responsible for computing the final merged truth of what
  24434. * docs are in the query. It gets notified of local and remote changes,
  24435. * and applies the query filters and limits to determine the most correct
  24436. * possible results.
  24437. */
  24438. view) {
  24439. this.query = query;
  24440. this.targetId = targetId;
  24441. this.view = view;
  24442. }
  24443. }
  24444. /** Tracks a limbo resolution. */
  24445. class LimboResolution {
  24446. constructor(key) {
  24447. this.key = key;
  24448. /**
  24449. * Set to true once we've received a document. This is used in
  24450. * getRemoteKeysForTarget() and ultimately used by WatchChangeAggregator to
  24451. * decide whether it needs to manufacture a delete event for the target once
  24452. * the target is CURRENT.
  24453. */
  24454. this.receivedDocument = false;
  24455. }
  24456. }
  24457. /**
  24458. * An implementation of `SyncEngine` coordinating with other parts of SDK.
  24459. *
  24460. * The parts of SyncEngine that act as a callback to RemoteStore need to be
  24461. * registered individually. This is done in `syncEngineWrite()` and
  24462. * `syncEngineListen()` (as well as `applyPrimaryState()`) as these methods
  24463. * serve as entry points to RemoteStore's functionality.
  24464. *
  24465. * Note: some field defined in this class might have public access level, but
  24466. * the class is not exported so they are only accessible from this module.
  24467. * This is useful to implement optional features (like bundles) in free
  24468. * functions, such that they are tree-shakeable.
  24469. */
  24470. class SyncEngineImpl {
  24471. constructor(localStore, remoteStore, eventManager,
  24472. // PORTING NOTE: Manages state synchronization in multi-tab environments.
  24473. sharedClientState, currentUser, maxConcurrentLimboResolutions) {
  24474. this.localStore = localStore;
  24475. this.remoteStore = remoteStore;
  24476. this.eventManager = eventManager;
  24477. this.sharedClientState = sharedClientState;
  24478. this.currentUser = currentUser;
  24479. this.maxConcurrentLimboResolutions = maxConcurrentLimboResolutions;
  24480. this.syncEngineListener = {};
  24481. this.queryViewsByQuery = new ObjectMap(q => canonifyQuery(q), queryEquals);
  24482. this.queriesByTarget = new Map();
  24483. /**
  24484. * The keys of documents that are in limbo for which we haven't yet started a
  24485. * limbo resolution query. The strings in this set are the result of calling
  24486. * `key.path.canonicalString()` where `key` is a `DocumentKey` object.
  24487. *
  24488. * The `Set` type was chosen because it provides efficient lookup and removal
  24489. * of arbitrary elements and it also maintains insertion order, providing the
  24490. * desired queue-like FIFO semantics.
  24491. */
  24492. this.enqueuedLimboResolutions = new Set();
  24493. /**
  24494. * Keeps track of the target ID for each document that is in limbo with an
  24495. * active target.
  24496. */
  24497. this.activeLimboTargetsByKey = new SortedMap(DocumentKey.comparator);
  24498. /**
  24499. * Keeps track of the information about an active limbo resolution for each
  24500. * active target ID that was started for the purpose of limbo resolution.
  24501. */
  24502. this.activeLimboResolutionsByTarget = new Map();
  24503. this.limboDocumentRefs = new ReferenceSet();
  24504. /** Stores user completion handlers, indexed by User and BatchId. */
  24505. this.mutationUserCallbacks = {};
  24506. /** Stores user callbacks waiting for all pending writes to be acknowledged. */
  24507. this.pendingWritesCallbacks = new Map();
  24508. this.limboTargetIdGenerator = TargetIdGenerator.forSyncEngine();
  24509. this.onlineState = "Unknown" /* OnlineState.Unknown */;
  24510. // The primary state is set to `true` or `false` immediately after Firestore
  24511. // startup. In the interim, a client should only be considered primary if
  24512. // `isPrimary` is true.
  24513. this._isPrimaryClient = undefined;
  24514. }
  24515. get isPrimaryClient() {
  24516. return this._isPrimaryClient === true;
  24517. }
  24518. }
  24519. function newSyncEngine(localStore, remoteStore, eventManager,
  24520. // PORTING NOTE: Manages state synchronization in multi-tab environments.
  24521. sharedClientState, currentUser, maxConcurrentLimboResolutions, isPrimary) {
  24522. const syncEngine = new SyncEngineImpl(localStore, remoteStore, eventManager, sharedClientState, currentUser, maxConcurrentLimboResolutions);
  24523. if (isPrimary) {
  24524. syncEngine._isPrimaryClient = true;
  24525. }
  24526. return syncEngine;
  24527. }
  24528. /**
  24529. * Initiates the new listen, resolves promise when listen enqueued to the
  24530. * server. All the subsequent view snapshots or errors are sent to the
  24531. * subscribed handlers. Returns the initial snapshot.
  24532. */
  24533. async function syncEngineListen(syncEngine, query) {
  24534. const syncEngineImpl = ensureWatchCallbacks(syncEngine);
  24535. let targetId;
  24536. let viewSnapshot;
  24537. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  24538. if (queryView) {
  24539. // PORTING NOTE: With Multi-Tab Web, it is possible that a query view
  24540. // already exists when EventManager calls us for the first time. This
  24541. // happens when the primary tab is already listening to this query on
  24542. // behalf of another tab and the user of the primary also starts listening
  24543. // to the query. EventManager will not have an assigned target ID in this
  24544. // case and calls `listen` to obtain this ID.
  24545. targetId = queryView.targetId;
  24546. syncEngineImpl.sharedClientState.addLocalQueryTarget(targetId);
  24547. viewSnapshot = queryView.view.computeInitialSnapshot();
  24548. }
  24549. else {
  24550. const targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, queryToTarget(query));
  24551. const status = syncEngineImpl.sharedClientState.addLocalQueryTarget(targetData.targetId);
  24552. targetId = targetData.targetId;
  24553. viewSnapshot = await initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, status === 'current', targetData.resumeToken);
  24554. if (syncEngineImpl.isPrimaryClient) {
  24555. remoteStoreListen(syncEngineImpl.remoteStore, targetData);
  24556. }
  24557. }
  24558. return viewSnapshot;
  24559. }
  24560. /**
  24561. * Registers a view for a previously unknown query and computes its initial
  24562. * snapshot.
  24563. */
  24564. async function initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, current, resumeToken) {
  24565. // PORTING NOTE: On Web only, we inject the code that registers new Limbo
  24566. // targets based on view changes. This allows us to only depend on Limbo
  24567. // changes when user code includes queries.
  24568. syncEngineImpl.applyDocChanges = (queryView, changes, remoteEvent) => applyDocChanges(syncEngineImpl, queryView, changes, remoteEvent);
  24569. const queryResult = await localStoreExecuteQuery(syncEngineImpl.localStore, query,
  24570. /* usePreviousResults= */ true);
  24571. const view = new View(query, queryResult.remoteKeys);
  24572. const viewDocChanges = view.computeDocChanges(queryResult.documents);
  24573. const synthesizedTargetChange = TargetChange.createSynthesizedTargetChangeForCurrentChange(targetId, current && syncEngineImpl.onlineState !== "Offline" /* OnlineState.Offline */, resumeToken);
  24574. const viewChange = view.applyChanges(viewDocChanges,
  24575. /* updateLimboDocuments= */ syncEngineImpl.isPrimaryClient, synthesizedTargetChange);
  24576. updateTrackedLimbos(syncEngineImpl, targetId, viewChange.limboChanges);
  24577. const data = new QueryView(query, targetId, view);
  24578. syncEngineImpl.queryViewsByQuery.set(query, data);
  24579. if (syncEngineImpl.queriesByTarget.has(targetId)) {
  24580. syncEngineImpl.queriesByTarget.get(targetId).push(query);
  24581. }
  24582. else {
  24583. syncEngineImpl.queriesByTarget.set(targetId, [query]);
  24584. }
  24585. return viewChange.snapshot;
  24586. }
  24587. /** Stops listening to the query. */
  24588. async function syncEngineUnlisten(syncEngine, query) {
  24589. const syncEngineImpl = debugCast(syncEngine);
  24590. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  24591. // Only clean up the query view and target if this is the only query mapped
  24592. // to the target.
  24593. const queries = syncEngineImpl.queriesByTarget.get(queryView.targetId);
  24594. if (queries.length > 1) {
  24595. syncEngineImpl.queriesByTarget.set(queryView.targetId, queries.filter(q => !queryEquals(q, query)));
  24596. syncEngineImpl.queryViewsByQuery.delete(query);
  24597. return;
  24598. }
  24599. // No other queries are mapped to the target, clean up the query and the target.
  24600. if (syncEngineImpl.isPrimaryClient) {
  24601. // We need to remove the local query target first to allow us to verify
  24602. // whether any other client is still interested in this target.
  24603. syncEngineImpl.sharedClientState.removeLocalQueryTarget(queryView.targetId);
  24604. const targetRemainsActive = syncEngineImpl.sharedClientState.isActiveQueryTarget(queryView.targetId);
  24605. if (!targetRemainsActive) {
  24606. await localStoreReleaseTarget(syncEngineImpl.localStore, queryView.targetId,
  24607. /*keepPersistedTargetData=*/ false)
  24608. .then(() => {
  24609. syncEngineImpl.sharedClientState.clearQueryState(queryView.targetId);
  24610. remoteStoreUnlisten(syncEngineImpl.remoteStore, queryView.targetId);
  24611. removeAndCleanupTarget(syncEngineImpl, queryView.targetId);
  24612. })
  24613. .catch(ignoreIfPrimaryLeaseLoss);
  24614. }
  24615. }
  24616. else {
  24617. removeAndCleanupTarget(syncEngineImpl, queryView.targetId);
  24618. await localStoreReleaseTarget(syncEngineImpl.localStore, queryView.targetId,
  24619. /*keepPersistedTargetData=*/ true);
  24620. }
  24621. }
  24622. /**
  24623. * Initiates the write of local mutation batch which involves adding the
  24624. * writes to the mutation queue, notifying the remote store about new
  24625. * mutations and raising events for any changes this write caused.
  24626. *
  24627. * The promise returned by this call is resolved when the above steps
  24628. * have completed, *not* when the write was acked by the backend. The
  24629. * userCallback is resolved once the write was acked/rejected by the
  24630. * backend (or failed locally for any other reason).
  24631. */
  24632. async function syncEngineWrite(syncEngine, batch, userCallback) {
  24633. const syncEngineImpl = syncEngineEnsureWriteCallbacks(syncEngine);
  24634. try {
  24635. const result = await localStoreWriteLocally(syncEngineImpl.localStore, batch);
  24636. syncEngineImpl.sharedClientState.addPendingMutation(result.batchId);
  24637. addMutationCallback(syncEngineImpl, result.batchId, userCallback);
  24638. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, result.changes);
  24639. await fillWritePipeline(syncEngineImpl.remoteStore);
  24640. }
  24641. catch (e) {
  24642. // If we can't persist the mutation, we reject the user callback and
  24643. // don't send the mutation. The user can then retry the write.
  24644. const error = wrapInUserErrorIfRecoverable(e, `Failed to persist write`);
  24645. userCallback.reject(error);
  24646. }
  24647. }
  24648. /**
  24649. * Applies one remote event to the sync engine, notifying any views of the
  24650. * changes, and releasing any pending mutation batches that would become
  24651. * visible because of the snapshot version the remote event contains.
  24652. */
  24653. async function syncEngineApplyRemoteEvent(syncEngine, remoteEvent) {
  24654. const syncEngineImpl = debugCast(syncEngine);
  24655. try {
  24656. const changes = await localStoreApplyRemoteEventToLocalCache(syncEngineImpl.localStore, remoteEvent);
  24657. // Update `receivedDocument` as appropriate for any limbo targets.
  24658. remoteEvent.targetChanges.forEach((targetChange, targetId) => {
  24659. const limboResolution = syncEngineImpl.activeLimboResolutionsByTarget.get(targetId);
  24660. if (limboResolution) {
  24661. // Since this is a limbo resolution lookup, it's for a single document
  24662. // and it could be added, modified, or removed, but not a combination.
  24663. hardAssert(targetChange.addedDocuments.size +
  24664. targetChange.modifiedDocuments.size +
  24665. targetChange.removedDocuments.size <=
  24666. 1);
  24667. if (targetChange.addedDocuments.size > 0) {
  24668. limboResolution.receivedDocument = true;
  24669. }
  24670. else if (targetChange.modifiedDocuments.size > 0) {
  24671. hardAssert(limboResolution.receivedDocument);
  24672. }
  24673. else if (targetChange.removedDocuments.size > 0) {
  24674. hardAssert(limboResolution.receivedDocument);
  24675. limboResolution.receivedDocument = false;
  24676. }
  24677. else {
  24678. // This was probably just a CURRENT targetChange or similar.
  24679. }
  24680. }
  24681. });
  24682. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, remoteEvent);
  24683. }
  24684. catch (error) {
  24685. await ignoreIfPrimaryLeaseLoss(error);
  24686. }
  24687. }
  24688. /**
  24689. * Applies an OnlineState change to the sync engine and notifies any views of
  24690. * the change.
  24691. */
  24692. function syncEngineApplyOnlineStateChange(syncEngine, onlineState, source) {
  24693. const syncEngineImpl = debugCast(syncEngine);
  24694. // If we are the secondary client, we explicitly ignore the remote store's
  24695. // online state (the local client may go offline, even though the primary
  24696. // tab remains online) and only apply the primary tab's online state from
  24697. // SharedClientState.
  24698. if ((syncEngineImpl.isPrimaryClient &&
  24699. source === 0 /* OnlineStateSource.RemoteStore */) ||
  24700. (!syncEngineImpl.isPrimaryClient &&
  24701. source === 1 /* OnlineStateSource.SharedClientState */)) {
  24702. const newViewSnapshots = [];
  24703. syncEngineImpl.queryViewsByQuery.forEach((query, queryView) => {
  24704. const viewChange = queryView.view.applyOnlineStateChange(onlineState);
  24705. if (viewChange.snapshot) {
  24706. newViewSnapshots.push(viewChange.snapshot);
  24707. }
  24708. });
  24709. eventManagerOnOnlineStateChange(syncEngineImpl.eventManager, onlineState);
  24710. if (newViewSnapshots.length) {
  24711. syncEngineImpl.syncEngineListener.onWatchChange(newViewSnapshots);
  24712. }
  24713. syncEngineImpl.onlineState = onlineState;
  24714. if (syncEngineImpl.isPrimaryClient) {
  24715. syncEngineImpl.sharedClientState.setOnlineState(onlineState);
  24716. }
  24717. }
  24718. }
  24719. /**
  24720. * Rejects the listen for the given targetID. This can be triggered by the
  24721. * backend for any active target.
  24722. *
  24723. * @param syncEngine - The sync engine implementation.
  24724. * @param targetId - The targetID corresponds to one previously initiated by the
  24725. * user as part of TargetData passed to listen() on RemoteStore.
  24726. * @param err - A description of the condition that has forced the rejection.
  24727. * Nearly always this will be an indication that the user is no longer
  24728. * authorized to see the data matching the target.
  24729. */
  24730. async function syncEngineRejectListen(syncEngine, targetId, err) {
  24731. const syncEngineImpl = debugCast(syncEngine);
  24732. // PORTING NOTE: Multi-tab only.
  24733. syncEngineImpl.sharedClientState.updateQueryState(targetId, 'rejected', err);
  24734. const limboResolution = syncEngineImpl.activeLimboResolutionsByTarget.get(targetId);
  24735. const limboKey = limboResolution && limboResolution.key;
  24736. if (limboKey) {
  24737. // TODO(klimt): We really only should do the following on permission
  24738. // denied errors, but we don't have the cause code here.
  24739. // It's a limbo doc. Create a synthetic event saying it was deleted.
  24740. // This is kind of a hack. Ideally, we would have a method in the local
  24741. // store to purge a document. However, it would be tricky to keep all of
  24742. // the local store's invariants with another method.
  24743. let documentUpdates = new SortedMap(DocumentKey.comparator);
  24744. // TODO(b/217189216): This limbo document should ideally have a read time,
  24745. // so that it is picked up by any read-time based scans. The backend,
  24746. // however, does not send a read time for target removals.
  24747. documentUpdates = documentUpdates.insert(limboKey, MutableDocument.newNoDocument(limboKey, SnapshotVersion.min()));
  24748. const resolvedLimboDocuments = documentKeySet().add(limboKey);
  24749. const event = new RemoteEvent(SnapshotVersion.min(),
  24750. /* targetChanges= */ new Map(),
  24751. /* targetMismatches= */ new SortedMap(primitiveComparator), documentUpdates, resolvedLimboDocuments);
  24752. await syncEngineApplyRemoteEvent(syncEngineImpl, event);
  24753. // Since this query failed, we won't want to manually unlisten to it.
  24754. // We only remove it from bookkeeping after we successfully applied the
  24755. // RemoteEvent. If `applyRemoteEvent()` throws, we want to re-listen to
  24756. // this query when the RemoteStore restarts the Watch stream, which should
  24757. // re-trigger the target failure.
  24758. syncEngineImpl.activeLimboTargetsByKey =
  24759. syncEngineImpl.activeLimboTargetsByKey.remove(limboKey);
  24760. syncEngineImpl.activeLimboResolutionsByTarget.delete(targetId);
  24761. pumpEnqueuedLimboResolutions(syncEngineImpl);
  24762. }
  24763. else {
  24764. await localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  24765. /* keepPersistedTargetData */ false)
  24766. .then(() => removeAndCleanupTarget(syncEngineImpl, targetId, err))
  24767. .catch(ignoreIfPrimaryLeaseLoss);
  24768. }
  24769. }
  24770. async function syncEngineApplySuccessfulWrite(syncEngine, mutationBatchResult) {
  24771. const syncEngineImpl = debugCast(syncEngine);
  24772. const batchId = mutationBatchResult.batch.batchId;
  24773. try {
  24774. const changes = await localStoreAcknowledgeBatch(syncEngineImpl.localStore, mutationBatchResult);
  24775. // The local store may or may not be able to apply the write result and
  24776. // raise events immediately (depending on whether the watcher is caught
  24777. // up), so we raise user callbacks first so that they consistently happen
  24778. // before listen events.
  24779. processUserCallback(syncEngineImpl, batchId, /*error=*/ null);
  24780. triggerPendingWritesCallbacks(syncEngineImpl, batchId);
  24781. syncEngineImpl.sharedClientState.updateMutationState(batchId, 'acknowledged');
  24782. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes);
  24783. }
  24784. catch (error) {
  24785. await ignoreIfPrimaryLeaseLoss(error);
  24786. }
  24787. }
  24788. async function syncEngineRejectFailedWrite(syncEngine, batchId, error) {
  24789. const syncEngineImpl = debugCast(syncEngine);
  24790. try {
  24791. const changes = await localStoreRejectBatch(syncEngineImpl.localStore, batchId);
  24792. // The local store may or may not be able to apply the write result and
  24793. // raise events immediately (depending on whether the watcher is caught up),
  24794. // so we raise user callbacks first so that they consistently happen before
  24795. // listen events.
  24796. processUserCallback(syncEngineImpl, batchId, error);
  24797. triggerPendingWritesCallbacks(syncEngineImpl, batchId);
  24798. syncEngineImpl.sharedClientState.updateMutationState(batchId, 'rejected', error);
  24799. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes);
  24800. }
  24801. catch (error) {
  24802. await ignoreIfPrimaryLeaseLoss(error);
  24803. }
  24804. }
  24805. /**
  24806. * Registers a user callback that resolves when all pending mutations at the moment of calling
  24807. * are acknowledged .
  24808. */
  24809. async function syncEngineRegisterPendingWritesCallback(syncEngine, callback) {
  24810. const syncEngineImpl = debugCast(syncEngine);
  24811. if (!canUseNetwork(syncEngineImpl.remoteStore)) {
  24812. logDebug(LOG_TAG$3, 'The network is disabled. The task returned by ' +
  24813. "'awaitPendingWrites()' will not complete until the network is enabled.");
  24814. }
  24815. try {
  24816. const highestBatchId = await localStoreGetHighestUnacknowledgedBatchId(syncEngineImpl.localStore);
  24817. if (highestBatchId === BATCHID_UNKNOWN) {
  24818. // Trigger the callback right away if there is no pending writes at the moment.
  24819. callback.resolve();
  24820. return;
  24821. }
  24822. const callbacks = syncEngineImpl.pendingWritesCallbacks.get(highestBatchId) || [];
  24823. callbacks.push(callback);
  24824. syncEngineImpl.pendingWritesCallbacks.set(highestBatchId, callbacks);
  24825. }
  24826. catch (e) {
  24827. const firestoreError = wrapInUserErrorIfRecoverable(e, 'Initialization of waitForPendingWrites() operation failed');
  24828. callback.reject(firestoreError);
  24829. }
  24830. }
  24831. /**
  24832. * Triggers the callbacks that are waiting for this batch id to get acknowledged by server,
  24833. * if there are any.
  24834. */
  24835. function triggerPendingWritesCallbacks(syncEngineImpl, batchId) {
  24836. (syncEngineImpl.pendingWritesCallbacks.get(batchId) || []).forEach(callback => {
  24837. callback.resolve();
  24838. });
  24839. syncEngineImpl.pendingWritesCallbacks.delete(batchId);
  24840. }
  24841. /** Reject all outstanding callbacks waiting for pending writes to complete. */
  24842. function rejectOutstandingPendingWritesCallbacks(syncEngineImpl, errorMessage) {
  24843. syncEngineImpl.pendingWritesCallbacks.forEach(callbacks => {
  24844. callbacks.forEach(callback => {
  24845. callback.reject(new FirestoreError(Code.CANCELLED, errorMessage));
  24846. });
  24847. });
  24848. syncEngineImpl.pendingWritesCallbacks.clear();
  24849. }
  24850. function addMutationCallback(syncEngineImpl, batchId, callback) {
  24851. let newCallbacks = syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()];
  24852. if (!newCallbacks) {
  24853. newCallbacks = new SortedMap(primitiveComparator);
  24854. }
  24855. newCallbacks = newCallbacks.insert(batchId, callback);
  24856. syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()] =
  24857. newCallbacks;
  24858. }
  24859. /**
  24860. * Resolves or rejects the user callback for the given batch and then discards
  24861. * it.
  24862. */
  24863. function processUserCallback(syncEngine, batchId, error) {
  24864. const syncEngineImpl = debugCast(syncEngine);
  24865. let newCallbacks = syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()];
  24866. // NOTE: Mutations restored from persistence won't have callbacks, so it's
  24867. // okay for there to be no callback for this ID.
  24868. if (newCallbacks) {
  24869. const callback = newCallbacks.get(batchId);
  24870. if (callback) {
  24871. if (error) {
  24872. callback.reject(error);
  24873. }
  24874. else {
  24875. callback.resolve();
  24876. }
  24877. newCallbacks = newCallbacks.remove(batchId);
  24878. }
  24879. syncEngineImpl.mutationUserCallbacks[syncEngineImpl.currentUser.toKey()] =
  24880. newCallbacks;
  24881. }
  24882. }
  24883. function removeAndCleanupTarget(syncEngineImpl, targetId, error = null) {
  24884. syncEngineImpl.sharedClientState.removeLocalQueryTarget(targetId);
  24885. for (const query of syncEngineImpl.queriesByTarget.get(targetId)) {
  24886. syncEngineImpl.queryViewsByQuery.delete(query);
  24887. if (error) {
  24888. syncEngineImpl.syncEngineListener.onWatchError(query, error);
  24889. }
  24890. }
  24891. syncEngineImpl.queriesByTarget.delete(targetId);
  24892. if (syncEngineImpl.isPrimaryClient) {
  24893. const limboKeys = syncEngineImpl.limboDocumentRefs.removeReferencesForId(targetId);
  24894. limboKeys.forEach(limboKey => {
  24895. const isReferenced = syncEngineImpl.limboDocumentRefs.containsKey(limboKey);
  24896. if (!isReferenced) {
  24897. // We removed the last reference for this key
  24898. removeLimboTarget(syncEngineImpl, limboKey);
  24899. }
  24900. });
  24901. }
  24902. }
  24903. function removeLimboTarget(syncEngineImpl, key) {
  24904. syncEngineImpl.enqueuedLimboResolutions.delete(key.path.canonicalString());
  24905. // It's possible that the target already got removed because the query failed. In that case,
  24906. // the key won't exist in `limboTargetsByKey`. Only do the cleanup if we still have the target.
  24907. const limboTargetId = syncEngineImpl.activeLimboTargetsByKey.get(key);
  24908. if (limboTargetId === null) {
  24909. // This target already got removed, because the query failed.
  24910. return;
  24911. }
  24912. remoteStoreUnlisten(syncEngineImpl.remoteStore, limboTargetId);
  24913. syncEngineImpl.activeLimboTargetsByKey =
  24914. syncEngineImpl.activeLimboTargetsByKey.remove(key);
  24915. syncEngineImpl.activeLimboResolutionsByTarget.delete(limboTargetId);
  24916. pumpEnqueuedLimboResolutions(syncEngineImpl);
  24917. }
  24918. function updateTrackedLimbos(syncEngineImpl, targetId, limboChanges) {
  24919. for (const limboChange of limboChanges) {
  24920. if (limboChange instanceof AddedLimboDocument) {
  24921. syncEngineImpl.limboDocumentRefs.addReference(limboChange.key, targetId);
  24922. trackLimboChange(syncEngineImpl, limboChange);
  24923. }
  24924. else if (limboChange instanceof RemovedLimboDocument) {
  24925. logDebug(LOG_TAG$3, 'Document no longer in limbo: ' + limboChange.key);
  24926. syncEngineImpl.limboDocumentRefs.removeReference(limboChange.key, targetId);
  24927. const isReferenced = syncEngineImpl.limboDocumentRefs.containsKey(limboChange.key);
  24928. if (!isReferenced) {
  24929. // We removed the last reference for this key
  24930. removeLimboTarget(syncEngineImpl, limboChange.key);
  24931. }
  24932. }
  24933. else {
  24934. fail();
  24935. }
  24936. }
  24937. }
  24938. function trackLimboChange(syncEngineImpl, limboChange) {
  24939. const key = limboChange.key;
  24940. const keyString = key.path.canonicalString();
  24941. if (!syncEngineImpl.activeLimboTargetsByKey.get(key) &&
  24942. !syncEngineImpl.enqueuedLimboResolutions.has(keyString)) {
  24943. logDebug(LOG_TAG$3, 'New document in limbo: ' + key);
  24944. syncEngineImpl.enqueuedLimboResolutions.add(keyString);
  24945. pumpEnqueuedLimboResolutions(syncEngineImpl);
  24946. }
  24947. }
  24948. /**
  24949. * Starts listens for documents in limbo that are enqueued for resolution,
  24950. * subject to a maximum number of concurrent resolutions.
  24951. *
  24952. * Without bounding the number of concurrent resolutions, the server can fail
  24953. * with "resource exhausted" errors which can lead to pathological client
  24954. * behavior as seen in https://github.com/firebase/firebase-js-sdk/issues/2683.
  24955. */
  24956. function pumpEnqueuedLimboResolutions(syncEngineImpl) {
  24957. while (syncEngineImpl.enqueuedLimboResolutions.size > 0 &&
  24958. syncEngineImpl.activeLimboTargetsByKey.size <
  24959. syncEngineImpl.maxConcurrentLimboResolutions) {
  24960. const keyString = syncEngineImpl.enqueuedLimboResolutions
  24961. .values()
  24962. .next().value;
  24963. syncEngineImpl.enqueuedLimboResolutions.delete(keyString);
  24964. const key = new DocumentKey(ResourcePath.fromString(keyString));
  24965. const limboTargetId = syncEngineImpl.limboTargetIdGenerator.next();
  24966. syncEngineImpl.activeLimboResolutionsByTarget.set(limboTargetId, new LimboResolution(key));
  24967. syncEngineImpl.activeLimboTargetsByKey =
  24968. syncEngineImpl.activeLimboTargetsByKey.insert(key, limboTargetId);
  24969. remoteStoreListen(syncEngineImpl.remoteStore, new TargetData(queryToTarget(newQueryForPath(key.path)), limboTargetId, "TargetPurposeLimboResolution" /* TargetPurpose.LimboResolution */, ListenSequence.INVALID));
  24970. }
  24971. }
  24972. async function syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, changes, remoteEvent) {
  24973. const syncEngineImpl = debugCast(syncEngine);
  24974. const newSnaps = [];
  24975. const docChangesInAllViews = [];
  24976. const queriesProcessed = [];
  24977. if (syncEngineImpl.queryViewsByQuery.isEmpty()) {
  24978. // Return early since `onWatchChange()` might not have been assigned yet.
  24979. return;
  24980. }
  24981. syncEngineImpl.queryViewsByQuery.forEach((_, queryView) => {
  24982. queriesProcessed.push(syncEngineImpl
  24983. .applyDocChanges(queryView, changes, remoteEvent)
  24984. .then(viewSnapshot => {
  24985. // If there are changes, or we are handling a global snapshot, notify
  24986. // secondary clients to update query state.
  24987. if (viewSnapshot || remoteEvent) {
  24988. if (syncEngineImpl.isPrimaryClient) {
  24989. syncEngineImpl.sharedClientState.updateQueryState(queryView.targetId, (viewSnapshot === null || viewSnapshot === void 0 ? void 0 : viewSnapshot.fromCache) ? 'not-current' : 'current');
  24990. }
  24991. }
  24992. // Update views if there are actual changes.
  24993. if (!!viewSnapshot) {
  24994. newSnaps.push(viewSnapshot);
  24995. const docChanges = LocalViewChanges.fromSnapshot(queryView.targetId, viewSnapshot);
  24996. docChangesInAllViews.push(docChanges);
  24997. }
  24998. }));
  24999. });
  25000. await Promise.all(queriesProcessed);
  25001. syncEngineImpl.syncEngineListener.onWatchChange(newSnaps);
  25002. await localStoreNotifyLocalViewChanges(syncEngineImpl.localStore, docChangesInAllViews);
  25003. }
  25004. async function applyDocChanges(syncEngineImpl, queryView, changes, remoteEvent) {
  25005. let viewDocChanges = queryView.view.computeDocChanges(changes);
  25006. if (viewDocChanges.needsRefill) {
  25007. // The query has a limit and some docs were removed, so we need
  25008. // to re-run the query against the local store to make sure we
  25009. // didn't lose any good docs that had been past the limit.
  25010. viewDocChanges = await localStoreExecuteQuery(syncEngineImpl.localStore, queryView.query,
  25011. /* usePreviousResults= */ false).then(({ documents }) => {
  25012. return queryView.view.computeDocChanges(documents, viewDocChanges);
  25013. });
  25014. }
  25015. const targetChange = remoteEvent && remoteEvent.targetChanges.get(queryView.targetId);
  25016. const viewChange = queryView.view.applyChanges(viewDocChanges,
  25017. /* updateLimboDocuments= */ syncEngineImpl.isPrimaryClient, targetChange);
  25018. updateTrackedLimbos(syncEngineImpl, queryView.targetId, viewChange.limboChanges);
  25019. return viewChange.snapshot;
  25020. }
  25021. async function syncEngineHandleCredentialChange(syncEngine, user) {
  25022. const syncEngineImpl = debugCast(syncEngine);
  25023. const userChanged = !syncEngineImpl.currentUser.isEqual(user);
  25024. if (userChanged) {
  25025. logDebug(LOG_TAG$3, 'User change. New user:', user.toKey());
  25026. const result = await localStoreHandleUserChange(syncEngineImpl.localStore, user);
  25027. syncEngineImpl.currentUser = user;
  25028. // Fails tasks waiting for pending writes requested by previous user.
  25029. rejectOutstandingPendingWritesCallbacks(syncEngineImpl, "'waitForPendingWrites' promise is rejected due to a user change.");
  25030. // TODO(b/114226417): Consider calling this only in the primary tab.
  25031. syncEngineImpl.sharedClientState.handleUserChange(user, result.removedBatchIds, result.addedBatchIds);
  25032. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, result.affectedDocuments);
  25033. }
  25034. }
  25035. function syncEngineGetRemoteKeysForTarget(syncEngine, targetId) {
  25036. const syncEngineImpl = debugCast(syncEngine);
  25037. const limboResolution = syncEngineImpl.activeLimboResolutionsByTarget.get(targetId);
  25038. if (limboResolution && limboResolution.receivedDocument) {
  25039. return documentKeySet().add(limboResolution.key);
  25040. }
  25041. else {
  25042. let keySet = documentKeySet();
  25043. const queries = syncEngineImpl.queriesByTarget.get(targetId);
  25044. if (!queries) {
  25045. return keySet;
  25046. }
  25047. for (const query of queries) {
  25048. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  25049. keySet = keySet.unionWith(queryView.view.syncedDocuments);
  25050. }
  25051. return keySet;
  25052. }
  25053. }
  25054. /**
  25055. * Reconcile the list of synced documents in an existing view with those
  25056. * from persistence.
  25057. */
  25058. async function synchronizeViewAndComputeSnapshot(syncEngine, queryView) {
  25059. const syncEngineImpl = debugCast(syncEngine);
  25060. const queryResult = await localStoreExecuteQuery(syncEngineImpl.localStore, queryView.query,
  25061. /* usePreviousResults= */ true);
  25062. const viewSnapshot = queryView.view.synchronizeWithPersistedState(queryResult);
  25063. if (syncEngineImpl.isPrimaryClient) {
  25064. updateTrackedLimbos(syncEngineImpl, queryView.targetId, viewSnapshot.limboChanges);
  25065. }
  25066. return viewSnapshot;
  25067. }
  25068. /**
  25069. * Retrieves newly changed documents from remote document cache and raises
  25070. * snapshots if needed.
  25071. */
  25072. // PORTING NOTE: Multi-Tab only.
  25073. async function syncEngineSynchronizeWithChangedDocuments(syncEngine, collectionGroup) {
  25074. const syncEngineImpl = debugCast(syncEngine);
  25075. return localStoreGetNewDocumentChanges(syncEngineImpl.localStore, collectionGroup).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
  25076. }
  25077. /** Applies a mutation state to an existing batch. */
  25078. // PORTING NOTE: Multi-Tab only.
  25079. async function syncEngineApplyBatchState(syncEngine, batchId, batchState, error) {
  25080. const syncEngineImpl = debugCast(syncEngine);
  25081. const documents = await localStoreLookupMutationDocuments(syncEngineImpl.localStore, batchId);
  25082. if (documents === null) {
  25083. // A throttled tab may not have seen the mutation before it was completed
  25084. // and removed from the mutation queue, in which case we won't have cached
  25085. // the affected documents. In this case we can safely ignore the update
  25086. // since that means we didn't apply the mutation locally at all (if we
  25087. // had, we would have cached the affected documents), and so we will just
  25088. // see any resulting document changes via normal remote document updates
  25089. // as applicable.
  25090. logDebug(LOG_TAG$3, 'Cannot apply mutation batch with id: ' + batchId);
  25091. return;
  25092. }
  25093. if (batchState === 'pending') {
  25094. // If we are the primary client, we need to send this write to the
  25095. // backend. Secondary clients will ignore these writes since their remote
  25096. // connection is disabled.
  25097. await fillWritePipeline(syncEngineImpl.remoteStore);
  25098. }
  25099. else if (batchState === 'acknowledged' || batchState === 'rejected') {
  25100. // NOTE: Both these methods are no-ops for batches that originated from
  25101. // other clients.
  25102. processUserCallback(syncEngineImpl, batchId, error ? error : null);
  25103. triggerPendingWritesCallbacks(syncEngineImpl, batchId);
  25104. localStoreRemoveCachedMutationBatchMetadata(syncEngineImpl.localStore, batchId);
  25105. }
  25106. else {
  25107. fail();
  25108. }
  25109. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, documents);
  25110. }
  25111. /** Applies a query target change from a different tab. */
  25112. // PORTING NOTE: Multi-Tab only.
  25113. async function syncEngineApplyPrimaryState(syncEngine, isPrimary) {
  25114. const syncEngineImpl = debugCast(syncEngine);
  25115. ensureWatchCallbacks(syncEngineImpl);
  25116. syncEngineEnsureWriteCallbacks(syncEngineImpl);
  25117. if (isPrimary === true && syncEngineImpl._isPrimaryClient !== true) {
  25118. // Secondary tabs only maintain Views for their local listeners and the
  25119. // Views internal state may not be 100% populated (in particular
  25120. // secondary tabs don't track syncedDocuments, the set of documents the
  25121. // server considers to be in the target). So when a secondary becomes
  25122. // primary, we need to need to make sure that all views for all targets
  25123. // match the state on disk.
  25124. const activeTargets = syncEngineImpl.sharedClientState.getAllActiveQueryTargets();
  25125. const activeQueries = await synchronizeQueryViewsAndRaiseSnapshots(syncEngineImpl, activeTargets.toArray());
  25126. syncEngineImpl._isPrimaryClient = true;
  25127. await remoteStoreApplyPrimaryState(syncEngineImpl.remoteStore, true);
  25128. for (const targetData of activeQueries) {
  25129. remoteStoreListen(syncEngineImpl.remoteStore, targetData);
  25130. }
  25131. }
  25132. else if (isPrimary === false && syncEngineImpl._isPrimaryClient !== false) {
  25133. const activeTargets = [];
  25134. let p = Promise.resolve();
  25135. syncEngineImpl.queriesByTarget.forEach((_, targetId) => {
  25136. if (syncEngineImpl.sharedClientState.isLocalQueryTarget(targetId)) {
  25137. activeTargets.push(targetId);
  25138. }
  25139. else {
  25140. p = p.then(() => {
  25141. removeAndCleanupTarget(syncEngineImpl, targetId);
  25142. return localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  25143. /*keepPersistedTargetData=*/ true);
  25144. });
  25145. }
  25146. remoteStoreUnlisten(syncEngineImpl.remoteStore, targetId);
  25147. });
  25148. await p;
  25149. await synchronizeQueryViewsAndRaiseSnapshots(syncEngineImpl, activeTargets);
  25150. resetLimboDocuments(syncEngineImpl);
  25151. syncEngineImpl._isPrimaryClient = false;
  25152. await remoteStoreApplyPrimaryState(syncEngineImpl.remoteStore, false);
  25153. }
  25154. }
  25155. // PORTING NOTE: Multi-Tab only.
  25156. function resetLimboDocuments(syncEngine) {
  25157. const syncEngineImpl = debugCast(syncEngine);
  25158. syncEngineImpl.activeLimboResolutionsByTarget.forEach((_, targetId) => {
  25159. remoteStoreUnlisten(syncEngineImpl.remoteStore, targetId);
  25160. });
  25161. syncEngineImpl.limboDocumentRefs.removeAllReferences();
  25162. syncEngineImpl.activeLimboResolutionsByTarget = new Map();
  25163. syncEngineImpl.activeLimboTargetsByKey = new SortedMap(DocumentKey.comparator);
  25164. }
  25165. /**
  25166. * Reconcile the query views of the provided query targets with the state from
  25167. * persistence. Raises snapshots for any changes that affect the local
  25168. * client and returns the updated state of all target's query data.
  25169. *
  25170. * @param syncEngine - The sync engine implementation
  25171. * @param targets - the list of targets with views that need to be recomputed
  25172. * @param transitionToPrimary - `true` iff the tab transitions from a secondary
  25173. * tab to a primary tab
  25174. */
  25175. // PORTING NOTE: Multi-Tab only.
  25176. async function synchronizeQueryViewsAndRaiseSnapshots(syncEngine, targets, transitionToPrimary) {
  25177. const syncEngineImpl = debugCast(syncEngine);
  25178. const activeQueries = [];
  25179. const newViewSnapshots = [];
  25180. for (const targetId of targets) {
  25181. let targetData;
  25182. const queries = syncEngineImpl.queriesByTarget.get(targetId);
  25183. if (queries && queries.length !== 0) {
  25184. // For queries that have a local View, we fetch their current state
  25185. // from LocalStore (as the resume token and the snapshot version
  25186. // might have changed) and reconcile their views with the persisted
  25187. // state (the list of syncedDocuments may have gotten out of sync).
  25188. targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, queryToTarget(queries[0]));
  25189. for (const query of queries) {
  25190. const queryView = syncEngineImpl.queryViewsByQuery.get(query);
  25191. const viewChange = await synchronizeViewAndComputeSnapshot(syncEngineImpl, queryView);
  25192. if (viewChange.snapshot) {
  25193. newViewSnapshots.push(viewChange.snapshot);
  25194. }
  25195. }
  25196. }
  25197. else {
  25198. // For queries that never executed on this client, we need to
  25199. // allocate the target in LocalStore and initialize a new View.
  25200. const target = await localStoreGetCachedTarget(syncEngineImpl.localStore, targetId);
  25201. targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, target);
  25202. await initializeViewAndComputeSnapshot(syncEngineImpl, synthesizeTargetToQuery(target), targetId,
  25203. /*current=*/ false, targetData.resumeToken);
  25204. }
  25205. activeQueries.push(targetData);
  25206. }
  25207. syncEngineImpl.syncEngineListener.onWatchChange(newViewSnapshots);
  25208. return activeQueries;
  25209. }
  25210. /**
  25211. * Creates a `Query` object from the specified `Target`. There is no way to
  25212. * obtain the original `Query`, so we synthesize a `Query` from the `Target`
  25213. * object.
  25214. *
  25215. * The synthesized result might be different from the original `Query`, but
  25216. * since the synthesized `Query` should return the same results as the
  25217. * original one (only the presentation of results might differ), the potential
  25218. * difference will not cause issues.
  25219. */
  25220. // PORTING NOTE: Multi-Tab only.
  25221. function synthesizeTargetToQuery(target) {
  25222. return newQuery(target.path, target.collectionGroup, target.orderBy, target.filters, target.limit, "F" /* LimitType.First */, target.startAt, target.endAt);
  25223. }
  25224. /** Returns the IDs of the clients that are currently active. */
  25225. // PORTING NOTE: Multi-Tab only.
  25226. function syncEngineGetActiveClients(syncEngine) {
  25227. const syncEngineImpl = debugCast(syncEngine);
  25228. return localStoreGetActiveClients(syncEngineImpl.localStore);
  25229. }
  25230. /** Applies a query target change from a different tab. */
  25231. // PORTING NOTE: Multi-Tab only.
  25232. async function syncEngineApplyTargetState(syncEngine, targetId, state, error) {
  25233. const syncEngineImpl = debugCast(syncEngine);
  25234. if (syncEngineImpl._isPrimaryClient) {
  25235. // If we receive a target state notification via WebStorage, we are
  25236. // either already secondary or another tab has taken the primary lease.
  25237. logDebug(LOG_TAG$3, 'Ignoring unexpected query state notification.');
  25238. return;
  25239. }
  25240. const query = syncEngineImpl.queriesByTarget.get(targetId);
  25241. if (query && query.length > 0) {
  25242. switch (state) {
  25243. case 'current':
  25244. case 'not-current': {
  25245. const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore, queryCollectionGroup(query[0]));
  25246. const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(targetId, state === 'current', ByteString.EMPTY_BYTE_STRING);
  25247. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, synthesizedRemoteEvent);
  25248. break;
  25249. }
  25250. case 'rejected': {
  25251. await localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  25252. /* keepPersistedTargetData */ true);
  25253. removeAndCleanupTarget(syncEngineImpl, targetId, error);
  25254. break;
  25255. }
  25256. default:
  25257. fail();
  25258. }
  25259. }
  25260. }
  25261. /** Adds or removes Watch targets for queries from different tabs. */
  25262. async function syncEngineApplyActiveTargetsChange(syncEngine, added, removed) {
  25263. const syncEngineImpl = ensureWatchCallbacks(syncEngine);
  25264. if (!syncEngineImpl._isPrimaryClient) {
  25265. return;
  25266. }
  25267. for (const targetId of added) {
  25268. if (syncEngineImpl.queriesByTarget.has(targetId)) {
  25269. // A target might have been added in a previous attempt
  25270. logDebug(LOG_TAG$3, 'Adding an already active target ' + targetId);
  25271. continue;
  25272. }
  25273. const target = await localStoreGetCachedTarget(syncEngineImpl.localStore, targetId);
  25274. const targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, target);
  25275. await initializeViewAndComputeSnapshot(syncEngineImpl, synthesizeTargetToQuery(target), targetData.targetId,
  25276. /*current=*/ false, targetData.resumeToken);
  25277. remoteStoreListen(syncEngineImpl.remoteStore, targetData);
  25278. }
  25279. for (const targetId of removed) {
  25280. // Check that the target is still active since the target might have been
  25281. // removed if it has been rejected by the backend.
  25282. if (!syncEngineImpl.queriesByTarget.has(targetId)) {
  25283. continue;
  25284. }
  25285. // Release queries that are still active.
  25286. await localStoreReleaseTarget(syncEngineImpl.localStore, targetId,
  25287. /* keepPersistedTargetData */ false)
  25288. .then(() => {
  25289. remoteStoreUnlisten(syncEngineImpl.remoteStore, targetId);
  25290. removeAndCleanupTarget(syncEngineImpl, targetId);
  25291. })
  25292. .catch(ignoreIfPrimaryLeaseLoss);
  25293. }
  25294. }
  25295. function ensureWatchCallbacks(syncEngine) {
  25296. const syncEngineImpl = debugCast(syncEngine);
  25297. syncEngineImpl.remoteStore.remoteSyncer.applyRemoteEvent =
  25298. syncEngineApplyRemoteEvent.bind(null, syncEngineImpl);
  25299. syncEngineImpl.remoteStore.remoteSyncer.getRemoteKeysForTarget =
  25300. syncEngineGetRemoteKeysForTarget.bind(null, syncEngineImpl);
  25301. syncEngineImpl.remoteStore.remoteSyncer.rejectListen =
  25302. syncEngineRejectListen.bind(null, syncEngineImpl);
  25303. syncEngineImpl.syncEngineListener.onWatchChange =
  25304. eventManagerOnWatchChange.bind(null, syncEngineImpl.eventManager);
  25305. syncEngineImpl.syncEngineListener.onWatchError =
  25306. eventManagerOnWatchError.bind(null, syncEngineImpl.eventManager);
  25307. return syncEngineImpl;
  25308. }
  25309. function syncEngineEnsureWriteCallbacks(syncEngine) {
  25310. const syncEngineImpl = debugCast(syncEngine);
  25311. syncEngineImpl.remoteStore.remoteSyncer.applySuccessfulWrite =
  25312. syncEngineApplySuccessfulWrite.bind(null, syncEngineImpl);
  25313. syncEngineImpl.remoteStore.remoteSyncer.rejectFailedWrite =
  25314. syncEngineRejectFailedWrite.bind(null, syncEngineImpl);
  25315. return syncEngineImpl;
  25316. }
  25317. /**
  25318. * Loads a Firestore bundle into the SDK. The returned promise resolves when
  25319. * the bundle finished loading.
  25320. *
  25321. * @param syncEngine - SyncEngine to use.
  25322. * @param bundleReader - Bundle to load into the SDK.
  25323. * @param task - LoadBundleTask used to update the loading progress to public API.
  25324. */
  25325. function syncEngineLoadBundle(syncEngine, bundleReader, task) {
  25326. const syncEngineImpl = debugCast(syncEngine);
  25327. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  25328. loadBundleImpl(syncEngineImpl, bundleReader, task).then(collectionGroups => {
  25329. syncEngineImpl.sharedClientState.notifyBundleLoaded(collectionGroups);
  25330. });
  25331. }
  25332. /** Loads a bundle and returns the list of affected collection groups. */
  25333. async function loadBundleImpl(syncEngine, reader, task) {
  25334. try {
  25335. const metadata = await reader.getMetadata();
  25336. const skip = await localStoreHasNewerBundle(syncEngine.localStore, metadata);
  25337. if (skip) {
  25338. await reader.close();
  25339. task._completeWith(bundleSuccessProgress(metadata));
  25340. return Promise.resolve(new Set());
  25341. }
  25342. task._updateProgress(bundleInitialProgress(metadata));
  25343. const loader = new BundleLoader(metadata, syncEngine.localStore, reader.serializer);
  25344. let element = await reader.nextElement();
  25345. while (element) {
  25346. ;
  25347. const progress = await loader.addSizedElement(element);
  25348. if (progress) {
  25349. task._updateProgress(progress);
  25350. }
  25351. element = await reader.nextElement();
  25352. }
  25353. const result = await loader.complete();
  25354. await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, result.changedDocs,
  25355. /* remoteEvent */ undefined);
  25356. // Save metadata, so loading the same bundle will skip.
  25357. await localStoreSaveBundle(syncEngine.localStore, metadata);
  25358. task._completeWith(result.progress);
  25359. return Promise.resolve(result.changedCollectionGroups);
  25360. }
  25361. catch (e) {
  25362. logWarn(LOG_TAG$3, `Loading bundle failed with ${e}`);
  25363. task._failWith(e);
  25364. return Promise.resolve(new Set());
  25365. }
  25366. }
  25367. /**
  25368. * @license
  25369. * Copyright 2020 Google LLC
  25370. *
  25371. * Licensed under the Apache License, Version 2.0 (the "License");
  25372. * you may not use this file except in compliance with the License.
  25373. * You may obtain a copy of the License at
  25374. *
  25375. * http://www.apache.org/licenses/LICENSE-2.0
  25376. *
  25377. * Unless required by applicable law or agreed to in writing, software
  25378. * distributed under the License is distributed on an "AS IS" BASIS,
  25379. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25380. * See the License for the specific language governing permissions and
  25381. * limitations under the License.
  25382. */
  25383. /**
  25384. * Provides all components needed for Firestore with in-memory persistence.
  25385. * Uses EagerGC garbage collection.
  25386. */
  25387. class MemoryOfflineComponentProvider {
  25388. constructor() {
  25389. this.synchronizeTabs = false;
  25390. }
  25391. async initialize(cfg) {
  25392. this.serializer = newSerializer(cfg.databaseInfo.databaseId);
  25393. this.sharedClientState = this.createSharedClientState(cfg);
  25394. this.persistence = this.createPersistence(cfg);
  25395. await this.persistence.start();
  25396. this.localStore = this.createLocalStore(cfg);
  25397. this.gcScheduler = this.createGarbageCollectionScheduler(cfg, this.localStore);
  25398. this.indexBackfillerScheduler = this.createIndexBackfillerScheduler(cfg, this.localStore);
  25399. }
  25400. createGarbageCollectionScheduler(cfg, localStore) {
  25401. return null;
  25402. }
  25403. createIndexBackfillerScheduler(cfg, localStore) {
  25404. return null;
  25405. }
  25406. createLocalStore(cfg) {
  25407. return newLocalStore(this.persistence, new QueryEngine(), cfg.initialUser, this.serializer);
  25408. }
  25409. createPersistence(cfg) {
  25410. return new MemoryPersistence(MemoryEagerDelegate.factory, this.serializer);
  25411. }
  25412. createSharedClientState(cfg) {
  25413. return new MemorySharedClientState();
  25414. }
  25415. async terminate() {
  25416. if (this.gcScheduler) {
  25417. this.gcScheduler.stop();
  25418. }
  25419. await this.sharedClientState.shutdown();
  25420. await this.persistence.shutdown();
  25421. }
  25422. }
  25423. class LruGcMemoryOfflineComponentProvider extends MemoryOfflineComponentProvider {
  25424. constructor(cacheSizeBytes) {
  25425. super();
  25426. this.cacheSizeBytes = cacheSizeBytes;
  25427. }
  25428. createGarbageCollectionScheduler(cfg, localStore) {
  25429. hardAssert(this.persistence.referenceDelegate instanceof MemoryLruDelegate);
  25430. const garbageCollector = this.persistence.referenceDelegate.garbageCollector;
  25431. return new LruScheduler(garbageCollector, cfg.asyncQueue, localStore);
  25432. }
  25433. createPersistence(cfg) {
  25434. const lruParams = this.cacheSizeBytes !== undefined
  25435. ? LruParams.withCacheSize(this.cacheSizeBytes)
  25436. : LruParams.DEFAULT;
  25437. return new MemoryPersistence(p => MemoryLruDelegate.factory(p, lruParams), this.serializer);
  25438. }
  25439. }
  25440. /**
  25441. * Provides all components needed for Firestore with IndexedDB persistence.
  25442. */
  25443. class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
  25444. constructor(onlineComponentProvider, cacheSizeBytes, forceOwnership) {
  25445. super();
  25446. this.onlineComponentProvider = onlineComponentProvider;
  25447. this.cacheSizeBytes = cacheSizeBytes;
  25448. this.forceOwnership = forceOwnership;
  25449. this.synchronizeTabs = false;
  25450. }
  25451. async initialize(cfg) {
  25452. await super.initialize(cfg);
  25453. await this.onlineComponentProvider.initialize(this, cfg);
  25454. // Enqueue writes from a previous session
  25455. await syncEngineEnsureWriteCallbacks(this.onlineComponentProvider.syncEngine);
  25456. await fillWritePipeline(this.onlineComponentProvider.remoteStore);
  25457. // NOTE: This will immediately call the listener, so we make sure to
  25458. // set it after localStore / remoteStore are started.
  25459. await this.persistence.setPrimaryStateListener(() => {
  25460. if (this.gcScheduler && !this.gcScheduler.started) {
  25461. this.gcScheduler.start();
  25462. }
  25463. if (this.indexBackfillerScheduler &&
  25464. !this.indexBackfillerScheduler.started) {
  25465. this.indexBackfillerScheduler.start();
  25466. }
  25467. return Promise.resolve();
  25468. });
  25469. }
  25470. createLocalStore(cfg) {
  25471. return newLocalStore(this.persistence, new QueryEngine(), cfg.initialUser, this.serializer);
  25472. }
  25473. createGarbageCollectionScheduler(cfg, localStore) {
  25474. const garbageCollector = this.persistence.referenceDelegate.garbageCollector;
  25475. return new LruScheduler(garbageCollector, cfg.asyncQueue, localStore);
  25476. }
  25477. createIndexBackfillerScheduler(cfg, localStore) {
  25478. const indexBackfiller = new IndexBackfiller(localStore, this.persistence);
  25479. return new IndexBackfillerScheduler(cfg.asyncQueue, indexBackfiller);
  25480. }
  25481. createPersistence(cfg) {
  25482. const persistenceKey = indexedDbStoragePrefix(cfg.databaseInfo.databaseId, cfg.databaseInfo.persistenceKey);
  25483. const lruParams = this.cacheSizeBytes !== undefined
  25484. ? LruParams.withCacheSize(this.cacheSizeBytes)
  25485. : LruParams.DEFAULT;
  25486. return new IndexedDbPersistence(this.synchronizeTabs, persistenceKey, cfg.clientId, lruParams, cfg.asyncQueue, getWindow(), getDocument(), this.serializer, this.sharedClientState, !!this.forceOwnership);
  25487. }
  25488. createSharedClientState(cfg) {
  25489. return new MemorySharedClientState();
  25490. }
  25491. }
  25492. /**
  25493. * Provides all components needed for Firestore with multi-tab IndexedDB
  25494. * persistence.
  25495. *
  25496. * In the legacy client, this provider is used to provide both multi-tab and
  25497. * non-multi-tab persistence since we cannot tell at build time whether
  25498. * `synchronizeTabs` will be enabled.
  25499. */
  25500. class MultiTabOfflineComponentProvider extends IndexedDbOfflineComponentProvider {
  25501. constructor(onlineComponentProvider, cacheSizeBytes) {
  25502. super(onlineComponentProvider, cacheSizeBytes, /* forceOwnership= */ false);
  25503. this.onlineComponentProvider = onlineComponentProvider;
  25504. this.cacheSizeBytes = cacheSizeBytes;
  25505. this.synchronizeTabs = true;
  25506. }
  25507. async initialize(cfg) {
  25508. await super.initialize(cfg);
  25509. const syncEngine = this.onlineComponentProvider.syncEngine;
  25510. if (this.sharedClientState instanceof WebStorageSharedClientState) {
  25511. this.sharedClientState.syncEngine = {
  25512. applyBatchState: syncEngineApplyBatchState.bind(null, syncEngine),
  25513. applyTargetState: syncEngineApplyTargetState.bind(null, syncEngine),
  25514. applyActiveTargetsChange: syncEngineApplyActiveTargetsChange.bind(null, syncEngine),
  25515. getActiveClients: syncEngineGetActiveClients.bind(null, syncEngine),
  25516. synchronizeWithChangedDocuments: syncEngineSynchronizeWithChangedDocuments.bind(null, syncEngine)
  25517. };
  25518. await this.sharedClientState.start();
  25519. }
  25520. // NOTE: This will immediately call the listener, so we make sure to
  25521. // set it after localStore / remoteStore are started.
  25522. await this.persistence.setPrimaryStateListener(async (isPrimary) => {
  25523. await syncEngineApplyPrimaryState(this.onlineComponentProvider.syncEngine, isPrimary);
  25524. if (this.gcScheduler) {
  25525. if (isPrimary && !this.gcScheduler.started) {
  25526. this.gcScheduler.start();
  25527. }
  25528. else if (!isPrimary) {
  25529. this.gcScheduler.stop();
  25530. }
  25531. }
  25532. if (this.indexBackfillerScheduler) {
  25533. if (isPrimary && !this.indexBackfillerScheduler.started) {
  25534. this.indexBackfillerScheduler.start();
  25535. }
  25536. else if (!isPrimary) {
  25537. this.indexBackfillerScheduler.stop();
  25538. }
  25539. }
  25540. });
  25541. }
  25542. createSharedClientState(cfg) {
  25543. const window = getWindow();
  25544. if (!WebStorageSharedClientState.isAvailable(window)) {
  25545. throw new FirestoreError(Code.UNIMPLEMENTED, 'IndexedDB persistence is only available on platforms that support LocalStorage.');
  25546. }
  25547. const persistenceKey = indexedDbStoragePrefix(cfg.databaseInfo.databaseId, cfg.databaseInfo.persistenceKey);
  25548. return new WebStorageSharedClientState(window, cfg.asyncQueue, persistenceKey, cfg.clientId, cfg.initialUser);
  25549. }
  25550. }
  25551. /**
  25552. * Initializes and wires the components that are needed to interface with the
  25553. * network.
  25554. */
  25555. class OnlineComponentProvider {
  25556. async initialize(offlineComponentProvider, cfg) {
  25557. if (this.localStore) {
  25558. // OnlineComponentProvider may get initialized multiple times if
  25559. // multi-tab persistence is used.
  25560. return;
  25561. }
  25562. this.localStore = offlineComponentProvider.localStore;
  25563. this.sharedClientState = offlineComponentProvider.sharedClientState;
  25564. this.datastore = this.createDatastore(cfg);
  25565. this.remoteStore = this.createRemoteStore(cfg);
  25566. this.eventManager = this.createEventManager(cfg);
  25567. this.syncEngine = this.createSyncEngine(cfg,
  25568. /* startAsPrimary=*/ !offlineComponentProvider.synchronizeTabs);
  25569. this.sharedClientState.onlineStateHandler = onlineState => syncEngineApplyOnlineStateChange(this.syncEngine, onlineState, 1 /* OnlineStateSource.SharedClientState */);
  25570. this.remoteStore.remoteSyncer.handleCredentialChange =
  25571. syncEngineHandleCredentialChange.bind(null, this.syncEngine);
  25572. await remoteStoreApplyPrimaryState(this.remoteStore, this.syncEngine.isPrimaryClient);
  25573. }
  25574. createEventManager(cfg) {
  25575. return newEventManager();
  25576. }
  25577. createDatastore(cfg) {
  25578. const serializer = newSerializer(cfg.databaseInfo.databaseId);
  25579. const connection = newConnection(cfg.databaseInfo);
  25580. return newDatastore(cfg.authCredentials, cfg.appCheckCredentials, connection, serializer);
  25581. }
  25582. createRemoteStore(cfg) {
  25583. return newRemoteStore(this.localStore, this.datastore, cfg.asyncQueue, onlineState => syncEngineApplyOnlineStateChange(this.syncEngine, onlineState, 0 /* OnlineStateSource.RemoteStore */), newConnectivityMonitor());
  25584. }
  25585. createSyncEngine(cfg, startAsPrimary) {
  25586. return newSyncEngine(this.localStore, this.remoteStore, this.eventManager, this.sharedClientState, cfg.initialUser, cfg.maxConcurrentLimboResolutions, startAsPrimary);
  25587. }
  25588. terminate() {
  25589. return remoteStoreShutdown(this.remoteStore);
  25590. }
  25591. }
  25592. /**
  25593. * @license
  25594. * Copyright 2020 Google LLC
  25595. *
  25596. * Licensed under the Apache License, Version 2.0 (the "License");
  25597. * you may not use this file except in compliance with the License.
  25598. * You may obtain a copy of the License at
  25599. *
  25600. * http://www.apache.org/licenses/LICENSE-2.0
  25601. *
  25602. * Unless required by applicable law or agreed to in writing, software
  25603. * distributed under the License is distributed on an "AS IS" BASIS,
  25604. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25605. * See the License for the specific language governing permissions and
  25606. * limitations under the License.
  25607. */
  25608. /**
  25609. * How many bytes to read each time when `ReadableStreamReader.read()` is
  25610. * called. Only applicable for byte streams that we control (e.g. those backed
  25611. * by an UInt8Array).
  25612. */
  25613. const DEFAULT_BYTES_PER_READ = 10240;
  25614. /**
  25615. * Builds a `ByteStreamReader` from a UInt8Array.
  25616. * @param source - The data source to use.
  25617. * @param bytesPerRead - How many bytes each `read()` from the returned reader
  25618. * will read.
  25619. */
  25620. function toByteStreamReaderHelper(source, bytesPerRead = DEFAULT_BYTES_PER_READ) {
  25621. let readFrom = 0;
  25622. // The TypeScript definition for ReadableStreamReader changed. We use
  25623. // `any` here to allow this code to compile with different versions.
  25624. // See https://github.com/microsoft/TypeScript/issues/42970
  25625. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25626. const reader = {
  25627. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25628. async read() {
  25629. if (readFrom < source.byteLength) {
  25630. const result = {
  25631. value: source.slice(readFrom, readFrom + bytesPerRead),
  25632. done: false
  25633. };
  25634. readFrom += bytesPerRead;
  25635. return result;
  25636. }
  25637. return { done: true };
  25638. },
  25639. async cancel() { },
  25640. releaseLock() { },
  25641. closed: Promise.resolve()
  25642. };
  25643. return reader;
  25644. }
  25645. /**
  25646. * @license
  25647. * Copyright 2017 Google LLC
  25648. *
  25649. * Licensed under the Apache License, Version 2.0 (the "License");
  25650. * you may not use this file except in compliance with the License.
  25651. * You may obtain a copy of the License at
  25652. *
  25653. * http://www.apache.org/licenses/LICENSE-2.0
  25654. *
  25655. * Unless required by applicable law or agreed to in writing, software
  25656. * distributed under the License is distributed on an "AS IS" BASIS,
  25657. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25658. * See the License for the specific language governing permissions and
  25659. * limitations under the License.
  25660. */
  25661. function validateNonEmptyArgument(functionName, argumentName, argument) {
  25662. if (!argument) {
  25663. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() cannot be called with an empty ${argumentName}.`);
  25664. }
  25665. }
  25666. /**
  25667. * Validates that two boolean options are not set at the same time.
  25668. * @internal
  25669. */
  25670. function validateIsNotUsedTogether(optionName1, argument1, optionName2, argument2) {
  25671. if (argument1 === true && argument2 === true) {
  25672. throw new FirestoreError(Code.INVALID_ARGUMENT, `${optionName1} and ${optionName2} cannot be used together.`);
  25673. }
  25674. }
  25675. /**
  25676. * Validates that `path` refers to a document (indicated by the fact it contains
  25677. * an even numbers of segments).
  25678. */
  25679. function validateDocumentPath(path) {
  25680. if (!DocumentKey.isDocumentKey(path)) {
  25681. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid document reference. Document references must have an even number of segments, but ${path} has ${path.length}.`);
  25682. }
  25683. }
  25684. /**
  25685. * Validates that `path` refers to a collection (indicated by the fact it
  25686. * contains an odd numbers of segments).
  25687. */
  25688. function validateCollectionPath(path) {
  25689. if (DocumentKey.isDocumentKey(path)) {
  25690. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid collection reference. Collection references must have an odd number of segments, but ${path} has ${path.length}.`);
  25691. }
  25692. }
  25693. /**
  25694. * Returns true if it's a non-null object without a custom prototype
  25695. * (i.e. excludes Array, Date, etc.).
  25696. */
  25697. function isPlainObject(input) {
  25698. return (typeof input === 'object' &&
  25699. input !== null &&
  25700. (Object.getPrototypeOf(input) === Object.prototype ||
  25701. Object.getPrototypeOf(input) === null));
  25702. }
  25703. /** Returns a string describing the type / value of the provided input. */
  25704. function valueDescription(input) {
  25705. if (input === undefined) {
  25706. return 'undefined';
  25707. }
  25708. else if (input === null) {
  25709. return 'null';
  25710. }
  25711. else if (typeof input === 'string') {
  25712. if (input.length > 20) {
  25713. input = `${input.substring(0, 20)}...`;
  25714. }
  25715. return JSON.stringify(input);
  25716. }
  25717. else if (typeof input === 'number' || typeof input === 'boolean') {
  25718. return '' + input;
  25719. }
  25720. else if (typeof input === 'object') {
  25721. if (input instanceof Array) {
  25722. return 'an array';
  25723. }
  25724. else {
  25725. const customObjectName = tryGetCustomObjectType(input);
  25726. if (customObjectName) {
  25727. return `a custom ${customObjectName} object`;
  25728. }
  25729. else {
  25730. return 'an object';
  25731. }
  25732. }
  25733. }
  25734. else if (typeof input === 'function') {
  25735. return 'a function';
  25736. }
  25737. else {
  25738. return fail();
  25739. }
  25740. }
  25741. /** try to get the constructor name for an object. */
  25742. function tryGetCustomObjectType(input) {
  25743. if (input.constructor) {
  25744. return input.constructor.name;
  25745. }
  25746. return null;
  25747. }
  25748. /**
  25749. * Casts `obj` to `T`, optionally unwrapping Compat types to expose the
  25750. * underlying instance. Throws if `obj` is not an instance of `T`.
  25751. *
  25752. * This cast is used in the Lite and Full SDK to verify instance types for
  25753. * arguments passed to the public API.
  25754. * @internal
  25755. */
  25756. function cast(obj,
  25757. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25758. constructor) {
  25759. if ('_delegate' in obj) {
  25760. // Unwrap Compat types
  25761. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25762. obj = obj._delegate;
  25763. }
  25764. if (!(obj instanceof constructor)) {
  25765. if (constructor.name === obj.constructor.name) {
  25766. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Type does not match the expected instance. Did you pass a ' +
  25767. `reference from a different Firestore SDK?`);
  25768. }
  25769. else {
  25770. const description = valueDescription(obj);
  25771. throw new FirestoreError(Code.INVALID_ARGUMENT, `Expected type '${constructor.name}', but it was: ${description}`);
  25772. }
  25773. }
  25774. return obj;
  25775. }
  25776. function validatePositiveNumber(functionName, n) {
  25777. if (n <= 0) {
  25778. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires a positive number, but it was: ${n}.`);
  25779. }
  25780. }
  25781. /**
  25782. * @license
  25783. * Copyright 2020 Google LLC
  25784. *
  25785. * Licensed under the Apache License, Version 2.0 (the "License");
  25786. * you may not use this file except in compliance with the License.
  25787. * You may obtain a copy of the License at
  25788. *
  25789. * http://www.apache.org/licenses/LICENSE-2.0
  25790. *
  25791. * Unless required by applicable law or agreed to in writing, software
  25792. * distributed under the License is distributed on an "AS IS" BASIS,
  25793. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25794. * See the License for the specific language governing permissions and
  25795. * limitations under the License.
  25796. */
  25797. /**
  25798. * On Node, only supported data source is a `Uint8Array` for now.
  25799. */
  25800. function toByteStreamReader(source, bytesPerRead) {
  25801. if (!(source instanceof Uint8Array)) {
  25802. throw new FirestoreError(Code.INVALID_ARGUMENT, `NodePlatform.toByteStreamReader expects source to be Uint8Array, got ${valueDescription(source)}`);
  25803. }
  25804. return toByteStreamReaderHelper(source, bytesPerRead);
  25805. }
  25806. /**
  25807. * @license
  25808. * Copyright 2017 Google LLC
  25809. *
  25810. * Licensed under the Apache License, Version 2.0 (the "License");
  25811. * you may not use this file except in compliance with the License.
  25812. * You may obtain a copy of the License at
  25813. *
  25814. * http://www.apache.org/licenses/LICENSE-2.0
  25815. *
  25816. * Unless required by applicable law or agreed to in writing, software
  25817. * distributed under the License is distributed on an "AS IS" BASIS,
  25818. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25819. * See the License for the specific language governing permissions and
  25820. * limitations under the License.
  25821. */
  25822. /*
  25823. * A wrapper implementation of Observer<T> that will dispatch events
  25824. * asynchronously. To allow immediate silencing, a mute call is added which
  25825. * causes events scheduled to no longer be raised.
  25826. */
  25827. class AsyncObserver {
  25828. constructor(observer) {
  25829. this.observer = observer;
  25830. /**
  25831. * When set to true, will not raise future events. Necessary to deal with
  25832. * async detachment of listener.
  25833. */
  25834. this.muted = false;
  25835. }
  25836. next(value) {
  25837. if (this.observer.next) {
  25838. this.scheduleEvent(this.observer.next, value);
  25839. }
  25840. }
  25841. error(error) {
  25842. if (this.observer.error) {
  25843. this.scheduleEvent(this.observer.error, error);
  25844. }
  25845. else {
  25846. logError('Uncaught Error in snapshot listener:', error.toString());
  25847. }
  25848. }
  25849. mute() {
  25850. this.muted = true;
  25851. }
  25852. scheduleEvent(eventHandler, event) {
  25853. if (!this.muted) {
  25854. setTimeout(() => {
  25855. if (!this.muted) {
  25856. eventHandler(event);
  25857. }
  25858. }, 0);
  25859. }
  25860. }
  25861. }
  25862. /**
  25863. * @license
  25864. * Copyright 2020 Google LLC
  25865. *
  25866. * Licensed under the Apache License, Version 2.0 (the "License");
  25867. * you may not use this file except in compliance with the License.
  25868. * You may obtain a copy of the License at
  25869. *
  25870. * http://www.apache.org/licenses/LICENSE-2.0
  25871. *
  25872. * Unless required by applicable law or agreed to in writing, software
  25873. * distributed under the License is distributed on an "AS IS" BASIS,
  25874. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25875. * See the License for the specific language governing permissions and
  25876. * limitations under the License.
  25877. */
  25878. /**
  25879. * A complete element in the bundle stream, together with the byte length it
  25880. * occupies in the stream.
  25881. */
  25882. class SizedBundleElement {
  25883. constructor(payload,
  25884. // How many bytes this element takes to store in the bundle.
  25885. byteLength) {
  25886. this.payload = payload;
  25887. this.byteLength = byteLength;
  25888. }
  25889. isBundleMetadata() {
  25890. return 'metadata' in this.payload;
  25891. }
  25892. }
  25893. /**
  25894. * @license
  25895. * Copyright 2020 Google LLC
  25896. *
  25897. * Licensed under the Apache License, Version 2.0 (the "License");
  25898. * you may not use this file except in compliance with the License.
  25899. * You may obtain a copy of the License at
  25900. *
  25901. * http://www.apache.org/licenses/LICENSE-2.0
  25902. *
  25903. * Unless required by applicable law or agreed to in writing, software
  25904. * distributed under the License is distributed on an "AS IS" BASIS,
  25905. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25906. * See the License for the specific language governing permissions and
  25907. * limitations under the License.
  25908. */
  25909. /**
  25910. * A class representing a bundle.
  25911. *
  25912. * Takes a bundle stream or buffer, and presents abstractions to read bundled
  25913. * elements out of the underlying content.
  25914. */
  25915. class BundleReaderImpl {
  25916. constructor(
  25917. /** The reader to read from underlying binary bundle data source. */
  25918. reader, serializer) {
  25919. this.reader = reader;
  25920. this.serializer = serializer;
  25921. /** Cached bundle metadata. */
  25922. this.metadata = new Deferred();
  25923. /**
  25924. * Internal buffer to hold bundle content, accumulating incomplete element
  25925. * content.
  25926. */
  25927. this.buffer = new Uint8Array();
  25928. this.textDecoder = newTextDecoder();
  25929. // Read the metadata (which is the first element).
  25930. this.nextElementImpl().then(element => {
  25931. if (element && element.isBundleMetadata()) {
  25932. this.metadata.resolve(element.payload.metadata);
  25933. }
  25934. else {
  25935. this.metadata.reject(new Error(`The first element of the bundle is not a metadata, it is
  25936. ${JSON.stringify(element === null || element === void 0 ? void 0 : element.payload)}`));
  25937. }
  25938. }, error => this.metadata.reject(error));
  25939. }
  25940. close() {
  25941. return this.reader.cancel();
  25942. }
  25943. async getMetadata() {
  25944. return this.metadata.promise;
  25945. }
  25946. async nextElement() {
  25947. // Makes sure metadata is read before proceeding.
  25948. await this.getMetadata();
  25949. return this.nextElementImpl();
  25950. }
  25951. /**
  25952. * Reads from the head of internal buffer, and pulling more data from
  25953. * underlying stream if a complete element cannot be found, until an
  25954. * element(including the prefixed length and the JSON string) is found.
  25955. *
  25956. * Once a complete element is read, it is dropped from internal buffer.
  25957. *
  25958. * Returns either the bundled element, or null if we have reached the end of
  25959. * the stream.
  25960. */
  25961. async nextElementImpl() {
  25962. const lengthBuffer = await this.readLength();
  25963. if (lengthBuffer === null) {
  25964. return null;
  25965. }
  25966. const lengthString = this.textDecoder.decode(lengthBuffer);
  25967. const length = Number(lengthString);
  25968. if (isNaN(length)) {
  25969. this.raiseError(`length string (${lengthString}) is not valid number`);
  25970. }
  25971. const jsonString = await this.readJsonString(length);
  25972. return new SizedBundleElement(JSON.parse(jsonString), lengthBuffer.length + length);
  25973. }
  25974. /** First index of '{' from the underlying buffer. */
  25975. indexOfOpenBracket() {
  25976. return this.buffer.findIndex(v => v === '{'.charCodeAt(0));
  25977. }
  25978. /**
  25979. * Reads from the beginning of the internal buffer, until the first '{', and
  25980. * return the content.
  25981. *
  25982. * If reached end of the stream, returns a null.
  25983. */
  25984. async readLength() {
  25985. while (this.indexOfOpenBracket() < 0) {
  25986. const done = await this.pullMoreDataToBuffer();
  25987. if (done) {
  25988. break;
  25989. }
  25990. }
  25991. // Broke out of the loop because underlying stream is closed, and there
  25992. // happens to be no more data to process.
  25993. if (this.buffer.length === 0) {
  25994. return null;
  25995. }
  25996. const position = this.indexOfOpenBracket();
  25997. // Broke out of the loop because underlying stream is closed, but still
  25998. // cannot find an open bracket.
  25999. if (position < 0) {
  26000. this.raiseError('Reached the end of bundle when a length string is expected.');
  26001. }
  26002. const result = this.buffer.slice(0, position);
  26003. // Update the internal buffer to drop the read length.
  26004. this.buffer = this.buffer.slice(position);
  26005. return result;
  26006. }
  26007. /**
  26008. * Reads from a specified position from the internal buffer, for a specified
  26009. * number of bytes, pulling more data from the underlying stream if needed.
  26010. *
  26011. * Returns a string decoded from the read bytes.
  26012. */
  26013. async readJsonString(length) {
  26014. while (this.buffer.length < length) {
  26015. const done = await this.pullMoreDataToBuffer();
  26016. if (done) {
  26017. this.raiseError('Reached the end of bundle when more is expected.');
  26018. }
  26019. }
  26020. const result = this.textDecoder.decode(this.buffer.slice(0, length));
  26021. // Update the internal buffer to drop the read json string.
  26022. this.buffer = this.buffer.slice(length);
  26023. return result;
  26024. }
  26025. raiseError(message) {
  26026. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  26027. this.reader.cancel();
  26028. throw new Error(`Invalid bundle format: ${message}`);
  26029. }
  26030. /**
  26031. * Pulls more data from underlying stream to internal buffer.
  26032. * Returns a boolean indicating whether the stream is finished.
  26033. */
  26034. async pullMoreDataToBuffer() {
  26035. const result = await this.reader.read();
  26036. if (!result.done) {
  26037. const newBuffer = new Uint8Array(this.buffer.length + result.value.length);
  26038. newBuffer.set(this.buffer);
  26039. newBuffer.set(result.value, this.buffer.length);
  26040. this.buffer = newBuffer;
  26041. }
  26042. return result.done;
  26043. }
  26044. }
  26045. function newBundleReader(reader, serializer) {
  26046. return new BundleReaderImpl(reader, serializer);
  26047. }
  26048. /**
  26049. * @license
  26050. * Copyright 2017 Google LLC
  26051. *
  26052. * Licensed under the Apache License, Version 2.0 (the "License");
  26053. * you may not use this file except in compliance with the License.
  26054. * You may obtain a copy of the License at
  26055. *
  26056. * http://www.apache.org/licenses/LICENSE-2.0
  26057. *
  26058. * Unless required by applicable law or agreed to in writing, software
  26059. * distributed under the License is distributed on an "AS IS" BASIS,
  26060. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26061. * See the License for the specific language governing permissions and
  26062. * limitations under the License.
  26063. */
  26064. /**
  26065. * Internal transaction object responsible for accumulating the mutations to
  26066. * perform and the base versions for any documents read.
  26067. */
  26068. class Transaction$2 {
  26069. constructor(datastore) {
  26070. this.datastore = datastore;
  26071. // The version of each document that was read during this transaction.
  26072. this.readVersions = new Map();
  26073. this.mutations = [];
  26074. this.committed = false;
  26075. /**
  26076. * A deferred usage error that occurred previously in this transaction that
  26077. * will cause the transaction to fail once it actually commits.
  26078. */
  26079. this.lastWriteError = null;
  26080. /**
  26081. * Set of documents that have been written in the transaction.
  26082. *
  26083. * When there's more than one write to the same key in a transaction, any
  26084. * writes after the first are handled differently.
  26085. */
  26086. this.writtenDocs = new Set();
  26087. }
  26088. async lookup(keys) {
  26089. this.ensureCommitNotCalled();
  26090. if (this.mutations.length > 0) {
  26091. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Firestore transactions require all reads to be executed before all writes.');
  26092. }
  26093. const docs = await invokeBatchGetDocumentsRpc(this.datastore, keys);
  26094. docs.forEach(doc => this.recordVersion(doc));
  26095. return docs;
  26096. }
  26097. set(key, data) {
  26098. this.write(data.toMutation(key, this.precondition(key)));
  26099. this.writtenDocs.add(key.toString());
  26100. }
  26101. update(key, data) {
  26102. try {
  26103. this.write(data.toMutation(key, this.preconditionForUpdate(key)));
  26104. }
  26105. catch (e) {
  26106. this.lastWriteError = e;
  26107. }
  26108. this.writtenDocs.add(key.toString());
  26109. }
  26110. delete(key) {
  26111. this.write(new DeleteMutation(key, this.precondition(key)));
  26112. this.writtenDocs.add(key.toString());
  26113. }
  26114. async commit() {
  26115. this.ensureCommitNotCalled();
  26116. if (this.lastWriteError) {
  26117. throw this.lastWriteError;
  26118. }
  26119. const unwritten = this.readVersions;
  26120. // For each mutation, note that the doc was written.
  26121. this.mutations.forEach(mutation => {
  26122. unwritten.delete(mutation.key.toString());
  26123. });
  26124. // For each document that was read but not written to, we want to perform
  26125. // a `verify` operation.
  26126. unwritten.forEach((_, path) => {
  26127. const key = DocumentKey.fromPath(path);
  26128. this.mutations.push(new VerifyMutation(key, this.precondition(key)));
  26129. });
  26130. await invokeCommitRpc(this.datastore, this.mutations);
  26131. this.committed = true;
  26132. }
  26133. recordVersion(doc) {
  26134. let docVersion;
  26135. if (doc.isFoundDocument()) {
  26136. docVersion = doc.version;
  26137. }
  26138. else if (doc.isNoDocument()) {
  26139. // Represent a deleted doc using SnapshotVersion.min().
  26140. docVersion = SnapshotVersion.min();
  26141. }
  26142. else {
  26143. throw fail();
  26144. }
  26145. const existingVersion = this.readVersions.get(doc.key.toString());
  26146. if (existingVersion) {
  26147. if (!docVersion.isEqual(existingVersion)) {
  26148. // This transaction will fail no matter what.
  26149. throw new FirestoreError(Code.ABORTED, 'Document version changed between two reads.');
  26150. }
  26151. }
  26152. else {
  26153. this.readVersions.set(doc.key.toString(), docVersion);
  26154. }
  26155. }
  26156. /**
  26157. * Returns the version of this document when it was read in this transaction,
  26158. * as a precondition, or no precondition if it was not read.
  26159. */
  26160. precondition(key) {
  26161. const version = this.readVersions.get(key.toString());
  26162. if (!this.writtenDocs.has(key.toString()) && version) {
  26163. if (version.isEqual(SnapshotVersion.min())) {
  26164. return Precondition.exists(false);
  26165. }
  26166. else {
  26167. return Precondition.updateTime(version);
  26168. }
  26169. }
  26170. else {
  26171. return Precondition.none();
  26172. }
  26173. }
  26174. /**
  26175. * Returns the precondition for a document if the operation is an update.
  26176. */
  26177. preconditionForUpdate(key) {
  26178. const version = this.readVersions.get(key.toString());
  26179. // The first time a document is written, we want to take into account the
  26180. // read time and existence
  26181. if (!this.writtenDocs.has(key.toString()) && version) {
  26182. if (version.isEqual(SnapshotVersion.min())) {
  26183. // The document doesn't exist, so fail the transaction.
  26184. // This has to be validated locally because you can't send a
  26185. // precondition that a document does not exist without changing the
  26186. // semantics of the backend write to be an insert. This is the reverse
  26187. // of what we want, since we want to assert that the document doesn't
  26188. // exist but then send the update and have it fail. Since we can't
  26189. // express that to the backend, we have to validate locally.
  26190. // Note: this can change once we can send separate verify writes in the
  26191. // transaction.
  26192. throw new FirestoreError(Code.INVALID_ARGUMENT, "Can't update a document that doesn't exist.");
  26193. }
  26194. // Document exists, base precondition on document update time.
  26195. return Precondition.updateTime(version);
  26196. }
  26197. else {
  26198. // Document was not read, so we just use the preconditions for a blind
  26199. // update.
  26200. return Precondition.exists(true);
  26201. }
  26202. }
  26203. write(mutation) {
  26204. this.ensureCommitNotCalled();
  26205. this.mutations.push(mutation);
  26206. }
  26207. ensureCommitNotCalled() {
  26208. }
  26209. }
  26210. /**
  26211. * @license
  26212. * Copyright 2019 Google LLC
  26213. *
  26214. * Licensed under the Apache License, Version 2.0 (the "License");
  26215. * you may not use this file except in compliance with the License.
  26216. * You may obtain a copy of the License at
  26217. *
  26218. * http://www.apache.org/licenses/LICENSE-2.0
  26219. *
  26220. * Unless required by applicable law or agreed to in writing, software
  26221. * distributed under the License is distributed on an "AS IS" BASIS,
  26222. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26223. * See the License for the specific language governing permissions and
  26224. * limitations under the License.
  26225. */
  26226. /**
  26227. * TransactionRunner encapsulates the logic needed to run and retry transactions
  26228. * with backoff.
  26229. */
  26230. class TransactionRunner {
  26231. constructor(asyncQueue, datastore, options, updateFunction, deferred) {
  26232. this.asyncQueue = asyncQueue;
  26233. this.datastore = datastore;
  26234. this.options = options;
  26235. this.updateFunction = updateFunction;
  26236. this.deferred = deferred;
  26237. this.attemptsRemaining = options.maxAttempts;
  26238. this.backoff = new ExponentialBackoff(this.asyncQueue, "transaction_retry" /* TimerId.TransactionRetry */);
  26239. }
  26240. /** Runs the transaction and sets the result on deferred. */
  26241. run() {
  26242. this.attemptsRemaining -= 1;
  26243. this.runWithBackOff();
  26244. }
  26245. runWithBackOff() {
  26246. this.backoff.backoffAndRun(async () => {
  26247. const transaction = new Transaction$2(this.datastore);
  26248. const userPromise = this.tryRunUpdateFunction(transaction);
  26249. if (userPromise) {
  26250. userPromise
  26251. .then(result => {
  26252. this.asyncQueue.enqueueAndForget(() => {
  26253. return transaction
  26254. .commit()
  26255. .then(() => {
  26256. this.deferred.resolve(result);
  26257. })
  26258. .catch(commitError => {
  26259. this.handleTransactionError(commitError);
  26260. });
  26261. });
  26262. })
  26263. .catch(userPromiseError => {
  26264. this.handleTransactionError(userPromiseError);
  26265. });
  26266. }
  26267. });
  26268. }
  26269. tryRunUpdateFunction(transaction) {
  26270. try {
  26271. const userPromise = this.updateFunction(transaction);
  26272. if (isNullOrUndefined(userPromise) ||
  26273. !userPromise.catch ||
  26274. !userPromise.then) {
  26275. this.deferred.reject(Error('Transaction callback must return a Promise'));
  26276. return null;
  26277. }
  26278. return userPromise;
  26279. }
  26280. catch (error) {
  26281. // Do not retry errors thrown by user provided updateFunction.
  26282. this.deferred.reject(error);
  26283. return null;
  26284. }
  26285. }
  26286. handleTransactionError(error) {
  26287. if (this.attemptsRemaining > 0 && this.isRetryableTransactionError(error)) {
  26288. this.attemptsRemaining -= 1;
  26289. this.asyncQueue.enqueueAndForget(() => {
  26290. this.runWithBackOff();
  26291. return Promise.resolve();
  26292. });
  26293. }
  26294. else {
  26295. this.deferred.reject(error);
  26296. }
  26297. }
  26298. isRetryableTransactionError(error) {
  26299. if (error.name === 'FirebaseError') {
  26300. // In transactions, the backend will fail outdated reads with FAILED_PRECONDITION and
  26301. // non-matching document versions with ABORTED. These errors should be retried.
  26302. const code = error.code;
  26303. return (code === 'aborted' ||
  26304. code === 'failed-precondition' ||
  26305. code === 'already-exists' ||
  26306. !isPermanentError(code));
  26307. }
  26308. return false;
  26309. }
  26310. }
  26311. /**
  26312. * @license
  26313. * Copyright 2017 Google LLC
  26314. *
  26315. * Licensed under the Apache License, Version 2.0 (the "License");
  26316. * you may not use this file except in compliance with the License.
  26317. * You may obtain a copy of the License at
  26318. *
  26319. * http://www.apache.org/licenses/LICENSE-2.0
  26320. *
  26321. * Unless required by applicable law or agreed to in writing, software
  26322. * distributed under the License is distributed on an "AS IS" BASIS,
  26323. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26324. * See the License for the specific language governing permissions and
  26325. * limitations under the License.
  26326. */
  26327. const LOG_TAG$2 = 'FirestoreClient';
  26328. const MAX_CONCURRENT_LIMBO_RESOLUTIONS = 100;
  26329. /** DOMException error code constants. */
  26330. const DOM_EXCEPTION_INVALID_STATE = 11;
  26331. const DOM_EXCEPTION_ABORTED = 20;
  26332. const DOM_EXCEPTION_QUOTA_EXCEEDED = 22;
  26333. /**
  26334. * FirestoreClient is a top-level class that constructs and owns all of the //
  26335. * pieces of the client SDK architecture. It is responsible for creating the //
  26336. * async queue that is shared by all of the other components in the system. //
  26337. */
  26338. class FirestoreClient {
  26339. constructor(authCredentials, appCheckCredentials,
  26340. /**
  26341. * Asynchronous queue responsible for all of our internal processing. When
  26342. * we get incoming work from the user (via public API) or the network
  26343. * (incoming GRPC messages), we should always schedule onto this queue.
  26344. * This ensures all of our work is properly serialized (e.g. we don't
  26345. * start processing a new operation while the previous one is waiting for
  26346. * an async I/O to complete).
  26347. */
  26348. asyncQueue, databaseInfo) {
  26349. this.authCredentials = authCredentials;
  26350. this.appCheckCredentials = appCheckCredentials;
  26351. this.asyncQueue = asyncQueue;
  26352. this.databaseInfo = databaseInfo;
  26353. this.user = User.UNAUTHENTICATED;
  26354. this.clientId = AutoId.newId();
  26355. this.authCredentialListener = () => Promise.resolve();
  26356. this.appCheckCredentialListener = () => Promise.resolve();
  26357. this.authCredentials.start(asyncQueue, async (user) => {
  26358. logDebug(LOG_TAG$2, 'Received user=', user.uid);
  26359. await this.authCredentialListener(user);
  26360. this.user = user;
  26361. });
  26362. this.appCheckCredentials.start(asyncQueue, newAppCheckToken => {
  26363. logDebug(LOG_TAG$2, 'Received new app check token=', newAppCheckToken);
  26364. return this.appCheckCredentialListener(newAppCheckToken, this.user);
  26365. });
  26366. }
  26367. async getConfiguration() {
  26368. return {
  26369. asyncQueue: this.asyncQueue,
  26370. databaseInfo: this.databaseInfo,
  26371. clientId: this.clientId,
  26372. authCredentials: this.authCredentials,
  26373. appCheckCredentials: this.appCheckCredentials,
  26374. initialUser: this.user,
  26375. maxConcurrentLimboResolutions: MAX_CONCURRENT_LIMBO_RESOLUTIONS
  26376. };
  26377. }
  26378. setCredentialChangeListener(listener) {
  26379. this.authCredentialListener = listener;
  26380. }
  26381. setAppCheckTokenChangeListener(listener) {
  26382. this.appCheckCredentialListener = listener;
  26383. }
  26384. /**
  26385. * Checks that the client has not been terminated. Ensures that other methods on //
  26386. * this class cannot be called after the client is terminated. //
  26387. */
  26388. verifyNotTerminated() {
  26389. if (this.asyncQueue.isShuttingDown) {
  26390. throw new FirestoreError(Code.FAILED_PRECONDITION, 'The client has already been terminated.');
  26391. }
  26392. }
  26393. terminate() {
  26394. this.asyncQueue.enterRestrictedMode();
  26395. const deferred = new Deferred();
  26396. this.asyncQueue.enqueueAndForgetEvenWhileRestricted(async () => {
  26397. try {
  26398. if (this._onlineComponents) {
  26399. await this._onlineComponents.terminate();
  26400. }
  26401. if (this._offlineComponents) {
  26402. await this._offlineComponents.terminate();
  26403. }
  26404. // The credentials provider must be terminated after shutting down the
  26405. // RemoteStore as it will prevent the RemoteStore from retrieving auth
  26406. // tokens.
  26407. this.authCredentials.shutdown();
  26408. this.appCheckCredentials.shutdown();
  26409. deferred.resolve();
  26410. }
  26411. catch (e) {
  26412. const firestoreError = wrapInUserErrorIfRecoverable(e, `Failed to shutdown persistence`);
  26413. deferred.reject(firestoreError);
  26414. }
  26415. });
  26416. return deferred.promise;
  26417. }
  26418. }
  26419. async function setOfflineComponentProvider(client, offlineComponentProvider) {
  26420. client.asyncQueue.verifyOperationInProgress();
  26421. logDebug(LOG_TAG$2, 'Initializing OfflineComponentProvider');
  26422. const configuration = await client.getConfiguration();
  26423. await offlineComponentProvider.initialize(configuration);
  26424. let currentUser = configuration.initialUser;
  26425. client.setCredentialChangeListener(async (user) => {
  26426. if (!currentUser.isEqual(user)) {
  26427. await localStoreHandleUserChange(offlineComponentProvider.localStore, user);
  26428. currentUser = user;
  26429. }
  26430. });
  26431. // When a user calls clearPersistence() in one client, all other clients
  26432. // need to be terminated to allow the delete to succeed.
  26433. offlineComponentProvider.persistence.setDatabaseDeletedListener(() => client.terminate());
  26434. client._offlineComponents = offlineComponentProvider;
  26435. }
  26436. async function setOnlineComponentProvider(client, onlineComponentProvider) {
  26437. client.asyncQueue.verifyOperationInProgress();
  26438. const offlineComponentProvider = await ensureOfflineComponents(client);
  26439. logDebug(LOG_TAG$2, 'Initializing OnlineComponentProvider');
  26440. const configuration = await client.getConfiguration();
  26441. await onlineComponentProvider.initialize(offlineComponentProvider, configuration);
  26442. // The CredentialChangeListener of the online component provider takes
  26443. // precedence over the offline component provider.
  26444. client.setCredentialChangeListener(user => remoteStoreHandleCredentialChange(onlineComponentProvider.remoteStore, user));
  26445. client.setAppCheckTokenChangeListener((_, user) => remoteStoreHandleCredentialChange(onlineComponentProvider.remoteStore, user));
  26446. client._onlineComponents = onlineComponentProvider;
  26447. }
  26448. /**
  26449. * Decides whether the provided error allows us to gracefully disable
  26450. * persistence (as opposed to crashing the client).
  26451. */
  26452. function canFallbackFromIndexedDbError(error) {
  26453. if (error.name === 'FirebaseError') {
  26454. return (error.code === Code.FAILED_PRECONDITION ||
  26455. error.code === Code.UNIMPLEMENTED);
  26456. }
  26457. else if (typeof DOMException !== 'undefined' &&
  26458. error instanceof DOMException) {
  26459. // There are a few known circumstances where we can open IndexedDb but
  26460. // trying to read/write will fail (e.g. quota exceeded). For
  26461. // well-understood cases, we attempt to detect these and then gracefully
  26462. // fall back to memory persistence.
  26463. // NOTE: Rather than continue to add to this list, we could decide to
  26464. // always fall back, with the risk that we might accidentally hide errors
  26465. // representing actual SDK bugs.
  26466. return (
  26467. // When the browser is out of quota we could get either quota exceeded
  26468. // or an aborted error depending on whether the error happened during
  26469. // schema migration.
  26470. error.code === DOM_EXCEPTION_QUOTA_EXCEEDED ||
  26471. error.code === DOM_EXCEPTION_ABORTED ||
  26472. // Firefox Private Browsing mode disables IndexedDb and returns
  26473. // INVALID_STATE for any usage.
  26474. error.code === DOM_EXCEPTION_INVALID_STATE);
  26475. }
  26476. return true;
  26477. }
  26478. async function ensureOfflineComponents(client) {
  26479. if (!client._offlineComponents) {
  26480. if (client._uninitializedComponentsProvider) {
  26481. logDebug(LOG_TAG$2, 'Using user provided OfflineComponentProvider');
  26482. try {
  26483. await setOfflineComponentProvider(client, client._uninitializedComponentsProvider._offline);
  26484. }
  26485. catch (e) {
  26486. const error = e;
  26487. if (!canFallbackFromIndexedDbError(error)) {
  26488. throw error;
  26489. }
  26490. logWarn('Error using user provided cache. Falling back to ' +
  26491. 'memory cache: ' +
  26492. error);
  26493. await setOfflineComponentProvider(client, new MemoryOfflineComponentProvider());
  26494. }
  26495. }
  26496. else {
  26497. logDebug(LOG_TAG$2, 'Using default OfflineComponentProvider');
  26498. await setOfflineComponentProvider(client, new MemoryOfflineComponentProvider());
  26499. }
  26500. }
  26501. return client._offlineComponents;
  26502. }
  26503. async function ensureOnlineComponents(client) {
  26504. if (!client._onlineComponents) {
  26505. if (client._uninitializedComponentsProvider) {
  26506. logDebug(LOG_TAG$2, 'Using user provided OnlineComponentProvider');
  26507. await setOnlineComponentProvider(client, client._uninitializedComponentsProvider._online);
  26508. }
  26509. else {
  26510. logDebug(LOG_TAG$2, 'Using default OnlineComponentProvider');
  26511. await setOnlineComponentProvider(client, new OnlineComponentProvider());
  26512. }
  26513. }
  26514. return client._onlineComponents;
  26515. }
  26516. function getPersistence(client) {
  26517. return ensureOfflineComponents(client).then(c => c.persistence);
  26518. }
  26519. function getLocalStore(client) {
  26520. return ensureOfflineComponents(client).then(c => c.localStore);
  26521. }
  26522. function getRemoteStore(client) {
  26523. return ensureOnlineComponents(client).then(c => c.remoteStore);
  26524. }
  26525. function getSyncEngine(client) {
  26526. return ensureOnlineComponents(client).then(c => c.syncEngine);
  26527. }
  26528. function getDatastore(client) {
  26529. return ensureOnlineComponents(client).then(c => c.datastore);
  26530. }
  26531. async function getEventManager(client) {
  26532. const onlineComponentProvider = await ensureOnlineComponents(client);
  26533. const eventManager = onlineComponentProvider.eventManager;
  26534. eventManager.onListen = syncEngineListen.bind(null, onlineComponentProvider.syncEngine);
  26535. eventManager.onUnlisten = syncEngineUnlisten.bind(null, onlineComponentProvider.syncEngine);
  26536. return eventManager;
  26537. }
  26538. /** Enables the network connection and re-enqueues all pending operations. */
  26539. function firestoreClientEnableNetwork(client) {
  26540. return client.asyncQueue.enqueue(async () => {
  26541. const persistence = await getPersistence(client);
  26542. const remoteStore = await getRemoteStore(client);
  26543. persistence.setNetworkEnabled(true);
  26544. return remoteStoreEnableNetwork(remoteStore);
  26545. });
  26546. }
  26547. /** Disables the network connection. Pending operations will not complete. */
  26548. function firestoreClientDisableNetwork(client) {
  26549. return client.asyncQueue.enqueue(async () => {
  26550. const persistence = await getPersistence(client);
  26551. const remoteStore = await getRemoteStore(client);
  26552. persistence.setNetworkEnabled(false);
  26553. return remoteStoreDisableNetwork(remoteStore);
  26554. });
  26555. }
  26556. /**
  26557. * Returns a Promise that resolves when all writes that were pending at the time
  26558. * this method was called received server acknowledgement. An acknowledgement
  26559. * can be either acceptance or rejection.
  26560. */
  26561. function firestoreClientWaitForPendingWrites(client) {
  26562. const deferred = new Deferred();
  26563. client.asyncQueue.enqueueAndForget(async () => {
  26564. const syncEngine = await getSyncEngine(client);
  26565. return syncEngineRegisterPendingWritesCallback(syncEngine, deferred);
  26566. });
  26567. return deferred.promise;
  26568. }
  26569. function firestoreClientListen(client, query, options, observer) {
  26570. const wrappedObserver = new AsyncObserver(observer);
  26571. const listener = new QueryListener(query, wrappedObserver, options);
  26572. client.asyncQueue.enqueueAndForget(async () => {
  26573. const eventManager = await getEventManager(client);
  26574. return eventManagerListen(eventManager, listener);
  26575. });
  26576. return () => {
  26577. wrappedObserver.mute();
  26578. client.asyncQueue.enqueueAndForget(async () => {
  26579. const eventManager = await getEventManager(client);
  26580. return eventManagerUnlisten(eventManager, listener);
  26581. });
  26582. };
  26583. }
  26584. function firestoreClientGetDocumentFromLocalCache(client, docKey) {
  26585. const deferred = new Deferred();
  26586. client.asyncQueue.enqueueAndForget(async () => {
  26587. const localStore = await getLocalStore(client);
  26588. return readDocumentFromCache(localStore, docKey, deferred);
  26589. });
  26590. return deferred.promise;
  26591. }
  26592. function firestoreClientGetDocumentViaSnapshotListener(client, key, options = {}) {
  26593. const deferred = new Deferred();
  26594. client.asyncQueue.enqueueAndForget(async () => {
  26595. const eventManager = await getEventManager(client);
  26596. return readDocumentViaSnapshotListener(eventManager, client.asyncQueue, key, options, deferred);
  26597. });
  26598. return deferred.promise;
  26599. }
  26600. function firestoreClientGetDocumentsFromLocalCache(client, query) {
  26601. const deferred = new Deferred();
  26602. client.asyncQueue.enqueueAndForget(async () => {
  26603. const localStore = await getLocalStore(client);
  26604. return executeQueryFromCache(localStore, query, deferred);
  26605. });
  26606. return deferred.promise;
  26607. }
  26608. function firestoreClientGetDocumentsViaSnapshotListener(client, query, options = {}) {
  26609. const deferred = new Deferred();
  26610. client.asyncQueue.enqueueAndForget(async () => {
  26611. const eventManager = await getEventManager(client);
  26612. return executeQueryViaSnapshotListener(eventManager, client.asyncQueue, query, options, deferred);
  26613. });
  26614. return deferred.promise;
  26615. }
  26616. function firestoreClientRunAggregateQuery(client, query, aggregates) {
  26617. const deferred = new Deferred();
  26618. client.asyncQueue.enqueueAndForget(async () => {
  26619. // TODO (sum/avg) should we update this to use the event manager?
  26620. // Implement and call executeAggregateQueryViaSnapshotListener, similar
  26621. // to the implementation in firestoreClientGetDocumentsViaSnapshotListener
  26622. // above
  26623. try {
  26624. // TODO(b/277628384): check `canUseNetwork()` and handle multi-tab.
  26625. const datastore = await getDatastore(client);
  26626. deferred.resolve(invokeRunAggregationQueryRpc(datastore, query, aggregates));
  26627. }
  26628. catch (e) {
  26629. deferred.reject(e);
  26630. }
  26631. });
  26632. return deferred.promise;
  26633. }
  26634. function firestoreClientWrite(client, mutations) {
  26635. const deferred = new Deferred();
  26636. client.asyncQueue.enqueueAndForget(async () => {
  26637. const syncEngine = await getSyncEngine(client);
  26638. return syncEngineWrite(syncEngine, mutations, deferred);
  26639. });
  26640. return deferred.promise;
  26641. }
  26642. function firestoreClientAddSnapshotsInSyncListener(client, observer) {
  26643. const wrappedObserver = new AsyncObserver(observer);
  26644. client.asyncQueue.enqueueAndForget(async () => {
  26645. const eventManager = await getEventManager(client);
  26646. return addSnapshotsInSyncListener(eventManager, wrappedObserver);
  26647. });
  26648. return () => {
  26649. wrappedObserver.mute();
  26650. client.asyncQueue.enqueueAndForget(async () => {
  26651. const eventManager = await getEventManager(client);
  26652. return removeSnapshotsInSyncListener(eventManager, wrappedObserver);
  26653. });
  26654. };
  26655. }
  26656. /**
  26657. * Takes an updateFunction in which a set of reads and writes can be performed
  26658. * atomically. In the updateFunction, the client can read and write values
  26659. * using the supplied transaction object. After the updateFunction, all
  26660. * changes will be committed. If a retryable error occurs (ex: some other
  26661. * client has changed any of the data referenced), then the updateFunction
  26662. * will be called again after a backoff. If the updateFunction still fails
  26663. * after all retries, then the transaction will be rejected.
  26664. *
  26665. * The transaction object passed to the updateFunction contains methods for
  26666. * accessing documents and collections. Unlike other datastore access, data
  26667. * accessed with the transaction will not reflect local changes that have not
  26668. * been committed. For this reason, it is required that all reads are
  26669. * performed before any writes. Transactions must be performed while online.
  26670. */
  26671. function firestoreClientTransaction(client, updateFunction, options) {
  26672. const deferred = new Deferred();
  26673. client.asyncQueue.enqueueAndForget(async () => {
  26674. const datastore = await getDatastore(client);
  26675. new TransactionRunner(client.asyncQueue, datastore, options, updateFunction, deferred).run();
  26676. });
  26677. return deferred.promise;
  26678. }
  26679. async function readDocumentFromCache(localStore, docKey, result) {
  26680. try {
  26681. const document = await localStoreReadDocument(localStore, docKey);
  26682. if (document.isFoundDocument()) {
  26683. result.resolve(document);
  26684. }
  26685. else if (document.isNoDocument()) {
  26686. result.resolve(null);
  26687. }
  26688. else {
  26689. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get document from cache. (However, this document may ' +
  26690. "exist on the server. Run again without setting 'source' in " +
  26691. 'the GetOptions to attempt to retrieve the document from the ' +
  26692. 'server.)'));
  26693. }
  26694. }
  26695. catch (e) {
  26696. const firestoreError = wrapInUserErrorIfRecoverable(e, `Failed to get document '${docKey} from cache`);
  26697. result.reject(firestoreError);
  26698. }
  26699. }
  26700. /**
  26701. * Retrieves a latency-compensated document from the backend via a
  26702. * SnapshotListener.
  26703. */
  26704. function readDocumentViaSnapshotListener(eventManager, asyncQueue, key, options, result) {
  26705. const wrappedObserver = new AsyncObserver({
  26706. next: (snap) => {
  26707. // Remove query first before passing event to user to avoid
  26708. // user actions affecting the now stale query.
  26709. asyncQueue.enqueueAndForget(() => eventManagerUnlisten(eventManager, listener));
  26710. const exists = snap.docs.has(key);
  26711. if (!exists && snap.fromCache) {
  26712. // TODO(dimond): If we're online and the document doesn't
  26713. // exist then we resolve with a doc.exists set to false. If
  26714. // we're offline however, we reject the Promise in this
  26715. // case. Two options: 1) Cache the negative response from
  26716. // the server so we can deliver that even when you're
  26717. // offline 2) Actually reject the Promise in the online case
  26718. // if the document doesn't exist.
  26719. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get document because the client is offline.'));
  26720. }
  26721. else if (exists &&
  26722. snap.fromCache &&
  26723. options &&
  26724. options.source === 'server') {
  26725. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get document from server. (However, this ' +
  26726. 'document does exist in the local cache. Run again ' +
  26727. 'without setting source to "server" to ' +
  26728. 'retrieve the cached document.)'));
  26729. }
  26730. else {
  26731. result.resolve(snap);
  26732. }
  26733. },
  26734. error: e => result.reject(e)
  26735. });
  26736. const listener = new QueryListener(newQueryForPath(key.path), wrappedObserver, {
  26737. includeMetadataChanges: true,
  26738. waitForSyncWhenOnline: true
  26739. });
  26740. return eventManagerListen(eventManager, listener);
  26741. }
  26742. async function executeQueryFromCache(localStore, query, result) {
  26743. try {
  26744. const queryResult = await localStoreExecuteQuery(localStore, query,
  26745. /* usePreviousResults= */ true);
  26746. const view = new View(query, queryResult.remoteKeys);
  26747. const viewDocChanges = view.computeDocChanges(queryResult.documents);
  26748. const viewChange = view.applyChanges(viewDocChanges,
  26749. /* updateLimboDocuments= */ false);
  26750. result.resolve(viewChange.snapshot);
  26751. }
  26752. catch (e) {
  26753. const firestoreError = wrapInUserErrorIfRecoverable(e, `Failed to execute query '${query} against cache`);
  26754. result.reject(firestoreError);
  26755. }
  26756. }
  26757. /**
  26758. * Retrieves a latency-compensated query snapshot from the backend via a
  26759. * SnapshotListener.
  26760. */
  26761. function executeQueryViaSnapshotListener(eventManager, asyncQueue, query, options, result) {
  26762. const wrappedObserver = new AsyncObserver({
  26763. next: snapshot => {
  26764. // Remove query first before passing event to user to avoid
  26765. // user actions affecting the now stale query.
  26766. asyncQueue.enqueueAndForget(() => eventManagerUnlisten(eventManager, listener));
  26767. if (snapshot.fromCache && options.source === 'server') {
  26768. result.reject(new FirestoreError(Code.UNAVAILABLE, 'Failed to get documents from server. (However, these ' +
  26769. 'documents may exist in the local cache. Run again ' +
  26770. 'without setting source to "server" to ' +
  26771. 'retrieve the cached documents.)'));
  26772. }
  26773. else {
  26774. result.resolve(snapshot);
  26775. }
  26776. },
  26777. error: e => result.reject(e)
  26778. });
  26779. const listener = new QueryListener(query, wrappedObserver, {
  26780. includeMetadataChanges: true,
  26781. waitForSyncWhenOnline: true
  26782. });
  26783. return eventManagerListen(eventManager, listener);
  26784. }
  26785. function firestoreClientLoadBundle(client, databaseId, data, resultTask) {
  26786. const reader = createBundleReader(data, newSerializer(databaseId));
  26787. client.asyncQueue.enqueueAndForget(async () => {
  26788. syncEngineLoadBundle(await getSyncEngine(client), reader, resultTask);
  26789. });
  26790. }
  26791. function firestoreClientGetNamedQuery(client, queryName) {
  26792. return client.asyncQueue.enqueue(async () => localStoreGetNamedQuery(await getLocalStore(client), queryName));
  26793. }
  26794. function createBundleReader(data, serializer) {
  26795. let content;
  26796. if (typeof data === 'string') {
  26797. content = newTextEncoder().encode(data);
  26798. }
  26799. else {
  26800. content = data;
  26801. }
  26802. return newBundleReader(toByteStreamReader(content), serializer);
  26803. }
  26804. function firestoreClientSetIndexConfiguration(client, indexes) {
  26805. return client.asyncQueue.enqueue(async () => {
  26806. return localStoreConfigureFieldIndexes(await getLocalStore(client), indexes);
  26807. });
  26808. }
  26809. /**
  26810. * @license
  26811. * Copyright 2023 Google LLC
  26812. *
  26813. * Licensed under the Apache License, Version 2.0 (the "License");
  26814. * you may not use this file except in compliance with the License.
  26815. * You may obtain a copy of the License at
  26816. *
  26817. * http://www.apache.org/licenses/LICENSE-2.0
  26818. *
  26819. * Unless required by applicable law or agreed to in writing, software
  26820. * distributed under the License is distributed on an "AS IS" BASIS,
  26821. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26822. * See the License for the specific language governing permissions and
  26823. * limitations under the License.
  26824. */
  26825. /**
  26826. * Compares two `ExperimentalLongPollingOptions` objects for equality.
  26827. */
  26828. function longPollingOptionsEqual(options1, options2) {
  26829. return options1.timeoutSeconds === options2.timeoutSeconds;
  26830. }
  26831. /**
  26832. * Creates and returns a new `ExperimentalLongPollingOptions` with the same
  26833. * option values as the given instance.
  26834. */
  26835. function cloneLongPollingOptions(options) {
  26836. const clone = {};
  26837. if (options.timeoutSeconds !== undefined) {
  26838. clone.timeoutSeconds = options.timeoutSeconds;
  26839. }
  26840. return clone;
  26841. }
  26842. /**
  26843. * @license
  26844. * Copyright 2020 Google LLC
  26845. *
  26846. * Licensed under the Apache License, Version 2.0 (the "License");
  26847. * you may not use this file except in compliance with the License.
  26848. * You may obtain a copy of the License at
  26849. *
  26850. * http://www.apache.org/licenses/LICENSE-2.0
  26851. *
  26852. * Unless required by applicable law or agreed to in writing, software
  26853. * distributed under the License is distributed on an "AS IS" BASIS,
  26854. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26855. * See the License for the specific language governing permissions and
  26856. * limitations under the License.
  26857. */
  26858. const LOG_TAG$1 = 'ComponentProvider';
  26859. /**
  26860. * An instance map that ensures only one Datastore exists per Firestore
  26861. * instance.
  26862. */
  26863. const datastoreInstances = new Map();
  26864. /**
  26865. * Removes all components associated with the provided instance. Must be called
  26866. * when the `Firestore` instance is terminated.
  26867. */
  26868. function removeComponents(firestore) {
  26869. const datastore = datastoreInstances.get(firestore);
  26870. if (datastore) {
  26871. logDebug(LOG_TAG$1, 'Removing Datastore');
  26872. datastoreInstances.delete(firestore);
  26873. datastore.terminate();
  26874. }
  26875. }
  26876. function makeDatabaseInfo(databaseId, appId, persistenceKey, settings) {
  26877. return new DatabaseInfo(databaseId, appId, persistenceKey, settings.host, settings.ssl, settings.experimentalForceLongPolling, settings.experimentalAutoDetectLongPolling, cloneLongPollingOptions(settings.experimentalLongPollingOptions), settings.useFetchStreams);
  26878. }
  26879. /**
  26880. * @license
  26881. * Copyright 2020 Google LLC
  26882. *
  26883. * Licensed under the Apache License, Version 2.0 (the "License");
  26884. * you may not use this file except in compliance with the License.
  26885. * You may obtain a copy of the License at
  26886. *
  26887. * http://www.apache.org/licenses/LICENSE-2.0
  26888. *
  26889. * Unless required by applicable law or agreed to in writing, software
  26890. * distributed under the License is distributed on an "AS IS" BASIS,
  26891. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26892. * See the License for the specific language governing permissions and
  26893. * limitations under the License.
  26894. */
  26895. // settings() defaults:
  26896. const DEFAULT_HOST = 'firestore.googleapis.com';
  26897. const DEFAULT_SSL = true;
  26898. // The minimum long-polling timeout is hardcoded on the server. The value here
  26899. // should be kept in sync with the value used by the server, as the server will
  26900. // silently ignore a value below the minimum and fall back to the default.
  26901. // Googlers see b/266868871 for relevant discussion.
  26902. const MIN_LONG_POLLING_TIMEOUT_SECONDS = 5;
  26903. // No maximum long-polling timeout is configured in the server, and defaults to
  26904. // 30 seconds, which is what Watch appears to use.
  26905. // Googlers see b/266868871 for relevant discussion.
  26906. const MAX_LONG_POLLING_TIMEOUT_SECONDS = 30;
  26907. // Whether long-polling auto-detected is enabled by default.
  26908. const DEFAULT_AUTO_DETECT_LONG_POLLING = true;
  26909. /**
  26910. * A concrete type describing all the values that can be applied via a
  26911. * user-supplied `FirestoreSettings` object. This is a separate type so that
  26912. * defaults can be supplied and the value can be checked for equality.
  26913. */
  26914. class FirestoreSettingsImpl {
  26915. constructor(settings) {
  26916. var _a, _b;
  26917. if (settings.host === undefined) {
  26918. if (settings.ssl !== undefined) {
  26919. throw new FirestoreError(Code.INVALID_ARGUMENT, "Can't provide ssl option if host option is not set");
  26920. }
  26921. this.host = DEFAULT_HOST;
  26922. this.ssl = DEFAULT_SSL;
  26923. }
  26924. else {
  26925. this.host = settings.host;
  26926. this.ssl = (_a = settings.ssl) !== null && _a !== void 0 ? _a : DEFAULT_SSL;
  26927. }
  26928. this.credentials = settings.credentials;
  26929. this.ignoreUndefinedProperties = !!settings.ignoreUndefinedProperties;
  26930. this.cache = settings.localCache;
  26931. if (settings.cacheSizeBytes === undefined) {
  26932. this.cacheSizeBytes = LRU_DEFAULT_CACHE_SIZE_BYTES;
  26933. }
  26934. else {
  26935. if (settings.cacheSizeBytes !== LRU_COLLECTION_DISABLED &&
  26936. settings.cacheSizeBytes < LRU_MINIMUM_CACHE_SIZE_BYTES) {
  26937. throw new FirestoreError(Code.INVALID_ARGUMENT, `cacheSizeBytes must be at least ${LRU_MINIMUM_CACHE_SIZE_BYTES}`);
  26938. }
  26939. else {
  26940. this.cacheSizeBytes = settings.cacheSizeBytes;
  26941. }
  26942. }
  26943. validateIsNotUsedTogether('experimentalForceLongPolling', settings.experimentalForceLongPolling, 'experimentalAutoDetectLongPolling', settings.experimentalAutoDetectLongPolling);
  26944. this.experimentalForceLongPolling = !!settings.experimentalForceLongPolling;
  26945. if (this.experimentalForceLongPolling) {
  26946. this.experimentalAutoDetectLongPolling = false;
  26947. }
  26948. else if (settings.experimentalAutoDetectLongPolling === undefined) {
  26949. this.experimentalAutoDetectLongPolling = DEFAULT_AUTO_DETECT_LONG_POLLING;
  26950. }
  26951. else {
  26952. // For backwards compatibility, coerce the value to boolean even though
  26953. // the TypeScript compiler has narrowed the type to boolean already.
  26954. // noinspection PointlessBooleanExpressionJS
  26955. this.experimentalAutoDetectLongPolling =
  26956. !!settings.experimentalAutoDetectLongPolling;
  26957. }
  26958. this.experimentalLongPollingOptions = cloneLongPollingOptions((_b = settings.experimentalLongPollingOptions) !== null && _b !== void 0 ? _b : {});
  26959. validateLongPollingOptions(this.experimentalLongPollingOptions);
  26960. this.useFetchStreams = !!settings.useFetchStreams;
  26961. }
  26962. isEqual(other) {
  26963. return (this.host === other.host &&
  26964. this.ssl === other.ssl &&
  26965. this.credentials === other.credentials &&
  26966. this.cacheSizeBytes === other.cacheSizeBytes &&
  26967. this.experimentalForceLongPolling ===
  26968. other.experimentalForceLongPolling &&
  26969. this.experimentalAutoDetectLongPolling ===
  26970. other.experimentalAutoDetectLongPolling &&
  26971. longPollingOptionsEqual(this.experimentalLongPollingOptions, other.experimentalLongPollingOptions) &&
  26972. this.ignoreUndefinedProperties === other.ignoreUndefinedProperties &&
  26973. this.useFetchStreams === other.useFetchStreams);
  26974. }
  26975. }
  26976. function validateLongPollingOptions(options) {
  26977. if (options.timeoutSeconds !== undefined) {
  26978. if (isNaN(options.timeoutSeconds)) {
  26979. throw new FirestoreError(Code.INVALID_ARGUMENT, `invalid long polling timeout: ` +
  26980. `${options.timeoutSeconds} (must not be NaN)`);
  26981. }
  26982. if (options.timeoutSeconds < MIN_LONG_POLLING_TIMEOUT_SECONDS) {
  26983. throw new FirestoreError(Code.INVALID_ARGUMENT, `invalid long polling timeout: ${options.timeoutSeconds} ` +
  26984. `(minimum allowed value is ${MIN_LONG_POLLING_TIMEOUT_SECONDS})`);
  26985. }
  26986. if (options.timeoutSeconds > MAX_LONG_POLLING_TIMEOUT_SECONDS) {
  26987. throw new FirestoreError(Code.INVALID_ARGUMENT, `invalid long polling timeout: ${options.timeoutSeconds} ` +
  26988. `(maximum allowed value is ${MAX_LONG_POLLING_TIMEOUT_SECONDS})`);
  26989. }
  26990. }
  26991. }
  26992. /**
  26993. * @license
  26994. * Copyright 2020 Google LLC
  26995. *
  26996. * Licensed under the Apache License, Version 2.0 (the "License");
  26997. * you may not use this file except in compliance with the License.
  26998. * You may obtain a copy of the License at
  26999. *
  27000. * http://www.apache.org/licenses/LICENSE-2.0
  27001. *
  27002. * Unless required by applicable law or agreed to in writing, software
  27003. * distributed under the License is distributed on an "AS IS" BASIS,
  27004. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27005. * See the License for the specific language governing permissions and
  27006. * limitations under the License.
  27007. */
  27008. /**
  27009. * The Cloud Firestore service interface.
  27010. *
  27011. * Do not call this constructor directly. Instead, use {@link (getFirestore:1)}.
  27012. */
  27013. class Firestore$1 {
  27014. /** @hideconstructor */
  27015. constructor(_authCredentials, _appCheckCredentials, _databaseId, _app) {
  27016. this._authCredentials = _authCredentials;
  27017. this._appCheckCredentials = _appCheckCredentials;
  27018. this._databaseId = _databaseId;
  27019. this._app = _app;
  27020. /**
  27021. * Whether it's a Firestore or Firestore Lite instance.
  27022. */
  27023. this.type = 'firestore-lite';
  27024. this._persistenceKey = '(lite)';
  27025. this._settings = new FirestoreSettingsImpl({});
  27026. this._settingsFrozen = false;
  27027. }
  27028. /**
  27029. * The {@link @firebase/app#FirebaseApp} associated with this `Firestore` service
  27030. * instance.
  27031. */
  27032. get app() {
  27033. if (!this._app) {
  27034. throw new FirestoreError(Code.FAILED_PRECONDITION, "Firestore was not initialized using the Firebase SDK. 'app' is " +
  27035. 'not available');
  27036. }
  27037. return this._app;
  27038. }
  27039. get _initialized() {
  27040. return this._settingsFrozen;
  27041. }
  27042. get _terminated() {
  27043. return this._terminateTask !== undefined;
  27044. }
  27045. _setSettings(settings) {
  27046. if (this._settingsFrozen) {
  27047. throw new FirestoreError(Code.FAILED_PRECONDITION, 'Firestore has already been started and its settings can no longer ' +
  27048. 'be changed. You can only modify settings before calling any other ' +
  27049. 'methods on a Firestore object.');
  27050. }
  27051. this._settings = new FirestoreSettingsImpl(settings);
  27052. if (settings.credentials !== undefined) {
  27053. this._authCredentials = makeAuthCredentialsProvider(settings.credentials);
  27054. }
  27055. }
  27056. _getSettings() {
  27057. return this._settings;
  27058. }
  27059. _freezeSettings() {
  27060. this._settingsFrozen = true;
  27061. return this._settings;
  27062. }
  27063. _delete() {
  27064. if (!this._terminateTask) {
  27065. this._terminateTask = this._terminate();
  27066. }
  27067. return this._terminateTask;
  27068. }
  27069. /** Returns a JSON-serializable representation of this `Firestore` instance. */
  27070. toJSON() {
  27071. return {
  27072. app: this._app,
  27073. databaseId: this._databaseId,
  27074. settings: this._settings
  27075. };
  27076. }
  27077. /**
  27078. * Terminates all components used by this client. Subclasses can override
  27079. * this method to clean up their own dependencies, but must also call this
  27080. * method.
  27081. *
  27082. * Only ever called once.
  27083. */
  27084. _terminate() {
  27085. removeComponents(this);
  27086. return Promise.resolve();
  27087. }
  27088. }
  27089. /**
  27090. * Modify this instance to communicate with the Cloud Firestore emulator.
  27091. *
  27092. * Note: This must be called before this instance has been used to do any
  27093. * operations.
  27094. *
  27095. * @param firestore - The `Firestore` instance to configure to connect to the
  27096. * emulator.
  27097. * @param host - the emulator host (ex: localhost).
  27098. * @param port - the emulator port (ex: 9000).
  27099. * @param options.mockUserToken - the mock auth token to use for unit testing
  27100. * Security Rules.
  27101. */
  27102. function connectFirestoreEmulator(firestore, host, port, options = {}) {
  27103. var _a;
  27104. firestore = cast(firestore, Firestore$1);
  27105. const settings = firestore._getSettings();
  27106. const newHostSetting = `${host}:${port}`;
  27107. if (settings.host !== DEFAULT_HOST && settings.host !== newHostSetting) {
  27108. logWarn('Host has been set in both settings() and connectFirestoreEmulator(), emulator host ' +
  27109. 'will be used.');
  27110. }
  27111. firestore._setSettings(Object.assign(Object.assign({}, settings), { host: newHostSetting, ssl: false }));
  27112. if (options.mockUserToken) {
  27113. let token;
  27114. let user;
  27115. if (typeof options.mockUserToken === 'string') {
  27116. token = options.mockUserToken;
  27117. user = User.MOCK_USER;
  27118. }
  27119. else {
  27120. // Let createMockUserToken validate first (catches common mistakes like
  27121. // invalid field "uid" and missing field "sub" / "user_id".)
  27122. token = createMockUserToken(options.mockUserToken, (_a = firestore._app) === null || _a === void 0 ? void 0 : _a.options.projectId);
  27123. const uid = options.mockUserToken.sub || options.mockUserToken.user_id;
  27124. if (!uid) {
  27125. throw new FirestoreError(Code.INVALID_ARGUMENT, "mockUserToken must contain 'sub' or 'user_id' field!");
  27126. }
  27127. user = new User(uid);
  27128. }
  27129. firestore._authCredentials = new EmulatorAuthCredentialsProvider(new OAuthToken(token, user));
  27130. }
  27131. }
  27132. /**
  27133. * @license
  27134. * Copyright 2020 Google LLC
  27135. *
  27136. * Licensed under the Apache License, Version 2.0 (the "License");
  27137. * you may not use this file except in compliance with the License.
  27138. * You may obtain a copy of the License at
  27139. *
  27140. * http://www.apache.org/licenses/LICENSE-2.0
  27141. *
  27142. * Unless required by applicable law or agreed to in writing, software
  27143. * distributed under the License is distributed on an "AS IS" BASIS,
  27144. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27145. * See the License for the specific language governing permissions and
  27146. * limitations under the License.
  27147. */
  27148. /**
  27149. * A `DocumentReference` refers to a document location in a Firestore database
  27150. * and can be used to write, read, or listen to the location. The document at
  27151. * the referenced location may or may not exist.
  27152. */
  27153. class DocumentReference {
  27154. /** @hideconstructor */
  27155. constructor(firestore,
  27156. /**
  27157. * If provided, the `FirestoreDataConverter` associated with this instance.
  27158. */
  27159. converter, _key) {
  27160. this.converter = converter;
  27161. this._key = _key;
  27162. /** The type of this Firestore reference. */
  27163. this.type = 'document';
  27164. this.firestore = firestore;
  27165. }
  27166. get _path() {
  27167. return this._key.path;
  27168. }
  27169. /**
  27170. * The document's identifier within its collection.
  27171. */
  27172. get id() {
  27173. return this._key.path.lastSegment();
  27174. }
  27175. /**
  27176. * A string representing the path of the referenced document (relative
  27177. * to the root of the database).
  27178. */
  27179. get path() {
  27180. return this._key.path.canonicalString();
  27181. }
  27182. /**
  27183. * The collection this `DocumentReference` belongs to.
  27184. */
  27185. get parent() {
  27186. return new CollectionReference(this.firestore, this.converter, this._key.path.popLast());
  27187. }
  27188. withConverter(converter) {
  27189. return new DocumentReference(this.firestore, converter, this._key);
  27190. }
  27191. }
  27192. /**
  27193. * A `Query` refers to a query which you can read or listen to. You can also
  27194. * construct refined `Query` objects by adding filters and ordering.
  27195. */
  27196. class Query {
  27197. // This is the lite version of the Query class in the main SDK.
  27198. /** @hideconstructor protected */
  27199. constructor(firestore,
  27200. /**
  27201. * If provided, the `FirestoreDataConverter` associated with this instance.
  27202. */
  27203. converter, _query) {
  27204. this.converter = converter;
  27205. this._query = _query;
  27206. /** The type of this Firestore reference. */
  27207. this.type = 'query';
  27208. this.firestore = firestore;
  27209. }
  27210. withConverter(converter) {
  27211. return new Query(this.firestore, converter, this._query);
  27212. }
  27213. }
  27214. /**
  27215. * A `CollectionReference` object can be used for adding documents, getting
  27216. * document references, and querying for documents (using {@link (query:1)}).
  27217. */
  27218. class CollectionReference extends Query {
  27219. /** @hideconstructor */
  27220. constructor(firestore, converter, _path) {
  27221. super(firestore, converter, newQueryForPath(_path));
  27222. this._path = _path;
  27223. /** The type of this Firestore reference. */
  27224. this.type = 'collection';
  27225. }
  27226. /** The collection's identifier. */
  27227. get id() {
  27228. return this._query.path.lastSegment();
  27229. }
  27230. /**
  27231. * A string representing the path of the referenced collection (relative
  27232. * to the root of the database).
  27233. */
  27234. get path() {
  27235. return this._query.path.canonicalString();
  27236. }
  27237. /**
  27238. * A reference to the containing `DocumentReference` if this is a
  27239. * subcollection. If this isn't a subcollection, the reference is null.
  27240. */
  27241. get parent() {
  27242. const parentPath = this._path.popLast();
  27243. if (parentPath.isEmpty()) {
  27244. return null;
  27245. }
  27246. else {
  27247. return new DocumentReference(this.firestore,
  27248. /* converter= */ null, new DocumentKey(parentPath));
  27249. }
  27250. }
  27251. withConverter(converter) {
  27252. return new CollectionReference(this.firestore, converter, this._path);
  27253. }
  27254. }
  27255. function collection(parent, path, ...pathSegments) {
  27256. parent = getModularInstance(parent);
  27257. validateNonEmptyArgument('collection', 'path', path);
  27258. if (parent instanceof Firestore$1) {
  27259. const absolutePath = ResourcePath.fromString(path, ...pathSegments);
  27260. validateCollectionPath(absolutePath);
  27261. return new CollectionReference(parent, /* converter= */ null, absolutePath);
  27262. }
  27263. else {
  27264. if (!(parent instanceof DocumentReference) &&
  27265. !(parent instanceof CollectionReference)) {
  27266. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Expected first argument to collection() to be a CollectionReference, ' +
  27267. 'a DocumentReference or FirebaseFirestore');
  27268. }
  27269. const absolutePath = parent._path.child(ResourcePath.fromString(path, ...pathSegments));
  27270. validateCollectionPath(absolutePath);
  27271. return new CollectionReference(parent.firestore,
  27272. /* converter= */ null, absolutePath);
  27273. }
  27274. }
  27275. // TODO(firestorelite): Consider using ErrorFactory -
  27276. // https://github.com/firebase/firebase-js-sdk/blob/0131e1f/packages/util/src/errors.ts#L106
  27277. /**
  27278. * Creates and returns a new `Query` instance that includes all documents in the
  27279. * database that are contained in a collection or subcollection with the
  27280. * given `collectionId`.
  27281. *
  27282. * @param firestore - A reference to the root `Firestore` instance.
  27283. * @param collectionId - Identifies the collections to query over. Every
  27284. * collection or subcollection with this ID as the last segment of its path
  27285. * will be included. Cannot contain a slash.
  27286. * @returns The created `Query`.
  27287. */
  27288. function collectionGroup(firestore, collectionId) {
  27289. firestore = cast(firestore, Firestore$1);
  27290. validateNonEmptyArgument('collectionGroup', 'collection id', collectionId);
  27291. if (collectionId.indexOf('/') >= 0) {
  27292. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid collection ID '${collectionId}' passed to function ` +
  27293. `collectionGroup(). Collection IDs must not contain '/'.`);
  27294. }
  27295. return new Query(firestore,
  27296. /* converter= */ null, newQueryForCollectionGroup(collectionId));
  27297. }
  27298. function doc(parent, path, ...pathSegments) {
  27299. parent = getModularInstance(parent);
  27300. // We allow omission of 'pathString' but explicitly prohibit passing in both
  27301. // 'undefined' and 'null'.
  27302. if (arguments.length === 1) {
  27303. path = AutoId.newId();
  27304. }
  27305. validateNonEmptyArgument('doc', 'path', path);
  27306. if (parent instanceof Firestore$1) {
  27307. const absolutePath = ResourcePath.fromString(path, ...pathSegments);
  27308. validateDocumentPath(absolutePath);
  27309. return new DocumentReference(parent,
  27310. /* converter= */ null, new DocumentKey(absolutePath));
  27311. }
  27312. else {
  27313. if (!(parent instanceof DocumentReference) &&
  27314. !(parent instanceof CollectionReference)) {
  27315. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Expected first argument to collection() to be a CollectionReference, ' +
  27316. 'a DocumentReference or FirebaseFirestore');
  27317. }
  27318. const absolutePath = parent._path.child(ResourcePath.fromString(path, ...pathSegments));
  27319. validateDocumentPath(absolutePath);
  27320. return new DocumentReference(parent.firestore, parent instanceof CollectionReference ? parent.converter : null, new DocumentKey(absolutePath));
  27321. }
  27322. }
  27323. /**
  27324. * Returns true if the provided references are equal.
  27325. *
  27326. * @param left - A reference to compare.
  27327. * @param right - A reference to compare.
  27328. * @returns true if the references point to the same location in the same
  27329. * Firestore database.
  27330. */
  27331. function refEqual(left, right) {
  27332. left = getModularInstance(left);
  27333. right = getModularInstance(right);
  27334. if ((left instanceof DocumentReference ||
  27335. left instanceof CollectionReference) &&
  27336. (right instanceof DocumentReference || right instanceof CollectionReference)) {
  27337. return (left.firestore === right.firestore &&
  27338. left.path === right.path &&
  27339. left.converter === right.converter);
  27340. }
  27341. return false;
  27342. }
  27343. /**
  27344. * Returns true if the provided queries point to the same collection and apply
  27345. * the same constraints.
  27346. *
  27347. * @param left - A `Query` to compare.
  27348. * @param right - A `Query` to compare.
  27349. * @returns true if the references point to the same location in the same
  27350. * Firestore database.
  27351. */
  27352. function queryEqual(left, right) {
  27353. left = getModularInstance(left);
  27354. right = getModularInstance(right);
  27355. if (left instanceof Query && right instanceof Query) {
  27356. return (left.firestore === right.firestore &&
  27357. queryEquals(left._query, right._query) &&
  27358. left.converter === right.converter);
  27359. }
  27360. return false;
  27361. }
  27362. /**
  27363. * @license
  27364. * Copyright 2020 Google LLC
  27365. *
  27366. * Licensed under the Apache License, Version 2.0 (the "License");
  27367. * you may not use this file except in compliance with the License.
  27368. * You may obtain a copy of the License at
  27369. *
  27370. * http://www.apache.org/licenses/LICENSE-2.0
  27371. *
  27372. * Unless required by applicable law or agreed to in writing, software
  27373. * distributed under the License is distributed on an "AS IS" BASIS,
  27374. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27375. * See the License for the specific language governing permissions and
  27376. * limitations under the License.
  27377. */
  27378. const LOG_TAG = 'AsyncQueue';
  27379. class AsyncQueueImpl {
  27380. constructor() {
  27381. // The last promise in the queue.
  27382. this.tail = Promise.resolve();
  27383. // A list of retryable operations. Retryable operations are run in order and
  27384. // retried with backoff.
  27385. this.retryableOps = [];
  27386. // Is this AsyncQueue being shut down? Once it is set to true, it will not
  27387. // be changed again.
  27388. this._isShuttingDown = false;
  27389. // Operations scheduled to be queued in the future. Operations are
  27390. // automatically removed after they are run or canceled.
  27391. this.delayedOperations = [];
  27392. // visible for testing
  27393. this.failure = null;
  27394. // Flag set while there's an outstanding AsyncQueue operation, used for
  27395. // assertion sanity-checks.
  27396. this.operationInProgress = false;
  27397. // Enabled during shutdown on Safari to prevent future access to IndexedDB.
  27398. this.skipNonRestrictedTasks = false;
  27399. // List of TimerIds to fast-forward delays for.
  27400. this.timerIdsToSkip = [];
  27401. // Backoff timer used to schedule retries for retryable operations
  27402. this.backoff = new ExponentialBackoff(this, "async_queue_retry" /* TimerId.AsyncQueueRetry */);
  27403. // Visibility handler that triggers an immediate retry of all retryable
  27404. // operations. Meant to speed up recovery when we regain file system access
  27405. // after page comes into foreground.
  27406. this.visibilityHandler = () => {
  27407. this.backoff.skipBackoff();
  27408. };
  27409. }
  27410. get isShuttingDown() {
  27411. return this._isShuttingDown;
  27412. }
  27413. /**
  27414. * Adds a new operation to the queue without waiting for it to complete (i.e.
  27415. * we ignore the Promise result).
  27416. */
  27417. enqueueAndForget(op) {
  27418. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  27419. this.enqueue(op);
  27420. }
  27421. enqueueAndForgetEvenWhileRestricted(op) {
  27422. this.verifyNotFailed();
  27423. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  27424. this.enqueueInternal(op);
  27425. }
  27426. enterRestrictedMode(purgeExistingTasks) {
  27427. if (!this._isShuttingDown) {
  27428. this._isShuttingDown = true;
  27429. this.skipNonRestrictedTasks = purgeExistingTasks || false;
  27430. }
  27431. }
  27432. enqueue(op) {
  27433. this.verifyNotFailed();
  27434. if (this._isShuttingDown) {
  27435. // Return a Promise which never resolves.
  27436. return new Promise(() => { });
  27437. }
  27438. // Create a deferred Promise that we can return to the callee. This
  27439. // allows us to return a "hanging Promise" only to the callee and still
  27440. // advance the queue even when the operation is not run.
  27441. const task = new Deferred();
  27442. return this.enqueueInternal(() => {
  27443. if (this._isShuttingDown && this.skipNonRestrictedTasks) {
  27444. // We do not resolve 'task'
  27445. return Promise.resolve();
  27446. }
  27447. op().then(task.resolve, task.reject);
  27448. return task.promise;
  27449. }).then(() => task.promise);
  27450. }
  27451. enqueueRetryable(op) {
  27452. this.enqueueAndForget(() => {
  27453. this.retryableOps.push(op);
  27454. return this.retryNextOp();
  27455. });
  27456. }
  27457. /**
  27458. * Runs the next operation from the retryable queue. If the operation fails,
  27459. * reschedules with backoff.
  27460. */
  27461. async retryNextOp() {
  27462. if (this.retryableOps.length === 0) {
  27463. return;
  27464. }
  27465. try {
  27466. await this.retryableOps[0]();
  27467. this.retryableOps.shift();
  27468. this.backoff.reset();
  27469. }
  27470. catch (e) {
  27471. if (isIndexedDbTransactionError(e)) {
  27472. logDebug(LOG_TAG, 'Operation failed with retryable error: ' + e);
  27473. }
  27474. else {
  27475. throw e; // Failure will be handled by AsyncQueue
  27476. }
  27477. }
  27478. if (this.retryableOps.length > 0) {
  27479. // If there are additional operations, we re-schedule `retryNextOp()`.
  27480. // This is necessary to run retryable operations that failed during
  27481. // their initial attempt since we don't know whether they are already
  27482. // enqueued. If, for example, `op1`, `op2`, `op3` are enqueued and `op1`
  27483. // needs to be re-run, we will run `op1`, `op1`, `op2` using the
  27484. // already enqueued calls to `retryNextOp()`. `op3()` will then run in the
  27485. // call scheduled here.
  27486. // Since `backoffAndRun()` cancels an existing backoff and schedules a
  27487. // new backoff on every call, there is only ever a single additional
  27488. // operation in the queue.
  27489. this.backoff.backoffAndRun(() => this.retryNextOp());
  27490. }
  27491. }
  27492. enqueueInternal(op) {
  27493. const newTail = this.tail.then(() => {
  27494. this.operationInProgress = true;
  27495. return op()
  27496. .catch((error) => {
  27497. this.failure = error;
  27498. this.operationInProgress = false;
  27499. const message = getMessageOrStack(error);
  27500. logError('INTERNAL UNHANDLED ERROR: ', message);
  27501. // Re-throw the error so that this.tail becomes a rejected Promise and
  27502. // all further attempts to chain (via .then) will just short-circuit
  27503. // and return the rejected Promise.
  27504. throw error;
  27505. })
  27506. .then(result => {
  27507. this.operationInProgress = false;
  27508. return result;
  27509. });
  27510. });
  27511. this.tail = newTail;
  27512. return newTail;
  27513. }
  27514. enqueueAfterDelay(timerId, delayMs, op) {
  27515. this.verifyNotFailed();
  27516. // Fast-forward delays for timerIds that have been overriden.
  27517. if (this.timerIdsToSkip.indexOf(timerId) > -1) {
  27518. delayMs = 0;
  27519. }
  27520. const delayedOp = DelayedOperation.createAndSchedule(this, timerId, delayMs, op, removedOp => this.removeDelayedOperation(removedOp));
  27521. this.delayedOperations.push(delayedOp);
  27522. return delayedOp;
  27523. }
  27524. verifyNotFailed() {
  27525. if (this.failure) {
  27526. fail();
  27527. }
  27528. }
  27529. verifyOperationInProgress() {
  27530. }
  27531. /**
  27532. * Waits until all currently queued tasks are finished executing. Delayed
  27533. * operations are not run.
  27534. */
  27535. async drain() {
  27536. // Operations in the queue prior to draining may have enqueued additional
  27537. // operations. Keep draining the queue until the tail is no longer advanced,
  27538. // which indicates that no more new operations were enqueued and that all
  27539. // operations were executed.
  27540. let currentTail;
  27541. do {
  27542. currentTail = this.tail;
  27543. await currentTail;
  27544. } while (currentTail !== this.tail);
  27545. }
  27546. /**
  27547. * For Tests: Determine if a delayed operation with a particular TimerId
  27548. * exists.
  27549. */
  27550. containsDelayedOperation(timerId) {
  27551. for (const op of this.delayedOperations) {
  27552. if (op.timerId === timerId) {
  27553. return true;
  27554. }
  27555. }
  27556. return false;
  27557. }
  27558. /**
  27559. * For Tests: Runs some or all delayed operations early.
  27560. *
  27561. * @param lastTimerId - Delayed operations up to and including this TimerId
  27562. * will be drained. Pass TimerId.All to run all delayed operations.
  27563. * @returns a Promise that resolves once all operations have been run.
  27564. */
  27565. runAllDelayedOperationsUntil(lastTimerId) {
  27566. // Note that draining may generate more delayed ops, so we do that first.
  27567. return this.drain().then(() => {
  27568. // Run ops in the same order they'd run if they ran naturally.
  27569. this.delayedOperations.sort((a, b) => a.targetTimeMs - b.targetTimeMs);
  27570. for (const op of this.delayedOperations) {
  27571. op.skipDelay();
  27572. if (lastTimerId !== "all" /* TimerId.All */ && op.timerId === lastTimerId) {
  27573. break;
  27574. }
  27575. }
  27576. return this.drain();
  27577. });
  27578. }
  27579. /**
  27580. * For Tests: Skip all subsequent delays for a timer id.
  27581. */
  27582. skipDelaysForTimerId(timerId) {
  27583. this.timerIdsToSkip.push(timerId);
  27584. }
  27585. /** Called once a DelayedOperation is run or canceled. */
  27586. removeDelayedOperation(op) {
  27587. // NOTE: indexOf / slice are O(n), but delayedOperations is expected to be small.
  27588. const index = this.delayedOperations.indexOf(op);
  27589. this.delayedOperations.splice(index, 1);
  27590. }
  27591. }
  27592. function newAsyncQueue() {
  27593. return new AsyncQueueImpl();
  27594. }
  27595. /**
  27596. * Chrome includes Error.message in Error.stack. Other browsers do not.
  27597. * This returns expected output of message + stack when available.
  27598. * @param error - Error or FirestoreError
  27599. */
  27600. function getMessageOrStack(error) {
  27601. let message = error.message || '';
  27602. if (error.stack) {
  27603. if (error.stack.includes(error.message)) {
  27604. message = error.stack;
  27605. }
  27606. else {
  27607. message = error.message + '\n' + error.stack;
  27608. }
  27609. }
  27610. return message;
  27611. }
  27612. /**
  27613. * @license
  27614. * Copyright 2020 Google LLC
  27615. *
  27616. * Licensed under the Apache License, Version 2.0 (the "License");
  27617. * you may not use this file except in compliance with the License.
  27618. * You may obtain a copy of the License at
  27619. *
  27620. * http://www.apache.org/licenses/LICENSE-2.0
  27621. *
  27622. * Unless required by applicable law or agreed to in writing, software
  27623. * distributed under the License is distributed on an "AS IS" BASIS,
  27624. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27625. * See the License for the specific language governing permissions and
  27626. * limitations under the License.
  27627. */
  27628. /**
  27629. * Represents the task of loading a Firestore bundle. It provides progress of bundle
  27630. * loading, as well as task completion and error events.
  27631. *
  27632. * The API is compatible with `Promise<LoadBundleTaskProgress>`.
  27633. */
  27634. class LoadBundleTask {
  27635. constructor() {
  27636. this._progressObserver = {};
  27637. this._taskCompletionResolver = new Deferred();
  27638. this._lastProgress = {
  27639. taskState: 'Running',
  27640. totalBytes: 0,
  27641. totalDocuments: 0,
  27642. bytesLoaded: 0,
  27643. documentsLoaded: 0
  27644. };
  27645. }
  27646. /**
  27647. * Registers functions to listen to bundle loading progress events.
  27648. * @param next - Called when there is a progress update from bundle loading. Typically `next` calls occur
  27649. * each time a Firestore document is loaded from the bundle.
  27650. * @param error - Called when an error occurs during bundle loading. The task aborts after reporting the
  27651. * error, and there should be no more updates after this.
  27652. * @param complete - Called when the loading task is complete.
  27653. */
  27654. onProgress(next, error, complete) {
  27655. this._progressObserver = {
  27656. next,
  27657. error,
  27658. complete
  27659. };
  27660. }
  27661. /**
  27662. * Implements the `Promise<LoadBundleTaskProgress>.catch` interface.
  27663. *
  27664. * @param onRejected - Called when an error occurs during bundle loading.
  27665. */
  27666. catch(onRejected) {
  27667. return this._taskCompletionResolver.promise.catch(onRejected);
  27668. }
  27669. /**
  27670. * Implements the `Promise<LoadBundleTaskProgress>.then` interface.
  27671. *
  27672. * @param onFulfilled - Called on the completion of the loading task with a final `LoadBundleTaskProgress` update.
  27673. * The update will always have its `taskState` set to `"Success"`.
  27674. * @param onRejected - Called when an error occurs during bundle loading.
  27675. */
  27676. then(onFulfilled, onRejected) {
  27677. return this._taskCompletionResolver.promise.then(onFulfilled, onRejected);
  27678. }
  27679. /**
  27680. * Notifies all observers that bundle loading has completed, with a provided
  27681. * `LoadBundleTaskProgress` object.
  27682. *
  27683. * @private
  27684. */
  27685. _completeWith(progress) {
  27686. this._updateProgress(progress);
  27687. if (this._progressObserver.complete) {
  27688. this._progressObserver.complete();
  27689. }
  27690. this._taskCompletionResolver.resolve(progress);
  27691. }
  27692. /**
  27693. * Notifies all observers that bundle loading has failed, with a provided
  27694. * `Error` as the reason.
  27695. *
  27696. * @private
  27697. */
  27698. _failWith(error) {
  27699. this._lastProgress.taskState = 'Error';
  27700. if (this._progressObserver.next) {
  27701. this._progressObserver.next(this._lastProgress);
  27702. }
  27703. if (this._progressObserver.error) {
  27704. this._progressObserver.error(error);
  27705. }
  27706. this._taskCompletionResolver.reject(error);
  27707. }
  27708. /**
  27709. * Notifies a progress update of loading a bundle.
  27710. * @param progress - The new progress.
  27711. *
  27712. * @private
  27713. */
  27714. _updateProgress(progress) {
  27715. this._lastProgress = progress;
  27716. if (this._progressObserver.next) {
  27717. this._progressObserver.next(progress);
  27718. }
  27719. }
  27720. }
  27721. /**
  27722. * @license
  27723. * Copyright 2020 Google LLC
  27724. *
  27725. * Licensed under the Apache License, Version 2.0 (the "License");
  27726. * you may not use this file except in compliance with the License.
  27727. * You may obtain a copy of the License at
  27728. *
  27729. * http://www.apache.org/licenses/LICENSE-2.0
  27730. *
  27731. * Unless required by applicable law or agreed to in writing, software
  27732. * distributed under the License is distributed on an "AS IS" BASIS,
  27733. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27734. * See the License for the specific language governing permissions and
  27735. * limitations under the License.
  27736. */
  27737. /**
  27738. * Constant used to indicate the LRU garbage collection should be disabled.
  27739. * Set this value as the `cacheSizeBytes` on the settings passed to the
  27740. * {@link Firestore} instance.
  27741. */
  27742. const CACHE_SIZE_UNLIMITED = LRU_COLLECTION_DISABLED;
  27743. /**
  27744. * The Cloud Firestore service interface.
  27745. *
  27746. * Do not call this constructor directly. Instead, use {@link (getFirestore:1)}.
  27747. */
  27748. class Firestore extends Firestore$1 {
  27749. /** @hideconstructor */
  27750. constructor(authCredentialsProvider, appCheckCredentialsProvider, databaseId, app) {
  27751. super(authCredentialsProvider, appCheckCredentialsProvider, databaseId, app);
  27752. /**
  27753. * Whether it's a {@link Firestore} or Firestore Lite instance.
  27754. */
  27755. this.type = 'firestore';
  27756. this._queue = newAsyncQueue();
  27757. this._persistenceKey = (app === null || app === void 0 ? void 0 : app.name) || '[DEFAULT]';
  27758. }
  27759. _terminate() {
  27760. if (!this._firestoreClient) {
  27761. // The client must be initialized to ensure that all subsequent API
  27762. // usage throws an exception.
  27763. configureFirestore(this);
  27764. }
  27765. return this._firestoreClient.terminate();
  27766. }
  27767. }
  27768. /**
  27769. * Initializes a new instance of {@link Firestore} with the provided settings.
  27770. * Can only be called before any other function, including
  27771. * {@link (getFirestore:1)}. If the custom settings are empty, this function is
  27772. * equivalent to calling {@link (getFirestore:1)}.
  27773. *
  27774. * @param app - The {@link @firebase/app#FirebaseApp} with which the {@link Firestore} instance will
  27775. * be associated.
  27776. * @param settings - A settings object to configure the {@link Firestore} instance.
  27777. * @param databaseId - The name of the database.
  27778. * @returns A newly initialized {@link Firestore} instance.
  27779. */
  27780. function initializeFirestore(app, settings, databaseId) {
  27781. if (!databaseId) {
  27782. databaseId = DEFAULT_DATABASE_NAME;
  27783. }
  27784. const provider = _getProvider(app, 'firestore');
  27785. if (provider.isInitialized(databaseId)) {
  27786. const existingInstance = provider.getImmediate({
  27787. identifier: databaseId
  27788. });
  27789. const initialSettings = provider.getOptions(databaseId);
  27790. if (deepEqual(initialSettings, settings)) {
  27791. return existingInstance;
  27792. }
  27793. else {
  27794. throw new FirestoreError(Code.FAILED_PRECONDITION, 'initializeFirestore() has already been called with ' +
  27795. 'different options. To avoid this error, call initializeFirestore() with the ' +
  27796. 'same options as when it was originally called, or call getFirestore() to return the' +
  27797. ' already initialized instance.');
  27798. }
  27799. }
  27800. if (settings.cacheSizeBytes !== undefined &&
  27801. settings.localCache !== undefined) {
  27802. throw new FirestoreError(Code.INVALID_ARGUMENT, `cache and cacheSizeBytes cannot be specified at the same time as cacheSizeBytes will` +
  27803. `be deprecated. Instead, specify the cache size in the cache object`);
  27804. }
  27805. if (settings.cacheSizeBytes !== undefined &&
  27806. settings.cacheSizeBytes !== CACHE_SIZE_UNLIMITED &&
  27807. settings.cacheSizeBytes < LRU_MINIMUM_CACHE_SIZE_BYTES) {
  27808. throw new FirestoreError(Code.INVALID_ARGUMENT, `cacheSizeBytes must be at least ${LRU_MINIMUM_CACHE_SIZE_BYTES}`);
  27809. }
  27810. return provider.initialize({
  27811. options: settings,
  27812. instanceIdentifier: databaseId
  27813. });
  27814. }
  27815. function getFirestore(appOrDatabaseId, optionalDatabaseId) {
  27816. const app = typeof appOrDatabaseId === 'object' ? appOrDatabaseId : getApp();
  27817. const databaseId = typeof appOrDatabaseId === 'string'
  27818. ? appOrDatabaseId
  27819. : optionalDatabaseId || DEFAULT_DATABASE_NAME;
  27820. const db = _getProvider(app, 'firestore').getImmediate({
  27821. identifier: databaseId
  27822. });
  27823. if (!db._initialized) {
  27824. const emulator = getDefaultEmulatorHostnameAndPort('firestore');
  27825. if (emulator) {
  27826. connectFirestoreEmulator(db, ...emulator);
  27827. }
  27828. }
  27829. return db;
  27830. }
  27831. /**
  27832. * @internal
  27833. */
  27834. function ensureFirestoreConfigured(firestore) {
  27835. if (!firestore._firestoreClient) {
  27836. configureFirestore(firestore);
  27837. }
  27838. firestore._firestoreClient.verifyNotTerminated();
  27839. return firestore._firestoreClient;
  27840. }
  27841. function configureFirestore(firestore) {
  27842. var _a, _b, _c;
  27843. const settings = firestore._freezeSettings();
  27844. const databaseInfo = makeDatabaseInfo(firestore._databaseId, ((_a = firestore._app) === null || _a === void 0 ? void 0 : _a.options.appId) || '', firestore._persistenceKey, settings);
  27845. firestore._firestoreClient = new FirestoreClient(firestore._authCredentials, firestore._appCheckCredentials, firestore._queue, databaseInfo);
  27846. if (((_b = settings.cache) === null || _b === void 0 ? void 0 : _b._offlineComponentProvider) &&
  27847. ((_c = settings.cache) === null || _c === void 0 ? void 0 : _c._onlineComponentProvider)) {
  27848. firestore._firestoreClient._uninitializedComponentsProvider = {
  27849. _offlineKind: settings.cache.kind,
  27850. _offline: settings.cache._offlineComponentProvider,
  27851. _online: settings.cache._onlineComponentProvider
  27852. };
  27853. }
  27854. }
  27855. /**
  27856. * Attempts to enable persistent storage, if possible.
  27857. *
  27858. * Must be called before any other functions (other than
  27859. * {@link initializeFirestore}, {@link (getFirestore:1)} or
  27860. * {@link clearIndexedDbPersistence}.
  27861. *
  27862. * If this fails, `enableIndexedDbPersistence()` will reject the promise it
  27863. * returns. Note that even after this failure, the {@link Firestore} instance will
  27864. * remain usable, however offline persistence will be disabled.
  27865. *
  27866. * There are several reasons why this can fail, which can be identified by
  27867. * the `code` on the error.
  27868. *
  27869. * * failed-precondition: The app is already open in another browser tab.
  27870. * * unimplemented: The browser is incompatible with the offline
  27871. * persistence implementation.
  27872. *
  27873. * Persistence cannot be used in a Node.js environment.
  27874. *
  27875. * @param firestore - The {@link Firestore} instance to enable persistence for.
  27876. * @param persistenceSettings - Optional settings object to configure
  27877. * persistence.
  27878. * @returns A `Promise` that represents successfully enabling persistent storage.
  27879. * @deprecated This function will be removed in a future major release. Instead, set
  27880. * `FirestoreSettings.cache` to an instance of `IndexedDbLocalCache` to
  27881. * turn on IndexedDb cache. Calling this function when `FirestoreSettings.cache`
  27882. * is already specified will throw an exception.
  27883. */
  27884. function enableIndexedDbPersistence(firestore, persistenceSettings) {
  27885. firestore = cast(firestore, Firestore);
  27886. verifyNotInitialized(firestore);
  27887. const client = ensureFirestoreConfigured(firestore);
  27888. if (client._uninitializedComponentsProvider) {
  27889. throw new FirestoreError(Code.FAILED_PRECONDITION, 'SDK cache is already specified.');
  27890. }
  27891. logWarn('enableIndexedDbPersistence() will be deprecated in the future, ' +
  27892. 'you can use `FirestoreSettings.cache` instead.');
  27893. const settings = firestore._freezeSettings();
  27894. const onlineComponentProvider = new OnlineComponentProvider();
  27895. const offlineComponentProvider = new IndexedDbOfflineComponentProvider(onlineComponentProvider, settings.cacheSizeBytes, persistenceSettings === null || persistenceSettings === void 0 ? void 0 : persistenceSettings.forceOwnership);
  27896. return setPersistenceProviders(client, onlineComponentProvider, offlineComponentProvider);
  27897. }
  27898. /**
  27899. * Attempts to enable multi-tab persistent storage, if possible. If enabled
  27900. * across all tabs, all operations share access to local persistence, including
  27901. * shared execution of queries and latency-compensated local document updates
  27902. * across all connected instances.
  27903. *
  27904. * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise
  27905. * it returns. Note that even after this failure, the {@link Firestore} instance will
  27906. * remain usable, however offline persistence will be disabled.
  27907. *
  27908. * There are several reasons why this can fail, which can be identified by
  27909. * the `code` on the error.
  27910. *
  27911. * * failed-precondition: The app is already open in another browser tab and
  27912. * multi-tab is not enabled.
  27913. * * unimplemented: The browser is incompatible with the offline
  27914. * persistence implementation.
  27915. *
  27916. * @param firestore - The {@link Firestore} instance to enable persistence for.
  27917. * @returns A `Promise` that represents successfully enabling persistent
  27918. * storage.
  27919. * @deprecated This function will be removed in a future major release. Instead, set
  27920. * `FirestoreSettings.cache` to an instance of `IndexedDbLocalCache` to
  27921. * turn on indexeddb cache. Calling this function when `FirestoreSettings.cache`
  27922. * is already specified will throw an exception.
  27923. */
  27924. function enableMultiTabIndexedDbPersistence(firestore) {
  27925. firestore = cast(firestore, Firestore);
  27926. verifyNotInitialized(firestore);
  27927. const client = ensureFirestoreConfigured(firestore);
  27928. if (client._uninitializedComponentsProvider) {
  27929. throw new FirestoreError(Code.FAILED_PRECONDITION, 'SDK cache is already specified.');
  27930. }
  27931. logWarn('enableMultiTabIndexedDbPersistence() will be deprecated in the future, ' +
  27932. 'you can use `FirestoreSettings.cache` instead.');
  27933. const settings = firestore._freezeSettings();
  27934. const onlineComponentProvider = new OnlineComponentProvider();
  27935. const offlineComponentProvider = new MultiTabOfflineComponentProvider(onlineComponentProvider, settings.cacheSizeBytes);
  27936. return setPersistenceProviders(client, onlineComponentProvider, offlineComponentProvider);
  27937. }
  27938. /**
  27939. * Registers both the `OfflineComponentProvider` and `OnlineComponentProvider`.
  27940. * If the operation fails with a recoverable error (see
  27941. * `canRecoverFromIndexedDbError()` below), the returned Promise is rejected
  27942. * but the client remains usable.
  27943. */
  27944. function setPersistenceProviders(client, onlineComponentProvider, offlineComponentProvider) {
  27945. const persistenceResult = new Deferred();
  27946. return client.asyncQueue
  27947. .enqueue(async () => {
  27948. try {
  27949. await setOfflineComponentProvider(client, offlineComponentProvider);
  27950. await setOnlineComponentProvider(client, onlineComponentProvider);
  27951. persistenceResult.resolve();
  27952. }
  27953. catch (e) {
  27954. const error = e;
  27955. if (!canFallbackFromIndexedDbError(error)) {
  27956. throw error;
  27957. }
  27958. logWarn('Error enabling indexeddb cache. Falling back to ' +
  27959. 'memory cache: ' +
  27960. error);
  27961. persistenceResult.reject(error);
  27962. }
  27963. })
  27964. .then(() => persistenceResult.promise);
  27965. }
  27966. /**
  27967. * Clears the persistent storage. This includes pending writes and cached
  27968. * documents.
  27969. *
  27970. * Must be called while the {@link Firestore} instance is not started (after the app is
  27971. * terminated or when the app is first initialized). On startup, this function
  27972. * must be called before other functions (other than {@link
  27973. * initializeFirestore} or {@link (getFirestore:1)})). If the {@link Firestore}
  27974. * instance is still running, the promise will be rejected with the error code
  27975. * of `failed-precondition`.
  27976. *
  27977. * Note: `clearIndexedDbPersistence()` is primarily intended to help write
  27978. * reliable tests that use Cloud Firestore. It uses an efficient mechanism for
  27979. * dropping existing data but does not attempt to securely overwrite or
  27980. * otherwise make cached data unrecoverable. For applications that are sensitive
  27981. * to the disclosure of cached data in between user sessions, we strongly
  27982. * recommend not enabling persistence at all.
  27983. *
  27984. * @param firestore - The {@link Firestore} instance to clear persistence for.
  27985. * @returns A `Promise` that is resolved when the persistent storage is
  27986. * cleared. Otherwise, the promise is rejected with an error.
  27987. */
  27988. function clearIndexedDbPersistence(firestore) {
  27989. if (firestore._initialized && !firestore._terminated) {
  27990. throw new FirestoreError(Code.FAILED_PRECONDITION, 'Persistence can only be cleared before a Firestore instance is ' +
  27991. 'initialized or after it is terminated.');
  27992. }
  27993. const deferred = new Deferred();
  27994. firestore._queue.enqueueAndForgetEvenWhileRestricted(async () => {
  27995. try {
  27996. await indexedDbClearPersistence(indexedDbStoragePrefix(firestore._databaseId, firestore._persistenceKey));
  27997. deferred.resolve();
  27998. }
  27999. catch (e) {
  28000. deferred.reject(e);
  28001. }
  28002. });
  28003. return deferred.promise;
  28004. }
  28005. /**
  28006. * Waits until all currently pending writes for the active user have been
  28007. * acknowledged by the backend.
  28008. *
  28009. * The returned promise resolves immediately if there are no outstanding writes.
  28010. * Otherwise, the promise waits for all previously issued writes (including
  28011. * those written in a previous app session), but it does not wait for writes
  28012. * that were added after the function is called. If you want to wait for
  28013. * additional writes, call `waitForPendingWrites()` again.
  28014. *
  28015. * Any outstanding `waitForPendingWrites()` promises are rejected during user
  28016. * changes.
  28017. *
  28018. * @returns A `Promise` which resolves when all currently pending writes have been
  28019. * acknowledged by the backend.
  28020. */
  28021. function waitForPendingWrites(firestore) {
  28022. firestore = cast(firestore, Firestore);
  28023. const client = ensureFirestoreConfigured(firestore);
  28024. return firestoreClientWaitForPendingWrites(client);
  28025. }
  28026. /**
  28027. * Re-enables use of the network for this {@link Firestore} instance after a prior
  28028. * call to {@link disableNetwork}.
  28029. *
  28030. * @returns A `Promise` that is resolved once the network has been enabled.
  28031. */
  28032. function enableNetwork(firestore) {
  28033. firestore = cast(firestore, Firestore);
  28034. const client = ensureFirestoreConfigured(firestore);
  28035. return firestoreClientEnableNetwork(client);
  28036. }
  28037. /**
  28038. * Disables network usage for this instance. It can be re-enabled via {@link
  28039. * enableNetwork}. While the network is disabled, any snapshot listeners,
  28040. * `getDoc()` or `getDocs()` calls will return results from cache, and any write
  28041. * operations will be queued until the network is restored.
  28042. *
  28043. * @returns A `Promise` that is resolved once the network has been disabled.
  28044. */
  28045. function disableNetwork(firestore) {
  28046. firestore = cast(firestore, Firestore);
  28047. const client = ensureFirestoreConfigured(firestore);
  28048. return firestoreClientDisableNetwork(client);
  28049. }
  28050. /**
  28051. * Terminates the provided {@link Firestore} instance.
  28052. *
  28053. * After calling `terminate()` only the `clearIndexedDbPersistence()` function
  28054. * may be used. Any other function will throw a `FirestoreError`.
  28055. *
  28056. * To restart after termination, create a new instance of FirebaseFirestore with
  28057. * {@link (getFirestore:1)}.
  28058. *
  28059. * Termination does not cancel any pending writes, and any promises that are
  28060. * awaiting a response from the server will not be resolved. If you have
  28061. * persistence enabled, the next time you start this instance, it will resume
  28062. * sending these writes to the server.
  28063. *
  28064. * Note: Under normal circumstances, calling `terminate()` is not required. This
  28065. * function is useful only when you want to force this instance to release all
  28066. * of its resources or in combination with `clearIndexedDbPersistence()` to
  28067. * ensure that all local state is destroyed between test runs.
  28068. *
  28069. * @returns A `Promise` that is resolved when the instance has been successfully
  28070. * terminated.
  28071. */
  28072. function terminate(firestore) {
  28073. _removeServiceInstance(firestore.app, 'firestore', firestore._databaseId.database);
  28074. return firestore._delete();
  28075. }
  28076. /**
  28077. * Loads a Firestore bundle into the local cache.
  28078. *
  28079. * @param firestore - The {@link Firestore} instance to load bundles for.
  28080. * @param bundleData - An object representing the bundle to be loaded. Valid
  28081. * objects are `ArrayBuffer`, `ReadableStream<Uint8Array>` or `string`.
  28082. *
  28083. * @returns A `LoadBundleTask` object, which notifies callers with progress
  28084. * updates, and completion or error events. It can be used as a
  28085. * `Promise<LoadBundleTaskProgress>`.
  28086. */
  28087. function loadBundle(firestore, bundleData) {
  28088. firestore = cast(firestore, Firestore);
  28089. const client = ensureFirestoreConfigured(firestore);
  28090. const resultTask = new LoadBundleTask();
  28091. firestoreClientLoadBundle(client, firestore._databaseId, bundleData, resultTask);
  28092. return resultTask;
  28093. }
  28094. /**
  28095. * Reads a Firestore {@link Query} from local cache, identified by the given
  28096. * name.
  28097. *
  28098. * The named queries are packaged into bundles on the server side (along
  28099. * with resulting documents), and loaded to local cache using `loadBundle`. Once
  28100. * in local cache, use this method to extract a {@link Query} by name.
  28101. *
  28102. * @param firestore - The {@link Firestore} instance to read the query from.
  28103. * @param name - The name of the query.
  28104. * @returns A `Promise` that is resolved with the Query or `null`.
  28105. */
  28106. function namedQuery(firestore, name) {
  28107. firestore = cast(firestore, Firestore);
  28108. const client = ensureFirestoreConfigured(firestore);
  28109. return firestoreClientGetNamedQuery(client, name).then(namedQuery => {
  28110. if (!namedQuery) {
  28111. return null;
  28112. }
  28113. return new Query(firestore, null, namedQuery.query);
  28114. });
  28115. }
  28116. function verifyNotInitialized(firestore) {
  28117. if (firestore._initialized || firestore._terminated) {
  28118. throw new FirestoreError(Code.FAILED_PRECONDITION, 'Firestore has already been started and persistence can no longer be ' +
  28119. 'enabled. You can only enable persistence before calling any other ' +
  28120. 'methods on a Firestore object.');
  28121. }
  28122. }
  28123. /**
  28124. * @license
  28125. * Copyright 2020 Google LLC
  28126. *
  28127. * Licensed under the Apache License, Version 2.0 (the "License");
  28128. * you may not use this file except in compliance with the License.
  28129. * You may obtain a copy of the License at
  28130. *
  28131. * http://www.apache.org/licenses/LICENSE-2.0
  28132. *
  28133. * Unless required by applicable law or agreed to in writing, software
  28134. * distributed under the License is distributed on an "AS IS" BASIS,
  28135. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28136. * See the License for the specific language governing permissions and
  28137. * limitations under the License.
  28138. */
  28139. function registerFirestore(variant, useFetchStreams = true) {
  28140. setSDKVersion(SDK_VERSION$1);
  28141. _registerComponent(new Component('firestore', (container, { instanceIdentifier: databaseId, options: settings }) => {
  28142. const app = container.getProvider('app').getImmediate();
  28143. const firestoreInstance = new Firestore(new FirebaseAuthCredentialsProvider(container.getProvider('auth-internal')), new FirebaseAppCheckTokenProvider(container.getProvider('app-check-internal')), databaseIdFromApp(app, databaseId), app);
  28144. settings = Object.assign({ useFetchStreams }, settings);
  28145. firestoreInstance._setSettings(settings);
  28146. return firestoreInstance;
  28147. }, 'PUBLIC').setMultipleInstances(true));
  28148. registerVersion(name, version$1, variant);
  28149. // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
  28150. registerVersion(name, version$1, 'esm2017');
  28151. }
  28152. /**
  28153. * @license
  28154. * Copyright 2023 Google LLC
  28155. *
  28156. * Licensed under the Apache License, Version 2.0 (the "License");
  28157. * you may not use this file except in compliance with the License.
  28158. * You may obtain a copy of the License at
  28159. *
  28160. * http://www.apache.org/licenses/LICENSE-2.0
  28161. *
  28162. * Unless required by applicable law or agreed to in writing, software
  28163. * distributed under the License is distributed on an "AS IS" BASIS,
  28164. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28165. * See the License for the specific language governing permissions and
  28166. * limitations under the License.
  28167. */
  28168. /**
  28169. * Concrete implementation of the Aggregate type.
  28170. */
  28171. class AggregateImpl {
  28172. constructor(alias, aggregateType, fieldPath) {
  28173. this.alias = alias;
  28174. this.aggregateType = aggregateType;
  28175. this.fieldPath = fieldPath;
  28176. }
  28177. }
  28178. /**
  28179. * @license
  28180. * Copyright 2022 Google LLC
  28181. *
  28182. * Licensed under the Apache License, Version 2.0 (the "License");
  28183. * you may not use this file except in compliance with the License.
  28184. * You may obtain a copy of the License at
  28185. *
  28186. * http://www.apache.org/licenses/LICENSE-2.0
  28187. *
  28188. * Unless required by applicable law or agreed to in writing, software
  28189. * distributed under the License is distributed on an "AS IS" BASIS,
  28190. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28191. * See the License for the specific language governing permissions and
  28192. * limitations under the License.
  28193. */
  28194. /**
  28195. * Represents an aggregation that can be performed by Firestore.
  28196. */
  28197. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  28198. class AggregateField {
  28199. /**
  28200. * Create a new AggregateField<T>
  28201. * @param _aggregateType Specifies the type of aggregation operation to perform.
  28202. * @param _internalFieldPath Optionally specifies the field that is aggregated.
  28203. * @internal
  28204. */
  28205. constructor(
  28206. // TODO (sum/avg) make aggregateType public when the feature is supported
  28207. _aggregateType = 'count', _internalFieldPath) {
  28208. this._aggregateType = _aggregateType;
  28209. this._internalFieldPath = _internalFieldPath;
  28210. /** A type string to uniquely identify instances of this class. */
  28211. this.type = 'AggregateField';
  28212. }
  28213. }
  28214. /**
  28215. * The results of executing an aggregation query.
  28216. */
  28217. class AggregateQuerySnapshot {
  28218. /** @hideconstructor */
  28219. constructor(query, _userDataWriter, _data) {
  28220. this._userDataWriter = _userDataWriter;
  28221. this._data = _data;
  28222. /** A type string to uniquely identify instances of this class. */
  28223. this.type = 'AggregateQuerySnapshot';
  28224. this.query = query;
  28225. }
  28226. /**
  28227. * Returns the results of the aggregations performed over the underlying
  28228. * query.
  28229. *
  28230. * The keys of the returned object will be the same as those of the
  28231. * `AggregateSpec` object specified to the aggregation method, and the values
  28232. * will be the corresponding aggregation result.
  28233. *
  28234. * @returns The results of the aggregations performed over the underlying
  28235. * query.
  28236. */
  28237. data() {
  28238. return this._userDataWriter.convertObjectMap(this._data);
  28239. }
  28240. }
  28241. /**
  28242. * @license
  28243. * Copyright 2020 Google LLC
  28244. *
  28245. * Licensed under the Apache License, Version 2.0 (the "License");
  28246. * you may not use this file except in compliance with the License.
  28247. * You may obtain a copy of the License at
  28248. *
  28249. * http://www.apache.org/licenses/LICENSE-2.0
  28250. *
  28251. * Unless required by applicable law or agreed to in writing, software
  28252. * distributed under the License is distributed on an "AS IS" BASIS,
  28253. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28254. * See the License for the specific language governing permissions and
  28255. * limitations under the License.
  28256. */
  28257. /**
  28258. * An immutable object representing an array of bytes.
  28259. */
  28260. class Bytes {
  28261. /** @hideconstructor */
  28262. constructor(byteString) {
  28263. this._byteString = byteString;
  28264. }
  28265. /**
  28266. * Creates a new `Bytes` object from the given Base64 string, converting it to
  28267. * bytes.
  28268. *
  28269. * @param base64 - The Base64 string used to create the `Bytes` object.
  28270. */
  28271. static fromBase64String(base64) {
  28272. try {
  28273. return new Bytes(ByteString.fromBase64String(base64));
  28274. }
  28275. catch (e) {
  28276. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Failed to construct data from Base64 string: ' + e);
  28277. }
  28278. }
  28279. /**
  28280. * Creates a new `Bytes` object from the given Uint8Array.
  28281. *
  28282. * @param array - The Uint8Array used to create the `Bytes` object.
  28283. */
  28284. static fromUint8Array(array) {
  28285. return new Bytes(ByteString.fromUint8Array(array));
  28286. }
  28287. /**
  28288. * Returns the underlying bytes as a Base64-encoded string.
  28289. *
  28290. * @returns The Base64-encoded string created from the `Bytes` object.
  28291. */
  28292. toBase64() {
  28293. return this._byteString.toBase64();
  28294. }
  28295. /**
  28296. * Returns the underlying bytes in a new `Uint8Array`.
  28297. *
  28298. * @returns The Uint8Array created from the `Bytes` object.
  28299. */
  28300. toUint8Array() {
  28301. return this._byteString.toUint8Array();
  28302. }
  28303. /**
  28304. * Returns a string representation of the `Bytes` object.
  28305. *
  28306. * @returns A string representation of the `Bytes` object.
  28307. */
  28308. toString() {
  28309. return 'Bytes(base64: ' + this.toBase64() + ')';
  28310. }
  28311. /**
  28312. * Returns true if this `Bytes` object is equal to the provided one.
  28313. *
  28314. * @param other - The `Bytes` object to compare against.
  28315. * @returns true if this `Bytes` object is equal to the provided one.
  28316. */
  28317. isEqual(other) {
  28318. return this._byteString.isEqual(other._byteString);
  28319. }
  28320. }
  28321. /**
  28322. * @license
  28323. * Copyright 2020 Google LLC
  28324. *
  28325. * Licensed under the Apache License, Version 2.0 (the "License");
  28326. * you may not use this file except in compliance with the License.
  28327. * You may obtain a copy of the License at
  28328. *
  28329. * http://www.apache.org/licenses/LICENSE-2.0
  28330. *
  28331. * Unless required by applicable law or agreed to in writing, software
  28332. * distributed under the License is distributed on an "AS IS" BASIS,
  28333. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28334. * See the License for the specific language governing permissions and
  28335. * limitations under the License.
  28336. */
  28337. /**
  28338. * A `FieldPath` refers to a field in a document. The path may consist of a
  28339. * single field name (referring to a top-level field in the document), or a
  28340. * list of field names (referring to a nested field in the document).
  28341. *
  28342. * Create a `FieldPath` by providing field names. If more than one field
  28343. * name is provided, the path will point to a nested field in a document.
  28344. */
  28345. class FieldPath {
  28346. /**
  28347. * Creates a `FieldPath` from the provided field names. If more than one field
  28348. * name is provided, the path will point to a nested field in a document.
  28349. *
  28350. * @param fieldNames - A list of field names.
  28351. */
  28352. constructor(...fieldNames) {
  28353. for (let i = 0; i < fieldNames.length; ++i) {
  28354. if (fieldNames[i].length === 0) {
  28355. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid field name at argument $(i + 1). ` +
  28356. 'Field names must not be empty.');
  28357. }
  28358. }
  28359. this._internalPath = new FieldPath$1(fieldNames);
  28360. }
  28361. /**
  28362. * Returns true if this `FieldPath` is equal to the provided one.
  28363. *
  28364. * @param other - The `FieldPath` to compare against.
  28365. * @returns true if this `FieldPath` is equal to the provided one.
  28366. */
  28367. isEqual(other) {
  28368. return this._internalPath.isEqual(other._internalPath);
  28369. }
  28370. }
  28371. /**
  28372. * Returns a special sentinel `FieldPath` to refer to the ID of a document.
  28373. * It can be used in queries to sort or filter by the document ID.
  28374. */
  28375. function documentId() {
  28376. return new FieldPath(DOCUMENT_KEY_NAME);
  28377. }
  28378. /**
  28379. * @license
  28380. * Copyright 2020 Google LLC
  28381. *
  28382. * Licensed under the Apache License, Version 2.0 (the "License");
  28383. * you may not use this file except in compliance with the License.
  28384. * You may obtain a copy of the License at
  28385. *
  28386. * http://www.apache.org/licenses/LICENSE-2.0
  28387. *
  28388. * Unless required by applicable law or agreed to in writing, software
  28389. * distributed under the License is distributed on an "AS IS" BASIS,
  28390. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28391. * See the License for the specific language governing permissions and
  28392. * limitations under the License.
  28393. */
  28394. /**
  28395. * Sentinel values that can be used when writing document fields with `set()`
  28396. * or `update()`.
  28397. */
  28398. class FieldValue {
  28399. /**
  28400. * @param _methodName - The public API endpoint that returns this class.
  28401. * @hideconstructor
  28402. */
  28403. constructor(_methodName) {
  28404. this._methodName = _methodName;
  28405. }
  28406. }
  28407. /**
  28408. * @license
  28409. * Copyright 2017 Google LLC
  28410. *
  28411. * Licensed under the Apache License, Version 2.0 (the "License");
  28412. * you may not use this file except in compliance with the License.
  28413. * You may obtain a copy of the License at
  28414. *
  28415. * http://www.apache.org/licenses/LICENSE-2.0
  28416. *
  28417. * Unless required by applicable law or agreed to in writing, software
  28418. * distributed under the License is distributed on an "AS IS" BASIS,
  28419. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28420. * See the License for the specific language governing permissions and
  28421. * limitations under the License.
  28422. */
  28423. /**
  28424. * An immutable object representing a geographic location in Firestore. The
  28425. * location is represented as latitude/longitude pair.
  28426. *
  28427. * Latitude values are in the range of [-90, 90].
  28428. * Longitude values are in the range of [-180, 180].
  28429. */
  28430. class GeoPoint {
  28431. /**
  28432. * Creates a new immutable `GeoPoint` object with the provided latitude and
  28433. * longitude values.
  28434. * @param latitude - The latitude as number between -90 and 90.
  28435. * @param longitude - The longitude as number between -180 and 180.
  28436. */
  28437. constructor(latitude, longitude) {
  28438. if (!isFinite(latitude) || latitude < -90 || latitude > 90) {
  28439. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Latitude must be a number between -90 and 90, but was: ' + latitude);
  28440. }
  28441. if (!isFinite(longitude) || longitude < -180 || longitude > 180) {
  28442. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Longitude must be a number between -180 and 180, but was: ' + longitude);
  28443. }
  28444. this._lat = latitude;
  28445. this._long = longitude;
  28446. }
  28447. /**
  28448. * The latitude of this `GeoPoint` instance.
  28449. */
  28450. get latitude() {
  28451. return this._lat;
  28452. }
  28453. /**
  28454. * The longitude of this `GeoPoint` instance.
  28455. */
  28456. get longitude() {
  28457. return this._long;
  28458. }
  28459. /**
  28460. * Returns true if this `GeoPoint` is equal to the provided one.
  28461. *
  28462. * @param other - The `GeoPoint` to compare against.
  28463. * @returns true if this `GeoPoint` is equal to the provided one.
  28464. */
  28465. isEqual(other) {
  28466. return this._lat === other._lat && this._long === other._long;
  28467. }
  28468. /** Returns a JSON-serializable representation of this GeoPoint. */
  28469. toJSON() {
  28470. return { latitude: this._lat, longitude: this._long };
  28471. }
  28472. /**
  28473. * Actually private to JS consumers of our API, so this function is prefixed
  28474. * with an underscore.
  28475. */
  28476. _compareTo(other) {
  28477. return (primitiveComparator(this._lat, other._lat) ||
  28478. primitiveComparator(this._long, other._long));
  28479. }
  28480. }
  28481. /**
  28482. * @license
  28483. * Copyright 2017 Google LLC
  28484. *
  28485. * Licensed under the Apache License, Version 2.0 (the "License");
  28486. * you may not use this file except in compliance with the License.
  28487. * You may obtain a copy of the License at
  28488. *
  28489. * http://www.apache.org/licenses/LICENSE-2.0
  28490. *
  28491. * Unless required by applicable law or agreed to in writing, software
  28492. * distributed under the License is distributed on an "AS IS" BASIS,
  28493. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28494. * See the License for the specific language governing permissions and
  28495. * limitations under the License.
  28496. */
  28497. const RESERVED_FIELD_REGEX = /^__.*__$/;
  28498. /** The result of parsing document data (e.g. for a setData call). */
  28499. class ParsedSetData {
  28500. constructor(data, fieldMask, fieldTransforms) {
  28501. this.data = data;
  28502. this.fieldMask = fieldMask;
  28503. this.fieldTransforms = fieldTransforms;
  28504. }
  28505. toMutation(key, precondition) {
  28506. if (this.fieldMask !== null) {
  28507. return new PatchMutation(key, this.data, this.fieldMask, precondition, this.fieldTransforms);
  28508. }
  28509. else {
  28510. return new SetMutation(key, this.data, precondition, this.fieldTransforms);
  28511. }
  28512. }
  28513. }
  28514. /** The result of parsing "update" data (i.e. for an updateData call). */
  28515. class ParsedUpdateData {
  28516. constructor(data,
  28517. // The fieldMask does not include document transforms.
  28518. fieldMask, fieldTransforms) {
  28519. this.data = data;
  28520. this.fieldMask = fieldMask;
  28521. this.fieldTransforms = fieldTransforms;
  28522. }
  28523. toMutation(key, precondition) {
  28524. return new PatchMutation(key, this.data, this.fieldMask, precondition, this.fieldTransforms);
  28525. }
  28526. }
  28527. function isWrite(dataSource) {
  28528. switch (dataSource) {
  28529. case 0 /* UserDataSource.Set */: // fall through
  28530. case 2 /* UserDataSource.MergeSet */: // fall through
  28531. case 1 /* UserDataSource.Update */:
  28532. return true;
  28533. case 3 /* UserDataSource.Argument */:
  28534. case 4 /* UserDataSource.ArrayArgument */:
  28535. return false;
  28536. default:
  28537. throw fail();
  28538. }
  28539. }
  28540. /** A "context" object passed around while parsing user data. */
  28541. class ParseContextImpl {
  28542. /**
  28543. * Initializes a ParseContext with the given source and path.
  28544. *
  28545. * @param settings - The settings for the parser.
  28546. * @param databaseId - The database ID of the Firestore instance.
  28547. * @param serializer - The serializer to use to generate the Value proto.
  28548. * @param ignoreUndefinedProperties - Whether to ignore undefined properties
  28549. * rather than throw.
  28550. * @param fieldTransforms - A mutable list of field transforms encountered
  28551. * while parsing the data.
  28552. * @param fieldMask - A mutable list of field paths encountered while parsing
  28553. * the data.
  28554. *
  28555. * TODO(b/34871131): We don't support array paths right now, so path can be
  28556. * null to indicate the context represents any location within an array (in
  28557. * which case certain features will not work and errors will be somewhat
  28558. * compromised).
  28559. */
  28560. constructor(settings, databaseId, serializer, ignoreUndefinedProperties, fieldTransforms, fieldMask) {
  28561. this.settings = settings;
  28562. this.databaseId = databaseId;
  28563. this.serializer = serializer;
  28564. this.ignoreUndefinedProperties = ignoreUndefinedProperties;
  28565. // Minor hack: If fieldTransforms is undefined, we assume this is an
  28566. // external call and we need to validate the entire path.
  28567. if (fieldTransforms === undefined) {
  28568. this.validatePath();
  28569. }
  28570. this.fieldTransforms = fieldTransforms || [];
  28571. this.fieldMask = fieldMask || [];
  28572. }
  28573. get path() {
  28574. return this.settings.path;
  28575. }
  28576. get dataSource() {
  28577. return this.settings.dataSource;
  28578. }
  28579. /** Returns a new context with the specified settings overwritten. */
  28580. contextWith(configuration) {
  28581. return new ParseContextImpl(Object.assign(Object.assign({}, this.settings), configuration), this.databaseId, this.serializer, this.ignoreUndefinedProperties, this.fieldTransforms, this.fieldMask);
  28582. }
  28583. childContextForField(field) {
  28584. var _a;
  28585. const childPath = (_a = this.path) === null || _a === void 0 ? void 0 : _a.child(field);
  28586. const context = this.contextWith({ path: childPath, arrayElement: false });
  28587. context.validatePathSegment(field);
  28588. return context;
  28589. }
  28590. childContextForFieldPath(field) {
  28591. var _a;
  28592. const childPath = (_a = this.path) === null || _a === void 0 ? void 0 : _a.child(field);
  28593. const context = this.contextWith({ path: childPath, arrayElement: false });
  28594. context.validatePath();
  28595. return context;
  28596. }
  28597. childContextForArray(index) {
  28598. // TODO(b/34871131): We don't support array paths right now; so make path
  28599. // undefined.
  28600. return this.contextWith({ path: undefined, arrayElement: true });
  28601. }
  28602. createError(reason) {
  28603. return createError(reason, this.settings.methodName, this.settings.hasConverter || false, this.path, this.settings.targetDoc);
  28604. }
  28605. /** Returns 'true' if 'fieldPath' was traversed when creating this context. */
  28606. contains(fieldPath) {
  28607. return (this.fieldMask.find(field => fieldPath.isPrefixOf(field)) !== undefined ||
  28608. this.fieldTransforms.find(transform => fieldPath.isPrefixOf(transform.field)) !== undefined);
  28609. }
  28610. validatePath() {
  28611. // TODO(b/34871131): Remove null check once we have proper paths for fields
  28612. // within arrays.
  28613. if (!this.path) {
  28614. return;
  28615. }
  28616. for (let i = 0; i < this.path.length; i++) {
  28617. this.validatePathSegment(this.path.get(i));
  28618. }
  28619. }
  28620. validatePathSegment(segment) {
  28621. if (segment.length === 0) {
  28622. throw this.createError('Document fields must not be empty');
  28623. }
  28624. if (isWrite(this.dataSource) && RESERVED_FIELD_REGEX.test(segment)) {
  28625. throw this.createError('Document fields cannot begin and end with "__"');
  28626. }
  28627. }
  28628. }
  28629. /**
  28630. * Helper for parsing raw user input (provided via the API) into internal model
  28631. * classes.
  28632. */
  28633. class UserDataReader {
  28634. constructor(databaseId, ignoreUndefinedProperties, serializer) {
  28635. this.databaseId = databaseId;
  28636. this.ignoreUndefinedProperties = ignoreUndefinedProperties;
  28637. this.serializer = serializer || newSerializer(databaseId);
  28638. }
  28639. /** Creates a new top-level parse context. */
  28640. createContext(dataSource, methodName, targetDoc, hasConverter = false) {
  28641. return new ParseContextImpl({
  28642. dataSource,
  28643. methodName,
  28644. targetDoc,
  28645. path: FieldPath$1.emptyPath(),
  28646. arrayElement: false,
  28647. hasConverter
  28648. }, this.databaseId, this.serializer, this.ignoreUndefinedProperties);
  28649. }
  28650. }
  28651. function newUserDataReader(firestore) {
  28652. const settings = firestore._freezeSettings();
  28653. const serializer = newSerializer(firestore._databaseId);
  28654. return new UserDataReader(firestore._databaseId, !!settings.ignoreUndefinedProperties, serializer);
  28655. }
  28656. /** Parse document data from a set() call. */
  28657. function parseSetData(userDataReader, methodName, targetDoc, input, hasConverter, options = {}) {
  28658. const context = userDataReader.createContext(options.merge || options.mergeFields
  28659. ? 2 /* UserDataSource.MergeSet */
  28660. : 0 /* UserDataSource.Set */, methodName, targetDoc, hasConverter);
  28661. validatePlainObject('Data must be an object, but it was:', context, input);
  28662. const updateData = parseObject(input, context);
  28663. let fieldMask;
  28664. let fieldTransforms;
  28665. if (options.merge) {
  28666. fieldMask = new FieldMask(context.fieldMask);
  28667. fieldTransforms = context.fieldTransforms;
  28668. }
  28669. else if (options.mergeFields) {
  28670. const validatedFieldPaths = [];
  28671. for (const stringOrFieldPath of options.mergeFields) {
  28672. const fieldPath = fieldPathFromArgument$1(methodName, stringOrFieldPath, targetDoc);
  28673. if (!context.contains(fieldPath)) {
  28674. throw new FirestoreError(Code.INVALID_ARGUMENT, `Field '${fieldPath}' is specified in your field mask but missing from your input data.`);
  28675. }
  28676. if (!fieldMaskContains(validatedFieldPaths, fieldPath)) {
  28677. validatedFieldPaths.push(fieldPath);
  28678. }
  28679. }
  28680. fieldMask = new FieldMask(validatedFieldPaths);
  28681. fieldTransforms = context.fieldTransforms.filter(transform => fieldMask.covers(transform.field));
  28682. }
  28683. else {
  28684. fieldMask = null;
  28685. fieldTransforms = context.fieldTransforms;
  28686. }
  28687. return new ParsedSetData(new ObjectValue(updateData), fieldMask, fieldTransforms);
  28688. }
  28689. class DeleteFieldValueImpl extends FieldValue {
  28690. _toFieldTransform(context) {
  28691. if (context.dataSource === 2 /* UserDataSource.MergeSet */) {
  28692. // No transform to add for a delete, but we need to add it to our
  28693. // fieldMask so it gets deleted.
  28694. context.fieldMask.push(context.path);
  28695. }
  28696. else if (context.dataSource === 1 /* UserDataSource.Update */) {
  28697. throw context.createError(`${this._methodName}() can only appear at the top level ` +
  28698. 'of your update data');
  28699. }
  28700. else {
  28701. // We shouldn't encounter delete sentinels for queries or non-merge set() calls.
  28702. throw context.createError(`${this._methodName}() cannot be used with set() unless you pass ` +
  28703. '{merge:true}');
  28704. }
  28705. return null;
  28706. }
  28707. isEqual(other) {
  28708. return other instanceof DeleteFieldValueImpl;
  28709. }
  28710. }
  28711. /**
  28712. * Creates a child context for parsing SerializableFieldValues.
  28713. *
  28714. * This is different than calling `ParseContext.contextWith` because it keeps
  28715. * the fieldTransforms and fieldMask separate.
  28716. *
  28717. * The created context has its `dataSource` set to `UserDataSource.Argument`.
  28718. * Although these values are used with writes, any elements in these FieldValues
  28719. * are not considered writes since they cannot contain any FieldValue sentinels,
  28720. * etc.
  28721. *
  28722. * @param fieldValue - The sentinel FieldValue for which to create a child
  28723. * context.
  28724. * @param context - The parent context.
  28725. * @param arrayElement - Whether or not the FieldValue has an array.
  28726. */
  28727. function createSentinelChildContext(fieldValue, context, arrayElement) {
  28728. return new ParseContextImpl({
  28729. dataSource: 3 /* UserDataSource.Argument */,
  28730. targetDoc: context.settings.targetDoc,
  28731. methodName: fieldValue._methodName,
  28732. arrayElement
  28733. }, context.databaseId, context.serializer, context.ignoreUndefinedProperties);
  28734. }
  28735. class ServerTimestampFieldValueImpl extends FieldValue {
  28736. _toFieldTransform(context) {
  28737. return new FieldTransform(context.path, new ServerTimestampTransform());
  28738. }
  28739. isEqual(other) {
  28740. return other instanceof ServerTimestampFieldValueImpl;
  28741. }
  28742. }
  28743. class ArrayUnionFieldValueImpl extends FieldValue {
  28744. constructor(methodName, _elements) {
  28745. super(methodName);
  28746. this._elements = _elements;
  28747. }
  28748. _toFieldTransform(context) {
  28749. const parseContext = createSentinelChildContext(this, context,
  28750. /*array=*/ true);
  28751. const parsedElements = this._elements.map(element => parseData(element, parseContext));
  28752. const arrayUnion = new ArrayUnionTransformOperation(parsedElements);
  28753. return new FieldTransform(context.path, arrayUnion);
  28754. }
  28755. isEqual(other) {
  28756. // TODO(mrschmidt): Implement isEquals
  28757. return this === other;
  28758. }
  28759. }
  28760. class ArrayRemoveFieldValueImpl extends FieldValue {
  28761. constructor(methodName, _elements) {
  28762. super(methodName);
  28763. this._elements = _elements;
  28764. }
  28765. _toFieldTransform(context) {
  28766. const parseContext = createSentinelChildContext(this, context,
  28767. /*array=*/ true);
  28768. const parsedElements = this._elements.map(element => parseData(element, parseContext));
  28769. const arrayUnion = new ArrayRemoveTransformOperation(parsedElements);
  28770. return new FieldTransform(context.path, arrayUnion);
  28771. }
  28772. isEqual(other) {
  28773. // TODO(mrschmidt): Implement isEquals
  28774. return this === other;
  28775. }
  28776. }
  28777. class NumericIncrementFieldValueImpl extends FieldValue {
  28778. constructor(methodName, _operand) {
  28779. super(methodName);
  28780. this._operand = _operand;
  28781. }
  28782. _toFieldTransform(context) {
  28783. const numericIncrement = new NumericIncrementTransformOperation(context.serializer, toNumber(context.serializer, this._operand));
  28784. return new FieldTransform(context.path, numericIncrement);
  28785. }
  28786. isEqual(other) {
  28787. // TODO(mrschmidt): Implement isEquals
  28788. return this === other;
  28789. }
  28790. }
  28791. /** Parse update data from an update() call. */
  28792. function parseUpdateData(userDataReader, methodName, targetDoc, input) {
  28793. const context = userDataReader.createContext(1 /* UserDataSource.Update */, methodName, targetDoc);
  28794. validatePlainObject('Data must be an object, but it was:', context, input);
  28795. const fieldMaskPaths = [];
  28796. const updateData = ObjectValue.empty();
  28797. forEach(input, (key, value) => {
  28798. const path = fieldPathFromDotSeparatedString(methodName, key, targetDoc);
  28799. // For Compat types, we have to "extract" the underlying types before
  28800. // performing validation.
  28801. value = getModularInstance(value);
  28802. const childContext = context.childContextForFieldPath(path);
  28803. if (value instanceof DeleteFieldValueImpl) {
  28804. // Add it to the field mask, but don't add anything to updateData.
  28805. fieldMaskPaths.push(path);
  28806. }
  28807. else {
  28808. const parsedValue = parseData(value, childContext);
  28809. if (parsedValue != null) {
  28810. fieldMaskPaths.push(path);
  28811. updateData.set(path, parsedValue);
  28812. }
  28813. }
  28814. });
  28815. const mask = new FieldMask(fieldMaskPaths);
  28816. return new ParsedUpdateData(updateData, mask, context.fieldTransforms);
  28817. }
  28818. /** Parse update data from a list of field/value arguments. */
  28819. function parseUpdateVarargs(userDataReader, methodName, targetDoc, field, value, moreFieldsAndValues) {
  28820. const context = userDataReader.createContext(1 /* UserDataSource.Update */, methodName, targetDoc);
  28821. const keys = [fieldPathFromArgument$1(methodName, field, targetDoc)];
  28822. const values = [value];
  28823. if (moreFieldsAndValues.length % 2 !== 0) {
  28824. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${methodName}() needs to be called with an even number ` +
  28825. 'of arguments that alternate between field names and values.');
  28826. }
  28827. for (let i = 0; i < moreFieldsAndValues.length; i += 2) {
  28828. keys.push(fieldPathFromArgument$1(methodName, moreFieldsAndValues[i]));
  28829. values.push(moreFieldsAndValues[i + 1]);
  28830. }
  28831. const fieldMaskPaths = [];
  28832. const updateData = ObjectValue.empty();
  28833. // We iterate in reverse order to pick the last value for a field if the
  28834. // user specified the field multiple times.
  28835. for (let i = keys.length - 1; i >= 0; --i) {
  28836. if (!fieldMaskContains(fieldMaskPaths, keys[i])) {
  28837. const path = keys[i];
  28838. let value = values[i];
  28839. // For Compat types, we have to "extract" the underlying types before
  28840. // performing validation.
  28841. value = getModularInstance(value);
  28842. const childContext = context.childContextForFieldPath(path);
  28843. if (value instanceof DeleteFieldValueImpl) {
  28844. // Add it to the field mask, but don't add anything to updateData.
  28845. fieldMaskPaths.push(path);
  28846. }
  28847. else {
  28848. const parsedValue = parseData(value, childContext);
  28849. if (parsedValue != null) {
  28850. fieldMaskPaths.push(path);
  28851. updateData.set(path, parsedValue);
  28852. }
  28853. }
  28854. }
  28855. }
  28856. const mask = new FieldMask(fieldMaskPaths);
  28857. return new ParsedUpdateData(updateData, mask, context.fieldTransforms);
  28858. }
  28859. /**
  28860. * Parse a "query value" (e.g. value in a where filter or a value in a cursor
  28861. * bound).
  28862. *
  28863. * @param allowArrays - Whether the query value is an array that may directly
  28864. * contain additional arrays (e.g. the operand of an `in` query).
  28865. */
  28866. function parseQueryValue(userDataReader, methodName, input, allowArrays = false) {
  28867. const context = userDataReader.createContext(allowArrays ? 4 /* UserDataSource.ArrayArgument */ : 3 /* UserDataSource.Argument */, methodName);
  28868. const parsed = parseData(input, context);
  28869. return parsed;
  28870. }
  28871. /**
  28872. * Parses user data to Protobuf Values.
  28873. *
  28874. * @param input - Data to be parsed.
  28875. * @param context - A context object representing the current path being parsed,
  28876. * the source of the data being parsed, etc.
  28877. * @returns The parsed value, or null if the value was a FieldValue sentinel
  28878. * that should not be included in the resulting parsed data.
  28879. */
  28880. function parseData(input, context) {
  28881. // Unwrap the API type from the Compat SDK. This will return the API type
  28882. // from firestore-exp.
  28883. input = getModularInstance(input);
  28884. if (looksLikeJsonObject(input)) {
  28885. validatePlainObject('Unsupported field value:', context, input);
  28886. return parseObject(input, context);
  28887. }
  28888. else if (input instanceof FieldValue) {
  28889. // FieldValues usually parse into transforms (except deleteField())
  28890. // in which case we do not want to include this field in our parsed data
  28891. // (as doing so will overwrite the field directly prior to the transform
  28892. // trying to transform it). So we don't add this location to
  28893. // context.fieldMask and we return null as our parsing result.
  28894. parseSentinelFieldValue(input, context);
  28895. return null;
  28896. }
  28897. else if (input === undefined && context.ignoreUndefinedProperties) {
  28898. // If the input is undefined it can never participate in the fieldMask, so
  28899. // don't handle this below. If `ignoreUndefinedProperties` is false,
  28900. // `parseScalarValue` will reject an undefined value.
  28901. return null;
  28902. }
  28903. else {
  28904. // If context.path is null we are inside an array and we don't support
  28905. // field mask paths more granular than the top-level array.
  28906. if (context.path) {
  28907. context.fieldMask.push(context.path);
  28908. }
  28909. if (input instanceof Array) {
  28910. // TODO(b/34871131): Include the path containing the array in the error
  28911. // message.
  28912. // In the case of IN queries, the parsed data is an array (representing
  28913. // the set of values to be included for the IN query) that may directly
  28914. // contain additional arrays (each representing an individual field
  28915. // value), so we disable this validation.
  28916. if (context.settings.arrayElement &&
  28917. context.dataSource !== 4 /* UserDataSource.ArrayArgument */) {
  28918. throw context.createError('Nested arrays are not supported');
  28919. }
  28920. return parseArray(input, context);
  28921. }
  28922. else {
  28923. return parseScalarValue(input, context);
  28924. }
  28925. }
  28926. }
  28927. function parseObject(obj, context) {
  28928. const fields = {};
  28929. if (isEmpty(obj)) {
  28930. // If we encounter an empty object, we explicitly add it to the update
  28931. // mask to ensure that the server creates a map entry.
  28932. if (context.path && context.path.length > 0) {
  28933. context.fieldMask.push(context.path);
  28934. }
  28935. }
  28936. else {
  28937. forEach(obj, (key, val) => {
  28938. const parsedValue = parseData(val, context.childContextForField(key));
  28939. if (parsedValue != null) {
  28940. fields[key] = parsedValue;
  28941. }
  28942. });
  28943. }
  28944. return { mapValue: { fields } };
  28945. }
  28946. function parseArray(array, context) {
  28947. const values = [];
  28948. let entryIndex = 0;
  28949. for (const entry of array) {
  28950. let parsedEntry = parseData(entry, context.childContextForArray(entryIndex));
  28951. if (parsedEntry == null) {
  28952. // Just include nulls in the array for fields being replaced with a
  28953. // sentinel.
  28954. parsedEntry = { nullValue: 'NULL_VALUE' };
  28955. }
  28956. values.push(parsedEntry);
  28957. entryIndex++;
  28958. }
  28959. return { arrayValue: { values } };
  28960. }
  28961. /**
  28962. * "Parses" the provided FieldValueImpl, adding any necessary transforms to
  28963. * context.fieldTransforms.
  28964. */
  28965. function parseSentinelFieldValue(value, context) {
  28966. // Sentinels are only supported with writes, and not within arrays.
  28967. if (!isWrite(context.dataSource)) {
  28968. throw context.createError(`${value._methodName}() can only be used with update() and set()`);
  28969. }
  28970. if (!context.path) {
  28971. throw context.createError(`${value._methodName}() is not currently supported inside arrays`);
  28972. }
  28973. const fieldTransform = value._toFieldTransform(context);
  28974. if (fieldTransform) {
  28975. context.fieldTransforms.push(fieldTransform);
  28976. }
  28977. }
  28978. /**
  28979. * Helper to parse a scalar value (i.e. not an Object, Array, or FieldValue)
  28980. *
  28981. * @returns The parsed value
  28982. */
  28983. function parseScalarValue(value, context) {
  28984. value = getModularInstance(value);
  28985. if (value === null) {
  28986. return { nullValue: 'NULL_VALUE' };
  28987. }
  28988. else if (typeof value === 'number') {
  28989. return toNumber(context.serializer, value);
  28990. }
  28991. else if (typeof value === 'boolean') {
  28992. return { booleanValue: value };
  28993. }
  28994. else if (typeof value === 'string') {
  28995. return { stringValue: value };
  28996. }
  28997. else if (value instanceof Date) {
  28998. const timestamp = Timestamp.fromDate(value);
  28999. return {
  29000. timestampValue: toTimestamp(context.serializer, timestamp)
  29001. };
  29002. }
  29003. else if (value instanceof Timestamp) {
  29004. // Firestore backend truncates precision down to microseconds. To ensure
  29005. // offline mode works the same with regards to truncation, perform the
  29006. // truncation immediately without waiting for the backend to do that.
  29007. const timestamp = new Timestamp(value.seconds, Math.floor(value.nanoseconds / 1000) * 1000);
  29008. return {
  29009. timestampValue: toTimestamp(context.serializer, timestamp)
  29010. };
  29011. }
  29012. else if (value instanceof GeoPoint) {
  29013. return {
  29014. geoPointValue: {
  29015. latitude: value.latitude,
  29016. longitude: value.longitude
  29017. }
  29018. };
  29019. }
  29020. else if (value instanceof Bytes) {
  29021. return { bytesValue: toBytes(context.serializer, value._byteString) };
  29022. }
  29023. else if (value instanceof DocumentReference) {
  29024. const thisDb = context.databaseId;
  29025. const otherDb = value.firestore._databaseId;
  29026. if (!otherDb.isEqual(thisDb)) {
  29027. throw context.createError('Document reference is for database ' +
  29028. `${otherDb.projectId}/${otherDb.database} but should be ` +
  29029. `for database ${thisDb.projectId}/${thisDb.database}`);
  29030. }
  29031. return {
  29032. referenceValue: toResourceName(value.firestore._databaseId || context.databaseId, value._key.path)
  29033. };
  29034. }
  29035. else {
  29036. throw context.createError(`Unsupported field value: ${valueDescription(value)}`);
  29037. }
  29038. }
  29039. /**
  29040. * Checks whether an object looks like a JSON object that should be converted
  29041. * into a struct. Normal class/prototype instances are considered to look like
  29042. * JSON objects since they should be converted to a struct value. Arrays, Dates,
  29043. * GeoPoints, etc. are not considered to look like JSON objects since they map
  29044. * to specific FieldValue types other than ObjectValue.
  29045. */
  29046. function looksLikeJsonObject(input) {
  29047. return (typeof input === 'object' &&
  29048. input !== null &&
  29049. !(input instanceof Array) &&
  29050. !(input instanceof Date) &&
  29051. !(input instanceof Timestamp) &&
  29052. !(input instanceof GeoPoint) &&
  29053. !(input instanceof Bytes) &&
  29054. !(input instanceof DocumentReference) &&
  29055. !(input instanceof FieldValue));
  29056. }
  29057. function validatePlainObject(message, context, input) {
  29058. if (!looksLikeJsonObject(input) || !isPlainObject(input)) {
  29059. const description = valueDescription(input);
  29060. if (description === 'an object') {
  29061. // Massage the error if it was an object.
  29062. throw context.createError(message + ' a custom object');
  29063. }
  29064. else {
  29065. throw context.createError(message + ' ' + description);
  29066. }
  29067. }
  29068. }
  29069. /**
  29070. * Helper that calls fromDotSeparatedString() but wraps any error thrown.
  29071. */
  29072. function fieldPathFromArgument$1(methodName, path, targetDoc) {
  29073. // If required, replace the FieldPath Compat class with with the firestore-exp
  29074. // FieldPath.
  29075. path = getModularInstance(path);
  29076. if (path instanceof FieldPath) {
  29077. return path._internalPath;
  29078. }
  29079. else if (typeof path === 'string') {
  29080. return fieldPathFromDotSeparatedString(methodName, path);
  29081. }
  29082. else {
  29083. const message = 'Field path arguments must be of type string or ';
  29084. throw createError(message, methodName,
  29085. /* hasConverter= */ false,
  29086. /* path= */ undefined, targetDoc);
  29087. }
  29088. }
  29089. /**
  29090. * Matches any characters in a field path string that are reserved.
  29091. */
  29092. const FIELD_PATH_RESERVED = new RegExp('[~\\*/\\[\\]]');
  29093. /**
  29094. * Wraps fromDotSeparatedString with an error message about the method that
  29095. * was thrown.
  29096. * @param methodName - The publicly visible method name
  29097. * @param path - The dot-separated string form of a field path which will be
  29098. * split on dots.
  29099. * @param targetDoc - The document against which the field path will be
  29100. * evaluated.
  29101. */
  29102. function fieldPathFromDotSeparatedString(methodName, path, targetDoc) {
  29103. const found = path.search(FIELD_PATH_RESERVED);
  29104. if (found >= 0) {
  29105. throw createError(`Invalid field path (${path}). Paths must not contain ` +
  29106. `'~', '*', '/', '[', or ']'`, methodName,
  29107. /* hasConverter= */ false,
  29108. /* path= */ undefined, targetDoc);
  29109. }
  29110. try {
  29111. return new FieldPath(...path.split('.'))._internalPath;
  29112. }
  29113. catch (e) {
  29114. throw createError(`Invalid field path (${path}). Paths must not be empty, ` +
  29115. `begin with '.', end with '.', or contain '..'`, methodName,
  29116. /* hasConverter= */ false,
  29117. /* path= */ undefined, targetDoc);
  29118. }
  29119. }
  29120. function createError(reason, methodName, hasConverter, path, targetDoc) {
  29121. const hasPath = path && !path.isEmpty();
  29122. const hasDocument = targetDoc !== undefined;
  29123. let message = `Function ${methodName}() called with invalid data`;
  29124. if (hasConverter) {
  29125. message += ' (via `toFirestore()`)';
  29126. }
  29127. message += '. ';
  29128. let description = '';
  29129. if (hasPath || hasDocument) {
  29130. description += ' (found';
  29131. if (hasPath) {
  29132. description += ` in field ${path}`;
  29133. }
  29134. if (hasDocument) {
  29135. description += ` in document ${targetDoc}`;
  29136. }
  29137. description += ')';
  29138. }
  29139. return new FirestoreError(Code.INVALID_ARGUMENT, message + reason + description);
  29140. }
  29141. /** Checks `haystack` if FieldPath `needle` is present. Runs in O(n). */
  29142. function fieldMaskContains(haystack, needle) {
  29143. return haystack.some(v => v.isEqual(needle));
  29144. }
  29145. /**
  29146. * @license
  29147. * Copyright 2020 Google LLC
  29148. *
  29149. * Licensed under the Apache License, Version 2.0 (the "License");
  29150. * you may not use this file except in compliance with the License.
  29151. * You may obtain a copy of the License at
  29152. *
  29153. * http://www.apache.org/licenses/LICENSE-2.0
  29154. *
  29155. * Unless required by applicable law or agreed to in writing, software
  29156. * distributed under the License is distributed on an "AS IS" BASIS,
  29157. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29158. * See the License for the specific language governing permissions and
  29159. * limitations under the License.
  29160. */
  29161. /**
  29162. * A `DocumentSnapshot` contains data read from a document in your Firestore
  29163. * database. The data can be extracted with `.data()` or `.get(<field>)` to
  29164. * get a specific field.
  29165. *
  29166. * For a `DocumentSnapshot` that points to a non-existing document, any data
  29167. * access will return 'undefined'. You can use the `exists()` method to
  29168. * explicitly verify a document's existence.
  29169. */
  29170. class DocumentSnapshot$1 {
  29171. // Note: This class is stripped down version of the DocumentSnapshot in
  29172. // the legacy SDK. The changes are:
  29173. // - No support for SnapshotMetadata.
  29174. // - No support for SnapshotOptions.
  29175. /** @hideconstructor protected */
  29176. constructor(_firestore, _userDataWriter, _key, _document, _converter) {
  29177. this._firestore = _firestore;
  29178. this._userDataWriter = _userDataWriter;
  29179. this._key = _key;
  29180. this._document = _document;
  29181. this._converter = _converter;
  29182. }
  29183. /** Property of the `DocumentSnapshot` that provides the document's ID. */
  29184. get id() {
  29185. return this._key.path.lastSegment();
  29186. }
  29187. /**
  29188. * The `DocumentReference` for the document included in the `DocumentSnapshot`.
  29189. */
  29190. get ref() {
  29191. return new DocumentReference(this._firestore, this._converter, this._key);
  29192. }
  29193. /**
  29194. * Signals whether or not the document at the snapshot's location exists.
  29195. *
  29196. * @returns true if the document exists.
  29197. */
  29198. exists() {
  29199. return this._document !== null;
  29200. }
  29201. /**
  29202. * Retrieves all fields in the document as an `Object`. Returns `undefined` if
  29203. * the document doesn't exist.
  29204. *
  29205. * @returns An `Object` containing all fields in the document or `undefined`
  29206. * if the document doesn't exist.
  29207. */
  29208. data() {
  29209. if (!this._document) {
  29210. return undefined;
  29211. }
  29212. else if (this._converter) {
  29213. // We only want to use the converter and create a new DocumentSnapshot
  29214. // if a converter has been provided.
  29215. const snapshot = new QueryDocumentSnapshot$1(this._firestore, this._userDataWriter, this._key, this._document,
  29216. /* converter= */ null);
  29217. return this._converter.fromFirestore(snapshot);
  29218. }
  29219. else {
  29220. return this._userDataWriter.convertValue(this._document.data.value);
  29221. }
  29222. }
  29223. /**
  29224. * Retrieves the field specified by `fieldPath`. Returns `undefined` if the
  29225. * document or field doesn't exist.
  29226. *
  29227. * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific
  29228. * field.
  29229. * @returns The data at the specified field location or undefined if no such
  29230. * field exists in the document.
  29231. */
  29232. // We are using `any` here to avoid an explicit cast by our users.
  29233. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  29234. get(fieldPath) {
  29235. if (this._document) {
  29236. const value = this._document.data.field(fieldPathFromArgument('DocumentSnapshot.get', fieldPath));
  29237. if (value !== null) {
  29238. return this._userDataWriter.convertValue(value);
  29239. }
  29240. }
  29241. return undefined;
  29242. }
  29243. }
  29244. /**
  29245. * A `QueryDocumentSnapshot` contains data read from a document in your
  29246. * Firestore database as part of a query. The document is guaranteed to exist
  29247. * and its data can be extracted with `.data()` or `.get(<field>)` to get a
  29248. * specific field.
  29249. *
  29250. * A `QueryDocumentSnapshot` offers the same API surface as a
  29251. * `DocumentSnapshot`. Since query results contain only existing documents, the
  29252. * `exists` property will always be true and `data()` will never return
  29253. * 'undefined'.
  29254. */
  29255. class QueryDocumentSnapshot$1 extends DocumentSnapshot$1 {
  29256. /**
  29257. * Retrieves all fields in the document as an `Object`.
  29258. *
  29259. * @override
  29260. * @returns An `Object` containing all fields in the document.
  29261. */
  29262. data() {
  29263. return super.data();
  29264. }
  29265. }
  29266. /**
  29267. * Helper that calls `fromDotSeparatedString()` but wraps any error thrown.
  29268. */
  29269. function fieldPathFromArgument(methodName, arg) {
  29270. if (typeof arg === 'string') {
  29271. return fieldPathFromDotSeparatedString(methodName, arg);
  29272. }
  29273. else if (arg instanceof FieldPath) {
  29274. return arg._internalPath;
  29275. }
  29276. else {
  29277. return arg._delegate._internalPath;
  29278. }
  29279. }
  29280. /**
  29281. * @license
  29282. * Copyright 2020 Google LLC
  29283. *
  29284. * Licensed under the Apache License, Version 2.0 (the "License");
  29285. * you may not use this file except in compliance with the License.
  29286. * You may obtain a copy of the License at
  29287. *
  29288. * http://www.apache.org/licenses/LICENSE-2.0
  29289. *
  29290. * Unless required by applicable law or agreed to in writing, software
  29291. * distributed under the License is distributed on an "AS IS" BASIS,
  29292. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29293. * See the License for the specific language governing permissions and
  29294. * limitations under the License.
  29295. */
  29296. function validateHasExplicitOrderByForLimitToLast(query) {
  29297. if (query.limitType === "L" /* LimitType.Last */ &&
  29298. query.explicitOrderBy.length === 0) {
  29299. throw new FirestoreError(Code.UNIMPLEMENTED, 'limitToLast() queries require specifying at least one orderBy() clause');
  29300. }
  29301. }
  29302. /**
  29303. * An `AppliableConstraint` is an abstraction of a constraint that can be applied
  29304. * to a Firestore query.
  29305. */
  29306. class AppliableConstraint {
  29307. }
  29308. /**
  29309. * A `QueryConstraint` is used to narrow the set of documents returned by a
  29310. * Firestore query. `QueryConstraint`s are created by invoking {@link where},
  29311. * {@link orderBy}, {@link (startAt:1)}, {@link (startAfter:1)}, {@link
  29312. * (endBefore:1)}, {@link (endAt:1)}, {@link limit}, {@link limitToLast} and
  29313. * can then be passed to {@link (query:1)} to create a new query instance that
  29314. * also contains this `QueryConstraint`.
  29315. */
  29316. class QueryConstraint extends AppliableConstraint {
  29317. }
  29318. function query(query, queryConstraint, ...additionalQueryConstraints) {
  29319. let queryConstraints = [];
  29320. if (queryConstraint instanceof AppliableConstraint) {
  29321. queryConstraints.push(queryConstraint);
  29322. }
  29323. queryConstraints = queryConstraints.concat(additionalQueryConstraints);
  29324. validateQueryConstraintArray(queryConstraints);
  29325. for (const constraint of queryConstraints) {
  29326. query = constraint._apply(query);
  29327. }
  29328. return query;
  29329. }
  29330. /**
  29331. * A `QueryFieldFilterConstraint` is used to narrow the set of documents returned by
  29332. * a Firestore query by filtering on one or more document fields.
  29333. * `QueryFieldFilterConstraint`s are created by invoking {@link where} and can then
  29334. * be passed to {@link (query:1)} to create a new query instance that also contains
  29335. * this `QueryFieldFilterConstraint`.
  29336. */
  29337. class QueryFieldFilterConstraint extends QueryConstraint {
  29338. /**
  29339. * @internal
  29340. */
  29341. constructor(_field, _op, _value) {
  29342. super();
  29343. this._field = _field;
  29344. this._op = _op;
  29345. this._value = _value;
  29346. /** The type of this query constraint */
  29347. this.type = 'where';
  29348. }
  29349. static _create(_field, _op, _value) {
  29350. return new QueryFieldFilterConstraint(_field, _op, _value);
  29351. }
  29352. _apply(query) {
  29353. const filter = this._parse(query);
  29354. validateNewFieldFilter(query._query, filter);
  29355. return new Query(query.firestore, query.converter, queryWithAddedFilter(query._query, filter));
  29356. }
  29357. _parse(query) {
  29358. const reader = newUserDataReader(query.firestore);
  29359. const filter = newQueryFilter(query._query, 'where', reader, query.firestore._databaseId, this._field, this._op, this._value);
  29360. return filter;
  29361. }
  29362. }
  29363. /**
  29364. * Creates a {@link QueryFieldFilterConstraint} that enforces that documents
  29365. * must contain the specified field and that the value should satisfy the
  29366. * relation constraint provided.
  29367. *
  29368. * @param fieldPath - The path to compare
  29369. * @param opStr - The operation string (e.g "&lt;", "&lt;=", "==", "&lt;",
  29370. * "&lt;=", "!=").
  29371. * @param value - The value for comparison
  29372. * @returns The created {@link QueryFieldFilterConstraint}.
  29373. */
  29374. function where(fieldPath, opStr, value) {
  29375. const op = opStr;
  29376. const field = fieldPathFromArgument('where', fieldPath);
  29377. return QueryFieldFilterConstraint._create(field, op, value);
  29378. }
  29379. /**
  29380. * A `QueryCompositeFilterConstraint` is used to narrow the set of documents
  29381. * returned by a Firestore query by performing the logical OR or AND of multiple
  29382. * {@link QueryFieldFilterConstraint}s or {@link QueryCompositeFilterConstraint}s.
  29383. * `QueryCompositeFilterConstraint`s are created by invoking {@link or} or
  29384. * {@link and} and can then be passed to {@link (query:1)} to create a new query
  29385. * instance that also contains the `QueryCompositeFilterConstraint`.
  29386. */
  29387. class QueryCompositeFilterConstraint extends AppliableConstraint {
  29388. /**
  29389. * @internal
  29390. */
  29391. constructor(
  29392. /** The type of this query constraint */
  29393. type, _queryConstraints) {
  29394. super();
  29395. this.type = type;
  29396. this._queryConstraints = _queryConstraints;
  29397. }
  29398. static _create(type, _queryConstraints) {
  29399. return new QueryCompositeFilterConstraint(type, _queryConstraints);
  29400. }
  29401. _parse(query) {
  29402. const parsedFilters = this._queryConstraints
  29403. .map(queryConstraint => {
  29404. return queryConstraint._parse(query);
  29405. })
  29406. .filter(parsedFilter => parsedFilter.getFilters().length > 0);
  29407. if (parsedFilters.length === 1) {
  29408. return parsedFilters[0];
  29409. }
  29410. return CompositeFilter.create(parsedFilters, this._getOperator());
  29411. }
  29412. _apply(query) {
  29413. const parsedFilter = this._parse(query);
  29414. if (parsedFilter.getFilters().length === 0) {
  29415. // Return the existing query if not adding any more filters (e.g. an empty
  29416. // composite filter).
  29417. return query;
  29418. }
  29419. validateNewFilter(query._query, parsedFilter);
  29420. return new Query(query.firestore, query.converter, queryWithAddedFilter(query._query, parsedFilter));
  29421. }
  29422. _getQueryConstraints() {
  29423. return this._queryConstraints;
  29424. }
  29425. _getOperator() {
  29426. return this.type === 'and' ? "and" /* CompositeOperator.AND */ : "or" /* CompositeOperator.OR */;
  29427. }
  29428. }
  29429. /**
  29430. * Creates a new {@link QueryCompositeFilterConstraint} that is a disjunction of
  29431. * the given filter constraints. A disjunction filter includes a document if it
  29432. * satisfies any of the given filters.
  29433. *
  29434. * @param queryConstraints - Optional. The list of
  29435. * {@link QueryFilterConstraint}s to perform a disjunction for. These must be
  29436. * created with calls to {@link where}, {@link or}, or {@link and}.
  29437. * @returns The newly created {@link QueryCompositeFilterConstraint}.
  29438. */
  29439. function or(...queryConstraints) {
  29440. // Only support QueryFilterConstraints
  29441. queryConstraints.forEach(queryConstraint => validateQueryFilterConstraint('or', queryConstraint));
  29442. return QueryCompositeFilterConstraint._create("or" /* CompositeOperator.OR */, queryConstraints);
  29443. }
  29444. /**
  29445. * Creates a new {@link QueryCompositeFilterConstraint} that is a conjunction of
  29446. * the given filter constraints. A conjunction filter includes a document if it
  29447. * satisfies all of the given filters.
  29448. *
  29449. * @param queryConstraints - Optional. The list of
  29450. * {@link QueryFilterConstraint}s to perform a conjunction for. These must be
  29451. * created with calls to {@link where}, {@link or}, or {@link and}.
  29452. * @returns The newly created {@link QueryCompositeFilterConstraint}.
  29453. */
  29454. function and(...queryConstraints) {
  29455. // Only support QueryFilterConstraints
  29456. queryConstraints.forEach(queryConstraint => validateQueryFilterConstraint('and', queryConstraint));
  29457. return QueryCompositeFilterConstraint._create("and" /* CompositeOperator.AND */, queryConstraints);
  29458. }
  29459. /**
  29460. * A `QueryOrderByConstraint` is used to sort the set of documents returned by a
  29461. * Firestore query. `QueryOrderByConstraint`s are created by invoking
  29462. * {@link orderBy} and can then be passed to {@link (query:1)} to create a new query
  29463. * instance that also contains this `QueryOrderByConstraint`.
  29464. *
  29465. * Note: Documents that do not contain the orderBy field will not be present in
  29466. * the query result.
  29467. */
  29468. class QueryOrderByConstraint extends QueryConstraint {
  29469. /**
  29470. * @internal
  29471. */
  29472. constructor(_field, _direction) {
  29473. super();
  29474. this._field = _field;
  29475. this._direction = _direction;
  29476. /** The type of this query constraint */
  29477. this.type = 'orderBy';
  29478. }
  29479. static _create(_field, _direction) {
  29480. return new QueryOrderByConstraint(_field, _direction);
  29481. }
  29482. _apply(query) {
  29483. const orderBy = newQueryOrderBy(query._query, this._field, this._direction);
  29484. return new Query(query.firestore, query.converter, queryWithAddedOrderBy(query._query, orderBy));
  29485. }
  29486. }
  29487. /**
  29488. * Creates a {@link QueryOrderByConstraint} that sorts the query result by the
  29489. * specified field, optionally in descending order instead of ascending.
  29490. *
  29491. * Note: Documents that do not contain the specified field will not be present
  29492. * in the query result.
  29493. *
  29494. * @param fieldPath - The field to sort by.
  29495. * @param directionStr - Optional direction to sort by ('asc' or 'desc'). If
  29496. * not specified, order will be ascending.
  29497. * @returns The created {@link QueryOrderByConstraint}.
  29498. */
  29499. function orderBy(fieldPath, directionStr = 'asc') {
  29500. const direction = directionStr;
  29501. const path = fieldPathFromArgument('orderBy', fieldPath);
  29502. return QueryOrderByConstraint._create(path, direction);
  29503. }
  29504. /**
  29505. * A `QueryLimitConstraint` is used to limit the number of documents returned by
  29506. * a Firestore query.
  29507. * `QueryLimitConstraint`s are created by invoking {@link limit} or
  29508. * {@link limitToLast} and can then be passed to {@link (query:1)} to create a new
  29509. * query instance that also contains this `QueryLimitConstraint`.
  29510. */
  29511. class QueryLimitConstraint extends QueryConstraint {
  29512. /**
  29513. * @internal
  29514. */
  29515. constructor(
  29516. /** The type of this query constraint */
  29517. type, _limit, _limitType) {
  29518. super();
  29519. this.type = type;
  29520. this._limit = _limit;
  29521. this._limitType = _limitType;
  29522. }
  29523. static _create(type, _limit, _limitType) {
  29524. return new QueryLimitConstraint(type, _limit, _limitType);
  29525. }
  29526. _apply(query) {
  29527. return new Query(query.firestore, query.converter, queryWithLimit(query._query, this._limit, this._limitType));
  29528. }
  29529. }
  29530. /**
  29531. * Creates a {@link QueryLimitConstraint} that only returns the first matching
  29532. * documents.
  29533. *
  29534. * @param limit - The maximum number of items to return.
  29535. * @returns The created {@link QueryLimitConstraint}.
  29536. */
  29537. function limit(limit) {
  29538. validatePositiveNumber('limit', limit);
  29539. return QueryLimitConstraint._create('limit', limit, "F" /* LimitType.First */);
  29540. }
  29541. /**
  29542. * Creates a {@link QueryLimitConstraint} that only returns the last matching
  29543. * documents.
  29544. *
  29545. * You must specify at least one `orderBy` clause for `limitToLast` queries,
  29546. * otherwise an exception will be thrown during execution.
  29547. *
  29548. * @param limit - The maximum number of items to return.
  29549. * @returns The created {@link QueryLimitConstraint}.
  29550. */
  29551. function limitToLast(limit) {
  29552. validatePositiveNumber('limitToLast', limit);
  29553. return QueryLimitConstraint._create('limitToLast', limit, "L" /* LimitType.Last */);
  29554. }
  29555. /**
  29556. * A `QueryStartAtConstraint` is used to exclude documents from the start of a
  29557. * result set returned by a Firestore query.
  29558. * `QueryStartAtConstraint`s are created by invoking {@link (startAt:1)} or
  29559. * {@link (startAfter:1)} and can then be passed to {@link (query:1)} to create a
  29560. * new query instance that also contains this `QueryStartAtConstraint`.
  29561. */
  29562. class QueryStartAtConstraint extends QueryConstraint {
  29563. /**
  29564. * @internal
  29565. */
  29566. constructor(
  29567. /** The type of this query constraint */
  29568. type, _docOrFields, _inclusive) {
  29569. super();
  29570. this.type = type;
  29571. this._docOrFields = _docOrFields;
  29572. this._inclusive = _inclusive;
  29573. }
  29574. static _create(type, _docOrFields, _inclusive) {
  29575. return new QueryStartAtConstraint(type, _docOrFields, _inclusive);
  29576. }
  29577. _apply(query) {
  29578. const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
  29579. return new Query(query.firestore, query.converter, queryWithStartAt(query._query, bound));
  29580. }
  29581. }
  29582. function startAt(...docOrFields) {
  29583. return QueryStartAtConstraint._create('startAt', docOrFields,
  29584. /*inclusive=*/ true);
  29585. }
  29586. function startAfter(...docOrFields) {
  29587. return QueryStartAtConstraint._create('startAfter', docOrFields,
  29588. /*inclusive=*/ false);
  29589. }
  29590. /**
  29591. * A `QueryEndAtConstraint` is used to exclude documents from the end of a
  29592. * result set returned by a Firestore query.
  29593. * `QueryEndAtConstraint`s are created by invoking {@link (endAt:1)} or
  29594. * {@link (endBefore:1)} and can then be passed to {@link (query:1)} to create a new
  29595. * query instance that also contains this `QueryEndAtConstraint`.
  29596. */
  29597. class QueryEndAtConstraint extends QueryConstraint {
  29598. /**
  29599. * @internal
  29600. */
  29601. constructor(
  29602. /** The type of this query constraint */
  29603. type, _docOrFields, _inclusive) {
  29604. super();
  29605. this.type = type;
  29606. this._docOrFields = _docOrFields;
  29607. this._inclusive = _inclusive;
  29608. }
  29609. static _create(type, _docOrFields, _inclusive) {
  29610. return new QueryEndAtConstraint(type, _docOrFields, _inclusive);
  29611. }
  29612. _apply(query) {
  29613. const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
  29614. return new Query(query.firestore, query.converter, queryWithEndAt(query._query, bound));
  29615. }
  29616. }
  29617. function endBefore(...docOrFields) {
  29618. return QueryEndAtConstraint._create('endBefore', docOrFields,
  29619. /*inclusive=*/ false);
  29620. }
  29621. function endAt(...docOrFields) {
  29622. return QueryEndAtConstraint._create('endAt', docOrFields,
  29623. /*inclusive=*/ true);
  29624. }
  29625. /** Helper function to create a bound from a document or fields */
  29626. function newQueryBoundFromDocOrFields(query, methodName, docOrFields, inclusive) {
  29627. docOrFields[0] = getModularInstance(docOrFields[0]);
  29628. if (docOrFields[0] instanceof DocumentSnapshot$1) {
  29629. return newQueryBoundFromDocument(query._query, query.firestore._databaseId, methodName, docOrFields[0]._document, inclusive);
  29630. }
  29631. else {
  29632. const reader = newUserDataReader(query.firestore);
  29633. return newQueryBoundFromFields(query._query, query.firestore._databaseId, reader, methodName, docOrFields, inclusive);
  29634. }
  29635. }
  29636. function newQueryFilter(query, methodName, dataReader, databaseId, fieldPath, op, value) {
  29637. let fieldValue;
  29638. if (fieldPath.isKeyField()) {
  29639. if (op === "array-contains" /* Operator.ARRAY_CONTAINS */ || op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  29640. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid Query. You can't perform '${op}' queries on documentId().`);
  29641. }
  29642. else if (op === "in" /* Operator.IN */ || op === "not-in" /* Operator.NOT_IN */) {
  29643. validateDisjunctiveFilterElements(value, op);
  29644. const referenceList = [];
  29645. for (const arrayValue of value) {
  29646. referenceList.push(parseDocumentIdValue(databaseId, query, arrayValue));
  29647. }
  29648. fieldValue = { arrayValue: { values: referenceList } };
  29649. }
  29650. else {
  29651. fieldValue = parseDocumentIdValue(databaseId, query, value);
  29652. }
  29653. }
  29654. else {
  29655. if (op === "in" /* Operator.IN */ ||
  29656. op === "not-in" /* Operator.NOT_IN */ ||
  29657. op === "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */) {
  29658. validateDisjunctiveFilterElements(value, op);
  29659. }
  29660. fieldValue = parseQueryValue(dataReader, methodName, value,
  29661. /* allowArrays= */ op === "in" /* Operator.IN */ || op === "not-in" /* Operator.NOT_IN */);
  29662. }
  29663. const filter = FieldFilter.create(fieldPath, op, fieldValue);
  29664. return filter;
  29665. }
  29666. function newQueryOrderBy(query, fieldPath, direction) {
  29667. if (query.startAt !== null) {
  29668. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You must not call startAt() or startAfter() before ' +
  29669. 'calling orderBy().');
  29670. }
  29671. if (query.endAt !== null) {
  29672. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You must not call endAt() or endBefore() before ' +
  29673. 'calling orderBy().');
  29674. }
  29675. const orderBy = new OrderBy(fieldPath, direction);
  29676. validateNewOrderBy(query, orderBy);
  29677. return orderBy;
  29678. }
  29679. /**
  29680. * Create a `Bound` from a query and a document.
  29681. *
  29682. * Note that the `Bound` will always include the key of the document
  29683. * and so only the provided document will compare equal to the returned
  29684. * position.
  29685. *
  29686. * Will throw if the document does not contain all fields of the order by
  29687. * of the query or if any of the fields in the order by are an uncommitted
  29688. * server timestamp.
  29689. */
  29690. function newQueryBoundFromDocument(query, databaseId, methodName, doc, inclusive) {
  29691. if (!doc) {
  29692. throw new FirestoreError(Code.NOT_FOUND, `Can't use a DocumentSnapshot that doesn't exist for ` +
  29693. `${methodName}().`);
  29694. }
  29695. const components = [];
  29696. // Because people expect to continue/end a query at the exact document
  29697. // provided, we need to use the implicit sort order rather than the explicit
  29698. // sort order, because it's guaranteed to contain the document key. That way
  29699. // the position becomes unambiguous and the query continues/ends exactly at
  29700. // the provided document. Without the key (by using the explicit sort
  29701. // orders), multiple documents could match the position, yielding duplicate
  29702. // results.
  29703. for (const orderBy of queryOrderBy(query)) {
  29704. if (orderBy.field.isKeyField()) {
  29705. components.push(refValue(databaseId, doc.key));
  29706. }
  29707. else {
  29708. const value = doc.data.field(orderBy.field);
  29709. if (isServerTimestamp(value)) {
  29710. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You are trying to start or end a query using a ' +
  29711. 'document for which the field "' +
  29712. orderBy.field +
  29713. '" is an uncommitted server timestamp. (Since the value of ' +
  29714. 'this field is unknown, you cannot start/end a query with it.)');
  29715. }
  29716. else if (value !== null) {
  29717. components.push(value);
  29718. }
  29719. else {
  29720. const field = orderBy.field.canonicalString();
  29721. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. You are trying to start or end a query using a ` +
  29722. `document for which the field '${field}' (used as the ` +
  29723. `orderBy) does not exist.`);
  29724. }
  29725. }
  29726. }
  29727. return new Bound(components, inclusive);
  29728. }
  29729. /**
  29730. * Converts a list of field values to a `Bound` for the given query.
  29731. */
  29732. function newQueryBoundFromFields(query, databaseId, dataReader, methodName, values, inclusive) {
  29733. // Use explicit order by's because it has to match the query the user made
  29734. const orderBy = query.explicitOrderBy;
  29735. if (values.length > orderBy.length) {
  29736. throw new FirestoreError(Code.INVALID_ARGUMENT, `Too many arguments provided to ${methodName}(). ` +
  29737. `The number of arguments must be less than or equal to the ` +
  29738. `number of orderBy() clauses`);
  29739. }
  29740. const components = [];
  29741. for (let i = 0; i < values.length; i++) {
  29742. const rawValue = values[i];
  29743. const orderByComponent = orderBy[i];
  29744. if (orderByComponent.field.isKeyField()) {
  29745. if (typeof rawValue !== 'string') {
  29746. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. Expected a string for document ID in ` +
  29747. `${methodName}(), but got a ${typeof rawValue}`);
  29748. }
  29749. if (!isCollectionGroupQuery(query) && rawValue.indexOf('/') !== -1) {
  29750. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection and ordering by documentId(), ` +
  29751. `the value passed to ${methodName}() must be a plain document ID, but ` +
  29752. `'${rawValue}' contains a slash.`);
  29753. }
  29754. const path = query.path.child(ResourcePath.fromString(rawValue));
  29755. if (!DocumentKey.isDocumentKey(path)) {
  29756. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection group and ordering by ` +
  29757. `documentId(), the value passed to ${methodName}() must result in a ` +
  29758. `valid document path, but '${path}' is not because it contains an odd number ` +
  29759. `of segments.`);
  29760. }
  29761. const key = new DocumentKey(path);
  29762. components.push(refValue(databaseId, key));
  29763. }
  29764. else {
  29765. const wrapped = parseQueryValue(dataReader, methodName, rawValue);
  29766. components.push(wrapped);
  29767. }
  29768. }
  29769. return new Bound(components, inclusive);
  29770. }
  29771. /**
  29772. * Parses the given `documentIdValue` into a `ReferenceValue`, throwing
  29773. * appropriate errors if the value is anything other than a `DocumentReference`
  29774. * or `string`, or if the string is malformed.
  29775. */
  29776. function parseDocumentIdValue(databaseId, query, documentIdValue) {
  29777. documentIdValue = getModularInstance(documentIdValue);
  29778. if (typeof documentIdValue === 'string') {
  29779. if (documentIdValue === '') {
  29780. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. When querying with documentId(), you ' +
  29781. 'must provide a valid document ID, but it was an empty string.');
  29782. }
  29783. if (!isCollectionGroupQuery(query) && documentIdValue.indexOf('/') !== -1) {
  29784. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection by ` +
  29785. `documentId(), you must provide a plain document ID, but ` +
  29786. `'${documentIdValue}' contains a '/' character.`);
  29787. }
  29788. const path = query.path.child(ResourcePath.fromString(documentIdValue));
  29789. if (!DocumentKey.isDocumentKey(path)) {
  29790. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying a collection group by ` +
  29791. `documentId(), the value provided must result in a valid document path, ` +
  29792. `but '${path}' is not because it has an odd number of segments (${path.length}).`);
  29793. }
  29794. return refValue(databaseId, new DocumentKey(path));
  29795. }
  29796. else if (documentIdValue instanceof DocumentReference) {
  29797. return refValue(databaseId, documentIdValue._key);
  29798. }
  29799. else {
  29800. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. When querying with documentId(), you must provide a valid ` +
  29801. `string or a DocumentReference, but it was: ` +
  29802. `${valueDescription(documentIdValue)}.`);
  29803. }
  29804. }
  29805. /**
  29806. * Validates that the value passed into a disjunctive filter satisfies all
  29807. * array requirements.
  29808. */
  29809. function validateDisjunctiveFilterElements(value, operator) {
  29810. if (!Array.isArray(value) || value.length === 0) {
  29811. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid Query. A non-empty array is required for ' +
  29812. `'${operator.toString()}' filters.`);
  29813. }
  29814. }
  29815. /**
  29816. * Given an operator, returns the set of operators that cannot be used with it.
  29817. *
  29818. * This is not a comprehensive check, and this function should be removed in the
  29819. * long term. Validations should occur in the Firestore backend.
  29820. *
  29821. * Operators in a query must adhere to the following set of rules:
  29822. * 1. Only one inequality per query.
  29823. * 2. `NOT_IN` cannot be used with array, disjunctive, or `NOT_EQUAL` operators.
  29824. */
  29825. function conflictingOps(op) {
  29826. switch (op) {
  29827. case "!=" /* Operator.NOT_EQUAL */:
  29828. return ["!=" /* Operator.NOT_EQUAL */, "not-in" /* Operator.NOT_IN */];
  29829. case "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */:
  29830. case "in" /* Operator.IN */:
  29831. return ["not-in" /* Operator.NOT_IN */];
  29832. case "not-in" /* Operator.NOT_IN */:
  29833. return [
  29834. "array-contains-any" /* Operator.ARRAY_CONTAINS_ANY */,
  29835. "in" /* Operator.IN */,
  29836. "not-in" /* Operator.NOT_IN */,
  29837. "!=" /* Operator.NOT_EQUAL */
  29838. ];
  29839. default:
  29840. return [];
  29841. }
  29842. }
  29843. function validateNewFieldFilter(query, fieldFilter) {
  29844. if (fieldFilter.isInequality()) {
  29845. const existingInequality = getInequalityFilterField(query);
  29846. const newInequality = fieldFilter.field;
  29847. if (existingInequality !== null &&
  29848. !existingInequality.isEqual(newInequality)) {
  29849. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. All where filters with an inequality' +
  29850. ' (<, <=, !=, not-in, >, or >=) must be on the same field. But you have' +
  29851. ` inequality filters on '${existingInequality.toString()}'` +
  29852. ` and '${newInequality.toString()}'`);
  29853. }
  29854. const firstOrderByField = getFirstOrderByField(query);
  29855. if (firstOrderByField !== null) {
  29856. validateOrderByAndInequalityMatch(query, newInequality, firstOrderByField);
  29857. }
  29858. }
  29859. const conflictingOp = findOpInsideFilters(query.filters, conflictingOps(fieldFilter.op));
  29860. if (conflictingOp !== null) {
  29861. // Special case when it's a duplicate op to give a slightly clearer error message.
  29862. if (conflictingOp === fieldFilter.op) {
  29863. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Invalid query. You cannot use more than one ' +
  29864. `'${fieldFilter.op.toString()}' filter.`);
  29865. }
  29866. else {
  29867. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. You cannot use '${fieldFilter.op.toString()}' filters ` +
  29868. `with '${conflictingOp.toString()}' filters.`);
  29869. }
  29870. }
  29871. }
  29872. function validateNewFilter(query, filter) {
  29873. let testQuery = query;
  29874. const subFilters = filter.getFlattenedFilters();
  29875. for (const subFilter of subFilters) {
  29876. validateNewFieldFilter(testQuery, subFilter);
  29877. testQuery = queryWithAddedFilter(testQuery, subFilter);
  29878. }
  29879. }
  29880. // Checks if any of the provided filter operators are included in the given list of filters and
  29881. // returns the first one that is, or null if none are.
  29882. function findOpInsideFilters(filters, operators) {
  29883. for (const filter of filters) {
  29884. for (const fieldFilter of filter.getFlattenedFilters()) {
  29885. if (operators.indexOf(fieldFilter.op) >= 0) {
  29886. return fieldFilter.op;
  29887. }
  29888. }
  29889. }
  29890. return null;
  29891. }
  29892. function validateNewOrderBy(query, orderBy) {
  29893. if (getFirstOrderByField(query) === null) {
  29894. // This is the first order by. It must match any inequality.
  29895. const inequalityField = getInequalityFilterField(query);
  29896. if (inequalityField !== null) {
  29897. validateOrderByAndInequalityMatch(query, inequalityField, orderBy.field);
  29898. }
  29899. }
  29900. }
  29901. function validateOrderByAndInequalityMatch(baseQuery, inequality, orderBy) {
  29902. if (!orderBy.isEqual(inequality)) {
  29903. throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid query. You have a where filter with an inequality ` +
  29904. `(<, <=, !=, not-in, >, or >=) on field '${inequality.toString()}' ` +
  29905. `and so you must also use '${inequality.toString()}' ` +
  29906. `as your first argument to orderBy(), but your first orderBy() ` +
  29907. `is on field '${orderBy.toString()}' instead.`);
  29908. }
  29909. }
  29910. function validateQueryFilterConstraint(functionName, queryConstraint) {
  29911. if (!(queryConstraint instanceof QueryFieldFilterConstraint) &&
  29912. !(queryConstraint instanceof QueryCompositeFilterConstraint)) {
  29913. throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires AppliableConstraints created with a call to 'where(...)', 'or(...)', or 'and(...)'.`);
  29914. }
  29915. }
  29916. function validateQueryConstraintArray(queryConstraint) {
  29917. const compositeFilterCount = queryConstraint.filter(filter => filter instanceof QueryCompositeFilterConstraint).length;
  29918. const fieldFilterCount = queryConstraint.filter(filter => filter instanceof QueryFieldFilterConstraint).length;
  29919. if (compositeFilterCount > 1 ||
  29920. (compositeFilterCount > 0 && fieldFilterCount > 0)) {
  29921. throw new FirestoreError(Code.INVALID_ARGUMENT, 'InvalidQuery. When using composite filters, you cannot use ' +
  29922. 'more than one filter at the top level. Consider nesting the multiple ' +
  29923. 'filters within an `and(...)` statement. For example: ' +
  29924. 'change `query(query, where(...), or(...))` to ' +
  29925. '`query(query, and(where(...), or(...)))`.');
  29926. }
  29927. }
  29928. /**
  29929. * @license
  29930. * Copyright 2020 Google LLC
  29931. *
  29932. * Licensed under the Apache License, Version 2.0 (the "License");
  29933. * you may not use this file except in compliance with the License.
  29934. * You may obtain a copy of the License at
  29935. *
  29936. * http://www.apache.org/licenses/LICENSE-2.0
  29937. *
  29938. * Unless required by applicable law or agreed to in writing, software
  29939. * distributed under the License is distributed on an "AS IS" BASIS,
  29940. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29941. * See the License for the specific language governing permissions and
  29942. * limitations under the License.
  29943. */
  29944. /**
  29945. * Converts Firestore's internal types to the JavaScript types that we expose
  29946. * to the user.
  29947. *
  29948. * @internal
  29949. */
  29950. class AbstractUserDataWriter {
  29951. convertValue(value, serverTimestampBehavior = 'none') {
  29952. switch (typeOrder(value)) {
  29953. case 0 /* TypeOrder.NullValue */:
  29954. return null;
  29955. case 1 /* TypeOrder.BooleanValue */:
  29956. return value.booleanValue;
  29957. case 2 /* TypeOrder.NumberValue */:
  29958. return normalizeNumber(value.integerValue || value.doubleValue);
  29959. case 3 /* TypeOrder.TimestampValue */:
  29960. return this.convertTimestamp(value.timestampValue);
  29961. case 4 /* TypeOrder.ServerTimestampValue */:
  29962. return this.convertServerTimestamp(value, serverTimestampBehavior);
  29963. case 5 /* TypeOrder.StringValue */:
  29964. return value.stringValue;
  29965. case 6 /* TypeOrder.BlobValue */:
  29966. return this.convertBytes(normalizeByteString(value.bytesValue));
  29967. case 7 /* TypeOrder.RefValue */:
  29968. return this.convertReference(value.referenceValue);
  29969. case 8 /* TypeOrder.GeoPointValue */:
  29970. return this.convertGeoPoint(value.geoPointValue);
  29971. case 9 /* TypeOrder.ArrayValue */:
  29972. return this.convertArray(value.arrayValue, serverTimestampBehavior);
  29973. case 10 /* TypeOrder.ObjectValue */:
  29974. return this.convertObject(value.mapValue, serverTimestampBehavior);
  29975. default:
  29976. throw fail();
  29977. }
  29978. }
  29979. convertObject(mapValue, serverTimestampBehavior) {
  29980. return this.convertObjectMap(mapValue.fields, serverTimestampBehavior);
  29981. }
  29982. /**
  29983. * @internal
  29984. */
  29985. convertObjectMap(fields, serverTimestampBehavior = 'none') {
  29986. const result = {};
  29987. forEach(fields, (key, value) => {
  29988. result[key] = this.convertValue(value, serverTimestampBehavior);
  29989. });
  29990. return result;
  29991. }
  29992. convertGeoPoint(value) {
  29993. return new GeoPoint(normalizeNumber(value.latitude), normalizeNumber(value.longitude));
  29994. }
  29995. convertArray(arrayValue, serverTimestampBehavior) {
  29996. return (arrayValue.values || []).map(value => this.convertValue(value, serverTimestampBehavior));
  29997. }
  29998. convertServerTimestamp(value, serverTimestampBehavior) {
  29999. switch (serverTimestampBehavior) {
  30000. case 'previous':
  30001. const previousValue = getPreviousValue(value);
  30002. if (previousValue == null) {
  30003. return null;
  30004. }
  30005. return this.convertValue(previousValue, serverTimestampBehavior);
  30006. case 'estimate':
  30007. return this.convertTimestamp(getLocalWriteTime(value));
  30008. default:
  30009. return null;
  30010. }
  30011. }
  30012. convertTimestamp(value) {
  30013. const normalizedValue = normalizeTimestamp(value);
  30014. return new Timestamp(normalizedValue.seconds, normalizedValue.nanos);
  30015. }
  30016. convertDocumentKey(name, expectedDatabaseId) {
  30017. const resourcePath = ResourcePath.fromString(name);
  30018. hardAssert(isValidResourceName(resourcePath));
  30019. const databaseId = new DatabaseId(resourcePath.get(1), resourcePath.get(3));
  30020. const key = new DocumentKey(resourcePath.popFirst(5));
  30021. if (!databaseId.isEqual(expectedDatabaseId)) {
  30022. // TODO(b/64130202): Somehow support foreign references.
  30023. logError(`Document ${key} contains a document ` +
  30024. `reference within a different database (` +
  30025. `${databaseId.projectId}/${databaseId.database}) which is not ` +
  30026. `supported. It will be treated as a reference in the current ` +
  30027. `database (${expectedDatabaseId.projectId}/${expectedDatabaseId.database}) ` +
  30028. `instead.`);
  30029. }
  30030. return key;
  30031. }
  30032. }
  30033. /**
  30034. * @license
  30035. * Copyright 2020 Google LLC
  30036. *
  30037. * Licensed under the Apache License, Version 2.0 (the "License");
  30038. * you may not use this file except in compliance with the License.
  30039. * You may obtain a copy of the License at
  30040. *
  30041. * http://www.apache.org/licenses/LICENSE-2.0
  30042. *
  30043. * Unless required by applicable law or agreed to in writing, software
  30044. * distributed under the License is distributed on an "AS IS" BASIS,
  30045. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30046. * See the License for the specific language governing permissions and
  30047. * limitations under the License.
  30048. */
  30049. /**
  30050. * Converts custom model object of type T into `DocumentData` by applying the
  30051. * converter if it exists.
  30052. *
  30053. * This function is used when converting user objects to `DocumentData`
  30054. * because we want to provide the user with a more specific error message if
  30055. * their `set()` or fails due to invalid data originating from a `toFirestore()`
  30056. * call.
  30057. */
  30058. function applyFirestoreDataConverter(converter, value, options) {
  30059. let convertedValue;
  30060. if (converter) {
  30061. if (options && (options.merge || options.mergeFields)) {
  30062. // Cast to `any` in order to satisfy the union type constraint on
  30063. // toFirestore().
  30064. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  30065. convertedValue = converter.toFirestore(value, options);
  30066. }
  30067. else {
  30068. convertedValue = converter.toFirestore(value);
  30069. }
  30070. }
  30071. else {
  30072. convertedValue = value;
  30073. }
  30074. return convertedValue;
  30075. }
  30076. class LiteUserDataWriter extends AbstractUserDataWriter {
  30077. constructor(firestore) {
  30078. super();
  30079. this.firestore = firestore;
  30080. }
  30081. convertBytes(bytes) {
  30082. return new Bytes(bytes);
  30083. }
  30084. convertReference(name) {
  30085. const key = this.convertDocumentKey(name, this.firestore._databaseId);
  30086. return new DocumentReference(this.firestore, /* converter= */ null, key);
  30087. }
  30088. }
  30089. /**
  30090. * @license
  30091. * Copyright 2022 Google LLC
  30092. *
  30093. * Licensed under the Apache License, Version 2.0 (the "License");
  30094. * you may not use this file except in compliance with the License.
  30095. * You may obtain a copy of the License at
  30096. *
  30097. * http://www.apache.org/licenses/LICENSE-2.0
  30098. *
  30099. * Unless required by applicable law or agreed to in writing, software
  30100. * distributed under the License is distributed on an "AS IS" BASIS,
  30101. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30102. * See the License for the specific language governing permissions and
  30103. * limitations under the License.
  30104. */
  30105. /**
  30106. * Create an AggregateField object that can be used to compute the sum of
  30107. * a specified field over a range of documents in the result set of a query.
  30108. * @param field Specifies the field to sum across the result set.
  30109. * @internal TODO (sum/avg) remove when public
  30110. */
  30111. function sum(field) {
  30112. return new AggregateField('sum', fieldPathFromArgument$1('sum', field));
  30113. }
  30114. /**
  30115. * Create an AggregateField object that can be used to compute the average of
  30116. * a specified field over a range of documents in the result set of a query.
  30117. * @param field Specifies the field to average across the result set.
  30118. * @internal TODO (sum/avg) remove when public
  30119. */
  30120. function average(field) {
  30121. return new AggregateField('avg', fieldPathFromArgument$1('average', field));
  30122. }
  30123. /**
  30124. * Create an AggregateField object that can be used to compute the count of
  30125. * documents in the result set of a query.
  30126. * @internal TODO (sum/avg) remove when public
  30127. */
  30128. function count() {
  30129. return new AggregateField('count');
  30130. }
  30131. /**
  30132. * Compares two 'AggregateField` instances for equality.
  30133. *
  30134. * @param left Compare this AggregateField to the `right`.
  30135. * @param right Compare this AggregateField to the `left`.
  30136. * @internal TODO (sum/avg) remove when public
  30137. */
  30138. function aggregateFieldEqual(left, right) {
  30139. var _a, _b;
  30140. return (left instanceof AggregateField &&
  30141. right instanceof AggregateField &&
  30142. left._aggregateType === right._aggregateType &&
  30143. ((_a = left._internalFieldPath) === null || _a === void 0 ? void 0 : _a.canonicalString()) ===
  30144. ((_b = right._internalFieldPath) === null || _b === void 0 ? void 0 : _b.canonicalString()));
  30145. }
  30146. /**
  30147. * Compares two `AggregateQuerySnapshot` instances for equality.
  30148. *
  30149. * Two `AggregateQuerySnapshot` instances are considered "equal" if they have
  30150. * underlying queries that compare equal, and the same data.
  30151. *
  30152. * @param left - The first `AggregateQuerySnapshot` to compare.
  30153. * @param right - The second `AggregateQuerySnapshot` to compare.
  30154. *
  30155. * @returns `true` if the objects are "equal", as defined above, or `false`
  30156. * otherwise.
  30157. */
  30158. function aggregateQuerySnapshotEqual(left, right) {
  30159. return (queryEqual(left.query, right.query) && deepEqual(left.data(), right.data()));
  30160. }
  30161. /**
  30162. * @license
  30163. * Copyright 2017 Google LLC
  30164. *
  30165. * Licensed under the Apache License, Version 2.0 (the "License");
  30166. * you may not use this file except in compliance with the License.
  30167. * You may obtain a copy of the License at
  30168. *
  30169. * http://www.apache.org/licenses/LICENSE-2.0
  30170. *
  30171. * Unless required by applicable law or agreed to in writing, software
  30172. * distributed under the License is distributed on an "AS IS" BASIS,
  30173. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30174. * See the License for the specific language governing permissions and
  30175. * limitations under the License.
  30176. */
  30177. function isPartialObserver(obj) {
  30178. return implementsAnyMethods(obj, ['next', 'error', 'complete']);
  30179. }
  30180. /**
  30181. * Returns true if obj is an object and contains at least one of the specified
  30182. * methods.
  30183. */
  30184. function implementsAnyMethods(obj, methods) {
  30185. if (typeof obj !== 'object' || obj === null) {
  30186. return false;
  30187. }
  30188. const object = obj;
  30189. for (const method of methods) {
  30190. if (method in object && typeof object[method] === 'function') {
  30191. return true;
  30192. }
  30193. }
  30194. return false;
  30195. }
  30196. /**
  30197. * @license
  30198. * Copyright 2020 Google LLC
  30199. *
  30200. * Licensed under the Apache License, Version 2.0 (the "License");
  30201. * you may not use this file except in compliance with the License.
  30202. * You may obtain a copy of the License at
  30203. *
  30204. * http://www.apache.org/licenses/LICENSE-2.0
  30205. *
  30206. * Unless required by applicable law or agreed to in writing, software
  30207. * distributed under the License is distributed on an "AS IS" BASIS,
  30208. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30209. * See the License for the specific language governing permissions and
  30210. * limitations under the License.
  30211. */
  30212. /**
  30213. * Metadata about a snapshot, describing the state of the snapshot.
  30214. */
  30215. class SnapshotMetadata {
  30216. /** @hideconstructor */
  30217. constructor(hasPendingWrites, fromCache) {
  30218. this.hasPendingWrites = hasPendingWrites;
  30219. this.fromCache = fromCache;
  30220. }
  30221. /**
  30222. * Returns true if this `SnapshotMetadata` is equal to the provided one.
  30223. *
  30224. * @param other - The `SnapshotMetadata` to compare against.
  30225. * @returns true if this `SnapshotMetadata` is equal to the provided one.
  30226. */
  30227. isEqual(other) {
  30228. return (this.hasPendingWrites === other.hasPendingWrites &&
  30229. this.fromCache === other.fromCache);
  30230. }
  30231. }
  30232. /**
  30233. * A `DocumentSnapshot` contains data read from a document in your Firestore
  30234. * database. The data can be extracted with `.data()` or `.get(<field>)` to
  30235. * get a specific field.
  30236. *
  30237. * For a `DocumentSnapshot` that points to a non-existing document, any data
  30238. * access will return 'undefined'. You can use the `exists()` method to
  30239. * explicitly verify a document's existence.
  30240. */
  30241. class DocumentSnapshot extends DocumentSnapshot$1 {
  30242. /** @hideconstructor protected */
  30243. constructor(_firestore, userDataWriter, key, document, metadata, converter) {
  30244. super(_firestore, userDataWriter, key, document, converter);
  30245. this._firestore = _firestore;
  30246. this._firestoreImpl = _firestore;
  30247. this.metadata = metadata;
  30248. }
  30249. /**
  30250. * Returns whether or not the data exists. True if the document exists.
  30251. */
  30252. exists() {
  30253. return super.exists();
  30254. }
  30255. /**
  30256. * Retrieves all fields in the document as an `Object`. Returns `undefined` if
  30257. * the document doesn't exist.
  30258. *
  30259. * By default, `serverTimestamp()` values that have not yet been
  30260. * set to their final value will be returned as `null`. You can override
  30261. * this by passing an options object.
  30262. *
  30263. * @param options - An options object to configure how data is retrieved from
  30264. * the snapshot (for example the desired behavior for server timestamps that
  30265. * have not yet been set to their final value).
  30266. * @returns An `Object` containing all fields in the document or `undefined` if
  30267. * the document doesn't exist.
  30268. */
  30269. data(options = {}) {
  30270. if (!this._document) {
  30271. return undefined;
  30272. }
  30273. else if (this._converter) {
  30274. // We only want to use the converter and create a new DocumentSnapshot
  30275. // if a converter has been provided.
  30276. const snapshot = new QueryDocumentSnapshot(this._firestore, this._userDataWriter, this._key, this._document, this.metadata,
  30277. /* converter= */ null);
  30278. return this._converter.fromFirestore(snapshot, options);
  30279. }
  30280. else {
  30281. return this._userDataWriter.convertValue(this._document.data.value, options.serverTimestamps);
  30282. }
  30283. }
  30284. /**
  30285. * Retrieves the field specified by `fieldPath`. Returns `undefined` if the
  30286. * document or field doesn't exist.
  30287. *
  30288. * By default, a `serverTimestamp()` that has not yet been set to
  30289. * its final value will be returned as `null`. You can override this by
  30290. * passing an options object.
  30291. *
  30292. * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific
  30293. * field.
  30294. * @param options - An options object to configure how the field is retrieved
  30295. * from the snapshot (for example the desired behavior for server timestamps
  30296. * that have not yet been set to their final value).
  30297. * @returns The data at the specified field location or undefined if no such
  30298. * field exists in the document.
  30299. */
  30300. // We are using `any` here to avoid an explicit cast by our users.
  30301. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  30302. get(fieldPath, options = {}) {
  30303. if (this._document) {
  30304. const value = this._document.data.field(fieldPathFromArgument('DocumentSnapshot.get', fieldPath));
  30305. if (value !== null) {
  30306. return this._userDataWriter.convertValue(value, options.serverTimestamps);
  30307. }
  30308. }
  30309. return undefined;
  30310. }
  30311. }
  30312. /**
  30313. * A `QueryDocumentSnapshot` contains data read from a document in your
  30314. * Firestore database as part of a query. The document is guaranteed to exist
  30315. * and its data can be extracted with `.data()` or `.get(<field>)` to get a
  30316. * specific field.
  30317. *
  30318. * A `QueryDocumentSnapshot` offers the same API surface as a
  30319. * `DocumentSnapshot`. Since query results contain only existing documents, the
  30320. * `exists` property will always be true and `data()` will never return
  30321. * 'undefined'.
  30322. */
  30323. class QueryDocumentSnapshot extends DocumentSnapshot {
  30324. /**
  30325. * Retrieves all fields in the document as an `Object`.
  30326. *
  30327. * By default, `serverTimestamp()` values that have not yet been
  30328. * set to their final value will be returned as `null`. You can override
  30329. * this by passing an options object.
  30330. *
  30331. * @override
  30332. * @param options - An options object to configure how data is retrieved from
  30333. * the snapshot (for example the desired behavior for server timestamps that
  30334. * have not yet been set to their final value).
  30335. * @returns An `Object` containing all fields in the document.
  30336. */
  30337. data(options = {}) {
  30338. return super.data(options);
  30339. }
  30340. }
  30341. /**
  30342. * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects
  30343. * representing the results of a query. The documents can be accessed as an
  30344. * array via the `docs` property or enumerated using the `forEach` method. The
  30345. * number of documents can be determined via the `empty` and `size`
  30346. * properties.
  30347. */
  30348. class QuerySnapshot {
  30349. /** @hideconstructor */
  30350. constructor(_firestore, _userDataWriter, query, _snapshot) {
  30351. this._firestore = _firestore;
  30352. this._userDataWriter = _userDataWriter;
  30353. this._snapshot = _snapshot;
  30354. this.metadata = new SnapshotMetadata(_snapshot.hasPendingWrites, _snapshot.fromCache);
  30355. this.query = query;
  30356. }
  30357. /** An array of all the documents in the `QuerySnapshot`. */
  30358. get docs() {
  30359. const result = [];
  30360. this.forEach(doc => result.push(doc));
  30361. return result;
  30362. }
  30363. /** The number of documents in the `QuerySnapshot`. */
  30364. get size() {
  30365. return this._snapshot.docs.size;
  30366. }
  30367. /** True if there are no documents in the `QuerySnapshot`. */
  30368. get empty() {
  30369. return this.size === 0;
  30370. }
  30371. /**
  30372. * Enumerates all of the documents in the `QuerySnapshot`.
  30373. *
  30374. * @param callback - A callback to be called with a `QueryDocumentSnapshot` for
  30375. * each document in the snapshot.
  30376. * @param thisArg - The `this` binding for the callback.
  30377. */
  30378. forEach(callback, thisArg) {
  30379. this._snapshot.docs.forEach(doc => {
  30380. 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));
  30381. });
  30382. }
  30383. /**
  30384. * Returns an array of the documents changes since the last snapshot. If this
  30385. * is the first snapshot, all documents will be in the list as 'added'
  30386. * changes.
  30387. *
  30388. * @param options - `SnapshotListenOptions` that control whether metadata-only
  30389. * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger
  30390. * snapshot events.
  30391. */
  30392. docChanges(options = {}) {
  30393. const includeMetadataChanges = !!options.includeMetadataChanges;
  30394. if (includeMetadataChanges && this._snapshot.excludesMetadataChanges) {
  30395. throw new FirestoreError(Code.INVALID_ARGUMENT, 'To include metadata changes with your document changes, you must ' +
  30396. 'also pass { includeMetadataChanges:true } to onSnapshot().');
  30397. }
  30398. if (!this._cachedChanges ||
  30399. this._cachedChangesIncludeMetadataChanges !== includeMetadataChanges) {
  30400. this._cachedChanges = changesFromSnapshot(this, includeMetadataChanges);
  30401. this._cachedChangesIncludeMetadataChanges = includeMetadataChanges;
  30402. }
  30403. return this._cachedChanges;
  30404. }
  30405. }
  30406. /** Calculates the array of `DocumentChange`s for a given `ViewSnapshot`. */
  30407. function changesFromSnapshot(querySnapshot, includeMetadataChanges) {
  30408. if (querySnapshot._snapshot.oldDocs.isEmpty()) {
  30409. let index = 0;
  30410. return querySnapshot._snapshot.docChanges.map(change => {
  30411. 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);
  30412. change.doc;
  30413. return {
  30414. type: 'added',
  30415. doc,
  30416. oldIndex: -1,
  30417. newIndex: index++
  30418. };
  30419. });
  30420. }
  30421. else {
  30422. // A `DocumentSet` that is updated incrementally as changes are applied to use
  30423. // to lookup the index of a document.
  30424. let indexTracker = querySnapshot._snapshot.oldDocs;
  30425. return querySnapshot._snapshot.docChanges
  30426. .filter(change => includeMetadataChanges || change.type !== 3 /* ChangeType.Metadata */)
  30427. .map(change => {
  30428. 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);
  30429. let oldIndex = -1;
  30430. let newIndex = -1;
  30431. if (change.type !== 0 /* ChangeType.Added */) {
  30432. oldIndex = indexTracker.indexOf(change.doc.key);
  30433. indexTracker = indexTracker.delete(change.doc.key);
  30434. }
  30435. if (change.type !== 1 /* ChangeType.Removed */) {
  30436. indexTracker = indexTracker.add(change.doc);
  30437. newIndex = indexTracker.indexOf(change.doc.key);
  30438. }
  30439. return {
  30440. type: resultChangeType(change.type),
  30441. doc,
  30442. oldIndex,
  30443. newIndex
  30444. };
  30445. });
  30446. }
  30447. }
  30448. function resultChangeType(type) {
  30449. switch (type) {
  30450. case 0 /* ChangeType.Added */:
  30451. return 'added';
  30452. case 2 /* ChangeType.Modified */:
  30453. case 3 /* ChangeType.Metadata */:
  30454. return 'modified';
  30455. case 1 /* ChangeType.Removed */:
  30456. return 'removed';
  30457. default:
  30458. return fail();
  30459. }
  30460. }
  30461. // TODO(firestoreexp): Add tests for snapshotEqual with different snapshot
  30462. // metadata
  30463. /**
  30464. * Returns true if the provided snapshots are equal.
  30465. *
  30466. * @param left - A snapshot to compare.
  30467. * @param right - A snapshot to compare.
  30468. * @returns true if the snapshots are equal.
  30469. */
  30470. function snapshotEqual(left, right) {
  30471. if (left instanceof DocumentSnapshot && right instanceof DocumentSnapshot) {
  30472. return (left._firestore === right._firestore &&
  30473. left._key.isEqual(right._key) &&
  30474. (left._document === null
  30475. ? right._document === null
  30476. : left._document.isEqual(right._document)) &&
  30477. left._converter === right._converter);
  30478. }
  30479. else if (left instanceof QuerySnapshot && right instanceof QuerySnapshot) {
  30480. return (left._firestore === right._firestore &&
  30481. queryEqual(left.query, right.query) &&
  30482. left.metadata.isEqual(right.metadata) &&
  30483. left._snapshot.isEqual(right._snapshot));
  30484. }
  30485. return false;
  30486. }
  30487. /**
  30488. * @license
  30489. * Copyright 2020 Google LLC
  30490. *
  30491. * Licensed under the Apache License, Version 2.0 (the "License");
  30492. * you may not use this file except in compliance with the License.
  30493. * You may obtain a copy of the License at
  30494. *
  30495. * http://www.apache.org/licenses/LICENSE-2.0
  30496. *
  30497. * Unless required by applicable law or agreed to in writing, software
  30498. * distributed under the License is distributed on an "AS IS" BASIS,
  30499. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30500. * See the License for the specific language governing permissions and
  30501. * limitations under the License.
  30502. */
  30503. /**
  30504. * Reads the document referred to by this `DocumentReference`.
  30505. *
  30506. * Note: `getDoc()` attempts to provide up-to-date data when possible by waiting
  30507. * for data from the server, but it may return cached data or fail if you are
  30508. * offline and the server cannot be reached. To specify this behavior, invoke
  30509. * {@link getDocFromCache} or {@link getDocFromServer}.
  30510. *
  30511. * @param reference - The reference of the document to fetch.
  30512. * @returns A Promise resolved with a `DocumentSnapshot` containing the
  30513. * current document contents.
  30514. */
  30515. function getDoc(reference) {
  30516. reference = cast(reference, DocumentReference);
  30517. const firestore = cast(reference.firestore, Firestore);
  30518. const client = ensureFirestoreConfigured(firestore);
  30519. return firestoreClientGetDocumentViaSnapshotListener(client, reference._key).then(snapshot => convertToDocSnapshot(firestore, reference, snapshot));
  30520. }
  30521. class ExpUserDataWriter extends AbstractUserDataWriter {
  30522. constructor(firestore) {
  30523. super();
  30524. this.firestore = firestore;
  30525. }
  30526. convertBytes(bytes) {
  30527. return new Bytes(bytes);
  30528. }
  30529. convertReference(name) {
  30530. const key = this.convertDocumentKey(name, this.firestore._databaseId);
  30531. return new DocumentReference(this.firestore, /* converter= */ null, key);
  30532. }
  30533. }
  30534. /**
  30535. * Reads the document referred to by this `DocumentReference` from cache.
  30536. * Returns an error if the document is not currently cached.
  30537. *
  30538. * @returns A `Promise` resolved with a `DocumentSnapshot` containing the
  30539. * current document contents.
  30540. */
  30541. function getDocFromCache(reference) {
  30542. reference = cast(reference, DocumentReference);
  30543. const firestore = cast(reference.firestore, Firestore);
  30544. const client = ensureFirestoreConfigured(firestore);
  30545. const userDataWriter = new ExpUserDataWriter(firestore);
  30546. return firestoreClientGetDocumentFromLocalCache(client, reference._key).then(doc => new DocumentSnapshot(firestore, userDataWriter, reference._key, doc, new SnapshotMetadata(doc !== null && doc.hasLocalMutations,
  30547. /* fromCache= */ true), reference.converter));
  30548. }
  30549. /**
  30550. * Reads the document referred to by this `DocumentReference` from the server.
  30551. * Returns an error if the network is not available.
  30552. *
  30553. * @returns A `Promise` resolved with a `DocumentSnapshot` containing the
  30554. * current document contents.
  30555. */
  30556. function getDocFromServer(reference) {
  30557. reference = cast(reference, DocumentReference);
  30558. const firestore = cast(reference.firestore, Firestore);
  30559. const client = ensureFirestoreConfigured(firestore);
  30560. return firestoreClientGetDocumentViaSnapshotListener(client, reference._key, {
  30561. source: 'server'
  30562. }).then(snapshot => convertToDocSnapshot(firestore, reference, snapshot));
  30563. }
  30564. /**
  30565. * Executes the query and returns the results as a `QuerySnapshot`.
  30566. *
  30567. * Note: `getDocs()` attempts to provide up-to-date data when possible by
  30568. * waiting for data from the server, but it may return cached data or fail if
  30569. * you are offline and the server cannot be reached. To specify this behavior,
  30570. * invoke {@link getDocsFromCache} or {@link getDocsFromServer}.
  30571. *
  30572. * @returns A `Promise` that will be resolved with the results of the query.
  30573. */
  30574. function getDocs(query) {
  30575. query = cast(query, Query);
  30576. const firestore = cast(query.firestore, Firestore);
  30577. const client = ensureFirestoreConfigured(firestore);
  30578. const userDataWriter = new ExpUserDataWriter(firestore);
  30579. validateHasExplicitOrderByForLimitToLast(query._query);
  30580. return firestoreClientGetDocumentsViaSnapshotListener(client, query._query).then(snapshot => new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  30581. }
  30582. /**
  30583. * Executes the query and returns the results as a `QuerySnapshot` from cache.
  30584. * Returns an empty result set if no documents matching the query are currently
  30585. * cached.
  30586. *
  30587. * @returns A `Promise` that will be resolved with the results of the query.
  30588. */
  30589. function getDocsFromCache(query) {
  30590. query = cast(query, Query);
  30591. const firestore = cast(query.firestore, Firestore);
  30592. const client = ensureFirestoreConfigured(firestore);
  30593. const userDataWriter = new ExpUserDataWriter(firestore);
  30594. return firestoreClientGetDocumentsFromLocalCache(client, query._query).then(snapshot => new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  30595. }
  30596. /**
  30597. * Executes the query and returns the results as a `QuerySnapshot` from the
  30598. * server. Returns an error if the network is not available.
  30599. *
  30600. * @returns A `Promise` that will be resolved with the results of the query.
  30601. */
  30602. function getDocsFromServer(query) {
  30603. query = cast(query, Query);
  30604. const firestore = cast(query.firestore, Firestore);
  30605. const client = ensureFirestoreConfigured(firestore);
  30606. const userDataWriter = new ExpUserDataWriter(firestore);
  30607. return firestoreClientGetDocumentsViaSnapshotListener(client, query._query, {
  30608. source: 'server'
  30609. }).then(snapshot => new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  30610. }
  30611. function setDoc(reference, data, options) {
  30612. reference = cast(reference, DocumentReference);
  30613. const firestore = cast(reference.firestore, Firestore);
  30614. const convertedValue = applyFirestoreDataConverter(reference.converter, data, options);
  30615. const dataReader = newUserDataReader(firestore);
  30616. const parsed = parseSetData(dataReader, 'setDoc', reference._key, convertedValue, reference.converter !== null, options);
  30617. const mutation = parsed.toMutation(reference._key, Precondition.none());
  30618. return executeWrite(firestore, [mutation]);
  30619. }
  30620. function updateDoc(reference, fieldOrUpdateData, value, ...moreFieldsAndValues) {
  30621. reference = cast(reference, DocumentReference);
  30622. const firestore = cast(reference.firestore, Firestore);
  30623. const dataReader = newUserDataReader(firestore);
  30624. // For Compat types, we have to "extract" the underlying types before
  30625. // performing validation.
  30626. fieldOrUpdateData = getModularInstance(fieldOrUpdateData);
  30627. let parsed;
  30628. if (typeof fieldOrUpdateData === 'string' ||
  30629. fieldOrUpdateData instanceof FieldPath) {
  30630. parsed = parseUpdateVarargs(dataReader, 'updateDoc', reference._key, fieldOrUpdateData, value, moreFieldsAndValues);
  30631. }
  30632. else {
  30633. parsed = parseUpdateData(dataReader, 'updateDoc', reference._key, fieldOrUpdateData);
  30634. }
  30635. const mutation = parsed.toMutation(reference._key, Precondition.exists(true));
  30636. return executeWrite(firestore, [mutation]);
  30637. }
  30638. /**
  30639. * Deletes the document referred to by the specified `DocumentReference`.
  30640. *
  30641. * @param reference - A reference to the document to delete.
  30642. * @returns A Promise resolved once the document has been successfully
  30643. * deleted from the backend (note that it won't resolve while you're offline).
  30644. */
  30645. function deleteDoc(reference) {
  30646. const firestore = cast(reference.firestore, Firestore);
  30647. const mutations = [new DeleteMutation(reference._key, Precondition.none())];
  30648. return executeWrite(firestore, mutations);
  30649. }
  30650. /**
  30651. * Add a new document to specified `CollectionReference` with the given data,
  30652. * assigning it a document ID automatically.
  30653. *
  30654. * @param reference - A reference to the collection to add this document to.
  30655. * @param data - An Object containing the data for the new document.
  30656. * @returns A `Promise` resolved with a `DocumentReference` pointing to the
  30657. * newly created document after it has been written to the backend (Note that it
  30658. * won't resolve while you're offline).
  30659. */
  30660. function addDoc(reference, data) {
  30661. const firestore = cast(reference.firestore, Firestore);
  30662. const docRef = doc(reference);
  30663. const convertedValue = applyFirestoreDataConverter(reference.converter, data);
  30664. const dataReader = newUserDataReader(reference.firestore);
  30665. const parsed = parseSetData(dataReader, 'addDoc', docRef._key, convertedValue, reference.converter !== null, {});
  30666. const mutation = parsed.toMutation(docRef._key, Precondition.exists(false));
  30667. return executeWrite(firestore, [mutation]).then(() => docRef);
  30668. }
  30669. function onSnapshot(reference, ...args) {
  30670. var _a, _b, _c;
  30671. reference = getModularInstance(reference);
  30672. let options = {
  30673. includeMetadataChanges: false
  30674. };
  30675. let currArg = 0;
  30676. if (typeof args[currArg] === 'object' && !isPartialObserver(args[currArg])) {
  30677. options = args[currArg];
  30678. currArg++;
  30679. }
  30680. const internalOptions = {
  30681. includeMetadataChanges: options.includeMetadataChanges
  30682. };
  30683. if (isPartialObserver(args[currArg])) {
  30684. const userObserver = args[currArg];
  30685. args[currArg] = (_a = userObserver.next) === null || _a === void 0 ? void 0 : _a.bind(userObserver);
  30686. args[currArg + 1] = (_b = userObserver.error) === null || _b === void 0 ? void 0 : _b.bind(userObserver);
  30687. args[currArg + 2] = (_c = userObserver.complete) === null || _c === void 0 ? void 0 : _c.bind(userObserver);
  30688. }
  30689. let observer;
  30690. let firestore;
  30691. let internalQuery;
  30692. if (reference instanceof DocumentReference) {
  30693. firestore = cast(reference.firestore, Firestore);
  30694. internalQuery = newQueryForPath(reference._key.path);
  30695. observer = {
  30696. next: snapshot => {
  30697. if (args[currArg]) {
  30698. args[currArg](convertToDocSnapshot(firestore, reference, snapshot));
  30699. }
  30700. },
  30701. error: args[currArg + 1],
  30702. complete: args[currArg + 2]
  30703. };
  30704. }
  30705. else {
  30706. const query = cast(reference, Query);
  30707. firestore = cast(query.firestore, Firestore);
  30708. internalQuery = query._query;
  30709. const userDataWriter = new ExpUserDataWriter(firestore);
  30710. observer = {
  30711. next: snapshot => {
  30712. if (args[currArg]) {
  30713. args[currArg](new QuerySnapshot(firestore, userDataWriter, query, snapshot));
  30714. }
  30715. },
  30716. error: args[currArg + 1],
  30717. complete: args[currArg + 2]
  30718. };
  30719. validateHasExplicitOrderByForLimitToLast(reference._query);
  30720. }
  30721. const client = ensureFirestoreConfigured(firestore);
  30722. return firestoreClientListen(client, internalQuery, internalOptions, observer);
  30723. }
  30724. function onSnapshotsInSync(firestore, arg) {
  30725. firestore = cast(firestore, Firestore);
  30726. const client = ensureFirestoreConfigured(firestore);
  30727. const observer = isPartialObserver(arg)
  30728. ? arg
  30729. : {
  30730. next: arg
  30731. };
  30732. return firestoreClientAddSnapshotsInSyncListener(client, observer);
  30733. }
  30734. /**
  30735. * Locally writes `mutations` on the async queue.
  30736. * @internal
  30737. */
  30738. function executeWrite(firestore, mutations) {
  30739. const client = ensureFirestoreConfigured(firestore);
  30740. return firestoreClientWrite(client, mutations);
  30741. }
  30742. /**
  30743. * Converts a {@link ViewSnapshot} that contains the single document specified by `ref`
  30744. * to a {@link DocumentSnapshot}.
  30745. */
  30746. function convertToDocSnapshot(firestore, ref, snapshot) {
  30747. const doc = snapshot.docs.get(ref._key);
  30748. const userDataWriter = new ExpUserDataWriter(firestore);
  30749. return new DocumentSnapshot(firestore, userDataWriter, ref._key, doc, new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache), ref.converter);
  30750. }
  30751. /**
  30752. * @license
  30753. * Copyright 2022 Google LLC
  30754. *
  30755. * Licensed under the Apache License, Version 2.0 (the "License");
  30756. * you may not use this file except in compliance with the License.
  30757. * You may obtain a copy of the License at
  30758. *
  30759. * http://www.apache.org/licenses/LICENSE-2.0
  30760. *
  30761. * Unless required by applicable law or agreed to in writing, software
  30762. * distributed under the License is distributed on an "AS IS" BASIS,
  30763. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30764. * See the License for the specific language governing permissions and
  30765. * limitations under the License.
  30766. */
  30767. /**
  30768. * Calculates the number of documents in the result set of the given query,
  30769. * without actually downloading the documents.
  30770. *
  30771. * Using this function to count the documents is efficient because only the
  30772. * final count, not the documents' data, is downloaded. This function can even
  30773. * count the documents if the result set would be prohibitively large to
  30774. * download entirely (e.g. thousands of documents).
  30775. *
  30776. * The result received from the server is presented, unaltered, without
  30777. * considering any local state. That is, documents in the local cache are not
  30778. * taken into consideration, neither are local modifications not yet
  30779. * synchronized with the server. Previously-downloaded results, if any, are not
  30780. * used: every request using this source necessarily involves a round trip to
  30781. * the server.
  30782. *
  30783. * @param query - The query whose result set size to calculate.
  30784. * @returns A Promise that will be resolved with the count; the count can be
  30785. * retrieved from `snapshot.data().count`, where `snapshot` is the
  30786. * `AggregateQuerySnapshot` to which the returned Promise resolves.
  30787. */
  30788. function getCountFromServer(query) {
  30789. const countQuerySpec = {
  30790. count: count()
  30791. };
  30792. return getAggregateFromServer(query, countQuerySpec);
  30793. }
  30794. /**
  30795. * Calculates the specified aggregations over the documents in the result
  30796. * set of the given query, without actually downloading the documents.
  30797. *
  30798. * Using this function to perform aggregations is efficient because only the
  30799. * final aggregation values, not the documents' data, is downloaded. This
  30800. * function can even perform aggregations of the documents if the result set
  30801. * would be prohibitively large to download entirely (e.g. thousands of documents).
  30802. *
  30803. * The result received from the server is presented, unaltered, without
  30804. * considering any local state. That is, documents in the local cache are not
  30805. * taken into consideration, neither are local modifications not yet
  30806. * synchronized with the server. Previously-downloaded results, if any, are not
  30807. * used: every request using this source necessarily involves a round trip to
  30808. * the server.
  30809. *
  30810. * @param query The query whose result set to aggregate over.
  30811. * @param aggregateSpec An `AggregateSpec` object that specifies the aggregates
  30812. * to perform over the result set. The AggregateSpec specifies aliases for each
  30813. * aggregate, which can be used to retrieve the aggregate result.
  30814. * @example
  30815. * ```typescript
  30816. * const aggregateSnapshot = await getAggregateFromServer(query, {
  30817. * countOfDocs: count(),
  30818. * totalHours: sum('hours'),
  30819. * averageScore: average('score')
  30820. * });
  30821. *
  30822. * const countOfDocs: number = aggregateSnapshot.data().countOfDocs;
  30823. * const totalHours: number = aggregateSnapshot.data().totalHours;
  30824. * const averageScore: number | null = aggregateSnapshot.data().averageScore;
  30825. * ```
  30826. * @internal TODO (sum/avg) remove when public
  30827. */
  30828. function getAggregateFromServer(query, aggregateSpec) {
  30829. const firestore = cast(query.firestore, Firestore);
  30830. const client = ensureFirestoreConfigured(firestore);
  30831. const internalAggregates = mapToArray(aggregateSpec, (aggregate, alias) => {
  30832. return new AggregateImpl(alias, aggregate._aggregateType, aggregate._internalFieldPath);
  30833. });
  30834. // Run the aggregation and convert the results
  30835. return firestoreClientRunAggregateQuery(client, query._query, internalAggregates).then(aggregateResult => convertToAggregateQuerySnapshot(firestore, query, aggregateResult));
  30836. }
  30837. /**
  30838. * Converts the core aggregration result to an `AggregateQuerySnapshot`
  30839. * that can be returned to the consumer.
  30840. * @param query
  30841. * @param aggregateResult Core aggregation result
  30842. * @internal
  30843. */
  30844. function convertToAggregateQuerySnapshot(firestore, query, aggregateResult) {
  30845. const userDataWriter = new ExpUserDataWriter(firestore);
  30846. const querySnapshot = new AggregateQuerySnapshot(query, userDataWriter, aggregateResult);
  30847. return querySnapshot;
  30848. }
  30849. /**
  30850. * @license
  30851. * Copyright 2023 Google LLC
  30852. *
  30853. * Licensed under the Apache License, Version 2.0 (the "License");
  30854. * you may not use this file except in compliance with the License.
  30855. * You may obtain a copy of the License at
  30856. *
  30857. * http://www.apache.org/licenses/LICENSE-2.0
  30858. *
  30859. * Unless required by applicable law or agreed to in writing, software
  30860. * distributed under the License is distributed on an "AS IS" BASIS,
  30861. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30862. * See the License for the specific language governing permissions and
  30863. * limitations under the License.
  30864. */
  30865. class MemoryLocalCacheImpl {
  30866. constructor(settings) {
  30867. this.kind = 'memory';
  30868. this._onlineComponentProvider = new OnlineComponentProvider();
  30869. if (settings === null || settings === void 0 ? void 0 : settings.garbageCollector) {
  30870. this._offlineComponentProvider =
  30871. settings.garbageCollector._offlineComponentProvider;
  30872. }
  30873. else {
  30874. this._offlineComponentProvider = new MemoryOfflineComponentProvider();
  30875. }
  30876. }
  30877. toJSON() {
  30878. return { kind: this.kind };
  30879. }
  30880. }
  30881. class PersistentLocalCacheImpl {
  30882. constructor(settings) {
  30883. this.kind = 'persistent';
  30884. let tabManager;
  30885. if (settings === null || settings === void 0 ? void 0 : settings.tabManager) {
  30886. settings.tabManager._initialize(settings);
  30887. tabManager = settings.tabManager;
  30888. }
  30889. else {
  30890. tabManager = persistentSingleTabManager(undefined);
  30891. tabManager._initialize(settings);
  30892. }
  30893. this._onlineComponentProvider = tabManager._onlineComponentProvider;
  30894. this._offlineComponentProvider = tabManager._offlineComponentProvider;
  30895. }
  30896. toJSON() {
  30897. return { kind: this.kind };
  30898. }
  30899. }
  30900. class MemoryEagerGabageCollectorImpl {
  30901. constructor() {
  30902. this.kind = 'memoryEager';
  30903. this._offlineComponentProvider = new MemoryOfflineComponentProvider();
  30904. }
  30905. toJSON() {
  30906. return { kind: this.kind };
  30907. }
  30908. }
  30909. class MemoryLruGabageCollectorImpl {
  30910. constructor(cacheSize) {
  30911. this.kind = 'memoryLru';
  30912. this._offlineComponentProvider = new LruGcMemoryOfflineComponentProvider(cacheSize);
  30913. }
  30914. toJSON() {
  30915. return { kind: this.kind };
  30916. }
  30917. }
  30918. /**
  30919. * Creates an instance of `MemoryEagerGarbageCollector`. This is also the
  30920. * default garbage collector unless it is explicitly specified otherwise.
  30921. */
  30922. function memoryEagerGarbageCollector() {
  30923. return new MemoryEagerGabageCollectorImpl();
  30924. }
  30925. /**
  30926. * Creates an instance of `MemoryLruGarbageCollector`.
  30927. *
  30928. * A target size can be specified as part of the setting parameter. The
  30929. * collector will start deleting documents once the cache size exceeds
  30930. * the given size. The default cache size is 40MB (40 * 1024 * 1024 bytes).
  30931. */
  30932. function memoryLruGarbageCollector(settings) {
  30933. return new MemoryLruGabageCollectorImpl(settings === null || settings === void 0 ? void 0 : settings.cacheSizeBytes);
  30934. }
  30935. /**
  30936. * Creates an instance of `MemoryLocalCache`. The instance can be set to
  30937. * `FirestoreSettings.cache` to tell the SDK which cache layer to use.
  30938. */
  30939. function memoryLocalCache(settings) {
  30940. return new MemoryLocalCacheImpl(settings);
  30941. }
  30942. /**
  30943. * Creates an instance of `PersistentLocalCache`. The instance can be set to
  30944. * `FirestoreSettings.cache` to tell the SDK which cache layer to use.
  30945. *
  30946. * Persistent cache cannot be used in a Node.js environment.
  30947. */
  30948. function persistentLocalCache(settings) {
  30949. return new PersistentLocalCacheImpl(settings);
  30950. }
  30951. class SingleTabManagerImpl {
  30952. constructor(forceOwnership) {
  30953. this.forceOwnership = forceOwnership;
  30954. this.kind = 'persistentSingleTab';
  30955. }
  30956. toJSON() {
  30957. return { kind: this.kind };
  30958. }
  30959. /**
  30960. * @internal
  30961. */
  30962. _initialize(settings) {
  30963. this._onlineComponentProvider = new OnlineComponentProvider();
  30964. this._offlineComponentProvider = new IndexedDbOfflineComponentProvider(this._onlineComponentProvider, settings === null || settings === void 0 ? void 0 : settings.cacheSizeBytes, this.forceOwnership);
  30965. }
  30966. }
  30967. class MultiTabManagerImpl {
  30968. constructor() {
  30969. this.kind = 'PersistentMultipleTab';
  30970. }
  30971. toJSON() {
  30972. return { kind: this.kind };
  30973. }
  30974. /**
  30975. * @internal
  30976. */
  30977. _initialize(settings) {
  30978. this._onlineComponentProvider = new OnlineComponentProvider();
  30979. this._offlineComponentProvider = new MultiTabOfflineComponentProvider(this._onlineComponentProvider, settings === null || settings === void 0 ? void 0 : settings.cacheSizeBytes);
  30980. }
  30981. }
  30982. /**
  30983. * Creates an instance of `PersistentSingleTabManager`.
  30984. *
  30985. * @param settings Configures the created tab manager.
  30986. */
  30987. function persistentSingleTabManager(settings) {
  30988. return new SingleTabManagerImpl(settings === null || settings === void 0 ? void 0 : settings.forceOwnership);
  30989. }
  30990. /**
  30991. * Creates an instance of `PersistentMultipleTabManager`.
  30992. */
  30993. function persistentMultipleTabManager() {
  30994. return new MultiTabManagerImpl();
  30995. }
  30996. /**
  30997. * @license
  30998. * Copyright 2022 Google LLC
  30999. *
  31000. * Licensed under the Apache License, Version 2.0 (the "License");
  31001. * you may not use this file except in compliance with the License.
  31002. * You may obtain a copy of the License at
  31003. *
  31004. * http://www.apache.org/licenses/LICENSE-2.0
  31005. *
  31006. * Unless required by applicable law or agreed to in writing, software
  31007. * distributed under the License is distributed on an "AS IS" BASIS,
  31008. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31009. * See the License for the specific language governing permissions and
  31010. * limitations under the License.
  31011. */
  31012. const DEFAULT_TRANSACTION_OPTIONS = {
  31013. maxAttempts: 5
  31014. };
  31015. function validateTransactionOptions(options) {
  31016. if (options.maxAttempts < 1) {
  31017. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Max attempts must be at least 1');
  31018. }
  31019. }
  31020. /**
  31021. * @license
  31022. * Copyright 2020 Google LLC
  31023. *
  31024. * Licensed under the Apache License, Version 2.0 (the "License");
  31025. * you may not use this file except in compliance with the License.
  31026. * You may obtain a copy of the License at
  31027. *
  31028. * http://www.apache.org/licenses/LICENSE-2.0
  31029. *
  31030. * Unless required by applicable law or agreed to in writing, software
  31031. * distributed under the License is distributed on an "AS IS" BASIS,
  31032. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31033. * See the License for the specific language governing permissions and
  31034. * limitations under the License.
  31035. */
  31036. /**
  31037. * A write batch, used to perform multiple writes as a single atomic unit.
  31038. *
  31039. * A `WriteBatch` object can be acquired by calling {@link writeBatch}. It
  31040. * provides methods for adding writes to the write batch. None of the writes
  31041. * will be committed (or visible locally) until {@link WriteBatch.commit} is
  31042. * called.
  31043. */
  31044. class WriteBatch {
  31045. /** @hideconstructor */
  31046. constructor(_firestore, _commitHandler) {
  31047. this._firestore = _firestore;
  31048. this._commitHandler = _commitHandler;
  31049. this._mutations = [];
  31050. this._committed = false;
  31051. this._dataReader = newUserDataReader(_firestore);
  31052. }
  31053. set(documentRef, data, options) {
  31054. this._verifyNotCommitted();
  31055. const ref = validateReference(documentRef, this._firestore);
  31056. const convertedValue = applyFirestoreDataConverter(ref.converter, data, options);
  31057. const parsed = parseSetData(this._dataReader, 'WriteBatch.set', ref._key, convertedValue, ref.converter !== null, options);
  31058. this._mutations.push(parsed.toMutation(ref._key, Precondition.none()));
  31059. return this;
  31060. }
  31061. update(documentRef, fieldOrUpdateData, value, ...moreFieldsAndValues) {
  31062. this._verifyNotCommitted();
  31063. const ref = validateReference(documentRef, this._firestore);
  31064. // For Compat types, we have to "extract" the underlying types before
  31065. // performing validation.
  31066. fieldOrUpdateData = getModularInstance(fieldOrUpdateData);
  31067. let parsed;
  31068. if (typeof fieldOrUpdateData === 'string' ||
  31069. fieldOrUpdateData instanceof FieldPath) {
  31070. parsed = parseUpdateVarargs(this._dataReader, 'WriteBatch.update', ref._key, fieldOrUpdateData, value, moreFieldsAndValues);
  31071. }
  31072. else {
  31073. parsed = parseUpdateData(this._dataReader, 'WriteBatch.update', ref._key, fieldOrUpdateData);
  31074. }
  31075. this._mutations.push(parsed.toMutation(ref._key, Precondition.exists(true)));
  31076. return this;
  31077. }
  31078. /**
  31079. * Deletes the document referred to by the provided {@link DocumentReference}.
  31080. *
  31081. * @param documentRef - A reference to the document to be deleted.
  31082. * @returns This `WriteBatch` instance. Used for chaining method calls.
  31083. */
  31084. delete(documentRef) {
  31085. this._verifyNotCommitted();
  31086. const ref = validateReference(documentRef, this._firestore);
  31087. this._mutations = this._mutations.concat(new DeleteMutation(ref._key, Precondition.none()));
  31088. return this;
  31089. }
  31090. /**
  31091. * Commits all of the writes in this write batch as a single atomic unit.
  31092. *
  31093. * The result of these writes will only be reflected in document reads that
  31094. * occur after the returned promise resolves. If the client is offline, the
  31095. * write fails. If you would like to see local modifications or buffer writes
  31096. * until the client is online, use the full Firestore SDK.
  31097. *
  31098. * @returns A `Promise` resolved once all of the writes in the batch have been
  31099. * successfully written to the backend as an atomic unit (note that it won't
  31100. * resolve while you're offline).
  31101. */
  31102. commit() {
  31103. this._verifyNotCommitted();
  31104. this._committed = true;
  31105. if (this._mutations.length > 0) {
  31106. return this._commitHandler(this._mutations);
  31107. }
  31108. return Promise.resolve();
  31109. }
  31110. _verifyNotCommitted() {
  31111. if (this._committed) {
  31112. throw new FirestoreError(Code.FAILED_PRECONDITION, 'A write batch can no longer be used after commit() ' +
  31113. 'has been called.');
  31114. }
  31115. }
  31116. }
  31117. function validateReference(documentRef, firestore) {
  31118. documentRef = getModularInstance(documentRef);
  31119. if (documentRef.firestore !== firestore) {
  31120. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Provided document reference is from a different Firestore instance.');
  31121. }
  31122. else {
  31123. return documentRef;
  31124. }
  31125. }
  31126. /**
  31127. * @license
  31128. * Copyright 2020 Google LLC
  31129. *
  31130. * Licensed under the Apache License, Version 2.0 (the "License");
  31131. * you may not use this file except in compliance with the License.
  31132. * You may obtain a copy of the License at
  31133. *
  31134. * http://www.apache.org/licenses/LICENSE-2.0
  31135. *
  31136. * Unless required by applicable law or agreed to in writing, software
  31137. * distributed under the License is distributed on an "AS IS" BASIS,
  31138. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31139. * See the License for the specific language governing permissions and
  31140. * limitations under the License.
  31141. */
  31142. // TODO(mrschmidt) Consider using `BaseTransaction` as the base class in the
  31143. // legacy SDK.
  31144. /**
  31145. * A reference to a transaction.
  31146. *
  31147. * The `Transaction` object passed to a transaction's `updateFunction` provides
  31148. * the methods to read and write data within the transaction context. See
  31149. * {@link runTransaction}.
  31150. */
  31151. class Transaction$1 {
  31152. /** @hideconstructor */
  31153. constructor(_firestore, _transaction) {
  31154. this._firestore = _firestore;
  31155. this._transaction = _transaction;
  31156. this._dataReader = newUserDataReader(_firestore);
  31157. }
  31158. /**
  31159. * Reads the document referenced by the provided {@link DocumentReference}.
  31160. *
  31161. * @param documentRef - A reference to the document to be read.
  31162. * @returns A `DocumentSnapshot` with the read data.
  31163. */
  31164. get(documentRef) {
  31165. const ref = validateReference(documentRef, this._firestore);
  31166. const userDataWriter = new LiteUserDataWriter(this._firestore);
  31167. return this._transaction.lookup([ref._key]).then(docs => {
  31168. if (!docs || docs.length !== 1) {
  31169. return fail();
  31170. }
  31171. const doc = docs[0];
  31172. if (doc.isFoundDocument()) {
  31173. return new DocumentSnapshot$1(this._firestore, userDataWriter, doc.key, doc, ref.converter);
  31174. }
  31175. else if (doc.isNoDocument()) {
  31176. return new DocumentSnapshot$1(this._firestore, userDataWriter, ref._key, null, ref.converter);
  31177. }
  31178. else {
  31179. throw fail();
  31180. }
  31181. });
  31182. }
  31183. set(documentRef, value, options) {
  31184. const ref = validateReference(documentRef, this._firestore);
  31185. const convertedValue = applyFirestoreDataConverter(ref.converter, value, options);
  31186. const parsed = parseSetData(this._dataReader, 'Transaction.set', ref._key, convertedValue, ref.converter !== null, options);
  31187. this._transaction.set(ref._key, parsed);
  31188. return this;
  31189. }
  31190. update(documentRef, fieldOrUpdateData, value, ...moreFieldsAndValues) {
  31191. const ref = validateReference(documentRef, this._firestore);
  31192. // For Compat types, we have to "extract" the underlying types before
  31193. // performing validation.
  31194. fieldOrUpdateData = getModularInstance(fieldOrUpdateData);
  31195. let parsed;
  31196. if (typeof fieldOrUpdateData === 'string' ||
  31197. fieldOrUpdateData instanceof FieldPath) {
  31198. parsed = parseUpdateVarargs(this._dataReader, 'Transaction.update', ref._key, fieldOrUpdateData, value, moreFieldsAndValues);
  31199. }
  31200. else {
  31201. parsed = parseUpdateData(this._dataReader, 'Transaction.update', ref._key, fieldOrUpdateData);
  31202. }
  31203. this._transaction.update(ref._key, parsed);
  31204. return this;
  31205. }
  31206. /**
  31207. * Deletes the document referred to by the provided {@link DocumentReference}.
  31208. *
  31209. * @param documentRef - A reference to the document to be deleted.
  31210. * @returns This `Transaction` instance. Used for chaining method calls.
  31211. */
  31212. delete(documentRef) {
  31213. const ref = validateReference(documentRef, this._firestore);
  31214. this._transaction.delete(ref._key);
  31215. return this;
  31216. }
  31217. }
  31218. /**
  31219. * @license
  31220. * Copyright 2020 Google LLC
  31221. *
  31222. * Licensed under the Apache License, Version 2.0 (the "License");
  31223. * you may not use this file except in compliance with the License.
  31224. * You may obtain a copy of the License at
  31225. *
  31226. * http://www.apache.org/licenses/LICENSE-2.0
  31227. *
  31228. * Unless required by applicable law or agreed to in writing, software
  31229. * distributed under the License is distributed on an "AS IS" BASIS,
  31230. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31231. * See the License for the specific language governing permissions and
  31232. * limitations under the License.
  31233. */
  31234. /**
  31235. * A reference to a transaction.
  31236. *
  31237. * The `Transaction` object passed to a transaction's `updateFunction` provides
  31238. * the methods to read and write data within the transaction context. See
  31239. * {@link runTransaction}.
  31240. */
  31241. class Transaction extends Transaction$1 {
  31242. // This class implements the same logic as the Transaction API in the Lite SDK
  31243. // but is subclassed in order to return its own DocumentSnapshot types.
  31244. /** @hideconstructor */
  31245. constructor(_firestore, _transaction) {
  31246. super(_firestore, _transaction);
  31247. this._firestore = _firestore;
  31248. }
  31249. /**
  31250. * Reads the document referenced by the provided {@link DocumentReference}.
  31251. *
  31252. * @param documentRef - A reference to the document to be read.
  31253. * @returns A `DocumentSnapshot` with the read data.
  31254. */
  31255. get(documentRef) {
  31256. const ref = validateReference(documentRef, this._firestore);
  31257. const userDataWriter = new ExpUserDataWriter(this._firestore);
  31258. return super
  31259. .get(documentRef)
  31260. .then(liteDocumentSnapshot => new DocumentSnapshot(this._firestore, userDataWriter, ref._key, liteDocumentSnapshot._document, new SnapshotMetadata(
  31261. /* hasPendingWrites= */ false,
  31262. /* fromCache= */ false), ref.converter));
  31263. }
  31264. }
  31265. /**
  31266. * Executes the given `updateFunction` and then attempts to commit the changes
  31267. * applied within the transaction. If any document read within the transaction
  31268. * has changed, Cloud Firestore retries the `updateFunction`. If it fails to
  31269. * commit after 5 attempts, the transaction fails.
  31270. *
  31271. * The maximum number of writes allowed in a single transaction is 500.
  31272. *
  31273. * @param firestore - A reference to the Firestore database to run this
  31274. * transaction against.
  31275. * @param updateFunction - The function to execute within the transaction
  31276. * context.
  31277. * @param options - An options object to configure maximum number of attempts to
  31278. * commit.
  31279. * @returns If the transaction completed successfully or was explicitly aborted
  31280. * (the `updateFunction` returned a failed promise), the promise returned by the
  31281. * `updateFunction `is returned here. Otherwise, if the transaction failed, a
  31282. * rejected promise with the corresponding failure error is returned.
  31283. */
  31284. function runTransaction(firestore, updateFunction, options) {
  31285. firestore = cast(firestore, Firestore);
  31286. const optionsWithDefaults = Object.assign(Object.assign({}, DEFAULT_TRANSACTION_OPTIONS), options);
  31287. validateTransactionOptions(optionsWithDefaults);
  31288. const client = ensureFirestoreConfigured(firestore);
  31289. return firestoreClientTransaction(client, internalTransaction => updateFunction(new Transaction(firestore, internalTransaction)), optionsWithDefaults);
  31290. }
  31291. /**
  31292. * @license
  31293. * Copyright 2020 Google LLC
  31294. *
  31295. * Licensed under the Apache License, Version 2.0 (the "License");
  31296. * you may not use this file except in compliance with the License.
  31297. * You may obtain a copy of the License at
  31298. *
  31299. * http://www.apache.org/licenses/LICENSE-2.0
  31300. *
  31301. * Unless required by applicable law or agreed to in writing, software
  31302. * distributed under the License is distributed on an "AS IS" BASIS,
  31303. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31304. * See the License for the specific language governing permissions and
  31305. * limitations under the License.
  31306. */
  31307. /**
  31308. * Returns a sentinel for use with {@link @firebase/firestore/lite#(updateDoc:1)} or
  31309. * {@link @firebase/firestore/lite#(setDoc:1)} with `{merge: true}` to mark a field for deletion.
  31310. */
  31311. function deleteField() {
  31312. return new DeleteFieldValueImpl('deleteField');
  31313. }
  31314. /**
  31315. * Returns a sentinel used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link @firebase/firestore/lite#(updateDoc:1)} to
  31316. * include a server-generated timestamp in the written data.
  31317. */
  31318. function serverTimestamp() {
  31319. return new ServerTimestampFieldValueImpl('serverTimestamp');
  31320. }
  31321. /**
  31322. * Returns a special value that can be used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link
  31323. * @firebase/firestore/lite#(updateDoc:1)} that tells the server to union the given elements with any array
  31324. * value that already exists on the server. Each specified element that doesn't
  31325. * already exist in the array will be added to the end. If the field being
  31326. * modified is not already an array it will be overwritten with an array
  31327. * containing exactly the specified elements.
  31328. *
  31329. * @param elements - The elements to union into the array.
  31330. * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
  31331. * `updateDoc()`.
  31332. */
  31333. function arrayUnion(...elements) {
  31334. // NOTE: We don't actually parse the data until it's used in set() or
  31335. // update() since we'd need the Firestore instance to do this.
  31336. return new ArrayUnionFieldValueImpl('arrayUnion', elements);
  31337. }
  31338. /**
  31339. * Returns a special value that can be used with {@link (setDoc:1)} or {@link
  31340. * updateDoc:1} that tells the server to remove the given elements from any
  31341. * array value that already exists on the server. All instances of each element
  31342. * specified will be removed from the array. If the field being modified is not
  31343. * already an array it will be overwritten with an empty array.
  31344. *
  31345. * @param elements - The elements to remove from the array.
  31346. * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
  31347. * `updateDoc()`
  31348. */
  31349. function arrayRemove(...elements) {
  31350. // NOTE: We don't actually parse the data until it's used in set() or
  31351. // update() since we'd need the Firestore instance to do this.
  31352. return new ArrayRemoveFieldValueImpl('arrayRemove', elements);
  31353. }
  31354. /**
  31355. * Returns a special value that can be used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link
  31356. * @firebase/firestore/lite#(updateDoc:1)} that tells the server to increment the field's current value by
  31357. * the given value.
  31358. *
  31359. * If either the operand or the current field value uses floating point
  31360. * precision, all arithmetic follows IEEE 754 semantics. If both values are
  31361. * integers, values outside of JavaScript's safe number range
  31362. * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject to
  31363. * precision loss. Furthermore, once processed by the Firestore backend, all
  31364. * integer operations are capped between -2^63 and 2^63-1.
  31365. *
  31366. * If the current field value is not of type `number`, or if the field does not
  31367. * yet exist, the transformation sets the field to the given value.
  31368. *
  31369. * @param n - The value to increment by.
  31370. * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
  31371. * `updateDoc()`
  31372. */
  31373. function increment(n) {
  31374. return new NumericIncrementFieldValueImpl('increment', n);
  31375. }
  31376. /**
  31377. * @license
  31378. * Copyright 2020 Google LLC
  31379. *
  31380. * Licensed under the Apache License, Version 2.0 (the "License");
  31381. * you may not use this file except in compliance with the License.
  31382. * You may obtain a copy of the License at
  31383. *
  31384. * http://www.apache.org/licenses/LICENSE-2.0
  31385. *
  31386. * Unless required by applicable law or agreed to in writing, software
  31387. * distributed under the License is distributed on an "AS IS" BASIS,
  31388. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31389. * See the License for the specific language governing permissions and
  31390. * limitations under the License.
  31391. */
  31392. /**
  31393. * Creates a write batch, used for performing multiple writes as a single
  31394. * atomic operation. The maximum number of writes allowed in a single {@link WriteBatch}
  31395. * is 500.
  31396. *
  31397. * Unlike transactions, write batches are persisted offline and therefore are
  31398. * preferable when you don't need to condition your writes on read data.
  31399. *
  31400. * @returns A {@link WriteBatch} that can be used to atomically execute multiple
  31401. * writes.
  31402. */
  31403. function writeBatch(firestore) {
  31404. firestore = cast(firestore, Firestore);
  31405. ensureFirestoreConfigured(firestore);
  31406. return new WriteBatch(firestore, mutations => executeWrite(firestore, mutations));
  31407. }
  31408. /**
  31409. * @license
  31410. * Copyright 2021 Google LLC
  31411. *
  31412. * Licensed under the Apache License, Version 2.0 (the "License");
  31413. * you may not use this file except in compliance with the License.
  31414. * You may obtain a copy of the License at
  31415. *
  31416. * http://www.apache.org/licenses/LICENSE-2.0
  31417. *
  31418. * Unless required by applicable law or agreed to in writing, software
  31419. * distributed under the License is distributed on an "AS IS" BASIS,
  31420. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31421. * See the License for the specific language governing permissions and
  31422. * limitations under the License.
  31423. */
  31424. function setIndexConfiguration(firestore, jsonOrConfiguration) {
  31425. var _a;
  31426. firestore = cast(firestore, Firestore);
  31427. const client = ensureFirestoreConfigured(firestore);
  31428. if (!client._uninitializedComponentsProvider ||
  31429. ((_a = client._uninitializedComponentsProvider) === null || _a === void 0 ? void 0 : _a._offlineKind) === 'memory') {
  31430. // PORTING NOTE: We don't return an error if the user has not enabled
  31431. // persistence since `enableIndexeddbPersistence()` can fail on the Web.
  31432. logWarn('Cannot enable indexes when persistence is disabled');
  31433. return Promise.resolve();
  31434. }
  31435. const parsedIndexes = parseIndexes(jsonOrConfiguration);
  31436. return firestoreClientSetIndexConfiguration(client, parsedIndexes);
  31437. }
  31438. function parseIndexes(jsonOrConfiguration) {
  31439. const indexConfiguration = typeof jsonOrConfiguration === 'string'
  31440. ? tryParseJson(jsonOrConfiguration)
  31441. : jsonOrConfiguration;
  31442. const parsedIndexes = [];
  31443. if (Array.isArray(indexConfiguration.indexes)) {
  31444. for (const index of indexConfiguration.indexes) {
  31445. const collectionGroup = tryGetString(index, 'collectionGroup');
  31446. const segments = [];
  31447. if (Array.isArray(index.fields)) {
  31448. for (const field of index.fields) {
  31449. const fieldPathString = tryGetString(field, 'fieldPath');
  31450. const fieldPath = fieldPathFromDotSeparatedString('setIndexConfiguration', fieldPathString);
  31451. if (field.arrayConfig === 'CONTAINS') {
  31452. segments.push(new IndexSegment(fieldPath, 2 /* IndexKind.CONTAINS */));
  31453. }
  31454. else if (field.order === 'ASCENDING') {
  31455. segments.push(new IndexSegment(fieldPath, 0 /* IndexKind.ASCENDING */));
  31456. }
  31457. else if (field.order === 'DESCENDING') {
  31458. segments.push(new IndexSegment(fieldPath, 1 /* IndexKind.DESCENDING */));
  31459. }
  31460. }
  31461. }
  31462. parsedIndexes.push(new FieldIndex(FieldIndex.UNKNOWN_ID, collectionGroup, segments, IndexState.empty()));
  31463. }
  31464. }
  31465. return parsedIndexes;
  31466. }
  31467. function tryParseJson(json) {
  31468. try {
  31469. return JSON.parse(json);
  31470. }
  31471. catch (e) {
  31472. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Failed to parse JSON: ' + (e === null || e === void 0 ? void 0 : e.message));
  31473. }
  31474. }
  31475. function tryGetString(data, property) {
  31476. if (typeof data[property] !== 'string') {
  31477. throw new FirestoreError(Code.INVALID_ARGUMENT, 'Missing string value for: ' + property);
  31478. }
  31479. return data[property];
  31480. }
  31481. /**
  31482. * @license
  31483. * Copyright 2021 Google LLC
  31484. *
  31485. * Licensed under the Apache License, Version 2.0 (the "License");
  31486. * you may not use this file except in compliance with the License.
  31487. * You may obtain a copy of the License at
  31488. *
  31489. * http://www.apache.org/licenses/LICENSE-2.0
  31490. *
  31491. * Unless required by applicable law or agreed to in writing, software
  31492. * distributed under the License is distributed on an "AS IS" BASIS,
  31493. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31494. * See the License for the specific language governing permissions and
  31495. * limitations under the License.
  31496. */
  31497. registerFirestore('node');
  31498. export { AbstractUserDataWriter, AggregateField, AggregateQuerySnapshot, Bytes, CACHE_SIZE_UNLIMITED, CollectionReference, DocumentReference, DocumentSnapshot, FieldPath, FieldValue, Firestore, FirestoreError, GeoPoint, LoadBundleTask, Query, QueryCompositeFilterConstraint, QueryConstraint, QueryDocumentSnapshot, QueryEndAtConstraint, QueryFieldFilterConstraint, QueryLimitConstraint, QueryOrderByConstraint, QuerySnapshot, QueryStartAtConstraint, SnapshotMetadata, Timestamp, Transaction, WriteBatch, DatabaseId as _DatabaseId, DocumentKey as _DocumentKey, EmptyAppCheckTokenProvider as _EmptyAppCheckTokenProvider, EmptyAuthCredentialsProvider as _EmptyAuthCredentialsProvider, FieldPath$1 as _FieldPath, TestingHooks as _TestingHooks, cast as _cast, debugAssert as _debugAssert, isBase64Available as _isBase64Available, logWarn as _logWarn, validateIsNotUsedTogether as _validateIsNotUsedTogether, addDoc, aggregateFieldEqual, aggregateQuerySnapshotEqual, and, arrayRemove, arrayUnion, average, clearIndexedDbPersistence, collection, collectionGroup, connectFirestoreEmulator, count, deleteDoc, deleteField, disableNetwork, doc, documentId, enableIndexedDbPersistence, enableMultiTabIndexedDbPersistence, enableNetwork, endAt, endBefore, ensureFirestoreConfigured, executeWrite, getAggregateFromServer, getCountFromServer, getDoc, getDocFromCache, getDocFromServer, getDocs, getDocsFromCache, getDocsFromServer, getFirestore, increment, initializeFirestore, limit, limitToLast, loadBundle, memoryEagerGarbageCollector, memoryLocalCache, memoryLruGarbageCollector, namedQuery, onSnapshot, onSnapshotsInSync, or, orderBy, persistentLocalCache, persistentMultipleTabManager, persistentSingleTabManager, query, queryEqual, refEqual, runTransaction, serverTimestamp, setDoc, setIndexConfiguration, setLogLevel, snapshotEqual, startAfter, startAt, sum, terminate, updateDoc, waitForPendingWrites, where, writeBatch };
  31499. //# sourceMappingURL=index.node.mjs.map