From 9a0ed9f89ab5c409ede4a39e58490bb00b47b54c Mon Sep 17 00:00:00 2001 From: Rolando Santamaria Maso Date: Mon, 15 Sep 2025 22:03:41 +0200 Subject: [PATCH 1/4] add TypeScript definitions for middleware endpoint configuration and execution options --- index.d.ts | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 index.d.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..2720dd6 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,101 @@ +/** + * Represents a route endpoint configuration for middleware matching. + */ +export interface RouteEndpoint { + /** HTTP methods to match. Defaults to ["GET"] if not specified. */ + methods?: string[]; + /** URL pattern to match against. Supports find-my-way route patterns. */ + url: string; + /** Optional version constraint for the route. */ + version?: string; + /** Whether to update req.params with matched route parameters. Defaults to false. */ + updateParams?: boolean; +} + +/** + * Configuration options for middleware execution conditions. + */ +export interface MiddlewareOptions { + /** Array of endpoints (strings or RouteEndpoint objects) to match against. */ + endpoints?: (string | RouteEndpoint)[]; + /** Custom function to determine if middleware should execute. */ + custom?: (req: any) => boolean; +} + +/** + * Standard Express/Connect-style middleware function signature. + * @param req - The request object + * @param res - The response object + * @param next - Function to call the next middleware in the chain + */ +export type MiddlewareFunction = (req: any, res: any, next: () => void) => void; + +/** + * Enhanced middleware function with conditional execution capabilities. + * Extends the base middleware function with iff and unless methods. + */ +export interface ExtendedMiddleware extends MiddlewareFunction { + /** + * Execute middleware only if the specified condition is met. + * @param options - Condition options: MiddlewareOptions object, custom function, or array of endpoints + * @returns New ExtendedMiddleware instance with the condition applied + */ + iff: (options: MiddlewareOptions | ((req: any) => boolean) | (string | RouteEndpoint)[]) => ExtendedMiddleware; + + /** + * Execute middleware unless the specified condition is met. + * @param options - Condition options: MiddlewareOptions object, custom function, or array of endpoints + * @returns New ExtendedMiddleware instance with the condition applied + */ + unless: (options: MiddlewareOptions | ((req: any) => boolean) | (string | RouteEndpoint)[]) => ExtendedMiddleware; +} + +/** + * Configuration options for the router instance. + */ +export interface RouterOptions { + /** Default route handler function. */ + defaultRoute?: (req: any, res: any) => boolean; + /** Additional router-specific options. */ + [key: string]: any; +} + +/** + * Factory function for creating router instances. + * @param options - Optional router configuration + * @returns Router instance + */ +export interface RouterFactory { + (options?: RouterOptions): any; +} + +/** + * Main middleware enhancement function that adds iff/unless capabilities to middleware. + * + * @param routerOpts - Optional router configuration options + * @param routerFactory - Optional router factory function (defaults to find-my-way) + * @returns Function that takes a middleware and returns an ExtendedMiddleware with iff/unless methods + * + * @example + * ```typescript + * import iffUnless from 'middleware-if-unless'; + * + * const iu = iffUnless(); + * const middleware = (req, res, next) => { + * console.log('Middleware executed'); + * next(); + * }; + * + * const enhanced = iu(middleware); + * + * // Execute only for specific routes + * app.use(enhanced.iff(['/api/*'])); + * + * // Execute unless specific routes match + * app.use(enhanced.unless(['/public/*'])); + * ``` + */ +export default function iffUnless( + routerOpts?: RouterOptions, + routerFactory?: RouterFactory +): (middleware: MiddlewareFunction) => ExtendedMiddleware; From cfc6aea5e764b653c24af934568f4eae241284e2 Mon Sep 17 00:00:00 2001 From: Rolando Santamaria Maso Date: Mon, 15 Sep 2025 22:03:47 +0200 Subject: [PATCH 2/4] refactor: optimize router handling and middleware execution logic --- index.js | 152 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 61 deletions(-) diff --git a/index.js b/index.js index 1fb8477..fd5a870 100644 --- a/index.js +++ b/index.js @@ -1,82 +1,112 @@ -const handlers = { - match: updateParams => (req, res, params) => { - if (updateParams) { - req.params = params - } +// Optimized handlers with minimal allocations +const createMatchHandler = (updateParams) => + updateParams + ? (req, res, params) => { + req.params = params; + return true; + } + : () => true; + +const defaultHandler = () => false; - return true - }, - default: () => false +// Router cache for reusing router instances +const routerCache = new WeakMap(); + +function normalizeEndpoint(endpoint) { + if (typeof endpoint === "string") { + return { url: endpoint, methods: ["GET"], updateParams: false }; + } + return { + methods: endpoint.methods || ["GET"], + url: endpoint.url, + version: endpoint.version, + updateParams: endpoint.updateParams || false, + }; } -module.exports = function (routerOpts = {}, routerFactory = require('find-my-way')) { - routerOpts.defaultRoute = handlers.default - - function exec (options, isIff = true) { - const middleware = this - - // independent router instance per config - const router = routerFactory(routerOpts) - - const opts = typeof options === 'function' ? { custom: options } : (Array.isArray(options) ? { endpoints: options } : options) - if (opts.endpoints && opts.endpoints.length) { - // setup matching router - opts.endpoints - .map(endpoint => typeof endpoint === 'string' ? { url: endpoint } : endpoint) - .forEach(({ methods = ['GET'], url, version, updateParams = false }) => { - if (version) { - router.on(methods, url, { constraints: { version } }, handlers.match(updateParams)) - } else { - router.on(methods, url, handlers.match(updateParams)) +module.exports = function (routerOpts = {}, routerFactory = require("find-my-way")) { + function exec(options, isIff = true) { + const middleware = this; + let router = null; + let customFn = null; + + // Process options efficiently + if (typeof options === "function") { + customFn = options; + } else { + const endpoints = Array.isArray(options) ? options : options?.endpoints; + + if (endpoints?.length) { + // Try to get cached router first + let cache = routerCache.get(routerOpts); + if (!cache) { + cache = new Map(); + routerCache.set(routerOpts, cache); + } + + const cacheKey = JSON.stringify(endpoints); + router = cache.get(cacheKey); + + if (!router) { + router = routerFactory({ ...routerOpts, defaultRoute: defaultHandler }); + + // Normalize and register routes + const normalized = endpoints.map(normalizeEndpoint); + for (const { methods, url, version, updateParams } of normalized) { + const handler = createMatchHandler(updateParams); + + if (version) { + router.on(methods, url, { constraints: { version } }, handler); + } else { + router.on(methods, url, handler); + } } - }) + + cache.set(cacheKey, router); + } + } + + if (options?.custom) { + customFn = options.custom; + } } + // Optimized execution function const result = function (req, res, next) { - // supporting custom matching function - if (opts.custom) { - if (opts.custom(req)) { - if (isIff) { - return middleware(req, res, next) - } - } else if (!isIff) { - return middleware(req, res, next) - } + let shouldExecute = false; - // leave here and do not process opts.endpoints - return next() + if (customFn) { + shouldExecute = customFn(req); + } else if (router) { + shouldExecute = router.lookup(req, res); } - // matching endpoints and moving forward - if (router.lookup(req, res)) { - if (isIff) { - return middleware(req, res, next) - } - } else if (!isIff) { - return middleware(req, res, next) + // Simplified logic: execute middleware if conditions match + if ((isIff && shouldExecute) || (!isIff && !shouldExecute)) { + return middleware(req, res, next); } - return next() - } + return next(); + }; - // allowing chaining - result.iff = iff - result.unless = unless + // Allow chaining + result.iff = iff; + result.unless = unless; - return result + return result; } - function iff (options) { - return exec.call(this, options, true) + function iff(options) { + return exec.call(this, options, true); } - function unless (options) { - return exec.call(this, options, false) + function unless(options) { + return exec.call(this, options, false); } return function (middleware) { - middleware.iff = iff - middleware.unless = unless + middleware.iff = iff; + middleware.unless = unless; - return middleware - } -} + return middleware; + }; +}; From 51ab57e0db4d4260ac36a2873a61cccca4421fb1 Mon Sep 17 00:00:00 2001 From: Rolando Santamaria Maso Date: Mon, 15 Sep 2025 22:03:59 +0200 Subject: [PATCH 3/4] chore: update package.json to include additional files and upgrade dependencies --- package.json | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index c041a23..339bfad 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,15 @@ "if", "unless" ], - "files": [ - "README.md" - ], "engines": { "node": ">=8" }, + "files": [ + "index.js", + "index.d.ts", + "README.md" + ], + "typings": "index.d.ts", "author": "Rolando Santamaria Maso ", "license": "MIT", "bugs": { @@ -29,13 +32,13 @@ "homepage": "https://github.com/jkyberneees/middleware-if-unless#readme", "devDependencies": { "chai": "^4.3.7", - "express-unless": "^1.0.0", - "mocha": "^10.2.0", - "nyc": "^15.1.0", - "restana": "^4.9.7", - "supertest": "^6.3.3" + "express-unless": "^2.1.3", + "mocha": "^11.7.2", + "nyc": "^17.1.0", + "restana": "^5.1.0", + "supertest": "^7.1.4" }, "dependencies": { - "find-my-way": "^9.0.1" + "find-my-way": "^9.3.0" } } From 0d163a5dc9deaf18463c9c8220f684fc6e6e7d82 Mon Sep 17 00:00:00 2001 From: Rolando Santamaria Maso Date: Mon, 15 Sep 2025 22:05:46 +0200 Subject: [PATCH 4/4] style: format code for consistency and readability --- index.js | 98 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/index.js b/index.js index fd5a870..29fe4ba 100644 --- a/index.js +++ b/index.js @@ -2,111 +2,111 @@ const createMatchHandler = (updateParams) => updateParams ? (req, res, params) => { - req.params = params; - return true; + req.params = params + return true } - : () => true; + : () => true -const defaultHandler = () => false; +const defaultHandler = () => false // Router cache for reusing router instances -const routerCache = new WeakMap(); +const routerCache = new WeakMap() -function normalizeEndpoint(endpoint) { - if (typeof endpoint === "string") { - return { url: endpoint, methods: ["GET"], updateParams: false }; +function normalizeEndpoint (endpoint) { + if (typeof endpoint === 'string') { + return { url: endpoint, methods: ['GET'], updateParams: false } } return { - methods: endpoint.methods || ["GET"], + methods: endpoint.methods || ['GET'], url: endpoint.url, version: endpoint.version, - updateParams: endpoint.updateParams || false, - }; + updateParams: endpoint.updateParams || false + } } -module.exports = function (routerOpts = {}, routerFactory = require("find-my-way")) { - function exec(options, isIff = true) { - const middleware = this; - let router = null; - let customFn = null; +module.exports = function (routerOpts = {}, routerFactory = require('find-my-way')) { + function exec (options, isIff = true) { + const middleware = this + let router = null + let customFn = null // Process options efficiently - if (typeof options === "function") { - customFn = options; + if (typeof options === 'function') { + customFn = options } else { - const endpoints = Array.isArray(options) ? options : options?.endpoints; + const endpoints = Array.isArray(options) ? options : options?.endpoints if (endpoints?.length) { // Try to get cached router first - let cache = routerCache.get(routerOpts); + let cache = routerCache.get(routerOpts) if (!cache) { - cache = new Map(); - routerCache.set(routerOpts, cache); + cache = new Map() + routerCache.set(routerOpts, cache) } - const cacheKey = JSON.stringify(endpoints); - router = cache.get(cacheKey); + const cacheKey = JSON.stringify(endpoints) + router = cache.get(cacheKey) if (!router) { - router = routerFactory({ ...routerOpts, defaultRoute: defaultHandler }); + router = routerFactory({ ...routerOpts, defaultRoute: defaultHandler }) // Normalize and register routes - const normalized = endpoints.map(normalizeEndpoint); + const normalized = endpoints.map(normalizeEndpoint) for (const { methods, url, version, updateParams } of normalized) { - const handler = createMatchHandler(updateParams); + const handler = createMatchHandler(updateParams) if (version) { - router.on(methods, url, { constraints: { version } }, handler); + router.on(methods, url, { constraints: { version } }, handler) } else { - router.on(methods, url, handler); + router.on(methods, url, handler) } } - cache.set(cacheKey, router); + cache.set(cacheKey, router) } } if (options?.custom) { - customFn = options.custom; + customFn = options.custom } } // Optimized execution function const result = function (req, res, next) { - let shouldExecute = false; + let shouldExecute = false if (customFn) { - shouldExecute = customFn(req); + shouldExecute = customFn(req) } else if (router) { - shouldExecute = router.lookup(req, res); + shouldExecute = router.lookup(req, res) } // Simplified logic: execute middleware if conditions match if ((isIff && shouldExecute) || (!isIff && !shouldExecute)) { - return middleware(req, res, next); + return middleware(req, res, next) } - return next(); - }; + return next() + } // Allow chaining - result.iff = iff; - result.unless = unless; + result.iff = iff + result.unless = unless - return result; + return result } - function iff(options) { - return exec.call(this, options, true); + function iff (options) { + return exec.call(this, options, true) } - function unless(options) { - return exec.call(this, options, false); + function unless (options) { + return exec.call(this, options, false) } return function (middleware) { - middleware.iff = iff; - middleware.unless = unless; + middleware.iff = iff + middleware.unless = unless - return middleware; - }; -}; + return middleware + } +}