Assets - Multi-source static file serving middleware.

Implements transparent asset serving with automatic source detection across three deployment modes: SEA embedded resources, JavaScript variables, and traditional filesystem. Zero-configuration operation with consistent security model and performance characteristics.

Priority-Based Mode Selection:

  1. SEA mode: Single Executable Application embedded resources (fastest)
  2. Global mode: JavaScript variables in globalThis.RavenJS.assets (fast)
  3. FileSystem mode: Traditional file system serving (flexible)

Security Model:

  • Only paths starting with '/' are served (public assets)
  • Path traversal prevention across all modes
  • Mode isolation prevents cross-contamination
  • Internal files never exposed

Error Handling:

  • Errors collected in ctx.errors for observability
  • Graceful fallthrough for missing assets
  • No pipeline disruption on failures
import { Assets } from '@raven-js/wings/server/middlewares/assets';

// Zero-config transparent operation
const assets = new Assets();
router.use(assets);

// Works identically across all deployment modes:
// • Development: serves from ./public/
// • SEA bundle: serves from embedded resources
// • JS bundle: serves from globalThis.RavenJS.assets
// Custom assets directory (filesystem mode only)
const assets = new Assets({ assetsDir: 'static' });
router.use(assets);

// Directory structure:
// static/
// ├── css/app.css → GET /css/app.css
// ├── js/bundle.js → GET /js/bundle.js
// └── images/logo.png → GET /images/logo.png
// SEA mode (automatic detection)
const assets = new Assets(); // assetsDir ignored
router.use(assets);

// Assets served from embedded SEA resources
// Fastest mode: in-memory access, no I/O
// Manifest: @raven-js/assets.json lists public assets
// JavaScript-embedded assets
globalThis.RavenJS = {
assets: {
'/css/app.css': 'body { margin: 0; }',
'/js/app.js': 'console.log("loaded");',
'/api/data.json': '{"version": "1.0"}'
}
};

const assets = new Assets();
router.use(assets);
// Fast mode: direct memory access, automatic type handling

Hierarchy

  • Middleware
    • Assets

Constructors

  • Create Assets middleware with automatic source detection.

    Performs environment probing to determine optimal asset serving mode. Configuration applies only to filesystem mode - other modes ignore options and operate based on environment capabilities.

    Parameters

    • Optionaloptions: AssetsOptions = {}

      Configuration options

    Returns Assets

    When assetsDir is invalid (non-string or empty)

    // Automatic mode detection with sensible defaults
    const assets = new Assets();
    router.use(assets);
    // → Uses 'public' directory in filesystem mode
    // → Auto-detects SEA/global modes when available
    // Custom assets directory and identifier
    const assets = new Assets({
    assetsDir: 'static',
    identifier: 'my-app/assets'
    });
    router.use(assets);
    // Same constructor works across deployment modes
    const assets = new Assets({ assetsDir: 'custom' });

    // Development: serves from ./custom/
    // SEA bundle: ignores assetsDir, uses embedded assets
    // JS bundle: ignores assetsDir, uses globalThis.RavenJS.assets

Properties

assetsDir: string
assetsList: string[] = []

Cached list of available assets.

Contains web-normalized paths (starting with '/') of all discoverable assets. Population strategy varies by mode: immediate for SEA/global, asynchronous for filesystem.

assetsPath: string = null

Full resolved path to assets directory.

Only used in filesystem mode for file operations. Null in SEA/global modes where assets are served from memory sources.

mode:
    | "sea"
    | "global"
    | "filesystem"
    | "uninitialized" = "uninitialized"

Current asset serving mode.

Reflects the detected and active asset source. Set during initialization based on environment capabilities and available sources.

Accessors

  • get handler(): Handler
  • Gets the middleware handler function.

    Returns the function that will be executed when the middleware runs. This getter provides read-only access to the handler function.

    Returns Handler

    The handler function

    const middleware = new Middleware((ctx) => {
    console.log('Processing request');
    });

    const handler = middleware.handler;
    console.log(typeof handler); // 'function'

    // Can be used for testing or introspection
    if (handler.toString().includes('console.log')) {
    console.log('This middleware includes logging');
    }
  • get identifier(): string
  • Gets the middleware identifier.

    Returns the identifier string or null if no identifier was set. This getter provides read-only access to the identifier.

    Returns string

    The identifier or null if not set

    // Middleware with identifier
    const authMiddleware = new Middleware((ctx) => {}, 'auth');
    console.log(authMiddleware.identifier); // 'auth'

    // Middleware without identifier
    const loggingMiddleware = new Middleware((ctx) => {});
    console.log(loggingMiddleware.identifier); // null

    // Use identifier for conditional logic
    if (middleware.identifier === 'authentication') {
    console.log('This is an authentication middleware');
    }

Methods

  • Executes the middleware handler with the given context.

    This method invokes the middleware handler function, passing the context object as the first argument. The method supports both synchronous and asynchronous handlers, always returning a Promise.

    Execution Flow:

    1. Validates that ctx is a Context instance
    2. Calls the handler function with the context
    3. Returns a Promise that resolves when the handler completes
    4. Propagates any errors thrown by the handler

    Error Handling: Any errors thrown by the handler (synchronous or asynchronous) are propagated to the caller. This allows middleware errors to be handled by the calling code.

    Parameters

    • ctx: Context

      The request/response context

    Returns Promise<void>

    Promise that resolves when the handler completes

    When ctx is not a Context instance

    Any error thrown by the middleware handler

    const middleware = new Middleware((ctx) => {
    console.log(`Processing ${ctx.method} request to ${ctx.path}`);
    ctx.data.processedAt = new Date().toISOString();
    });

    const url = new URL('http://localhost/api/users');
    const ctx = new Context('GET', url, new Headers());

    // Execute middleware
    await middleware.execute(ctx);
    console.log(ctx.data.processedAt); // ISO timestamp

    // Async middleware
    const asyncMiddleware = new Middleware(async (ctx) => {
    await new Promise(resolve => setTimeout(resolve, 100));
    ctx.data.asyncProcessed = true;
    });

    await asyncMiddleware.execute(ctx);
    console.log(ctx.data.asyncProcessed); // true

    // Error handling
    const errorMiddleware = new Middleware((ctx) => {
    throw new Error('Middleware error');
    });

    try {
    await errorMiddleware.execute(ctx);
    } catch (error) {
    console.error('Middleware failed:', error.message);
    }

    // Invalid context
    try {
    await middleware.execute(null);
    } catch (error) {
    console.error(error.message); // "Context must be a Context instance"
    }
  • Checks if this middleware has the same identifier as another middleware.

    This method is used for duplicate detection and middleware comparison. Two middleware are considered to have the same identifier if:

    • Both have non-null identifiers
    • Both identifiers are exactly equal (string comparison)

    Note: Middleware with null identifiers are never considered equal, even if both have null identifiers.

    Parameters

    • other: Middleware

      The other middleware to compare with

    Returns boolean

    True if both middlewares have the same non-null identifier

    const auth1 = new Middleware((ctx) => {}, 'authentication');
    const auth2 = new Middleware((ctx) => {}, 'authentication');
    const logging = new Middleware((ctx) => {}, 'logging');
    const generic = new Middleware((ctx) => {});

    // Same identifier
    auth1.hasSameIdentifier(auth2); // true
    auth2.hasSameIdentifier(auth1); // true

    // Different identifiers
    auth1.hasSameIdentifier(logging); // false
    logging.hasSameIdentifier(auth1); // false

    // Null identifiers (never equal)
    auth1.hasSameIdentifier(generic); // false
    generic.hasSameIdentifier(auth1); // false
    generic.hasSameIdentifier(new Middleware((ctx) => {})); // false

    // Invalid parameters
    auth1.hasSameIdentifier(null); // false
    auth1.hasSameIdentifier({}); // false
    auth1.hasSameIdentifier('string'); // false

    // Use for duplicate prevention
    function addMiddlewareIfNotExists(middlewareList, newMiddleware) {
    const hasDuplicate = middlewareList.some(existing =>
    existing.hasSameIdentifier(newMiddleware)
    );

    if (!hasDuplicate) {
    middlewareList.push(newMiddleware);
    }
    }