Skip to content

createEventFlowsApp()

Creates a fully-configured EventFlows application from module factories.

Basic Usage

typescript
import { createModule, createEventFlowsApp, InMemoryEventBus } from '@eventflows/core';

const userModule = createModule({
  name: 'users',
  setup: ({ eventStore }) => ({
    commandHandlers: {
      CreateUser: new CreateUserHandler(new UserRepository(eventStore)),
    },
    queryHandlers: {
      GetUserById: new GetUserByIdHandler(),
    },
  }),
});

const app = createEventFlowsApp({
  eventStore: myEventStore,
  eventBus: new InMemoryEventBus({}),
  modules: [userModule] as const,
});

Configuration

The createEventFlowsApp() function accepts:

PropertyTypeDescription
eventStoreEventStoreEvent store for persisting events
eventBusEventBusEvent bus for pub/sub
modulesEventFlowsModule[]Array of modules (use as const for type inference)

Multi-Module Apps

Compose multiple domain modules into a single application:

typescript
const userModule = createModule({
  name: 'users',
  setup: ({ eventStore }) => ({
    commandHandlers: {
      CreateUser: new CreateUserHandler(new UserRepository(eventStore)),
      UpdateUserProfile: new UpdateUserProfileHandler(new UserRepository(eventStore)),
    },
    queryHandlers: {
      GetUserById: new GetUserByIdHandler(),
    },
  }),
});

const orderModule = createModule({
  name: 'orders',
  setup: ({ eventStore }) => ({
    commandHandlers: {
      PlaceOrder: new PlaceOrderHandler(new OrderRepository(eventStore)),
      CancelOrder: new CancelOrderHandler(new OrderRepository(eventStore)),
    },
    queryHandlers: {
      GetOrderById: new GetOrderByIdHandler(),
      ListOrdersByCustomer: new ListOrdersByCustomerHandler(),
    },
    eventHandlers: {
      UserCreated: [(event) => console.log('New user:', event.payload.userId)],
    },
  }),
});

const app = createEventFlowsApp({
  eventStore,
  eventBus: new InMemoryEventBus({}),
  modules: [userModule, orderModule] as const,
});

Namespaced API

The app exposes typed commands and queries objects with full intellisense:

typescript
// Commands - typing 'app.commands.' shows all available commands
await app.commands.CreateUser({ userId: '123', name: 'John' });
await app.commands.PlaceOrder({ orderId: '456', items: [{ sku: 'ABC', qty: 2 }] });

// Queries - typing 'app.queries.' shows all available queries
const user = await app.queries.GetUserById({ userId: '123' });
const orders = await app.queries.ListOrdersByCustomer({ customerId: '123' });

The payload types and return types are fully inferred from the handlers:

typescript
// TypeScript knows the exact payload shape
await app.commands.CreateUser({
  userId: '123',   // Required
  name: 'John',    // Required
  email: 'j@x.com' // If handler expects it
});

// TypeScript knows the return type
const user = await app.queries.GetUserById({ userId: '123' });
// user is typed as the handler's return type (e.g., User | null)

Infrastructure Access

Access underlying infrastructure for advanced use cases:

typescript
const app = createEventFlowsApp({
  eventStore,
  eventBus,
  modules: [userModule] as const,
});

// Direct access to buses
app.commandBus.onCommandExecuted((cmd, result) => {
  console.log(`Executed: ${cmd.commandName}`);
});

// Subscribe to additional events
app.eventBus.subscribe('CustomEvent', async (event) => {
  // Handle event
});

// Access event store for replays or debugging
const events = await app.eventStore.getEvents('user-123');

Available Properties

PropertyTypeDescription
commandsTyped executorsNamespaced command execution
queriesTyped executorsNamespaced query execution
commandBusCommandBusInternal command bus
queryBusQueryBusInternal query bus
eventBusEventBusProvided event bus
eventStoreEventStoreProvided event store

Automatic Event Wiring

The app automatically wires the event store to the event bus:

typescript
// When a command handler saves an aggregate...
class CreateUserHandler implements ICommandHandler<CreateUserCommand, void> {
  async execute(command: CreateUserCommand): Promise<void> {
    const user = User.create(command.userId, command.name);
    await this.repository.save(user);
    // Events are automatically published to the event bus!
  }
}

// Event handlers receive them automatically
const notificationModule = createModule({
  name: 'notifications',
  setup: () => ({
    eventHandlers: {
      UserCreated: [
        async (event) => {
          await sendWelcomeEmail(event.payload.userId);
        },
      ],
    },
  }),
});

Error Handling

Duplicate Handler Detection

The app throws ModuleRegistrationError if duplicate handlers are detected:

typescript
const moduleA = createModule({
  name: 'moduleA',
  setup: () => ({
    commandHandlers: {
      CreateUser: new CreateUserHandlerA(), // First registration
    },
  }),
});

const moduleB = createModule({
  name: 'moduleB',
  setup: () => ({
    commandHandlers: {
      CreateUser: new CreateUserHandlerB(), // Duplicate!
    },
  }),
});

// Throws ModuleRegistrationError
const app = createEventFlowsApp({
  eventStore,
  eventBus,
  modules: [moduleA, moduleB] as const,
});
// Error: Duplicate command handler 'CreateUser' found.
// Already registered by module 'moduleA',
// attempted to register again in module 'moduleB'.

Handler Execution Errors

Errors from handlers propagate normally:

typescript
try {
  await app.commands.CreateUser({ userId: '123', name: 'John' });
} catch (error) {
  // Handle command execution error
}

try {
  const user = await app.queries.GetUserById({ userId: '123' });
} catch (error) {
  // Handle query execution error
}

Event Handler Errors

Event handler errors follow the EventBus error handling pattern:

typescript
const eventBus = new InMemoryEventBus({});
eventBus.setErrorHandler((error, eventName) => {
  console.error(`Event handler error for ${eventName}:`, error);
});

const app = createEventFlowsApp({
  eventStore,
  eventBus,
  modules: [userModule] as const,
});

Complete Example

typescript
import {
  createModule,
  createEventFlowsApp,
  InMemoryEventBus,
} from '@eventflows/core';

// User Module
const userModule = createModule({
  name: 'users',
  setup: ({ eventStore }) => {
    const userRepository = new UserRepository(eventStore);
    const userReadRepository = new InMemoryUserReadRepository();
    const userProjection = new UserProjection(userReadRepository);

    return {
      commandHandlers: {
        CreateUser: new CreateUserHandler(userRepository),
        UpdateUserProfile: new UpdateUserProfileHandler(userRepository),
      },
      queryHandlers: {
        GetUserById: new GetUserByIdHandler(userReadRepository),
        SearchUsers: new SearchUsersHandler(userReadRepository),
      },
      eventHandlers: {
        UserCreated: [(event) => userProjection.onUserCreated(event)],
        UserProfileUpdated: [(event) => userProjection.onProfileUpdated(event)],
      },
    };
  },
});

// Order Module
const orderModule = createModule({
  name: 'orders',
  setup: ({ eventStore }) => {
    const orderRepository = new OrderRepository(eventStore);
    const orderReadRepository = new InMemoryOrderReadRepository();
    const orderProjection = new OrderProjection(orderReadRepository);

    return {
      commandHandlers: {
        PlaceOrder: new PlaceOrderHandler(orderRepository),
        ShipOrder: new ShipOrderHandler(orderRepository),
      },
      queryHandlers: {
        GetOrderById: new GetOrderByIdHandler(orderReadRepository),
      },
      eventHandlers: {
        OrderPlaced: [(event) => orderProjection.onOrderPlaced(event)],
        OrderShipped: [(event) => orderProjection.onOrderShipped(event)],
      },
    };
  },
});

// Create Application
const app = createEventFlowsApp({
  eventStore: new InMemoryEventStore(),
  eventBus: new InMemoryEventBus({}),
  modules: [userModule, orderModule] as const,
});

// Use the application
async function main() {
  // Create a user
  await app.commands.CreateUser({
    userId: 'user-123',
    name: 'John Doe',
    email: 'john@example.com',
  });

  // Query the user
  const user = await app.queries.GetUserById({ userId: 'user-123' });

  // Place an order
  await app.commands.PlaceOrder({
    orderId: 'order-456',
    customerId: 'user-123',
    items: [{ sku: 'WIDGET-1', quantity: 2 }],
  });
}

See implementation in packages/core/src/module/create-app.ts

Released under the MIT License.