[Typescript] Creating Chainable Method Abstractions with Generics and the Builder Pattern - 05
import { expect, it } from "vitest";
import { fetchUser } from "fake-external-lib";
type Middleware<TInput, TOutput> = (input: TInput) => TOutput;
class DynamicMiddleware<TInput, TOutput> {
private middleware: Middleware<any, any>[] = [];
constructor(firstMiddleware: Middleware<TInput, TOutput>) {
this.middleware.push(firstMiddleware);
}
// use middleware, the input should be previous step's output
// the output of the middleware should be new, using genric
// the outptu of the middleware will be the input of next middleware
use<NewTOutput>(
middleware: Middleware<TOutput, NewTOutput>
): DynamicMiddleware<TInput, NewTOutput> {
this.middleware.push(middleware);
return this as any;
}
async run(input: TInput): Promise<TOutput> {
let result: TOutput = input as any;
for (const middleware of this.middleware) {
result = await middleware(result);
}
return result;
}
}
const middleware = new DynamicMiddleware((req: Request) => {
return {
...req,
userId: req.url.split("/")[2],
};
})
.use((req) => {
if (req.userId === "123") {
throw new Error();
}
return req;
})
.use(async (req) => {
return {
...req,
user: await fetchUser(req.userId),
};
});
it("Should fail if the user id is 123", () => {
expect(middleware.run({ url: "/user/123" } as Request)).rejects.toThrow();
});
it("Should return a request with a user", async () => {
const result = await middleware.run({ url: "/user/matt" } as Request);
expect(result.user.id).toBe("matt");
expect(result.user.firstName).toBe("John");
expect(result.user.lastName).toBe("Doe");
});