Package Exports
- @remediator/core
Readme
@remediator/core
โก CQRS-style mediator pattern for frontend apps (Remix, React Router 7, Vite/Webpack support)
Minimal, convention-based mediator designed for frontend frameworks like Remix and React Router 7. Automatically registers your *.mediator.ts
files for clean, scalable architecture.
๐ง Install
npm install @remediator/core
๐ Quick Start
1. Create a Query or Command
// /app/user/GetUserQuery.mediator.ts
export class GetUserQuery {}
export class GetUserQueryHandler {
async handle(query: GetUserQuery) {
// fetch user data here
return { id: "u1", name: "Jeremy" };
}
}
โ File name must end in
.mediator.ts
โ Class names must follow this convention:
XQuery
+XQueryHandler
XCommand
+XCommandHandler
Every Query
or Command
must have a corresponding Handler
class in the same file.
2. Auto-Register Your Files
In Remix (e.g. server.entry.ts
or root loader):
import { registerAll } from "@remediator/core";
registerAll();
In React Router 7:
import { registerAll } from "@remediator/core";
export function loader() {
registerAll(); // Safe to call multiple times (runs once)
}
3. Send a Command or Query
import { reMediator } from "@remediator/core";
const result = await reMediator.send(new GetUserQuery(), request);
๐ก Using Middleware
Middleware lets you inject behavior (auth, logging, etc.) into the send pipeline.
Example: authMiddleware
import type { Pipeline } from "@remediator/core";
import { getSession } from "./session.server";
import { UnauthorizedError } from "~/shared/lib/errors.server";
const publicRoutes = ["/login", "/register", "/forgot-password"];
export const authMiddleware: Pipeline = async (req, context, next) => {
const cookie = context.rawRequest.headers.get("Cookie");
const session = await getSession(cookie);
const userId = session.get("userId");
if (publicRoutes.includes(context.rawRequest.url)) {
return next();
}
if (!userId) {
throw new UnauthorizedError();
}
context.userId = userId;
return next();
};
Register it:
import { reMediator } from "@remediator/core";
import { authMiddleware } from "./auth.middleware";
reMediator.use(authMiddleware);
๐งช Usage Examples in Remix
โ Loader Example
import { reMediator } from "@remediator/core";
import { redirect, type LoaderFunctionArgs } from "@remix-run/node";
import { UnauthorizedError } from "~/shared/lib/errors.server";
export async function loader({ request }: LoaderFunctionArgs) {
try {
const user = await reMediator.send(new GetUserQuery(), request);
const cycles = await reMediator.send(new GetCyclesForUserQuery(), request);
const projects = await reMediator.send(
new GetProjectsForUserQuery(),
request
);
return data({ user, cycles, projects });
} catch (error) {
if (error instanceof UnauthorizedError) {
return redirect("/");
}
throw error;
}
}
โ Action Example
import { reMediator } from "@remediator/core";
import { redirect, type ActionFunctionArgs } from "@remix-run/node";
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const dataEntries = Object.fromEntries(formData);
if (dataEntries.delete === "true") {
const result = await reMediator.send<IResponse>(
new ProjectDeleteCommand(
dataEntries.id as string,
dataEntries.deleteCycles === "true"
),
request
);
if (result.success) {
return redirect(`/app/projects`);
}
return data({ errors: result.errors }, { status: 400 });
}
const result = await reMediator.send<IResponse>(
new ProjectUpsertCommand(
dataEntries.id as string,
dataEntries.name as string
),
request
);
if (result.success) {
return redirect(`/app/project/${result.id}`);
}
return data({ errors: result.errors }, { status: 400 });
}
๐ง send()
Arguments
reMediator.send(request: IRequest, rawRequest: Request, middleware?: Array<Pipeline | string>)
Middleware Options:
undefined
: use all registered middleware (default)[]
: use no middleware["authMiddleware"]
: use only named middleware[authMiddleware]
: pass in specific middleware functions
โ๏ธ Configuring registerAll(options)
registerAll({
path: "/app", // folder to scan
includeFileNames: ["*.mediator.ts"],
});
โ Summary of Conventions
Type | File Format | Class Format |
---|---|---|
Command | *.mediator.ts |
XCommand + XCommandHandler |
Query | *.mediator.ts |
XQuery + XQueryHandler |
Middleware | *.mediator.ts |
Ends with Middleware function name |
All handlers must be in the same file as their command/query. Auto-registration depends on this structure.
๐ฆ Project Structure Example
/app
/auth
GetUserQuery.mediator.ts
GetCyclesForUserQuery.mediator.ts
AuthMiddleware.mediator.ts
ProjectUpsertCommand.mediator.ts
โจ Credits
Created by @remediator โ solving CQRS needs for modern frontend frameworks.