[{"data":1,"prerenderedAt":260},["ShallowReactive",2],{"i-lucide:layers":3,"i-lucide:git-commit-horizontal":8,"i-lucide:refresh-cw":10,"i-lucide:book-open":12,"i-lucide:image":14,"i-lucide:webhook":16,"i-lucide:home":18,"i-lucide:layout-grid":20,"i-lucide:tag":22,"i-lucide:newspaper":24,"i-lucide:map":26,"i-lucide:flask-conical":28,"i-lucide:arrow-right":30,"i-lucide:chevron-right":32,"i-lucide:search":34,"i-lucide:monitor":36,"content:\u002Finternal\u002Farchitecture\u002Fprojections":38,"i-lucide:lightbulb":258,"i-lucide:layers-2":255,"i-lucide:key-round":255,"i-lucide:shield-check":255,"i-lucide:box":255,"i-lucide:share-2":255,"i-lucide:clock":255,"i-lucide:dices":255,"i-lucide:building-2":255,"i-lucide:bell":255,"i-lucide:sprout":255,"i-lucide:puzzle":255,"i-lucide:shield-alert":255,"i-lucide:activity":255,"i-lucide:blocks":255},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z\"\u002F>\u003Cpath d=\"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12\"\u002F>\u003Cpath d=\"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"3\"\u002F>\u003Cpath d=\"M3 12h6m6 0h6\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":11},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M3 12a9 9 0 0 1 9-9a9.75 9.75 0 0 1 6.74 2.74L21 8\"\u002F>\u003Cpath d=\"M21 3v5h-5m5 4a9 9 0 0 1-9 9a9.75 9.75 0 0 1-6.74-2.74L3 16\"\u002F>\u003Cpath d=\"M8 16H3v5\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":13},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 5v16m8.001-2A2 2 0 0 0 22 17V5a2 2 0 0 0-1.999-2L16 3.002A5 5 0 0 0 12 5a5 5 0 0 0-4-2H4a2 2 0 0 0-2 2v12a2 2 0 0 0 1.999 2H8a5 5 0 0 1 4 2a5 5 0 0 1 4-2z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":15},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Crect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"\u002F>\u003Ccircle cx=\"9\" cy=\"9\" r=\"2\"\u002F>\u003Cpath d=\"m21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":17},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2\"\u002F>\u003Cpath d=\"m6 17l3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06\"\u002F>\u003Cpath d=\"m12 6l3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":19},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8\"\u002F>\u003Cpath d=\"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":21},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Crect width=\"7\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\"\u002F>\u003Crect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\"\u002F>\u003Crect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\"\u002F>\u003Crect width=\"7\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":23},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z\"\u002F>\u003Ccircle cx=\"7.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":25},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M15 18h-5m8-4h-8m-6 8h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2\"\u002F>\u003Crect width=\"8\" height=\"4\" x=\"10\" y=\"6\" rx=\"1\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":27},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0zm.894.211v15M9 3.236v15\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":29},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2M6.453 15h11.094M8.5 2h7\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":31},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7l7 7l-7 7\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":33},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m9 18l6-6l-6-6\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":35},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"m21 21l-4.34-4.34\"\u002F>\u003Ccircle cx=\"11\" cy=\"11\" r=\"8\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":37},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Crect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"\u002F>\u003Cpath d=\"M8 21h8m-4-4v4\"\u002F>\u003C\u002Fg>",{"id":39,"title":40,"body":41,"description":248,"extension":249,"eyebrow":250,"hostedOnly":6,"meta":251,"navigation":252,"path":253,"seo":254,"short":255,"stem":256,"__hash__":257},"docs\u002Finternal\u002Farchitecture\u002Fprojections.md","Projections",{"type":42,"value":43,"toc":243},"minimark",[44,53,58,61,80,83,189,193,207,214,226,230,239],[45,46,47,48,52],"p",{},"A ",[49,50,51],"strong",{},"projection"," reads events and writes a read model. The read side of the app queries the read model, never the event stream. A projector processes each event and updates its table.",[54,55,57],"h2",{"id":56},"idempotency-by-a-version-gate","Idempotency by a version gate",[45,59,60],{},"An event can arrive more than once. So a projector must be safe to run twice on the same event. Chronicler does not read first to check. It relies on the store's atomic version gate.",[62,63,64,73],"ul",{},[65,66,67,68,72],"li",{},"An update carries ",[69,70,71],"code",{},"WHERE ... _version \u003C ?",", so a stale or repeated write does nothing.",[65,74,75,76,79],{},"An insert carries ",[69,77,78],{},"ON CONFLICT (id) DO NOTHING",", so a repeat is a no-op.",[45,81,82],{},"The gated write returns whether it changed a row. The handler turns that into an outcome.",[84,85,90],"pre",{"className":86,"code":87,"language":88,"meta":89,"style":89},"language-go shiki shiki-themes github-light github-dark","\u002F\u002F The store returns (applied bool, err). The handler reports the outcome.\napplied, err := store.Upsert(ctx, row)\nif err != nil {\n    return projection.Outcome{}, err\n}\n\nreturn projection.Outcome(applied), nil\n","go","",[69,91,92,101,122,141,159,165,172],{"__ignoreMap":89},[93,94,97],"span",{"class":95,"line":96},"line",1,[93,98,100],{"class":99},"sJ8bj","\u002F\u002F The store returns (applied bool, err). The handler reports the outcome.\n",[93,102,104,108,112,115,119],{"class":95,"line":103},2,[93,105,107],{"class":106},"sVt8B","applied, err ",[93,109,111],{"class":110},"szBVR",":=",[93,113,114],{"class":106}," store.",[93,116,118],{"class":117},"sScJk","Upsert",[93,120,121],{"class":106},"(ctx, row)\n",[93,123,125,128,131,134,138],{"class":95,"line":124},3,[93,126,127],{"class":110},"if",[93,129,130],{"class":106}," err ",[93,132,133],{"class":110},"!=",[93,135,137],{"class":136},"sj4cs"," nil",[93,139,140],{"class":106}," {\n",[93,142,144,147,150,153,156],{"class":95,"line":143},4,[93,145,146],{"class":110},"    return",[93,148,149],{"class":117}," projection",[93,151,152],{"class":106},".",[93,154,155],{"class":117},"Outcome",[93,157,158],{"class":106},"{}, err\n",[93,160,162],{"class":95,"line":161},5,[93,163,164],{"class":106},"}\n",[93,166,168],{"class":95,"line":167},6,[93,169,171],{"emptyLinePlaceholder":170},true,"\n",[93,173,175,178,181,183,186],{"class":95,"line":174},7,[93,176,177],{"class":110},"return",[93,179,180],{"class":106}," projection.",[93,182,155],{"class":117},[93,184,185],{"class":106},"(applied), ",[93,187,188],{"class":136},"nil\n",[54,190,192],{"id":191},"outcomes-and-retries","Outcomes and retries",[45,194,195,196,199,200,202,203,206],{},"A projector's ",[69,197,198],{},"Handle"," returns an ",[69,201,155],{},", one of Applied, Skipped, or Ignored. Build it with ",[69,204,205],{},"projection.Outcome",", so the metrics can tell a real write from a stale skip.",[45,208,209,210,213],{},"When a projection depends on data that has not arrived yet, do not fail hard. Return ",[69,211,212],{},"RetryAfterErr"," so the event is retried later.",[215,216,219],"callout",{"title":217,"tone":218},"System columns start with an underscore","tip",[45,220,221,222,225],{},"A leading-underscore column, such as ",[69,223,224],{},"_version",", is system bookkeeping. The domain layer never reads it. It exists to make the write gate atomic.",[54,227,229],{"id":228},"what-feeds-this","What feeds this",[45,231,232,233,238],{},"The events come from the aggregates. That is the ",[234,235,237],"a",{"href":236},"\u002Finternal\u002Farchitecture\u002Fevent-sourcing","Event sourcing"," pattern.",[240,241,242],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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":89,"searchDepth":103,"depth":103,"links":244},[245,246,247],{"id":56,"depth":103,"text":57},{"id":191,"depth":103,"text":192},{"id":228,"depth":103,"text":229},"A projection turns the event stream into a read model. It must be idempotent, because an event can arrive more than once. Here is how Chronicler keeps that safe.","md","Architecture",{},{"title":40},"\u002Finternal\u002Farchitecture\u002Fprojections",{"title":40,"description":248},null,"internal\u002Farchitecture\u002Fprojections","meNDfSnKXGneIP7CO6HUVnrPOX3K9AlI-GalXXuqlRc",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":259},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 14c.2-1 .7-1.7 1.5-2.5c1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5c.7.7 1.3 1.5 1.5 2.5m0 4h6m-5 4h4\"\u002F>",1788784066565]