UNPKG

@mements/serve

Version:

A lightweight, type-safe server framework for Bun with automatic performance tracking.

321 lines (259 loc) 8.49 kB
# @mements/serve A lightweight, type-safe server framework for Bun with automatic performance tracking. ## Features - **Type-Safe API Routes**: Build endpoints with complete type safety - **API Endpoint Typing**: Define and enforce request/response contracts - **Server-Side Data Injection**: Add dynamic data to your pages - **Import Management**: Handle frontend dependencies automatically - **Performance Tracking**: Built-in nested performance monitoring with request IDs - **Development Ready**: Fast hot reloading for quick iterations - **Static Assets**: Serve static files from a dedicated assets directory - **Dependency Analysis**: Automatic package.json parsing and import generation ## Installation ```bash npm install @mements/serve ``` or ```bash yarn add @mements/serve ``` ## Quick Start Create a complete app with server-rendered pages and type-safe API: ```ts import { serve } from "@mements/serve"; // Create a user dashboard application serve({ // Define pages with data handlers pages: [ { route: '/dashboard', target: './pages/dashboard.tsx', handler: async (ctx) => { // Performance is automatically tracked const userId = ctx.query.userId || 'default'; // Data from API gets injected into the page return { user: { name: 'Jane Doe', role: 'Admin' }, stats: { visitors: 1024, conversions: 89 }, lastLogin: new Date().toISOString() }; }, }, ], // Type-safe API endpoints api: { '/api/users': async (req) => { const users = [ { id: 1, name: 'Jane Doe', role: 'Admin' }, { id: 2, name: 'John Smith', role: 'Editor' } ]; return new Response(JSON.stringify(users), { headers: { "Content-Type": "application/json" }, }); }, '/api/stats': async (req) => { const url = new URL(req.url); const period = url.searchParams.get('period') || 'week'; const stats = { period, visitors: 1024, conversions: 89 }; return new Response(JSON.stringify(stats), { headers: { "Content-Type": "application/json" }, }); } }, // Frontend dependencies automatically managed imports: [ { name: 'react', version: '18.2.0' }, { name: 'react-dom/client', version: '18.2.0' }, { name: '@chakra-ui/react', version: '2.5.1', deps: ['react'] }, { name: 'recharts', version: '2.4.3', deps: ['react'] } ], }); ``` ## Core Concepts ### Type-Safe API Endpoints Define concrete API endpoint types with strict typing: ```ts // Define API endpoint types export type ApiEndpoint = { method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; path: string; body?: any; query?: Record<string, string | number | boolean>; response: any; }; // Create specific endpoints export type GetHealthEndpoint = { method: 'GET'; path: '/api/health'; query?: undefined; response: { status: string }; }; // Server implementation with full type-checking const api = { '/api/health': async (req: Request): Promise<Response> => { return new Response(JSON.stringify({ status: 'ok' }), { headers: { "Content-Type": "application/json" }, }); } }; ``` ### Server-Side Data Injection Your pages automatically receive data from server handlers: ```ts // Server-side: pages/dashboard.tsx handler { route: '/dashboard', target: './pages/dashboard.tsx', handler: async (ctx) => { // Get user data from database based on query params const userId = ctx.query.userId; return { user: await getUserById(userId), permissions: await getPermissions(userId), lastLogin: await getLastLogin(userId) }; } } // Client-side: access data in your React component function Dashboard() { // Data is automatically injected by the framework const { user, permissions, lastLogin } = window.serverData; return ( <div> <h1>Welcome back, {user.name}</h1> <p>Last login: {new Date(lastLogin).toLocaleString()}</p> <PermissionsList permissions={permissions} /> </div> ); } ``` ### Performance Tracking Every operation is automatically timed and logged with unique request IDs and visual indicators: ```ts // Nested performance tracking included automatically const result = await measure( async (measure) => { // Run some expensive operation const data = await fetchData(); // Measure nested operations with context return await measure( async () => processData(data), "Process fetched data" ); }, "Fetch and process data", { requestId } ); // Console output: // [abc123] >$ Fetch and process data... // [abc123] >>$ Process fetched data... // [abc123] <<$ Process fetched data ✓ 45.32ms // [abc123] <$ Fetch and process data ✓ 134.56ms ``` The framework automatically: 1. Generates unique request IDs 2. Tracks nested operations with visual indicators for start/end 3. Measures and logs execution time with success/failure states 4. Maintains context across async operations ### Import Management Never worry about managing frontend dependencies - just declare what you need or let the framework analyze your package.json: ```ts // Manual imports definition serve({ imports: [ { name: 'react', version: '18.2.0' }, { name: 'react-dom/client', version: '18.2.0' }, { name: '@mui/material', version: '5.11.0', deps: ['react'] }, ], }); // Or use automatic package.json analysis import { serve, generateImports } from "@mements/serve"; import packageJson from "./package.json"; serve({ // Automatically analyze dependencies imports: generateImports(packageJson), // ...rest of config }); ``` The framework automatically: 1. Creates an import map for your frontend code 2. Resolves all dependencies through ESM.sh 3. Injects it into your HTML 4. Supports development mode with proper sourcemaps 5. Handles special cases like React JSX runtime ## Static Assets Place your static files in the `./assets` directory and they'll be served automatically: ``` myapp/ ├── assets/ # Static assets served directly │ ├── favicon.ico │ ├── images/ │ ├── fonts/ │ └── ... ├── pages/ # Dynamic pages with server handlers ├── dist/ # Build output (created automatically) └── ... ``` ## Handler Context Page handlers receive a rich context with everything you need: ```ts async function dashboardHandler(ctx) { // Access request data const { userId } = ctx.query; const { token } = ctx.headers; // Unique request ID for tracing console.log(`Request ID: ${ctx.requestId}`); // Performance tracking const userData = await ctx.measure( async () => fetchUserData(userId), "Fetch user data" ); if (ctx.method === 'POST') { // Handle form submissions with parsed body await processFormData(ctx.body); } // Return JSON responses directly if (ctx.path.endsWith('/api')) { return ctx.json({ success: true }); } // Add response headers ctx.setHeader('Cache-Control', 'max-age=3600'); // Return data to inject into the page return { user: userData, isAdmin: await checkPermissions(userId, 'admin') }; } ``` ## How It Works ```mermaid graph TD A[Browser Request] --> B[Bun Server] B --> C{Route Type} C -->|Static Asset| D[Serve from assets/] C -->|Built File| E[Serve from dist/] C -->|Page Route| F[Execute Page Handler] C -->|API Endpoint| G[Execute API Handler] F --> H[Get Dynamic Data] H --> I[Build/Rebuild Page] I --> J[Cache File Path] J --> K[Inject Data via HTMLRewriter] K --> L[Add Import Map] G --> M[Process API Request] M --> N[Return JSON Response] D --> O[Response to Browser] E --> O L --> O N --> O ``` ## Development Features - **Hot Reloading**: Pages rebuild on each request in development mode - **Request Tracing**: Unique IDs for tracking requests through logs - **Visual Performance Monitoring**: Operations tracked with visual indicators - **File Path Caching**: Improved performance for built files - **Static Assets Support**: Dedicated directory for static files - **Package Analysis**: Automatic import generation from package.json - **Error Handling**: Detailed stack traces during development - **Type Safety**: Full TypeScript support throughout your codebase ## License MIT