JSPM

  • Created
  • Published
  • Downloads 13
  • Score
    100M100P100Q52361F
  • License ISC

Remix/React Router 7 Mediator

Package Exports

  • @remediator/core

Readme

@remediator/core

A lightweight, CQRS-style mediator for modern JavaScript frameworks.

Focus on your business logic. Decouple your UI from your application layer with a simple, powerful, and type-safe mediator pattern.


Features

  • Type-Safe: Full TypeScript support from end to end.
  • 🚀 Zero Dependencies: A tiny footprint for a fast experience.
  • ⚙️ Optional Vite Plugin: Powerful auto-registration for handlers and middleware.
  • 🛠️ Framework Agnostic: Works with Remix, Next.js, React Router, or any JS project.
  • 📦 Manual & Automatic Modes: Use the Vite plugin for convenience or register handlers manually for ultimate control.

🔧 Install

npm install @remediator/core

🚀 Two Ways to Use reMediator

You can use this package in two modes: Automatic (with the Vite plugin) or Manual.

Let the Vite plugin discover and register your handlers and middleware automatically.

1. Configure the Plugin

Add the plugin to your vite.config.ts.

// vite.config.ts
import { reMediatorPlugin } from "@remediator/core";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    reMediatorPlugin({
      // Options are optional, these are the defaults:
      // path: "app",
      // outputDir: "remediator",
      // includeFileNames: [
      //   "*.mediator.ts",
      //   "*.mediator.server.ts",
      //   "*.server.mediator.ts",
      // ],
    }),
  ],
  // ... other config
});

2. Follow the Naming Conventions

The plugin works by scanning for files and exports that follow specific naming conventions.

  • Files: Must end with .mediator.ts, .mediator.server.ts, or .server.mediator.ts.
  • Handlers: Must be an exported class named XxxHandler.
  • Requests: Must be an exported class named XxxQuery or XxxCommand that matches a handler.
  • Middleware: Must be an exported function named xxxMiddleware.
// app/modules/users/getUser.mediator.ts

// A "Request" class (Query or Command)
export class GetUserQuery implements IRequest<User> {
  // This phantom property is crucial for TypeScript's type inference.
  readonly _response?: User;
  constructor(public id: string) {}
}

// A "Command" that returns nothing
export class DeleteUserCommand implements IRequest<void> {
  readonly _response?: void;
  constructor(public userId: string) {}
}

// A "Handler" class with a matching name
export class GetUserQueryHandler
  implements IRequestHandler<GetUserQuery, User>
{
  async handle(query: GetUserQuery): Promise<User> {
    // Your business logic lives here
    return await getUserFromDb(query.id);
  }
}

// A "Middleware" function
export const authMiddleware: Pipeline = async (req, ctx, next) => {
  if (!ctx.isAuthenticated) throw new Error("Unauthorized");
  return next();
};

3. Import the Generated Client

The plugin generates a remediator/client.ts file. This is the only instance you should import in your application code.

// In your loader, action, or component
import { reMediator } from "~/remediator/client"; // Use your project's path alias

export async function loader({ request, params }: LoaderFunctionArgs) {
  const user = await reMediator.send(GetUserQuery, { id: params.id }, request);
  return json({ user });
}

Mode 2: Manual Registration (For other build tools or full control)

If you aren't using Vite or prefer to be explicit, you can register everything manually.

1. Import from Core

Import the reMediatorInstance and helper functions directly from the core package.

// /app/remediator-setup.ts
import {
  reMediatorInstance,
  registerHandler,
  registerMiddleware,
} from "@remediator/core";

// Import your classes and functions
import {
  GetUserQuery,
  GetUserQueryHandler,
} from "./modules/users/getUser.mediator";
import { authMiddleware } from "./modules/auth/auth.middleware";

// Register everything
registerHandler(GetUserQuery, new GetUserQueryHandler());
registerMiddleware(authMiddleware);

// IMPORTANT: Import this setup file once in your app's entry point (e.g., main.ts)

2. Use the Instance

In the rest of your app, import the default instance from the core package.

import reMediator from "@remediator/core";

export async function loader({ request, params }: LoaderFunctionArgs) {
  const user = await reMediator.send(GetUserQuery, { id: params.id }, request);
  return json({ user });
}

📡 Dispatching Requests

reMediator.send() has two convenient overloads:

  1. Constructor + data: (Recommended) Instantiate and hydrate a request in one step.

    await reMediator.send(
      GetUserQuery, // Request class
      { id: "u123" }, // Properties to hydrate the class
      request, // Raw Request object
      [localMiddleware] // Optional, per-call middleware
    );
  2. Instance: Use a pre-built request object.

    const command = new UpdateOrderCommand("o456", "shipped");
    await reMediator.send(command, request);

🛠️ Middleware

Middleware are powerful functions that run before your handler. They are perfect for cross-cutting concerns like authentication, logging, or caching.

// A middleware function must match the Pipeline signature
export const authMiddleware: Pipeline = async (request, context, next) => {
  // 1. Read from the raw request or context
  const token = context.rawRequest.headers.get("Authorization");

  // 2. Short-circuit the request
  if (!token) throw new UnauthorizedError();

  // 3. Add to the context for downstream handlers
  context.user = await verifyToken(token);

  // 4. Continue to the next middleware or the handler
  return next();
};

🔧 Plugin Options

You can customize the Vite plugin's behavior.

reMediatorPlugin({
  // Directory to search for mediator files.
  path: "app", // Default: 'app'

  // File patterns to include in the scan.
  includeFileNames: [
    // Default:
    "*.mediator.ts",
    "*.mediator.server.ts",
    "*.server.mediator.ts",
  ],

  // Where to write the generated client.ts and manifest.json.
  outputDir: "remediator", // Default: 'remediator'
});

Simple, explicit, and easy to test — perfect for clean frontend architecture.

© 2025 @remediator/core