# Radiant Framework Reference (llms.txt) This document is a comprehensive prompt reference for LLMs working with the Radiant Framework. It contains all the context, rules, syntax, and APIs needed to scaffold projects, write DSL schemas, build TypeScript backend logic, configure plugins, and manage real-time functionality. *** ## 1. Overview and Core Philosophy Radiant is a **schema-first backend framework**. It uses a custom declarative Domain-Specific Language (DSL) written in `.radiant` files to define your data models, access controls, and infrastructure configurations. - **Language-Agnostic Core:** The DSL compiles into a Universal Abstract Syntax Tree (AST), which then lowers into a native target runtime (currently Bun, with Go and Python planned). - **Source of Truth:** `.radiant` files are the canonical definition. The database schema, TypeScript types, and CRUD endpoints are derived automatically. - **No Migration Files:** Changes are synced directly to the database by diffing the compiled AST against the database schema using the CLI. - **Out-of-the-box Features:** Auto-generated REST endpoints, Authentication (JWT, session, API key), Access Control (TS rules), Realtime subscriptions (WS/SSE), Rate Limiting, OpenAPI specs, and Admin UI generation. ### Example DSL Syntax ```radiant // config.radiant config { core: { api: { prefix: "/api" } } security: { auth: { strategies: ["jwt"] } } } // collections.radiant collection users { auth: true fields: { name: text email: email @unique password: password role: text @default("user") } } ``` ## 2. Installation and Getting Started Radiant is distributed as a fast, standalone binary with no Node.js pre-requisites for the CLI itself. **Installation:** - **macOS / Linux:** `curl -fsSL https://radiant.dev/install | bash` - **Windows:** `irm https://radiant.dev/install.ps1 | iex` **Initialization:** ```bash # Initialize a new project interactively or with a specified directory radiant init [--dir my-app] cd my-app ``` **Development:** ```bash # Start the schema compiler watcher (recompiles DSL on change) radiant dev # In a separate terminal, run the generated Bun application bun run --hot src/index.ts ``` ## 3. Project Structure A Radiant application enforces a strict separation between the declarative schema (`radiant/`), the runtime business logic (`src/`), and generated artifacts. ```text my-app/ ├── radiant/ # Source of Truth: Your DSL files │ ├── config.radiant # Framework configuration │ ├── collections.radiant # Can be split into users.radiant, posts.radiant, etc. │ ├── schema.json # (DO NOT EDIT) Compiled schema │ ├── gen.ts # (DO NOT EDIT) Runtime entry point │ └── radiant-sdk.ts # (DO NOT EDIT) Generated client SDK ├── src/ # Your TypeScript Application Code │ ├── app.ts # App initialization (adapter, plugins) │ ├── index.ts # Server entry point │ ├── access.ts # TS Access control rules (read, create, update, delete) │ ├── hooks.ts # TS Lifecycle hooks (beforeCreate, afterUpdate, etc.) │ └── custom-routes.ts # Custom TS endpoints beyond auto-generated CRUD ├── public/ # Static assets ├── radiant-types.ts # (DO NOT EDIT) Generated TS interfaces for collections ├── .env # Environment variables (DATABASE_URL, JWT_SECRET, etc.) ├── package.json └── tsconfig.json ``` ## 4. CLI Reference The CLI commands drive the code generation and database sync workflows. Inside a project, it can also be accessed via `npx radiant` or `bunx radiant`. | Command | Description | Common Flags | |---|---|---| | `radiant init` | Scaffolds a new Radiant project. | `-d, --dir ` | | `radiant generate` | Compiles `.radiant` files into `schema.json`, `gen.ts`, and `radiant-types.ts`. Validates cross-file relationships. Exits with code 1 on parsing/semantic errors. | `-r, --runtime `, `-d, --dir ` | | `radiant dev` | Watches the `radiant/` directory and recompiles the schema automatically on save. Compilation errors are printed but do not exit the process. | `-r, --runtime `, `-d, --dir ` | | `radiant db:sync` | Diffs `schema.json` against the database (via `DATABASE_URL`). Adds tables/columns automatically. Skips drops unless forced. | `--force` (Drop orphaned columns/tables) | | `radiant lsp` | Starts the Language Server Protocol (usually launched via editor). | | **Database Adapters (`radiant db:sync`)** The `db:sync` command identifies the database dialect using the `DATABASE_URL` in `.env`: - `file:` / `sqlite:` → SQLite - `postgres:` / `postgresql:` → PostgreSQL - `redis:` → Redis - `http:` / `https:` → SurrealDB ## 5. Environment Variables (`env()`) You can reference environment variables directly within `.radiant` files using the `env("VAR_NAME", "fallback_value")` function. ```radiant config { security: { auth: { jwt: { accessTokenExpiry: env("JWT_EXPIRY", "15m") } } cors: { origin: env("CORS_ORIGINS", "http://localhost:3000") } } } ``` - **Arguments:** First argument is the string literal name of the env variable. Second (optional) is a fallback default (string, number, or boolean). - **Compilation:** It compiles into a `$env` object inside `schema.json`. At runtime, the server resolves it from `process.env`. - Standard `.env` setups include `DATABASE_URL`, `JWT_SECRET`, and provider credentials. ## 6. Editor Support and Language Server (LSP) The `vscode-radiant` extension provides first-class support for `.radiant` files, launching `radiant lsp` automatically. - **Diagnostics:** Surfaces lexing, parsing, and semantic compilation errors (e.g., duplicated collections, invalid relationship targets). Scans across the entire workspace for cross-file validation. - **Autocompletion:** Context-aware suggestions for structural keywords (`config`, `collection`), field types (`text`, `email`, `relationship`, etc.), and keys inside nested blocks. - **Decorators:** Completions are provided when typing `@`: - `@unique`, `@optional`, `@hidden`, `@index` - `@default(value)` - **Formatting:** Formats on save to standard 2-space indentation, preserving line comments and fixing nested object syntaxes. Breaks in code prevent formatting from triggering. *** # Radiant DSL LLM Reference Guide The Radiant DSL is a declarative language for defining backend schemas and configurations. ## 1. File Conventions - **Location:** All DSL files must use the `.radiant` extension and reside in the `radiant/` directory (or its subdirectories). - **Merging:** The compiler automatically discovers and merges all `.radiant` files together. - **Top-Level Blocks:** A file can contain zero or more top-level blocks in any order: - `config { ... }` (Appears exactly once across all files) - `collection { ... }` (For data tables/documents) - `global { ... }` (For singletons, like site settings) ## 2. Syntax & Value Types Properties are written as `key: value` pairs. Comments start with `//`. | Value Type | Syntax | Example | |---|---|---| | **String** | `"..."` | `"hello world"` | | **Number** | integer/decimal | `42`, `-3.14` | | **Boolean** | `true` / `false` | `true` | | **Identifier** | bare word | `text`, `boolean` | | **Function Call** | `name(args)` | `relationship("users")`, `select("a", "b")` | | **Array** | `[...]` | `["jwt", "session"]` (or add `[]` suffix to types: `text[]`) | | **Object** | `{ key: value }` | `{ enabled: true, path: "/health" }` | | **Environment** | `env("VAR", default)` | `env("JWT_EXPIRY", "15m")` | ## 3. The `config` Block Defines global framework settings. Allowed top-level keys inside `config {}`: - **`core`**: Contains `api` (`prefix`, `maxBodyBytes`, `trustedProxies`), `openapi` (`enabled`, `path`), and `upload` (`maxFileSize`, `allowedTypes`). - **`security`**: Contains `auth` (strategies like `"jwt"`, `"session"`, cookie settings, password policy, lockout), `cors` (`origin`, `credentials`), `rateLimit` (`write`, `login`), `headers` (enables security headers), `secrets`, `audit` (tamper-proof logs), and `csrfTrustedOrigins`. - **`monitoring`**: Contains `enabled`, `apiKey`, `healthCheck`, and `requestId`. - **`adminUI`**: Contains `enabled` and `user` (points to the slug of the auth collection). - **`apiPrefix`**: A shorthand for `core.api.prefix` (e.g., `"/api"`). - **`migrate`**: Database migration config (e.g., `dropOrphan: true`). - **`output`**: Directory for generated TS types, relative to `radiant/`. **Example:** ```radiant config { apiPrefix: "/api" security: { auth: { strategies: ["jwt"] jwt: { accessTokenExpiry: env("JWT_EXPIRY", "15m") } } cors: { origin: ["http://localhost:3000"] } } } ``` ## 4. `collection` Blocks Defines a data model, which automatically generates a database table and full REST CRUD endpoints (GET, POST, PUT, DELETE). - **`fields`**: (Required) Object block defining the schema. - **`auth`**: `true` marks the collection as the authentication collection (adds login/registration/refresh routes, and auto-hashes the `password` field). Only one collection can be `auth: true`. - **`realtime`**: Configures subscriptions (`ws: ["create", "update", "delete"]`, `sse: true`, `durableStream: false`). - **`cache`**: Configures per-collection cache (`ttl`, `strategy`). - **`hooks`**: Boolean flags referencing TypeScript hooks (e.g., `beforeCreate: true`). - **`admin`**: Dashboard settings (`list`, `searchable`, `pageSize`). **Example:** ```radiant collection users { auth: true fields: { name: text email: email @unique password: password } } ``` ## 5. `global` Blocks Defines a singleton document (e.g., site-wide settings). Globals have exactly one record, but share the same property capabilities as `collection` (`fields`, `hooks`, `cache`, `admin`). **Example:** ```radiant global siteSettings { fields: { maintenanceMode: boolean @default(false) theme: select("light", "dark", "auto") @default("auto") } } ``` ## 6. Field Types Fields map cleanly to database types and TypeScript types. Auto-generated fields (`id`, `createdAt`, `updatedAt`) exist automatically and should **not** be declared. | Category | Type Name | Description | |---|---|---| | **Scalars** | `text`, `textarea`, `richtext`, `email`, `password`, `boolean`, `integer`, `number`, `date` | Standard primitives. `password` auto-hashes. `email` validates formatting. `date` stores ISO 8601 strings. | | **Selection** | `select("a", "b")`, `multiselect("a", "b")`, or `["a", "b"]` (shorthand array) | Enumerated choices. Must have at least 1 option. | | **Relationships**| `relationship("slug")` | FK reference to another collection. Fails compilation if the target slug doesn't exist. Add `[]` for arrays: `relationship("tags")[]` | | **Complex** | `json`, `upload`, `array(type)` | `upload` handles file metadata (size, MIME, path). | | **Nested** | `{ key: type }` | You can define nested blocks directly within fields. Stored as `JSONB`. | ## 7. Decorators Decorators provide metadata annotations. They start with `@` and multiple decorators can be chained (e.g., `@unique @optional @index`). Order does not matter. | Decorator | Effect | Example | |---|---|---| | **`@unique`** | Enforces database uniqueness constraint. | `email: email @unique` | | **`@optional`** | Marks the field as nullable (TS `?`). Defaults are required. | `bio: text @optional` | | **`@default(val)`**| Sets a fallback value on creation. Accepts strings, numbers, booleans. | `role: text @default("user")` | | **`@hidden`** | Strips the field from API responses (still accessible in DB/hooks). | `internalNotes: text @hidden` | | **`@index`** | Creates a DB index for faster sorting/filtering. | `status: select("a", "b") @index` | *** # Radiant TS Backend Capabilities ## 1. REST API Radiant automatically generates a full suite of CRUD HTTP endpoints for every collection defined in your schema, mounted under a configurable API prefix (e.g., `/api/`). * **Features**: Out-of-the-box support for filtering (using a `where` JSON parameter with operators like `eq`, `like`, `in`), sorting (`sort`), pagination (`limit`, `page`), and relationship population (`depth`). * **Auth & System**: Generates authentication endpoints (login, register, refresh, password reset) if the collection has `auth: true`. Provides global singletons endpoints, and system endpoints for file uploads, WebSockets/SSE, and interactive OpenAPI docs (Scalar) at `//docs`. * **Interaction**: No TypeScript code is required to generate these. Developers and clients interact with them via standard HTTP requests and JWT Bearer tokens. ## 2. Custom Endpoints When auto-generated routes aren't enough (e.g., webhooks, custom aggregations), developers can define custom routes using the `app.router` instance in `src/custom-routes.ts`. * **Routing**: Supports standard HTTP methods: `app.router.get()`, `.post()`, `.patch()`, etc. * **Context (`RadiantRouteContext`)**: Handlers receive a context object containing the parsed `url`, `params`, `query`, `body`, authenticated `user`, and the `radiant` runtime instance for database access. * **Schema Validation**: Uses TypeBox (`t`) to validate `body`, `query`, `params`, and `response`. Invalid inputs automatically return a `400 Bad Request`. Validated routes are added to the OpenAPI spec. * **Interaction**: ```typescript app.router.post("/transfer", (ctx) => { return { success: true, amount: ctx.body.amount }; }, { body: t.Object({ amount: t.Number() }), authRequired: true // Rejects unauthenticated requests with 401 }); ``` ## 3. Local API The Local API provides programmatic, direct access to the database adapter without routing through HTTP. It is accessible anywhere the `app` or runtime instance is available (e.g., inside hooks, custom routes, and access rules). * **Methods**: `find()`, `findById()`, `create()`, `update()`, `delete()`, and `count()`. * **Querying**: Mirrors the REST API query structure (using `where` clauses, `sort`, `limit`, `depth` for relations). * **Interaction**: ```typescript const result = await ctx.radiant.find("todos", { where: { completed: { eq: false } }, limit: 10 }); ``` ## 4. Hooks Hooks are lifecycle middleware that execute before or after CRUD operations to a collection, allowing developers to inject business logic. They are registered in `src/hooks.ts`. * **Types**: * `beforeCreate`, `beforeUpdate`: Can modify and return the data before it is saved to the database. Throwing an error aborts the transaction. * `afterCreate`, `afterUpdate`, `afterDelete`: Used strictly for side effects (e.g., sending emails, cache invalidation, webhooks). * **Context (`HookContext`)**: Provides access to the `data` being modified, the `user` making the request, the HTTP `request`, and the `radiant` Local API. * **Interaction**: ```typescript app.hooks("todos", { beforeCreate: async (ctx) => { ctx.data.author = ctx.user?.id; // Mutate data return ctx.data; }, afterCreate: async (ctx) => { console.log("Created:", ctx.data.id); // Side-effects } }); ``` ## 5. Access Control Access control defines who is authorized to perform operations on a collection. Rules are evaluated per-request and defined in `src/access.ts`. If no rules are set, a collection is open by default. * **Rules**: `read`, `create`, `update`, and `delete`. * **Logic**: A rule is a function returning a `boolean` or `Promise`. Returning `false` yields a `403 Forbidden`. * **Context (`RadiantRequestContext`)**: Provides the authenticated `user` (with decoded JWT payload), the HTTP `request`, and the `radiant` Local API for database-driven permission checks. * **Interaction**: ```typescript app.access("posts", { read: () => true, // Public read update: (ctx) => ctx.user?.id === ctx.data.author || ctx.user?.role === "admin" }); ``` ## 6. Security Radiant includes a comprehensive and highly configurable security subsystem: * **Authentication**: Supports JWT (with rotation/refresh tokens), API Keys, and MFA/TOTP (with enrollment flows and backup codes). Also supports password policies and account lockouts. * **Encryption at Rest**: By adding the `@encrypt` decorator to a schema field, Radiant transparently applies AES-256-GCM field-level encryption on write and decrypts on read. * **Audit Logging**: Opt-in, tamper-evident (HMAC-SHA256 chained) audit logs record all `create`, `update`, and `delete` mutations with the associated user and request IDs. * **Rate Limiting & Idempotency**: Configurable endpoint rate limiting (using KV stores). Clients can pass an `Idempotency-Key` header to safely retry requests without duplicating side effects. * **Additional Defenses**: Built-in CSRF protection, configurable Security Headers (CSP, HSTS), Webhook HMAC signing tools (`signWebhook`), and Secret Provider abstractions (to pull from Vault/KMS instead of `.env`). SBOM generation and dependency vulnerability audits are natively supported via the CLI (`bun run audit` / `bun run sbom`). *** # Radiant Plugins and Client SDKs Summary ## Core Plugins Architecture - **Purpose**: Plugins extend the Radiant runtime by tapping into custom lifecycle hooks. They can override core behaviour (like storage), add global middleware (like request logging or auth guards), or integrate external services. - **Hooks**: `onInit`, `beforeRequest`, `afterRequest`, and `onError`. - **Registration**: Plugins run in the order they're registered. You can pass them in the `createRadiant()` config or push them dynamically onto `app.plugins`. ## Database Plugins Configuration Radiant supports multiple database engines via adapter plugins. Each plugin implements the `RadiantAdapter` interface. ### PostgreSQL A production-grade relational database optimized for high concurrency. It fully leverages native `JSONB` for deep object queries and handles connection pooling. - **Installation**: `bun add @codesordinatestudio/radiant-plugin-postgres` - **Configuration**: ```typescript import { postgres } from "@codesordinatestudio/radiant-plugin-postgres"; export const app = createRadiant({ adapter: postgres({ url: process.env.DATABASE_URL!, // e.g. postgresql://user:pass@localhost:5432/mydb pool: { max: 10 }, // optional connection pool size pgBouncer: false, // set to true if using PgBouncer }), }); ``` ### SQLite A zero-config, file-based database leveraging Bun's highly performant native driver (`bun:sqlite`). Ideal for development and small deployments. - **Installation**: `bun add @codesordinatestudio/radiant-plugin-sqlite` - **Configuration**: ```typescript import { sqlite } from "@codesordinatestudio/radiant-plugin-sqlite"; export const app = createRadiant({ adapter: sqlite({ url: process.env.DATABASE_URL || "file:./radiant.sqlite", }), }); ``` ### SurrealDB A multi-model database that unlocks advanced graph, document, and relational database features. It integrates fully with Radiant's query builder. - **Installation**: `bun add @codesordinatestudio/radiant-plugin-surrealdb` - **Configuration**: ```typescript import { surrealdb } from "@codesordinatestudio/radiant-plugin-surrealdb"; export const app = createRadiant({ adapter: surrealdb({ url: process.env.SURREAL_URL!, // e.g. http://localhost:8000 namespace: process.env.SURREAL_NS || "test", database: process.env.SURREAL_DB || "test", auth: { username: process.env.SURREAL_USER || "root", password: process.env.SURREAL_PASS || "root", } }), }); ``` ## Using the RPC Client SDK Radiant provides a fully-typed frontend RPC client plugin to consume your API dynamically, inferring backend schema types for end-to-end type safety in your browser applications. - **Installation**: `bun add @codesordinatestudio/radiant-plugin-rpc` - **Usage**: 1. **Initialize the client**: Import the generated `Collections` type from your backend and pass it to `createRpc`. ```typescript import { createRpc } from "@codesordinatestudio/radiant-plugin-rpc"; import type { Collections } from "../backend/src/radiant"; export const api = createRpc("http://localhost:3000/api", { headers: token ? { Authorization: `Bearer ${token}` } : {}, credentials: "include", }); ``` 2. **CRUD Operations**: Use `.find()`, `.create()`, `.update()`, etc. ```typescript await api.todos.find({ query: { status: "active" } }); await api.todos.create({ title: "Finish the RPC plugin", completed: false }); ``` 3. **File Uploads**: Pass a `File` object directly in your request payload. The client automatically converts the payload to `multipart/form-data`. 4. **Real-time Subscriptions**: - Server-Sent Events (SSE): `const sse = api.todos.sse();` - WebSockets: `const ws = api.messages.ws({ query: { room: "general" } });` ## Zero-Dependency Client SDK (`radiant-sdk.ts`) As an alternative to the RPC plugin, Radiant also generates a standalone, zero-dependency Client SDK alongside your backend on compilation. - Provides end-to-end type safety using native HTTP verbs (`.get()`, `.post()`, `.patch()`, `.delete()`). - **Authentication**: Automatically centralized under `.auth` namespace (e.g., `api.auth.login`). - **File Uploads**: Native understanding of `File`/`Blob` objects. - **Subscriptions**: Cleanly typed `.subscribeWS()` and `.subscribeSSE()` methods. - **Custom Streams**: Seamlessly returns an `AsyncGenerator` for streaming custom endpoints to iterate via `for await`. - **SSR / Loaders Support**: Provides the `api.withToken(token)` method to create an isolated clone bound to a specific user's token for server-side requests. *** # Radiant Real-Time & Streaming Capabilities Radiant provides four primary mechanisms for real-time data and streaming: Server-Sent Events (SSE), WebSockets, HTTP Readable Streams, and Durable Streams. ## 1. Server-Sent Events (SSE) SSE provides a unidirectional server-to-client push over HTTP. It is simpler than WebSockets, requiring no custom protocol, and works easily through proxies. **How it works:** When enabled on a collection, Radiant automatically mounts a `GET /api/sse` endpoint and broadcasts CRUD events to subscribed clients. **Configuration:** Enable it in your `.radiant` DSL file for specific CRUD operations: ```radiant collection todos { realtime: { sse: ["create", "update", "delete"] // true = all events } fields: { title: text } } ``` **Client-Side Code:** Use the native `EventSource` API and subscribe to channels via query parameters (`channel=todos` or `channels=todos,posts`): ```javascript const es = new EventSource("http://localhost:3000/api/sse?channel=todos"); es.onmessage = (event) => { const data = JSON.parse(event.data); // e.g. {"event":"todos:created","data":{"id":"...","title":"..."}} console.log(data); }; ``` **Server-Side Code:** You can programmatically manage SSE and broadcast messages using `RadiantSSE`: ```typescript import { RadiantSSE } from "@codesordinatestudio/radiant-bun"; RadiantSSE.broadcastToChannel("todos", { event: "custom", data: "..." }); RadiantSSE.broadcastAll({ type: "announcement" }); RadiantSSE.sendTo(connectionId, { event: "private", data: "..." }); ``` ## 2. WebSockets WebSockets provide bidirectional, persistent connections featuring room-based pub/sub architecture. **How it works:** When enabled, Radiant mounts a `GET /api/ws` endpoint. Clients can connect, join specific rooms (collections), and receive CRUD events or custom broadcasts. **Configuration:** Enable it in your `.radiant` DSL file: ```radiant collection todos { realtime: { ws: ["create", "update", "delete"] // true = all events } // ... } ``` **Client-Side Code:** Connect via the native `WebSocket` API, explicitly join/leave rooms, and respond to server pings for connection health: ```javascript const ws = new WebSocket("ws://localhost:3000/api/ws"); ws.onmessage = (event) => { const msg = JSON.parse(event.data); // Respond to Heartbeat if (msg.type === "ping") { ws.send(JSON.stringify({ type: "pong" })); } }; // Join or leave a room ws.send(JSON.stringify({ type: "join", room: "todos" })); ``` **Server-Side Code:** Manage WebSockets using `RadiantWebsocket`: ```typescript import { RadiantWebsocket } from "@codesordinatestudio/radiant-bun"; RadiantWebsocket.broadcastToRoom("todos", payload, { exclude: [senderId] }); RadiantWebsocket.sendTo(connectionId, payload); RadiantWebsocket.broadcastAll(payload); ``` ## 3. HTTP Readable Streams HTTP streams allow for progressive data streaming directly over an HTTP connection, ideal for use cases like LLM text generation. **How it works:** The connection stays open while the server iterates and writes chunks to the response body using the native Web Streams API. It can also automatically format chunks as SSE standard messages. **Configuration & Server-Side Code:** Inside custom routes, use the `ctx.stream()` utility. To automatically tag the route for OpenAPI/SDK generation as an event stream, pass `{ stream: true }` to the route options: ```typescript app.router.get("/ai-chat", async (ctx) => { // Pass an 'event' option to auto-format as SSE await ctx.stream({ status: "thinking" }, { event: "connection" }); for (const token of llm.generate()) { await ctx.stream({ token }, { event: "message" }); } // Returning closes the stream }, { stream: true }); ``` Alternatively, for lower-level control with iterables or AsyncGenerators: ```typescript import { stream } from "@codesordinatestudio/radiant-bun"; app.router.get("/raw-stream", (ctx) => stream(myAsyncGenerator())); ``` ## 4. Durable Streams Unlike SSE and WebSockets which are ephemeral, Durable Streams persist change events in a log, allowing clients to replay them after disconnects. **How it works:** Radiant logs events to a stream store. By default (in development), it uses an in-memory ring buffer (up to 1000 events). For production, it relies on an external sync layer (like ElectricSQL/Redis) using a plugin. **Configuration:** Enable it in the `.radiant` DSL file: ```radiant collection todos { realtime: { durableStream: true } // ... } ``` For production, configure the plugin in your Radiant setup (`radiant.config.ts` or initialization): ```bash bun add @codesordinatestudio/radiant-plugin-durable-streams ``` ```typescript import { createRadiant } from "../radiant/gen"; import { durableStreams } from "@codesordinatestudio/radiant-plugin-durable-streams"; export const app = createRadiant({ streamStore: durableStreams({ url: process.env.DURABLE_STREAMS_URL!, }), }); ``` **Client-Side Code:** Clients fetch the `/api//stream` endpoint, optionally passing a cursor (`lastEventId`) to resume from: ```bash # Get all events GET /api/todos/stream # Replay events after a specific cursor GET /api/todos/stream?lastEventId=1234567890-abcde ``` **Server-Side Code:** You can read the stream history programmatically: ```typescript // Read all events or read after cursor const events = await app.streamStore.read("todos", "last-event-id"); ``` *** # Radiant Utility Features Reference ## 1. Email (`app.mailer`) - **Overview**: Built-in templated email system for common auth flows (welcome, password reset, verification). - **Configuration**: Configured in `createRadiant()` under the `email` object. Supports `nodemailer` (SMTP) and `resend` (API) transport plugins. ```typescript import { nodemailerEmail } from "@codesordinatestudio/radiant-plugin-nodemailer"; export const app = createRadiant({ // ... email: { from: "no-reply@myapp.com", appName: "My App", transport: nodemailerEmail({ host: process.env.SMTP_HOST, auth: { user, pass } }), // Optional: override templates templates: { welcome: ({ to, appName }) => ({ subject, html, text }) } } }); ``` - **TS API**: - Low-level send: `await app.mailer.send({ to, subject, html, text })` - Built-in templates: `sendWelcome()`, `sendForgotPassword()`, `sendPasswordResetSuccess()`, `sendVerificationEmail()` - Auth collections with `auth: true` use these templates automatically. ## 2. File Storage - **Overview**: Pluggable storage for file uploads. Defaults to `LocalStorageProvider` (saves to disk `uploads/`). S3 plugin available for production. - **Configuration**: Configured in `createRadiant()` under `storage`. ```typescript import { s3Storage } from "@codesordinatestudio/radiant-plugin-s3"; export const app = createRadiant({ // ... storage: s3Storage({ bucket, region, accessKeyId, secretAccessKey }), }); ``` - **TS API**: - Endpoints automatically generated: `POST /api/upload` (multipart) and `GET /api/uploads/:filename`. - Providers implement `StorageProvider` interface: `saveFile(file)` and `deleteFile(filename)`. - In Schema DSL, fields of type `upload` store metadata returned by the upload endpoint. ## 3. KV Store (`RadiantKV`) - **Overview**: Async key-value facade that proxies to `sqlite` (default), `memory`, or `redis` drivers. - **Configuration**: ```typescript import { RadiantKV } from "@codesordinatestudio/radiant"; const kv = new RadiantKV({ driver: "redis", redis: { url: process.env.REDIS_URL } }); ``` - **TS API**: - Basic: `await kv.set(key, val, ttl)`, `await kv.get(key)`, `kv.has(key)`, `kv.delete(key)` - Batching: `kv.setMany(items)`, `kv.getMany(keys)`, `kv.deleteMany(keys)` - Utils: `kv.keys(prefix)`, `kv.count()`, `kv.clear()`, `kv.cleanup()` (forces TTL cleanup) ## 4. Queue Manager - **Overview**: BullMQ-backed background job processor (requires Redis). Replaces `Bun.cron` with repeatable jobs when initialized. - **Configuration**: ```typescript import { RadiantQueueManager } from "@codesordinatestudio/radiant-bun"; RadiantQueueManager.initialize({ bullmq: { connection: { host, port } } }); ``` - **TS API**: - Get instance: `const qm = RadiantQueueManager.getInstance();` - Jobs: `await qm.addJob(queue, jobName, data, opts)` or `qm.addBulk(queue, jobs)` - Workers: `qm.registerWorker(queue, async (job) => { ... })` - Teardown: `await qm.close()` handles graceful shutdown of queues and workers. ## 5. Logger - **Overview**: Pino-based structured logger with auto-formatting for dev/prod. Integrates with Radiant KV to persist error logs. - **Configuration**: - `LUCENT_LOG_LEVEL`: (`trace`, `debug`, `info`, `warn`, `error`, `fatal`). - `LUCENT_ERROR_LOG_KV=true`: Persists `.error()` logs to SQLite KV store. - **TS API**: - Basic: `import { logger } from "@codesordinatestudio/radiant"; logger.info("...");` - Child loggers: `import { createLogger } from "..."; const dbLogger = createLogger("database");` - Access saved errors: `await RadiantErrorLogs.get({ limit: 100 })`, `count()`, `clear()`. ## 6. Monitoring - **Overview**: Buffers runtime events, metrics, health, and traces. Emits via HTTP or configurable exporters. - **Configuration** (in `config.radiant`): ```radiant config { monitoring: { enabled: true, apiKey: env("API_KEY"), healthCheck: { enabled: true, path: "/health" } } } ``` - **TS Exporters API**: - `app.monitoring?.addExporter({ name, kind, batchSize, flushIntervalMs, filter, export, onError })` - Provides a built-in `OpenTelemetryExporter` for OTLP pushing. - **HTTP API**: - `/api/monitor/events` (JSON events with filters) - `/api/monitor/metrics` (Aggregate stats) - `/api/monitor/metrics/prometheus` (Prometheus text format) - `/api/monitor/stream` (SSE live stream) - `/health` (Database and cache health check) ## 7. Database Sync - **Overview**: CLI command to diff compiled `schema.json` against a live database and apply changes without migration scripts. - **Usage**: - `radiant db:sync`: Safe mode (default). Introspects DB and applies **additive** changes only (creates missing tables/columns). - `radiant db:sync --force`: Applies **destructive** changes (drops orphaned tables/columns). Required to drop anything in production. - **Details**: - Adapter is auto-selected based on `DATABASE_URL` (`sqlite:`, `postgres:`, `http:` for SurrealDB). - Preserves Radiant system tables (`radiant_migrations`, `radiant_refresh_tokens`, `radiant_audit_log`). ## 8. Deployment - **Overview**: Production build and environment guardrails. - **Build**: `bun build src/index.ts --outdir dist --target bun` - **Environment Variables**: Requires `NODE_ENV=production`, `DATABASE_URL`, and `JWT_SECRET`. - **Production Guardrails**: - **Cache**: Will fail to start if using an in-memory cache; requires a Redis-backed KV cache. - **Rate Limiter**: Should be configured with Redis KV to support multiple instances. - **Schema Sync**: Auto-sync at startup (`syncDatabaseSchema`) is additive-only in production, ignoring drop directives in DSL. - **Graceful Shutdown**: Automatically registers a SIGTERM handler that stops requests, drains in-flight requests (using `drainTimeoutMs`), stops cron/jobs, and shuts down safely. *** # Radiant Plugins Summary: Storage, Email, and Streams ## S3 Storage Plugin **Package:** `@codesordinatestudio/radiant-plugin-s3` The S3 Plugin provides an S3-compatible storage adapter for Radiant, leveraging Bun's native `Bun.S3Client` for maximum performance. It overrides Radiant's default local file storage mechanism, seamlessly routing all incoming file uploads (via `.radiant` schema collections using the `file` or `image` type, or `/upload` API calls) directly to AWS S3 or any S3-compatible API like Cloudflare R2, MinIO, or DigitalOcean Spaces. It automatically rewrites file URLs in the database to point to the S3 or custom CDN domain. ### Configuration & Usage ```typescript import { createRadiant /* or radiant */ } from "@codesordinatestudio/radiant-bun"; import { s3Storage } from "@codesordinatestudio/radiant-plugin-s3"; export const app = createRadiant({ // Either in plugins array plugins: [ s3Storage({ bucket: "my-bucket", region: "us-east-1", accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, // Optional settings for S3-compatible providers // endpoint: "https://.r2.cloudflarestorage.com", // publicEndpoint: "https://cdn.my-app.com", // forcePathStyle: true, }), ] // Or configured via the `storage` property directly in radiant.config.ts }); ``` ## Email Plugins Radiant supports sending lifecycle and transactional emails (such as forgot password and verification workflows) through plugins, configurable via the `email` setting in the core config. ### 1. Resend Plugin **Package:** `@codesordinatestudio/radiant-plugin-resend` Uses Resend's modern REST API instead of traditional SMTP for faster, more reliable delivery. **Configuration & Usage:** ```typescript import { createRadiant /* or radiant */ } from "@codesordinatestudio/radiant-bun"; import { resendEmail } from "@codesordinatestudio/radiant-plugin-resend"; export const app = createRadiant({ email: { from: '"Radiant App" ', appName: "My Awesome SaaS", transport: resendEmail(process.env.RESEND_API_KEY!) // Or pass an object: resendEmail({ apiKey: process.env.RESEND_API_KEY, from: '...' }) } }); ``` *Note: If instantiated without explicit arguments, `resendEmail()` can automatically infer credentials from `Bun.env.RESEND_API_KEY` and `Bun.env.SMTP_FROM`.* ### 2. Nodemailer Plugin **Package:** `@codesordinatestudio/radiant-plugin-nodemailer` Provides robust SMTP capabilities using the industry-standard `nodemailer` library. Suitable for Mailgun, SendGrid, Amazon SES, or custom SMTP servers. **Configuration & Usage:** ```typescript import { createRadiant /* or radiant */ } from "@codesordinatestudio/radiant-bun"; import { nodemailerEmail } from "@codesordinatestudio/radiant-plugin-nodemailer"; export const app = createRadiant({ email: { appName: "My Awesome SaaS", transport: nodemailerEmail({ host: "smtp.mailgun.org", port: 587, auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS!, }, from: '"Radiant App" ' }) } }); ``` *Note: If explicit credentials are omitted, `nodemailerEmail()` automatically initializes using environment variables (`SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM`, `SMTP_SECURE`).* ## Durable Streams Plugin **Package:** `@codesordinatestudio/radiant-plugin-durable-streams` Upgrades Radiant's standard real-time event feeds into resilient, resumable streams. Ideal for serverless environments (e.g., across multiple instances and load balancers). It tracks an `Event ID`, so when a client reconnects after losing connection (e.g. passing through a tunnel), the server will immediately flush all missed events before returning to real-time feeds. Works best when backed by a persistent store like Redis or Postgres. ### Configuration & Usage ```typescript import { createRadiant } from "@codesordinatestudio/radiant-bun"; import { durableStreams } from "@codesordinatestudio/radiant-plugin-durable-streams"; export const app = createRadiant({ plugins: [ durableStreams({ // Configuration options }) ] }); ```