JSPM

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

Remix/React Router 7 Mediator

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 {
  constructor(public id?: string) {}
}

export class GetUserQueryHandler {
  async handle(query: GetUserQuery) {
    // fetch user data here
    return { id: query.id, 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

โœ… Option 1: Classic

await reMediator.send(new GetUserQuery("u1"), request);

โœ… Option 2: Plain Object (auto mapped)

await reMediator.send<GetUserQuery>({ id: "u1" }, request);

โœ… Option 3: Decorator + Named Usage

@RemediatorRequest("GetUserQuery")
export class GetUserQuery {
  constructor(public id?: string) {}
}

await reMediator.send("GetUserQuery", { id: "u1" }, request);

๐Ÿ’ก Using Middleware

Middleware lets you inject behavior (auth, logging, etc.) into the send pipeline.

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:

reMediator.use(authMiddleware);

๐Ÿงช Usage Examples in Remix

โœ… Loader Example

const user = await reMediator.send(new GetUserQuery(), request);
const cycles = await reMediator.send(new GetCyclesForUserQuery(), request);
const projects = await reMediator.send(new GetProjectsForUserQuery(), request);

โœ… Action Example

const formData = await request.formData();
const data = Object.fromEntries(formData);

const result = await reMediator.send(
  new ProjectUpsertCommand(data.id, data.name),
  request
);

๐Ÿง  send() Arguments

reMediator.send(request: IRequest | object | string, rawRequest: Request, middleware?: Array<Pipeline | string>)
  • Pass a class instance, plain object, or registered name (with @RemediatorRequest)
  • Optional middleware can be [], ["name"], or [func]

โœ… 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

๐Ÿ“ฆ Project Structure Example

/app
  /auth
    AuthMiddleware.mediator.ts
  /GetUser
    GetUserQuery.mediator.ts
    GetUserQueryHandler.mediator.ts
  /Project
    UpsertProjectCommand.mediator.ts
    UpsertProjectCommandHandler.mediator.ts

๐ŸŽฏ The Purpose of Mediator (@remediator/core)

  • โœ… Promotes thin loaders/actions
  • โœ… Enables clean separation of concerns
  • โœ… Centralizes cross-cutting concerns (via middleware)
  • โœ… Supports reusable, testable request/response structures

๐Ÿค” Why Not Just Use a Class?

const result = await new GetUserQuery().fetch();

But that:

  • Couples logic to routes
  • Makes testing harder
  • Scales poorly with cross-cutting logic
  • Encourages inconsistent patterns

โœ… Mediator FTW

await reMediator.send(new GetUserQuery(), request);
  • Feels declarative and clean
  • Consistent across commands/queries
  • Easily extended via middleware

โœจ Credits

Created by @remediator โ€” solving CQRS needs for modern frontend frameworks.