Files
2026-05-27 23:29:53 +01:00
..
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00

@suaveplan/demo

Unified Bun + Hono server with a React 19 SPA front-end and a JSON API in one process. Demonstrates locale-prefix routing across five base locales, @suaveplan/api-plugin-auth-driven Better-Auth authentication backed by SurrealDB, and React + Tailwind v4 styling.

What's wired up

  • Server — single Bun process running a Hono app on port 3300 (override with PORT=…). The Hono instance is the one owned by a SuaveApp from @suaveplan/api. /api/health is registered by registerHealthRoutes; /api/auth/* is mounted by @suaveplan/api-plugin-auth (9 OpenAPI routes + the Better-Auth catch-all); /api/me is a demo convenience that calls authService.getSession(headers); everything else falls through to the Vite-built client bundle in dist/client/, with index.html fallback so the client router owns deep links.
  • Auth — the canonical genesis pipeline. The Hono app is assembled via defineApp() from @suaveplan/api, with createAuthPlugin(...) from @suaveplan/api-plugin-auth registered as a plugin. The auth plugin's 6-step onInit applies the bundled 17-table Better-Auth SurrealQL schema, constructs the real Better-Auth instance backed by betterAuthSurrealAdapter (from @suaveplan/better-auth-surrealdb, the Better-Auth v1.6 DBAdapterInstance factory), registers the typed authService on the host ServiceRegistry, mounts the 9 OpenAPI routes plus the Better-Auth catch-all under /api/auth/*, and installs the global session-resolver middleware so every request gets a typed Principal on its Hono context. A small inline demo-db-seed plugin (in src/server/auth.ts) drops the live SurrealAdapter into ServiceRegistry["db"] during onRegister so the auth plugin can resolve it during onInit. Backing store is an external SurrealDB reached via SURREAL_URL (default ws://localhost:8000), namespace SURREAL_NS (default demo), database SURREAL_DB (default demo), credentials SURREAL_USER / SURREAL_PASS (default root / root).
  • Client — React 19 + Vite + @tailwindcss/vite for Tailwind v4. Routing via react-router-dom@7 with a locale-prefix design. The app root composes <ReactUiProvider> (from @suaveplan/react-ui) around the entire tree, seeded with a singleton I18nInstance (createI18n from @suaveplan/i18n) and the chassis lightTheme plugin. <ReactUiProvider> internally wraps <TranslationProvider> → <ThemeProvider> → <PackRegistryProvider> and auto-registers the matching chassis locale bundle on mount / language change, so any react-ui primitive that calls useTranslation(...) finds a populated catalog. Form pages (/auth/login, /auth/register, /auth/forgot-password, /user/settings) compose <Card>, <Field>, <FieldLabel>, <Input>, <Button>, <Alert> from the chassis — no hand-rolled Tailwind on those primitives. Demo-specific copy continues to flow through a typed LocaleProvider so the page-level message catalogs stay co-located and statically checked; the i18n instance is driven to the matching BCP47 variant (en → en-GB, zh → zh-CN, ar → ar-AE, cy → cy-GB, ru → ru-RU) via syncDemoLocale(base) from a useEffect.

Locale-prefix routing

Five base locales: en, zh, ar, cy, ru. Region variants (e.g. en-GB, zh-CN, ar-EG) parse case-insensitively and canonicalise to lowercase-base + UPPERCASE-region. Anything else renders the NotFoundPage with a language picker.

  • / → Navigate to /<detected>/home (uses navigator.languages).
  • /<locale>/home
  • /<locale>/about-us
  • /<locale>/contact-us
  • /<locale>/privacy-policy
  • /<locale>/terms-of-service
  • /<locale>/auth/login
  • /<locale>/auth/register
  • /<locale>/auth/forgot-password
  • /<locale>/auth/logout
  • /<locale>/user/control-panel (gated — RequireAuth)
  • /<locale>/user/settings (gated — RequireAuth)
  • /<locale>/admin/control-panel (gated — RequireRole admin)
  • /<locale>/admin/settings (gated — RequireRole admin)

ar flips <html dir="rtl">. The <LocaleSwitcher> rewrites the URL in place when a user changes language.

Running the demo

The demo requires a reachable SurrealDB instance. The simplest path:

docker run -d --name demo-surreal -p 8000:8000 \
  surrealdb/surrealdb:latest start --user root --pass root --bind 0.0.0.0:8000

Then, from the repo root:

bun install                                  # workspace deps
bunx turbo build --filter=@suaveplan/demo    # build api/auth deps + dist/client
bun run --filter=@suaveplan/demo dev         # boot the server on :3300

Visit http://localhost:3300 — it should redirect to /en/home (or your browser's preferred locale if it matches one of the five).

Smoke endpoints:

curl http://localhost:3300/api/health
curl http://localhost:3300/api/me
curl http://localhost:3300/api/auth/get-session

End-to-end sign-up roundtrip — produces a 200 with a Better-Auth session token + user record:

curl -X POST http://localhost:3300/api/auth/sign-up/email \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"hunter2","name":"Alice"}'

Env overrides (all optional):

Variable Default
PORT 3300
SURREAL_URL ws://localhost:8000
SURREAL_NS demo
SURREAL_DB demo
SURREAL_USER root
SURREAL_PASS root
BETTER_AUTH_URL http://localhost:3300
BETTER_AUTH_SECRET demo placeholder — set in prod