[{"data":1,"prerenderedAt":704},["ShallowReactive",2],{"fougere:post.list:{}":3,"docs:\u002Fdocs\u002Fdata\u002Fmirror":6,"docs-uses":392,"docs-nav":566},{"items":4,"hasMore":5},[],false,{"id":7,"title":8,"body":9,"description":378,"extension":379,"forms":380,"kind":382,"meta":383,"navigation":384,"path":385,"seo":386,"stem":387,"step":388,"uses":389,"__hash__":391},"docs\u002Fdocs\u002F3.data\u002F7.mirror.md","Mirror a source you cannot query",{"type":10,"value":11,"toc":376},"minimark",[12,25,318,345,352,355,372],[13,14,15,16,20,21,24],"p",{},"A real database is queried where it is. Some sources cannot be queried at all — an HTTP API, a\npartner's catalog that only answers ",[17,18,19],"code",{},"?page=&since=",". ",[17,22,23],{},"Mirror\u003CShape>"," keeps a local copy of their\nrows, which you can then query like any other. A subclass writes one thing, how to pull:",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-ts shiki shiki-themes material-theme-lighter fougere-pastel material-theme-palenight","\u002F\u002F services\u002FPartnerCatalog.ts\nexport default class PartnerCatalog extends Mirror\u003CBookCard> {\n  async *pull(since?: Date) {\n    for (let page = 0; page !== null; ) {\n      const body = await fetch(`${api}?page=${page}&since=${since?.toISOString() ?? ''}`);\n      const { items, next } = await body.json();\n      yield items.map(toCard);\n      page = next;\n    }\n  }\n}\n","ts","",[17,33,34,43,80,112,155,230,266,288,300,306,312],{"__ignoreMap":31},[35,36,39],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"skXKP","\u002F\u002F services\u002FPartnerCatalog.ts\n",[35,44,46,50,53,57,61,64,67,71,74,77],{"class":37,"line":45},2,[35,47,49],{"class":48},"sK6XW","export",[35,51,52],{"class":48}," default",[35,54,56],{"class":55},"s76I7"," class",[35,58,60],{"class":59},"sBZYL"," PartnerCatalog",[35,62,63],{"class":55}," extends",[35,65,66],{"class":59}," Mirror",[35,68,70],{"class":69},"s15xx","\u003C",[35,72,73],{"class":59},"BookCard",[35,75,76],{"class":69},">",[35,78,79],{"class":69}," {\n",[35,81,83,86,90,94,97,101,104,107,110],{"class":37,"line":82},3,[35,84,85],{"class":55},"  async",[35,87,89],{"class":88},"sFoTR"," *",[35,91,93],{"class":92},"shDkW","pull",[35,95,96],{"class":69},"(",[35,98,100],{"class":99},"shDUQ","since",[35,102,103],{"class":69},"?:",[35,105,106],{"class":59}," Date",[35,108,109],{"class":69},")",[35,111,79],{"class":69},[35,113,115,118,122,125,129,132,136,139,141,144,148,150,153],{"class":37,"line":114},4,[35,116,117],{"class":48},"    for",[35,119,121],{"class":120},"spAFX"," (",[35,123,124],{"class":55},"let",[35,126,128],{"class":127},"sPEhv"," page",[35,130,131],{"class":69}," =",[35,133,135],{"class":134},"s3dPA"," 0",[35,137,138],{"class":69},";",[35,140,128],{"class":127},[35,142,143],{"class":69}," !==",[35,145,147],{"class":146},"sDDC1"," null",[35,149,138],{"class":69},[35,151,152],{"class":120}," )",[35,154,79],{"class":69},[35,156,158,161,164,166,169,173,175,178,181,184,187,191,193,196,198,201,203,205,208,211,215,218,221,223,225,227],{"class":37,"line":157},5,[35,159,160],{"class":55},"      const",[35,162,163],{"class":127}," body",[35,165,131],{"class":69},[35,167,168],{"class":48}," await",[35,170,172],{"class":171},"s4mR9"," fetch",[35,174,96],{"class":120},[35,176,177],{"class":146},"`",[35,179,180],{"class":69},"${",[35,182,183],{"class":127},"api",[35,185,186],{"class":69},"}",[35,188,190],{"class":189},"sN-8n","?page=",[35,192,180],{"class":69},[35,194,195],{"class":127},"page",[35,197,186],{"class":69},[35,199,200],{"class":189},"&since=",[35,202,180],{"class":69},[35,204,100],{"class":127},[35,206,207],{"class":69},"?.",[35,209,210],{"class":171},"toISOString",[35,212,214],{"class":213},"srlTG","()",[35,216,217],{"class":69}," ??",[35,219,220],{"class":146}," ''",[35,222,186],{"class":69},[35,224,177],{"class":146},[35,226,109],{"class":120},[35,228,229],{"class":69},";\n",[35,231,233,235,238,241,244,247,250,252,254,256,259,262,264],{"class":37,"line":232},6,[35,234,160],{"class":55},[35,236,237],{"class":69}," {",[35,239,240],{"class":127}," items",[35,242,243],{"class":69},",",[35,245,246],{"class":127}," next",[35,248,249],{"class":69}," }",[35,251,131],{"class":69},[35,253,168],{"class":48},[35,255,163],{"class":127},[35,257,258],{"class":69},".",[35,260,261],{"class":171},"json",[35,263,214],{"class":120},[35,265,229],{"class":69},[35,267,269,272,274,276,279,281,284,286],{"class":37,"line":268},7,[35,270,271],{"class":48},"      yield",[35,273,240],{"class":127},[35,275,258],{"class":69},[35,277,278],{"class":171},"map",[35,280,96],{"class":120},[35,282,283],{"class":127},"toCard",[35,285,109],{"class":120},[35,287,229],{"class":69},[35,289,291,294,296,298],{"class":37,"line":290},8,[35,292,293],{"class":127},"      page",[35,295,131],{"class":69},[35,297,246],{"class":127},[35,299,229],{"class":69},[35,301,303],{"class":37,"line":302},9,[35,304,305],{"class":69},"    }\n",[35,307,309],{"class":37,"line":308},10,[35,310,311],{"class":69},"  }\n",[35,313,315],{"class":37,"line":314},11,[35,316,317],{"class":69},"}\n",[13,319,320,321,328,329,332,333,336,337,340,341,344],{},"It is a ",[322,323,325],"a",{"href":324},"\u002Fdocs\u002Fconcepts\u002Frepository",[17,326,327],{},"Repository\u003CBookCard>"," that fills itself: the copy reads like any\nrows, ",[17,330,331],{},"this.list()",", and a handler extending it serves the copy and ",[17,334,335],{},"refresh",". Everything around the pull is the same for every mirror and lives in the base: ",[17,338,339],{},"refresh(since?)","\npulls from the mark it is given and writes each page in one ",[17,342,343],{},"upsertAll",". The mark — the date of the\nlast pass — is the caller's to keep, and to move only once a pass has returned: a pass that failed\nhalfway must not skip what the source changed in the meantime.",[13,346,347,348,351],{},"Each page is checked like every other write, strictly: otherwise a partner renaming ",[17,349,350],{},"label"," would\nwrite nulls without a word. A refused page stops the pass; what\nearlier pages wrote stays, because an upsert is idempotent and the next run resumes from\nthe mark.",[13,353,354],{},"It does not schedule itself. A boot hook, a cron, an operation behind a facade are all\nlegitimate and none of them is this class's business.",[356,357,358],"note",{},[13,359,360,363,364,367,368,371],{},[17,361,362],{},"Mirror"," has no directory of its own in the frond vocabulary — a ",[17,365,366],{},"mirrors\u002F"," folder is not\nscanned. Place it in ",[17,369,370],{},"services\u002F",", where nothing\nserves it, or extend it from a handler to serve the copy.",[373,374,375],"style",{},"html pre.shiki code .skXKP, html code.shiki .skXKP{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7D8C80;--shiki-default-font-style:inherit;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sK6XW, html code.shiki .sK6XW{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#8FCF86;--shiki-default-font-style:inherit;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s76I7, html code.shiki .s76I7{--shiki-light:#9C3EDA;--shiki-default:#8FCF86;--shiki-dark:#C792EA}html pre.shiki code .sBZYL, html code.shiki .sBZYL{--shiki-light:#E2931D;--shiki-default:#EFCD8F;--shiki-dark:#FFCB6B}html pre.shiki code .s15xx, html code.shiki .s15xx{--shiki-light:#39ADB5;--shiki-default:#A3B0A5;--shiki-dark:#89DDFF}html pre.shiki code .sFoTR, html code.shiki .sFoTR{--shiki-light:#39ADB5;--shiki-default:#8FCF86;--shiki-dark:#89DDFF}html pre.shiki code .shDkW, html code.shiki .shDkW{--shiki-light:#E53935;--shiki-default:#C9B6EA;--shiki-dark:#F07178}html pre.shiki code .shDUQ, html code.shiki .shDUQ{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#DDE5DA;--shiki-default-font-style:inherit;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spAFX, html code.shiki .spAFX{--shiki-light:#E53935;--shiki-default:#A3B0A5;--shiki-dark:#F07178}html pre.shiki code .sPEhv, html code.shiki .sPEhv{--shiki-light:#90A4AE;--shiki-default:#DDE5DA;--shiki-dark:#BABED8}html pre.shiki code .s3dPA, html code.shiki .s3dPA{--shiki-light:#F76D47;--shiki-default:#EE9F6B;--shiki-dark:#F78C6C}html pre.shiki code .sDDC1, html code.shiki .sDDC1{--shiki-light:#39ADB5;--shiki-default:#EE9F6B;--shiki-dark:#89DDFF}html pre.shiki code .s4mR9, html code.shiki .s4mR9{--shiki-light:#6182B8;--shiki-default:#C9B6EA;--shiki-dark:#82AAFF}html pre.shiki code .sN-8n, html code.shiki .sN-8n{--shiki-light:#91B859;--shiki-default:#EE9F6B;--shiki-dark:#C3E88D}html pre.shiki code .srlTG, html code.shiki .srlTG{--shiki-light:#90A4AE;--shiki-default:#A3B0A5;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":31,"searchDepth":45,"depth":45,"links":377},[],"A local copy of rows behind an API, refreshed from a mark.","md",[381],"Mirror\u003CT>","task",{},true,"\u002Fdocs\u002Fdata\u002Fmirror",{"title":8,"description":378},"docs\u002F3.data\u002F7.mirror",null,[390],"repository","seuZqvSs6R4G1z96fQ-YcdY6hu6xU7GmqMipY3ddu5k",[393,396,399,401,404,407,410,413,416,422,428,433,437,441,445,449,453,455,459,463,468,472,477,481,485,489,493,497,501,505,509,513,517,521,525,529,533,536,539,542,545,548,551,554,557,560,563],{"path":394,"title":395,"uses":388},"\u002Fdocs\u002Fconcepts\u002Fentity","Entity",{"path":397,"title":398,"uses":388},"\u002Fdocs\u002Fconcepts\u002Fhandler","Handler",{"path":324,"title":400,"uses":388},"Repository",{"path":402,"title":403,"uses":388},"\u002Fdocs\u002Fconcepts\u002Ffrond","Frond",{"path":405,"title":406,"uses":388},"\u002Fdocs\u002Fconcepts\u002Ffacade","Facade",{"path":408,"title":409,"uses":388},"\u002Fdocs\u002Fconcepts\u002Ffact","Fact",{"path":411,"title":412,"uses":388},"\u002Fdocs\u002Fconcepts\u002Fport","Port",{"path":414,"title":415,"uses":388},"\u002Fdocs\u002Fconcepts\u002Fconfiguration","Configuration",{"path":417,"title":418,"uses":419},"\u002Fdocs\u002Faccess\u002Fauthentication","Authentication",[420,421],"port","frond",{"path":423,"title":424,"uses":425},"\u002Fdocs\u002Faccess\u002Fstate","Keep a caller's state",[426,427],"configuration","handler",{"path":429,"title":430,"uses":431},"\u002Fdocs\u002Fdata\u002Fsql","SQLite or Postgres",[432,426],"entity",{"path":434,"title":435,"uses":436},"\u002Fdocs\u002Fdata\u002Fseveral-databases","Several databases",[426,432],{"path":438,"title":439,"uses":440},"\u002Fdocs\u002Fdata\u002Fcomputed-field","A computed field",[432,420],{"path":442,"title":443,"uses":444},"\u002Fdocs\u002Fdata\u002Fevolve","Evolve a schema",[432],{"path":446,"title":447,"uses":448},"\u002Fdocs\u002Fdata\u002Fseeds","Seed data",[432],{"path":450,"title":451,"uses":452},"\u002Fdocs\u002Fdata\u002Fvalues","Read settings and secrets",[432,426],{"path":385,"title":8,"uses":454},[390],{"path":456,"title":457,"uses":458},"\u002Fdocs\u002Fdata\u002Ftogether","Two writes as one",[390],{"path":460,"title":461,"uses":462},"\u002Fdocs\u002Fbehaviour\u002Faround-every-call","Around every call",[420,421],{"path":464,"title":465,"uses":466},"\u002Fdocs\u002Fbehaviour\u002Ffinish-a-fact","Finish a fact",[467],"fact",{"path":469,"title":470,"uses":471},"\u002Fdocs\u002Fserve\u002Frest-graphql","REST, OpenAPI, GraphQL",[427,426],{"path":473,"title":474,"uses":475},"\u002Fdocs\u002Fserve\u002Fpages","Pages: queries and commands",[427,476],"facade",{"path":478,"title":479,"uses":480},"\u002Fdocs\u002Fserve\u002Fforms","A form",[432],{"path":482,"title":483,"uses":484},"\u002Fdocs\u002Fserve\u002Fadmin","An admin panel",[427],{"path":486,"title":487,"uses":488},"\u002Fdocs\u002Frun\u002Fdeploy","Deploy a split",[426,421],{"path":490,"title":491,"uses":492},"\u002Fdocs\u002Frun\u002Fsecure","Secure calls between processes",[426,421],{"path":494,"title":495,"uses":496},"\u002Fdocs\u002Frun\u002Flifecycle","Reload and shut down",[426],{"path":498,"title":499,"uses":500},"\u002Fdocs\u002Frun\u002Fobserve","Observe",[426,427],{"path":502,"title":503,"uses":504},"\u002Fdocs\u002Frun\u002Fdev-panel","The dev panel",[427],{"path":506,"title":507,"uses":508},"\u002Fdocs\u002Frun\u002Ftest","Test",[432,420],{"path":510,"title":511,"uses":512},"\u002Fdocs\u002Fadopt\u002Fnew-project","Start a project",[421,426],{"path":514,"title":515,"uses":516},"\u002Fdocs\u002Fadopt\u002Fexisting-app","Add Fougere to an existing app",[421,426],{"path":518,"title":519,"uses":520},"\u002Fdocs\u002Fadopt\u002Fbring-your-schema","Bring your schema",[432],{"path":522,"title":523,"uses":524},"\u002Fdocs\u002Fadopt\u002Fhosts","Pick a host",[426],{"path":526,"title":527,"uses":528},"\u002Fdocs\u002Fadopt\u002Fanother-repository","A frond from another repository",[476,421],{"path":530,"title":531,"uses":532},"\u002Fdocs\u002Fadopt\u002Fdemos","Demos",[],{"path":534,"title":535,"uses":388},"\u002Fdocs\u002Fextending\u002Fscan","How the scan reads a project",{"path":537,"title":538,"uses":388},"\u002Fdocs\u002Fextending\u002Fwithout-compiler","Boot without the compiler",{"path":540,"title":541,"uses":388},"\u002Fdocs\u002Fextending\u002Fboot","The boot, step by step",{"path":543,"title":544,"uses":388},"\u002Fdocs\u002Fextending\u002Fcard-and-wire","The card and the wire",{"path":546,"title":547,"uses":388},"\u002Fdocs\u002Fextending\u002Fextension-points","Registries and extension points",{"path":549,"title":550,"uses":388},"\u002Fdocs\u002Fextending\u002Fvocabulary","Teach a word",{"path":552,"title":553,"uses":388},"\u002Fdocs\u002Fextending\u002Fextensions","Run code at boot and shutdown",{"path":555,"title":556,"uses":388},"\u002Fdocs\u002Fextending\u002Fsource-adapter","Write a source adapter",{"path":558,"title":559,"uses":388},"\u002Fdocs\u002Fextending\u002Fsurface","Write a surface",{"path":561,"title":562,"uses":388},"\u002Fdocs\u002Fextending\u002Fforeign-frond","A frond in another language",{"path":564,"title":565,"uses":388},"\u002Fdocs","What is Fougere",[567],{"title":568,"path":564,"stem":569,"children":570,"page":-1},"Docs","docs",[571,573,594,603,623,632,645,662,679],{"title":565,"path":564,"stem":572},"docs\u002Findex",{"title":574,"path":575,"stem":576,"children":577,"page":5},"Concepts","\u002Fdocs\u002Fconcepts","docs\u002F1.concepts",[578,580,582,584,586,588,590,592],{"title":395,"path":394,"stem":579},"docs\u002F1.concepts\u002F1.entity",{"title":398,"path":397,"stem":581},"docs\u002F1.concepts\u002F2.handler",{"title":400,"path":324,"stem":583},"docs\u002F1.concepts\u002F3.repository",{"title":403,"path":402,"stem":585},"docs\u002F1.concepts\u002F4.frond",{"title":406,"path":405,"stem":587},"docs\u002F1.concepts\u002F5.facade",{"title":409,"path":408,"stem":589},"docs\u002F1.concepts\u002F6.fact",{"title":412,"path":411,"stem":591},"docs\u002F1.concepts\u002F7.port",{"title":415,"path":414,"stem":593},"docs\u002F1.concepts\u002F8.configuration",{"title":595,"path":596,"stem":597,"children":598,"page":5},"Access","\u002Fdocs\u002Faccess","docs\u002F2.access",[599,601],{"title":418,"path":417,"stem":600},"docs\u002F2.access\u002F1.authentication",{"title":424,"path":423,"stem":602},"docs\u002F2.access\u002F2.state",{"title":604,"path":605,"stem":606,"children":607,"page":5},"Data","\u002Fdocs\u002Fdata","docs\u002F3.data",[608,610,612,614,616,618,620,621],{"title":430,"path":429,"stem":609},"docs\u002F3.data\u002F1.sql",{"title":435,"path":434,"stem":611},"docs\u002F3.data\u002F2.several-databases",{"title":439,"path":438,"stem":613},"docs\u002F3.data\u002F3.computed-field",{"title":443,"path":442,"stem":615},"docs\u002F3.data\u002F4.evolve",{"title":447,"path":446,"stem":617},"docs\u002F3.data\u002F5.seeds",{"title":451,"path":450,"stem":619},"docs\u002F3.data\u002F6.values",{"title":8,"path":385,"stem":387},{"title":457,"path":456,"stem":622},"docs\u002F3.data\u002F8.together",{"title":624,"path":625,"stem":626,"children":627,"page":5},"Behaviour","\u002Fdocs\u002Fbehaviour","docs\u002F4.behaviour",[628,630],{"title":461,"path":460,"stem":629},"docs\u002F4.behaviour\u002F1.around-every-call",{"title":465,"path":464,"stem":631},"docs\u002F4.behaviour\u002F2.finish-a-fact",{"title":633,"path":634,"stem":635,"children":636,"page":5},"Serve","\u002Fdocs\u002Fserve","docs\u002F5.serve",[637,639,641,643],{"title":470,"path":469,"stem":638},"docs\u002F5.serve\u002F1.rest-graphql",{"title":474,"path":473,"stem":640},"docs\u002F5.serve\u002F2.pages",{"title":479,"path":478,"stem":642},"docs\u002F5.serve\u002F3.forms",{"title":483,"path":482,"stem":644},"docs\u002F5.serve\u002F4.admin",{"title":646,"path":647,"stem":648,"children":649,"page":5},"Run","\u002Fdocs\u002Frun","docs\u002F6.run",[650,652,654,656,658,660],{"title":487,"path":486,"stem":651},"docs\u002F6.run\u002F1.deploy",{"title":491,"path":490,"stem":653},"docs\u002F6.run\u002F2.secure",{"title":495,"path":494,"stem":655},"docs\u002F6.run\u002F3.lifecycle",{"title":499,"path":498,"stem":657},"docs\u002F6.run\u002F4.observe",{"title":503,"path":502,"stem":659},"docs\u002F6.run\u002F5.dev-panel",{"title":507,"path":506,"stem":661},"docs\u002F6.run\u002F6.test",{"title":663,"path":664,"stem":665,"children":666,"page":5},"Adopt","\u002Fdocs\u002Fadopt","docs\u002F7.adopt",[667,669,671,673,675,677],{"title":511,"path":510,"stem":668},"docs\u002F7.adopt\u002F1.new-project",{"title":515,"path":514,"stem":670},"docs\u002F7.adopt\u002F2.existing-app",{"title":519,"path":518,"stem":672},"docs\u002F7.adopt\u002F3.bring-your-schema",{"title":523,"path":522,"stem":674},"docs\u002F7.adopt\u002F4.hosts",{"title":527,"path":526,"stem":676},"docs\u002F7.adopt\u002F5.another-repository",{"title":531,"path":530,"stem":678},"docs\u002F7.adopt\u002F6.demos",{"title":680,"path":681,"stem":682,"children":683,"page":5},"Extending","\u002Fdocs\u002Fextending","docs\u002F8.extending",[684,686,688,690,692,694,696,698,700,702],{"title":535,"path":534,"stem":685},"docs\u002F8.extending\u002F1.scan",{"title":538,"path":537,"stem":687},"docs\u002F8.extending\u002F10.without-compiler",{"title":541,"path":540,"stem":689},"docs\u002F8.extending\u002F2.boot",{"title":544,"path":543,"stem":691},"docs\u002F8.extending\u002F3.card-and-wire",{"title":547,"path":546,"stem":693},"docs\u002F8.extending\u002F4.extension-points",{"title":550,"path":549,"stem":695},"docs\u002F8.extending\u002F5.vocabulary",{"title":553,"path":552,"stem":697},"docs\u002F8.extending\u002F6.extensions",{"title":556,"path":555,"stem":699},"docs\u002F8.extending\u002F7.source-adapter",{"title":559,"path":558,"stem":701},"docs\u002F8.extending\u002F8.surface",{"title":562,"path":561,"stem":703},"docs\u002F8.extending\u002F9.foreign-frond",1794396614526]