Schema als Code
Die Kernidee
Abschnitt betitelt „Die Kernidee“In Rebase sind Ihre TypeScript-Sammlungsdefinitionen die einzige Quelle der Wahrheit. Aus einem Satz von TypeScript-Objekten generiert Rebase:
- PostgreSQL-Tabellen über die Drizzle ORM-Schemaerzeugung
- CRUD-Benutzeroberfläche — Formulare, Tabellen, Validierung, Feldtypen
- REST-API-Endpunkte mit Filterung, Sortierung und Paginierung
- Client-SDK — typsichere Datenoperationen
- RLS-Richtlinien — Zeilenebenen-Sicherheit in Postgres
Das bedeutet, Ihr Schema ist:
- Versionskontrolliert — jede Änderung ist ein Git-Commit
- Typsicher — TypeScript fängt Fehler zur Kompilierzeit ab
- Überprüfbar — Schemaänderungen durchlaufen Pull-Requests
- Portabel — dieselbe Definition funktioniert über Frontend, Backend und CLI hinweg
Visuelle Bearbeitung mit AST-Manipulation
Abschnitt betitelt „Visuelle Bearbeitung mit AST-Manipulation“Rebase bietet auch einen visuellen Sammlungseditor im Studio-Modus. Wenn ein Nicht-Entwickler den visuellen Editor verwendet, um ein Feld hinzuzufügen:
- Das Studio modifiziert die Datenbank nicht direkt
- Stattdessen verwendet es ts-morph, um Ihre TypeScript-Quelldatei als AST zu parsen
- Es fügt die neue Eigenschaftsdefinition präzise in den
properties-Block ein - Alle vorhandenen Code, Callbacks und benutzerdefinierte Logik bleiben unberührt erhalten
- Die Datei wird gespeichert, wodurch ein Hot-Reload ausgelöst wird
Dieser Ansatz „UI als Code-Generator“ bedeutet, dass visuelle Bearbeitungen dasselbe saubere TypeScript erzeugen, das ein Entwickler von Hand schreiben würde.
Schema-Generierungs-Pipeline
Abschnitt betitelt „Schema-Generierungs-Pipeline“Your collections are read once and emitted twice — as a Drizzle schema the running server queries through, and as SQL that describes the database you meant to have. It is the SQL that the database is brought into line with, by Atlas, which diffs your desired schema against the live one and plans the change:
Collections (TypeScript) │ ┌─────────────────────┴─────────────────────┐ ▼ ▼ rebase schema generate (the same command also │ writes the SQL below) ▼ │ backend/src/schema.generated.ts ▼ the Drizzle schema the runtime drizzle/schema.sql ← Atlas's desired state reads and writes rows through policies.sql ← RLS, applied separately search.sql, vector.sql ← Atlas cannot manage these │ ┌─────────────┴─────────────┐ ▼ ▼ rebase db push rebase db generate Atlas plans the diff Atlas writes the diff and applies it now to drizzle/migrations/ │ │ │ ▼ │ rebase db migrate │ applies them in order └─────────────┬─────────────┘ ▼ PostgreSQLdb push is the development loop; db generate + db migrate is the
reviewable one, and the one to use in production. Both go through the same
generated SQL, so they cannot disagree about what your collections mean. See
Schema Generation for every flag.
Beispiel
Abschnitt betitelt „Beispiel“Gegeben diese Sammlung:
import { defineCollection } from "@rebasepro/cms-types";const productsCollection = defineCollection({ slug: "products", name: "Products", table: "products", properties: { name: { type: "string", name: "Name", validation: { required: true } }, price: { type: "number", name: "Price", columnType: "numeric" }, active: { type: "boolean", name: "Active", defaultValue: true }, createdAt: { type: "date", name: "Created", autoValue: "on_create" } }});Rebase generiert dieses Drizzle-Schema:
// schema.generated.ts// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.
import { boolean, numeric, pgPolicy, pgTable, text, timestamp } from 'drizzle-orm/pg-core';import { relations as drizzleRelations, sql } from 'drizzle-orm';
export const products = pgTable("products", { name: text("name").notNull(), price: numeric("price"), active: boolean("active").default(sql`TRUE`), createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`now()`), id: text("id").primaryKey()}, (table) => ([ pgPolicy("products_default_admin_read", { as: "permissive", for: "select", to: ["public"], using: sql`(rebase.uid() IS NULL) OR (string_to_array(rebase.roles(), ',') && ARRAY['admin'])` }), pgPolicy("products_default_admin_write_insert", { as: "permissive", for: "insert", to: ["public"], withCheck: sql`(rebase.uid() IS NULL) OR (string_to_array(rebase.roles(), ',') && ARRAY['admin'])` }), pgPolicy("products_default_admin_write_update", { as: "permissive", for: "update", to: ["public"], using: sql`(rebase.uid() IS NULL) OR (string_to_array(rebase.roles(), ',') && ARRAY['admin'])`, withCheck: sql`(rebase.uid() IS NULL) OR (string_to_array(rebase.roles(), ',') && ARRAY['admin'])` }), pgPolicy("products_default_admin_write_delete", { as: "permissive", for: "delete", to: ["public"], using: sql`(rebase.uid() IS NULL) OR (string_to_array(rebase.roles(), ',') && ARRAY['admin'])` }),])).enableRLS();
export const tables = { products };export const enums = { };export const relations = { };Two things in there are worth reading twice. The id column you did not
declare: every collection gets a text primary key unless a property claims
isId. And the pgPolicy block: row level security is enabled on every table,
and those baseline policies are what keep the trusted server context and the
admin role able to read it at all — see
Security Rules.
Das ergibt diese SQL:
-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.
CREATE SCHEMA IF NOT EXISTS "rebase";
CREATE TABLE "public"."products" ( "id" TEXT PRIMARY KEY, "name" TEXT NOT NULL, "price" NUMERIC, "active" BOOLEAN DEFAULT TRUE, "created_at" TIMESTAMP WITH TIME ZONE DEFAULT now());Policies, search columns, vector indexes and updated_at triggers are written to
files of their own and applied by the CLI in their own right — Atlas manages
none of the four.
Sicherheit und nicht gemappte Datenbankobjekte
Abschnitt betitelt „Sicherheit und nicht gemappte Datenbankobjekte“Wenn Rebase das Datenbankschema aktualisiert, ordnet es TypeScript-Sammlungsdefinitionen Datenbanktabellen zu. Um sicherzustellen, dass nicht gemappte Datenbankobjekte (z. B. Tabellen, Sichten, Enums) niemals gelöscht oder geändert werden, implementiert Rebase mehrere Sicherheitsebenen:
- Strikte Tabellenfilterung (
tablesFilter): Die generierte Drizzle-Konfiguration beschränkt die Synchronisierung dynamisch auf die im generierten Schema exportierten Tabellen. Alle nicht erkannten Tabellen oder Tabellen aus Legacy-Systemen, die in der Datenbank vorhanden sind, werden von der Synchronisierungs-Engine ignoriert. - Schema-Einschränkungen (
schemaFilter): Die DB-Synchronisierung ist ausschließlich auf daspublic-Schema beschränkt. Interne Datenbanktabellen, benutzerdefinierte Schemata und erweiterungsspezifische Tabellen bleiben unberührt. - Rollen- und Erweiterungsschutz: Drizzle ist so konfiguriert, dass es keine Datenbankrollen (
entities.roles: false) oder Hilfstabellen von Erweiterungen wie PostGIS verwaltet. - Interaktive Bestätigung im Entwicklungsmodus: Beim Ausführen von
rebase db pushin der Entwicklung wird die CLI mit den Flags--strictund--verboseausgeführt. Dies stellt sicher, dass Entwickler alle destruktiven SQL-Aktionen vor der Ausführung explizit überprüfen und genehmigen müssen. - Index-Besitz über den Namen: Indizes sind das eine Objekt, das auf einer gemappten Tabelle liegt, also kann Tabellen-Filterung sie nicht schützen — und ein Push plante früher
DROP INDEXfür jeden Index, der im Zielzustand fehlte, also für jeden handgeschriebenen. Rebase benennt die von ihm erzeugten Indizes jetzt<table>_<columns>_ix_<7 hex>(_ux_beiunique), eine Form, die kein anderer Namensgeber hier erzeugt, und schließt alles Übrige namentlich vom Diff aus. Ein selbst geschriebener oder per Introspektion übernommener Index wird nie angefasst; eine gelöschte Deklaration entfernt ihren Index weiterhin, was so gewollt ist. Siehe Indizes.
Nächste Schritte
Abschnitt betitelt „Nächste Schritte“- Sammlungen — Vollständige Referenz zur Sammlungs-Konfiguration
- Eigenschaften — Detaillierte Spaltentyp-Zuordnungen
- Indizes — Die Indizes deklarieren, die Ihre Abfragen brauchen