[{"data":1,"prerenderedAt":585},["ShallowReactive",2],{"docs-nav:en":3,"docs:\u002Fdocs\u002Fschema\u002Fevolution":186},[4],{"title":5,"path":6,"stem":7,"children":8},"Docs","\u002Fdocs","docs",[9,12,16,20,24,28,50,75,120,145],{"title":10,"path":6,"stem":11},"What is Fougere","docs\u002Findex",{"title":13,"path":14,"stem":15},"Getting started","\u002Fdocs\u002Fgetting-started","docs\u002F01.getting-started",{"title":17,"path":18,"stem":19},"An app you already have","\u002Fdocs\u002Fexisting-app","docs\u002F02.existing-app",{"title":21,"path":22,"stem":23},"Bring your schema","\u002Fdocs\u002Fbring-your-schema","docs\u002F03.bring-your-schema",{"title":25,"path":26,"stem":27},"The CLI","\u002Fdocs\u002Fcli","docs\u002F04.cli",{"title":29,"path":30,"stem":31,"children":32,"page":49},"Concepts","\u002Fdocs\u002Fconcepts","docs\u002F2.concepts",[33,37,41,45],{"title":34,"path":35,"stem":36},"Philosophy","\u002Fdocs\u002Fconcepts\u002Fphilosophy","docs\u002F2.concepts\u002F1.philosophy",{"title":38,"path":39,"stem":40},"The Frond","\u002Fdocs\u002Fconcepts\u002Ffrond","docs\u002F2.concepts\u002F2.frond",{"title":42,"path":43,"stem":44},"The shortest path","\u002Fdocs\u002Fconcepts\u002Fshortest-path","docs\u002F2.concepts\u002F3.shortest-path",{"title":46,"path":47,"stem":48},"The base","\u002Fdocs\u002Fconcepts\u002Fthe-base","docs\u002F2.concepts\u002F4.the-base",false,{"title":51,"path":52,"stem":53,"children":54,"page":49},"Schema","\u002Fdocs\u002Fschema","docs\u002F3.schema",[55,59,63,67,71],{"title":56,"path":57,"stem":58},"Entities","\u002Fdocs\u002Fschema\u002Fentities","docs\u002F3.schema\u002F1.entities",{"title":60,"path":61,"stem":62},"Views","\u002Fdocs\u002Fschema\u002Fviews","docs\u002F3.schema\u002F2.views",{"title":64,"path":65,"stem":66},"The identity card","\u002Fdocs\u002Fschema\u002Fcard","docs\u002F3.schema\u002F3.card",{"title":68,"path":69,"stem":70},"Standard Schema","\u002Fdocs\u002Fschema\u002Fstandard-schema","docs\u002F3.schema\u002F4.standard-schema",{"title":72,"path":73,"stem":74},"How a schema moves","\u002Fdocs\u002Fschema\u002Fevolution","docs\u002F3.schema\u002F5.evolution",{"title":76,"path":77,"stem":78,"children":79,"page":49},"Business","\u002Fdocs\u002Fbusiness","docs\u002F4.business",[80,84,88,92,96,100,104,108,112,116],{"title":81,"path":82,"stem":83},"Handlers","\u002Fdocs\u002Fbusiness\u002Fhandlers","docs\u002F4.business\u002F1.handlers",{"title":85,"path":86,"stem":87},"Ports","\u002Fdocs\u002Fbusiness\u002Fports","docs\u002F4.business\u002F10.ports",{"title":89,"path":90,"stem":91},"Presenters","\u002Fdocs\u002Fbusiness\u002Fpresenters","docs\u002F4.business\u002F2.presenters",{"title":93,"path":94,"stem":95},"Collectors","\u002Fdocs\u002Fbusiness\u002Fcollectors","docs\u002F4.business\u002F3.collectors",{"title":97,"path":98,"stem":99},"Errors","\u002Fdocs\u002Fbusiness\u002Ferrors","docs\u002F4.business\u002F4.errors",{"title":101,"path":102,"stem":103},"Seeds","\u002Fdocs\u002Fbusiness\u002Fseeds","docs\u002F4.business\u002F5.seeds",{"title":105,"path":106,"stem":107},"Facts","\u002Fdocs\u002Fbusiness\u002Ffacts","docs\u002F4.business\u002F6.facts",{"title":109,"path":110,"stem":111},"Storage","\u002Fdocs\u002Fbusiness\u002Fstorage","docs\u002F4.business\u002F7.storage",{"title":113,"path":114,"stem":115},"Writes that stand or fall as one","\u002Fdocs\u002Fbusiness\u002Ftogether","docs\u002F4.business\u002F8.together",{"title":117,"path":118,"stem":119},"Repositories","\u002Fdocs\u002Fbusiness\u002Frepositories","docs\u002F4.business\u002F9.repositories",{"title":121,"path":122,"stem":123,"children":124,"page":49},"Client","\u002Fdocs\u002Fclient","docs\u002F5.client",[125,129,133,137,141],{"title":126,"path":127,"stem":128},"Queries & commands","\u002Fdocs\u002Fclient\u002Fqueries-commands","docs\u002F5.client\u002F1.queries-commands",{"title":130,"path":131,"stem":132},"Forms","\u002Fdocs\u002Fclient\u002Fforms","docs\u002F5.client\u002F2.forms",{"title":134,"path":135,"stem":136},"Session","\u002Fdocs\u002Fclient\u002Fsession","docs\u002F5.client\u002F3.session",{"title":138,"path":139,"stem":140},"invoke","\u002Fdocs\u002Fclient\u002Finvoke","docs\u002F5.client\u002F4.invoke",{"title":142,"path":143,"stem":144},"Back-office","\u002Fdocs\u002Fclient\u002Fadmin","docs\u002F5.client\u002F5.admin",{"title":146,"path":147,"stem":148,"children":149,"page":49},"Infrastructure","\u002Fdocs\u002Finfra","docs\u002F6.infra",[150,154,158,162,166,170,174,178,182],{"title":151,"path":152,"stem":153},"The gradient","\u002Fdocs\u002Finfra\u002Fgradient","docs\u002F6.infra\u002F1.gradient",{"title":155,"path":156,"stem":157},"Surfaces — REST & GraphQL","\u002Fdocs\u002Finfra\u002Fsurfaces","docs\u002F6.infra\u002F2.surfaces",{"title":159,"path":160,"stem":161},"Deployment","\u002Fdocs\u002Finfra\u002Fdeployment","docs\u002F6.infra\u002F3.deployment",{"title":163,"path":164,"stem":165},"Hosts","\u002Fdocs\u002Finfra\u002Fhosts","docs\u002F6.infra\u002F4.hosts",{"title":167,"path":168,"stem":169},"Sources","\u002Fdocs\u002Finfra\u002Fsources","docs\u002F6.infra\u002F5.sources",{"title":171,"path":172,"stem":173},"Lifecycle","\u002Fdocs\u002Finfra\u002Flifecycle","docs\u002F6.infra\u002F6.lifecycle",{"title":175,"path":176,"stem":177},"Identity across Fronds","\u002Fdocs\u002Finfra\u002Fidentity","docs\u002F6.infra\u002F7.identity",{"title":179,"path":180,"stem":181},"Observability","\u002Fdocs\u002Finfra\u002Fobservability","docs\u002F6.infra\u002F8.observability",{"title":183,"path":184,"stem":185},"Testing","\u002Fdocs\u002Finfra\u002Ftesting","docs\u002F6.infra\u002F9.testing",{"id":187,"title":72,"body":188,"description":579,"extension":580,"meta":581,"navigation":582,"path":73,"seo":583,"stem":74,"__hash__":584},"docs_en\u002Fdocs\u002F3.schema\u002F5.evolution.md",{"type":189,"value":190,"toc":572},"minimark",[191,195,209,263,268,276,288,303,307,318,324,335,339,350,426,433,440,508,515,519,534,541,545,551,562,568],[192,193,72],"h1",{"id":194},"how-a-schema-moves",[196,197,198,199,203,204,208],"p",{},"An entity describes itself as it is today. ",[200,201,202],"code",{},"fougere freeze"," records what it ",[205,206,207],"em",{},"was",", so the\ndifference between two moments becomes a value something can act on.",[210,211,216],"pre",{"className":212,"code":213,"language":214,"meta":215,"style":215},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","fougere freeze v1        # fronds\u002Fblog\u002Fversions\u002Fv1\u002Fshape.json\nfougere migrate          # what the database has not caught up with\nfougere migrate --apply  # make it so\n","bash","",[200,217,218,238,249],{"__ignoreMap":215},[219,220,223,227,231,234],"span",{"class":221,"line":222},"line",1,[219,224,226],{"class":225},"sbgvK","fougere",[219,228,230],{"class":229},"s_sjI"," freeze",[219,232,233],{"class":229}," v1",[219,235,237],{"class":236},"sutJx","        # fronds\u002Fblog\u002Fversions\u002Fv1\u002Fshape.json\n",[219,239,241,243,246],{"class":221,"line":240},2,[219,242,226],{"class":225},[219,244,245],{"class":229}," migrate",[219,247,248],{"class":236},"          # what the database has not caught up with\n",[219,250,252,254,256,260],{"class":221,"line":251},3,[219,253,226],{"class":225},[219,255,245],{"class":229},[219,257,259],{"class":258},"stzsN"," --apply",[219,261,262],{"class":236},"  # make it so\n",[264,265,267],"h2",{"id":266},"one-artefact-several-readers","One artefact, several readers",[210,269,274],{"className":270,"code":272,"language":273},[271],"language-text","fronds\u002Fblog\u002Fversions\u002F\n  v1\u002F  shape.json                  what the shapes WERE\n  v2\u002F  shape.json  from.json       …and the step that leads here\n","text",[200,275,272],{"__ignoreMap":215},[196,277,278,279,283,284,287],{},"Both are written, and neither replaces the other. ",[280,281,282],"strong",{},"A snapshot alone loses the intent"," — a\nfield gone plus a field appeared cannot be told from a rename. ",[280,285,286],{},"A step alone corrupts in\nsilence"," — nothing checks it against anything. Together they carry an invariant: replaying\nthe step over the previous snapshot must reproduce this one.",[196,289,290,291,294,295,298,299,302],{},"A migration reads the step ",[280,292,293],{},"once",", against the rows. The second reader is why the step is\nan artefact rather than a function call: an old API version still being served would replay\nthe same step at ",[280,296,297],{},"every call",", at the door. ",[280,300,301],{},"That reader is not written yet"," — and it is\nthe reason the comparison lives in one place, because writing it twice would let the two\ndisagree about the same two shapes.",[264,304,306],{"id":305},"a-rename-is-declared-never-guessed","A rename is declared, never guessed",[196,308,309,310,313,314,317],{},"Fougere will not decide that ",[200,311,312],{},"body"," became ",[200,315,316],{},"content",", even when it is the only possibility:",[210,319,322],{"className":320,"code":321,"language":273},[271],"◆  post.body — what happened to it?\n   ❯ renamed to content    the data moves with it\n     dropped               the column goes, and what it held goes with it\n",[200,323,321],{"__ignoreMap":215},[196,325,326,327,330,331,334],{},"The two readings produce ",[280,328,329],{},"opposite"," DDL — one keeps a column of live data, the other\nthrows it away. The intent lives in the moment the change was made, and no pair of snapshots\nholds it. This is the one place ",[200,332,333],{},"freeze"," asks, and nothing reaches the disk until it is\nsettled.",[264,336,338],{"id":337},"what-a-migration-realises","What a migration realises",[196,340,341,342,345,346,349],{},"An automatic pass is additive on purpose: it creates a missing table and adds a missing\ncolumn, and ",[280,343,344],{},"nothing else ever happens",". ",[200,347,348],{},"fougere migrate"," is the other half — and only\nwhat a step actually says:",[351,352,353,366],"table",{},[354,355,356],"thead",{},[357,358,359,363],"tr",{},[360,361,362],"th",{},"The step says",[360,364,365],{},"The database does",[367,368,369,383,395,406,414],"tbody",{},[357,370,371,377],{},[372,373,374],"td",{},[200,375,376],{},"renamed",[372,378,379,382],{},[200,380,381],{},"ALTER TABLE … RENAME COLUMN"," — the data follows",[357,384,385,390],{},[372,386,387],{},[200,388,389],{},"removed",[372,391,392],{},[200,393,394],{},"DROP COLUMN",[357,396,397,403],{},[372,398,399,402],{},[200,400,401],{},"added",", optional",[372,404,405],{},"nothing; the additive pass already covers it",[357,407,408,411],{},[372,409,410],{},"an index that appeared",[372,412,413],{},"nothing; every boot proposes every declared index",[357,415,416,423],{},[372,417,418,419,422],{},"a ",[200,420,421],{},"boundary"," that moved",[372,424,425],{},"nothing — who may read a field is not something a column holds",[196,427,428,429,432],{},"A comparison reads all ",[280,430,431],{},"four axes",", not the shape alone: an index, a unique group, a\nrelation, a lifecycle and a boundary are differences too, and a step that reported only the\nshape was answering a quarter of the question.",[196,434,435,436,439],{},"Everything else is refused ",[280,437,438],{},"by name",", in one run rather than one at a time:",[441,442,443,458,464,470,480,486,492,502],"ul",{},[444,445,446,449,450,453,454,457],"li",{},[280,447,448],{},"a required field with no default"," — existing rows have nothing to hold. Declare\n",[200,451,452],{},"text({ default: … })",", the same answer a migration gives when adding a ",[200,455,456],{},"NOT NULL"," column\nto a table that already has rows.",[444,459,460,463],{},[280,461,462],{},"a field that became required"," — same reason, from the other direction.",[444,465,466,469],{},[280,467,468],{},"a type that moved"," — no conversion is derivable from two shapes.",[444,471,472,475,476,479],{},[280,473,474],{},"bounds that tightened"," — the door still enforces them; the table keeps its old ",[200,477,478],{},"CHECK","\nuntil you migrate it yourself.",[444,481,482,485],{},[280,483,484],{},"a unique group that moved"," — rows already stored may contradict it, so adding the\nconstraint is a decision about the rows and not about the DDL.",[444,487,488,491],{},[280,489,490],{},"a relation that moved"," — a foreign key is a constraint, and nothing here alters one.",[444,493,494,497,498,501],{},[280,495,496],{},"a default that moved"," — the table keeps the old one; nothing here alters a ",[200,499,500],{},"DEFAULT",".",[444,503,504,507],{},[280,505,506],{},"an index that went"," — nothing drops an index today, so the table keeps it.",[196,509,510,511,514],{},"A plan carrying a single refusal runs ",[280,512,513],{},"nothing",". Half a rename is worse than none: the data\nends up in a column nothing knows the name of any more.",[264,516,518],{"id":517},"replaying-is-safe","Replaying is safe",[196,520,521,522,525,526,529,530,533],{},"Run ",[200,523,524],{},"migrate"," twice and the second run answers ",[205,527,528],{},"up to date",". There is no ledger of applied\nmigrations — a change is skipped because the ",[280,531,532],{},"columns themselves"," say it happened: a rename\nwhose old name is gone and whose new one is there has run.",[196,535,536,537,540],{},"That is why the chain is read whole and ",[280,538,539],{},"composed"," into one step rather than replayed a\nstep at a time: a field renamed twice is followed to the name it ends on, which is the only\none the tables can answer for. An intermediate name they never held is not a question they\ncan be asked. Nothing has to know which version the database sits at, and a column someone\nrenamed by hand is seen rather than contradicted. A ledger would be a second record of a\nfact the columns already carry, and the two would disagree the day they diverged.",[264,542,544],{"id":543},"what-this-does-not-do","What this does not do",[196,546,547,550],{},[280,548,549],{},"Nothing runs at boot."," Dropping and renaming touch live data, so it is a command you run,\nhaving read the plan. The additive pass stays where it was.",[196,552,553,556,557,561],{},[280,554,555],{},"A freeze is not a build artefact."," It is committed, because it records a past that no\ncurrent code can regenerate — unlike ",[558,559,560],"a",{"href":65},"the identity card",", which is derived\nfrom today's entities and re-derivable at any time.",[196,563,564,565,567],{},"Next: ",[558,566,64],{"href":65}," — the same idea across processes rather than\nacross time.",[569,570,571],"style",{},"html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}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":215,"searchDepth":240,"depth":240,"links":573},[574,575,576,577,578],{"id":266,"depth":240,"text":267},{"id":305,"depth":240,"text":306},{"id":337,"depth":240,"text":338},{"id":517,"depth":240,"text":518},{"id":543,"depth":240,"text":544},"Freezing what a shape was, the step that leads from it, and the migration that realises it — one artefact, several readers.","md",{},true,{"title":72,"description":579},"eMK29rdbqI0WCaPn-bY4v_CRKgY9697zHQAMVXrTFYA",1788560230938]