The 7 Pillars
The seven architectural pillars of D.O.R.I.S.
Every pillar is optional. Add or remove bindings as your app grows.
The pillars are ordered from the foundation up — skip what you don't need.
01. Rust → WASM via `worker-build`
Rust compiles to WebAssembly using the `worker` crate and `worker-build`. Axum runs inside the Worker for ergonomic HTTP routing.
Key dependencies: `worker`, `axum`, `serde`, `tower-http`, `chrono`, `uuid`, `jwt-compact`.
Pin the Rust toolchain in `rust-toolchain.toml` for reproducible CI builds:
# rust-toolchain.toml
[toolchain]
channel = "1.91.1"
profile = "minimal"
components = ["rustfmt", "clippy"]
targets = ["wasm32-unknown-unknown"]02. JS Entry Wrapper
Thin JS layer (`src/entry.js`):
- Imports the Rust WASM module
- Routes WebSocket connections to `NotifyDo` Durable Object
- Routes CPU-heavy endpoints to `HeavyDo` Durable Object
- Falls through everything else to Rust WASM
// Pattern: route → Durable Object → fall through to Rust
if (env.HEAVY_DO && shouldOffloadToHeavyDo(request, url)) {
const stub = env.HEAVY_DO.get(env.HEAVY_DO.idFromName(shardKey));
return stub.fetch(request);
}
const worker = new RustWorker(ctx, env);
return worker.fetch(request);Enabling/disabling: just add/remove the `HEAVY_DO` binding in `wrangler.toml`.
03. HeavyDo CPU Offload
Durable Objects have a 30s CPU budget vs Workers' few ms on the free plan.
HeavyDo reuses the same Axum router — no duplicated business logic.
Routes CPU-heavy endpoints: import, password hashing (PBKDF2), key rotation, 2FA, account deletion.
#[durable_object]
pub struct HeavyDo { state: State, env: Env }
impl DurableObject for HeavyDo {
async fn fetch(&self, req: Request) -> Result<Response> {
let http_req: HttpRequest = req.try_into()?;
let mut app = router::api_router(self.env.clone()) // same router
.layer(cors)
.layer(DefaultBodyLimit::max(5 * 1024 * 1024));
let http_resp = app.call(http_req).await?;
http_resp.try_into()
}
}Each DO instance is sharded by `user:shardKey` for isolation.
04. NotifyDo WebSocket Push
`NotifyDo` handles WebSocket connections for live sync — desktop and browser extensions.
- Tags-based fan-out: each WS connection tagged by user ID
- Anonymous hub for Send file downloads
- Mobile push via Bitwarden relay service (optional)
05. D1 Database
SQLite-compatible, serverless, auto-scaling.
- Session-backed reads with `first-primary` consistency for write-then-read patterns
- `d1_query!` macro replaces `worker::query!` for ergonomic prepared statements
- Migrations managed via `wrangler d1 migrations apply`
[[d1_databases]]
binding = "vault1"
database_name = "vault1"
database_id = "${D1_DATABASE_ID}"
migrations_dir = "migrations"06. Rate Limiting + Assets
Cloudflare-native rate limiting via `[[ratelimits]]` in `wrangler.toml`.
Static assets served directly from Cloudflare's edge. `run_worker_first` limits Worker invocation to API routes only. Secrets via Cloudflare Dashboard.
[[ratelimits]]
name = "LOGIN_RATE_LIMITER"
namespace_id = "1001"
simple = { limit = 5, period = 60 }07. One-Click CI/CD
3 workflows: Build (prod on `main`), Deploy Dev (`dev`), Backup D1 (daily cron).
Required secrets: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`, `D1_DATABASE_ID`
# CI/CD pipeline steps:
1. Checkout + Node.js + Rust toolchain
2. Install worker-build (pinned version)
3. Download & extract frontend assets + apply overrides
4. Bootstrap D1 schema (idempotent empty-DB check)
5. Apply D1 migrations (with retry for edge cases)
6. npx wrangler deployFork → set 3 secrets → push main → auto-deploys 🚀