Eric Bower
·
19 Jan 24
compose.ts
1import { Instruction, Operation } from "./deps.ts";
2import type { Next } from "./types.ts";
3
4export interface BaseCtx {
5 // deno-lint-ignore no-explicit-any
6 [key: string]: any;
7}
8
9export type BaseMiddleware<Ctx extends BaseCtx = BaseCtx, T = unknown> = (
10 ctx: Ctx,
11 next: Next,
12) => Operation<T | undefined>;
13
14export function compose<Ctx extends BaseCtx = BaseCtx, T = unknown>(
15 middleware: BaseMiddleware<Ctx, T>[],
16) {
17 if (!Array.isArray(middleware)) {
18 throw new TypeError("Middleware stack must be an array!");
19 }
20
21 for (const fn of middleware) {
22 if (typeof fn !== "function") {
23 throw new TypeError("Middleware must be composed of functions!");
24 }
25 }
26
27 return function* composeFn(context: Ctx, mdw?: BaseMiddleware<Ctx, T>) {
28 // last called middleware #
29 let index = -1;
30
31 function* dispatch(i: number): Generator<Instruction, void, void> {
32 if (i <= index) {
33 throw new Error("next() called multiple times");
34 }
35 index = i;
36 let fn: BaseMiddleware<Ctx, T> | undefined = middleware[i];
37 if (i === middleware.length) {
38 fn = mdw;
39 }
40 if (!fn) {
41 return;
42 }
43 const nxt = dispatch.bind(null, i + 1);
44 yield* fn(context, nxt);
45 }
46
47 yield* dispatch(0);
48 };
49}