Skip to content

DB Dialect: Datenbank-Abstraktion

Aktuell: Drizzle + PostgreSQL

Drizzle ist der beste Kompromiss fuer unseren Use Case: SQL-nah, Schema-Builder, Relations, Migrations, klein (~7kb), Bun-nativ.

Drizzle unterstuetzt

DialectDatenbanken
postgresqlPostgreSQL, Neon, Supabase, PGlite
mysqlMySQL, MariaDB, PlanetScale, TiDB
sqliteSQLite, Turso, Cloudflare D1, Bun SQLite

Drizzle unterstuetzt NICHT

  • MongoDB (Dokument-DB, kein SQL)
  • CouchDB, DynamoDB, etc.

3-Stufen Plan

Stufe 1: Jetzt — Zentraler Re-Export

Alle PostgreSQL-spezifischen Imports an eine Stelle. Kein Code im Framework importiert direkt aus drizzle-orm/pg-core.

// packages/framework/src/db/dialect.ts — EINZIGE Stelle mit PG-Imports
// Drizzle PG
export { pgTable as table, serial, text, integer, boolean, timestamp } from "drizzle-orm/pg-core";
export { drizzle } from "drizzle-orm/postgres-js";
export type { PgTableWithColumns as TableColumns } from "drizzle-orm/pg-core";
export type { PgSelect as SelectQuery } from "drizzle-orm/pg-core";
// Connection
export { default as createClient } from "postgres";
// SQL Specifics
export const dialectConfig = {
autoIncrement: "SERIAL",
jsonType: "JSONB",
booleanType: "BOOLEAN",
timestampDefault: "NOW()",
} as const;

Ueberall sonst:

// VORHER (verstreut):
import { pgTable, serial, text } from "drizzle-orm/pg-core";
import { drizzle } from "drizzle-orm/postgres-js";
import type { PgTableWithColumns } from "drizzle-orm/pg-core";
// NACHHER (zentral):
import { table, serial, text, drizzle } from "../db/dialect";
import type { TableColumns } from "../db/dialect";

Aufwand: Klein. Nur Imports umbiegen. Keine Logik-Aenderung.

Stufe 2: Spaeter — Dialect-Packages fuer SQL

Wenn MySQL/MariaDB Support gebraucht wird:

@kumiko/framework ← Core (importiert aus dialect.ts)
@kumiko/dialect-pg ← PostgreSQL (Re-Exports + PG-spezifisches)
@kumiko/dialect-mysql ← MySQL/MariaDB (Re-Exports + MySQL-spezifisches)
@kumiko/dialect-sqlite ← SQLite (Re-Exports + SQLite-spezifisches)
// App waehlt:
import { pgDialect } from "@kumiko/dialect-pg";
createApp({
db: { dialect: pgDialect, url: "postgres://..." },
features: [...],
});

Dialect-Package Interface:

type SqlDialect = {
// Drizzle Specifics
table: typeof pgTable; // oder mysqlTable, sqliteTable
drizzle: (url: string) => DrizzleInstance;
// Typ-Mappings
types: {
serial: ColumnBuilder;
text: ColumnBuilder;
integer: ColumnBuilder;
boolean: ColumnBuilder;
timestamp: ColumnBuilder;
json: ColumnBuilder; // PG: jsonb, MySQL: json, SQLite: text
};
// SQL Generierung
sql: {
autoIncrement: string; // PG: "SERIAL", MySQL: "AUTO_INCREMENT"
jsonType: string; // PG: "JSONB", MySQL: "JSON"
timestampDefault: string; // PG: "NOW()", MySQL: "CURRENT_TIMESTAMP"
};
// Testing
createTestDb: () => Promise<TestDbHandle>;
createTestConnection: (url: string) => DrizzleInstance;
};

Stufe 3: Zukunft — Non-SQL (MongoDB etc.)

Wenn jemand MongoDB will, braucht es eine hoehere Abstraktion. Der CrudExecutor arbeitet dann nicht mehr direkt mit Drizzle sondern mit einem generischen Interface:

type DbDialect = {
connect(url: string): DbConnection;
// CRUD Operationen (abstrakt, DB-agnostisch)
insert(table: string, data: Record<string, unknown>): Promise<{ id: number }>;
findById(table: string, id: number, tenantId: number): Promise<Record<string, unknown> | null>;
findMany(table: string, query: FindManyOptions): Promise<Record<string, unknown>[]>;
update(table: string, id: number, tenantId: number, changes: Record<string, unknown>): Promise<Record<string, unknown>>;
delete(table: string, id: number, tenantId: number): Promise<void>;
// Schema
ensureSchema(name: string, entity: EntityDefinition): Promise<void>;
// Testing
createTestDb(): Promise<TestDbHandle>;
};
@kumiko/dialect-pg ← Drizzle + PostgreSQL (implementiert DbDialect)
@kumiko/dialect-mysql ← Drizzle + MySQL (implementiert DbDialect)
@kumiko/dialect-sqlite ← Drizzle + SQLite (implementiert DbDialect)
@kumiko/dialect-mongo ← MongoDB Native / Mongoose (implementiert DbDialect)

Entity-Definitionen sind schon DB-agnostisch:

r.entity("order", {
fields: {
state: { type: "select", options: [...] },
price: { type: "number" },
},
});
// PostgreSQL: → SQL Table mit Columns
// MongoDB: → Collection mit Document Schema

WICHTIG: Stufe 3 ist bewusst nicht designed. Zu frueh abstrahieren macht alles langsamer und komplizierter. Das Interface oben ist ein Entwurf — die echte API ergibt sich wenn der Use Case da ist.

Was sich NICHT aendert (egal welche DB)

  • defineFeature() API
  • Entity-Definitionen (fields, relations)
  • Handler-Signaturen
  • ctx.db (immer tenant-scoped)
  • Config, Jobs, Delivery, etc.

Die DB ist ein Implementierungsdetail. Der Feature-Code ist DB-agnostisch.

Raw SQL: Escape Hatch

Fuer Features die direkte SQL/DB-Zugriffe brauchen:

// ctx.db.raw — Zugriff auf die native Drizzle/Mongo Connection
// Typsicherheit ist dann Sache des Entwicklers
const result = await ctx.db.raw.execute(sql`SELECT ...`);

Das ist bewusst raw benannt — man sieht sofort: hier verlasse ich die Abstraktion.

Betroffene Dateien (Stufe 1)

Diese Dateien importieren aktuell direkt aus drizzle-orm/pg-core:

DateiWas
db/table-builder.tspgTable, serial, text, integer, boolean, timestamp
db/crud-executor.tsPgTableWithColumns
db/cursor.tsPgSelect
pipeline/cascade-handler.tsPgTableWithColumns
db/connection.tsdrizzle, postgres
db/pg-adapter.tsdrizzle, postgres
testing/index.tsdrizzle, postgres
files/file-ref-table.tspgTable, serial, text, integer, timestamp
bundled-features/*/table.tspgTable, serial, text, integer, timestamp

Plus Raw SQL mit SERIAL in 7+ Dateien (Tests + Features).

Alle muessen auf den zentralen dialect.ts Import umgestellt werden.