The Unbreakable Data Foundation.
Stop struggling with raw ORMs and brittle database security policies. Aeion OS shields its massive PostgreSQL infrastructure behind a Transparent Database Wrapper powered by Drizzle. It natively enforces 3-Stage RBAC security, provides "Holographic" Temporal Time Travel to query historical data, and ensures zero-data-loss via the Aegis backup system.
Enterprise Data Orchestration
Engineered to protect multi-tenant cloud ecosystems automatically.
Transparent Drizzle Proxy
Aeion utilizes a sophisticated JavaScript Proxy architecture. Developers simply call `ctx.db.find()`, and the wrapper intelligently routes the call to the high-level Collection API (enforcing security) or falls through to raw Drizzle ORM for low-level queries seamlessly, eradicating cognitive load.
Three Tenant Isolation Modes — One API
Aeion supports three different multi-tenant isolation strategies. The Wrapper picks the right one per-tenant transparently, so application code is identical across all three.
COLUMN mode (default)
All tenants share the same Postgres tables; the tenant ID is auto-injected into every WHERE/INSERT/UPDATE/DELETE clause. Cheapest to operate, most efficient for SMB SaaS workloads with many tenants. The tenant predicate is enforced _before_ the query reaches the database — developers cannot construct a cross-tenant query even by mistake.
SCHEMA mode
Each tenant gets a dedicated Postgres schema. The connection's search path is set per-tenant automatically. Used for compliance-sensitive workloads (HIPAA covered entities, EU data residency) that demand stronger physical isolation than column scoping. Same TypeScript API.
DATABASE mode
Each tenant gets a dedicated Postgres database, handed out from a per-tenant connection pool. Used for white-label enterprise deployments that contractually demand "your data lives on your dedicated infrastructure." Same TypeScript API.
The Temporal Subsystem
Time-travel isn't a marketing word. It's a working subsystem that records reverse JSON Patches on every mutation and can rebuild any past document state in milliseconds.
Point-in-time reconstruction
Rebuild any document at any past timestamp by replaying reverse patches newest-to-oldest.
Time-travel HTTP API
Dedicated endpoints reconstruct, diff, or walk the full timeline of any tenant-scoped document.
`.asOf(timestamp)` queries
A query-builder modifier that fetches the world as it was, inline with your normal query pipeline — no separate history API to learn.
Hot to archive tiering
Events older than 30 days offload to cold storage automatically; queries transparently span both tiers so old history is never actually out of reach.
Retention enforcement
Per-collection retention windows, with a nightly purge of expired events that's itself audited.
Mutation recording
Reverse-patch capture wired into every collection's write path, so nothing has to opt in per-feature to get history.
The 3-Stage RBAC Pipeline
Every database call routes through three intercept points. Security is NOT optional — there's no "raw query" escape hatch in application code.
Stage 1 — Pre-Query (Row-Level Security). Before the query hits Postgres, the wrapper invokes the collection's access.read() access function with the authenticated user. The function returns either true (full access), false (no access — returns 404 to avoid leaking row existence), or a Filter object (e.g., { ownerId: { equals: user.id } }) that gets _intersected_ with the developer's query predicate. Developers cannot bypass this — the wrapper mutates the query before Drizzle compiles it.
Stage 2 — Post-Read (Field-Level Redaction). After Postgres returns rows, the wrapper iterates fields and consults the per-field read guards. Fields marked access.read: ({ user, doc }) => ... are stripped from the response if the guard returns false. A user without view:salary permission gets the row back with salary: undefined — never even sees the field name in the JSON.
Time-Travel Queries Inline
```typescript // Read the live state const post = await ctx.services["posts"] .query(ctx) .where({ id: "post_abc" }) .first();
// Read the state as of last Tuesday const lastTuesday = await ctx.services["posts"] .query(ctx) .where({ id: "post_abc" }) .asOf("2026-05-12T10:30:00Z") .first();
// Compare two timestamps const diff = await fetch( "/api/v1/temporal/compare?collection=posts&id=post_abc&a=2026-05-12T10:30:00Z&b=2026-05-15T14:00:00Z", ); // → { added: { tags: ["new"] }, changed: { title: { from: "...", to: "..." } } } ```
Each .asOf() call is implemented as: (1) find the latest version of the document at-or-before the target timestamp, (2) apply reverse JSON Patches newest→oldest to reconstruct that historical state. Sub-100ms typical latency on a healthy event log.
asOf() works for .first(), .findMany(), .count(), and .exists(). Filters apply against the historical state, so "what contacts did we have on March 1?" is a single query.
What the Wrapper Won't Let You Do
The Wrapper structurally forbids the operations that have caused real incidents in real SaaS products.
Subtitle: The Wrapper structurally forbids the operations that have caused real incidents in real SaaS products.
- Raw SQL without tenant scope — Application code can't construct a query string. The Wrapper's
db.find/create/update/deleteAPI is the only path, and it auto-injects tenant scope. A raw escape hatch exists for the platform's own internal subsystems, but it's never exposed to module code — the lesson that led to this rule is the same one behind the Aegis backup system below. - DROP TABLE / DROP COLUMN on non-empty tables — Layer 1 of Aegis runs _before_ Drizzle's migration applies. Row counts are checked; non-empty data refuses the migration unless
--force-destructiveis set. - Spoof the audit trail —
createdBy,updatedBy,updatedAtare overwritten on every write. Even if a developer's API handler accidentally accepted these from the request body, the Wrapper rewrites them. - Forget to log a destructive action — Every CRUD operation auto-emits a
record.created/record.updated
## Why These Guardrails Exist
Aeion's own engineering team learned this lesson the hard way: a destructive migration once ran outside the safety wrapper during a routine schema sync, and real data was lost before a proper safety net existed. That's not a hypothetical risk — it's the reason the pre-destructive guard, the always-on Wrapper interceptors, the temporal subsystem, and the Aegis backup tiers all exist and are on by default, for every tenant, with no opt-in required.
The Wrapper isn't a marketing feature; it's a lesson learned, built into the platform so no customer has to learn it the same way. See **`/platform/aegis`** for the full four-layer backup architecture.
Frequently Asked Questions
It refuses them in application code. System-level operations (cross-tenant migrations, platform admin dashboards) use the `sysDb` client directly — a separate non-tenant-scoped surface only accessible to platform-tenant code. Module code can't import it. The TypeScript types enforce the boundary: `AeionContext.db` is the tenant-scoped wrapper, `AeionContext.sysDb` is `unknown` outside platform-tenant code paths.
Stage 1 adds the access function's filter to the WHERE clause — typically a single boolean conjunction, ~zero query-plan impact. Stage 2 runs in TypeScript over the result set — measured at ~3-5 μs per row per guarded field. Stage 3 runs Zod validation, which is ~5-20 μs per write. Total wrapper overhead is dominated by network + Postgres, not the interceptors.
From application code, no. Module routes get a `ctx.db` that is the tenant-scoped wrapper, period. A raw escape hatch exists deep in the platform's internals for the platform's own subsystems (the temporal recorder, the migration engine, Aegis) that legitimately need bypass. Your collection code does not have access to this escape hatch, by design.
The `afterChange` / `afterDelete` hooks run *after* the primary write commits, in the same transaction. The reverse-patch generation is a memory-only diff (RFC 6902) and a single INSERT into `temporal_events` — typically <2 ms on modern hardware. For very high-write workloads (>1000 TPS), enable the async-archive option which buffers events through Redis before flushing.
Roughly proportional to your write volume × retention window. A typical SMB tenant with 10K-100K records sees ~1-5 GB/month. Tier-2 archive offloads events older than 30 days to S3 as compressed JSONL — about 90% storage savings. `.asOf()` queries against archived data transparently fetch + decompress on demand (slower than live events but still sub-second).
Transactions: yes, the Wrapper accepts a transaction handle and threads it through. Views: read-only views are queryable via the wrapper; materialized views run outside the wrapper since the data is denormalized at refresh time. Custom Postgres extensions (pgvector, PostGIS, pg_trgm): supported — the wrapper passes through unknown operators to Drizzle.
No — and you shouldn't want to. Every measured workload we've profiled has shown that the security + audit overhead is far less than the cost of the underlying query. If you genuinely need to bypass (e.g., for a million-row analytical aggregation), use the `sysDb` admin-side or build a materialized view + scheduled refresh.
Yes. BullMQ workers receive a primitive payload + tenant ID, reconstruct the AeionContext via `api.createContext(tenantId)`, and write through the Wrapper exactly like sync code. The reconstruction step also re-applies the 3-stage RBAC against the system user identity that owns the job. There's no async path that escapes the Wrapper.
Structural tenant isolation, not a convention developers have to remember.
A missing `WHERE tenant_id =` clause on a raw SQL query is a classic cross-tenant data leak in hand-rolled multi-tenant SaaS. The Transparent Database Wrapper intercepts every query, enforces RBAC, and injects tenant isolation automatically — with a temporal time-travel record of every change — so no developer has to hand-write the safety check on every query.
Drizzle ORM Proxy (Transparent)
3-Stage RBAC Gating (Pre-Query / Post-Read / Pre-Write)
Temporal RFC 6902 Diffing — Across Every Collection
Aegis Point-in-Time Recovery
Structural Cross-Tenant Isolation (COLUMN / SCHEMA / DATABASE Modes)
Automated Audit Blame Injection
Fail-Safe-by-Default Access Control
Zero-Downtime Schema Migrations
Ready to secure your data layer?
Dive into the technical specifications of the Transparent Database Wrapper.