Database Wrapper Architecture & Specs
A hyper-optimized kernel interceptor featuring a JavaScript Proxy router, 3-Stage Role-Based Access Control (RBAC), and Holographic Temporal time travel.
System & Interception Metrics
Proxy Routing Overhead
<0.001ms latency
Temporal Diffing
Fast RFC 6902 JSON patching
Multi-Tenant Scoping
Mathematically enforced `tenantId`
RBAC Processing
Pre-compiled Filter AST
1. The JS Proxy Router
Developers should not have to context-switch between high-level collection APIs and low-level SQL builders. A native JavaScript Proxy sitting in front of ctx.db solves this.
- Smart Routing: When a developer calls ctx.db.find, the Proxy intercepts the property access. It identifies it as a Collection method and routes it to the security-enforced Collection interface. If they call ctx.db.select(), the proxy falls through directly to the raw Drizzle ORM.
- Type Safety: The TypeScript compiler treats ctx.db as a union of the raw Drizzle client and the Collection API, providing perfect IDE autocomplete for both operational paradigms simultaneously.
typescript
// Example: The 3-Stage RBAC architecture dynamically injecting Row-Level Securityexport const Warehouses: CollectionConfig = { slug: "scm_warehouses", access: createAccessControl({ // 1. PRE-QUERY RBAC: Inject SQL WHERE clauses dynamically read: createScopedReadAccess(async (ctx, user) => { if (hasAnyRole(user, ["admin", "superadmin", "scm-manager"])) return true; // Normal operators only query warehouses where they are the manager return { manager: { equals: user.id } }; }), // 2. PRE-WRITE RBAC: Reject unauthorized mutations update: "owner-or-admin" }), fields: [ { name: "totalCapacity", type: "number", access: { // 3. POST-READ RBAC: Redact sensitive fields from the payload read: ({ user }) => hasAnyRole(user, ["admin", "superadmin", "scm-manager"]) } } ]};2. Holographic Temporal State
Aeion OS moves beyond simple "UpdatedAt" timestamps by treating the database holographically (Event Sourcing Lite).
- RFC 6902 Patching: When temporal tracking is enabled on a collection, every mutation is intercepted before it commits. The engine calculates the diff between the old record and the new payload, generates a strict RFC 6902 JSON patch, and saves it to the temporal event store.
- Time-Travel Queries: Developers can reconstruct a record's exact state at any past moment by asking for it as-of a timestamp — the engine replays the sequence of JSON patches in reverse to rebuild that historical state on demand.
- Why reverse patches, not full snapshots: Storing a complete copy of every row on every write would balloon storage and slow down the hot path. Storing only the minimal RFC 6902 diff keeps writes to a memory-only diff plus a single INSERT, and reconstruction walks patches newest-to-oldest from the current live row — so the common case (querying recent history) touches the fewest patches. The same log powers the .asOf() query modifier, per-document Version History, and admin-driven bulk undo.
3. Structural Multi-Tenant Isolation
Tenant scoping is not a WHERE clause developers have to remember — it is enforced by the Wrapper before a query ever reaches Postgres. The same TypeScript API compiles to one of three physical isolation strategies, chosen per-tenant:
- COLUMN mode (default): All tenants share tables; the
tenantIdpredicate is injected into every WHERE / INSERT / UPDATE / DELETE. Cheapest to operate and efficient for many-tenant SaaS. Because the predicate is added _before_ Drizzle compiles the query, application code cannot construct a cross-tenant read even by mistake. - SCHEMA mode: Each tenant gets a dedicated Postgres schema with a per-tenant search path — stronger physical separation for compliance-sensitive workloads (HIPAA covered entities, EU data residency).
- DATABASE mode: Each tenant gets a dedicated database from a per-tenant connection pool — for white-label deployments that contractually require isolated infrastructure.
Switching modes is a migration, not a code change: the mode is configured per-tenant and applied during schema sync, and the module code above it never changes.
Database schema syncs can be dangerous. The `Aegis` pre-destructive guardrail aggressively protects data. Running a database schema sync (`bun run db:sync`) forces Aegis to scan the generated Drizzle SQL. If it detects `DROP TABLE` or `TRUNCATE` commands aimed at non-empty tables, it throws a hard refusal. To bypass it (`--force-destructive`), Aegis mandates an automatic `pg_dump` to an S3/MinIO safety bucket first.
Aeion optimizes massive datasets through a split-storage strategy. Strictly defined fields (Text, Numbers, Relationships) generate real SQL columns for fast indexing. Dynamic or repeating fields (like Rich Text blocks, Arrays, or Layout components), by contrast, are automatically mapped into a single, unified JSONB column, providing NoSQL-level flexibility within the rigid ACID compliance of PostgreSQL.
Frequently Asked Questions
No. Module routes receive a `ctx.db` that is the tenant-scoped Wrapper — the `find` / `create` / `update` / `delete` API is the only path, and it auto-injects tenant scope. A raw escape hatch (`sysDb`) exists for the platform's own internal subsystems (the temporal recorder, the migration engine, Aegis), but module code cannot import it and the TypeScript types enforce the boundary.
Stage 1 — Pre-Query: the collection's `access.read()` returns `true`, `false`, or a `Filter` that is intersected into the WHERE clause before Drizzle compiles it. Stage 2 — Post-Read: per-field read guards strip unauthorized fields so a user never even sees the field name. Stage 3 — Pre-Write: `create` / `update` / `delete` guards run, then Zod validation, then `createdBy` / `updatedBy` / `updatedAt` are forcibly injected — overwriting any spoofed values in the request body.
It finds the latest version of the document at-or-before the target timestamp, then replays reverse RFC 6902 patches newest-to-oldest to rebuild that historical state. The modifier works with `.first()`, `.findMany()`, `.count()`, and `.exists()`, and filters apply against the reconstructed historical state.
The reverse-patch capture runs after the primary write commits, as a memory-only diff plus a single INSERT into the temporal event store — negligible against the network + Postgres cost that dominates any write. Very high-write workloads can buffer events asynchronously before flushing.
Fail-closed. A collection with no `access` config resolves to no access until an author explicitly opens it — security is opt-in, not opt-out, so a forgotten policy denies rather than leaks.
See the data foundation in action
Every query routes through the Wrapper — RBAC, tenant isolation, and temporal history, on by default for every tenant.