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.
Mode 1: Auto-Registration with Vite (Recommended)
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
orXxxCommand
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:
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 );
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