Internal Packages
Overview of the 11 monorepo packages and their responsibilities.
Raypx is organized as a pnpm workspace monorepo with 11 internal packages. Each package has a single responsibility and exports a clean public API. All packages follow the @raypx/{name} naming convention.
Package Overview
Infrastructure
| Package | Description |
|---|---|
@raypx/env | Environment variable validation using Zod. Every package imports from here to get typed, validated config values. |
@raypx/logger | Structured logging with Pino. Provides a shared logger instance with consistent formatting and log levels. |
@raypx/database | Drizzle ORM setup with PostgreSQL. Contains schema definitions, migrations, and the database client factory. |
Feature
| Package | Description |
|---|---|
@raypx/auth | Better Auth server configuration. Wraps Better Auth with plugins (OAuth, admin, email), a Drizzle adapter, and typed client exports. |
@raypx/rpc | oRPC router definitions and context. Contains all API procedures, input/output types, and the request context factory. |
@raypx/storage | File storage abstraction. Implements a driver interface with local disk and S3 backends. |
@raypx/email | Transactional email service. Integrates Resend with React Email templates for verification, password reset, and notification emails. |
@raypx/otp | One-time password service. Generates, stores, and verifies OTPs with configurable expiry and rate limits. |
@raypx/billing | Payment and subscription management. Integrates Polar for SaaS billing with plan management and webhook handling. |
Presentation
| Package | Description |
|---|---|
@raypx/i18n | Internationalization with use-intl. Provides locale detection, message loading, server-side locale context, and locale-prefixed routing. |
@raypx/ui | Shared UI components built on shadcn/ui. Contains button, card, input, dialog, and other primitives used across the application. |
Package Details
@raypx/env
Validates environment variables at application startup. Each package defines its required and optional variables in a Zod schema:
// packages/env/src/env.ts
import { z } from "zod/v4"
export const envSchema = z.object({
APP_KEY: z.string().min(1),
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
})
export type Env = z.infer<typeof envSchema>The @raypx/auth package extends this with its own variables:
// packages/auth/src/env.ts
export const authEnvSchema = z.object({
AUTH_URL: z.string().url(),
AUTH_GOOGLE_SECRET: z.string().optional(),
AUTH_GITHUB_ID: z.string().optional(),
AUTH_GITHUB_SECRET: z.string().optional(),
ADMIN_EMAILS: z.string().optional(),
})@raypx/database
Contains Drizzle schema definitions organized by domain. The client is created lazily to avoid importing Node.js modules at the top level:
packages/database/src/
schema/ # Drizzle table definitions
migrate.ts # Migration runner
health.ts # Health check query
client.ts # Database client factory@raypx/auth
Wraps Better Auth with all plugins pre-configured. Exports both the server instance and a typed client for use in React components:
// packages/auth/src/index.ts
import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { admin } from "better-auth/plugins"
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg" }),
plugins: [admin()],
emailAndPassword: { enabled: true },
socialProviders: { google: { ... }, github: { ... } },
})@raypx/rpc
Defines all oRPC procedures organized into routers. The context factory creates a per-request context object with the current user session:
// packages/rpc/src/context.ts
export async function createContext({ req }: { req: Request }) {
const session = await auth.api.getSession({ headers: req.headers })
return { session, user: session?.user ?? null }
}@raypx/i18n
Manages internationalization with two locales (en-US, zh-CN). Provides server-side middleware for locale detection from URL paths and cookies, and client-side hooks for locale switching:
// packages/i18n/src/request.ts
export function handleLocaleMiddleware(request: Request) {
const path = new URL(request.url).pathname
const locale = detectLocale(path) // extract from /en-US/ or /zh-CN/ prefix
return { locale, redirect: needsRedirect(path, locale), setCookie: ... }
}@raypx/ui
Shared React components built on Radix primitives and Tailwind CSS. Managed with the shadcn CLI — new components are added with pnpm bump-ui and live in packages/ui/src/components/.
Adding a New Package
To add a new internal package:
- Create the package directory under
packages/:
mkdir -p packages/my-package/src- Create a
package.jsonwith the@raypx/scope:
{
"name": "@raypx/my-package",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": { ".": "./src/index.ts" }
}-
Add environment variables to
packages/env/src/env.tsif needed. -
Import the package in the root application:
pnpm add @raypx/my-package@workspace:*- Turborepo will automatically detect the new workspace. No changes to
turbo.jsonare needed unless you want to define custom tasks.
Next Steps
- Architecture Overview — system design and layer breakdown.
- Request Lifecycle — how requests flow through the system.