From 94532c3c686de4478742fbea7b7001e668eb9571 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 8 Dec 2025 12:19:01 +0000 Subject: [PATCH] feat(typedserver): Add configurable response compression (Brotli + Gzip) with defaults enabled and documentation --- changelog.md | 9 +++ readme.md | 73 ++++++++++++++++++++++ readme.todo.md | 5 ++ ts/00_commitinfo_data.ts | 2 +- ts/classes.typedserver.ts | 7 +++ ts/utilityservers/classes.websiteserver.ts | 5 ++ 6 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 readme.todo.md diff --git a/changelog.md b/changelog.md index f432e67..c7e8b54 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-12-08 - 7.11.0 - feat(typedserver) +Add configurable response compression (Brotli + Gzip) with defaults enabled and documentation + +- Expose a new compression option on IServerOptions (plugins.smartserve.ICompressionConfig | boolean). +- Pass the compression setting through to SmartServe (smartServeOptions.compression = this.options.compression). +- Add compression option to UtilityWebsiteServer and forward it when creating SmartServe options. +- Update README: new Compression section with global config examples, per-route decorator usage, and options reference. +- Add a small readme.todo.md with service worker wake/reload TODO notes. + ## 2025-12-05 - 7.10.2 - fix(docs) Update README with routing examples and utility server config; bump @cloudflare/workers-types and @push.rocks/smartserve versions diff --git a/readme.md b/readme.md index b62d3ef..afdd69d 100644 --- a/readme.md +++ b/readme.md @@ -341,6 +341,75 @@ await server.start(); | `Cross-Origin-Embedder-Policy` | `crossOriginEmbedderPolicy` | Controls cross-origin embedding | | `Cross-Origin-Resource-Policy` | `crossOriginResourcePolicy` | Controls cross-origin resource sharing | +## 🗜️ Compression + +TypedServer supports automatic response compression using Brotli and Gzip. Compression is powered by smartserve and enabled by default. + +### Global Configuration + +```typescript +import { TypedServer } from '@api.global/typedserver'; + +const server = new TypedServer({ + serveDir: './dist', + cors: true, + + // Enable with defaults (brotli + gzip, threshold: 1024 bytes) + compression: true, + + // Or disable completely + compression: false, + + // Or configure in detail + compression: { + enabled: true, + algorithms: ['br', 'gzip'], // Preferred order + threshold: 1024, // Min size to compress (bytes) + level: 4, // Compression level (1-11 for brotli, 1-9 for gzip) + exclude: ['/api/stream/*'], // Skip these paths + }, +}); +``` + +### Per-Route Control with Decorators + +Use `@Compress` and `@NoCompress` decorators for fine-grained control: + +```typescript +import * as smartserve from '@push.rocks/smartserve'; + +@smartserve.Route('/api') +class ApiController { + // Force maximum compression for this endpoint + @smartserve.Get('/large-data') + @smartserve.Compress({ level: 11 }) + async getLargeData(ctx: smartserve.IRequestContext): Promise { + return new Response(JSON.stringify(largeDataset)); + } + + // Disable compression for streaming endpoint + @smartserve.Get('/events') + @smartserve.NoCompress() + async streamEvents(ctx: smartserve.IRequestContext): Promise { + // Server-Sent Events shouldn't be compressed + return new Response(eventStream, { + headers: { 'Content-Type': 'text/event-stream' }, + }); + } +} +``` + +### Compression Options Reference + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `enabled` | `boolean` | `true` | Enable/disable compression | +| `algorithms` | `string[]` | `['br', 'gzip']` | Preferred algorithms in order | +| `threshold` | `number` | `1024` | Minimum response size (bytes) to compress | +| `level` | `number` | `4` | Compression level (1-11 for brotli, 1-9 for gzip) | +| `compressibleTypes` | `string[]` | auto | MIME types to compress | +| `exclude` | `string[]` | `[]` | Path patterns to skip | + ## 📋 Configuration Reference ### IServerOptions @@ -366,6 +435,7 @@ await server.start(); | `feedMetadata` | `object` | - | RSS feed metadata options | | `blockWaybackMachine` | `boolean` | `false` | Block Wayback Machine archiving | | `securityHeaders` | `ISecurityHeaders` | - | Security headers configuration | +| `compression` | `ICompressionConfig \| boolean` | `true` | Response compression configuration | ## 🏗️ Package Exports @@ -408,6 +478,9 @@ const websiteServer = new utilityservers.UtilityWebsiteServer({ xContentTypeOptions: true, }, + // Compression (enabled by default) + compression: true, // or { level: 6, threshold: 512 } + // Other options cors: true, // default: true forceSsl: false, // default: false diff --git a/readme.todo.md b/readme.todo.md new file mode 100644 index 0000000..1babd94 --- /dev/null +++ b/readme.todo.md @@ -0,0 +1,5 @@ +- Wake up the service worker before sending stuff. + +Handle reload properly. Make sure service worker is up. + +Pill handling of service worker status. diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 9812f17..7f634f1 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@api.global/typedserver', - version: '7.10.2', + version: '7.11.0', description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.' } diff --git a/ts/classes.typedserver.ts b/ts/classes.typedserver.ts index 65085ad..17da122 100644 --- a/ts/classes.typedserver.ts +++ b/ts/classes.typedserver.ts @@ -137,6 +137,12 @@ export interface IServerOptions { * Security headers configuration (CSP, HSTS, X-Frame-Options, etc.) */ securityHeaders?: ISecurityHeaders; + + /** + * Response compression configuration + * Set to true for defaults (brotli + gzip), false to disable, or provide detailed config + */ + compression?: plugins.smartserve.ICompressionConfig | boolean; } export type THttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'ALL'; @@ -281,6 +287,7 @@ export class TypedServer { const smartServeOptions: plugins.smartserve.ISmartServeOptions = { port, hostname: '0.0.0.0', + compression: this.options.compression, tls: this.options.privateKey && this.options.publicKey ? { diff --git a/ts/utilityservers/classes.websiteserver.ts b/ts/utilityservers/classes.websiteserver.ts index aef796a..656efa2 100644 --- a/ts/utilityservers/classes.websiteserver.ts +++ b/ts/utilityservers/classes.websiteserver.ts @@ -25,6 +25,8 @@ export interface IUtilityWebsiteServerConstructorOptions { port?: number; /** ads.txt entries (only served if configured) */ adsTxt?: string[]; + /** Response compression configuration (default: enabled with brotli + gzip) */ + compression?: plugins.smartserve.ICompressionConfig | boolean; } /** @@ -67,6 +69,9 @@ export class UtilityWebsiteServer { forceSsl: this.options.forceSsl ?? false, securityHeaders: this.options.securityHeaders, + // Compression + compression: this.options.compression, + // PWA manifest manifest: { name: this.options.domain,