Files
smartserve/readme.md

411 lines
11 KiB
Markdown
Raw Normal View History

2025-11-29 15:24:00 +00:00
# @push.rocks/smartserve
A cross-platform HTTP server module for Node.js, Deno, and Bun with decorator-based routing, WebSocket support, static file serving, and WebDAV protocol. 🚀
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
## Install
```bash
npm install @push.rocks/smartserve
# or
pnpm add @push.rocks/smartserve
```
## Features
**Cross-Platform** - Works seamlessly on Node.js, Deno, and Bun
🎯 **Decorator-Based Routing** - Clean, expressive `@Route`, `@Get`, `@Post` decorators
🛡️ **Guards & Interceptors** - Built-in `@Guard`, `@Transform`, `@Intercept` for auth and transformation
📁 **Static File Server** - Streaming, ETags, Range requests, directory listing
🌐 **WebDAV Support** - Mount as network drive with full RFC 4918 compliance
🔌 **WebSocket Ready** - Native WebSocket support across all runtimes
**Zero Overhead** - Native Web Standards API (Request/Response) on Deno/Bun
🔒 **HTTPS/TLS** - Built-in TLS support with certificate configuration
## Quick Start
```typescript
import { SmartServe, Route, Get, Post, Guard, type IRequestContext } from '@push.rocks/smartserve';
// Define a controller with decorators
@Route('/api')
class UserController {
@Get('/hello')
hello() {
return { message: 'Hello World! 👋' };
}
@Get('/users/:id')
getUser(ctx: IRequestContext) {
return { id: ctx.params.id, name: 'John Doe' };
}
@Post('/users')
createUser(ctx: IRequestContext<{ name: string; email: string }>) {
return { id: 'new-id', ...ctx.body };
}
}
// Create and start server
const server = new SmartServe({ port: 3000 });
server.register(UserController);
await server.start();
console.log('🚀 Server running at http://localhost:3000');
```
## Decorators
### Route Decorators
```typescript
import { Route, Get, Post, Put, Delete, Patch, All } from '@push.rocks/smartserve';
@Route('/api/v1') // Base path for all routes in this controller
class ApiController {
@Get('/items') // GET /api/v1/items
listItems() { ... }
@Get('/items/:id') // GET /api/v1/items/:id - path parameters
getItem(ctx: IRequestContext) {
return { id: ctx.params.id };
}
@Post('/items') // POST /api/v1/items
createItem(ctx: IRequestContext<{ name: string }>) {
return { created: ctx.body.name };
}
@Put('/items/:id') // PUT /api/v1/items/:id
updateItem(ctx: IRequestContext) { ... }
@Delete('/items/:id') // DELETE /api/v1/items/:id
deleteItem(ctx: IRequestContext) { ... }
@All('/webhook') // Matches ALL HTTP methods
handleWebhook(ctx: IRequestContext) { ... }
}
```
### Guards (Authentication/Authorization)
Guards protect routes by returning `true` (allow) or `false` (reject with 403):
```typescript
import { Route, Get, Guard, type IRequestContext } from '@push.rocks/smartserve';
// Guard function
const isAuthenticated = (ctx: IRequestContext) => {
return ctx.headers.has('Authorization');
};
const isAdmin = (ctx: IRequestContext) => {
return ctx.headers.get('X-Role') === 'admin';
};
// Apply guard to entire controller
@Route('/admin')
@Guard(isAuthenticated)
@Guard(isAdmin) // Multiple guards - all must pass
class AdminController {
@Get('/dashboard')
dashboard() {
return { admin: true };
}
// Method-level guard (runs after class guards)
@Get('/super-secret')
@Guard((ctx) => ctx.headers.get('X-Super') === 'yes')
superSecret() {
return { level: 'super-secret' };
}
}
```
### Transforms (Response Modification)
Transforms modify the response before sending:
```typescript
import { Route, Get, Transform } from '@push.rocks/smartserve';
// Transform function
const wrapResponse = <T>(data: T) => ({
success: true,
data,
timestamp: Date.now(),
});
const addVersion = <T extends object>(data: T) => ({
...data,
apiVersion: '2.0',
});
@Route('/api')
@Transform(wrapResponse) // Applied to all routes in controller
class ApiController {
@Get('/info')
@Transform(addVersion) // Stacks with class transform
getInfo() {
return { name: 'MyAPI' };
}
// Response: { success: true, data: { name: 'MyAPI', apiVersion: '2.0' }, timestamp: ... }
}
```
### Intercept (Full Control)
For complete control over request/response flow:
```typescript
import { Route, Get, Intercept, type IRequestContext } from '@push.rocks/smartserve';
@Route('/api')
@Intercept({
// Runs before handler
request: async (ctx) => {
console.log(`${ctx.method} ${ctx.path}`);
// Return Response to short-circuit
// Return modified context to continue
// Return void to continue with original
},
// Runs after handler
response: async (data, ctx) => {
return { ...data, processedAt: new Date().toISOString() };
},
})
class LoggedController {
@Get('/data')
getData() {
return { items: [1, 2, 3] };
}
}
```
## Static File Server
Serve static files with streaming, ETags, and directory listing:
```typescript
const server = new SmartServe({
port: 3000,
static: {
root: './public',
index: ['index.html', 'index.htm'],
dotFiles: 'deny', // 'allow' | 'deny' | 'ignore'
etag: true, // Generate ETags
lastModified: true, // Add Last-Modified header
cacheControl: 'max-age=3600',
extensions: ['.html'], // Try these extensions
directoryListing: {
showHidden: false,
sortBy: 'name', // 'name' | 'size' | 'modified'
sortOrder: 'asc',
},
},
});
```
Or simply:
```typescript
const server = new SmartServe({
port: 3000,
static: './public', // Shorthand - uses defaults
});
```
## WebDAV Support
Mount the server as a network drive on macOS, Windows, or Linux:
```typescript
const server = new SmartServe({
port: 8080,
webdav: {
root: '/path/to/files',
auth: (ctx) => {
// Optional Basic auth
const auth = ctx.headers.get('Authorization');
if (!auth) return false;
const [, credentials] = auth.split(' ');
const [user, pass] = atob(credentials).split(':');
return user === 'admin' && pass === 'secret';
},
locking: true, // Enable RFC 4918 file locking
},
});
await server.start();
// Mount: Connect to Server → http://localhost:8080
```
**Supported WebDAV Methods:**
- `OPTIONS` - Capability discovery
- `PROPFIND` - Directory listing and file metadata
- `MKCOL` - Create directory
- `COPY` / `MOVE` - Copy and move operations
- `LOCK` / `UNLOCK` - Exclusive write locking
- `GET` / `PUT` / `DELETE` - File operations
## WebSocket Support
WebSocket connections are handled natively:
```typescript
const server = new SmartServe({
port: 3000,
websocket: {
onOpen: (peer) => {
console.log(`Connected: ${peer.id}`);
peer.send('Welcome!');
},
onMessage: (peer, message) => {
console.log(`Received: ${message.text}`);
peer.send(`Echo: ${message.text}`);
},
onClose: (peer, code, reason) => {
console.log(`Disconnected: ${peer.id}`);
},
onError: (peer, error) => {
console.error(`Error: ${error.message}`);
},
},
});
```
## HTTPS/TLS
Enable HTTPS with certificate configuration:
```typescript
const server = new SmartServe({
port: 443,
tls: {
cert: fs.readFileSync('./cert.pem'),
key: fs.readFileSync('./key.pem'),
// Optional
ca: fs.readFileSync('./ca.pem'),
minVersion: 'TLSv1.2',
},
});
```
## Error Handling
Built-in HTTP error classes:
```typescript
import { HttpError, type IRequestContext } from '@push.rocks/smartserve';
@Route('/api')
class ApiController {
@Get('/users/:id')
async getUser(ctx: IRequestContext) {
const user = await findUser(ctx.params.id);
if (!user) {
throw HttpError.notFound('User not found', { id: ctx.params.id });
}
return user;
}
}
// Available factory methods:
HttpError.badRequest(message, details); // 400
HttpError.unauthorized(message, details); // 401
HttpError.forbidden(message, details); // 403
HttpError.notFound(message, details); // 404
HttpError.conflict(message, details); // 409
HttpError.internal(message, details); // 500
```
Global error handling:
```typescript
const server = new SmartServe({
port: 3000,
onError: (error, request) => {
console.error('Server error:', error);
return new Response(JSON.stringify({ error: 'Something went wrong' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
},
});
```
## Request Context
Every handler receives a typed request context:
```typescript
interface IRequestContext<TBody = unknown> {
request: Request; // Original Web Standards Request
body: TBody; // Parsed and typed body
params: Record<string, string>; // URL path parameters
query: Record<string, string>; // Query string parameters
headers: Headers; // Request headers
path: string; // Matched route path
method: THttpMethod; // HTTP method
url: URL; // Full URL object
runtime: 'node' | 'deno' | 'bun'; // Current runtime
state: Record<string, unknown>; // Per-request state bag
}
```
## Custom Request Handler
Bypass decorator routing entirely:
```typescript
const server = new SmartServe({ port: 3000 });
server.setHandler(async (request, connectionInfo) => {
const url = new URL(request.url);
if (url.pathname === '/health') {
return new Response('OK', { status: 200 });
}
return new Response('Not Found', { status: 404 });
});
await server.start();
```
## Runtime Detection
SmartServe automatically detects and optimizes for the current runtime:
```typescript
const instance = await server.start();
console.log(instance.runtime); // 'node' | 'deno' | 'bun'
console.log(instance.port); // 3000
console.log(instance.secure); // true if TLS enabled
```
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.