Package Exports
- @remediator/core
Readme
@remediator/core
⚡ Lightweight frontend mediator for Remix, React Router, Vite & Webpack.
A simple CQRS-style mediator that lets you register handlers and middleware, and dispatch commands or queries with minimal ceremony.
🔧 Install
npm install @remediator/core
📁 Conventions
- Request classes implement
IRequest<TResult>
- Handlers implement
IRequestHandler<TRequest, TResult>
- Registration pairs a request ctor with its handler via
reMediator.register(...)
- Middleware are functions matching
(req, ctx, next) => Promise<TResult>
and added withreMediator.use(...)
🚀 Setup
Manual Registration (Recommended for production)
Register your handlers and middleware once at app startup:
// register handlers
reMediator.register(GetUserQuery, new GetUserQueryHandler());
reMediator.register(UpdateOrderCommand, new UpdateOrderCommandHandler());
// register global middleware (optional)
reMediator.use(authMiddleware);
Auto-Registration (Optional - Development)
Use the Vite plugin for automatic registration during development:
1. Configure the plugin
// vite.config.ts
import { reMediatorPlugin } from "@remediator/core";
export default defineConfig({
plugins: [
reMediatorPlugin({
path: "app", // where to look for mediator files
includeFileNames: ["*.mediator.ts"], // file patterns to include
outputDir: "remediator", // where to generate client (default: 'remediator')
}),
],
});
2. Create mediator files
// app/modules/users/getUser/getUserQuery.mediator.ts
export class GetUserQuery implements IRequest<User> {
constructor(public id: string) {}
}
export class GetUserQueryHandler
implements IRequestHandler<GetUserQuery, User>
{
async handle(query: GetUserQuery, context?: IRequestContext): Promise<User> {
// Your handler logic
return await getUserById(query.id);
}
}
3. Import the generated client
// In your loader/action
import { reMediator } from "~/remediator/client";
export async function loader({ request }: LoaderFunctionArgs) {
const user = await reMediator.send(GetUserQuery, { id: "u1" }, request);
return json({ user });
}
The plugin generates:
remediator/client.ts
- Auto-registered reMediator instanceremediator/manifest.json
- List of discovered mediator files
📡 Dispatching Requests
reMediator.send()
has two overloads:
Constructor + data: instantiate + hydrate
await reMediator.send( GetUserQuery, // request class { id: "u1" }, // partial properties request, // raw Request object [logMiddleware] // optional per-call middleware );
Instance: use an existing request object
const cmd = new UpdateOrderCommand(); cmd.orderId = "A123"; cmd.status = "Shipped"; await reMediator.send( cmd, // fully-built instance request, [auditMiddleware] // optional );
Both return the handler's result type.
🛠️ Middleware
Middleware run before the handler and can:
- Examine or modify
req
fields - Read/request context via
ctx.rawRequest
- Short-circuit or throw errors
- Invoke
await next()
to continue the pipeline
export const authMiddleware: Pipeline = async (req, ctx, next) => {
const token = ctx.rawRequest.headers.get("Authorization");
if (!token) throw new UnauthorizedError();
ctx.user = verifyToken(token);
return next();
};
📦 Full Example
Manual Registration
// Handler registration
reMediator.register(GetUserQuery, new GetUserQueryHandler());
reMediator.use(authMiddleware);
// In a Remix loader
export async function loader({ request }: LoaderFunctionArgs) {
// 1) Ctor + data
const user1 = await reMediator.send(GetUserQuery, { id: "u1" }, request);
// 2) Instance
const q = new GetUserQuery();
q.id = "u2";
const user2 = await reMediator.send(q, request);
return json({ user1, user2 });
}
Auto-Registration
// File: app/modules/users/getUser/getUserQuery.mediator.ts
export class GetUserQuery implements IRequest<User> {
constructor(public id: string) {}
}
export class GetUserQueryHandler
implements IRequestHandler<GetUserQuery, User>
{
async handle(query: GetUserQuery): Promise<User> {
return await getUserById(query.id);
}
}
// File: app/routes/users.$id.tsx
import { reMediator } from "~/remediator/client";
export async function loader({ request, params }: LoaderFunctionArgs) {
const user = await reMediator.send(GetUserQuery, { id: params.id }, request);
return json({ user });
}
🔧 Plugin Options
reMediatorPlugin({
path: "app", // Directory to search (default: 'app')
includeFileNames: ["*.mediator.ts"], // File patterns to include
outputDir: "remediator", // Output directory (default: 'remediator')
});
Simple, explicit, and easy to test — perfect for clean frontend CQRS.
© 2025 @remediator/core