236 lines
7.2 KiB
Markdown
236 lines
7.2 KiB
Markdown
# SmartServe Development Hints
|
|
|
|
## Architecture Overview
|
|
|
|
SmartServe is a cross-platform HTTP server for Node.js, Deno, and Bun using:
|
|
- **Web Standards API** (Request/Response) for cross-platform compatibility
|
|
- **TC39 Stage 3 decorators** (TypeScript 5.0+) for route definition
|
|
- **Adapter pattern** for runtime-specific implementations
|
|
|
|
## Key Files
|
|
|
|
### Core
|
|
- `ts/core/smartserve.classes.smartserve.ts` - Main server class
|
|
- `ts/core/smartserve.interfaces.ts` - All type definitions
|
|
- `ts/core/smartserve.errors.ts` - HTTP error classes
|
|
|
|
### Adapters
|
|
- `ts/adapters/adapter.factory.ts` - Runtime detection via @push.rocks/smartenv
|
|
- `ts/adapters/adapter.node.ts` - Node.js with Request/Response conversion
|
|
- `ts/adapters/adapter.deno.ts` - Deno (zero overhead, native)
|
|
- `ts/adapters/adapter.bun.ts` - Bun (zero overhead, native)
|
|
|
|
### Decorators
|
|
- `ts/decorators/decorators.route.ts` - @Route class decorator
|
|
- `ts/decorators/decorators.methods.ts` - @Get, @Post, etc.
|
|
- `ts/decorators/decorators.interceptors.ts` - @Guard, @Transform, @Intercept
|
|
- `ts/decorators/decorators.registry.ts` - Controller registration and route matching
|
|
|
|
### Static Files
|
|
- `ts/files/file.server.ts` - Static file serving with streaming, ETags, directory listing
|
|
- `ts/utils/utils.mime.ts` - MIME type detection
|
|
- `ts/utils/utils.etag.ts` - ETag generation
|
|
|
|
### Compression
|
|
- `ts/compression/compression.runtime.ts` - Cross-runtime compression (Node.js zlib, Web CompressionStream)
|
|
- `ts/compression/compression.middleware.ts` - Compression config and helpers
|
|
- `ts/utils/utils.encoding.ts` - Accept-Encoding parsing
|
|
- `ts/decorators/decorators.compress.ts` - @Compress and @NoCompress decorators
|
|
|
|
### Protocols
|
|
- `ts/protocols/webdav/webdav.handler.ts` - WebDAV RFC 4918 handler
|
|
- `ts/protocols/webdav/webdav.xml.ts` - XML generation (multistatus, lock responses)
|
|
- `ts/protocols/webdav/webdav.types.ts` - WebDAV type definitions
|
|
|
|
## Decorator System
|
|
|
|
Uses TC39 decorators (NOT experimental decorators). Metadata stored via Symbol property on classes.
|
|
|
|
### Interceptor Execution Order (Onion Model)
|
|
```
|
|
Request → Class Guards → Method Guards → Handler → Method Transforms → Class Transforms → Response
|
|
```
|
|
|
|
### Guard vs Transform vs Intercept
|
|
- `@Guard(fn)` = `@Intercept({ request: fn })` - returns boolean (true=allow, false=403)
|
|
- `@Transform(fn)` = `@Intercept({ response: fn })` - modifies response
|
|
- `@Intercept({ request?, response? })` - full control
|
|
|
|
## Dependencies
|
|
|
|
Required:
|
|
- `@push.rocks/smartenv` - Runtime detection
|
|
- `@push.rocks/smartpath` - Path utilities
|
|
- `@push.rocks/smartlog` - Logging
|
|
- `@push.rocks/lik` - Collections
|
|
|
|
Optional:
|
|
- `ws` - WebSocket support for Node.js (Deno/Bun have native)
|
|
|
|
## WebDAV Usage
|
|
|
|
```typescript
|
|
import { SmartServe } from '@push.rocks/smartserve';
|
|
|
|
const server = new SmartServe({
|
|
port: 8080,
|
|
webdav: {
|
|
root: '/path/to/files',
|
|
auth: (ctx) => {
|
|
// Optional auth - return true to allow, false to reject
|
|
const auth = ctx.headers.get('Authorization');
|
|
return auth === 'Basic dXNlcjpwYXNz';
|
|
},
|
|
locking: true, // Enable file locking (RFC 4918)
|
|
}
|
|
});
|
|
|
|
await server.start();
|
|
// Mount at http://localhost:8080 as network drive
|
|
```
|
|
|
|
### Supported WebDAV Methods
|
|
- OPTIONS - Capability discovery
|
|
- PROPFIND - Directory listing and file metadata
|
|
- PROPPATCH - Property modification (returns 403)
|
|
- MKCOL - Create directory
|
|
- COPY - Copy files/directories
|
|
- MOVE - Move/rename files/directories
|
|
- LOCK/UNLOCK - Exclusive write locking
|
|
- GET/HEAD/PUT/DELETE - Standard file operations
|
|
|
|
## TypedRouter WebSocket Integration
|
|
|
|
SmartServe supports first-class TypedRouter integration for type-safe RPC over WebSockets.
|
|
|
|
### Usage
|
|
|
|
```typescript
|
|
import { SmartServe } from '@push.rocks/smartserve';
|
|
import { TypedRouter, TypedHandler } from '@api.global/typedrequest';
|
|
|
|
const router = new TypedRouter();
|
|
router.addTypedHandler(new TypedHandler('echo', async (data) => {
|
|
return { echoed: data.message };
|
|
}));
|
|
|
|
const server = new SmartServe({
|
|
port: 3000,
|
|
websocket: {
|
|
typedRouter: router,
|
|
onConnectionOpen: (peer) => {
|
|
peer.tags.add('authenticated');
|
|
console.log(`Client connected: ${peer.id}`);
|
|
},
|
|
onConnectionClose: (peer) => {
|
|
console.log(`Client disconnected: ${peer.id}`);
|
|
},
|
|
},
|
|
});
|
|
|
|
await server.start();
|
|
|
|
// Broadcast to all connections
|
|
server.broadcastWebSocket({ type: 'notification', message: 'Hello!' });
|
|
|
|
// Broadcast to specific tag
|
|
server.broadcastWebSocketByTag('authenticated', { type: 'secure-message' });
|
|
|
|
// Get all connections
|
|
const connections = server.getWebSocketConnections();
|
|
```
|
|
|
|
### Key Features
|
|
|
|
- **TypedRouter mode**: Set `typedRouter` for automatic JSON-RPC routing (mutually exclusive with `onMessage`)
|
|
- **Connection registry**: Active only when `typedRouter` is set
|
|
- **Peer tags**: Use `peer.tags.add/has/delete` for connection filtering
|
|
- **Broadcast methods**: `broadcastWebSocket()` and `broadcastWebSocketByTag()`
|
|
- **Lifecycle hooks**: `onConnectionOpen` and `onConnectionClose` (alongside existing `onOpen`/`onClose`)
|
|
|
|
### Architecture Notes
|
|
|
|
- `typedRouter` and `onMessage` are mutually exclusive (throws `WebSocketConfigError` if both set)
|
|
- Connection registry only active when `typedRouter` is configured
|
|
- Bun adapter stores peer ID/tags in `ws.data` for persistence across events
|
|
- Internal `_connectionCallbacks` passed to adapters for registry communication
|
|
|
|
## Compression
|
|
|
|
SmartServe supports automatic response compression with Brotli and gzip.
|
|
|
|
### Configuration
|
|
|
|
```typescript
|
|
const server = new SmartServe({
|
|
port: 3000,
|
|
|
|
// Simple: enable with defaults (compression is ON by default)
|
|
compression: true,
|
|
|
|
// Detailed configuration
|
|
compression: {
|
|
enabled: true,
|
|
algorithms: ['br', 'gzip'], // Brotli preferred, gzip fallback
|
|
threshold: 1024, // Don't compress < 1KB
|
|
level: 4, // Compression level
|
|
compressibleTypes: [ // Custom MIME types
|
|
'text/',
|
|
'application/json',
|
|
'application/javascript',
|
|
],
|
|
exclude: ['/api/stream/*'], // Skip certain paths
|
|
},
|
|
|
|
// Pre-compressed static files
|
|
static: {
|
|
root: './public',
|
|
precompressed: true, // Serve index.html.br, index.html.gz if available
|
|
},
|
|
});
|
|
```
|
|
|
|
### Per-Route Control
|
|
|
|
```typescript
|
|
@Controller('/api')
|
|
class ApiController {
|
|
@Get('/data')
|
|
getData() {
|
|
return { large: 'json' }; // Compressed by default
|
|
}
|
|
|
|
@Get('/stream')
|
|
@NoCompress() // Skip compression for SSE/streaming
|
|
getStream() {
|
|
return new Response(eventStream, {
|
|
headers: { 'Content-Type': 'text/event-stream' }
|
|
});
|
|
}
|
|
|
|
@Get('/heavy')
|
|
@Compress({ level: 11 }) // Force max brotli compression
|
|
getHeavy() {
|
|
return massiveData;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cross-Runtime Support
|
|
|
|
| Runtime | Brotli | gzip | Implementation |
|
|
|---------|--------|------|----------------|
|
|
| Node.js | ✅ | ✅ | Native zlib module |
|
|
| Deno | ⚠️ | ✅ | CompressionStream API |
|
|
| Bun | ⚠️ | ✅ | CompressionStream API |
|
|
|
|
Note: Brotli in Deno/Bun falls back to gzip if CompressionStream doesn't support it.
|
|
|
|
## TODO
|
|
|
|
- [x] WebDAV protocol support (PROPFIND, MKCOL, COPY, MOVE, LOCK, UNLOCK)
|
|
- [x] TypedRouter WebSocket integration
|
|
- [x] Brotli/gzip compression with per-route control
|
|
- [ ] HTTP/2 support investigation
|
|
- [ ] Performance benchmarks
|