Skip to content

Commit 61db875

Browse files
authored
Introducing performance optimizations and TypeScript Types (#9)
* add TypeScript definitions for middleware endpoint configuration and execution options * refactor: optimize router handling and middleware execution logic * chore: update package.json to include additional files and upgrade dependencies
1 parent 458b397 commit 61db875

File tree

3 files changed

+185
-51
lines changed

3 files changed

+185
-51
lines changed

index.d.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Represents a route endpoint configuration for middleware matching.
3+
*/
4+
export interface RouteEndpoint {
5+
/** HTTP methods to match. Defaults to ["GET"] if not specified. */
6+
methods?: string[];
7+
/** URL pattern to match against. Supports find-my-way route patterns. */
8+
url: string;
9+
/** Optional version constraint for the route. */
10+
version?: string;
11+
/** Whether to update req.params with matched route parameters. Defaults to false. */
12+
updateParams?: boolean;
13+
}
14+
15+
/**
16+
* Configuration options for middleware execution conditions.
17+
*/
18+
export interface MiddlewareOptions {
19+
/** Array of endpoints (strings or RouteEndpoint objects) to match against. */
20+
endpoints?: (string | RouteEndpoint)[];
21+
/** Custom function to determine if middleware should execute. */
22+
custom?: (req: any) => boolean;
23+
}
24+
25+
/**
26+
* Standard Express/Connect-style middleware function signature.
27+
* @param req - The request object
28+
* @param res - The response object
29+
* @param next - Function to call the next middleware in the chain
30+
*/
31+
export type MiddlewareFunction = (req: any, res: any, next: () => void) => void;
32+
33+
/**
34+
* Enhanced middleware function with conditional execution capabilities.
35+
* Extends the base middleware function with iff and unless methods.
36+
*/
37+
export interface ExtendedMiddleware extends MiddlewareFunction {
38+
/**
39+
* Execute middleware only if the specified condition is met.
40+
* @param options - Condition options: MiddlewareOptions object, custom function, or array of endpoints
41+
* @returns New ExtendedMiddleware instance with the condition applied
42+
*/
43+
iff: (options: MiddlewareOptions | ((req: any) => boolean) | (string | RouteEndpoint)[]) => ExtendedMiddleware;
44+
45+
/**
46+
* Execute middleware unless the specified condition is met.
47+
* @param options - Condition options: MiddlewareOptions object, custom function, or array of endpoints
48+
* @returns New ExtendedMiddleware instance with the condition applied
49+
*/
50+
unless: (options: MiddlewareOptions | ((req: any) => boolean) | (string | RouteEndpoint)[]) => ExtendedMiddleware;
51+
}
52+
53+
/**
54+
* Configuration options for the router instance.
55+
*/
56+
export interface RouterOptions {
57+
/** Default route handler function. */
58+
defaultRoute?: (req: any, res: any) => boolean;
59+
/** Additional router-specific options. */
60+
[key: string]: any;
61+
}
62+
63+
/**
64+
* Factory function for creating router instances.
65+
* @param options - Optional router configuration
66+
* @returns Router instance
67+
*/
68+
export interface RouterFactory {
69+
(options?: RouterOptions): any;
70+
}
71+
72+
/**
73+
* Main middleware enhancement function that adds iff/unless capabilities to middleware.
74+
*
75+
* @param routerOpts - Optional router configuration options
76+
* @param routerFactory - Optional router factory function (defaults to find-my-way)
77+
* @returns Function that takes a middleware and returns an ExtendedMiddleware with iff/unless methods
78+
*
79+
* @example
80+
* ```typescript
81+
* import iffUnless from 'middleware-if-unless';
82+
*
83+
* const iu = iffUnless();
84+
* const middleware = (req, res, next) => {
85+
* console.log('Middleware executed');
86+
* next();
87+
* };
88+
*
89+
* const enhanced = iu(middleware);
90+
*
91+
* // Execute only for specific routes
92+
* app.use(enhanced.iff(['/api/*']));
93+
*
94+
* // Execute unless specific routes match
95+
* app.use(enhanced.unless(['/public/*']));
96+
* ```
97+
*/
98+
export default function iffUnless(
99+
routerOpts?: RouterOptions,
100+
routerFactory?: RouterFactory
101+
): (middleware: MiddlewareFunction) => ExtendedMiddleware;

index.js

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,95 @@
1-
const handlers = {
2-
match: updateParams => (req, res, params) => {
3-
if (updateParams) {
4-
req.params = params
5-
}
1+
// Optimized handlers with minimal allocations
2+
const createMatchHandler = (updateParams) =>
3+
updateParams
4+
? (req, res, params) => {
5+
req.params = params
6+
return true
7+
}
8+
: () => true
9+
10+
const defaultHandler = () => false
611

7-
return true
8-
},
9-
default: () => false
12+
// Router cache for reusing router instances
13+
const routerCache = new WeakMap()
14+
15+
function normalizeEndpoint (endpoint) {
16+
if (typeof endpoint === 'string') {
17+
return { url: endpoint, methods: ['GET'], updateParams: false }
18+
}
19+
return {
20+
methods: endpoint.methods || ['GET'],
21+
url: endpoint.url,
22+
version: endpoint.version,
23+
updateParams: endpoint.updateParams || false
24+
}
1025
}
1126

1227
module.exports = function (routerOpts = {}, routerFactory = require('find-my-way')) {
13-
routerOpts.defaultRoute = handlers.default
14-
1528
function exec (options, isIff = true) {
1629
const middleware = this
30+
let router = null
31+
let customFn = null
32+
33+
// Process options efficiently
34+
if (typeof options === 'function') {
35+
customFn = options
36+
} else {
37+
const endpoints = Array.isArray(options) ? options : options?.endpoints
38+
39+
if (endpoints?.length) {
40+
// Try to get cached router first
41+
let cache = routerCache.get(routerOpts)
42+
if (!cache) {
43+
cache = new Map()
44+
routerCache.set(routerOpts, cache)
45+
}
46+
47+
const cacheKey = JSON.stringify(endpoints)
48+
router = cache.get(cacheKey)
1749

18-
// independent router instance per config
19-
const router = routerFactory(routerOpts)
20-
21-
const opts = typeof options === 'function' ? { custom: options } : (Array.isArray(options) ? { endpoints: options } : options)
22-
if (opts.endpoints && opts.endpoints.length) {
23-
// setup matching router
24-
opts.endpoints
25-
.map(endpoint => typeof endpoint === 'string' ? { url: endpoint } : endpoint)
26-
.forEach(({ methods = ['GET'], url, version, updateParams = false }) => {
27-
if (version) {
28-
router.on(methods, url, { constraints: { version } }, handlers.match(updateParams))
29-
} else {
30-
router.on(methods, url, handlers.match(updateParams))
50+
if (!router) {
51+
router = routerFactory({ ...routerOpts, defaultRoute: defaultHandler })
52+
53+
// Normalize and register routes
54+
const normalized = endpoints.map(normalizeEndpoint)
55+
for (const { methods, url, version, updateParams } of normalized) {
56+
const handler = createMatchHandler(updateParams)
57+
58+
if (version) {
59+
router.on(methods, url, { constraints: { version } }, handler)
60+
} else {
61+
router.on(methods, url, handler)
62+
}
3163
}
32-
})
64+
65+
cache.set(cacheKey, router)
66+
}
67+
}
68+
69+
if (options?.custom) {
70+
customFn = options.custom
71+
}
3372
}
3473

74+
// Optimized execution function
3575
const result = function (req, res, next) {
36-
// supporting custom matching function
37-
if (opts.custom) {
38-
if (opts.custom(req)) {
39-
if (isIff) {
40-
return middleware(req, res, next)
41-
}
42-
} else if (!isIff) {
43-
return middleware(req, res, next)
44-
}
76+
let shouldExecute = false
4577

46-
// leave here and do not process opts.endpoints
47-
return next()
78+
if (customFn) {
79+
shouldExecute = customFn(req)
80+
} else if (router) {
81+
shouldExecute = router.lookup(req, res)
4882
}
4983

50-
// matching endpoints and moving forward
51-
if (router.lookup(req, res)) {
52-
if (isIff) {
53-
return middleware(req, res, next)
54-
}
55-
} else if (!isIff) {
84+
// Simplified logic: execute middleware if conditions match
85+
if ((isIff && shouldExecute) || (!isIff && !shouldExecute)) {
5686
return middleware(req, res, next)
5787
}
5888

5989
return next()
6090
}
6191

62-
// allowing chaining
92+
// Allow chaining
6393
result.iff = iff
6494
result.unless = unless
6595

package.json

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
"if",
1616
"unless"
1717
],
18-
"files": [
19-
"README.md"
20-
],
2118
"engines": {
2219
"node": ">=8"
2320
},
21+
"files": [
22+
"index.js",
23+
"index.d.ts",
24+
"README.md"
25+
],
26+
"typings": "index.d.ts",
2427
"author": "Rolando Santamaria Maso <[email protected]>",
2528
"license": "MIT",
2629
"bugs": {
@@ -29,13 +32,13 @@
2932
"homepage": "https://github.com/jkyberneees/middleware-if-unless#readme",
3033
"devDependencies": {
3134
"chai": "^4.3.7",
32-
"express-unless": "^1.0.0",
33-
"mocha": "^10.2.0",
34-
"nyc": "^15.1.0",
35-
"restana": "^4.9.7",
36-
"supertest": "^6.3.3"
35+
"express-unless": "^2.1.3",
36+
"mocha": "^11.7.2",
37+
"nyc": "^17.1.0",
38+
"restana": "^5.1.0",
39+
"supertest": "^7.1.4"
3740
},
3841
"dependencies": {
39-
"find-my-way": "^9.0.1"
42+
"find-my-way": "^9.3.0"
4043
}
4144
}

0 commit comments

Comments
 (0)