mcpField
B
boxlang-interceptors

boxlang-interceptors

github.com/ortus-boxlangDevelopment043 installsUpdated 1w ago
Repository

About boxlang-interceptors

A BoxLang skill for building event-driven interceptors that keep cross-cutting logic separate from business code. It covers registration, event handling, execution priorities, and asynchronous announcements.

Key features

  1. 01Named-event listener handlers
  2. 02Synchronous and asynchronous announcements
  3. 03Priority-based execution order
  4. 04Application and module registration
  5. 05Class-instantiation lifecycle events

Use cases

  1. 01Add logging or security checks around operations
  2. 02Validate or sanitize data before saving
  3. 03Run background work after an event
  4. 04Inject dependencies or audit class creation

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/ortus-boxlang/skills --skill boxlang-interceptors

After installing, type/boxlang-interceptorsin Claude Code to confirm it loaded.

Details

From the project README

BoxLang Interceptors

BoxLang is the AI-native software productivity platform for building, modernizing and running applications, with developers and AI agents working together.

Overview

BoxLang interceptors provide Aspect-Oriented Programming (AOP) capabilities through an event-driven announcement system. Interceptors listen to named events and execute cross-cutting logic — logging, security, caching, validation — without coupling that logic to business code. Priority controls execution order; async announcements run in background threads.

Creating an Interceptor

/**
 * interceptors/RequestLogger.bx
 */
class {

    function onRequestStart( struct interceptData ) {
        request.startTime = getTickCount()
        writeLog( text: "Request started: #cgi.script_name#", type: "information" )
    }

    function onRequestEnd( struct interceptData ) {
        var duration = getTickCount() - request.startTime
        writeLog( text: "Completed in #duration#ms: #cgi.script_name#", type: "information" )
    }
}

Registering Interceptors

Application.bx (application-scoped)
class {
    this.name = "MyApp"

    this.interceptors = [
        { class: "interceptors.RequestLogger" },
        { class: "interceptors.SecurityCheck", priority: 1 },
        { class: "interceptors.CacheWarmer",   priority: 10 }
    ]

    function onApplicationStart() {
        announce( "onApplicationStart", { timestamp: now() } )
    }
}
Programmatic Registration (BIF)
// Register at runtime using BoxRegisterInterceptor()
BoxRegisterInterceptor( "interceptors.RequestLogger" )

// With named options
BoxRegisterInterceptor(
    class: "interceptors.SecurityCheck",
    priority: 1,
    properties: { secret: getSetting( "JWT_SECRET" ) }
)

Announcing Events

// Synchronous — all listeners execute before returning
announce( "userCreated", {
    userID: newUser.id,
    email: newUser.email,
    timestamp: now()
} )

// Asynchronous — runs in a background thread
announceAsync( "emailQueued", {
    to: user.email,
    subject: "Welcome"
} )

Common Interceptor Patterns

Pre / Post Operation Hooks
class DataSanitizer {

    /** Sanitize data before save */
    function preDataSave( interceptData ) {
        var data = interceptData.data

        data.each( ( key, value ) -> {
            if ( isSimpleValue( value ) ) data[key] = trim( value )
        } )

        if ( data.keyExists( "description" ) ) {
            data.description = htmlEditFormat( data.description )
        }
    }

    /** Clear cache after save */
    function postDataSave( interceptData ) {
        cacheRemove( "#interceptData.entityName#_#interceptData.entityID#" )
    }
}
Validation Guard
class ValidationInterceptor {

    property name="validationService" inject="ValidationService"

    function preEntitySave( interceptData ) {
        var result = validationService.validate( interceptData.entity )

        if ( result.hasErrors() ) {
            interceptData.abortProcessing = true
            throw(
                type: "ValidationException",
                message: "Validation failed",
                detail: jsonSerialize( result.getAllErrors() )
            )
        }
    }
}
Security Guard
class SecurityInterceptor {

    property name="authService" inject="AuthService"

    function preHandler( interceptData ) {
        // Skip public areas
        if ( listFindNoCase( "home,auth", interceptData.handler ) ) return

        if ( !authService.isLoggedIn() ) {
            interceptData.event.overrideEvent( "auth.login" )
            interceptData.abortProcessing = true
        }
    }
}
Async Background Processing
class AsyncProcessor {

    /** Kick off work asynchronously */
    function onEmailQueued( interceptData ) {
        announceAsync( "processEmail", {
            to:      interceptData.to,
            subject: interceptData.subject,
            body:    interceptData.body
        } )
    }

    /** Worker — runs in background thread */
    function onProcessEmail( interceptData ) {
        mailService.send(
            to:      interceptData.to,
            subject: interceptData.subject,
            body:    interceptData.body
        )
    }
}
Chain of Responsibility with Priority
// Lower number = runs earlier
this.interceptors = [
    { class: "interceptors.RequestLogger", priority: 1  },
    { class: "interceptors.AuthCheck",     priority: 2  },
    { class: "interceptors.RateLimiter",   priority: 3  }
]

Interceptor State

class RequestTracker {

    variables.requestMap = {}

    function onRequestStart( interceptData ) {
        var id = createUUID()
        variables.requestMap[id] = { startTime: getTickCount(), path: cgi.script_name }
        interceptData.requestID = id
    }

    function onRequestEnd( interceptData ) {
        var id   = interceptData.requestID
        var data = variables.requestMap[id]
        writeLog( "Request #id# done in #getTickCount() - data.startTime#ms" )
        structDelete( variables.requestMap, id )
    }
}

Module Registration

Modules register interceptors in ModuleConfig.bx:

// ModuleConfig.bx
class {

    function configure() {
        interceptors = [
            { class: "#moduleMapping#.interceptors.MyInterceptor", priority: 5 }
        ]
    }
}

Class Instantiation Events (v1.18+)

EventWhenData
afterBoxClassCreationInstance fully defined (pseudo-constructor ran, interfaces and abstract methods validated) but before init(). Also fires for noInit creations such as createObject() and deserialization. Never fires for super classes in an extends chain.instance, className, noInit, context
afterBoxClassInitAfter init() (or the implicit constructor) completes. Does not fire for noInit creations or when init() throws.instance, result, className, context

Use them for dependency injection, auditing and observability.

Best Practices

  • Single responsibility — one interceptor, one concern
  • Keep interceptors fast — move slow work to announceAsync()
  • Always try/catch inside handlers — a thrown exception inside onUserCreated should never break user creation
  • Conditional early return — skip irrelevant entities or events at the top of the method
  • Priority — register security interceptors at priority 1 (lowest number), logging at high numbers
  • Avoid recursion — never announce the same event you are listening to
// ✅ Fast: log then hand off heavy work
function onUserCreated( interceptData ) {
    writeLog( "User created: #interceptData.email#" )
    announceAsync( "sendWelcomeEmail", interceptData )
}

// ✅ Guard with early return
function onDataSave( interceptData ) {
    if ( interceptData.entityName != "User" ) return
    // user-specific logic
}

// ✅ Error-safe side effects
function onUserCreated( interceptData ) {
    try {
        sendEmail( interceptData.email )
    } catch ( any e ) {
        writeLog( "Email error: #e.message#" )
        // Do NOT rethrow — don't break user creation
    }
}

// ❌ Recursion: triggers same interceptor again
function onDataSave( interceptData ) {
    announce( "onDataSave", data )   // infinite loop
}

Comments

Markdown supported