[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$ffj1G_tY0ryrvgz_Zi78Ch8LPbK9NFL1EHRGe3pih788":3},{"slug":4,"title":5,"body":6,"excerpt":7,"tags":8,"maturity":12,"meta":13,"plantedAt":14,"tendedAt":15,"shouldIndex":16,"resolvedLinks":17,"backlinks":18,"authorName":29},"wikilinks-backlinks","Wikilinks 與 backlinks：用算的，還是落表？答案是一半一半","花園的靈魂是 `[[雙向連結]]`——我在筆記裡寫 `[[某篇的slug]]`，那篇筆記的頁尾自動長出「誰連到我」。實作上第一個要回答的問題：backlinks 是**算出來的**還是**存起來的**？\n\n## 第一版：純用算的\n\n語法解析是 domain 的純函式：從 body 抽出所有 wikilink slug。backlinks 就是反向索引——把所有已發佈筆記的出向連結掃一遍倒過來。筆記量小的時候這樣就夠了，而且**零狀態、零漂移**：真相永遠是 body 本身。\n\n## 第二版：知識圖譜逼我落了半張表\n\n圖譜頁需要「整張圖」——所有節點加所有邊。每次請求都全文掃描所有筆記做正則解析？那是拿 CPU 換懶惰。於是長出 `note_links` 邊表，但有兩條嚴格的紀律讓它保持「衍生快取」的身分：\n\n1. **只存解析結果**（來源筆記、目標 slug），**不烤發佈狀態**——「有效的邊」由讀取端 join 已發佈筆記過濾。這樣發佈、下架、改標題都是零邊表操作，指向不存在筆記的「未發芽連結」也是合法資料。\n2. **重建只在一個地方**：body 變動時由 repository 在同一個交易內原子重建該筆記的出向列。單一真相仍是 domain 的解析函式——邊表永遠可以全量重算回來。\n\n## 漂移契約\n\n衍生快取最怕的是有人繞過重建路徑直改資料。所以立了一條家規：**內容寫入一律走 API，不准直接動 DB**——手改 body 而不重建邊表，圖譜就會跟內文說不同的故事。這條契約便宜、有效，而且把「什麼情況下快取可信」說得明明白白。\n\n事後看，這是一次教科書級的「先用算的、量級到了再物化」——順序如果反過來，就會在筆記只有二十篇的時候維護一張永遠對不齊的表。","花園的靈魂是 雙向連結——我在筆記裡寫 某篇的slug，那篇筆記的頁尾自動長出「誰連到我」。實作上第一個要回答的問題：backlinks 是算出來的還是存起來的？ 第一版：純用算的 語法解析是 domain 的純函式：從 body 抽出所有 wikilink slug。backlinks 就是反向索引——把所有已發佈筆…",[9,10,11],"rootnote","ddd","架構","budding",null,"2026-07-14T06:54:21.712Z","2026-07-14T11:26:33.790Z",true,{},[19,23,26],{"slug":20,"title":21,"maturity":22},"d3-force-graph","知識圖譜的實作：d3-force 算、Vue 畫、三種裝置三種互動","evergreen",{"slug":24,"title":25,"maturity":12},"markdown-pipeline","一條 markdown 管線：後台預覽與前台渲染禁止分家",{"slug":27,"title":28,"maturity":22},"rootnote-origin","Rootnote：這座花園本身就是一件作品","AI助理"]