nflow
Version:
event/data/control flow
247 lines (232 loc) • 7.96 kB
JavaScript
import { assert, getLocalName } from '../utils'
import { ERRORS, NS_SEPARATOR } from '../consts'
export default (flow, defaults, name, data) => {
/**
* **Getter only**.
* Return the full namespace of the node including:
* - implicit namespace identifiers (see {@link flow.namespace.implicit}),
* - explicit namespace identifiers (see {@link flow.namespace.explicit})
* - and the local name. (see {@link flow.namespace.localName})
*
* ```
* let foo = nflow
* .create('a')
* .create('b')
* .create('x:y:foo')
*
* foo.namespace() // -> "nflow:a:b:x:y:foo"
* ```
* @tutorial namespacing
* @return {String} The full namespace of the node
*/
flow.namespace = (...args) => {
assert(args.length, ERRORS.invalidNamespaceArgs)
return flow.namespace.path()
.map(f => f.name())
.join(NS_SEPARATOR)
}
/**
* **Getter only**.
* Return all {@link flow} nodes that form the current node's namespace
* ```js
* let a = nflow.create('a')
* let b = a.create('b')
* let foo = b.create('x:y:foo')
*
* a.namespace() // -> [nflow, a]
* foo.namespace() // -> [nflow, a, b, foo]
* ```
* @tutorial namespacing
* @alias namespace.path
* @memberof flow
* @return {flow[]} Array of nodes that make up the current node's full namespace
*/
flow.namespace.path = (...args) => {
assert(args.length, ERRORS.invalidNamespaceArgs)
return flow
.parents()
.reverse()
.concat(flow)
}
/**
* Return the implicit namespace segment of the current node.
*
* A node's implicit namespace is defined by the node's parents:
* ```js
* let a = nflow.create('a')
* let b = a.create('b')
* let foo = b.create('x:y:foo')
*
* a.namespace.implicit() // -> ["nflow"]
* foo.namespace.implicit() // -> ["nflow", "a", "b"]
* ```
* @alias namespace.implicit
* @memberof flow
* @return {String[]} The implicit namespace segment of the node
*/
flow.namespace.implicit = (...args) => {
assert(args.length, ERRORS.invalidNamespaceArgs)
return flow
.parents()
.reverse()
.map(f => f.name())
}
/**
* Return the `explicit` namespace segment of the node.
* The explicit namespace segment is given as part of the node's {@link flow.name|name}
* ```
* let foo = nflow.create(a:b:foo)
* foo.namespace.explicit() // "a:b"
* ```
* @alias namespace.explicit
* @memberof flow
* @return {String} The explicit namespace identifier of the node
*/
flow.namespace.explicit = (...args) => {
assert(args.length, ERRORS.invalidNamespaceArgs)
let ns = flow
.name()
.split(NS_SEPARATOR)
ns.pop()
return ns
}
/**
* Return the `local name` segment of the node.
* The local name is the node's name, minus any explicit namespace given as part of the node's {@link flow.name|name}.
* ```
* let foo = nflow.create(a:b:foo)
* foo.namespace.localName() // "foo"
* ```
* @see flow.namespace.explicit
* @alias namespace.localName
* @memberof flow
* @return {String} The explicit namespace identifier of the node
*/
flow.namespace.localName = (...args) => {
assert(args.length, ERRORS.invalidNamespaceArgs)
if (flow.namespace.localName.cache === null) {
flow.namespace.localName.cache = flow
.name()
.split(NS_SEPARATOR)
.pop()
}
return flow.namespace.localName.cache
}
flow.namespace.localName.cache = null
/**
* **Getter only**.
* Return the full namespace of the node including:
* - implicit namespace identifiers (see {@link flow.namespace.implicit}),
* - explicit namespace identifiers (see {@link flow.namespace.explicit})
* - and the local name. (see {@link flow.namespace.localName})
*
* ```
* let foo = nflow
* .create('a')
* .create('b')
* .create('x:y:foo')
*
* foo.namespace.full() // -> ["nflow", "a", "b", "x", "y", "foo"]
* ```
* @tutorial namespacing
* @return {String[]} The full namespace of the node
* @alias namespace.full
* @memberof flow
* */
flow.namespace.full = (...args) => {
assert(args.length, ERRORS.invalidNamespaceArgs)
return flow.namespace().split(NS_SEPARATOR)
}
/**
* @internal
* Checks if the emitted event and the receiving node are in compatible namespaces.
* An event can be delivered if the following checks pass:
* - the local names are the same
* - the full NS of the sender contains the explicit NS of the receiver
* - the full NS of the receiver contains the explicit NS of the sender
* @param {flow} listenerNode The node receiving the event
* @param {String} listenerName the name of the event, optionally including the explicit namespace, eg.:`x:y:foo`
* @return {Boolean} true if the event can be delivered to the receiving node
*/
flow.namespace.match = (listenerNode, listenerName) => {
assert(
typeof (listenerName) !== 'string'
, ERRORS.invalidListener
, listenerName)
// 1. check for exact match
if (flow.name.value === listenerName && listenerName.indexOf(NS_SEPARATOR) === -1) return true
// 2. Check for local name match
if (!isLocalNameMatch({id: flow.namespace.localName(), f: flow}, listenerName)) return false
// 3. Check that the receiver's explicit NS matches the sender's NS
if (!isNamespaceMatch(flow, flow.name(), listenerNode.namespace.localise(listenerName))) return false
// 4. Check that the sender's explicit NS matches the receiver's NS
if (!isNamespaceMatch(listenerNode, listenerName, flow.namespace.localise(flow.name()))) return false
return true
}
/*
* Checks if the node's FULL NS matches the name's explicit identifiers
* @param {flow} node the node to get the full NS from
* @param {String} localisedNameTo The node name to get the explicit identifiers from
* @return {Boolean} true if the name's explicit identifiers sit inside the node's NS
*/
function isNamespaceMatch (node, nameFrom, localisedNameTo) {
let fullIDs = nameFrom
.split(NS_SEPARATOR)
.map(id => ({ id, f: node }))
.reverse()
.concat(node
.parents()
.map(f => ({ id: f.name(), f }))
)
let explicitIDs = localisedNameTo.split(NS_SEPARATOR).reverse()
return explicitIDs.every(segment => {
let foundSegment = false
while (fullIDs.length) {
let p = fullIDs.shift()
if (isLocalNameMatch(p, segment)) {
foundSegment = true
break
}
}
return foundSegment
})
}
/**
* Resolves a listener name(ie. explicit ns + local name) to a localised one,
* replacing generic namespace identifiers with explicit guid-s
* @example
* let foo = flow
* .create('x')
* .create('y')
* .create('x:z:foo')
* foo.namespace.localise() // -> '{guid-of-x}:z:foo'
* @param {String} listenerName The name of the listener, optionally including the explicit ns
* @return {String} The localised Namespace
*/
flow.namespace.localise = (ns) => {
assert(
typeof (ns) !== 'string'
, ERRORS.invalidListener
, ns)
let segments = ns.split(NS_SEPARATOR)
if (segments.length > 1 &&
flow.parents.has(segments[0])) {
segments[0] = flow.parents.get(segments[0]).guid()
}
return segments.join(NS_SEPARATOR)
}
}
/*
* Checks if the local name of the sender and receiver nodes are the same
* @param {String} senderLocalName The name of the emitted event
* @param {String} receiverLocalName The name of the listener
* @return {Boolean} true if the local names are the same
*/
function isLocalNameMatch ({id, f}, receiverName) {
const receiverLocalName = getLocalName(receiverName)
const match = id === '*' ||
receiverLocalName === '*' ||
f.guid() === receiverLocalName ||
id === receiverLocalName
return match
}