# Atlas full documentation > Complete canonical context for Atlas. Use llms.txt and individual Markdown alternates when a smaller context is sufficient. # Atlas > Two boilerplates: composable Bun/TypeScript packages for APIs, full-stack applications, command-line tools, retrieval, and tool-using agents; and a Rust workspace on gpui and guise for native desktop applications. Atlas installs as `@wess/atlas`. Public imports use `@wess/atlas/`. The shorter `@atlas/` spelling requires the optional path aliases documented in the project README. When generating Atlas code: - Read the agent guide, then only the package references required by the task. - Use functions and immutable data; do not introduce classes or mutate inputs. - Prefer Bun and Web APIs over compatibility packages. - Treat an export as unavailable unless it appears in a canonical package reference or the API index. - Run `bun run check`, `bun run typecheck`, and `bun test` before declaring repository work complete. ## Start here - [Agent guide](https://wess.io/atlas/docs/agents/index.md): Grounding order, import rules, package selection, MCP safety, context budgets, and verification. - [Quick start](https://wess.io/atlas/docs/quickstart/index.md): Build an authenticated application with storage and administration. - [API reference](https://wess.io/atlas/docs/api/index.md): Condensed cross-package exports, types, and signatures. - [Documentation map](https://wess.io/atlas/docs/index.md): Every guide and package reference. - [Field survey](https://wess.io/atlas/index.md): The site's branching entry point; each numbered section ends in a concrete stack, command, and code. ## Package references - [config](https://wess.io/atlas/docs/config/index.md): Typed environment variables through `defineConfig` and `env`. - [db](https://wess.io/atlas/docs/db/index.md): Query building, schemas, changesets, and database drivers. - [migrate](https://wess.io/atlas/docs/migrate/index.md): Timestamped migrations and schema diffing. - [server](https://wess.io/atlas/docs/server/index.md): Pipe-based HTTP, typed routes, WebSockets, and server-sent events. - [edge](https://wess.io/atlas/docs/edge/index.md): TLS-terminating reverse proxy. - [auth](https://wess.io/atlas/docs/auth/index.md): Passwords, tokens, sessions, authentication flows, and social sign-in. - [security](https://wess.io/atlas/docs/security/index.md): Headers, rate limits, audit logs, TOTP, and revocable sessions. - [oauth](https://wess.io/atlas/docs/oauth/index.md): OAuth 2.1 authorization server. - [sso](https://wess.io/atlas/docs/sso/index.md): OIDC relying party. - [email](https://wess.io/atlas/docs/email/index.md): Provider-neutral delivery and account templates. - [share](https://wess.io/atlas/docs/share/index.md): Share URLs and server-side share-by-email. - [storage](https://wess.io/atlas/docs/storage/index.md): S3-compatible object operations and presigned URLs. - [cache](https://wess.io/atlas/docs/cache/index.md): Redis caching and cache-aside helpers. - [request](https://wess.io/atlas/docs/request/index.md): Fetch client, retries, interceptors, and provider presets. - [cli](https://wess.io/atlas/docs/cli/index.md): Commands, scaffolding, documentation lookup, and process management. - [ui](https://wess.io/atlas/docs/ui/index.md): React application blocks. - [admin](https://wess.io/atlas/docs/admin/index.md): Schema-driven administration. - [mcp](https://wess.io/atlas/docs/mcp/index.md): Documentation access and runtime introspection over MCP. - [ai](https://wess.io/atlas/docs/ai/index.md): Chat, embeddings, retrieval, streaming, and tool-use loops. ## Desktop - [Desktop architecture](https://wess.io/atlas/docs/desktop/architecture/index.md): Crate layering, the tokio bridge, state scopes, and the store. - [Desktop scaffolding](https://wess.io/atlas/docs/desktop/scaffolding/index.md): The scaffolding script, the three templates, and renaming an application. - [Desktop packaging](https://wess.io/atlas/docs/desktop/packaging/index.md): macOS, Linux, and Windows artifacts and where each comes from. - [Desktop releases](https://wess.io/atlas/docs/desktop/release/index.md): Signing, notarization, release cutting, and in-place self-update. - [Desktop gotchas](https://wess.io/atlas/docs/desktop/gotchas/index.md): gpui and guise traps, each with the symptom that led to it. ## Machine-readable interfaces - [Complete documentation context](https://wess.io/atlas/llms-full.txt): Combined canonical guides and package references for one-fetch indexing. - [Search index](https://wess.io/atlas/search.json): Titles, descriptions, source kind, routes, and searchable text. - [MCP reference](https://wess.io/atlas/docs/mcp/index.md): `docs.list`, `docs.read`, health, and conditional service tools. - [CLI reference](https://wess.io/atlas/docs/cli/index.md): Local documentation access with `atlas docs [name]`. ## Optional - [Architecture overview](https://wess.io/atlas/docs/overview/index.md): Package graph, design philosophy, and module boundaries. - [Cookbook](https://wess.io/atlas/docs/cookbook/index.md): Practical cross-package recipes. - [Project README](https://wess.io/atlas/docs/readme/index.md): Installation, templates, development, and package inventory. # Atlas Agent Guide Atlas exposes the same documentation through the web, package files, the CLI, and MCP. Use the smallest transport and context set that can answer the task. ## Fast Path 1. Fetch `https://wess.io/atlas/llms.txt`. 2. Read this guide and the reference for each package the task actually uses. 3. Use the API reference only when a signature is still unclear. 4. Load the architecture overview only for cross-package design decisions. 5. Run the repository checks before presenting generated code as complete. Do not begin by loading every package reference. Atlas is intentionally composable; most changes need two or three packages. ## Documentation Transports | Transport | Entry point | Best use | |---|---|---| | Concise web index | `/atlas/llms.txt` | Discover the minimum relevant sources | | Complete web context | `/atlas/llms-full.txt` | One-fetch offline indexing or large-context work | | Page Markdown | `/atlas/docs//index.md` | Fetch one canonical guide or package reference | | Search index | `/atlas/search.json` | Programmatic title, description, kind, and text search | | Installed CLI | `atlas docs [name]` | Read package-local docs without network access | | MCP | `atlas mcp` | Discover docs and inspect a running Atlas application | Every documentation HTML page declares its Markdown alternate and the covering `llms.txt` file in the document head. ## Import Rule Atlas installs as one package: ```bash bun add @wess/atlas ``` Use public subpath imports in generated consumer code: ```ts import { defineConfig, env } from "@wess/atlas/config" import { connect } from "@wess/atlas/db" import { get, json, serve } from "@wess/atlas/server" ``` The shorter `@atlas/` spelling appears in some repository examples. It requires the `tsconfig.json` path aliases documented in the README. Do not emit it for a new consumer unless that alias is already configured. ## Package Selection | Need | Read first | Common companions | |---|---|---| | Environment and configuration | `config` | `server`, `db` | | SQL, schemas, and validation | `db` | `migrate`, `admin` | | HTTP routes and responses | `server` | `config`, `security` | | Login and sessions | `auth` | `db`, `security`, `email` | | Authorization server | `oauth` | `auth`, `security` | | Sign in with an identity provider | `sso` | `auth`, `security` | | Files and object storage | `storage` | `server`, `security` | | Redis-backed state | `cache` | `server` | | Outbound HTTP | `request` | `config` | | TLS edge routing | `edge` | `server` | | React application blocks | `ui` | `server`, `auth` | | Generated administration | `admin` | `db`, `ui` | | Chat, retrieval, or tool loops | `ai` | `request`, `server` | | Runtime introspection | `mcp` | the services being exposed | | Commands and scaffolding | `cli` | the packages in the generated template | ## Generation Contract When writing Atlas code: - Use functions and immutable values. Do not introduce classes or mutate inputs. - Prefer Bun and Web APIs over compatibility packages. - Import from `@wess/atlas/` in consumer projects. - Read each selected package's canonical reference before inventing an export. - Derive database row types with `RowOf`. - Compose HTTP behavior with `pipeline()`; use `halt()` for deliberate short-circuiting. - Use typed `route()` validation rather than parsing the same request body manually. - Keep filenames lowercase with no dashes, underscores, or spaces. - Put tests under `packages//test/` when changing Atlas itself. If a requested export is absent from the package reference and API lookup, treat it as absent. Do not infer an API from a neighboring framework or package. ## MCP Workflow `atlas mcp` always exposes: - `docs.list` to discover package, guide, and root documentation identifiers. - `docs.read` to read one canonical source. - `health.check` to report configured service connectivity. Additional tools appear only when their service is present in the MCP context. Read-only introspection includes schema, route, migration-status, cache-read, storage-list, and log-tail operations. Migration, cache-write, cache-delete, cache-flush, and other state-changing tools must be treated as mutations, not discovery. Recommended sequence: 1. Call `docs.list`. 2. Read the references needed for the task. 3. Call `health.check` and read-only inspection tools. 4. Explain the intended mutation before invoking a state-changing tool. 5. Re-read the affected state after the mutation. ## Context Budgets ### Small Use `llms.txt`, this guide, and one package reference. ### Feature Add the quick start or API reference plus every package directly involved in the feature. ### Architecture Add the overview and references for both sides of each package boundary. ### Complete Use `llms-full.txt` only when building a local index, working offline after one fetch, or when the available context can hold the full corpus. It duplicates the sources linked by `llms.txt`. ## Verification Run checks in this order: ```bash bun run check bun run typecheck bun test ``` For documentation work, also run: ```bash bun run site:build bun test site/test/site.test.ts ``` Generated code is not grounded until its imports exist, its package references agree with the usage, and the relevant checks pass. # Atlas: Architecture Overview Atlas is a collection of composable, functional Bun/TypeScript packages for building APIs, full-stack applications, and CLI tools. This document describes the architecture, package relationships, and design philosophy. ## Dependency Graph ``` @atlas/config (foundation — typed env loading) ├── @atlas/db ├── @atlas/server ├── @atlas/storage └── @atlas/cli @atlas/migrate (depends on @atlas/db) @atlas/auth (depends on @atlas/db, @atlas/server) └── @atlas/auth/social (subpath — OAuth-client side: Google/GitHub/Apple/MS/FB/X/TikTok) @atlas/admin (depends on @atlas/db, @atlas/server) @atlas/security (depends on @atlas/db, @atlas/auth) @atlas/oauth (depends on @atlas/db, @atlas/server, @atlas/auth) @atlas/sso (depends on @atlas/db, @atlas/server, @atlas/auth) @atlas/edge (standalone — no sibling deps) @atlas/email (standalone — no sibling deps) @atlas/share (depends on @atlas/email — share URL builders + server-side share-by-email) @atlas/cache (optional, can use @atlas/config) @atlas/request (standalone — no sibling deps) @atlas/mcp (depends on @atlas/db, @atlas/server, @atlas/config) @atlas/ai (depends on @atlas/server for the withAi pipe; no external deps) @atlas/ui (frontend, optional browser-side usage) ``` Packages are shallow — max 1 level of dependencies on siblings. This keeps them lightweight and composable. ## Core Packages ### Config `@atlas/config` is the foundation. It provides typed environment variable loading that reads `.env` at startup and produces a frozen, immutable config object. ```ts const config = defineConfig({ database: env("DATABASE_URL"), port: env("PORT", { parse: Number, default: "3000" }), }) ``` All other packages optionally depend on config or accept config values directly. No forced wiring. ### Database `@atlas/db` is the largest package, with three layers: 1. **Query Builder** — Ecto-inspired fluent chains for SELECT, INSERT, UPDATE, DELETE, JOIN operations. Fully immutable, functional API. 2. **Drivers** — Uniform interface to Postgres (via `Bun.sql`) and SQLite (via `bun:sqlite`) with transaction support. 3. **Schemas & Changesets** — Type-safe table definitions and Zod-powered input validation. ```ts const users = defineSchema("users", { id: column.serial().primaryKey(), email: column.text().unique(), }) const query = from(users).where(q => q("email").equals("user@test.com")) const result = await db.one(query) ``` ### Server `@atlas/server` wraps `Bun.serve` with a Plug-inspired pipe system. Requests flow through immutable `Conn` objects that pipes transform. Sub-modules: - `@atlas/server` — Core HTTP routing with `get()`, `post()`, `put()`, `patch()`, `del()` route builders, plus the adapter pattern (`createAdapter`, `compose`) for running multiple listeners - `@atlas/server/ws` — WebSocket support - `@atlas/server/sse` — Server-Sent Events ```ts const logger = pipe(c => { console.log(c.method); return c }) const auth = pipe(c => { const token = c.headers.get("authorization") return token ? assign(c, { userId: decode(token).id }) : halt(c, 401) }) serve({ routes: [ get("/users", pipeline(logger, auth)(handler)), ], }) ``` Pipes are composable via `pipeline()`, which short-circuits on `halt()`. This approach is borrowed from Elixir and is more testable and readable than middleware. ### Edge `@atlas/edge` is a TLS-terminating reverse proxy with built-in Let's Encrypt automation — the layer that would otherwise be Caddy or nginx in front of a Bun app. - **ACME v2 client** — ECDSA P-256 account, JWS-signed requests, HTTP-01 challenge solver, hand-rolled CSR via a small DER encoder. No external deps. - **Reverse proxy** — forwards requests with `X-Real-IP`, `X-Forwarded-For` (appended), `X-Forwarded-Proto`, `X-Forwarded-Host`. Hop-by-hop headers stripped. Host rewritten unless `preserveHost: true`. - **Cert lifecycle** — certs persist to a configurable store (filesystem default, in-memory for tests). A renewal timer reissues 30 days before expiry and `server.reload({ tls })` swaps without dropping connections. - **Localhost dev** — when every site host matches `isLocalHost`, the edge runs plain HTTP on a non-privileged port. No certs, no sudo. The same `edge.ts` runs in dev and prod. ```ts import { defineEdge, proxy } from "@atlas/edge" defineEdge({ acme: { email: "you@example.com", storage: "/var/atlas/edge" }, sites: [{ host: process.env.DOMAIN!, compress: ["gzip", "zstd"], routes: [{ handler: proxy("http://app:3000") }], }], }).listen() ``` The `templates/edge` scaffold ships a complete deploy pattern: Procfile for local, Dockerfile + compose.yaml for production, with the cert volume and ACME staging-then-prod recipe pre-wired. ### Auth `@atlas/auth` provides: - **Primitives** — `hash()`, `verify()`, `token.sign()`, `token.verify()` built on Bun's crypto - **Flows** — Prebuilt pipes like `signup()`, `login()`, `requireAuth()`, `passwordReset()` that work with `@atlas/server` and `@atlas/db` - **Social login** (`@atlas/auth/social` subpath) — pluggable OAuth-*client* for Google, GitHub, Apple, Microsoft, Facebook, X (Twitter), and TikTok. PKCE-S256 mandatory, state + verifier held in a signed HttpOnly cookie so the flow stays stateless. Your app owns the user table — `onSuccess` receives a normalized `SocialProfile` and you decide upsert/link. ```ts const signupPipe = signup({ db, table: "users", fields: ["email", "password"], onSuccess: (c, user) => json(c, 201, user), }) // Social — same Conn/PipeFn shape: const social = socialAuth({ secret, providers: { google: google({ clientId, clientSecret, redirectUri }), github: github({ clientId, clientSecret, redirectUri }), tiktok: tiktok({ clientKey, clientSecret, redirectUri }), // …apple, microsoft, facebook, twitter }, }) get("/auth/google", social.start("google")) get("/auth/google/callback", social.callback("google", { onSuccess: async (c, { profile }) => json(c, 200, { profile }), })) ``` ### Security `@atlas/security` ships hardening primitives most apps end up writing themselves: - `withSecurityHeaders()` — strict default headers (HSTS, COOP/CORP, Permissions-Policy) and a CSP that defaults to `'self'`-only with no inline script in production. Also stashes the real Bun socket peer onto `req.peerIp` so downstream rate-limit / audit code does not have to trust client-supplied `X-Forwarded-For`. - `createDbRateLimit()` / `createMemoryRateLimit()` — atomic UPSERT on Postgres, transactional read-modify-write on SQLite, in-memory for tests. - `clientIp(req, { trustedProxies })` — only honors forwarded headers when the request actually arrived from a configured trusted proxy. - `createSessionStore()` — DB-backed, revocable JWT sessions with `last_used_at` tracking and a sweep helper. Pair with `@atlas/auth#requireAuth`. - `generateSecret` / `verifyTotp` / `otpauthUrl` / `generateBackupCodes` — pure `node:crypto` TOTP with backup codes. - `decideInline()` — safe-MIME allowlist for `Content-Disposition: inline` (never inlines SVG). - `createAuditLogger()` — fire-and-forget audit-event recorder; never throws, never blocks the response. ### OAuth `@atlas/oauth` is an OAuth 2.1 server: PKCE-required authorization code, refresh-token rotation, device-code flow, RFC 8414 discovery, RFC 7009 revoke, and admin client management. ```ts import { oauthRoutes } from "@atlas/oauth" import { requireAuth } from "@atlas/auth" serve({ routes: [ ...oauthRoutes({ db, secret, scopes: ["read", "write"], loadUser, buildAccessTokenClaims, requireUser: requireAuth({ secret }), requireAdmin, }), ...appRoutes, ], }) ``` `oauthRoutes(cfg)` returns every endpoint as a flat `Route[]` ready to spread into `serve`. Mount only what you need by calling the per-flow factories (`oauthAuthorizeRoutes`, `oauthTokenRoutes`, …) directly. Sweeps for expired auth codes / refresh tokens / device codes are exported separately so you can run them on a schedule. ### Email `@atlas/email` is a provider-agnostic transport plus a small HTML shell and two stock templates. ```ts import { createEmailer, inviteEmail } from "@atlas/email" const emailer = createEmailer({ apiKey: process.env.RESEND_API_KEY, from: process.env.RESEND_FROM }) await emailer.send({ to: "user@example.com", ...inviteEmail({ inviterName, product, signupUrl }) }) ``` `createEmailer` returns the real Resend transport when both env vars are set and a console transport otherwise — dev environments stay unblocked without configuring a sending domain. `send` never throws; failures come back as `{ ok: false, error }`. The included `layout()` is a 560px Outlook-friendly card; always pass user-supplied strings through `escapeHtml`. ### Share `@atlas/share` is the smallest practical layer for "share this link" UX. Pure URL builders for the eight channels users actually use, plus a server-side `shareEmail` that dispatches through any `@atlas/email` transport. ```ts import { share, shareUrl, shareEmail } from "@atlas/share" shareUrl("twitter", { url, title, hashtags: ["atlas"] }) // → https://twitter.com/intent/tweet?url=…&text=…&hashtags=atlas share({ url, title, text }) // → { twitter, facebook, linkedin, reddit, whatsapp, telegram, sms, email } await shareEmail({ emailer, to: "friend@example.com", sharerName: "Wess", content: { url, title }, }) ``` The URL builders have zero dependencies; `shareEmail` reuses the same `layout()` shell as `inviteEmail`/`passwordResetEmail` and escapes untrusted strings the same way. ### Storage `@atlas/storage` provides S3-compatible object storage with: - `upload()` — PUT file to bucket - `download()` — GET file from bucket - `presign()` — Generate presigned URLs for direct client access - `list()` — List objects by prefix Uses AWS Signature V4 (implemented from scratch, ~250 lines) with no external dependencies. ### Cache `@atlas/cache` wraps `Bun.redis` with: - `createCache()` — Redis-backed cache - `createMemoryCache()` — In-memory (for testing) - `cached()` — Cache-aside pattern: fetch from cache, fallback to function, store result - `invalidate()` — Bust cache keys ```ts const getUser = cached(cache, "user", async (id) => { return await db.one(from(users).where(q => q("id").equals(id))) }, { ttl: 600 }) ``` ### Request `@atlas/request` is an HTTP client built on `fetch` with: - `request()` — One-off requests - `createClient()` — Preconfigured clients with base URL, headers, retries - Providers — Drop-in configs for GitHub, Stripe, OpenAI, Resend (import from `@atlas/request/providers`) - Retry logic with exponential backoff - Request/response interceptors ```ts import { github } from "@atlas/request/providers" const gh = github({ token: process.env.GITHUB_TOKEN! }) const repos = await (await gh.get("/user/repos")).json() ``` ### CLI `@atlas/cli` provides: - **Command parser** — Define commands, flags, subcommands; parse argv - **Foreman** — Procfile parser and concurrent process runner with colored output - **Built-in commands** — `atlas init` (scaffold project), `atlas add` (add packages), `atlas dev` (start dev server), `atlas mcp` (launch MCP server) ```ts cli("myapp", [ command("serve", { flags: { port: flag("p", { type: "number", default: 3000 }) }, run: ({ flags }) => startServer(flags.port), }), ]) await foreman({ web: "bun run server.ts", worker: "bun run worker.ts" }) ``` ### Migrate `@atlas/migrate` manages timestamped SQL migrations with up/down support. ```ts migrate.create("./migrations", "add_users") await migrate.up(db, "./migrations") await migrate.status(db, "./migrations") ``` Tracks applied migrations in `schema_migrations` table. Works with Postgres and SQLite. ### UI `@atlas/ui` is a modular React + Mantine package with independent blocks: - `@atlas/ui/provider` — Theme and layout shell - `@atlas/ui/forms` — Form primitives and helpers - `@atlas/ui/table` — Data tables with sort/filter/pagination - `@atlas/ui/auth` — Login, signup, password reset pages - `@atlas/ui/storage` — File upload and image preview - `@atlas/ui/nav` — Navigation components - `@atlas/ui/cache` — Cache inspector and status badge - `@atlas/ui/ai` — Chat panel, message list, streaming display Each block is independently importable and tree-shakeable. ### Admin `@atlas/admin` auto-generates a Django-style admin panel from `@atlas/db` schemas. Provides: - **API routes** — CRUD endpoints for each model - **Admin SPA** — React UI with list, detail, create views, search, filters, bulk actions, custom actions, query builder - **Metadata routes** — Schema introspection, field info, relation discovery ```ts const panel = admin({ db, models: [ model({ schema: users, searchFields: ["email", "name"], filterFields: ["status"], bulkActions: ["delete", "export"], }), ], }) serve({ routes: panel.mount([]) }) ``` Serves SPA at `/admin` with full CRUD UI. ### MCP `@atlas/mcp` provides a Model Context Protocol server for AI/LLM debugging and introspection. It exposes your app's database, routes, config, and logs to AI agents through the standard MCP protocol. ```ts import { collectTools, createContext, createMcpServer } from "@atlas/mcp" const ctx = createContext({ db, routes: myRoutes, config }) const mcp = createMcpServer(collectTools(ctx), ctx) mcp.start() ``` Launch via CLI: `atlas mcp`. AI agents can then query your database, inspect routes, view config, and read logs interactively. ### AI `@atlas/ai` provides a unified interface for AI/LLM operations with zero external dependencies — it calls provider REST APIs directly via `fetch`. - **Providers** — `createProvider({ provider, key })` returns an `AiProvider` for `"openai"`, `"anthropic"`, or `"ollama"` - **Chat** — `provider.chat(opts)` for completions, `provider.chatStream(opts)` for streaming `StreamChunk`s - **Conversations** — `createConversation`/`send` for immutable message-history tracking - **Embeddings** — `embed(provider, inputs)` plus an in-memory `createVectorStore()` - **RAG** — `index(rag, id, text)` + `query(rag, question)` over a `{ ai, store, topK? }` bag - **Agents** — `runAgent({ ai, system?, tools, maxIterations? }, prompt)` for tool-using loops - **Server pipe** — `withAi(provider)` attaches the provider to `conn.assigns.ai` ```ts import { createProvider, send, createConversation, runAgent, tool } from "@atlas/ai" const openai = createProvider({ provider: "openai", key: process.env.OPENAI_API_KEY! }) const conv = createConversation("You are a helpful assistant") const { response } = await send(openai, conv, "Hello") for await (const chunk of openai.chatStream({ messages: [{ role: "user", content: "Hi" }] })) { if (chunk.type === "text") process.stdout.write(chunk.content ?? "") } ``` Works with OpenAI, Anthropic, Ollama, or any OpenAI-compatible endpoint (`baseUrl` override). ## Templates Atlas ships with 10 project templates, scaffolded via `atlas init --template `: | Template | Description | Key Packages | |----------|-------------|-------------| | `minimal` | Just server + config | config, server | | `api` | REST API with db, auth, migrations | config, db, migrate, server, auth | | `edge` | App + TLS-terminating edge (replaces Caddy/nginx) | config, server, edge | | `fullstack` | API + React frontend | config, db, server, auth, ui | | `admin` | API + admin panel | config, db, server, auth, admin | | `worker` | Background job processor | config, db, cache, cli | | `realtime` | WebSocket + SSE | config, server | | `socialnetwork` | Users, posts, follows, likes, feeds, media, real-time | config, db, server, auth, cache, storage | | `cms` | Headless CMS with content types, publishing, webhooks | config, db, server, auth, storage, admin | | `ai` | Chatbot, RAG, agents, embeddings, streaming | config, server, ai | ## Composition Patterns ### Typical Backend App ```ts // 1. Config const config = defineConfig({ database: env("DATABASE_URL"), ... }) // 2. Database const db = connect({ driver: "postgres", url: config.database }) // 3. Migrations await migrate.up(db) // 4. HTTP with pipes serve({ routes: [ post("/auth/signup", signup({ db, table: "users", ... })), post("/auth/login", login({ db, table: "users", ... })), get("/api/users", pipeline(requireAuth({ secret }))(listUsers)), post("/api/files", pipeline(requireAuth({ secret }), parseMultipart)(uploadFile)), ], }) ``` ### Full Stack Use `@atlas/ui` blocks on the client: ```tsx // server.ts import { get, halt, pipe, putHeader, serve } from "@atlas/server" const page = await Bun.file("./admin.html").text() // HTML file that loads the React SPA const spa = pipe((c) => putHeader(halt(c, 200, page), "content-type", "text/html; charset=utf-8")) serve({ routes: [ get("/admin", spa), get("/admin/*", spa), ...apiRoutes, ], }) ``` ```html
``` ```tsx // admin.tsx import { AdminApp } from "@atlas/admin" import { createRoot } from "react-dom/client" const root = createRoot(document.getElementById("root")!) root.render() ``` ## AGENTS.md Convention Each package includes an `AGENTS.md` at its root — a compact manifest (~80 lines) of the public API. **Structure:** - **Exports** — Each public function/type with signature and return type - **Types** — Data shapes that users need to generate correct code - **Usage** — Minimal working example - **Dependencies** — Sibling and external packages **Why this matters:** LLMs can read `packages/db/AGENTS.md` instead of traversing 20+ source files. This is critical for token efficiency in prompt engineering and code generation workflows. **Conventions:** - Keep under 100 lines - Every public export must be listed - Include return types and argument types - Update whenever the API changes - Treat as part of the build process ## Design Philosophy 1. **Functional** — No classes, immutable data, composition over inheritance 2. **Minimal dependencies** — Wrap Bun's native APIs, don't reach for external packages 3. **Composable** — Each package works independently or with others 4. **AI/LLM-friendly** — Clear APIs, good types, predictable patterns, AGENTS.md reference 5. **No framework lock-in** — Use what you need, combine with anything else 6. **Bun-native** — Idiomatic to Bun's philosophy and APIs 7. **Shallow dependencies** — Max 1 level of sibling package dependencies ## External Dependencies | Package | Deps | |---------|------| | config | none | | db | `zod` | | migrate | none | | server | none | | auth | none | | security | none | | oauth | none | | edge | none | | email | none | | share | none | | storage | none | | cache | none | | request | none | | sso | none | | cli | none | | ui | `react`, `@mantine/*`, `@tanstack/*`, `lucide-react` | | admin | `react`, `@mantine/*`, `@tanstack/*`, `lucide-react` | | mcp | none | | ai | none | Total: Only `zod` on the backend. Frontend uses React + Mantine + TanStack. ## Development Environment - **Monorepo** — Bun workspaces - **Testing** — `bun test` across all packages - **TypeScript** — Strict mode - **Linting** — Biome (run `bun run check` / `bun run tidy`) - **File naming** — All lowercase, no dashes or underscores, subdirectories for organization - **Environment** — `.env` in root (Postgres on localhost, SQLite in memory for tests)
# Atlas Quick Start This guide walks you through building a complete app with user authentication, file uploads, and an admin panel. ## Prerequisites - **Bun 1.0+** — Install from https://bun.sh - **Postgres** (optional) — For production; SQLite works for development - **Redis** (optional) — For caching; memory cache works for dev This guide uses SQLite for simplicity. ## Step 1: Create a Project ```bash mkdir myapp cd myapp bun init -y ``` Add Atlas: ```bash bun add @wess/atlas ``` Every package is a subpath export (`@wess/atlas/config`, `@wess/atlas/db`, …). To use the `@atlas/` spelling this guide uses, map it via `tsconfig.json` `paths` (bun reads tsconfig paths at runtime) — the full mapping lives in the README's Install section. Create `.env`: ``` DATABASE_URL="sqlite:./app.db" PORT=3000 SECRET="dev-secret-key-change-in-production" S3_ENDPOINT="http://localhost:9000" S3_BUCKET="files" S3_ACCESS_KEY="minioadmin" S3_SECRET_KEY="minioadmin" ``` ## Step 2: Define Schemas Create `src/schema.ts`: ```ts import { defineSchema, column } from "@atlas/db" export const users = defineSchema("users", { id: column.serial().primaryKey(), email: column.text().unique(), name: column.text(), passwordHash: column.text(), createdAt: column.timestamp().defaultRaw("CURRENT_TIMESTAMP"), }) export const uploads = defineSchema("uploads", { id: column.serial().primaryKey(), userId: column.integer().ref("users", "id"), filename: column.text(), key: column.text(), size: column.integer(), contentType: column.text(), createdAt: column.timestamp().defaultRaw("CURRENT_TIMESTAMP"), }) ``` ## Step 3: Set Up Config Create `src/config.ts`: ```ts import { defineConfig, env } from "@atlas/config" export const config = defineConfig({ database: env("DATABASE_URL"), port: env("PORT", { parse: Number, default: "3000" }), secret: env("SECRET"), s3: { endpoint: env("S3_ENDPOINT"), bucket: env("S3_BUCKET"), accessKey: env("S3_ACCESS_KEY"), secretKey: env("S3_SECRET_KEY"), }, }) ``` ## Step 4: Create Migrations You can hand-write migrations under `migrations/_/{up,down}.sql`, or — preferred — generate them from the `defineSchema()` you already wrote: ```ts // scripts/diff.ts import { connect } from "@atlas/db" import { migrate } from "@atlas/migrate" import { users, uploads } from "../src/schema" const db = connect({ driver: "sqlite", path: "./app.db" }) const result = await migrate.diff(db, [users, uploads], { name: "init" }) if (result.noop) console.log("schema in sync") else console.log(`wrote ${result.path}`) await db.close() ``` ```bash bun scripts/diff.ts # writes migrations/_init/up.sql + down.sql ``` `migrate.diff` introspects the live database and emits SQL for any new tables, added/removed columns, and (as `-- ALTER` comments) type/nullability mismatches. Re-running it after a schema edit produces an incremental migration. For reference, here's what an init migration ends up looking like: ```sql -- migrations/_init/up.sql (generated) CREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL, name TEXT NOT NULL, passwordHash TEXT NOT NULL, createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE uploads ( id INTEGER PRIMARY KEY, userId INTEGER NOT NULL, filename TEXT NOT NULL, key TEXT NOT NULL, size INTEGER NOT NULL, contentType TEXT NOT NULL, createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); ``` ## Step 5: Build the API Create `src/server.ts`: ```ts import { config } from "./config" import { users, uploads } from "./schema" import { connect, from, type RowOf } from "@atlas/db" import { migrate } from "@atlas/migrate" import { serve, pipeline, parseMultipart, json, badRequest, getR, postR, post, parseJson, } from "@atlas/server" import { token, signup, login, requireAuth } from "@atlas/auth" import { createStore, upload as uploadFile, presign } from "@atlas/storage" import { admin, model } from "@atlas/admin" // Connect to database const db = connect({ driver: "sqlite", path: "./app.db" }) // Run migrations await migrate.up(db, "./migrations") // Storage setup const store = createStore({ endpoint: config.s3.endpoint, bucket: config.s3.bucket, accessKey: config.s3.accessKey, secretKey: config.s3.secretKey, }) // Pipes that populate conn.assigns.auth (claims from the JWT). const authGuard = requireAuth({ secret: config.secret }) type AuthClaims = { auth: { id: number } } // Typed routes — c.assigns.auth.id is `number`, no casts. const meRoute = getR, never, Record, AuthClaims>( "/api/me", { before: [authGuard], assigns: {} as AuthClaims }, (c) => json(c, 200, { id: c.assigns.auth.id }), ) const filesRoute = getR, never, Record, AuthClaims>( "/api/files", { before: [authGuard], assigns: {} as AuthClaims }, async (c) => { type UploadRow = RowOf const rows: Pick[] = await db.all( from(uploads) .where((q) => q("userId").equals(c.assigns.auth.id)) .select("id", "filename", "key", "size", "createdAt"), ) return json(c, 200, rows) }, ) // Multipart upload still uses parseMultipart in `before` — typed body validators // expect JSON. `throw badRequest(...)` becomes a 400 with { error, code? }. const uploadRoute = post( "/api/upload", pipeline(authGuard, parseMultipart)(async (c) => { const userId = (c.assigns.auth as { id: number }).id const body = c.body as FormData const file = body.get("file") as File | null if (!file) throw badRequest("missing file", { code: "MISSING_FILE" }) const key = `uploads/${userId}/${file.name}` await uploadFile(store, { key, body: file, contentType: file.type }) const [created] = await db.execute( from(uploads) .insert({ userId, filename: file.name, key, size: file.size, contentType: file.type, }) .returning("id"), ) return json(c, 201, { id: created?.id, filename: file.name, url: presign(store, key, { expires: 3600 }), }) }), ) // Admin panel const adminPanel = admin({ db, basePath: "/admin", auth: { secret: config.secret }, models: [ model({ schema: users, searchFields: ["email", "name"], filterFields: ["createdAt"], }), model({ schema: uploads, searchFields: ["filename"], readOnly: true, }), ], }) // Routes serve({ port: config.port, hostname: "0.0.0.0", routes: [ // Auth post("/auth/signup", pipeline(parseJson)( signup({ db, table: "users", fields: ["email", "name", "password"], onSuccess: (c, user) => json(c, 201, { id: user.id, email: user.email, name: user.name, }), }) )), post("/auth/login", pipeline(parseJson)( login({ db, table: "users", identity: "email", password: "password", onSuccess: async (c, user) => json(c, 200, { token: await token.sign({ id: user.id }, config.secret), user: { id: user.id, email: user.email, name: user.name }, }), }) )), // Protected API meRoute, uploadRoute, filesRoute, // Admin ...adminPanel.mount([]), ], development: true, }) console.log(`Server running on http://localhost:${config.port}`) console.log(`Admin panel at http://localhost:${config.port}/admin`) ``` ## Step 6: Run the Server ```bash bun src/server.ts ``` You should see: ``` Server running on http://localhost:3000 Admin panel at http://localhost:3000/admin ``` ## Step 7: Test the API ### Sign up a user: ```bash curl -X POST http://localhost:3000/auth/signup \ -H "content-type: application/json" \ -d '{ "email": "user@example.com", "name": "John Doe", "password": "secure123" }' ``` Response: ```json { "id": 1, "email": "user@example.com", "name": "John Doe" } ``` ### Log in: ```bash curl -X POST http://localhost:3000/auth/login \ -H "content-type: application/json" \ -d '{ "email": "user@example.com", "password": "secure123" }' ``` Response: ```json { "token": "eyJhbGciOiJIUzI1NiJ9...", "user": { "id": 1, "email": "user@example.com", "name": "John Doe" } } ``` ### Get authenticated user info: ```bash curl -X GET http://localhost:3000/api/me \ -H "authorization: Bearer " ``` ### Upload a file: ```bash curl -X POST http://localhost:3000/api/upload \ -H "authorization: Bearer " \ -F "file=@/path/to/file.pdf" ``` ### List files: ```bash curl -X GET http://localhost:3000/api/files \ -H "authorization: Bearer " ``` ## Step 8: Access the Admin Panel Open http://localhost:3000/admin in your browser. You'll see: - **Users** list with email and name search - **Uploads** list (read-only) showing all file uploads - Full CRUD for users (create, edit, delete) - Filters, bulk actions, custom query builder ## Next Steps ### Add Frontend Create a React frontend using `@atlas/ui` blocks: ```tsx // frontend.tsx import React from "react" import { createRoot } from "react-dom/client" import { AtlasProvider, AppShell } from "@atlas/ui/provider" import { LoginPage } from "@atlas/ui/auth" import { FileUpload } from "@atlas/ui/storage" export default function App() { const [token, setToken] = React.useState(null) if (!token) { return ( { const res = await fetch("/auth/login", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, password }), }) const data = await res.json() if (data.token) { setToken(data.token) return {} } return { error: "Login failed" } }} /> ) } return ( { const form = new FormData() form.append("file", file) await fetch("/api/upload", { method: "POST", headers: { authorization: `Bearer ${token}` }, body: form, }) }} /> ) } const root = createRoot(document.body) root.render() ``` ### Add Caching ```ts import { createCache, cached } from "@atlas/cache" const cache = createCache({ url: process.env.REDIS_URL || "redis://localhost" }) const getUser = cached(cache, "user", async (id: number) => { return await db.one(from(users).where(q => q("id").equals(id))) }, { ttl: 600 }) const user = await getUser(1) // cached for 10 minutes ``` ### Use Postgres in Production ```ts const db = connect({ driver: "postgres", url: config.database, pool: 10, }) ``` Update migrations path as needed. Everything else stays the same. ### Add AI ```ts import { createProvider } from "@atlas/ai" const openai = createProvider({ provider: "openai", key: process.env.OPENAI_API_KEY! }) const reply = await openai.chat({ messages: [{ role: "user", content: "Summarize this document" }], }) // Streaming for await (const chunk of openai.chatStream({ messages: [{ role: "user", content: "Stream me" }] })) { if (chunk.type === "text") process.stdout.write(chunk.content ?? "") } ``` Add the AI UI block to your frontend: ```tsx import { ChatWindow } from "@atlas/ui/ai" sendToApi(text)} /> ``` ### Add Social Login Drop "Sign in with Google / GitHub / etc." onto the existing password flow. PKCE + state ride in a signed HttpOnly cookie, so the flow stays stateless — no extra schema, no session table. ```ts import { socialAuth, google, github, tiktok } from "@atlas/auth/social" import { token } from "@atlas/auth" import { get, post, redirect, putHeader, parseForm, pipeline } from "@atlas/server" const origin = `http://localhost:${config.port}` const social = socialAuth({ secret: process.env.OAUTH_STATE_SECRET!, cookie: { secure: process.env.NODE_ENV === "production" }, providers: { google: google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, redirectUri: `${origin}/auth/google/callback`, }), github: github({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, redirectUri: `${origin}/auth/github/callback`, }), tiktok: tiktok({ clientKey: process.env.TIKTOK_CLIENT_KEY!, clientSecret: process.env.TIKTOK_CLIENT_SECRET!, redirectUri: `${origin}/auth/tiktok/callback`, }), // Add apple/microsoft/facebook/twitter the same way. }, }) const onSocialSuccess = async (c, { profile }) => { // Upsert by (provider, providerId); see docs/cookbook.md for the full pattern. const user = await upsertUserFromProfile(profile) const jwt = await token.sign({ id: user.id }, config.secret, { expiresIn: 86400 }) return redirect(putHeader(c, "set-cookie", `session=${jwt}; HttpOnly; Path=/`), "/") } const socialRoutes = [ get("/auth/google", social.start("google")), get("/auth/google/callback", social.callback("google", { onSuccess: onSocialSuccess })), get("/auth/github", social.start("github")), get("/auth/github/callback", social.callback("github", { onSuccess: onSocialSuccess })), get("/auth/tiktok", social.start("tiktok")), get("/auth/tiktok/callback", social.callback("tiktok", { onSuccess: onSocialSuccess })), // Apple delivers form_post → use POST + parseForm: // post("/auth/apple/callback", pipeline(parseForm)(social.callback("apple", { onSuccess: onSocialSuccess }))), ] ``` Slot `...socialRoutes` into your `serve({ routes: [...] })`. The full provider matrix and Apple-specific notes live in `docs/cookbook.md`. ### Add Share Buttons ```ts import { share, shareUrl, shareEmail } from "@atlas/share" import { createEmailer } from "@atlas/email" const content = { url: "https://example.com/post/123", title: "Look at this" } // One channel: shareUrl("twitter", { ...content, hashtags: ["atlas"] }) // All eight channels at once — render however your UI prefers: share(content) // → { twitter, facebook, linkedin, reddit, whatsapp, telegram, sms, email } // Server-side share-by-email (reuses your @atlas/email transport): const emailer = createEmailer({ apiKey: process.env.RESEND_API_KEY, from: process.env.RESEND_FROM }) await shareEmail({ emailer, to: "friend@example.com", sharerName: "Wess", content }) ``` ### Add External API Calls ```ts import { github } from "@atlas/request/providers" const gh = github({ token: process.env.GITHUB_TOKEN! }) const repos = await (await gh.get("/user/repos")).json() ``` ### Add MCP Debugging ```ts import { collectTools, createContext, createMcpServer } from "@atlas/mcp" const ctx = createContext({ db, routes: myRoutes, config }) const mcp = createMcpServer(collectTools(ctx), ctx) mcp.start() ``` Or launch via the CLI: `atlas mcp` ## Templates Scaffold a complete project with `atlas init`: ```bash atlas init -n myapp --template