Messaging Subsystem

Event Bus & SandBox Routing

Decouple components using an event bus wrapped inside isolated component SandBoxes for automatic listener cleanup, memory safety, and cross-module RPC request handling.

Why Sandboxed Messaging?

Global event emitters in traditional JS applications frequently cause memory leaks because event listeners remain registered even after DOM elements are removed. Huchi OS routes all messaging through a component's SandBox enclosure (x or box) to enforce complete lifecycle control:

Messaging Aspect Standard Event Emitter / React Context Huchi Sandboxed Event Bus
Access Pattern Direct global object import or Context provider nesting SandBox Injected: Accessed directly via x inside the component's module() closure.
Lifecycle Cleanup Manual off() calls inside unmount hooks Automatic Teardown: When a component node is removed, box.dump() strips all listeners.
Communication Models Pub/Sub only (one-way broadcast) Dual Model: Asynchronous Pub/Sub (emit/on) + RPC Request Handling (request/handle).
Decoupling High coupling through direct component references Zero Reference Coupling: Components communicate using string-keyed messages without needing props.

Two Primary Messaging Patterns

The event bus supports two distinct communication protocols accessed via the sandbox wrapper:

1. Asynchronous Broadcast (Pub/Sub)

Use x.emit(event, data) to broadcast events to any listening components without expecting a return value, and x.on(event, callback) to listen for broadcast messages.

2. RPC Request-Response (Request / Handle)

Use x.handle(key, handlerFn) to register a request receiver that processes data and optionally returns a result, and x.request(key, payload) to invoke a registered request handler anywhere across the app.

Production Code Implementation Examples

1. Registering Handlers & Emitting Events in a Modal Controller

Shows how a component uses x.handle() and x.on() inside its module closure to control interface overlays:

import { Component } from "../HuchiOS/Component.js";

export default class Modal extends Component {
    get _ui_html() {
        return `
            <div class="huchi-modal-card relative bg-white w-full max-w-xl rounded-[2.5rem] p-8">
                <button type="button" class="huchi-modal-close-trigger absolute top-6 right-6">
                    <i class="fa-solid fa-xmark"></i>
                </button>
                <div id="modal-content"></div>
            </div>
        `;
    }

    module() { 
        return (x) => {
            let modalRoot = x.mod();
            let container = x.id("modal-content");

            const showModal = (config) => {
                const { content, callback } = config;
                x.manualSwap(container.node(), content);
                modalRoot.removeClass("hidden").addClass("flex modal-visible");
                
                if (typeof callback === "function") {
                    callback(modalRoot.node(), container.node());
                }
            };

            const hideModal = () => {
                modalRoot.removeClass("flex modal-visible").addClass("hidden");
                x.manualSwap(container.node(), "");
            };

            return {
                init: () => {
                    // Register system RPC request handlers on the sandbox
                    x.handle("SYSTEM_MODAL_OPEN", showModal);
                    x.handle("SYSTEM_MODAL_CLOSE", hideModal);

                    // Subscribe to broadcast events
                    x.on("SYSTEM_MODAL_OPEN", showModal);
                    x.on("SYSTEM_MODAL_CLOSE", hideModal);
                    
                    const closeTrigger = modalRoot.node().querySelector('.huchi-modal-close-trigger');
                    if (closeTrigger) {
                        closeTrigger.addEventListener('click', () => {
                            hideModal();
                            // Broadcast event to notify application components of dismissal
                            x.emit("MODAL_DISMISSED", { timestamp: Date.now() });
                        });
                    }
                }               
            };
        };
    }
}

2. Invoking RPC Requests Across Components

Shows how a separate utility component dispatches requests via x.request() without needing a direct import or reference to the target component:

import { Component } from "../HuchiOS/Component.js";
import { dom } from "../HuchiOS/Utils.js";

export default class Dialogue extends Component {
    module() {
        return (x) => {
            const openDialogue = (body, onConfirm, onCancel) => {
                // Dispatch RPC request to the Modal component via the Sandbox
                x.request("SYSTEM_MODAL_OPEN", {
                    size: 'md',
                    content: `
                        <div class="huchi-dialogue flex flex-col">
                            <div class="dialogue-body">${body}</div>
                            <div class="mt-6 flex justify-end gap-2">
                                <button type="button" class="btn-cancel">Cancel</button>
                                <button type="button" class="btn-confirm">Confirm</button>
                            </div>
                        </div>
                    `,
                    callback: (modalParent, modalBody) => {
                        const bodyWrapper = dom(modalBody);
                        
                        bodyWrapper.select('.btn-confirm').on('click', async (e) => {
                            x.stop(e);
                            if (typeof onConfirm === "function") await onConfirm(bodyWrapper);
                            // Close modal by firing request
                            x.request("SYSTEM_MODAL_CLOSE");
                        });

                        bodyWrapper.select('.btn-cancel').on('click', (e) => {
                            x.stop(e);
                            if (typeof onCancel === "function") onCancel(bodyWrapper);
                            x.request("SYSTEM_MODAL_CLOSE");
                        });
                    }
                });
            };

            return {
                init: () => {
                    window.confirmDialogue = (body, onConfirm, onCancel) => {
                        openDialogue(body, onConfirm, onCancel);
                    };
                }
            };
        };
    }
}

SandBox Messaging API Reference

Method Parameters Description
x.emit(event, data) event: String, data: Any Publishes an asynchronous broadcast message across the application event bus.
x.on(event, handler) event: String, handler: Function Subscribes to a broadcast event. Automatically cleaned up when the component dumps.
x.handle(key, handlerFn) key: String, handlerFn: Function Registers an RPC request handler capable of processing requests and returning data.
x.request(key, payload) key: String, payload: Any Executes a registered request handler and returns its output synchronously or via Promise.
x.stop(event) event: Event Utility method to call preventDefault() and stopPropagation() on browser DOM events.