---
title: "Mandu v0.x → v1.0 Migration Guide (DRAFT)"
status: draft
audience: Mandu app authors
created: 2026-04-18
---

# v0.x → v1.0 Migration Guide (DRAFT)

> **Status**: 1.0.0 is not yet released. This guide was written alongside
> Phase 6.4's release-prep work and stays in draft form for when 1.0.0
> ships. The **content below is accurate for the current 0.x `main`** —
> the only thing deferred is the version number. Use this as the
> "what changed since 0.21" reference today.

The guide covers touchpoints where behavior changed, plus the new
capabilities shipped across Phase 2–6.

---

## What's new in v1.0 (non-breaking additions)

### Auth primitives (`@mandujs/core/auth/*`)
- `hashPassword` / `verifyPassword` — argon2id via `Bun.password`
- `loginUser` / `logoutUser` / `currentUserId` / `loggedAt`
- `createEmailVerification` — signed token → email → consume
- `createPasswordReset` — single-use reset tokens

### New middleware (`@mandujs/core/middleware/*`)
- `session()` — attaches `req.session` from pluggable `SessionStorage`
- `csrf()` — double-submit cookie via `Bun.CSRF` (or HMAC fallback)
- `oauth()` — OAuth 2.0 + PKCE with GitHub + Google presets
- `rateLimit()` — sliding-window rate limiter (in-memory + SQLite stores)
- `secure()` — Helmet-equivalent header bundle (CSP, HSTS, frame-options, referrer-policy, permissions-policy)

### Data + storage
- `@mandujs/core/db` — `Bun.sql` wrapper: Postgres/MySQL/SQLite via one callable tagged-template API with `.one()`, `.transaction()`, `.close()`
- `@mandujs/core/filling/session-sqlite` — SQLite-backed `SessionStorage` (session payloads > 4 KB, server-side invalidation)
- `@mandujs/core/storage/s3` — `Bun.s3` wrapper (AWS / R2 / MinIO)
- `@mandujs/core/email` — `EmailSender` interface + Resend / memory adapters

### Operations
- `@mandujs/core/scheduler` — `defineCron({...}).start()` wrapping `Bun.cron`
- `@mandujs/core/perf` — `Bun.nanoseconds` instrumentation (opt-in via `MANDU_PERF=1`)
- `@mandujs/core/id` — `newId()` returning UUID v7 (time-ordered, DB-index-friendly)

### Runtime helpers
- `redirect(url, { status? })` — loaders can `return` or `throw`
- `notFound({ message? })` — same pattern for 404s; renders `app/not-found.tsx` if present
- `unauthorized()` / `forbidden()` / `badRequest()` — 4xx helpers available outside ctx

### Conventions
- `app/not-found.tsx` — global 404 page (optional)
- `app/error.tsx` — error boundary. Now receives `{ error, digest }` uniformly with stack trace redacted to 3 frames in production

### Infrastructure
- `bunfig.toml` sets `linker = "isolated"` — phantom-dep prevention, per-package `node_modules` symlinked to `.bun` store
- Bun minimum bumped to **1.3.12** (engines) — required for `Bun.cron`, `Bun.CSRF`, `Bun.CookieMap`, `Bun.sql` MySQL+SQLite support
- `packageManager: "bun@1.3.12"` pinned

---

## Breaking changes

### Bun 1.3.12 minimum (engines)
If your project pinned Bun < 1.3.12, upgrade:
```bash
bun upgrade
```
Then update `package.json` (generated by `mandu init` in v1):
```diff
  "engines": {
-   "bun": ">=1.3.0"
+   "bun": ">=1.3.12"
  }
```

### `CookieManager.get()` now reflects response-side writes
In v0.x, `ctx.cookies.get("x")` only returned request cookies. In v1.0, a cookie `set` earlier in the same request is visible to subsequent `get`. If your code depended on the old "request-only" semantics, inspect your middleware ordering — this is almost always the desired behavior.

### Loader default-export shape more forgiving
In v0.x, page files had to use `export default { component, filling }`. v1.0 accepts both:
```ts
// v1.0 — both work
export default PageComponent;
export const filling = Mandu.filling().loader(...);

// ...or...
export default { component: PageComponent, filling };
```
No action needed for existing code.

### `Layout` cookies now propagate
Layout slot's `ctx.cookies.set(...)` used to be silently dropped in v0.x. v1.0 merges layout + page cookies into the response. If you accidentally depended on this being dropped, you now see it in Set-Cookie headers — almost always a fix, not a breakage.

### Engines change means CI may need updating
If your own CI pins a specific Bun version, bump it to 1.3.12+:
```yaml
- uses: oven-sh/setup-bun@v2
  with:
    bun-version: 1.3.12   # or "latest"
```

---

## Deprecations

None removed in v1.0. From this release forward, removal of any existing public export requires a major bump.

---

## Recommended adoption order

If you're starting from a v0.21+ app and want to absorb v1.0's new capabilities:

1. **Bump engines** to `bun >=1.3.12` and `bun upgrade` locally + in CI
2. **Install v1.0**: `bun add @mandujs/core@^1.0.0 @mandujs/cli@^1.0.0`
3. **Adopt `notFound()` + `app/not-found.tsx`** — immediate DX win for missing routes
4. **Add `secure()` middleware** at the manifest level — one-line security-header bundle
5. **Add `csrf()` middleware** to your forms (if not already)
6. **Consider `session-sqlite`** if you need > 4 KB sessions or server-side logout
7. **Add `rateLimit()`** to your auth routes (login, signup, password reset) — Phase 5.3's send() spam gap documented

---

## Trying the new auth flow end-to-end

```bash
mandu init my-app --template auth-starter
cd my-app
bun install
bun run dev
```

`auth-starter` is the official reference template — signup + login + logout + avatar upload + CSRF-protected forms + (opt-in) SQLite session store + hourly session-gc cron. Read its source to see how the pieces compose.

---

## Reporting issues

See `CONTRIBUTING.md` or file an issue at https://github.com/konamgil/mandu/issues.

v1.0 API surface will be frozen — bugs fix-forward in 1.0.x patch releases, new APIs land in 1.x minor, breaking changes wait for 2.0.
