{"id":18879709,"url":"https://github.com/loilo/tapsig","last_synced_at":"2026-02-20T03:30:18.200Z","repository":{"id":65514555,"uuid":"125363187","full_name":"loilo/tapsig","owner":"loilo","description":"Tacks custom extensions onto existing JavaScript libraries","archived":false,"fork":false,"pushed_at":"2020-12-23T23:02:20.000Z","size":168,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-31T14:18:59.311Z","etag":null,"topics":["javascript","library","proxy"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/loilo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-03-15T12:21:35.000Z","updated_at":"2023-04-11T15:22:38.000Z","dependencies_parsed_at":"2023-01-26T21:15:46.576Z","dependency_job_id":null,"html_url":"https://github.com/loilo/tapsig","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Ftapsig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Ftapsig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Ftapsig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Ftapsig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/loilo","download_url":"https://codeload.github.com/loilo/tapsig/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239841742,"owners_count":19705981,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","library","proxy"],"created_at":"2024-11-08T06:38:54.436Z","updated_at":"2026-02-20T03:30:18.170Z","avatar_url":"https://github.com/loilo.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tapsig\n\n[![JavaScript Style Guide](https://badgen.net/badge/code%20style/standard/green)](https://standardjs.com)\n[![Tests](https://badgen.net/github/checks/loilo/tapsig/master)](https://github.com/loilo/tapsig/actions)\n[![npm](https://badgen.net/npm/v/tapsig)](https://npmjs.com/package/tapsig)\n\n![Tapsig](tapsig.png)\n\nThis tiny library (0.8kb minified \u0026 gzipped) tacks custom extensions onto existing JavaScript functions and objects. That makes it incredibly easy to supplement existing JavaScript libraries with custom methods without touching its original code.\n\nIt works by wrapping the target in a [Proxy](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy). The Proxy sticks to the tapped library by attaching itself to properties accessed or methods called on it.\n\nThis package works in Node.js and in the browser. Note however that the browser *must* support ES2015 Proxies (which are not polyfillable), which leaves out IE11 in particular.\n\n## Table of Contents\n\n* [Installation](#installation)\n  * [Include in the Browser](#include-in-the-browser)\n  * [Include in Node.js](#include-in-nodejs)\n* Usage\n  * [Basic Example](#basic-example)\n  * [Dynamic Injections](#dynamic-injections)\n  * [Naming Conflicts](#naming-conflicts)\n  * [Inject Getters](#inject-getters)\n  * [Untapping](#untapping)\n  * [Catch-Missing and Catch-All](#catch-missing-and-catch-all)\n    * [`MISSING`](#missing)\n    * [`ALL`](#all)\n  * [Checking if an Object is Tapped](#checking-if-an-object-is-tapped)\n  * [Masking Values](#masking-values)\n  * [Debugging](#debugging)\n* [\"Tapsig\"?](#tapsig-1)\n\n\n## Installation\nInstall it from npm:\n\n```bash\nnpm install --save tapsig\n```\n\n### Include in the Browser\nYou can use this package in your browser with one of the following snippets:\n\n* The most common version. Introduces a global `tapsig` variable, runs in all modern browsers:\n\n  ```html\n  \u003cscript src=\"node_modules/tapsig/dist/browser.min.js\"\u003e\u003c/script\u003e\n\n  \u003c!-- or from CDN: --\u003e\n\n  \u003cscript src=\"https://unpkg.com/tapsig\"\u003e\u003c/script\u003e\n  ```\n\n* If you're really living on the bleeding edge and use ES modules directly in the browser, you can `import` the package as well:\n\n  ```javascript\n  import * as tapsig from \"./node_modules/tapsig/dist/browser.esm.min.js\"\n\n  // or from CDN:\n\n  import * as tapsig from \"https://unpkg.com/tapsig/dist/browser.esm.min.js\"\n  ```\n\n  As opposed to the first snippet, this will not create a global `tapsig` function.\n\n\n### Include in Node.js\nInclude this package in Node.js like you usually do:\n\n```javascript\nconst tapsig = require('tapsig')\n```\n\nIf you use `--experimental-modules`, there's a `.mjs` version, too:\n\n```javascript\nimport * as tapsig from 'tapsig/dist/node.esm'\n```\n\n\n## Usage\n### Basic Example\nNow that we have grabbed the `tapsig` object, we can start injecting custom properties and methods into a library. Since most of us probably know jQuery, let's take that as an example.\n\nRemember older jQuery versions? They had a `size()` method that was removed in favor of the `length` property.\n\nNow let's re-implement that method. We do so by passing the library we want to wrap and a thing we call an \"injection object\" to the `tap()` method:\n\n```javascript\nconst $ = tapsig.tap(jQuery, {\n  size () {\n    return this.length\n  }\n})\n\n$('div').size() // Returns some number\n```\n\nThere are some things to note here:\n1. Notice how the `size()` method is available not only on `$` but also on `$('div')`? That's the whole point of Tapsig: it reproduces and attaches itself recursively to every property or method you access on the originally tapped library.\n\n   This also means that if we returned an object or a function from our `size()` method, that return value would also be tapped.\n2. The `this` context of the `size()` method (and any other method defined on the injection object) points to the tapped object the method it is called on — in our case that's the tapped `$('div')` collection. If you want to access the underlying *untapped* object, use the [`untap()`](#untapping) method.\n\n### Dynamic Injections\nThe first point noted at the end of the last section is a feature, but in our example it can be quite unhandy: In most cases, we want to inject our custom properties only under certain circumstances.\n\nIn the example above, the `size()` method is not only available on the `$('div')`, but also on the `$` itself. However, `$` is not a jQuery collection and thus `$.size()` would return `undefined`.\n\nThat's why we only want to provide the `size()` method on a jQuery collection. For that purpose, we can inject a function instead of an object. The function decides on a case-by-case basis which properties to provide:\n\n```javascript\nconst $ = tapsig.tap(jQuery, target =\u003e {\n  // Only add the `size()` method on a jQuery collection\n  if (target instanceof jQuery) {\n    return {\n      size () {\n        return this.length\n      }\n    }\n  }\n\n  // If we don't return anything, no custom properties are added\n})\n\n$('div').size() // Still returns some number\n$.size() // TypeError: $.size is not a function\n```\n\n### Naming Conflicts\nInjected custom properties will shadow existing ones. In other words, custom properties will always take precedence over builtin properties.\n\n### Inject Getters\nYou may provide getters in an injection object:\n\n```javascript\nconst $ = tapsig.tap(jQuery, {\n  get version () {\n    return jQuery.fn.jquery\n  }\n})\n```\n\n### Untapping\nYou can unwrap a tapped object with the `untap()` method, e.g. if you need to use the original API in a method call:\n\n```javascript\nconst $ = tapsig.tap(jQuery, {\n  foo () {\n    // We want to check if there's a `foo` property\n    // in the tapped object:\n    return 'foo' in tapsig.untap(this)\n  }\n})\n```\n\n\u003e **Note:** Both the `tap()` and the `untap()` methods are idempotent. Tapping an already tapped object won't do anything, just like untapping a non-tapped object will have no effect.\n\n### Catch-Missing and Catch-All\nThe Tapsig library exposes the `ALL` and `MISSING` symbols. You can use them as method names in the injection object to achieve certain behaviour.\n\n#### `MISSING`\nA method named with the `MISSING` symbol will be used to handle property access for properties that are *not* explicitely defined in the injection object and *not* found in the original tapped API:\n\n```javascript\nconst $ = tapsig.tap(jQuery, {\n  foo: 'bar',\n  [tapsig.MISSING] (name) {\n    return `no such property '${name}'`\n  }\n})\n\n$.foo // \"bar\", as defined in the injection object\n$.baz // \"no such property 'baz'\", returned by the MISSING method\n$.ajax // The AJAX function from the jQuery library\n```\n\n#### `ALL`\nA method named with the `ALL` symbol will be used to handle *every* property access for properties that are *not* explicitely defined in the injection object. This means that the `ALL` method takes precedence even over built-in properties.\n\n```javascript\nconst $ = tapsig.tap(jQuery, {\n  foo: 'bar',\n  [tapsig.ALL] (name) {\n    return `no such property '${name}'`\n  }\n})\n\n$.foo // \"bar\", as defined in the injection object\n$.baz // \"no such property 'baz'\", returned by the ALL method\n$.ajax // \"no such property 'ajax'\", returned by the ALL method\n```\n\n\u003e **Warning:** Be **very careful** when using the `ALL` method. It will be called to answer requests for JavaScript-Builtins like `prototype`, literally *any* property. This can lead to unexpected results, so you should always be aware and possibly quite restrictive about which properties you answer:\n\n  ```javascript\n  const $ = tapsig.tap(jQuery, {\n    [tapsig.ALL] (name) {\n      if (name.startsWith('foo_')) {\n        return // something you want to achieve with all `foo_` properties\n      } else {\n        return tapsig.untap(this)[name]\n      }\n    }\n  })\n  ```\n\n\u003e **Note:** The results of both the `MISSING` and `ALL` method will be tapped before they go back to the user.\n\n### Checking if an Object is Tapped\nYou can check if an object is tapped by running `tapsig.isTapped(object)`.\n\n### Masking Values\nBy default, all injected properties and all results returned from injected functions will be tapped.\n\nIf you want to prevent such a value to be tapped, you can use the `mask()` method:\n\n```javascript\nconst $ = tapsig.tap(jQuery, {\n  originalJQuery () {\n    // Untap the proxy, then mask it\n    return tapsig.mask(tapsig.untap(this))\n  }\n})\n\n// $.originalJQuery() === jQuery\n```\n\n### Tap Promises\nThere are [edge cases](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Called_on_incompatible_type) where wrapping an object in a Proxy does not work. Promises are one of those.\nTherefore, `tapsig` will not tap Promises themselves, but wrap them in another Promise whose resolved result will in turn be tapped.\n\n### Debugging\nThe Node.js version of Tapsig uses the [debug](https://npmjs.com/package/debug) utility to print logs.\n\nFor the sake of bundle size, the browser build uses just a simple `console.log()`. As opposed to the Node.js logs it has to be enabled manually by setting the `verbose` parameter (3rd parameter of `tapsig.tap()`) to `true`.\n\n## \"Tapsig\"?\nIt's hard these days to find a good module name that's available on npm. I wanted some playful name related to \"tap\" and came up with a word in my mother tongue: \"tapsig\" (roughly pronounced like \"tub-zig\") is German for \"clumsy\", the kind you can observe when kittens or toddlers practice their first steps. 🐾\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floilo%2Ftapsig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Floilo%2Ftapsig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floilo%2Ftapsig/lists"}