npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

var-trap

v1.0.0

Published

a utility for creating trap objects to capture related values in a collection

Readme

var-trap

Table of Contents

Introduction

The utility creates an object with one or more trap properties. The latter are setters that store values passed to them via the equality (=) in a collection. The collection can be retrieved at a later point and "worked" with. A trap definition specifies a type of collection that is to be instantiated for a trap property and how a value passed via assignment is to be added to the aggregating data structure. A trap definition can also include methods that could be invoked on a trap property and receive a respective collection plus optional parameters. Each trapper property includes a delete() method, an invocation of which will delete the trap from its object.

Motivation

var-trap was originally written as an alternative to tracking rxjs subscriptions and "releasing" them once a module's life cycle is completed. One of the recommended generic mechanisms for unsubscribing from a single or multiple observables is to call pipe() on each observable with takeUntil() operator listening for the complete() call from the specifically instantiated unsubscriber observable. This following example illustrates the approach.

Unsubscribing using takeUntil() and an unsubscriber observable

import {Subject}   from 'rxjs';
import {takeUntil} from 'rxjs/operators';

@Component({
  selector: 'some',
  templateUrl: './some.component.html'
})
export class SomeComponent {
  unsubscribe$ = new Subject();
  
  constructor(private dataState$, private stream$) {}

  ngOnInit() {
    this.stream$.pipe(takeUntil(this.unsubscribe$)).subscribe((data) => console.log(data));
    this.dataState$.pipe(takeUntil(this.unsubscribe$)).subscribe((state) => console.log(state));
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}

In terms of code maintainability, the above approach has an advantage over storing subscriptions in variables and then disconnecting observables' listeners by calling unsubscribe() directly on each subscription. The disadvantage of the pipe()-takeUntil() method is extra verbosity. var-trap can be used instead to capture and store multiple subscriptions and do so with a smaller footprint as illustrated below.

Unsubscribing using var-trap

Before a trap object with trapper properties can be used, an appropriate trap definition must be imported or specified.

some-angular-component-file.js

import {createTrapObject} from 'var-trap';

@Component({
  selector: 'some',
  templateUrl: './some.component.html'
})
export class SomeComponent {
  trap = createTrapObject({$: 'observable'});
  
  constructor(private dataState$, private stream$) {}

  ngOnInit() {
    this.trap.$ = this.stream$.subscribe((data) => console.log(data));
    this.trap.$ = this.dataState$.subscribe((state) => console.log(state));
  }

  ngOnDestroy() {
    this.trap.$.unsubscribe();
  }
}

For the above instance, the var-trap version is about 20 percent smaller. Most application modules will be bigger than the examples and code "savings" due to the use of the library will be smaller, especially if traps would have to be locally defined. Nonetheless, even across a large program and using traps will result in a noticeable decrease in verbosity.

Installation

Installing the Library

To fetch var-trap, run the following command:

npm install --save var-trap

Distributed Versions

The library's default import (from var-trap) is either an EcmaScript (ES) or a CommonJS (as an UMD) module that bundles the source code without transpilation. The library makes use of proxies, latest native methods (e.g., Object.hasOwn), and data structures such as Set. The defaults are provided as such with the expectations that var-trap will be included as a dependency to a host project that, in turn, will be transpiled for some target environment or used, as is, in a browser or server-side environment (e.g., Node 20+) that supports the utilized language features.

For those rare circumstances when var-trap has to be utilized in older backend environments or included in a larger bundle without transpilation (for older browsers), the EcmaScript 5 distributable is available from var-trap\es5.

Usage

Defining Trap Types

storeFactory and valueAdder

storeFactory() and valueAdder() functions are required for a trap definition. storeFactory() should return an instance of a collection. valueAdder() receives a value and a collection and then adds the former to the latter.

The library will error out if any one of these is not provided.

import {addTrapDefinitions} from 'var-trap';

addTrapDefinitions('array', {
  storeFactory: () => [],
  valueAdder: (value, array) => array.push(value),
});

methods

methods is a list of user-defined functions that, internally and by default, receive a collection. Each function receives an actual (not cloned) collection created by storeFactory();

addTrapDefinitions('callbacks', {
  storeFactory: () => [],
  valueAdder: (callback, callbacks) => callbacks.push(callback),
  methods: {
    run(callbacks) {
      for(let callback of callbacks) {
        callback();
      }
    }
  }
});

A method, in addition to a collection, can also receive parameters.

addTrapDefinitions('array', {
  storeFactory: () => [],
  valueAdder: (value, array) => array.push(value),
  methods: {
    print(array, asString) {
      if(asString) {
        array = array.join(', ');
      }

      console.log(array);
    }
  }
});

By default, all of the methods are chainable. If a definition includes methods sum(), print(), and clear(), then they can be called one after another: trap.a.sum().print().clear().

var-trap does allow specifying methods that will return their value instead of an instance from which they are called.

addTrapDefinitions('numbers', {
  storeFactory: () => [],
  valueAdder: (number, numbers) => numbers.push(number),
  methods: {
    sum: {
      method(numbers) {
        return numbers.reduce((sum, number) => {
          return sum + number;
        });
      },
      configs: {returnValue: true}
    }
  }
});

Working with Trap Objects

Creating Trap Objects

All of the necessary trap definitions must be declared before a trap object relying on these definitions can be created. Because of a wide variety of circumstances in which trap objects can be used, at this time, no default trap pattern definitions are included with the library.

trap-definitions.js

import {addTrapDefinitions} from 'var-trap';

addTrapDefinitions({
  callbacks: {
    storeFactory: () => new Set(),
    valueAdder: (callback, callbacks) => callbacks.add(callback),
    methods: {
      clear(callbacks) {
        callbacks.clear()
      },
      run(callbacks) {
        callbacks.forEach((callback) => callback());
      }
    }
  },
  array: {
    storeFactory: () => [],
    valueAdder: (value, array) => array.push(value),
    methods: {
      print(array, asString) {
        if(asString) {
          array = array.join(', ');
        }

        console.log(array);
      }
    }
  }
});

To create a trap object, import createTrapObject() and provide it an object of trap property/trap definition name pairs.

file-that-uses-trap-objects.js

import {createTrapObject} from 'var-trap';

let trap = createTrapObject({a: 'array', c: 'callbacks'});

A trap object can be created as is for the purpose of traps being added later.

trap-objects.js

import {createTrapObject} from 'var-trap';

export const trap = createTrapObject();

trap-creating-file.js

import {trap} from './trap-objects';

trap.addTraps({a: 'array'});

Sending Values to a Trap

For each trap property, var-trap creates a setter that uses valueAdder() to store a value received via assignment (ie., =) inside a collection.

import {createTrapObject} from 'var-trap';
import {process1}         from './process1';
import {process2}         from './process2';
import {process3}         from './process3';

let trap = createTrapObject({c: 'callbacks'});

trap.c = process1.subscribe(() => {});
trap.c = process2.subscribe(() => {});
trap.c = process3.subscribe(() => {});

Retrieving a Collection of Trapped Values

For each trap property, var-trap creates a getter that returns an internally created instance that combines a collection that stores values with declared methods that can operate on the collection. Within the instance a collection can be accessed via store.

let trap = createTrapObject({a: 'array'});

trap.a = 1;
trap.a = 2;
trap.a = 3;

console.log(trap.a.store); // [1, 2, 3]

Invoking Methods on a Trap

Operating on the gathered values can be done directly.

let trap = createTrapObject({c: 'callbacks'});

trap.c = process1.subscribe(() => {});
trap.c = process2.subscribe(() => {});
trap.c = process3.subscribe(() => {});

trap.c.store.forEach((unsubscribe) => unsubscribe());
trap.c.store.clear();

The above example is appropriate for relatively rare situations. Procedures that are commonly applied to captured values could be abstracted as a part of a trap definition.

let trap = createTrapObject({c: 'callbacks'});

trap.c = process1.subscribe(() => {});
trap.c = process2.subscribe(() => {});
trap.c = process3.subscribe(() => {});

this.c.run().clear();

Invoking Methods on a Trap with Arguments

In the above array trap definition the print() method takes an optional asString argument. Just this parameter should be passed to the function to trigger the appropriate functionality.

let trap = createTrapObject({a: 'array'});

trap.a = 1;
trap.a = 2;

trap.a.print();     // [1, 2]
trap.a.print(true); // 1, 2

Deleting a Trap

A trap can be removed using the delete operator or the trap's default delete() method. The advantage of using the method is an option of executing delete() at the end of a methods chain.

let trap = createTrapObject({c: 'callbacks'});

trap.c = process1.subscribe(() => {});
trap.c = process2.subscribe(() => {});

delete trap.c;  // same as trap.c.delete()
let trap = createTrapObject({c: 'callbacks'});

trap.c = process1.subscribe(() => {});
trap.c = process2.subscribe(() => {});

trap.c.run().clear().delete();

Adding a Trap to an Existing Trap Object

Trap object comes with addTraps() method that is used internally by createTrapObject. addTraps() can be used after trap object instantiation to add extra trap properties.

let trap = createTrapObject({a: 'array'});

trap.addTraps({c: 'callbacks'});

Deleting a Trap Definition

The library makes available deleteTrapDefinitions for those rare cases when a trap definition needs to be removed.

let {deleteTrapDefinitions} from 'var-trap';

deleteTrapDefinitions('array', 'callbacks');

Development

Development Setup

Perform the following steps to setup the repository locally.

git clone https://github.com/aptivator/var-trap.git
cd var-trap
npm install

To start development mode run npm run dev or npm run dev:coverage.

Contributing Changes

The general recommendations for contributions are to use the latest JavaScript features, have tests with complete code coverage, and include documentation. The latter may be necessary only if a new feature is added or an existing documented feature is modified.