← 返回 /首頁
資料模型 · page 與 lect

用同一種形狀
承載所有內容。

0xCMS 入面每一份內容都是一個 page,page 的 body 是一個 lect — 單一、輕 schema 的 JSON object,通用多語,並且 可無限擴充blueprint 描述形狀;lect 就是活的文件。

draft_pages.lect — mail_list #2185…
{
  "_type": "mail_list",
  "_pointers": {
    "event": "21857528035279",
    "edm":   "21844625140640"
  },
  "allow_checkin": "yes",
  "name": { "en": "01 FULL RUN" },
  "_modifier": 1,
  "_updated_at": "2026-06-24T15:54Z"
}

// 01 — page

萬物皆是 page

Page 是內容單位 — 可以是一個活動、一篇文章、一位嘉賓、一個 email template。少量結構化 metadata 會放在真實欄位,而實際內容全部放在一個 lect 欄位。

draft_pages row

uuid穩定身份,發佈及還原後仍保留
name · slug人類可讀標題及 URL key
page_type由哪個 blueprint 定義 lect 形狀
page_id父頁面(樹狀結構)
weight同層排序
start · end · tz可選發佈時段
lect⟵ JSON 內容 payload
…_version_idappend-only 歷史的目前 head

草稿 → 版本化 → 已發佈

  • 編輯內容存在 draft_pages — 私有,絕不公開。
  • 每次儲存都把 lect snapshot 到 page_versions — 可回復到任一時間點。
  • 發佈會把 lect 分發到 live targets(D1、靜態 JSON、插件)。
  • Blocks、nested items 及 tags 同樣 是 lect — 由上到下都是同一形狀。

page : lect  ::  record : document

// 02 — lect

一種 通用 內容格式

lect 是一個帶有少量慣例的 JSON object。同一種形狀可表示 page、content block、repeatable item 或 tag — 因此 editor、renderer 及 plugins 都講同一種語言。

_type

標示這個 lect 跟隨哪個 blueprint — 例如 "event""mail_list"、某個 block type 等。

value fields  → { lang: value }

人類文字以 language map 儲存:"name": { "en": "Gala", "zh-hant": "晚宴" }。系統按 requested language resolve。

@ attributes  → scalars

與語言無關的設定直接儲存:"allow_checkin": "yes"。在 blueprint 以 @field 宣告。

_pointers  → references

以 id 連到其他 pages:"_pointers": { "event": "2185…" }。屬於 soft relation,不需要 rigid foreign key。

_blocks  ·  items[]

有序的 nested lects — content blocks(_blocks)及 repeatable groups("session": [ … ]),每個都有自己的 _type_weight_name

_modifier · _updated_at

CMS 與內容一起保留的 metadata。未知 keys 會原封不動保存 — 格式向前兼容。

活動頁面的 lect
{
  "_type": "event",
  "start": "2026-06-25T19:00",
  "_pointers": { "venue": "21857528035279" },
  "name":  { "mis": "Launch", "zh-hant": "發佈會" },
  "body":  { "en": "Join us at the edge…" },
  "session": [
    {
      "_weight": 0,
      "start": "19:00",
      "name": { "en": "Keynote" }
    }
  ],
  "_blocks": [
    { "_type": "paragraph", "_weight": 0,
      "body": { "en": "## Agenda…" } }
  ]
}

// 03 — 多語

每個 value 預設都是 多語

文字不是單一 flat string,而是按語言為 key 的 map。新增語言只需一行 config,不是 schema migration。讀者要求某語言時,resolver 會回傳該語言,否則 fallback 到中性 base。

// 一個欄位,所有語言
"name": {
  "mis":     "Launch",   // base / neutral
  "en":      "Launch",
  "zh-hant": "發佈會",
  "zh-hans": "发布会"
}

mis base language

0xCMS 預設提供 languages: ['mis', 'en', 'zh-hant', 'zh-hans'],並以 mis 為 default — 這是 ISO 639 中代表 language-neutral base 的代碼。resolve 指定語言時,會先回傳該語言的 value,再 fallback 到 mis,最後 fallback 到任何已存在 value,所以部分翻譯也不會 render 空白。

mis en zh-hant zh-hans + 加入自己的語言

// 04 — blueprint ⟷ lect

Schema 遇上 document

blueprint 是一個小型 declarative array,用來描述 page type 的欄位。它是 schema;lect 是 document。Editor 讀取 blueprint 來 render 正確 widget,並由它 scaffold 空 lect — 但 lect 本身仍然是自由 JSON。

blueprint  — 形狀(cms-config.ts)
blueprint.event = [
  "@start:date/datetime",
  "*venue",
  "name:text/title",
  "body:richtext/md",
  { session: [ "@start", "name" ] }
]
lect  — 符合該形狀的 document
{
  "_type": "event",
  "start": "2026-06-25T19:00",
  "_pointers": { "venue": "2185…" },
  "name": { "mis": "Launch" },
  "body": { "en": "## Hi" },
  "session": [ { "start": "19:00", "name": {…} } ]
}

// blueprint tokens → 在 lect 中會變成甚麼

name本地化 value → { lang: value }
@attrscalar attribute → 直接 value
*refpointer → _pointers.ref
field:renderer同一 value,自訂 widget
{ group: [ … ] }repeatable items → lect array
blockLists可用 blocks → _blocks[]

一個 blueprint 可塑造 多個 lect。Blueprint 指引 editor 並填入預設值,但不會鎖死 document:lect 可以帶有 blueprint 從未提及的 keys,所以插件可存放自己的資料(例如 sent_edm log、額外 _pointers.event),round-trip 後仍保持不變。

// 05 — 擴充

毋須 migration 也可 擴展模型

因為內容存在 declarative blueprint 背後的 JSON,擴充模型只需 additive change — 不用 ALTER TABLE,不用 downtime。

新增欄位

在 blueprint array 加一個 token。它會即時出現在 editor 及新 lect;現有 lect 只是暫時沒有該 key。

🌐

新增語言

languages 加一個 code。每個 value field 本來已是 map,譯者只需填入新 key — 沒有欄位會被鎖成單語。

🎛️

新增 widget

field:renderer 會 resolve 到 /snippets/pagefield/<renderer> 的 Liquid snippet。放入 template 就可推出新 field type — date picker、地圖、markdown。

🧱

新增 block

blocks 註冊 block type,並在 blockList 列出。Editor 會把 blocks 疊入 _blocks[] — 每個都是 nested lect。

🔗

連結 pages

*pointer 以 id 把一個 page 連到另一個 page — 可把 EDM 及 guest list 歸到活動之下、引用 venue — 全部都是 _pointers 裏的 soft relation。

🧩

透過插件擴充

插件 Worker 可透過 HTTPS 向 effective config 注入完整 page types、blocks 及 field snippets — 新內容形狀 毋須重新部署 CMS

為何撐得住: 輕 schema JSON 保留未知 keys 同一形狀:page · block · item · tag additive,毋須 migration

// 到 editor 看看

形狀很簡單。
延展力不簡單。

打開 live editor,看看 blueprint 如何 render 成多語 lect。