-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: middleware * chore: add example jsdoc * test: middleware * refactor: reorder parameters * refactor: rename function * refactor: use overload signature for jsdoc * refactor: rename parameter * test: use mock.calls
- Loading branch information
Showing
4 changed files
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import {beforeEach, describe, expect, it, vi} from "vitest"; | ||
|
||
import {applyMiddleware, type Container, createContainer, inject, injectAll, type Middleware, type Token} from ".."; | ||
|
||
describe("Middleware", () => { | ||
let container: Container; | ||
|
||
beforeEach(() => { | ||
container = createContainer(); | ||
}); | ||
|
||
it("should apply middleware", () => { | ||
const log = vi.fn(); | ||
|
||
function getLogger(loggerName: string): Middleware { | ||
return (composer) => { | ||
composer | ||
.use("resolve", (next) => <T>(token: Token<T>) => { | ||
log(`[${loggerName}] pre resolve ${token.name}`); | ||
const result = next(token); | ||
log(`[${loggerName}] post resolve ${String(result)}`); | ||
return result; | ||
}) | ||
.use("resolveAll", (next) => <T>(token: Token<T>) => { | ||
log(`[${loggerName}] pre resolveAll ${token.name}`); | ||
const result = next(token); | ||
log(`[${loggerName}] post resolveAll [${String(result)}]`); | ||
return result; | ||
}); | ||
}; | ||
} | ||
|
||
applyMiddleware( | ||
container, | ||
[getLogger("A"), getLogger("B")], | ||
); | ||
|
||
class Decoration { | ||
toString() { | ||
return "Decoration {}"; | ||
} | ||
} | ||
|
||
class Wand { | ||
decorations = injectAll(Decoration); | ||
|
||
toString() { | ||
return `Wand {decorations: [${String(this.decorations)}]}`; | ||
} | ||
} | ||
|
||
class Wizard { | ||
wand = inject(Wand); | ||
|
||
toString() { | ||
return `Wizard {wand: ${String(this.wand)}}`; | ||
} | ||
} | ||
|
||
const wizard = container.resolve(Wizard); | ||
|
||
expect(wizard).toBeInstanceOf(Wizard); | ||
expect(wizard.wand).toBeInstanceOf(Wand); | ||
expect(wizard.wand.decorations).toEqual([new Decoration()]); | ||
|
||
expect(log.mock.calls).toEqual([ | ||
["[B] pre resolve Wizard"], | ||
["[A] pre resolve Wizard"], | ||
["[B] pre resolve Wand"], | ||
["[A] pre resolve Wand"], | ||
["[B] pre resolveAll Decoration"], | ||
["[A] pre resolveAll Decoration"], | ||
["[A] post resolveAll [Decoration {}]"], | ||
["[B] post resolveAll [Decoration {}]"], | ||
["[A] post resolve Wand {decorations: [Decoration {}]}"], | ||
["[B] post resolve Wand {decorations: [Decoration {}]}"], | ||
["[A] post resolve Wizard {wand: Wand {decorations: [Decoration {}]}}"], | ||
["[B] post resolve Wizard {wand: Wand {decorations: [Decoration {}]}}"], | ||
]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import type {Container} from "./container"; | ||
|
||
/** | ||
* Middleware function that can be used to extend the container. | ||
* | ||
* @example | ||
* ```ts | ||
* const logger: Middleware = (composer, _api) => { | ||
* composer | ||
* .use("resolve", (next) => (...args) => { | ||
* console.log("resolve", args); | ||
* return next(...args); | ||
* }) | ||
* .use("resolveAll", (next) => (...args) => { | ||
* console.log("resolveAll", args); | ||
* return next(...args); | ||
* }); | ||
* }; | ||
* ``` | ||
*/ | ||
export interface Middleware { | ||
(composer: MiddlewareComposer, api: Readonly<Container>): void; | ||
} | ||
|
||
/** | ||
* Composer API for middleware functions. | ||
*/ | ||
export interface MiddlewareComposer { | ||
/** | ||
* Add a middleware function to the composer. | ||
* | ||
* @template Key - The key of the container method to wrap. | ||
*/ | ||
use<Key extends keyof Container>( | ||
key: Key, | ||
wrap: Container[Key] extends Function | ||
? (next: Container[Key]) => Container[Key] | ||
: never | ||
): MiddlewareComposer; | ||
} | ||
|
||
/** | ||
* Apply middleware functions to a container. | ||
* | ||
* Middlewares are applied in array order, but execute in reverse order. | ||
* | ||
* @example | ||
* ```ts | ||
* const container = applyMiddleware( | ||
* createContainer(), | ||
* [A, B], | ||
* ); | ||
* ``` | ||
* | ||
* The execution order will be: | ||
* | ||
* 1. B before | ||
* 2. A before | ||
* 3. original function | ||
* 4. A after | ||
* 5. B after | ||
* | ||
* This allows outer middlewares to wrap and control the behavior of inner middlewares. | ||
*/ | ||
export function applyMiddleware(container: Container, middlewares: Middleware[]): Container; | ||
|
||
export function applyMiddleware(container: Container, middlewares: Middleware[]) { | ||
const composer: MiddlewareComposer = { | ||
use(key, wrap) { | ||
container[key] = wrap(container[key]); | ||
return composer; | ||
}, | ||
}; | ||
middlewares.forEach((middleware) => middleware(composer, {...container})); | ||
return container; | ||
} |