Parcel

Parcel is a data container. Its job is to hold your data, split it into smaller parts, and merge changes back together.

If you're using React, you probably won't be instanciating parcels directly. Please see the getting started page to see how to best use Parcels in a React app.

import Parcel from 'dataparcels';
import Parcel from 'react-dataparcels';
new Parcel({
    value?: any,
    handleChange?: Function
});
  • value ?: any = undefined

    The value you want to put in the Parcel. This value will be changed immutably when change methods are called on the Parcel. The data type of the value will determine the type of Parcel that will be created, and will determine which methods you can use to change the value. Please read Parcel types for more info.

  • handleChange ?: (newParcel: Parcel, changeRequest: ChangeRequest) => void

    The handleChange function will be called whenever the Parcel's value has been triggered to change. It is passed newParcel, a replacement Parcel containing the changes; and changeRequest, a ChangeRequest that contains details about the change itself.

    In handleChange you would typically implement logic to replace your current parcel with newParcel, but if you're using React you should read getting started to save you the trouble of implementing this yourself.

// creates a Parcel that contains a value of 123
let parcel = new Parcel({
    value: 123,
    handleChange: (newParcel) => {
        // here you can insert logic to replace
        // your existing parcel with the newParcel
    })
});

Examples can be found on the data editing page.

Properties

value

value: any

Returns the Parcel's value.

let value = 123;
let parcel = new Parcel({value});
parcel.value; // returns 123

meta

meta: Object

Returns an object containing the parcel's meta data. Read Parcel meta for more info.

let value = 123;
let parcel = new Parcel({value});
parcel.meta; // returns {}

// set initial meta and check again
parcel
    .initialMeta({abc: 123})
    .meta; // returns {abc: 123}

data

data: Object

Returns an object containing the Parcel's data, which includes:

  • value - The Parcel's value
  • meta - The Parcel's meta object
  • key - The Parcel's key
  • child - The Parcel's child information, which includes any meta, key and child data related to the values children.
let value = 123;
let parcel = new Parcel({value});
parcel.data;

// return {
//     child: undefined,
//     key: '^',
//     meta: {},
//     value: 123,
// }

key

key: string

Returns the Parcel's key. Dataparcels automatically gives unique keys to all children of a parent parcel. See parcel keys for more info.

Because they are unique, the can be used as keys when rendering an array of elements with React. This is demonstrated here.

let value = {
    abc: 123,
    def: 456
};
let parcel = new Parcel({value});
parcel.get("abc").key; // returns "abc"
let value = [
    123,
    456
];
let parcel = new Parcel({value});
parcel.get(0).key; // returns "#a"

id

id: string

Returns the Parcel's id. Under most circumstances, ids are unique among all Parcels that are descendants of a single original Parcel. You won't often need to use this, but it can sometimes be useful for debugging.

let value = {
    abc: 123,
    def: 456
};
let parcel = new Parcel({value});
parcel.get("abc").id; // returns "^.abc"

path

path: string[]

Returns the Parcel's path, an array of strings indicating how to access the current Parcel's value.

let value = {
    abc: {
        def: 123
    }
};
let parcel = new Parcel({value});
parcel.get("abc").get("def").path; // returns ["abc", "def"]

Branch methods

get()

get(key: string|number): Parcel // only on ParentParcels
get(key: string|number, notSetValue: any): Parcel // only on ParentParcels

Returns a Parcel containing the value associated with the provided key / index. If the key / index doesn't exist, a Parcel with a value of notSetValue will be returned. If notSetValue is not provided then a Parcel with a value of undefined will be returned.

let value = {
    abc: 123,
    def: 456
};
let parcel = new Parcel({value});
parcel.get('abc').value; // returns 123
parcel.get('xyz').value; // returns undefined
parcel.get('xyz', 789).value; // returns 789

get() with indexed values

When called on a Parcel with an indexed value, such as an array, this method can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Dataparcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.
let value = ['abc', 'def', 'ghi'];
let parcel = new Parcel({value});
parcel.get(0).value; // returns 'abc'
parcel.get(-1).value; // returns 'ghi'
parcel.get('#a').value; // returns 'abc'

getIn()

getIn(keyPath: Array<string|number>): Parcel // only on ParentParcels
getIn(keyPath: Array<string|number>, notSetValue: any): Parcel // only on ParentParcels

Returns a Parcel containing the value associated with the provided key path. If the key path doesn't exist, a Parcel with a value of notSetValue will be returned. If notSetValue is not provided then a Parcel with a value of undefined will be returned.

let value = {
    a: {
        b: 123
    }
};
let parcel = new Parcel({value});
parcel.getIn(['a','b']).value; // returns 123
parcel.getIn(['a','z']).value; // returns undefined
parcel.getIn(['a','z'], 789).value; // returns 789

When called on a Parcel with an indexed value, such as an array, this method can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Dataparcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.

children()

children(mapper?: ParcelMapper): ParentType<Parcel> // only on ParentParcels

type ParcelMapper = (
    item: Parcel,
    property: string|number,
    parent: Parcel
) => any;

Returns all of the Parcel's children as new ChildParcels, contained within the original Parcel's data structure.

An optional mapper function can be passed, which will be called on each child.

let value = {
    abc: 123,
    def: 456
};

let parcel = new Parcel({value});
parcel.children();

// returns {
//    abc: Parcel, // contains a value of 123
//    def: Parcel // contains a value of 456
// }

toArray()

toArray(mapper?: ParcelMapper): Array<Parcel> // only on ParentParcels

type ParcelMapper = (
    item: Parcel,
    property: string|number,
    parent: Parcel
) => any;

Like children(), expect the returned data structure is cast to an array.

An optional mapper function can be passed, which will be called on each child.

let value = {
    abc: 123,
    def: 456
};

let parcel = new Parcel({value});
parcel.toArray();

// returns [
//    Parcel, // contains a value of 123
//    Parcel // contains a value of 456
// ]

metaAsParcel()

metaAsParcel(key: string): Parcel

Typically Parcel meta is accessed and set via the .meta property and the .setMeta() method. The metaAsParcel() function is an alternative that creates a Parcel that controls a piece of meta data. It's useful for binding meta changes to inputs.

let parcel = new Parcel({value: 123});

<input {...parcel.metaAsValue('foo').spreadDOM()} />

// if 'bar' is entered in the input, parcel.meta.foo will equal 'bar'

Input binding methods

spread()

spread(): {value: *, onChange: OnChangeFunction}
spread(notFoundValue: any): {value: *, onChange: OnChangeFunction}

type OnChangeFunction = (value: any) => void;

This is designed to bind a Parcel with an input component that expects a value and an onChange callback. The onChange callback is expected to pass an updated value as its first argument.

If notFoundValue is provided, and the Parcel's value is undefined or has been marked for deletion, the returned value will be equal to notFoundValue.

let parcel = new Parcel({
    value: 123
});

<MyInputComponent {...parcel.spread()} />

// ^ this is equivalent to
// <MyInputComponent value={parcel.value} onChange={parcel.onChange} />

spreadDOM()

spreadDOM(): {value: *, onChange: OnChangeDOMFunction}
spreadDOM(notFoundValue: any): {value: *, onChange: OnChangeDOMFunction}

type OnChangeDOMFunction = (event: HTMLEvent) => void;

This is designed to bind a Parcel with an HTML input. It returns an object with the Parcel's value and its onChangeDOM function.

If notFoundValue is provided, and the Parcel's value is undefined or has been marked for deletion, the returned value will be equal to notFoundValue.

let parcel = new Parcel({
    value: 123
});

<input {...parcel.spreadDOM()} />

// ^ this is equivalent to
// <input value={parcel.value} onChange={parcel.onChangeDOM} />

spreadDOMCheckbox()

spreadDOMCheckbox(): {value: *, onChange: OnChangeDOMCheckboxFunction}
spreadDOMCheckbox(notFoundValue: boolean): {value: *, onChange: OnChangeDOMCheckboxFunction}

type OnChangeDOMCheckboxFunction = (event: HTMLEvent) => void;

This is designed to bind a Parcel with an HTML checkbox. It returns an object with checked and onChange, where checked is the Parcel's value cast to a boolean, and onChange is the Parcel's onChangeDOMCheckbox function.

If notFoundValue is provided, and the Parcel's value is undefined or has been marked for deletion, the returned value will be equal to notFoundValue cast to a boolean.

let parcel = new Parcel({
    value: 123
});

<input type="checkbox" {...parcel.spreadDOMCheckbox()} />

onChange()

onChange(value: any): void

This is designed for use with input components that call onChange with a new value. It triggers a change that replaces the current value in the Parcel with the value provided.

If called on a ParentParcel, any child information that the Parcel had is removed, such as child keys or meta.

It is equivalent to calling set() with no key.

let parcel = new Parcel({
    value: 123
});

<MyInputComponent
    value={parcel.value}
    onChange={parcel.onChange}
/>
See also:
- spread for convenient spreading of value and onChange onto an input
- onChangeDOM for use with HTML inputs
- onChangeDOMCheckbox for use with HTML checkboxes

onChangeDOM()

onChangeDOM(event: HTMLEvent): void

This is designed for use with HTML inputs. It triggers a change that replaces the current value in the Parcel with the value of the event provided.

If called on a ParentParcel, any child information that the Parcel had is removed, such as child keys or meta.

let parcel = new Parcel({
    value: 123
});

<input
    value={parcel.value}
    onChangeDOM={parcel.onChangeDOM}
/>
See also:
- spreadDOM for convenient spreading of value and onChangeDOM onto an input
- onChange for use with input components that call `onChange` with a new value.
- onChangeDOMCheckbox for use with HTML checkboxes

onChangeDOMCheckbox()

onChangeDOMCheckbox(event: HTMLEvent): void

This is designed for use with HTML checkboxes. It triggers a change that replaces the current value in the Parcel with the checked state of the checkbox.

let parcel = new Parcel({
    value: false
});

<input
    type="checkbox"
    value={parcel.value}
    onChangeDOMCheckbox={parcel.onChangeDOMCheckbox}
/>
See also:
- spreadDOMCheckbox for convenient spreading of value and onChange onto an input
- onChangeDOM for use with input components that call `onChange` with a new value.

Child methods

isFirst()

isFirst(): boolean

Returns true if this parcel is the first of its siblings.

isLast()

isLast(): boolean

Returns true if this parcel is the last of its siblings.

Change methods

set()

set(value: any): void

Calling set() will trigger a change that replaces the current value in the Parcel with the value provided. This is equivalent to calling onChange().

let parcel = new Parcel({
    value: 123
});
parcel.set(456);
// this triggers a change that sets the parcel's value to 456

When called on a Parcel with an indexed value, such as an array, this method can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Dataparcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.

delete()

delete(): void // only on ChildParcels

Calling delete() will trigger a change that will delete the current Parcel off of its parent. This variation of the delete() method only exists on ChildParcels.

let value = {
    abc: 123,
    def: 456
};
let parcel = new Parcel({value});
parcel.get('abc').delete();
// this triggers a change that sets the parcel's value to {def: 456}

When called on a Parcel with an indexed value, such as an array, this method can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Dataparcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.

update()

// updates value - only to be used if shape doesn't change
update((value: any) => any): Parcel

// updates shape / meta
update(asNode((node: ParcelNode) => ParcelNode)): Parcel
update(asChildNodes((nodes: any) => any): Parcel

Calling update() will trigger a change that replaces the current value in the Parcel with the result of the value updater provided to it. The value updater is passed the current value of the Parcel, from which you can return the intended replacement value.

let parcel = new Parcel({
    value: 123
});
parcel.update(value => value + 1);
// this triggers a change that sets the parcel's value to 124

Please be careful

This method is safe to use in most simple cases, but in some cases it should not be used:

  • If the updater gives you a primitive value or childless value, you can return anything.
  • If the updater gives you a value that has children, you can always return a primitive value or childless value.
  • If the updater gives you a value that has children, you can return a value with children only if the shape hasn't changed.

To find out why, and what to do about it, please read about parcel updaters.

map()

// updates value - only to be used if shape doesn't change
map((value: any) => any): Parcel

// updates shape / meta
update(asNode((node: ParcelNode) => ParcelNode)): Parcel
update(asChildNodes((nodes: any) => any): Parcel

The map() function will trigger a change that will update every child value. It is passed each child value in order, from which you can return each intended replacement value.

let value = {
    a: 1,
    b: 2,
    c: 3
};
let parcel = new Parcel({value});
parcel.map(value => value * 2);
// this triggers a change that sets the parcel's value to {a: 2, b: 4, c: 6}

Please be careful

This method is safe to use in most simple cases, but in some cases it should not be used:

  • If the updater gives you a primitive value or childless value, you can return anything.
  • If the updater gives you a value that has children, you can always return a primitive value or childless value.
  • If the updater gives you a value that has children, you can return a value with children only if the shape hasn't changed.

To find out why, and what to do about it, please read about parcel updaters.

setMeta()

setMeta(partialMeta: Object): void

Triggers a change that sets meta at the current parcel's location. Values on the partialMeta object are merged shallowly onto any existing meta. Read Parcel meta for more info.

let parcel = new Parcel({
    value: "abc"
});

parcel.setMeta({
    abc: 123
});
// ^ this triggers a change that sets the parcel's meta to {abc: 123}

parcel.setMeta({
    def: 456
});
// ^ this triggers a change that sets the parcel's meta to {abc: 123, def: 456}

dispatch()

dispatch(dispatchable: Action|Action[]|ChangeRequest): void

The dispatch() method is used by Parcels internally to pass a ChangeRequest upward to the next Parcel in the chain.

Indexed and element change methods

insertAfter()

insertAfter(value: any): void // only on ElementParcels, will insert after self

This inserts value after the current Parcel, within the current ParentParcel.

let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.get(1).insertAfter('!');
// this triggers a change that sets the parcel's value to ['a','b','!','c'];

When called on a Parcel with an indexed value, such as an array, this method can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Dataparcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.

insertBefore()

insertBefore(value: any): void // only on ElementParcels, will insert before self

This inserts value before the current Parcel, within the current ParentParcel.

let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.get(1).insertBefore('!');
// this triggers a change that sets the parcel's value to ['a','!','b','c'];

When called on a Parcel with an indexed value, such as an array, this method can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Dataparcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.

push()

push(...values: Array<*>): void // only on IndexedParcels

This triggers a change that pushes all provided values to the end of the current ParentParcel.

let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.push('d','e');
// this triggers a change that sets the parcel's value to ['a','b','c','d','e'];

pop()

pop(): void // only on IndexedParcels

This triggers a change that pops the last value off of the end of the current ParentParcel.

let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.pop();
// this triggers a change that sets the parcel's value to ['a','b'];

shift()

shift(): void // only on IndexedParcels

This triggers a change that pops the first value off of the start of the current ParentParcel.

let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.shift();
// this triggers a change that sets the parcel's value to ['b','c'];

swap()

swap(keyA: string|number, keyB: string|number): void // only on IndexedParcels, will swap children

This swaps the child Parcel at keyA with the one at the position of keyB.

let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.swap(0, 1);
// this also triggers a change that sets the parcel's value to ['b','a','c'];

When called on a Parcel with an indexed value, such as an array, this method can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Dataparcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.

swapNext()

swapNext(): void // only on ElementParcels, will swap with next sibling

This swaps the current Parcel with its next sibling Parcel, within the current ParentParcel. If called on the last child, it swaps with the first child.

let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.get(0).swapNext();
// this triggers a change that sets the parcel's value to ['b','a','c'];

When called on a Parcel with an indexed value, such as an array, this method can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Dataparcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.

swapPrev()

swapPrev(): void // only on ElementParcels, will swap with previous sibling

This swaps the current Parcel with its previous sibling Parcel, within the current ParentParcel. If called on the first child, it swaps with the last child.

let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.get(1).swapPrev();
// this triggers a change that sets the parcel's value to ['b','a','c'];

When called on a Parcel with an indexed value, such as an array, this method can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Dataparcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.

unshift()

unshift(...values: Array<*>): void // only on IndexedParcels

This triggers a change that unshifts all provided values to the start of the current ParentParcel.

let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.unshift('d','e');
// this triggers a change that sets the parcel's value to ['d','e','a','b','c'];

Parent methods

has()

has(key: string|number): boolean // only on ParentParcels

Returns true if the Parcel has a child at the provided key or index, or false otherwise.

size()

size(): number // only on ParentParcels

Returns the number of children this Parcel has.

Type methods

isChild()

isChild(): boolean

Returns true if the parcel is a child parcel. Read Parcel types for more info.

When a parcel is a child parcel, it allows the use of child methods.

isElement()

isElement(): boolean

Returns true if the parcel is an element parcel. Read Parcel types for more info.

When a parcel is an element parcel, it allows the use of element methods.

isIndexed()

isIndexed(): boolean

Returns true if the parcel is an indexed parcel. Read Parcel types for more info.

When a parcel is an indexed parcel, it allows the use of indexed methods.

isParent()

isParent(): boolean

Returns true if the parcel is a parent parcel. Read Parcel types for more info.

When a parcel is a parent parcel, it allows the use of branch methods and parent methods.

isTopLevel()

isTopLevel(): boolean

Returns true if the parcel is a top level parcel. Read Parcel types for more info.

Modify methods

modifyDown()

// updates value - only to be used if shape doesn't change
modifyDown((value: any) => any): Parcel

// updates shape / meta
update(asNode((node: ParcelNode) => ParcelNode)): Parcel
update(asChildNodes((nodes: any) => any): Parcel

modifyDown lets you modify a Parcel's value so that subsequent Parcels in the chain receive and make changes against the modified value. See Modifying data to fit the UI for examples.

let parcel = new Parcel({
    value: "abc"
});

parcel
    .modifyDown(value => value.toUpperCase());
    .value // "ABC" (top level Parcel is still "abc")

It does not trigger any changes of its own, it just modifies the values that pass through it from above. Changes fired from beneath .modifyDown() are passed through unchanged.

Please be careful

This method is safe to use in most simple cases, but in some cases it should not be used:

  • If the updater gives you a primitive value or childless value, you can return anything.
  • If the updater gives you a value that has children, you can always return a primitive value or childless value.
  • If the updater gives you a value that has children, you can return a value with children only if the shape hasn't changed.

To find out why, and what to do about it, please read about parcel updaters.

modifyUp()

// updates value - only to be used if shape doesn't change
modifyUp((value: any, changeRequest: ChangeRequest) => any): Parcel

// updates shape / meta
update(asNode((node: ParcelNode, changeRequest: ChangeRequest) => ParcelNode)): Parcel
update(asChildNodes((nodes: any, changeRequest: ChangeRequest) => any): Parcel

modifyUp() lets you modify a Parcel's new value when a change is being propagated upward.

let parcel = new Parcel({
    value: "abc"
});

parcel
    .modifyUp(value => value.toUpperCase());
    .set("def");

// this triggers a change to set the value to "def"
// which propagates upward through .modifyUp()
// .modifyUp() turns "def" into "DEF"
// and the change request continues up to the original Parcel

// The Parcel then has a new value of "DEF"

It does not trigger any changes of its own, but awaits a change from below. Values from above are passed through unchanged. See Modifying data to fit the UI for examples.

Cancelling a change

You can also cancel a change by returning cancel from modifyUp()'s updater. This allows you to programatically prevent certain changes from being applied to the data in the top level Parcel. This example shows an input that cancels any changes that would set the value to null:

import cancel from 'dataparcels/cancel';
// or
import cancel from 'react-dataparcels/cancel';

let parcel = new Parcel({
    value: 123
})

parcel = parcel.modifyUp(value => value === null ? cancel : value);

parcel.set(456); // this would work, value becomes 123
parcel.set(null); // this would cause no change

Please be careful

This method is safe to use in most simple cases, but in some cases it should not be used:

  • If the updater gives you a primitive value or childless value, you can return anything.
  • If the updater gives you a value that has children, you can always return a primitive value or childless value.
  • If the updater gives you a value that has children, you can return a value with children only if the shape hasn't changed.

To find out why, and what to do about it, please read about parcel updaters.

initialMeta()

initialMeta(initialMeta: Object): Parcel

Parcel meta defaults to an empty object. The initialMeta method replaces the meta for all descendant Parcels.

Once a descendant Parcel triggers a change, the initial meta is also propagated up to the top level Parcel.

let parcel = new Parcel({
    value: "abc"
});

parcel
    .initialMeta({
        abc: 123
    })
    .meta // this returns {abc: 123} initially, but this can change after subsequent calls to setMeta()

Side-effect methods

spy()

spy(sideEffect: SideEffectFunction): Parcel

type SideEffectFunction = (parcel: Parcel) => void;

When the spy method is called on a parcel, it immediately calls the sideEffect function, passing itself as the first parameter. The return value of sideEffect is ignored. It returns the original parcel, so it can be chained. This is useful for debugging.

let value = {
    abc: 123
};
let parcel = new Parcel({value});
parcel
    .spy(parcel => console.log(parcel.data)) // 1. logs the parcel to the console ({abc: 123})
    .get('abc')
    .spy(parcel => console.log(parcel.data)) // 2. logs the parcel to the console (123)
    .value; // 3. returns 123

spyChange()

spyChange(sideEffect: SideEffectFunction): Parcel

type SideEffectFunction = (changeRequest: ChangeRequest) => void;

Once the spyChange method is called on a parcel, it will call the sideEffect function each time a change is requested from beneath, passing the associated ChangeRequest as the first parameter. The return value of sideEffect is ignored. It returns a clone of the original parcel, so it can be chained. This is useful for debugging.

let value = {
    abc: 123
};
let parcel = new Parcel({value});
parcel
    .spyChange(changeRequest => console.log(changeRequest.nextData)) // 3. logs the change request to the console (containing {abc: 456})
    .get('abc')
    .spyChange(changeRequest => console.log(changeRequest.nextData)) // 2. logs the change request to the console (containing 456)
    .onChange(456); // 1. a change is made

Composition methods

pipe()

pipe(...updaters: Function[]): Parcel

The pipe method allows for a parcel to be passed through one or more parcel modifying functions, while retaining the ability to chain. It allows for easier function composition.

let valueToString = (parcel) => parcel.modifyDown(value => `${value}`);
let changeToNumber = (parcel) => parcel.modifyUp(value => Number(value));

let parcel = new Parcel({value: 123});
parcel
    .pipe(
        valueToString,
        changeToNumber
    )
    .value // returns "123"

The above is equivalent to:

let parcel = new Parcel({value: 123});
parcel
    .modifyDown(value => `${value}`)
    .modifyUp(value => Number(value))
    .value // returns "123"