Events

Gebweb's event bus is in-process pub/sub: publishers fire named events with a payload dict; subscribers register methods with @On("event-name") and receive an Event context.

Dispatch is synchronous - gebweb.publish returns once every subscriber has finished. Subscriber exceptions are collected and re-thrown as a single aggregated error after the dispatch, so one buggy listener can't silently break the chain.

Distributed transports (Redis, NATS, ...) will land as sibling packages that reuse the same subscriber registry through a Transport interface. v1 is single-process only.

Wiring

import gebweb;

let app = gebweb.app([HomeController()]);
gebweb.useEvents(app);
gebweb.registerEventSubscribers(app, [WelcomeMailer(), AuditLog()]);

Publishing

Fire from any code holding the app reference:

class SignupController {
    @Post("/signup")
    func signup(SignupForm form): dict<string, any> {
        let user = userRepo.create(form);
        gebweb.publish(app, "user.created", {"id": user.id, "email": user.email});
        return {"id": user.id};
    }
}

The payload is any JSON-serialisable dict. Subscribers receive an Event context:

Field Type Description
name string The event name passed to publish.
payload dict<string, any> The published payload.
app any The GebwebApp; subscribers use this to resolve DI or enqueue follow-on jobs.

Subscribing

class WelcomeMailer {
    @On("user.created")
    func send(gebweb.Event ev): void {
        let email = ev.payload["email"] as string;
        mailer.welcome(email);
    }
}

class AuditLog {
    @On("user.created")
    @On("user.deleted")
    func record(gebweb.Event ev): void {
        auditService.write(ev.name, ev.payload);
    }
}

A single method may stack multiple @On decorators to subscribe to several events. Subscriber order within an event is the registration order.

When a subscriber fails

Publishing is synchronous: gebweb.publish runs every subscriber in turn and only returns after the last one finishes. If a subscriber throws, the framework remembers the failure and keeps running the rest. After they've all had a turn, a single RuntimeError is thrown back to the publisher with every subscriber's error joined into one message.

That means the publisher only needs one catch:

try {
    gebweb.publish(app, "user.created", {"id": "u-1"});
} catch (RuntimeError e) {
    log.error("event delivery had failures", {"reason": e.message});
}

Successful subscribers always run, so a buggy listener can't silently break the chain. The publisher sees the combined failure and can decide whether to retry, log, or fail the request.

At-least-once delivery

Events are best-effort, in-process, and synchronous. For guaranteed delivery (retry on failure, survives process death), enqueue a job from the subscriber:

class OrderListener {
    @On("order.placed")
    func sendReceipt(gebweb.Event ev): void {
        /* Hand off to the job queue, which retries with backoff. */
        gebweb.enqueue(ev.app, "send-receipt", ev.payload);
    }
}

Reference

Helper Purpose
gebweb.useEvents(app) Attach the in-process event bus.
gebweb.registerEventSubscribers(app, instances) Discover @On subscribers on each instance.
gebweb.publish(app, name, payload) Synchronously fire name to every subscriber.
gebweb.Event Subscriber context: name, payload, app.
gebweb.events.subscriberCount(config, name) Subscriber count for name (mostly tests).