內容模型

本頁內容

EmDash 採用資料庫優先的內容模型:schema 定義存放在資料庫而非程式碼中。這是支持執行時期改 schema、並讓非開發者也能上手的基礎設計。

Schema 即資料

Strapi、Keystatic 等傳統 CMS 常在程式碼裡定義 schema:

// 傳統方式:schema 寫在程式碼裡
const posts = collection({
	fields: {
		title: text({ required: true }),
		content: richText(),
	},
});

EmDash 把同樣的資訊存在表中:

-- _emdash_collections 表
INSERT INTO _emdash_collections (slug, label)
VALUES ('posts', 'Blog Posts');

-- _emdash_fields 表
INSERT INTO _emdash_fields (collection_id, slug, type, required)
VALUES
  ('coll_abc', 'title', 'string', true),
  ('coll_abc', 'content', 'portableText', false);

兩種方式描述的是同一套內容結構,差別在於結構存放在哪、如何修改。

為何資料庫優先?

執行時期修改

無需改程式碼或重新建構即可增刪內容類型。非開發者也可在管理後台介面設計資料模型。

真實 SQL 列

不同於 WordPress 的 EAV,每個欄位對應真實列,便於索引、外鍵與查詢優化。

自描述

可用資料庫工具直接查看 schema,不必從程式碼反推資料模型。

遷移路徑

可將 schema 導出為 JSON 做版本管理,並在新環境中導入。

Schema 表

兩張系統表描述內容結構:

集合表

CREATE TABLE _emdash_collections (
  id TEXT PRIMARY KEY,
  slug TEXT UNIQUE NOT NULL,
  label TEXT NOT NULL,
  label_singular TEXT,
  description TEXT,
  icon TEXT,
  supports JSON,
  source TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP,
  updated_at TEXT
);

source 記錄集合的來源:

說明
manual通過管理後台建立
template:blog來自模板種子文件
import:wordpress從 WordPress 導入
discovered從已有資料自動發現

欄位表

CREATE TABLE _emdash_fields (
  id TEXT PRIMARY KEY,
  collection_id TEXT REFERENCES _emdash_collections(id),
  slug TEXT NOT NULL,
  label TEXT NOT NULL,
  type TEXT NOT NULL,
  column_type TEXT NOT NULL,
  required INTEGER DEFAULT 0,
  unique_field INTEGER DEFAULT 0,
  default_value TEXT,
  validation JSON,
  widget TEXT,
  options JSON,
  sort_order INTEGER,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(collection_id, slug)
);

內容表

每個集合有帶 ec_ 前綴的表。

CREATE TABLE ec_products (
  id TEXT PRIMARY KEY,
  slug TEXT UNIQUE,
  status TEXT DEFAULT 'draft',
  author_id TEXT,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now')),
  published_at TEXT,
  deleted_at TEXT,
  version INTEGER DEFAULT 1,
  title TEXT NOT NULL,
  price REAL
);

執行時期 schema 變更

在管理後台添加欄位時,EmDash 會:

  1. _emdash_fields 插入記錄
  2. 執行 ALTER TABLE ec_<collection> ADD COLUMN …
  3. 重新生成用於驗證的 Zod schema

SQLite 在執行時期支持的 ALTER TABLE 操作:

操作是否支持
添加列
重命名列
刪除列是(SQLite 3.35+)
修改列類型否(需重建表)

類型變更時,EmDash 會透明地完成表重建。

Schema 與內容分離

方面位置
Schema系統表_emdash_collections, _emdash_fields
內容按集合分表ec_posts, ec_products
媒體獨立表 + 存儲media + R2/S3
設置選項表site: 前綴的 options

執行時期驗證

啟動時 EmDash 根據欄位定義建構 Zod schema,並在每次建立、更新時驗證。

function buildSchema(fields: Field[]): ZodSchema {
	const shape: Record<string, ZodType> = {};
	for (const field of fields) {
		let zodType = fieldTypeToZod(field.type);
		if (field.required) zodType = zodType.required();
		if (field.validation?.min !== undefined) zodType = zodType.min(field.validation.min);
		shape[field.slug] = zodType;
	}
	return z.object(shape);
}

與 TypeScript 整合

npx emdash types

會生成 .emdash/types.ts,並為 getEmDashCollection / getEmDashEntry 提供重載。

開發者與非開發者

開發者npx emdash typesnpx emdash export-seed > seed.json

非開發者:僅用管理後台 — 「內容類型」、添加集合、可視化欄位建構器。

兩者修改的是同一套底層表。

種子文件

import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";
const { valid, errors } = validateSeed(seedData);
await applySeed(db, seedData, { includeContent: true, onConflict: "skip" });

與其他方案對比

方案Schema 位置執行時期修改類型
EmDash資料庫是(完整)由 DB 生成
WordPressPHP + EAV有限(元資料)
Strapi程式碼否(需重建)建構時生成
Sanity程式碼否(需部署 schema)內置
Directus資料庫是(完整)由 DB 生成

下一步