npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hexaijs/plugin-application-builder

v0.1.0

Published

Build plugin for generating ApplicationBuilder code from decorated handlers

Readme

@hexaijs/plugin-application-builder

Build plugin for generating ApplicationBuilder code from decorated handlers

Overview

@hexaijs/plugin-application-builder eliminates the boilerplate of manually registering every handler with ApplicationBuilder. Instead of maintaining a growing list of .withCommandHandler() and .withEventHandler() calls, you decorate your handler classes and let the build tool generate the registration code automatically.

The plugin works at build time by:

  1. Scanning your TypeScript files for handler classes decorated with marker decorators
  2. Extracting metadata using TypeScript's AST (Abstract Syntax Tree) parser
  3. Generating a createApplicationBuilder() function with all handlers properly registered

This approach keeps your handler files self-documenting (the decorator shows what type of handler it is) while centralizing the wiring in a generated file that stays in sync automatically.

Installation

npm install @hexaijs/plugin-application-builder

Peer dependencies:

  • @hexaijs/core
  • @hexaijs/application
  • typescript ^5.0.0

Core Concepts

Marker Decorators

The package provides three decorators that serve as markers for the code generator. These decorators have no runtime behavior - they simply tag classes for discovery during the build process.

import {
    CommandHandlerMarker,
    QueryHandlerMarker,
    EventHandlerMarker
} from "@hexaijs/plugin-application-builder";

CommandHandlerMarker - Marks a class as a command handler and links it to its request type:

import { CommandHandlerMarker } from "@hexaijs/plugin-application-builder";
import { CreateOrderRequest } from "./request";

@CommandHandlerMarker(CreateOrderRequest)
export class CreateOrderHandler extends BaseHandler<
    CreateOrderRequest,
    CreateOrderResponse,
    OrderApplicationContext
> {
    protected async doExecute(
        request: CreateOrderRequest,
        ctx: OrderApplicationContext
    ): Promise<CreateOrderResponse> {
        // Implementation
    }
}

QueryHandlerMarker - Marks a class as a query handler:

import { QueryHandlerMarker } from "@hexaijs/plugin-application-builder";
import { GetOrderQuery } from "./query";

@QueryHandlerMarker(GetOrderQuery)
export class GetOrderHandler extends BaseHandler<
    GetOrderQuery,
    OrderDto,
    OrderApplicationContext
> {
    protected async doExecute(
        query: GetOrderQuery,
        ctx: OrderApplicationContext
    ): Promise<OrderDto> {
        // Implementation
    }
}

EventHandlerMarker - Marks a class as an event handler:

import { Message } from "@hexaijs/core";
import { EventHandlerMarker } from "@hexaijs/plugin-application-builder";

@EventHandlerMarker()
export class HandleOrderPlaced extends BaseEventHandler<OrderApplicationContext> {
    canHandle(message: Message): message is OrderPlaced {
        return message.getMessageType() === OrderPlaced.getType();
    }

    async handle(
        event: OrderPlaced,
        ctx: OrderApplicationContext
    ): Promise<void> {
        const payload = event.getPayload();
        // React to event
    }
}

For event handlers that need a unique identifier (useful for idempotency tracking), provide a name option:

@EventHandlerMarker({ name: "send-order-confirmation" })
export class SendOrderConfirmationEmail extends BaseEventHandler<OrderApplicationContext> {
    // ...
}

Configuration

Create an application.config.ts file in your package root:

import { RawBuildPluginConfig } from "@hexaijs/plugin-application-builder";

const config: RawBuildPluginConfig = {
    // Glob patterns to find handler files
    handlers: [
        "src/commands/**/*.ts",
        "src/queries/**/*.ts",
        "src/event-handlers/**/*.ts"
    ],

    // Import path for ApplicationBuilder in generated code
    applicationBuilderImportPath: "@/application-builder",

    // Output file path (optional, defaults to "src/.generated/application-builder.ts")
    outputFile: "src/.generated/application-builder.ts"
};

export default config;

| Option | Required | Default | Description | |--------|----------|---------|-------------| | handlers | Yes | - | Glob patterns for files containing decorated handlers | | applicationBuilderImportPath | Yes | - | Import path for your ApplicationBuilder class | | outputFile | No | src/.generated/application-builder.ts | Where to write the generated code |

Code Generation

Run the generator using the CLI:

npx generate-app-builder

Or specify a custom path:

npx generate-app-builder --context-path ./packages/my-bounded-context

The generator produces a file like:

// src/.generated/application-builder.ts (auto-generated)
import { ApplicationBuilder } from '@/application-builder';
import { CreateOrderHandler } from '../commands/create-order/handler';
import { CreateOrderRequest } from '../commands/create-order/request';
import { GetOrderHandler } from '../queries/get-order/handler';
import { GetOrderQuery } from '../queries/get-order/query';
import { HandleOrderPlaced } from '../event-handlers/handle-order-placed';

export function createApplicationBuilder(): ApplicationBuilder {
  return new ApplicationBuilder()
    .withCommandHandler(CreateOrderRequest, () => new CreateOrderHandler())
    .withQueryHandler(GetOrderQuery, () => new GetOrderHandler())
    .withEventHandler(() => new HandleOrderPlaced());
}

Programmatic API

For custom build scripts, use the generateApplicationBuilder function:

import { generateApplicationBuilder } from "@hexaijs/plugin-application-builder";

await generateApplicationBuilder("./packages/order", {
    configFile: "application.config.ts"  // optional
});

Usage

Integrating into Your Build

Add the generator to your build scripts:

{
  "scripts": {
    "prebuild": "generate-app-builder",
    "build": "tsc"
  }
}

This ensures the generated file is always up-to-date before compilation.

Using the Generated Builder

Import and use the generated function in your application setup:

import { createApplicationBuilder } from "./.generated/application-builder";

// The generated builder has all handlers registered
const builder = createApplicationBuilder();

// Add your application context and build
const application = builder
    .withApplicationContext(new OrderApplicationContext())
    .build();

Path Alias Support

The generator respects TypeScript path aliases defined in your tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

When handlers import request classes using aliases (e.g., import { CreateOrderRequest } from "@/commands/create-order/request"), the generator correctly resolves these paths.

Error Handling

The generator validates your configuration and throws descriptive errors:

DuplicateCommandHandlerError - Thrown when multiple handlers are registered for the same command:

Duplicate command handlers for "CreateOrderRequest": CreateOrderHandler, AnotherCreateOrderHandler

DuplicateQueryHandlerError - Thrown when multiple handlers are registered for the same query.

DuplicateEventHandlerError - Thrown when multiple event handlers share the same name option.

MessageClassNotFoundError - Thrown when a decorator references a class that cannot be found:

Cannot find "CreateOrderRequest" - not imported and not defined in "src/commands/create-order/handler.ts"

API Highlights

| Export | Description | |--------|-------------| | generateApplicationBuilder(path, options?) | Programmatic API to run code generation | | CommandHandlerMarker | Decorator to mark command handlers | | QueryHandlerMarker | Decorator to mark query handlers | | EventHandlerMarker | Decorator to mark event handlers | | EventHandlerOptions | Type for event handler decorator options | | DuplicateCommandHandlerError | Error for duplicate command handler registration | | DuplicateQueryHandlerError | Error for duplicate query handler registration | | DuplicateEventHandlerError | Error for duplicate event handler registration | | HandlerMetadataExtractor | Class for extracting handler metadata from TypeScript files |

See Also