Architecture
How the pieces fit together.
superapp is three npm packages: @superapp/backend (server), @superapp/db (data client), and @superapp/auth (auth client). The server owns all data access, auth, and permissions. The data client uses Drizzle Proxy to send parameterized SQL to the server, where permissions are applied before execution.
System Diagram
@superapp/db (Data) @superapp/auth (Auth)
┌────────────────────────┐ ┌──────────────────────────┐
│ drizzle() │ │ createAuth() │
│ Drizzle Proxy driver │ │ AuthCard · AuthProvider │
│ Sends SQL + params │ │ useSession · UserButton │
└────────────┬───────────┘ └────────────┬─────────────┘
│ POST /data + JWT │ POST /auth/*
└────────────┬───────────────┘
▼
@superapp/backend (Server)
┌───────────────────────────────────┐
│ HTTP Adapter (Hono/Express/Next) │
│ │ │
│ ▼ │
│ Auth (better-auth / custom) │
│ │ │
│ ▼ │
│ Permissions (CASL) │
│ Validate SQL + inject filters │
│ │ │
│ ▼ │
│ DuckDB (in-process) │
└─────────┬─────────────────────────┘
│
┌───────┼───────┬───────┐
▼ ▼ ▼ ▼
Postgres MySQL SQLite CSVThree Packages
@superapp/backend (Server)
The backend is the single source of truth for all data access. It:
- Receives parameterized SQL from the Drizzle Proxy client over HTTP.
- Verifies JWTs through a pluggable auth provider.
- Enforces permissions using CASL-based rules that validate the SQL, inject row filters, restrict columns, validate writes, and preset values.
- Executes the permission-filtered SQL through DuckDB, which fans out to attached databases.
- Stores metadata (sessions, roles, audit logs) in a Turso or local SQLite database via Drizzle ORM.
import { createEngine } from '@superapp/backend'
import { createHonoMiddleware } from '@superapp/backend/adapters/hono'@superapp/db (Data Client)
The data client is real Drizzle ORM built on Drizzle Proxy. Drizzle builds parameterized SQL on the client, and the proxy driver sends SQL + params to the server. The data you get back is already filtered, restricted, and validated by the backend's permission engine. It:
- Exposes standard Drizzle ORM query syntax that mirrors your schema.
- Sends parameterized SQL + params to the server's
/dataendpoint via Drizzle Proxy. - Returns permission-filtered data — row filters, column restrictions, and write validations are applied transparently by the backend before results reach the client.
- Generates TypeScript types from your server's
/schemaendpoint for full autocomplete.
import { drizzle } from '@superapp/db'@superapp/auth (Auth Client)
The auth client handles session management and ships pre-built auth UI components. It uses better-auth by default, but supports custom adapters for any auth provider. It:
- Manages JWT sessions — sign-in, sign-up, token refresh, sign-out.
- Ships auth UI components (
AuthCard,AuthProvider,UserButton) built with React. - Provides React hooks (
useSession) for accessing the current session. - Supports custom adapters — swap the default better-auth adapter for Firebase, Auth0, Clerk, or any custom auth system.
import { createAuth } from '@superapp/auth'
import { useSession } from '@superapp/auth'
import { AuthCard, AuthProvider, UserButton } from '@superapp/auth/components'Tech Stack
| Layer | Technology | Role |
|---|---|---|
| Query engine | DuckDB | In-process OLAP engine, ATTACH to external databases |
| HTTP framework | Hono | Lightweight, edge-compatible HTTP routing |
| Metadata store | Turso + Drizzle | Sessions, roles, audit logs, admin configuration |
| Authentication | better-auth | JWT-based auth with session management |
| Authorization | CASL | Attribute-based permission evaluation |
| Admin UI | React + Vite | Visual permission and connection management |
| Data client | @superapp/db | Drizzle Proxy over HTTP, returns permission-filtered data |
| Auth client | @superapp/auth | Session management, auth UI components (better-auth default, custom adapters) |
Dependency Graph
createEngine()
├── integrations[] → Database providers ──→ DuckDB ATTACH
├── connections{} → Named DB configs ──→ DuckDB ATTACH
├── auth → AuthProvider
│ ├── verifyToken()
│ ├── findUser()
│ └── resolveSession()
├── permissions{} → Permission definitions ──→ CASL abilities
├── roles{} → Role → permission mappings
├── duckdb{} → Pool config
├── limits{} → Rate and query limits
├── audit{} → Audit logging
└── adapter → HTTP adapter
├── POST /data
├── POST /auth/*
└── GET /schemaRequest Lifecycle
Every query follows this exact path:
- HTTP -- Adapter receives
POST /datawith JWT inAuthorizationheader. The body contains parameterized SQL + params from the Drizzle Proxy client. - Auth --
verifyToken()validates the JWT signature.findUser()retrieves the user record.resolveSession()enriches with roles and org memberships. - Permissions -- CASL evaluates which permissions apply based on the user's role. The engine validates the incoming SQL, injects WHERE filters, strips unauthorized columns, and for writes, validates data against
checkrules and injectspresetvalues. - DuckDB -- Executes the permission-filtered SQL against the attached database (Postgres, MySQL, SQLite, or CSV).
- Response -- Results are serialized to JSON and returned to the client.
No step can be skipped. There is no "bypass" mode in production. All SQL is parameterized (preventing injection), and the server validates and modifies every query before execution.