A set of utilities for writing Rosmaro handlers.
import {
typeHandler,
defaultHandler,
partialReturns,
targetedActions,
callChildren,
supportEntryActions,
triggerEntryActions
} from 'rosmaro-binding-utils';
const makeHandler = handlerPlan => supportEntryActions(targetedActions()(partialReturns(typeHandler({defaultHandler})(handlerPlan))));
// ...
const model = triggerEntryActions(rosmaro({graph, bindings}));
Allows to call all the children at once.
import {callChildren} from 'rosmaro-binding-utils';
// It's transparent for the context of leaves.
const action = {type: 'DO_YOUR_JOB'};
const context = {a: 1, b: 2};
const children = {};
expect(
fn({context, action, children})
).toEqual({
context: {a: 1, b: 2},
result: undefined,
arrows: []
});
// It extends the arrow followed by a single child node.
const action = {type: 'DO_YOUR_JOB'};
const context = {a: 1, b: 2};
const children = {
A: ({action}) => ({
arrows: [[['main:A', 'x']]],
result: 'AResult',
context: {a: 2, b: 4},
})
};
expect(
callChildren({context, action, children})
).toEqual({
context: {a: 2, b: 4},
result: 'AResult',
arrows: [
[['main:A', 'x'], ['main', 'x']],
]
});
// Merges composites.
const action = {type: 'DO_YOUR_JOB'};
const context = {a: 1, b: 2};
const children = {
A: ({action}) => ({
arrows: [[['main:A', 'x']]],
result: 'AResult',
context: {a: 2, b: 2},
}),
B: ({action}) => ({
arrows: [[['main:B', 'y']]],
result: 'BResult',
context: {a: 1, b: 4},
})
};
expect(
callChildren({context, action, children})
).toEqual({
context: {a: 2, b: 4},
result: {A: 'AResult', B: 'BResult'},
arrows: [
[['main:A', 'x'], ['main', 'x']],
[['main:B', 'y'], ['main', 'y']],
]
});
import 'defaultHandler' from 'rosmaro-binding-utils';
If a node has just one child, this handler is simply transparent.
If a node has many children, let's say A
and B
, then the result looks like this:
{
data: {A: 'AResult', B: 'BResult'},
effect: [
{type: 'A_EFFECT_1'},
{type: 'A_EFFECT_2'},
{type: 'B_EFFECT_1'}
]
}
Enable entry actions for Rosmaro nodes.
Import:
import {triggerEntryActions} from 'rosmaro-binding-utils';
Wrap the model with triggerEntryActions
:
const myModel = rosmaro({bindings, graph});
const aModelThatTriggersEntryActions = triggerEntryActions(myModel);
Follow arrows and return effects from the ON_ENTRY
handler:
{
ON_ENTRY: ({context}) => ({
arrow: 'started',
effect: {type: 'START_AN_AWESOME_PROJECT'},
})
}
"Extends" the given arrows in such a away that the parent nodes also follow them.
import {extendArrows} from 'rosmaro-binding-utils';
extendArrows(
[
[['a:a:a', 'x']],
[['a:a:b', 'y']]
]
)
/*
[
[['a:a:a', 'x'], ['a:a', 'x']],
[['a:a:b', 'y'], ['a:a', 'y']]
]
*/
extendArrows(
[
[['a:a:a', 'x']],
[['a', 'y']]
]
)
/*
[
[['a:a:a', 'x'], ['a:a', 'x']],
[['a', 'y']]
]
*/
extendArrows(
[
[['a:a:a', 'x']],
[]
]
)
/*
[
[['a:a:a', 'x'], ['a:a', 'x']],
[]
]
*/
Extracts the parent node.
import {extractParent} from 'rosmaro-binding-utils';
extractParent('a') // null
extractParent('a:b:c') // 'a:b'
Allows to set an initial value for parts which are undefined.
import {initialValueLens} from 'rosmaro-binding-utils';
// Alters the context if it is undefined.
testLens({
lens: initialValueLens({a: 123}),
zoomInInput: undefined,
zoomInOutput: {a: 123},
zoomOutInput: {a: 123},
zoomOutOutput: {a: 123},
})
// Does not alter empty objects.
testLens({
lens: initialValueLens({a: 123}),
zoomInInput: {},
zoomInOutput: {},
zoomOutInput: {},
zoomOutOutput: {},
})
Merges arrows from two sources into one set of arrows.
import {mergeContexts} from 'rosmaro-binding-utils';
mergeArrows([
[
[['a:a:a', 'x'], ['a:a', 'x'], ['a', 'x']],
[['c:a:a', 'x'], ['c:a', 'x'], ['c', 'x']],
],
[
[['a:a:b', 'x'], ['a:a', 'x'], ['a', 'x']],
]
])
/*
[
[['a:a:a', 'x'], ['a:a', 'x'], ['a', 'x']],
[['c:a:a', 'x'], ['c:a', 'x'], ['c', 'x']],
[['a:a:b', 'x'], ['a:a', 'x'], ['a', 'x']],
]
*/
Applies differences between the initial context and the new contexts to the initial context.
import {mergeContexts} from 'rosmaro-binding-utils';
mergeContexts(
{a: 123, b: 456},
[
{a: 123, b: 456},
{a: 123, b: 456, c: 789},
{a: 123, b: 456},
]
)
// {a: 123, b: 456, c: 789}
mergeContexts(
{a: 123, b: 456, c: 789},
[
{a: 123, b: 456, c: 789},
{a: 123, b: 456},
{a: 123, b: 456, c: 789},
]
)
// {a: 123, b: 456}
mergeContexts(
{a: 123, b: 456, c: 789},
[
{a: 123, b: 456, c: 789},
{a: 123, b: 654, c: 789},
{a: 123, b: 456, c: 789},
]
)
// {a: 123, b: 654, c: 789}
Takes a handler which may return just some of the data and returns a handler that always returns an object like {result: {data, effect}, arrows, context}
.
import {partialReturns} from 'rosmaro-binding-utils';
// Allows to return just some result.
partialReturns(
opts => ({resultOf: opts})
)({context: {a: 123}})
/*
({
result: {
data: {resultOf: {context: {a: 123}}},
effect: undefined,
},
arrows: [],
context: {a: 123}
})
*/
// Allows to return just an arrow.
partialReturns(
opts => ({arrow: 'x'})
)({context: {a: 123}, node: {id: 'main:a:b'}})
/*
({
result: {
data: undefined,
effect: undefined,
},
arrows: [[['main:a:b', 'x']]],
context: {a: 123}
})
*/
// Allows to return just an effect.
partialReturns(
opts => ({effect: {type: 'TICK'}})
)({context: {a: 123}})
/*
({
result: {
data: undefined,
effect: {type: 'TICK'},
},
arrows: [],
context: {a: 123}
})
*/
// Allows to return just some data and an effect.
partialReturns(
opts => ({result: {theResult: opts.context}, effect: {type: 'TICK'}})
)({context: {a: 123}})
/*
({
result: {
data: {theResult: {a: 123}},
effect: {type: 'TICK'},
},
arrows: [],
context: {a: 123}
})
*/
// Allows to return an arrow, an effect, a context and some data
partialReturns(
opts => ({
arrow: 'x',
result: {theResult: opts.context},
context: {a: 567},
effect: {type: 'TICK'}
})
)({context: {a: 123}, node: {id: 'main:a:b'}})
/*
({
result: {
data: {theResult: {a: 123}},
effect: {type: 'TICK'},
},
arrows: [[['main:a:b', 'x']]],
context: {a: 567}
})
*/
// Does not touch a result meeting the format requirements.
partialReturns(
opts => ({
arrows: [[['main:a:b', 'x']]],
result: {data: {theResult: opts.context}, effect: {type: 'TICK'}},
context: {a: 567},
})
)({context: {a: 123}})
/*
({
arrows: [[['main:a:b', 'x']]],
result: {data: {theResult: {a: 123}}, effect: {type: 'TICK'}},
context: {a: 567},
})
*/
Used to slice the context.
import {sliceLens} from 'rosmaro-binding-utils';
// Allows to work with just part of an object.
testLens({
lens: sliceLens('b'),
zoomInInput: {a: 123, b: {c: 456}},
zoomInOutput: {c: 456},
zoomOutInput: {c: 987},
zoomOutOutput: {a: 123, b: {c: 987}},
})
// Returns undefined if the desired property does not exist.
testLens({
lens: sliceLens('b'),
zoomInInput: {a: 123},
zoomInOutput: undefined,
zoomOutInput: {c: 987},
zoomOutOutput: {a: 123, b: {c: 987}},
})
Allows to dispatch an action targeted at a particular node.
The targetedActions
function is used to modify a handler in two ways.
First, it makes it ignore an action if it has a target
property that doesn't start with the node.id
value passed to the handler. In other words, an action with target: 'main:a:b'
is consumed by main
, main:a
and main:a:b
handlers, but not by the main:a:c
handler.
To make building targeted actions easier, every handler is injected a toNode
function. It takes an action and returns a new one which is targeted at the handler's node.
import {targetedActions} from 'rosmaro-binding-utils';
const baseHandler = ({toNode}) => ({
//...
result: {
'action targeted at this node': toNode({type: 'SOME_TARGETED_ACTION'})
}
});
const handler = targetedActions()(baseHandler);
Does nothing to the context
import {transparentLens} from 'rosmaro-binding-utils';
testLens({
lens: transparentLens,
zoomInInput: {a: 123, b: {c: 987}},
zoomInOutput: {a: 123, b: {c: 987}},
zoomOutInput: {a: 123, b: {c: 987}},
zoomOutOutput: {a: 123, b: {c: 987}},
})
Associated handlers with action types.
import {typeHandler} from 'rosmaro-binding-utils';
// The handler which is going to be used when the
// given action is neither FIRST_ACTION nor SECOND_ACTION.
const defaultHandler = (opts) => ({UNSUPPORTED_ACTION: opts});
// Handles actions of 'FIRST_ACTION' type.
const firstActionHandler = (opts) => ({FIRST_ACTION: opts});
// Handles actions of 'SECOND_ACTION' type.
const secondActionHandler = (opts) => ({SECOND_ACTION: opts});
// The whole handler.
const handler = typeHandler({defaultHandler})({
// Actions of 'FIRST_ACTION' type are dispatched to this handler.
FIRST_ACTION: firstActionHandler,
// Actions of 'SECOND_ACTION' type are dispatched to this handler.
SECOND_ACTION: secondActionHandler,
});
handler({action: {type: 'FIRST_ACTION'}, something: 'else'})
// {FIRST_ACTION: {action: {type: 'FIRST_ACTION'}, something: 'else'}}
handler({action: {type: 'SECOND_ACTION'}, something: 'else'})
// {SECOND_ACTION: {action: {type: 'SECOND_ACTION'}, something: 'else'}}
handler({action: {type: 'THIRD_ACTION'}, something: 'else'})
// {UNSUPPORTED_ACTION: {action: {type: 'THIRD_ACTION'}, something: 'else'}}