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 🙏

© 2024 – Pkg Stats / Ryan Hefner

property-tunnel

v1.2.2

Published

Library providing class/object property & function alias (aka delegate or tunnel) support.

Downloads

64

Readme

property-tunnel: aliases to reduce boilerplate in Typescript and Javascript

npm Travis Maintainability Test Coverage

This library bores tunnels from one object property to another object property in order to create aliases. Includes a decorator for use on class properties (both instance and static).

Installation

Yarn: yarn add property-tunnel

npm: npm install --save property-tunnel

Typescript

To use @alias you need to set experimentalDecorators: true in your tsconfig.json.

Targeting ES5 and below

If you want to use @alias to make a class iterable you'll need to set downlevelIteration: true in your tsconfig.json.

Javascript

To use @alias you need to use the transform-decorators Babel plugin.

API

The public API is what is available directly via the 'property-tunnel' module. Everything else is subject to change. These are:

@alias(["property", "to", "alias"], { <options> })

A decorator for use on class properties that creates an alias to another property or subproperty of the same class. Works on both instance and static properties.

It takes one or two arguments: an array of property keys that designates the property to alias and an options object.

Options are described by the AliasOptions interface.

Example 1: Null-safe alias for a deeply nested property

import { alias } from "property-tunnel";

class Person {
    public parents: { mother?: Person, father?: Person } = {};

    public firstName: string;
    public lastName: string;

    // If you know that everyone who ever uses your code will be using
    // Typescript you can omit the `access` option for readonly aliases
    // and just use the compiler-enforced readonly keyword.
    @alias(["parents", "father", "lastName"], { access: "readonly" })
    public readonly maidenName?: string;

    public constructor(firstName: string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Example 2: Using an alias to make a class iterable through a property

import { alias } from "property-tunnel";

class Worksheet {
    public title: string;

    public constructor(title: string) {
        this.title = title;
    }

    // ...the rest of the class...
}

class Workbook {
    public sheets: Worksheet[] = [];
    private lastUntitledNum = 0;

    public constructor() {
        // Create a default sheet titled "Untitled 0"
        this.createSheet();
    }

    public createSheet(title?: string): Worksheet {
        if (!title) title = `Untitled ${this.lastUntitledNum++}`;
        const sheet = new Worksheet(title);
        this.sheets.push(sheet);
        return sheet;
    }

    // Implement the iterable protocol through aliasing.
    @alias(["sheets", Symbol.iterator])
    public [Symbol.iterator]: () => Iterator<Worksheet>;
}

const workbook = new Workbook();
// Add a second sheet (see constructor)
workbook.createSheet("My Sheet");
for (let sheet of workbook) {
    console.log(sheet.title);
}
// prints: "Untitled 0", "My Sheet"

interface AliasOptions

Contains four options, none of which are required:

const myOptions: AliasOptions = {
    /**
     * One of: "readonly" or "readwrite" (specified by TunnelAccess)
     *
     * If "readonly" is used then any assignments to the alias result in a
     * TypeError.
     *
     * If a readwrite property is assigned and the destination property is
     * unreachable a ReferenceError will be thrown.
     *
     * N.B. An option may be added in a later version that will permit
     * automatic creation of the destination property when it doesn't exist.
     *
     * Default: "readwrite"
     */
    access: "readonly",

    /**
     * The value of the alias if the destination is null, undefined or
     * if one of the intermediate properties is unreachable.
     *
     * Default: undefined
     */
    defaultValue: "my default value",

    /**
     * If provided maps values to/from the destination property.
     *
     * See TunnelConverter below.
     */
    converter: null,

    /**
     * Whether the alias should be bound to the destination if the destination
     * property is a function.
     *
     * Default: true
     */
    autoBindFunctions: false,
};

tunnel(to, key, { <options> })

Used to create a tunnel on an arbitrary object. Used by alias to implement class property aliases.

Options are described by the TunnelOptions interface.

Usage

const destination = { prop: "foobar" };
const proxy = {};
tunnel(proxy, "alias", { destination, destinationKey: "prop" });
console.log(proxy.alias); // prints "foobar"
proxy.alias = "bazquirk";
console.log(destination.prop); // prints "bazquirk"

interface TunnelOptions

Contains all of the options from AliasOptions plus destination and destinationPath.

const myOptions: TunnelOptions = {
    /**
     * The destination object that owns the property to tunnel to.
     */
    destination: theDestination,

    /**
     * An array composed of strings, symbols and/or numbers that designates
     * the property of `destination` to tunnel to.
     */
    destinationPath: ["a", "property", "of", "theDestination"]
};

type TunnelAccess = "readonly" | "readwrite"

Literally one of the strings "readonly" or "readwrite".

interface TunnelConverter

Used to specify two functions that convert between the alias/tunnel and destination values.

You might use this to convert between types or to reformat values.

Usage

const percentConverter = {
    // Read as "to tunnel value"
    toTunnel(value: number) { return value * 100; },
    // Read as "from tunnel value"
    fromTunnel(value: number) { return value / 100; }
};

class Progress {
    public value: number = 0;

    // Then on a class property
    @alias(["value"], { converter: percentConverter })
    public percent: number;
}

const progress = new Progress();
progress.value = 0.5;
console.log(progress.percent); // prints: 50
progress.percent = 100;
console.log(progress.value); // prints: 1

PropertyTunnel class

This is a lower level class that can be used if you ever need to connect multiple tunnels to the same destination.

It's unlikely you'll ever need to use this. The tunnel() function uses it internally and returns the instance it creates.

If you find you need to use it have a look at the src/PropertyTunnel.ts file, it's heavily commented.

Notes

I've liberated this library from one of my own proprietary projects. Some of the code that isn't part of the public API has much greater reuse value in the original project.

For the time being I've included some of that code in this library but it will be moved to another open source library I'll be releasing in good time.

It's for this reason that it's particularly important that you don't use anything that isn't explicitly included in the public API as summarised above.