@remediator/core
Version:
Remix/React Router 7 Mediator
235 lines (171 loc) • 6.66 kB
Markdown
# /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
```bash
npm install /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`.
```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`.
```ts
// 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.**
```ts
// 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.
```ts
// /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.
```ts
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.
```ts
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.
```ts
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.
```ts
// 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.
```ts
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 /core