BRAIDGROUP
RESEARCH & DEV
55. Framework Docs

Sessions and Caching

Junction provides built-in session management (cookie-based, in-memory store with configurable TTL) and response caching (LRU-eviction with TTL, Vary header support, ETag generation, and Cache-Control parsing). Both are optional middleware that integrate directly with the app object.

Session Management

The session module provides an in-memory store with configurable TTL, cookie integration, and secure defaults. Sessions are identified by a session_id cookie (default name) that is httpOnly, secure, and SameSite=Lax by default.

Creating a Session Store

// Default store: 3600s TTL
let store = session.new_store(3600);

// Custom configuration
let store = session.new_store_with_config({
    "ttl_seconds": 7200,
    "cookie_name": "myapp_session",
    "cookie_path": "/app",
    "cookie_domain": ".example.com",
    "secure": true,
    "http_only": true,
    "same_site": "Strict"
});

Session CRUD Operations

// Get or create session
let data = session.get_session(store, session_id);
// Returns the data object (creates a new session if none exists or expired)

// Set session data
session.set_session(store, session_id, {"user_id": 42, "role": "admin"});

// Get/set/delete individual values
session.set_session_value(store, session_id, "theme", "dark");
let theme = session.get_session_value(store, session_id, "theme");
session.delete_session_value(store, session_id, "theme");

// Destroy session
session.destroy_session(store, session_id);

// Regenerate session ID (prevents session fixation)
let new_id = session.regenerate_session_id(store, session_id);

// Clean expired sessions
session.clean_expired_sessions(store);

// Count active sessions
let count = session.session_count(store);

Session User Flow

junction.route(app, "POST", "/login", fn(req: Request) -> Response {
    let body = junction.parse_json(req.body);
    let username = body["username"];
    let password = body["password"];

    // Validate credentials
    if (username != "admin" || password != "secret") {
        return junction.error("LOGIN_FAILED", "Invalid credentials", 401);
    }

    // Create session
    let session_id = "sess_" + std.crypto.random_hex(16);
    session.set_session(app.sessions, session_id, {
        "user_id": 1,
        "username": username,
        "role": "admin"
    });

    let res = junction.json({"login": "ok"}, 200);
    junction.set_cookie(res, "session_id", session_id, {
        "path": "/",
        "http_only": true,
        "secure": true,
        "same_site": "Lax",
        "max_age": 3600
    });
    return res;
});

Session Middleware

The session_middleware(app) automatically resolves the session from the incoming cookie and attaches it to req.auth_context["session_data"]. The save_session() function persists the modified data and refreshes the cookie on the response.

junction.use_middleware(app, junction.session_middleware(app));

junction.route(app, "GET", "/profile", fn(req: Request) -> Response {
    let session_data = req.auth_context["session_data"];
    if (session_data == null || session_data["user_id"] == null) {
        return junction.error("NOT_LOGGED_IN", "Login required", 401);
    }
    let res = junction.json({"user_id": session_data["user_id"]}, 200);
    return junction.save_session(app, req, res);
});

Session Configuration Options

FieldDefaultDescription
ttl_seconds3600Session time-to-live in seconds
cookie_namesession_idName of the session cookie
cookie_path/Cookie path scope
cookie_domain""Cookie domain scope
securetrueOnly send cookie over HTTPS
http_onlytruePrevent JavaScript access to cookie
same_siteLaxSameSite policy (Strict, Lax, None)

Response Caching

The cache module provides an in-memory LRU-eviction cache for HTTP responses. It supports TTL-based expiration, Vary header differentiation, ETag generation, and Cache-Control header parsing. The cache is automatically created as app.cache_store when the app is created.

Creating a Cache Store

// Default: 300s TTL, 1000 max entries
let cache = cache.new_cache();

// Custom configuration
let cache = cache.new_cache_with_opts(600, 5000);

Caching Responses

junction.route(app, "GET", "/expensive-data", fn(req: Request) -> Response {
    // Check cache first
    let cached = cache.get_cached_response(app.cache_store, req.path, req.query);
    if (cached != null) {
        return cached;
    }

    // Generate response
    let data = compute_expensive_result();
    let res = junction.json(data, 200);

    // Store in cache with 60s TTL
    cache.cache_response(app.cache_store, req.path, req.query, res, 60);
    return res;
});

Cache Invalidation

// Invalidate a specific cache entry
cache.invalidate(app.cache_store, "/expensive-data");

// Invalidate all entries with a given path prefix
cache.invalidate_by_path(app.cache_store, "/api/v1");

// Clear entire cache
cache.invalidate_all(app.cache_store);

Vary Header Support

When a cached response has a Vary header, the cache stores the vary header values and validates them on lookup. Use get_cached_with_vary() to check vary headers explicitly.

let cached = cache.get_cached_with_vary(
    app.cache_store, req.path, req.query, req.headers
);

ETag Generation

let etag = cache.generate_etag_for_response(response);
// MD5 hash of body + serialized headers + status code

Cache Statistics

let stats = cache.cache_stats(app.cache_store);
print(stats["entries"]);      // Current entry count
print(stats["hit_count"]);    // Total cache hits
print(stats["miss_count"]);   // Total cache misses
print(stats["max_entries"]);  // Maximum entries before eviction

Cache-Control Header Parsing

let cc = cache.parse_cache_control("public, max-age=3600, must-revalidate");
if (cc["public"]) { /* cacheable by shared caches */ }
if (cc["max_age"] > 0) { /* freshness lifetime */ }
if (cc["no_cache"]) { /* must revalidate */ }
if (cc["no_store"]) { /* do not cache */ }

Cache Middleware

The cache_middleware(app) automatically serves cached responses for GET and HEAD requests before they reach the router.

junction.use_middleware(app, junction.cache_middleware(app));

// Later, in a handler, store the response:
junction.cache_response(app, req, res, 120);

Cache Eviction Policy

When the cache exceeds max_entries, the oldest entry (by created_at timestamp) is evicted. The eviction check runs on each new cache write. Both the global hit and miss counters are maintained for observability.

LRU Record Keeping

// Direct access to cache internals
let entry_count = std.collections.length(app.cache_store["entries"]);
let hit_ratio = app.cache_store["hit_count"] /
    (app.cache_store["hit_count"] + app.cache_store["miss_count"]);