Architettura

In questa pagina

EmDash si integra profondamente con Astro per fornire un’esperienza CMS completa. Questa pagina spiega le decisioni architetturali chiave e come i pezzi si incastrano.

Panoramica di alto livello

┌──────────────────────────────────────────────────────────────────┐
│                         Il tuo sito Astro                         │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                   Integrazione EmDash                      │  │
│  │                                                            │  │
│  │  ┌──────────────┐   ┌──────────────┐   ┌───────────────┐   │  │
│  │  │   Contenuto  │   │    Admin     │   │    Plugin     │   │  │
│  │  │    APIs      │   │   Pannello   │   │               │   │  │
│  │  └──────────────┘   └──────────────┘   └───────────────┘   │  │
│  │                                                            │  │
│  │  ┌──────────────────────────────────────────────────────┐  │  │
│  │  │                    Layer dati                        │  │  │
│  │  │   Database (D1/SQLite)  +  Storage (R2/S3)           │  │  │
│  │  └──────────────────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                    Framework Astro                         │  │
│  │         Live Collections · Middleware · Sessioni           │  │
│  └────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

EmDash funziona come integrazione Astro. Inietta route per il pannello di amministrazione e le API REST, fornisce un loader di contenuti per Live Collections e gestisce migrazioni del database e connessioni di storage.

Schema database-first

A differenza dei CMS tradizionali che definiscono lo schema nel codice, EmDash memorizza le definizioni dello schema nel database stesso. Due tabelle di sistema tracciano la struttura del contenuto:

  • _emdash_collections — Metadati della collection (slug, label, funzionalità)
  • _emdash_fields — Definizioni dei campi per ogni collection

Quando crei una collection “products” con campi title e price tramite l’interfaccia di amministrazione, EmDash:

  1. Inserisce record in _emdash_collections e _emdash_fields
  2. Esegue ALTER TABLE per creare ec_products con le colonne appropriate

Questo design abilita:

  • Modifica dello schema a runtime — Creare e modificare tipi di contenuto senza modifiche al codice o rebuild
  • Setup friendly per non-sviluppatori — Gli editor di contenuti possono progettare il loro modello dati tramite l’interfaccia
  • Vere colonne SQL — Indicizzazione appropriata, chiavi esterne e ottimizzazione delle query

Tabelle per collection

Ogni collection ottiene la propria tabella SQLite con prefisso ec_:

-- Creata quando viene aggiunta la collection "posts"
CREATE TABLE ec_posts (
  -- Colonne di sistema (sempre presenti)
  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,              -- Soft delete
  version INTEGER DEFAULT 1,    -- Optimistic locking

  -- Colonne di contenuto (dalle definizioni dei campi)
  title TEXT NOT NULL,
  content JSON,                 -- Portable Text
  excerpt TEXT
);

Perché tabelle per collection invece di una singola tabella di contenuto con JSON?

  • Le vere colonne SQL abilitano indicizzazione e query appropriate
  • Le chiavi esterne funzionano correttamente
  • Lo schema è auto-documentante nel database
  • Nessun overhead di parsing JSON per l’accesso ai campi
  • Gli strumenti di database possono ispezionare lo schema direttamente

Integrazione Live Collections

EmDash usa le Live Collections di Astro 6 per servire contenuti a runtime. Le modifiche ai contenuti sono immediatamente disponibili senza rebuild statici.

emdashLoader() implementa l’interfaccia LiveLoader di Astro:

// src/live.config.ts
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";

export const collections = {
	_emdash: defineLiveCollection({ loader: emdashLoader() }),
};

Interroga il contenuto usando le funzioni wrapper fornite:

import { getEmDashCollection, getEmDashEntry } from "emdash";

// Ottieni tutti i post pubblicati
const { entries: posts } = await getEmDashCollection("posts");

// Ottieni le bozze
const { entries: drafts } = await getEmDashCollection("posts", {
	status: "draft",
});

// Ottieni una singola voce per slug
const { entry: post } = await getEmDashEntry("posts", "my-post-slug");

Iniezione di route

L’integrazione EmDash usa l’API injectRoute di Astro per aggiungere route di amministrazione e API:

Pattern del percorsoScopo
/_emdash/admin/[...path]SPA del pannello di amministrazione
/_emdash/api/manifestManifest admin (collection, plugin)
/_emdash/api/content/[collection]CRUD per voci di contenuto
/_emdash/api/media/*Operazioni libreria media
/_emdash/api/schema/*Gestione schema
/_emdash/api/settingsImpostazioni del sito
/_emdash/api/menus/*Menu di navigazione
/_emdash/api/taxonomies/*Categorie, tag, tassonomie personalizzate

Le route sono iniettate dal package emdash — nulla viene copiato nel tuo progetto.

Layer dati

EmDash usa Kysely per query SQL type-safe su tutti i database supportati:

SQLite

Sviluppo locale con sqlite({ url: "file:./data.db" })

D1

SQL serverless di Cloudflare con d1({ binding: "DB" })

libSQL

SQLite remoto con libsql({ url: "...", authToken: "..." })

La configurazione del database viene passata all’integrazione in 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",
			}),
		}),
	],
});

Astrazione dello storage

I file multimediali sono memorizzati separatamente dal database. EmDash supporta:

  • Filesystem locale — Sviluppo e deployment semplici
  • Cloudflare R2 — Object storage compatibile S3 all’edge
  • Compatibile S3 — Qualsiasi object storage compatibile S3

Gli upload usano URL firmati per upload diretti client-to-storage, bypassando i limiti di dimensione del body dei Workers.

Architettura dei plugin

I plugin estendono EmDash tramite un sistema di hook ispirato a WordPress:

  • Hook di contenutocontent:beforeSave, content:afterSave, content:beforeDelete, content:afterDelete
  • Hook mediamedia:beforeUpload, media:afterUpload
  • Storage isolato — Ogni plugin ottiene accesso KV con namespace
  • Estensioni UI admin — Widget dashboard, pagine impostazioni, editor di campi personalizzati

I plugin possono funzionare in due modalità:

  1. Native — Accesso completo all’ambiente host (per plugin proprietari)
  2. Sandboxed — Esecuzione in isolate V8 con permessi basati su capability (per plugin di terze parti su Cloudflare)
// astro.config.mjs
import { seoPlugin } from "@emdash-cms/plugin-seo";

emdash({
	plugins: [seoPlugin({ maxTitleLength: 60 })],
});

Flusso delle richieste

Una tipica richiesta di contenuto segue questo percorso:

  1. Astro riceve la richiesta — Il componente della tua pagina viene eseguito
  2. Interroga il contenutogetEmDashCollection() chiama getLiveCollection() di Astro
  3. Il loader viene eseguitoemdashLoader interroga la tabella ec_* appropriata tramite Kysely
  4. Dati restituiti — Le voci sono mappate al formato di voce di Astro con id, slug e data
  5. La pagina viene renderizzata — Il tuo componente riceve il contenuto e renderizza HTML

Per le richieste admin:

  1. Il middleware autentica — Valida il token di sessione
  2. La route API gestisce la richiesta — Operazioni CRUD tramite repository
  3. Gli hook vengono attivatibeforeCreate, afterUpdate, ecc.
  4. Aggiornamenti database — Kysely esegue SQL
  5. Risposta restituita — Risposta JSON alla SPA admin

Moduli virtuali

EmDash genera moduli virtuali al momento del build per configurare il runtime:

ModuloScopo
virtual:emdash/configConfigurazione database e storage
virtual:emdash/dialectFactory dialect database
virtual:emdash/plugin-adminsImport statici per UI admin plugin

Questo approccio garantisce che i bundler possano risolvere correttamente e tree-shake il codice dei plugin.

Prossimi passi