Safe Monkeypatching for JavaScript
The `shimmer` package (version 1.2.1) is a JavaScript utility designed for safe monkeypatching of functions, primarily within Node.js CommonJS environments. It provides a set of tools, including `wrap`, `massWrap`, `unwrap`, and `massUnwrap`, to intercept and augment the behavior of existing functions on objects or entire modules. The library's core philosophy is to add behavior around an original function, rather than replacing it, and includes important guidelines for maintaining function integrity (e.g., preserving return values, not altering async/sync nature). Released approximately seven years ago, its current status suggests it is in maintenance mode rather than active development. It differentiates itself by providing explicit safety mechanisms and logging for potential issues during monkeypatching, defaulting to `console.error` for non-throwing error reporting. This makes it suitable for extending or observing existing Node.js module functionality with reduced risk compared to direct function reassignment.
Common errors
-
TypeError: original.apply is not a function
cause The `wrapper` function did not correctly receive or invoke the `original` function, or the `name` provided to `shimmer.wrap` referred to a non-function property.fixEnsure your `wrapper` function signature is `function (original) { return function () { /* ... */ original.apply(this, arguments); } }` and that `name` points to an actual function on the `nodule`. -
Error: Cannot find module 'shimmer'
cause The `shimmer` package is not installed or the Node.js runtime cannot locate it.fixRun `npm install shimmer` or `yarn add shimmer` to install the package. -
ReferenceError: shimmer is not defined
cause The `shimmer` module was not imported or required correctly before use.fixAdd `const shimmer = require('shimmer');` at the top of your file to import the module in CommonJS environments.
Warnings
- breaking This library is an older package (last updated ~7 years ago) and is not actively maintained. Compatibility with very recent Node.js versions or complex ESM setups may not be guaranteed without specific workarounds.
- gotcha Monkeypatching is inherently risky. The library includes a 'mandatory disclaimer' that modifying runtime behavior on the fly is rarely a good idea and should only be done out of necessity, not for fun.
- gotcha When providing a `wrapper` function, you *must* call the `original.apply(this, arguments)` unless you are intentionally transforming arguments or replacing the function's logic.
- gotcha Always capture and return the return value from the `original` function within your `wrapper`. Ignoring it can lead to unexpected behavior later, especially with callbacks or promise-based APIs.
- gotcha Do not change an asynchronous function to be synchronous or vice versa within your `wrapper`. This fundamentally alters the contract of the original function and can cause significant issues in consuming code.
- gotcha `shimmer` defaults to logging failures via `console.error` rather than throwing exceptions, making it unobtrusive but potentially masking immediate errors.
- gotcha `shimmer.unwrap` will not unwrap a function if it has been monkeypatched by another library *after* your `shimmer` patch, and it will only log this event.
Install
-
npm install shimmer -
yarn add shimmer -
pnpm add shimmer
Imports
- shimmer
const shimmer = require('shimmer'); - shimmer.wrap
import { wrap } from 'shimmer'; // Not directly exported as named exportconst shimmer = require('shimmer'); shimmer.wrap(targetModule, 'methodName', function (original) { /* ... */ }); - shimmer.unwrap
import { unwrap } from 'shimmer'; // Not directly exported as named exportconst shimmer = require('shimmer'); shimmer.unwrap(targetModule, 'methodName'); - shimmer(options)
import shimmer from 'shimmer'; shimmer({ logger: myCustomLogger }); // ESM import might not initialize the configurable instance correctly without CJS interopconst shimmer = require('shimmer'); const customShimmer = shimmer({ logger: myCustomLogger });
Quickstart
const http = require('http');
const shimmer = require('shimmer');
console.log('Original http.request is:', http.request.__wrapped ? 'wrapped' : 'not wrapped');
shimmer.wrap(http, 'request', function (original) {
return function () {
console.log('>>> Intercepting http.request: Starting request!');
const args = Array.from(arguments);
const options = typeof args[0] === 'string' ? new URL(args[0]) : args[0];
console.log(' Request options:', options);
const returned = original.apply(this, arguments);
console.log('<<< Intercepting http.request: Done setting up request.');
return returned;
};
});
console.log('Patched http.request is:', http.request.__wrapped ? 'wrapped' : 'not wrapped');
// Example usage to trigger the wrapped function
const req = http.request('http://www.google.com', (res) => {
console.log(`STATUS: ${res.statusCode}`);
res.setEncoding('utf8');
res.on('data', (chunk) => {
// console.log(`BODY: ${chunk}`);
});
res.on('end', () => {
console.log('No more data in response.');
});
});
req.on('error', (e) => {
console.error(`problem with request: ${e.message}`);
});
req.end();
// Clean up
shimmer.unwrap(http, 'request');
console.log('Unwrapped http.request is:', http.request.__wrapped ? 'wrapped' : 'not wrapped');