Sometimes, writing code for JavaScript getters and setters can get repetitive. Macro decorators allow you to DRY up your code by creating a decorator that can duplicate a getter/setter's functionality in multiple places!
Checkout the docs to get started, or continue reading for more information.
Using NPM:
npm install macro-decorators
Using Yarn:
yarn add macro-decorators
You can import the macro
decorator from macro-decorators
and use it to
define macros:
import macro from 'macro-decorators';
function fullNameMacro() {
return macro({
get() {
return `${this.firstName} ${this.lastName}`;
},
set(obj, key, value) {
let [firstName, lastName] = value.split(' ');
this.firstName = firstName;
this.lastName = lastName;
},
});
}
class User {
firstName;
lastName;
@fullNameMacro fullName;
}
class Admin {
firstName;
lastName;
@fullNameMacro fullName;
}
let captainMarvel = new User();
captainMarvel.fullName = 'Carol Danvers';
console.log(captainMarvel.firstName); // 'Carol'
console.log(captainMarvel.lastName); // 'Danvers'
console.log(captainMarvel.fullName); // 'Carol Danvers'
You can also create dynamic macros which accept parameters, to make them more reusable and composable:
import macro, { filter, reads } from 'macro-decorators';
function percent(dividendName, divisorName) {
return macro(function() {
let divisor = this[divisorName];
if (!divisor) {
return null;
}
return (this[dividendName] / divisor) * 100;
});
}
function formattedPercent(percentPropertyName) {
return macro(function() {
let value = this[percentPropertyName];
if (!value) {
return '--';
}
value = value.toFixed(2);
return `${value}%`;
});
}
class TestResultComponent {
testResults = [];
@filter('testResults', result => !result.finished)
errorBuilds;
@filter('testResults', result => result.finished && !result.succeeded))
failedBuilds;
@filter('testResults', result => result.finished && result.succeeded))
passedBuilds;
@reads('testResults.length') numberOfBuilds;
@reads('errorBuilds.length') numberOfErrorBuilds;
@reads('failedBuilds.length') numberOfFailedBuilds;
@reads('passedBuilds.length') numberOfPassedBuilds;
@percent('numberOfErrorBuilds', 'numberOfBuilds') percentOfErrorBuilds;
@percent('numberOfFailedBuilds', 'numberOfBuilds') percentOfFailedBuilds;
@percent('numberOfPassedBuilds', 'numberOfBuilds') percentOfPassedBuilds;
@formattedPercent('percentOfErrorBuilds') formattedPercentOfErrorBuilds;
@formattedPercent('percentOfFailedBuilds') formattedPercentOfFailedBuilds;
@formattedPercent('percentOfPassedBuilds') formattedPercentOfPassedBuilds;
}
The macro-decorators
library also ships with a number of predefined macros,
including the @filter
and @reads
decorators from the last example. Check out
the API doc for more information
on these macros.
Built in macros that receive a key to a different property as an argument can also receive a path of keys separated by periods:
import { reads } from 'macro-decorators';
class Person {
friends = [];
@reads('friends.length') numFriends;
}
Paths can be any length, but can only consist of string based property keys separated by periods. They cannot be dynamic.
See the Contributing guide for details.
macro-decorators
is built using TypeScript, and is compatible with both the
TypeScript and Babel Legacy stage 1 decorators transforms. This is following the
recommendation
of the decorators proposal champions.
The decorators spec is still changing and not finalized at all, but this library
intends to provide an upgrade path for the @macro
decorator and all of the
macros defined in it. This plan will be finalized when the decorators proposal
is at least stage 3 and beginning to see wide adoption. Ideally, it will:
Whether or not these goals will be technically feasible will depend on the final spec and its implementation.
This project is licensed under the MIT License.
import { MacroGetter } from 'macro-decorators';
A getter for a macro.
The instance of the class that the macro was applied to
The key of the class field that the macro was applied to
The value generated by the macro getter
import { MacroSetter } from 'macro-decorators';
A setter for a macro.
The instance of the class that the macro was applied to
The key of the class field that the macro was applied to
The value to set the macro to
import { alias } from 'macro-decorators';
A macro that aliases another property.
class Person {
fullName = 'Tony Stark';
@alias('fullName') properName;
}
let ironMan = new Person();
console.log(ironMan.properName); // Tony Stark
The alias is both ways, so updating the aliased property will also update the original property.
class Person {
fullName = 'Tony Stark';
@alias('fullName') properName;
}
let ironMan = new Person();
ironMan.properName = 'Anthony Stark';
console.log(ironMan.fullName); // Anthony Stark
The property path to alias
import { and } from 'macro-decorators';
A macro that gets the values of the paths that are passed to it, performs a logical and on them, and returns the result.
class Person {
hasSuit = true;
hasPowers = false;
hasCoolName = true;
@and('hasSuit', 'hasPowers', 'hasCoolName')
isHero;
}
let person = new Person();
console.log(person.isHero); // false
person.hasPowers = true;
console.log(person.isHero); // true
The paths of the properties to perform the and
operation on
import { bool } from 'macro-decorators';
A macro that gets the value of the path that is passed to it, and returns its coerced boolean value.
class Person {
name;
@bool('name') hasName;
}
let wonderWoman = new Person();
console.log(wonderWoman.hasName); // false
person.name = 'Diana Prince';
console.log(wonderWoman.hasName); // true
The path of the property to return the boolean value of
import { collect } from 'macro-decorators';
A macro that collects the values of one or more property paths and returns them in an array.
class Person {
suit;
cape;
helmet;
@collect('suit', 'cape', 'helmet') costumeParts;
}
let ironMan = new Person();
console.log(ironMan.costumeParts); // [undefined, undefined, undefined];
ironMan.suit = 'Iron Suit';
ironMan.helmet = 'Iron Helmet';
console.log(ironMan.costumeParts); // ['Iron Suit', undefined, 'Iron Helmet'];
The paths of the properties to collect into the array
import { deprecatingAlias } from 'macro-decorators';
A macro that aliases another property, but warns the user if they access it. This is useful for renaming properties or warning users of pending deprecations.
class Person {
fullName = 'Tony Stark';
@deprecatingAlias('fullName', 'No longer necessary since the press conference')
secretIdentity;
}
let ironMan = new Person();
console.log(ironMan.secretIdentity); // Tony Stark
The property path to alias
The warning to log when the property is accessed
import { diff } from 'macro-decorators';
A macro that returns a new array with all the items from the first array that are not in any of the other arrays passed to it.
class Hamster {
likes = [
'banana',
'grape',
'kale'
];
fruits = [
'grape',
'kale',
]
@diff('likes', 'fruits') wants;
}
hamster.wants; // ['banana']
The paths of the arrays to diff
import { empty } from 'macro-decorators';
Returns whether or not a field is empty. The field will be considered empty if it is falsy OR if it is an empty array.
class TodoList {
todos = [];
@empty('todos') done;
}
let list = new TodoList();
console.log(list.done); // true
list.todos.push('Stop Thanos');
console.log(list.done); // false
The path of the property to check for emptiness
import { equal } from 'macro-decorators';
A macro that gets the provided path and checks to see if it is equal to the given value.
class Person {
name;
@equal('name', 'Tony Stark') isIronMan;
}
let tony = new Person();
console.log(tony.isIronMan); // false
tony.name = 'Tony Stark';
console.log(tony.isIronMan); // true
The path of the value to compare
The value to compare against
import { filter } from 'macro-decorators';
A macro that returns an array filtered by a filter function.
class Earth {
people = [
{
name: 'Carol Danvers',
isHero: true,
},
{
name: 'Tony Stark',
isHero: true,
},
{
name: 'Otto Octavius',
isVillain: true,
},
];
@filter('people', p => p.isHero) heroes;
}
let earth = new Earth();
console.log(earth.heroes); // [{ name: 'Carol Danvers', ... }, { name: 'Tony Stark', ... }]
The path of the array to filter
The callback function to filter the array with
import { filterBy } from 'macro-decorators';
A macro that returns an array of objects filtered by a property on the objects.
class Earth {
people = [
{
name: 'Carol Danvers',
isHero: true,
},
{
name: 'Tony Stark',
isHero: true,
},
{
name: 'Otto Octavius',
isHero: false,
},
];
@filterBy('people', 'isHero') heroes;
}
let earth = new Earth();
console.log(earth.heroes); // [{ name: 'Carol Danvers', ... }, { name: 'Tony Stark', ... }]
A value can also be passed to compare the property to.
class Earth {
people = [
{
name: 'Carol Danvers',
isHero: true,
},
{
name: 'Tony Stark',
isHero: true,
},
{
name: 'Otto Octavius',
isHero: false,
},
];
@filterBy('people', 'isHero', false) villains;
}
let earth = new Earth();
console.log(earth.heroes); // [{ name: 'Otto Octavius', ... }]
The path of the array of objects to filter
The key to filter the objects by
A value to compare against when filtering
import { gt } from 'macro-decorators';
A macro that gets the provided path and compares it to see if it is greater than the given value.
class Person {
age = 64;
@gt('age', 64) isSeniorCitizen;
}
let cap = new Person();
console.log(cap.isSeniorCitizen); // false;
cap.age++;
console.log(cap.isSeniorCitizen); // true;
The path of the value to compare
The value to compare against
import { gte } from 'macro-decorators';
A macro that gets the provided path and compares it to see if it is greater than or equal to the given value.
class Person {
age = 64;
@gte('age', 65) isSeniorCitizen;
}
let cap = new Person();
console.log(cap.isSeniorCitizen); // false;
cap.age++;
console.log(cap.isSeniorCitizen); // true;
The path of the value to compare
The value to compare against
import { intersect } from 'macro-decorators';
A macro that returns the intersection of one or more arrays that are passed to it:
class NumbersBelowTen {
prime = [1, 2, 3, 5, 7];
fib = [1, 1, 2, 3, 5, 8];
odd = [1, 3, 5, 7, 9];
@intersect('prime', 'fib', 'odd') superSpecialNums;
}
let belowTen = new NumbersBelowTen();
console.log(belowTen.superSpecialNums); // [1,3,5]
The paths of the arrays to get the intersection of
import { lt } from 'macro-decorators';
A macro that gets the provided path and compares it to see if it is less than to the given value.
class Person {
age = 16;
@lt('age', 18) isAKid;
}
let spidey = new Person();
console.log(spidey.isAKid); // true;
spidey.age = 18;
console.log(spidey.isAKid); // false;
The path of the value to compare
The value to compare against
import { lte } from 'macro-decorators';
A macro that gets the provided path and compares it to see if it is less than or equal to the given value.
class Person {
age = 16;
@lte('age', 17) isAKid;
}
let spidey = new Person();
console.log(spidey.isAKid); // true;
spidey.age = 18;
console.log(spidey.isAKid); // false;
The path of the value to compare
The value to compare against
import macro from 'macro-decorators';
The @macro
decorator can be used to define custom macro decorators for
getters and setters, which can DRY up code that is repetitive and boilerplate
heavy.
function fullNameMacro(firstNameKey, lastNameKey) {
return macro(function() {
return `${this[firstNameKey]} ${this[lastNameKey]}`;
});
}
class Person {
firstName = 'Carol';
lastName = 'Danvers';
@fullNameMacro('firstName', 'lastName')
fullName;
@fullNameMacro('firstName', 'lastName')
properName;
}
let captainMarvel = new Person();
console.log(captainMarvel.fullName); // Carol Danvers
macro
receives either a getter function, or a
descriptor object that contains a getter and/or setter:
function fullNameMacro(firstNameKey, lastNameKey) {
return macro({
get() {
return `${this[firstNameKey]} ${this[lastNameKey]}`;
},
set(obj, key, value) {
let [firstName, lastName] = value.split(' ');
this[firstNameKey] = firstName;
this[lastNameKey] = lastName;
}
});
}
class Person {
firstName = 'Carol';
lastName = 'Danvers';
@fullNameMacro('firstName', 'lastName')
fullName;
}
let captainMarvel = new Person();
console.log(captainMarvel.fullName); // Carol Danvers
captainMarvel.fullName = 'Monica Rambeau';
console.log(captainMarvel.firstName); // Monica
console.log(captainMarvel.lastName); // Rambeau
The getter and setter functions both receive the class instance as the first
argument and the key being accessed as the second. The setter recieves tha
value to be set as the third argument. See the definitions for
MacroGetter and MacroSetter for more details.
Both functions are called with the class instance bound as the this
context, if possible.
The definition of the macro to apply to the field
import { map } from 'macro-decorators';
A macro that returns an array mapped by a function.
class Earth {
people = [
{
name: 'Carol Danvers',
},
{
name: 'Tony Stark',
},
{
name: 'Otto Octavius',
},
];
@map('people', p => p.name) names;
}
let earth = new Earth();
console.log(earth.names); // ['Carol Danvers', 'Tony Stark', 'Otto Octavius']
The path of the array to map over
The function to map over the array with
import { mapBy } from 'macro-decorators';
A macro that returns an array of objects mapped by the specified key.
class Earth {
people = [
{
name: 'Carol Danvers',
},
{
name: 'Tony Stark',
},
{
name: 'Otto Octavius',
},
];
@mapBy('people', 'name') names;
}
let earth = new Earth();
console.log(earth.names); // ['Carol Danvers', 'Tony Stark', 'Otto Octavius']
The path of the array of objects to map over
The key of the objects to pluck into the new array
import { match } from 'macro-decorators';
A macro that returns whether or not the provided path matches a regular expression.
class Person {
age = 29;
@match('age', /\d+/) ageIsValid;
}
let person = new Person();
console.log(person.ageIsValid); // true
person.age = 'twenty-nine';
console.log(person.ageIsValid); // false
The path of the value to match against
import { max } from 'macro-decorators';
A macro that returns the maximum value from the specified array.
class NumbersBelowTen {
prime = [1, 2, 3, 5, 7];
fib = [1, 1, 2, 3, 5, 8];
odd = [1, 3, 5, 7, 9];
@max('prime') biggestPrime;
}
let belowTen = new NumbersBelowTen();
console.log(belowTen.biggestPrime); // 7
The path to the array to find the max value of
import { min } from 'macro-decorators';
A macro that returns the minimum value from the specified array.
class NumbersBelowTen {
prime = [1, 2, 3, 5, 7];
fib = [1, 1, 2, 3, 5, 8];
odd = [1, 3, 5, 7, 9];
@max('prime') smallestPrime;
}
let belowTen = new NumbersBelowTen();
console.log(belowTen.smallestPrime); // 1
The path to the array to find the min value of
import { not } from 'macro-decorators';
A macro that returns the logical not of the provided path.
class Car {
speed = 0;
@not('speed') isParked;
}
let batmobile = new Car();
console.log(batmobile.isParked); // true
batmobile.speed = 100;
console.log(batmobile.isParked); // false
The path of the property to perform the logical not on
import { notEmpty } from 'macro-decorators';
Returns whether or not a field is NOT empty. The field will be considered non-empty if it is truthy OR if it is a non-empty array.
class Person {
frends = [];
@notEmpty('friends') hasFriends;
}
let hulk = new Person();
console.log(hulk.hasFriends); // false
hulk.friends.push('Thor');
console.log(hulk.hasFriends); // true
The path of the property to check for non-emptiness
import { nullish } from 'macro-decorators';
A macro that returns whether or not the provided path is nullish.
The path of the property to check for nullish-ness of
import { or } from 'macro-decorators';
A macro that gets the values of the paths that are passed to it, performs a logical or on them, and returns the result.
class Person {
hasSuit = false;
hasPowers = false;
hasCoolName = false;
savesLives = false
@or('hasSuit', 'hasPowers', 'hasCoolName', 'savesLives')
isHero;
}
let person = new Person();
console.log(person.isHero); // false
person.savesLives = true;
console.log(person.isHero); // true
import { overridableReads } from 'macro-decorators';
A macro that provides an overridable read-only alias to another property. When set, the alias will be overwritten and disconnected from the value that was aliased, disconnecting the two for good.
class Hero {
ownedBy = 'Disney';
@overridableReads('ownedBy') universe;
}
let wolverine = new Hero();
console.log(wolverine.universe); // Disney
wolverine.universe = 'X-men';
console.log(wolverine.ownedBy); // Disney
console.log(wolverine.universe); // X-men
The property path to alias
import { reads } from 'macro-decorators';
A macro that provides a read-only alias to another property.
class Person {
fullName = 'Tony Stark';
@reads('fullName') properName;
}
let ironMan = new Person();
console.log(ironMan.properName); // Tony Stark
ironMan.properName = 'Anthony Stark'; // Throws an error
A default value can be provided as the second parameter to the decorator. If the value that is aliased is nullish, then the default value will be returned instead:
class Hero {
ownedBy;
@reads('ownedBy', 'Marvel') universe;
}
let batman = new Hero();
console.log(batman.universe); // Marvel
batman.ownedBy = 'DC';
console.log(batman.universe); // DC
If the default value is a function, then the function will be called and its return value will be used instead. This should be used for values like arrays or objects, so they are unique per-instance of the class.
class Person {
@reads('contacts', () => []) friends;
}
The property path to alias
The default value to set the property to if the aliased property is nullish. If a function, the function will be called.
import { sort } from 'macro-decorators';
A macro that returns the specified array sorted by a sort function. The array is duplicated, so the original is not modified in any way.
class Earth {
people = [
{
name: 'Carol Danvers',
},
{
name: 'Tony Stark',
},
{
name: 'Otto Octavius',
},
];
@sort('people', (person1, person2) => {
return person1.name > person2.name ? 1 : -1
}) sortedPeople;
}
let earth = new Earth();
console.log(earth.sortedPeople); // [{ name: 'Carol Danvers', ... }, { name: 'Otto Octavius', ... }, { name: 'Tony Stark', ... }]
The path of the array to sort
The function to sort the array with
import { sortBy } from 'macro-decorators';
A macro that returns the specified array of objects sorted by the specified
key. Uses standard JavaScript comparisons (>
and <
operators) for
sorting. Can also specify a direction (ascending or descending).
class Earth {
people = [
{
name: 'Carol Danvers',
},
{
name: 'Tony Stark',
},
{
name: 'Otto Octavius',
},
];
@sortBy('people', 'name') sortedPeople;
}
let earth = new Earth();
console.log(earth.sortedPeople); // [{ name: 'Carol Danvers', ... }, { name: 'Otto Octavius', ... }, { name: 'Tony Stark', ... }]
The path of the array of objects to sort
The key of the value to sort the objects by
Whether the sort should be ascending or descinding
import { sum } from 'macro-decorators';
A macro that returns the sum of the values in the specified array.
class NumbersBelowTen {
prime = [1, 2, 3, 5, 7];
fib = [1, 1, 2, 3, 5, 8];
odd = [1, 3, 5, 7, 9];
@sum('prime') sumOfThePrimes;
}
let belowTen = new NumbersBelowTen();
console.log(belowTen.sumOfThePrimes); // 18
The path of the array to sum
import { union } from 'macro-decorators';
A macro that returns the intersection of one or more arrays that are passed to it:
class NumbersBelowTen {
prime = [1, 2, 3, 5, 7];
fib = [1, 1, 2, 3, 5, 8];
odd = [1, 3, 5, 7, 9];
@union('prime', 'fib', 'odd') otherSuperSpecialNums;
}
let belowTen = new NumbersBelowTen();
console.log(belowTen.otherSuperSpecialNums); // [1,2,3,5,7,8,9]
The paths of the arrays to get the union of
import { unique } from 'macro-decorators';
A macro that returns the unique values in an array.
class NumbersBelowTen {
prime = [1, 2, 3, 5, 7];
fib = [1, 1, 2, 3, 5, 8];
odd = [1, 3, 5, 7, 9];
@unique('fib') uniqueFib;
}
let belowTen = new NumbersBelowTen();
console.log(belowTen.uniqueFib); // [1,2,3,5,8]
The path of the array to get all unique values of
import { unique } from 'macro-decorators';
A macro that returns the values in an array of objects that are unique by the specified key.
class Person {
contacts = [
{ name: 'Jessica', phone: '555-1234' }
{ name: 'Jake', phone: '555-4321' }
{ name: 'Jess', phone: '555-1234' }
]
@uniqueBy('contacts', 'phone') friends;
}
let person = new Person();
console.log(person.friends); // [{ name: 'Jessica', ... }, { name: 'Jake', ... }]
The path of the array of objects
The key of the value to check for uniqueness
Generated using TypeDoc
import { MacroDescriptor } from 'macro-decorators';
A descriptor for a macro. Contains either a macro getter, a macro setter, or both.