Platform Support
Platform Support
The bloqr-backend includes a platform abstraction layer that enables running in any JavaScript runtime, including:
- Deno (default)
- Node.js (via npm compatibility)
- Cloudflare Workers
- Deno Deploy
- Vercel Edge Functions
- AWS Lambda@Edge
- Web Workers (browser)
- Browsers (with server-side proxy for CORS)
The platform layer is designed to be pluggable - you can easily add or remove fetchers without modifying the core compiler.
Core Concepts
The platform layer provides:
WorkerCompiler- A platform-agnostic compiler that works without file system accessPreFetchedContentFetcher- Supply source content directly instead of fetching from URLsHttpFetcher- Standard Fetch API-based content fetching (works everywhere)CompositeFetcher- Chain multiple fetchers together (pre-fetched takes priority)PlatformDownloader- Handles!#includedirectives and conditional compilation
Edge Runtimes (Generic)
The WorkerCompiler works in any edge runtime or serverless environment that supports the standard Fetch API. The pattern is the same across all platforms:
- Pre-fetch source content on the server (avoids CORS and network restrictions)
- Pass content to the compiler via
preFetchedContent - Configure and compile using the standard API
import { CompositeFetcher, HttpFetcher, type IConfiguration, PreFetchedContentFetcher, WorkerCompiler } from '@jk-com/bloqr-compiler';
// Option 1: Use pre-fetched content (recommended for edge)async function compileWithPreFetched(sourceUrls: string[]): Promise<string[]> { // Fetch all sources const preFetched = new Map<string, string>(); for (const url of sourceUrls) { const response = await fetch(url); preFetched.set(url, await response.text()); }
const compiler = new WorkerCompiler({ preFetchedContent: preFetched }); const config: IConfiguration = { name: 'My Filter List', sources: sourceUrls.map((url) => ({ source: url })), transformations: ['Deduplicate', 'RemoveEmptyLines'], };
return compiler.compile(config);}
// Option 2: Build a custom fetcher chainfunction createCustomCompiler() { const preFetched = new PreFetchedContentFetcher( new Map([ ['local://rules', 'my-custom-rule'], ]), ); const http = new HttpFetcher(); const composite = new CompositeFetcher([preFetched, http]);
return new WorkerCompiler({ customFetcher: composite });}Cloudflare Workers
The compiler runs natively in Cloudflare Workers. A production-ready implementation is available at the repository root in the worker/ directory with a comprehensive web UI in public/.
Quick Start:
# Install dependenciesnpm install
# Run locallydeno task wrangler:dev
# Deploy to Cloudflaredeno task wrangler:deployDeployment: A wrangler.toml configuration file is provided in the repository root for easy deployment via Cloudflare’s Git integration or using wrangler deploy.
⚠️ Important: This project uses wrangler deploy for Cloudflare Workers, NOT deno deploy. While this is a Deno-based project, it deploys to Cloudflare Workers runtime. See the Cloudflare Pages Deployment Guide for Pages-specific configuration.
The production worker (worker/worker.ts) includes:
- Interactive Web UI at
/(seepublic/index.html) - API Testing Interface at
/test.html - JSON API at
POST /compile - Streaming API at
POST /compile/streamwith Server-Sent Events - Batch API at
POST /compile/batch - Async API at
POST /compile/asyncfor queue-based processing - Batch Async API at
POST /compile/batch/asyncfor queue-based batch processing - Metrics at
GET /metrics - Pre-fetched content support to bypass CORS restrictions
- Caching with KV storage
- Rate limiting
- Request body size limits (DoS protection)
- Request deduplication
- Cloudflare Queue integration for async compilation
Request Body Size Limits:
The worker validates request body sizes to prevent denial-of-service attacks via large payloads. The default limit is 1MB.
# Configure in environment (megabytes)MAX_REQUEST_BODY_MB="2" # Set to 2MB limitRequests exceeding the limit receive a 413 Payload Too Large response:
{ "error": "Request body size (2097152 bytes) exceeds maximum allowed size (1048576 bytes)"}Cloudflare Queue Support:
The worker supports asynchronous compilation through Cloudflare Queues, allowing you to:
- Offload long-running compilations to background processing
- Process batch operations without blocking
- Pre-warm the cache with popular filter lists
- Bypass rate limits for queued requests
📚 Queue Support Documentation - Complete guide for using async compilation
Tail Worker for Observability:
A Cloudflare Tail Worker is included for advanced logging and monitoring. The tail worker captures logs, exceptions, and events from the main worker in real-time.
# Deploy the tail workerdeno task wrangler:tail:deploy
# View tail worker logsdeno task wrangler:tail:logsFeatures:
- Log Persistence: Store logs in Cloudflare KV
- Error Forwarding: Send critical errors to webhooks (Slack, Discord, etc.)
- Structured Events: Format logs for external systems
- Automatic Cleanup: Configurable log retention
📚 Tail Worker Documentation - Complete guide for setup and configuration
import { type IConfiguration, WorkerCompiler } from '@jk-com/bloqr-compiler';
export default { async fetch(request: Request): Promise<Response> { // Pre-fetch content on the server where there are no CORS restrictions const sourceContent = await fetch('https://example.com/filters.txt').then((r) => r.text());
const compiler = new WorkerCompiler({ preFetchedContent: { 'https://example.com/filters.txt': sourceContent, }, });
const configuration: IConfiguration = { name: 'My Filter List', sources: [ { source: 'https://example.com/filters.txt' }, ], transformations: ['Deduplicate', 'RemoveEmptyLines'], };
const result = await compiler.compile(configuration);
return new Response(result.join('\n'), { headers: { 'Content-Type': 'text/plain' }, }); },};Using the Web UI:
- Visit the root URL of your deployed worker
- Use Simple Mode for quick filter list compilation
- Use Advanced Mode for JSON configuration
- Use Test Page to test API endpoints directly
- View real-time progress with streaming compilation
Web Workers
Use WorkerCompiler in Web Workers for background compilation:
import { type IConfiguration, WorkerCompiler } from '@jk-com/bloqr-compiler';
self.onmessage = async (event) => { const { configuration, preFetchedContent } = event.data;
const compiler = new WorkerCompiler({ preFetchedContent, events: { onProgress: (progress) => { self.postMessage({ type: 'progress', progress }); }, }, });
const result = await compiler.compile(configuration); self.postMessage({ type: 'complete', rules: result });};Browser Usage
For browser environments, pre-fetch all source content server-side to avoid CORS issues:
import { type IConfiguration, WorkerCompiler } from '@jk-com/bloqr-compiler';
// Fetch sources through your server proxy to avoid CORSasync function fetchSources(urls: string[]): Promise<Map<string, string>> { const content = new Map<string, string>(); for (const url of urls) { const response = await fetch(`/api/proxy?url=${encodeURIComponent(url)}`); content.set(url, await response.text()); } return content;}
// Usageconst sources = await fetchSources([ 'https://example.com/filters.txt',]);
const compiler = new WorkerCompiler({ preFetchedContent: Object.fromEntries(sources),});
const configuration: IConfiguration = { name: 'Browser Compiled List', sources: [ { source: 'https://example.com/filters.txt' }, ],};
const rules = await compiler.compile(configuration);Platform API Reference
WorkerCompiler
interface WorkerCompilerOptions { // Pre-fetched content (Map or Record) preFetchedContent?: Map<string, string> | Record<string, string>;
// Custom content fetcher (for advanced use cases) customFetcher?: IContentFetcher;
// Options for the default HTTP fetcher (timeout, user agent, custom headers) httpOptions?: IHttpFetcherOptions;
// Tracing context for diagnostics and OpenTelemetry integration tracingContext?: TracingContext;
// Injectable dependencies (for testing/customization) dependencies?: WorkerCompilerDependencies;}
class WorkerCompiler { constructor(options?: WorkerCompilerOptions);
// Compile and return rules compile(configuration: IConfiguration): Promise<string[]>;
// Compile with optional benchmarking metrics compileWithMetrics( configuration: IConfiguration, benchmark?: boolean, ): Promise<WorkerCompilationResult>;}IContentFetcher
Implement this interface to create custom content fetchers:
interface IContentFetcher { canHandle(source: string): boolean; fetch(source: string): Promise<string>;}Custom Fetchers
You can implement custom fetchers for specialized use cases:
import { CompositeFetcher, HttpFetcher, type IContentFetcher, WorkerCompiler } from '@jk-com/bloqr-compiler';
// Example: Redis-backed cache fetcherclass RedisCacheFetcher implements IContentFetcher { constructor(private redis: RedisClient, private ttl: number) {}
canHandle(source: string): boolean { return source.startsWith('http://') || source.startsWith('https://'); }
async fetch(source: string): Promise<string> { const cached = await this.redis.get(`filter:${source}`); if (cached) return cached;
const response = await fetch(source); const content = await response.text();
await this.redis.setex(`filter:${source}`, this.ttl, content); return content; }}
// Example: S3/R2-backed storage fetcherclass S3StorageFetcher implements IContentFetcher { constructor(private bucket: S3Bucket) {}
canHandle(source: string): boolean { return source.startsWith('s3://'); }
async fetch(source: string): Promise<string> { const key = source.replace('s3://', ''); const object = await this.bucket.get(key); return object?.text() ?? ''; }}
// Chain fetchers together - first match winsconst compiler = new WorkerCompiler({ customFetcher: new CompositeFetcher([ new RedisCacheFetcher(redis, 3600), new S3StorageFetcher(bucket), new HttpFetcher(), ]),});This pluggable architecture allows you to:
- Add caching layers (Redis, KV, memory)
- Support custom protocols (S3, R2, database)
- Implement authentication/authorization
- Add logging and metrics
- Mock sources for testing