EmDash は Astro と深く統合され、完全な CMS エクスペリエンスを提供します。このページでは、主要なアーキテクチャ上の決定と、各部分がどのように連携するかを説明します。
高レベル概要
┌──────────────────────────────────────────────────────────────────┐
│ あなたの Astro サイト │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ EmDash 統合 │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │ │
│ │ │ コンテンツ │ │ 管理 │ │ プラグイン │ │ │
│ │ │ API │ │ パネル │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └───────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ データ層 │ │ │
│ │ │ データベース (D1/SQLite) + ストレージ (R2/S3) │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Astro フレームワーク │ │
│ │ Live Collections · ミドルウェア · セッション │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
EmDash は Astro 統合として実行されます。管理パネルと REST API のルートを注入し、Live Collections のコンテンツローダーを提供し、データベースマイグレーションとストレージ接続を管理します。
データベースファーストスキーマ
従来の CMS がコード内でスキーマを定義するのとは異なり、EmDash はスキーマ定義をデータベース自体に格納します。2つのシステムテーブルがコンテンツ構造を追跡します:
_emdash_collections— コレクションメタデータ(slug、ラベル、機能)_emdash_fields— 各コレクションのフィールド定義
管理UI を通じて title と price フィールドを持つ “products” コレクションを作成すると、EmDash は:
_emdash_collectionsと_emdash_fieldsにレコードを挿入ALTER TABLEを実行して適切なカラムを持つec_productsを作成
この設計により以下が実現されます:
- ランタイムスキーマ変更 — コード変更や再ビルドなしでコンテンツタイプを作成・編集
- 非開発者フレンドリーなセットアップ — コンテンツ編集者が UI を通じてデータモデルを設計可能
- 実際の SQL カラム — 適切なインデックス、外部キー、クエリ最適化
コレクションごとのテーブル
各コレクションは ec_ プレフィックスを持つ独自の SQLite テーブルを持ちます:
-- "posts" コレクション追加時に作成
CREATE TABLE ec_posts (
-- システムカラム(常に存在)
id TEXT PRIMARY KEY,
slug TEXT UNIQUE,
status TEXT DEFAULT 'draft', -- draft, published, scheduled
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,
content JSON, -- Portable Text
excerpt TEXT
);
なぜ JSON を含む単一のコンテンツテーブルではなく、コレクションごとのテーブルなのか?
- 実際の SQL カラムにより適切なインデックスとクエリが可能
- 外部キーが正しく機能
- スキーマがデータベース内で自己文書化
- フィールドアクセスに JSON 解析のオーバーヘッドがない
- データベースツールでスキーマを直接検査可能
Live Collections 統合
EmDash は Astro 6 の Live Collections を使用して、ランタイムでコンテンツを提供します。コンテンツの変更は静的再ビルドなしで即座に利用可能になります。
emdashLoader() は Astro の LiveLoader インターフェースを実装します:
// src/live.config.ts
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({ loader: emdashLoader() }),
};
提供されたラッパー関数を使用してコンテンツをクエリします:
import { getEmDashCollection, getEmDashEntry } from "emdash";
// すべての公開された投稿を取得
const { entries: posts } = await getEmDashCollection("posts");
// 下書きを取得
const { entries: drafts } = await getEmDashCollection("posts", {
status: "draft",
});
// slug で単一のエントリを取得
const { entry: post } = await getEmDashEntry("posts", "my-post-slug");
ルート注入
EmDash 統合は Astro の injectRoute API を使用して、管理および API ルートを追加します:
| パスパターン | 目的 |
|---|---|
/_emdash/admin/[...path] | 管理パネル SPA |
/_emdash/api/manifest | 管理マニフェスト(コレクション、プラグイン) |
/_emdash/api/content/[collection] | コンテンツエントリの CRUD |
/_emdash/api/media/* | メディアライブラリ操作 |
/_emdash/api/schema/* | スキーマ管理 |
/_emdash/api/settings | サイト設定 |
/_emdash/api/menus/* | ナビゲーションメニュー |
/_emdash/api/taxonomies/* | カテゴリ、タグ、カスタムタクソノミー |
ルートは emdash パッケージから注入されます — プロジェクトにはコピーされません。
データ層
EmDash は Kysely を使用して、サポートされているすべてのデータベースで型安全な SQL クエリを実行します:
SQLite
sqlite({ url: "file:./data.db" }) でローカル開発
D1
Cloudflare のサーバーレス SQL、d1({ binding: "DB" }) を使用
libSQL
リモート SQLite、libsql({ url: "...", authToken: "..." }) を使用
データベース設定は astro.config.mjs で統合に渡されます:
import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { sqlite } from "emdash/db";
import { local } from "emdash/storage";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});
ストレージ抽象化
メディアファイルはデータベースとは別に保存されます。EmDash は以下をサポートします:
- ローカルファイルシステム — 開発とシンプルなデプロイメント
- Cloudflare R2 — エッジでの S3 互換オブジェクトストレージ
- S3 互換 — 任意の S3 互換オブジェクトストレージ
アップロードは署名付き URL を使用して、クライアントからストレージへの直接アップロードを行い、Workers のボディサイズ制限をバイパスします。
プラグインアーキテクチャ
プラグインは WordPress にインスパイアされたフックシステムを通じて EmDash を拡張します:
- コンテンツフック —
content:beforeSave、content:afterSave、content:beforeDelete、content:afterDelete - メディアフック —
media:beforeUpload、media:afterUpload - 分離されたストレージ — 各プラグインは名前空間付き KV アクセスを取得
- 管理 UI 拡張 — ダッシュボードウィジェット、設定ページ、カスタムフィールドエディタ
プラグインは2つのモードで実行できます:
- ネイティブ — ホスト環境への完全アクセス(ファーストパーティプラグイン用)
- サンドボックス — 能力ベースの権限を持つ V8 isolates で実行(Cloudflare 上のサードパーティプラグイン用)
// astro.config.mjs
import { seoPlugin } from "@emdash-cms/plugin-seo";
emdash({
plugins: [seoPlugin({ maxTitleLength: 60 })],
});
リクエストフロー
典型的なコンテンツリクエストは次のパスをたどります:
- Astro がリクエストを受信 — ページコンポーネントが実行
- コンテンツをクエリ —
getEmDashCollection()が Astro のgetLiveCollection()を呼び出し - ローダーが実行 —
emdashLoaderが Kysely を介して適切なec_*テーブルをクエリ - データが返される — エントリは
id、slug、dataを含む Astro のエントリ形式にマッピング - ページがレンダリング — コンポーネントがコンテンツを受け取り HTML をレンダリング
管理リクエストの場合:
- ミドルウェアが認証 — セッショントークンを検証
- API ルートがリクエストを処理 — リポジトリを介した CRUD 操作
- フックが発火 —
beforeCreate、afterUpdateなど - データベースが更新 — Kysely が SQL を実行
- レスポンスが返される — 管理 SPA に JSON レスポンスを返す
仮想モジュール
EmDash はビルド時に仮想モジュールを生成してランタイムを設定します:
| モジュール | 目的 |
|---|---|
virtual:emdash/config | データベースとストレージの設定 |
virtual:emdash/dialect | データベース方言ファクトリ |
virtual:emdash/plugin-admins | プラグイン管理 UI の静的インポート |
このアプローチにより、バンドラーがプラグインコードを適切に解決してツリーシェイクできることが保証されます。