property-tunnel
TypeScript icon, indicating that this package has built-in type declarations

1.2.2 • Public • Published

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.

Package Sidebar

Install

npm i property-tunnel

Weekly Downloads

0

Version

1.2.2

License

MIT

Unpacked Size

67.9 kB

Total Files

46

Last publish

Collaborators

  • ogwh