Comparing version 0.2.14 to 0.2.16
@@ -0,1 +1,4 @@ | ||
# 0.2.15 | ||
- emitter performance fixes | ||
- initial jsdocs | ||
# 0.2.12 | ||
@@ -2,0 +5,0 @@ - event name caching |
{ | ||
"name": "nflow", | ||
"version": "0.2.14" | ||
"version": "0.2.16" | ||
} |
{ | ||
"name": "nflow", | ||
"version": "0.2.14", | ||
"version": "0.2.16", | ||
"description": "event/data/control flow", | ||
"main": "dist/nflow.js", | ||
"scripts": { | ||
"start": "webpack-dev-server", | ||
"build": "webpack --watch", | ||
"start": "webpack-dev-server --config webpack.config.test.js", | ||
"docs": "jsdoc -c src-docs/config.json", | ||
"build:test": "webpack --config webpack.config.test.js", | ||
"build:dist": "webpack --config webpack.config.dist.js", | ||
"eslint": "eslint 'src/**/*.js' 'test/**/*.js'", | ||
"pretest": "webpack", | ||
"test": "eslint && mocha ./dist/node-test.js --reporter mocha-circleci-reporter" | ||
"pretest": "npm run eslint && npm run build:test", | ||
"test": "mocha ./docs/dist/node-test.js --reporter mocha-circleci-reporter", | ||
"posttest": "npm run build:dist" | ||
}, | ||
@@ -29,3 +32,6 @@ "keywords": [ | ||
"babel-preset-stage-0": "^6.3.13", | ||
"benchmark": "^2.1.3", | ||
"chai": "^3.5.0", | ||
"child-process": "^1.0.2", | ||
"d3": "^3.5.17", | ||
"es6-promise": "^4.0.5", | ||
@@ -36,7 +42,13 @@ "eslint": "^3.9.0", | ||
"eslint-plugin-standard": "^2.0.1", | ||
"jaguarjs-jsdoc": "^1.0.1", | ||
"jsdoc": "^3.4.1", | ||
"file-loader": "^0.10.0", | ||
"glob": "^7.1.1", | ||
"jsdoc": "^3.4.3", | ||
"jsdoc-webpack-plugin": "0.0.1", | ||
"lodash": "^4.17.4", | ||
"lodash.clonedeep": "^4.5.0", | ||
"matcha": "^0.7.0", | ||
"minami": "^1.1.1", | ||
"mocha-circleci-reporter": "^0.0.2", | ||
"mocha-loader": "^1.0.0", | ||
"nflow-vis": "^0.1.11", | ||
"node-circleci-autorelease": "^2.2.4", | ||
@@ -43,0 +55,0 @@ "webpack": "^1.12.8", |
@@ -23,3 +23,2 @@ | ||
### How to develop | ||
@@ -30,7 +29,12 @@ | ||
Clone this repo and run: | ||
- `npm start` | ||
- navigate to `http://localhost:5000/webpack-dev-server/` to run the tests in the browser | ||
- `npm start` | ||
- Unit tests are served on: http://localhost:5000/test/ | ||
(Hot loader: http://localhost:5000/webpack-dev-server/test) | ||
- Docs are served on: http://localhost:5000/docs/ | ||
(Hot Loader: http://localhost:5000/webpack-dev-server/docs) | ||
To run the unit tests, clone this repo and run: | ||
### Unit Tests | ||
To run all unit tests, clone this repo and run: | ||
- `npm install` | ||
- `npm test` |
{ | ||
"tags": { | ||
"allowUnknownTags": true, | ||
"dictionaries": ["jsdoc","closure"] | ||
"dictionaries": ["jsdoc"] | ||
}, | ||
"source": { | ||
"include": "src", | ||
"includePattern": ".+\\.js(doc|x)?$", | ||
"include": ["src", "src/README.md"], | ||
"includePattern": ".+\\.(js|jsx)?$", | ||
"excludePattern": "(^|\\/|\\\\)_" | ||
@@ -13,3 +13,4 @@ }, | ||
"plugins/markdown", | ||
"./src-docs/plugins/codepen" | ||
"./src-docs/plugin/codepen", | ||
"./src-docs/plugin/live-example" | ||
], | ||
@@ -19,17 +20,18 @@ "markdown": { | ||
"hardwrap": true, | ||
"tags": ["examples"] | ||
"excludeTags": ["liveexample"] | ||
}, | ||
"templates": { | ||
"applicationName": "nflow API", | ||
"cleverLinks": true, | ||
"monospaceLinks": true, | ||
"useLongnameInNav": true, | ||
"applicationName": "nflow", | ||
"disqus": "", | ||
"googleAnalytics": "", | ||
"openGraph": { | ||
"title": "nflow API", | ||
"title": "nflow Documentation", | ||
"type": "website", | ||
"image": "logo.svg", | ||
"site_name": "nflow API Docs", | ||
"url": "", | ||
"default": { | ||
"outputSourceFiles" : true | ||
} | ||
"url": "" | ||
}, | ||
@@ -41,12 +43,32 @@ "meta": { | ||
}, | ||
"linenums": true | ||
"linenums": true, | ||
"default":{ | ||
"outputSourceFiles" : true, | ||
"cssFiles":{ | ||
"include":["foo.css"], | ||
"exclude":["styles/prettify-tomorrow.css"] | ||
}, | ||
"jsFiles":{ | ||
"include":["foo.js"], | ||
"exclude":["scripts/prettify/lang-css.js"] | ||
}, | ||
"templateFiles":{ | ||
"examples":"" | ||
}, | ||
"staticFiles":{ | ||
"include": [ | ||
"./src-docs/assets", | ||
"./node_modules/nflow-vis/dist" | ||
] | ||
} | ||
} | ||
}, | ||
"opts": { | ||
"_template": "templates/default", | ||
"template": "./node_modules/jaguarjs-jsdoc", | ||
"template": "./node_modules/minami", | ||
"encoding": "utf8", | ||
"destination": "./docs/", | ||
"private": true, | ||
"recurse": true, | ||
"_tutorials": "./src-docs" | ||
"tutorials": "./src-docs/tutorials" | ||
} | ||
} |
@@ -10,15 +10,22 @@ import { ERRORS | ||
/** | ||
* Cancel the current {@link flow} node. | ||
* | ||
* Cancels the current {@link flow} node.<br><br> | ||
* Cancelling has the following effects: | ||
* - Cancelled nodes cannot receive events. | ||
* - Cancelled nodes cannot emit events. | ||
* - Cancelled nodes cannot propagate events. | ||
* - <b>All</b> child nodes of a cancelled node <b>are also cancelled recursively</b>.<br> | ||
* | ||
* Cancelling has the following effects:<br> | ||
* - <b>All</b> child nodes of a cancelled node are also cancelled <b>recursively</b>.<br> | ||
* - Cancelled nodes cannot receive events.<br> | ||
* - Cancelled nodes cannot emit events.<br> | ||
* - Cancelled nodes cannot propagate events.<br> | ||
* Cancellation is final, cancelled nodes cannot be un-cancelled. | ||
* @method | ||
* @memberof module:flow | ||
* @return {flow} flow - the current {@link flow} node | ||
* @liveexample | ||
* let foo = nflow.create('foo') | ||
* .on('hello', cb) | ||
* | ||
* test | ||
* @codepen GjRaYQ | ||
* @return {flow} flow - the current flow node | ||
* | ||
* foo.cancel() | ||
* @emits 'flow.cancel' | ||
* @emits 'flow.children.cancel' | ||
* @emits 'flow.parent.cancel' | ||
*/ | ||
@@ -30,2 +37,27 @@ flow.cancel = (...args) => { | ||
flow.cancel.value = true | ||
/** | ||
* | ||
* Dispatched when a node has been cancelled. | ||
* @event 'flow.cancel' | ||
* @property {flow} flow - the node to be cancelled. | ||
* @see flow.cancel | ||
* @example | ||
* nflow.create('timer-service') | ||
* .on('flow.cancel', stopTimer) | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's parents has been cancelled. | ||
* @event 'flow.parent.cancel' | ||
* @property {flow} flow - the node to be cancelled. | ||
* @see flow.cancel | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's children(recursive) has been cancelled. | ||
* @internal | ||
* @event 'flow.children.cancel' | ||
* @property {flow} flow - the node to be cancelled. | ||
* @see flow.cancel | ||
*/ | ||
dispatchInternalEvent(flow, 'cancel', true, previousValue) | ||
@@ -36,2 +68,7 @@ return flow | ||
/** | ||
* @memberof module:flow | ||
* @readonly | ||
* @return {Boolean} `true` if the node or any of the node's parents have been cancelled, else `false` | ||
*/ | ||
flow.isCancelled = () => { | ||
@@ -44,2 +81,29 @@ return [flow] | ||
/** | ||
* Stop or augments propagation of the emitted event. | ||
* | ||
* If the method is called with no parameters, the event will not be delivered to other listeners. | ||
* ``` | ||
* .on('price-update', function(){ | ||
* this.stopPropagation() // no further listeners will receive the event | ||
* }) | ||
* ``` | ||
* If a {@link flow.DIRECTION|direction} is given, the event propagation gets restricted in the given direction. | ||
* ``` | ||
* foo.on('price-update', function(){ | ||
* // child nodes of `foo` will not receive the `price-update` event | ||
* this.stopPropagation('DOWNSTREAM') | ||
* }) | ||
* ``` | ||
* @tutorial propagation | ||
* @see flow.propagationStopped | ||
* @param {DIRECTION} [direction] Optional direction for augmenting the event propagation | ||
* @return {flow} flow - the current {@link flow} node | ||
* @emits 'flow.propagationStopped' | ||
* @emits 'flow.children.propagationStopped' | ||
* @emits 'flow.parent.propagationStopped' | ||
* @emits 'flow.propagationAugmented' | ||
* @emits 'flow.children.propagationAugmented' | ||
* @emits 'flow.parent.propagationAugmented' | ||
*/ | ||
flow.stopPropagation = (direction = UNSET) => { | ||
@@ -54,8 +118,65 @@ direction !== UNSET && assert(!DIRECTION[direction.toUpperCase()] | ||
flow.stopPropagation.modifiers[flow.target.guid] = -1 // bitmask fill | ||
/** | ||
* | ||
* Dispatched when a dispatched event's propagation has been stopped. | ||
* @event 'flow.propagationStopped' | ||
* @property {flow} flow - the node that has stopped propagating | ||
* @see flow.stopPropagation | ||
* @example | ||
* nflow.create('timer-service') | ||
* .on('flow.propagationStopped', cb) | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's parents' propagation has been stopped. | ||
* @event 'flow.parent.propagationStopped' | ||
* @property {flow} flow - the node that has stopped propagating. | ||
* @see flow.stopPropagation | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's children(recursive) has stopped propagating. | ||
* @internal | ||
* @event 'flow.children.propagationStopped' | ||
* @property {flow} flow - the node that has stopped propagating | ||
* @see flow.stopPropagation | ||
*/ | ||
dispatchInternalEvent(flow, 'propagationStopped', true) | ||
} else { | ||
let d = DIRECTION[direction.toUpperCase()] | ||
/** | ||
* | ||
* Dispatched when a dispatched event's propagation has been augmented. | ||
* @event 'flow.propagationAugmented' | ||
* @property {flow} flow - the affected node | ||
* @property {object} changes - The changes applied to the emitted event | ||
* @property {DIRECTION} changes.direction - The changes applied to the emitted event | ||
* @property {flow} changes.target - The node that augmented the event flow | ||
* @see flow.stopPropagation | ||
* | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's parents' propagation has been augmented. | ||
* @event 'flow.parent.propagationAugmented' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - The changes applied to the emitted event | ||
* @property {DIRECTION} changes.direction - The changes applied to the emitted event | ||
* @property {flow} changes.target - The node that augmented the event flow | ||
* @see flow.stopPropagation | ||
*/ | ||
/** | ||
* | ||
* Dispatched when the propagation of one of the node's children(recursive) has been augmented. | ||
* @internal | ||
* @event 'flow.children.propagationAugmented' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - The changes applied to the emitted event | ||
* @property {DIRECTION} changes.direction - The changes applied to the emitted event | ||
* @property {flow} changes.target - The node that augmented the event flow | ||
* @see flow.stopPropagation | ||
*/ | ||
dispatchInternalEvent(flow, 'propagationAugmented', { | ||
direction: d, | ||
target: flow.target.toObj('name', 'guid') | ||
target: flow.target | ||
}) | ||
@@ -71,2 +192,9 @@ flow.stopPropagation.modifiers[flow.target.guid.value] |= DIRECTION_BITMASK[d] | ||
/** | ||
* @readonly | ||
* @tutorial propagation | ||
* @see flow.stopPropagation | ||
* @return {Boolean} `true` if the propagation was completely stopped, else `false` (even if the propagation was augmented). | ||
* | ||
*/ | ||
flow.propagationStopped = () => { | ||
@@ -73,0 +201,0 @@ return flow.stopPropagation.value |
@@ -11,4 +11,13 @@ import { assert, | ||
/** | ||
* [children description] | ||
* @parameters none | ||
* Return the immediate child nodes of the current node. | ||
* **Getter only.** | ||
* | ||
* To create new child nodes, use the {@link flow.create} API. | ||
* To reparent existing nodes, use the {@link flow.parent} API. | ||
* > **Note:** | ||
* > Note: this API only returns the immediate children of the current node. | ||
* > To get all downstream nodes recursively, use the {@link flow.children.all} API. | ||
* @see flow.parent | ||
* @see flow.create | ||
* @readonly | ||
* @return {flow[]} children - Array of child nodes | ||
@@ -22,17 +31,93 @@ */ | ||
flow.children.has = (matcher, recursive) => flow.children.find(matcher, recursive) !== undefined | ||
flow.children.find = (matcher, recursive = true) => flow.children.findAll(matcher, recursive).pop() | ||
flow.children.findAll = (matcher, recursive) => { | ||
/** | ||
* Check if the given node exists. | ||
* @alias children.has | ||
* @memberof flow | ||
* @param {(String|Function|RegEx|flow)} matcher Matcher expression: | ||
* ``` | ||
* // function: | ||
* .has(node => node.data() === 5) | ||
* | ||
* // string: | ||
* .has('foo') | ||
* | ||
* // regex | ||
* .has(/$foo[a-Z]*^/) | ||
* | ||
* // flow | ||
* .has(flowInstance) | ||
* ``` | ||
* @param {Boolean} [recursive=true] recursive search or immediate children only. | ||
* @return {Boolean} `true` if the matcher finds at least one node, else `false`. | ||
*/ | ||
flow.children.has = (matcher, recursive = true) => flow.children.find(matcher, recursive) !== undefined | ||
/** | ||
* > **Aliases:** | ||
* > - `children.find` | ||
* @alias children.get | ||
* @memberof flow | ||
* @param {(String|Function|RegEx|flow)} matcher Matcher expression: | ||
* ``` | ||
* // function: | ||
* .children.get(node => node.data() === 5) | ||
* | ||
* // string: | ||
* .children.get('foo') | ||
* | ||
* // regex | ||
* .children.get(/$foo[a-Z]*^/) | ||
* | ||
* // flow | ||
* .children.get(flowInstance) | ||
* ``` | ||
* @param {Boolean} [recursive=true] recursive search or immediate children only. | ||
* @return {flow|undefined} The first child node that matches the filter criteria, else `undefined` | ||
*/ | ||
flow.children.find = (matcher, recursive = true) => flow.children.find.all(matcher, recursive).pop() | ||
/** | ||
* Find a child node based on a search criteria. | ||
* | ||
* > **Aliases:** | ||
* > - `children.find.all` | ||
* > - `children.findAll` (DEPRECATED) | ||
* @alias children.get.all | ||
* @memberof flow | ||
* @param {(String|Function|RegEx|flow)} matcher Matcher expression: | ||
* ``` | ||
* // function: | ||
* .children.get.all(node => node.data() === 5) | ||
* | ||
* // string: | ||
* .children.get.all('foo') | ||
* | ||
* // regex | ||
* .children.get.all(/$foo[a-Z]*^/) | ||
* | ||
* // flow | ||
* .children.get.all(flowInstance) | ||
* ``` | ||
* @param {Boolean} [recursive=true] recursive search or immediate children only. | ||
* @return {flow[]} All child nodes that match the filter criteria | ||
*/ | ||
flow.children.find.all = (matcher, recursive) => { | ||
let filter = createMatcher(matcher) | ||
var children = recursive | ||
? flow.children.all() | ||
: flow.children() | ||
: flow.children.value | ||
return children.filter(filter) | ||
} | ||
flow.children.findAll = flow.children.find.all | ||
flow.get = flow.children.find | ||
flow.get.all = flow.children.find.all | ||
flow.getAll = flow.children.findAll | ||
/** | ||
* return all children recursively | ||
* Return all child nodes recursively. | ||
* | ||
* @alias children.all | ||
* @memberof flow | ||
* @return {flow[]} All child nodes of the current node (recursive) | ||
*/ | ||
@@ -53,15 +138,30 @@ flow.children.all = (...args) => { | ||
} | ||
/** | ||
* Get the parent of the node | ||
* @returns {Flow} parent the current date | ||
*/ | ||
/** | ||
* Set the parent of the node | ||
* @param {Flow} parent the new parent node | ||
* @returns {Flow} flow the current flow node | ||
* @example | ||
* Get or set the the parent of the current node. | ||
* | ||
* **Reparenting:** | ||
* ``` | ||
* let a = nflow.create('a') | ||
* let b = nflow.create('b') | ||
* a.parent(b) // reparent a onto b | ||
* ``` | ||
* | ||
* **Unparenting:** | ||
* You can create a new standalone tree by setting the `parent` to `null`. | ||
* ``` | ||
* let a = nflow.create('a') | ||
* a.parent(null) // unparent `a` to form a new tree | ||
* ``` | ||
* | ||
* @param {(flow|null)} [parent] - the new parent node | ||
* @returns {flow|null} | ||
* (setter) The current flow node if a `parent` argument was given. | ||
* (getter) The parent node of the current flow node if no arguments were given. | ||
* (getter) `null` if no arguments were given and the current node has no parent. | ||
* @emits 'flow.parent' | ||
* @emits 'flow.parent.parent' | ||
* @emits 'flow.children.parent' | ||
* @emits 'flow.parented' | ||
* @emits 'flow.parent.parented' | ||
* @emits 'flow.children.parented' | ||
*/ | ||
@@ -75,4 +175,58 @@ flow.parent = (...parentArgs) => { | ||
detach(flow) | ||
/** | ||
* | ||
* Dispatched when a node is about to be reparented. | ||
* @event 'flow.parent' | ||
* @property {flow} flow - the node to be reparented. | ||
* @property {flow} newParent - the the new parent node | ||
* @property {flow} oldParent - the the old parent node | ||
* @see flow.parent | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's parents is about to be reparented. | ||
* @event 'flow.parent.parent' | ||
* @property {flow} flow - the node to be reparented. | ||
* @property {flow} newParent - the the new parent node | ||
* @property {flow} oldParent - the the old parent node | ||
* @see flow.parent | ||
*/ | ||
/** | ||
* | ||
* Dispatched when ove of the node's children(recursive) is about to be reparented. | ||
* @event 'flow.children.parent' | ||
* @property {flow} flow - the node to be reparented. | ||
* @property {flow} newParent - the the new parent node | ||
* @property {flow} oldParent - the the old parent node | ||
* @see flow.parent | ||
*/ | ||
dispatchInternalEvent(flow, 'parent', parent, previousParent) | ||
attach(parent) | ||
/** | ||
* | ||
* Dispatched when a node has been reparented. | ||
* @event 'flow.parented' | ||
* @property {flow} flow - the reparented node. | ||
* @property {flow} newParent - the the new parent node | ||
* @property {flow} oldParent - the the old parent node | ||
* @see flow.parent | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's parents has been reparented. | ||
* @event 'flow.parent.parented' | ||
* @property {flow} flow - the reparented node. | ||
* @property {flow} newParent - the the new parent node | ||
* @property {flow} oldParent - the the old parent node | ||
* @see flow.parent | ||
*/ | ||
/** | ||
* | ||
* Dispatched when ove of the node's children(recursive) has been reparented. | ||
* @event 'flow.children.parented' | ||
* @property {flow} flow - the reparented node. | ||
* @property {flow} newParent - the the new parent node | ||
* @property {flow} oldParent - the the old parent node | ||
* @see flow.parent | ||
*/ | ||
dispatchInternalEvent(flow, 'parented', parent, previousParent) | ||
@@ -82,2 +236,6 @@ return flow | ||
/** | ||
* Return an array of all parent nodes, starting from the elements parent, going upstream until a root node is found. | ||
* @returns {flow[]} All parent nodes starting from the immediate parent to the root | ||
*/ | ||
flow.parents = (...args) => { | ||
@@ -96,3 +254,25 @@ assert(args.length, ERRORS.invalidParents) | ||
flow.parents.get = | ||
/** | ||
* Find a parent node based on a search criteria. | ||
* | ||
* > **Aliases:** | ||
* > - `parents.find` | ||
* @alias parents.get | ||
* @memberof flow | ||
* @param {(String|Function|RegEx|flow)} matcher Matcher expression: | ||
* ``` | ||
* // function: | ||
* .parents.get(node => node.data() === 5) | ||
* | ||
* // string: | ||
* .parents.get('foo') | ||
* | ||
* // regex | ||
* .parents.get(/$foo[a-Z]*^/) | ||
* | ||
* // flow | ||
* .parents.get(flowInstance) | ||
* ``` | ||
* @return {flow|undefined} The nearest parent node that matches the criteria, else `undefined` | ||
*/ | ||
flow.parents.find = (matcher) => { | ||
@@ -108,2 +288,24 @@ if (matcher === null) return null | ||
} | ||
flow.parents.get = flow.parents.find | ||
/** | ||
* Check if the given parent node exists. | ||
* @alias parents.has | ||
* @memberof flow | ||
* @param {(String|Function|RegEx|flow)} matcher Matcher expression: | ||
* ``` | ||
* // function: | ||
* .parents.has(node => node.data() === 5) | ||
* | ||
* // string: | ||
* .parents.has('foo') | ||
* | ||
* // regex | ||
* .parents.has(/$foo[a-Z]*^/) | ||
* | ||
* // flow | ||
* .parents.has(flowInstance) | ||
* ``` | ||
* @return {Boolean} `true` if the matcher finds at least one node, else `false`. | ||
*/ | ||
flow.parents.has = (matcher) => ( | ||
@@ -113,2 +315,8 @@ !!flow.parents.find(matcher) | ||
/** | ||
* Return the last node in the parent chain, ie. the node that has no further parents. | ||
* @alias parents.root | ||
* @memberof flow | ||
* @return {flow|undefined} The root node if the current node has at least one parent, else `undefined` | ||
*/ | ||
flow.parents.root = (...args) => { | ||
@@ -121,2 +329,5 @@ assert(args.length, ERRORS.invalidRoot) | ||
/** | ||
* @internal | ||
*/ | ||
flow.children.detach = (child) => { | ||
@@ -123,0 +334,0 @@ flow.children.value = |
@@ -5,15 +5,40 @@ import factory from '../factory' | ||
export default (flow, defaults) => { | ||
/* jshint ignore:start */ | ||
/** | ||
* Create a new flow node. | ||
* Create a new flow instance. | ||
* > **Note**: The parent of the newly created {@link flow} node is automatically set | ||
* to the flow node it was created from. | ||
* > | ||
* > To create a new event tree that's not connected to existing nodes, simply unparent it after creation: | ||
* > ``` | ||
* > let a = nflow | ||
* > .create('new-tree') | ||
* > .parent(null) | ||
* > ``` | ||
* **Aliases** | ||
* The following command chains have identical end results: | ||
* - `.create('a', someData)` | ||
* - `.create('a').data(someData)` | ||
* - `.create().name('a').data(someData)` | ||
* | ||
* @memberof flow | ||
* @param {string} name The name of the new node | ||
* @param {...object} [data] optional data stored in the node | ||
* @returns {flow} a new flow instance | ||
* @codepen | ||
* flow.create('user', { | ||
* id: 12345, | ||
* userName: 'jsmith' | ||
* }) | ||
* @param {...object} [data] optional data stored in the new node | ||
* @returns {flow} the new flow instance | ||
* @example | ||
* let a = nflow.create('a') | ||
* let b = nflow.create('b') | ||
* | ||
* let c = a.create('c') | ||
* let d = a.create('d') | ||
* | ||
* @example <caption>second demo:</caption> | ||
* let a = nflow.create('a') | ||
* let b = nflow.create('b') | ||
* | ||
* @emits 'flow.create' | ||
* @emits 'flow.parent.create' | ||
* @emits 'flow.children.create' | ||
*/ | ||
/* jshint ignore:end */ | ||
flow.create = (name, ...data) => { | ||
@@ -24,2 +49,27 @@ var instance = factory(flow.create.defaults, name, data) | ||
inheritStats(instance) | ||
/** | ||
* | ||
* Dispatched when a node has been created. | ||
* @event 'flow.create' | ||
* @property {flow} flow - the node where the new node was created from(ie. the parent). | ||
* @property {flow} newNode - the created node. | ||
* @see flow.create | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's parents has been created. | ||
* @event 'flow.parent.create' | ||
* @property {flow} flow - the node where the new node was created from(ie. the parent). | ||
* @property {flow} newNode - the created node. | ||
* @see flow.create | ||
*/ | ||
/** | ||
* | ||
* Dispatched when ove of the node's children(recursive) has been created. | ||
* @event 'flow.children.create' | ||
* @property {flow} flow - the node where the new node was created from(ie. the parent). | ||
* @property {flow} newNode - the created node. | ||
* @see flow.create | ||
*/ | ||
dispatchInternalEvent(flow, 'create', instance) | ||
@@ -30,2 +80,23 @@ | ||
/** | ||
* Create a new flow node or return the existing one if the current node already has a child with the same name. | ||
* If multiple children have the same name, the first one will be returned. | ||
* If `...data` parameters are given, it will also set the data on the newly created or existing node. | ||
* @alias create.once | ||
* @memberof flow | ||
* @param {string} name The name of the new node | ||
* @param {...object} [data] optional data stored in the node | ||
* @returns {flow} the newly created or already existing flow instance | ||
*/ | ||
flow.create.once = (name, ...data) => { | ||
let instance = flow.get(name) | ||
if (instance) instance.data(...data) | ||
else instance = flow.create(name, ...data) | ||
return instance | ||
} | ||
/** | ||
* @internal | ||
* @type {Object} | ||
*/ | ||
flow.create.defaults = { | ||
@@ -32,0 +103,0 @@ factory: defaults.factory, |
@@ -5,6 +5,23 @@ import { assert, dispatchInternalEvent } from '../utils' | ||
export default (flow, defaults) => { | ||
/* jshint ignore:start */ | ||
/** | ||
* [dispose description] | ||
* @return {flow} flow - the current node | ||
* Dispose the current {@link flow} node. | ||
* Use this operation if the node is no longer needed. | ||
* | ||
* Disposing a node has the following effects: | ||
* - Disposed nodes cannot receive events. | ||
* - Disposed nodes cannot emit events. | ||
* - Disposed nodes cannot propagate events. | ||
* - Disposed nodes are unparented | ||
* - <b>All child nodes</b> of a disposed node <b>are also disposed recursively</b>.<br> | ||
* | ||
* Unless the application holds a reference to the node, disposed nodes are **eligible for garbage collection**. | ||
* | ||
* This operation is final, disposed nodes cannot be re-activated. | ||
* @return {flow} the disposed node | ||
* @emits 'flow.dispose' | ||
* @emits 'flow.children.dispose' | ||
* @emits 'flow.parent.dispose' | ||
*/ | ||
/* jshint ignore:end */ | ||
flow.dispose = (...args) => { | ||
@@ -16,4 +33,28 @@ assert(args.length | ||
// recursively(depth first) dispose all downstream nodes | ||
flow.children().forEach(f => f.dispose()) | ||
flow.children.value.forEach(f => f.dispose()) | ||
/** | ||
* | ||
* Dispatched when a node is about to be disposed. | ||
* @event 'flow.dispose' | ||
* @property {flow} flow - the node to be disposed. | ||
* @see flow.dispose | ||
* @example | ||
* nflow.create('timer-service') | ||
* .on('flow.dispose', stopTimer) | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's parents is about to be disposed. | ||
* @event 'flow.parent.dispose' | ||
* @property {flow} flow - the node to be disposed. | ||
* @see flow.dispose | ||
*/ | ||
/** | ||
* | ||
* Dispatched when ove of the node's children(recursive) is about to be disposed. | ||
* @internal | ||
* @event 'flow.children.dispose' | ||
* @property {flow} flow - the node to be disposed. | ||
* @see flow.dispose | ||
*/ | ||
dispatchInternalEvent(flow, 'dispose', true) | ||
@@ -20,0 +61,0 @@ flow.parent(null) |
@@ -15,2 +15,7 @@ import { ERRORS | ||
export default (flow) => { | ||
/** | ||
* return the current status of the node | ||
* @readonly | ||
* @return {STATUS} The current status of the node | ||
*/ | ||
flow.status = (...args) => { | ||
@@ -23,2 +28,7 @@ assert(args.length | ||
} | ||
/** | ||
* @internal | ||
* @param {STATUS} The new status of the node | ||
*/ | ||
flow.status.set = (status) => { | ||
@@ -32,2 +42,8 @@ if (status === flow.status.value) return | ||
/** | ||
* Set the traversal direction of the node. | ||
* The direction defines how the node traverses the event tree when it's emitted. | ||
* @param {DIRECTION} [direction] The traversal direction | ||
* @return {flow} The current flow node | ||
*/ | ||
flow.direction = (direction = UNSET) => { | ||
@@ -43,2 +59,43 @@ if (direction === UNSET) return flow.direction.value | ||
/** | ||
* Emit a node to traverse the flow tree. | ||
* | ||
* In nflow `nodes` and `events` are the same type of objects. | ||
* An event is a node that gets detached from the parent, traverses the tree (see {@tutorial propagation}) and gets delivered to all listeners (see {@tutorial namespacing}). | ||
* > The `.emit` API has **3 distinct behaviours**: | ||
* ```js | ||
* foo.emit() // turns foo into an event and emits it | ||
* foo.emit('bar') // creates bar and emits it on foo | ||
* foo.emit(anotherNode) // reparents anotherNode to foo and emits it | ||
* ``` | ||
* Essentially, the following two operations are the same: | ||
* ```js | ||
* foo.emit('bar') | ||
* foo.create('bar').emit() | ||
* ``` | ||
* | ||
* #### Listener Context | ||
* Listeners are always invoked in the context of the emitted event: | ||
* ```js | ||
* .on('price-update', function(d){ | ||
* this // refers to the emitted event | ||
* this.data() // === d | ||
* this.name() // === 'price-update' | ||
* }) | ||
* ``` | ||
* | ||
* Since **events are also flow objects**, you can dispatch further events on them! ({@tutorial event-chain}) | ||
* @param {String} [name] The name of the event | ||
* @param {...object} [data] optional data stored on the event | ||
* @returns {flow} the emitted event | ||
* @tutorial event-chain | ||
* @tutorial propagation | ||
* @tutorial namespacing | ||
* @emits 'flow.emit' | ||
* @emits 'flow.parent.emit' | ||
* @emits 'flow.children.emit' | ||
* @emits 'flow.emitted' | ||
* @emits 'flow.parent.emitted' | ||
* @emits 'flow.children.emitted' | ||
*/ | ||
flow.emit = (name = UNSET, ...args) => { | ||
@@ -50,3 +107,50 @@ return emit(name, args) | ||
createEmitAPI(flow) | ||
/** | ||
* | ||
* Dispatched when a node is about to be emitted. | ||
* @event 'flow.emit' | ||
* @property {flow} parent - The emitter, ie. the parent of the emitted node. | ||
* @property {flow} flow - the emitted node. | ||
* @see flow.parent | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's parents is about to be emitted. | ||
* @event 'flow.parent.emit' | ||
* @property {flow} parent - The emitter, ie. the parent of the emitted node. | ||
* @property {flow} flow - the emitted node. | ||
* @see flow.emit | ||
*/ | ||
/** | ||
* | ||
* Dispatched when ove of the node's children(recursive) is about to be emitted. | ||
* @event 'flow.children.emit' | ||
* @property {flow} parent - The emitter, ie. the parent of the emitted node. | ||
* @property {flow} flow - the emitted node. | ||
* @see flow.emit | ||
*/ | ||
/** | ||
* | ||
* Dispatched after a node has been emitted. | ||
* @event 'flow.emitted' | ||
* @property {flow} parent - The emitter, ie. the parent of the emitted node. | ||
* @property {flow} flow - the emitted node. | ||
* @see flow.parent | ||
*/ | ||
/** | ||
* | ||
* Dispatched after one of the node's parents has been emitted. | ||
* @event 'flow.parent.emitted' | ||
* @property {flow} parent - The emitter, ie. the parent of the emitted node. | ||
* @property {flow} flow - the emitted node. | ||
* @see flow.emit | ||
*/ | ||
/** | ||
* | ||
* Dispatched after one of the node's children(recursive) has been emitted. | ||
* @event 'flow.children.emitted' | ||
* @property {flow} parent - The emitter, ie. the parent of the emitted node. | ||
* @property {flow} flow - the emitted node. | ||
* @see flow.emit | ||
*/ | ||
function emit (name = UNSET, args, direction) { | ||
@@ -89,2 +193,5 @@ if (name === UNSET) { | ||
/** | ||
* @internal | ||
*/ | ||
flow.emit.route = (event) => { | ||
@@ -91,0 +198,0 @@ event.stopPropagation.value = false |
@@ -6,2 +6,8 @@ import { ERRORS | ||
export default (flow, defaults, name) => { | ||
/** | ||
* Return the unique ID of the node. | ||
* | ||
* @readonly | ||
* @return {String} UUID4 identifier of the current node | ||
*/ | ||
flow.guid = (...args) => { | ||
@@ -14,2 +20,20 @@ assert(args.length | ||
/** | ||
* Get/Set the name of a flow instance. | ||
* | ||
* #### Getter | ||
* ```js | ||
* .name() // returns the name of the current node | ||
* ``` | ||
* #### Setter | ||
* ```js | ||
* .name('foo') // sets the name of the current node | ||
* ``` | ||
* @param {string} name The name of the flow instance | ||
* @returns {flow} flow (Setter) the current flow instance | ||
* @returns {String} name (Getter) the name of the flow instance | ||
* @emits 'flow.name' | ||
* @emits 'flow.parent.name' | ||
* @emits 'flow.children.name' | ||
*/ | ||
flow.name = (name = UNSET) => { | ||
@@ -21,2 +45,30 @@ if (name === UNSET) return flow.name.value | ||
flow.name.value = name | ||
flow.namespace.localName.cache = null | ||
/** | ||
* | ||
* Dispatched when a node has been renamed. | ||
* @event 'flow.name' | ||
* @property {flow} flow - the renamed node. | ||
* @property {flow} newName - the new name of the node | ||
* @property {flow} oldName - the previous name of the node | ||
* @see flow.name | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one of the node's parents has been renamed. | ||
* @event 'flow.parent.name' | ||
* @property {flow} flow - the renamed node. | ||
* @property {flow} newName - the new name of the node | ||
* @property {flow} oldName - the previous name of the node | ||
* @see flow.name | ||
*/ | ||
/** | ||
* | ||
* Dispatched when ove of the node's children(recursive) has been renamed. | ||
* @event 'flow.children.name' | ||
* @property {flow} flow - the renamed node. | ||
* @property {flow} newName - the new name of the node | ||
* @property {flow} oldName - the previous name of the node | ||
* @see flow.name | ||
*/ | ||
dispatchInternalEvent(flow, 'name', name, previousName) | ||
@@ -23,0 +75,0 @@ return flow |
@@ -5,2 +5,53 @@ import { ERRORS, UNSET, STATUS, DIRECTION_BITMASK } from '../consts' | ||
export default (flow) => { | ||
/** | ||
* Register one or more listeners on the current node. | ||
* | ||
* The listener will be invoked if an emitted flow object's name matches the listener name. | ||
* | ||
* #### How to delete event listeners | ||
* Simply set the handler to `null`: | ||
* ```js | ||
* f.on('price-update', null) | ||
* ``` | ||
* | ||
* > **Note:** | ||
* Setting a new listener on the same event name deletes the existing listener(s): | ||
* ```js | ||
* f.on('register-user', validateName) | ||
* f.on('register-user', validateEmail) // this will delete validateName! | ||
* ``` | ||
* | ||
* #### How to register multiple event handlers on the same event | ||
* ```js | ||
* flow.on('register-user', validateName | ||
* , validateEmail | ||
* , validatePassword | ||
* , registerUser) | ||
* ``` | ||
* If you specify multiple listeners, they will called in sequential order. | ||
* | ||
* @param {String} [name] the name of the listener | ||
* @param {...function} [listeners] The callback function(s) to be invoked. | ||
* @return {flow} the current flow object | ||
* @tutorial propagation | ||
* @tutorial namespacing | ||
* @example | ||
* services | ||
* .create('user-service') | ||
* .on('login' , login) | ||
* .on('logout' , logout) | ||
* .on('register', validateName | ||
* , validateEmail | ||
* , validatePassword | ||
* , register) | ||
* @emits 'flow.listenerAdded' | ||
* @emits 'flow.children.listenerAdded' | ||
* @emits 'flow.parent.listenerAdded' | ||
* @emits 'flow.listenerChanged' | ||
* @emits 'flow.children.listenerChanged' | ||
* @emits 'flow.parent.listenerChanged' | ||
* @emits 'flow.listenerRemoved' | ||
* @emits 'flow.children.listenerRemoved' | ||
* @emits 'flow.parent.listenerRemoved' | ||
*/ | ||
flow.on = (name = UNSET, ...args) => { | ||
@@ -18,2 +69,29 @@ if (name === UNSET) return flow.on.listenerMap | ||
invalidateListenerCache(flow) | ||
/** | ||
* | ||
* Dispatched when one or more listeners have been removed from the node. | ||
* @event 'flow.listenerRemoved' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - the changes applied to the node | ||
* @property {object} changes.name - the name of the listener removed | ||
* @see flow.on | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one or more listeners have been removed from the node's parents. | ||
* @event 'flow.parent.listenerRemoved' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - the changes applied to the node | ||
* @property {object} changes.name - the name of the listener removed | ||
* @see flow.on | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one or more listeners have been removed from the node's children(recursive) | ||
* @event 'flow.children.listenerRemoved' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - the changes applied to the node | ||
* @property {object} changes.name - the name of the listener removed | ||
* @see flow.on | ||
*/ | ||
dispatchInternalEvent(flow, 'listenerRemoved', {name}) | ||
@@ -28,6 +106,60 @@ return flow | ||
invalidateListenerCache(flow) | ||
/** | ||
* | ||
* Dispatched when one or more listeners have been added to the node. | ||
* @event 'flow.listenerAdded' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - the changes applied to the node | ||
* @property {object} changes.name - the name of the listener added | ||
* @see flow.on | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one or more listeners have been added to the node's parents. | ||
* @event 'flow.parent.listenerAdded' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - the changes applied to the node | ||
* @property {object} changes.name - the name of the listener added | ||
* @see flow.on | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one or more listeners have been added to the node's children(recursive) | ||
* @event 'flow.children.listenerAdded' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - the changes applied to the node | ||
* @property {object} changes.name - the name of the listener added | ||
* @see flow.on | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one or more listeners have been changed on the node. | ||
* @event 'flow.listenerChanged' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - the changes applied to the node | ||
* @property {object} changes.name - the name of the changed listener | ||
* @see flow.on | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one or more listeners have been changed on the node's parents. | ||
* @event 'flow.parent.listenerChanged' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - the changes applied to the node | ||
* @property {object} changes.name - the name of the changed listener | ||
* @see flow.on | ||
*/ | ||
/** | ||
* | ||
* Dispatched when one or more listeners have been changed on the node's children(recursive) | ||
* @event 'flow.children.listenerChanged' | ||
* @property {flow} flow - the affected node. | ||
* @property {object} changes - the changes applied to the node | ||
* @property {object} changes.name - the name of the changed listener | ||
* @see flow.on | ||
*/ | ||
dispatchInternalEvent(flow, oldListeners | ||
? 'listenerChanged' | ||
: 'listenerAdded' | ||
, {name, handlers: args.map(d => d.name || 'function')}) | ||
, {name, handlers: args}) | ||
return flow | ||
@@ -34,0 +166,0 @@ } |
import { serialise } from '../utils' | ||
export default (flow) => { | ||
flow.toString = () => { | ||
return JSON.stringify(flow.toObj()) | ||
/** | ||
* JSON Stringified version of {@link flow.toObj} for logging and debugging. | ||
* @see flow.toObj | ||
* @memberof flow | ||
* @param {String[]} props - The {@link flow} properties to include in the serialisation. | ||
* ``` | ||
* let counter = nflow.create('counter-service') | ||
* .data({ counter: 5 }) | ||
* | ||
* counter.toObj('name', 'data') | ||
* // -> '{ "name": "timer-service", "counter": "5"}' | ||
* ``` | ||
* Available properties: | ||
* - `"name"` - see {@link flow.name} | ||
* - `"guid"` - see {@link flow.guid} | ||
* - `"version"` - see {@link flow.version} | ||
* - `"data"` - see {@link flow.data} | ||
* - `"status"` - see {@link flow.status} | ||
* - `"parent"` - see {@link flow.parent} | ||
* - `"listeners"` - see {@link flow.on} | ||
* - `"children"` - see {@link flow.children} | ||
* - `"recipients"` - see {@link flow.emit.recipients} | ||
* @return {String} Serialised and JSON Stringified representation of the current node. | ||
*/ | ||
flow.toString = (...args) => { | ||
return JSON.stringify(flow.toObj(...args)) | ||
} | ||
/** | ||
* Serialise the flow node into a json object | ||
* @param {String[]} props - The {@link flow} properties to include in the serialisation. | ||
* ``` | ||
* let counter = nflow.create('counter-service') | ||
* .data({ counter: 5 }) | ||
* | ||
* counter.toObj('name', 'data') | ||
* // -> { "name": "timer-service", "counter": "5"} | ||
* ``` | ||
* Available properties: | ||
* - `"name"` - see {@link flow.name} | ||
* - `"guid"` - see {@link flow.guid} | ||
* - `"version"` - see {@link flow.version} | ||
* - `"data"` - see {@link flow.data} | ||
* - `"status"` - see {@link flow.status} | ||
* - `"parent"` - see {@link flow.parent} | ||
* - `"listeners"` - see {@link flow.on} | ||
* - `"children"` - see {@link flow.children} | ||
* - `"recipients"` - see {@link flow.emit.recipients} | ||
* @return {Object} Serialised JSON representation of the {@link flow} node | ||
*/ | ||
flow.toObj = (...args) => { | ||
@@ -32,3 +77,3 @@ const props = args.reduce((a, b) => { a[b] = 1; return a }, {}) | ||
}) | ||
add('children', () => flow.children() | ||
add('children', () => flow.children.value | ||
.map(f => f.toObj('name', 'guid'))) | ||
@@ -35,0 +80,0 @@ |
@@ -5,2 +5,20 @@ import { assert, getLocalName } from '../utils' | ||
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) => { | ||
@@ -13,2 +31,18 @@ assert(args.length, ERRORS.invalidNamespaceArgs) | ||
/** | ||
* **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) => { | ||
@@ -23,7 +57,15 @@ assert(args.length, ERRORS.invalidNamespaceArgs) | ||
/** | ||
* The Implicit namespace part of the node is defined by its parents. | ||
* For example: | ||
* If the node's parent is `a` and `a`'s parent is `b`: | ||
* The node's implicit namespace is `a:b` | ||
* @param none | ||
* 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 | ||
@@ -39,8 +81,11 @@ */ | ||
/** | ||
* The Explicit namespace identifier is defined as part of the node's name | ||
* For example: | ||
* If the node's name is `x:y:foo` | ||
* The node's explicit namespace is `x:y` | ||
* @param none | ||
* @return {String[]} The explicit namespace identifier of the node | ||
* 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 | ||
*/ | ||
@@ -57,34 +102,51 @@ flow.namespace.explicit = (...args) => { | ||
/** | ||
* The local name segment of the full namespace | ||
* @example | ||
* let foo = flow.create('x:y:foo') | ||
* foo.namespace.localName() // -> foo' | ||
* @param none | ||
* @return {String} The local name of the node | ||
* 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) | ||
return flow | ||
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 | ||
/** | ||
* The full namespace of the node including the: | ||
* - implicit ns identifiers, | ||
* - explicit namespace identifiers | ||
* - and the local name. | ||
* @example | ||
* let foo = flow.create('a').create('b').create('x:y:foo') | ||
* foo.namespace.full() // -> ['a','b','x','y','foo'] | ||
* @param none | ||
* @return {Array} The full namespace of the node | ||
*/ | ||
* **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.implicit() | ||
.concat(flow.name().split(NS_SEPARATOR)) | ||
return flow.namespace().split(NS_SEPARATOR) | ||
} | ||
/** | ||
* @internal | ||
* Checks if the emitted event and the receiving node are in compatible namespaces. | ||
@@ -96,4 +158,4 @@ * An event can be delivered if the following checks pass: | ||
* @param {flow} listenerNode The node receiving the event | ||
* @param {[type]} listenerName the name of the event, optionally including the explicit namespace, eg.:`x:y:foo` | ||
* @return {[type]} true if the event can be delivered to the receiving node | ||
* @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 | ||
*/ | ||
@@ -107,3 +169,3 @@ flow.namespace.match = (listenerNode, listenerName) => { | ||
// 1. Check that the local names match | ||
if (!isLocalNameMatch({id: getLocalName(flow.name()), f: flow}, listenerName)) return false | ||
if (!isLocalNameMatch({id: flow.namespace.localName(), f: flow}, listenerName)) return false | ||
// 2. Check that the receiver's explicit NS matches the sender's NS | ||
@@ -116,6 +178,6 @@ if (!isNamespaceMatch(flow, flow.name(), listenerNode.namespace.localise(listenerName))) return false | ||
/** | ||
/* | ||
* Checks if the node's FULL NS matches the name's explicit identifiers | ||
* @param {[type]} node the node to get the full NS from | ||
* @param {[type]} localisedNameTo The node name to get the explicit identifiers from | ||
* @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 | ||
@@ -175,3 +237,3 @@ */ | ||
/** | ||
/* | ||
* Checks if the local name of the sender and receiver nodes are the same | ||
@@ -178,0 +240,0 @@ * @param {String} senderLocalName The name of the emitted event |
import { dispatchInternalEvent } from '../utils' | ||
export default (flow, defaults, name, data) => { | ||
/** | ||
* `Get` or `Set` the current node's data. | ||
* | ||
* Every {@link flow} node has an internal data storage for storing state. | ||
* @param {...object} [data] - the data to be stored in the node | ||
* @return {object|object[]} (Getter) the data stored in the node. | ||
* If multiple objects are stored in the node, they are returned as an array: | ||
* ```js | ||
* let foo = nflow.create('foo') | ||
* .data({a:5}, "somethingElse") | ||
* | ||
* foo.data() // [{a:5}, "somethingElse"] | ||
* ``` | ||
* @return {flow} (Setter) The current node | ||
* @emits 'flow.data' | ||
* @emits 'flow.parent.data' | ||
* @emits 'flow.children.data' | ||
*/ | ||
flow.data = (...data) => { | ||
@@ -12,2 +30,29 @@ if (!data.length) { | ||
flow.data.value = data | ||
/** | ||
* | ||
* Dispatched when the data of the node has been changed. | ||
* @event 'flow.data' | ||
* @property {flow} flow - the node that stores the data | ||
* @property {object} newData - The new data stored in the node | ||
* @property {object} oldData - The old data stored in the node | ||
* @see flow.data | ||
*/ | ||
/** | ||
* | ||
* Dispatched when the data of a parent node has changed | ||
* @event 'flow.parent.data' | ||
* @property {flow} flow - the node that stores the data | ||
* @property {object} newData - The new data stored in the node | ||
* @property {object} oldData - The old data stored in the node | ||
* @see flow.data | ||
*/ | ||
/** | ||
* | ||
* Dispatched when the data of a child(recursive) node has changed | ||
* @event 'flow.children.data' | ||
* @property {flow} flow - the node that stores the data | ||
* @property {object} newData - The new data stored in the node | ||
* @property {object} oldData - The old data stored in the node | ||
* @see flow.data | ||
*/ | ||
dispatchInternalEvent(flow, 'data' | ||
@@ -14,0 +59,0 @@ , (data.length > 1 ? data : data[0]) |
@@ -6,2 +6,14 @@ /* globals VERSION */ | ||
export default (flow, defaults) => { | ||
/** | ||
* The stats API is for configuring {@link flow} nodes for debugging. | ||
* ``` | ||
* nflow.create('noisy-timer-service') | ||
* .stats({ ignore: true }) | ||
* ``` | ||
* @param {*} data - configuration options | ||
* @param {Boolean} data.ignore - do not track this node in the debugger/visualiser | ||
* @param {Boolean} data.collapsed - collapse this node by default in the visualiser | ||
* @param {String} data.description - extra description of the node to show in the visualiser | ||
* @return {flow} the current node. | ||
*/ | ||
flow.stats = (d) => { | ||
@@ -8,0 +20,0 @@ if (d === undefined) return flow.stats.value |
export const UNSET = {} | ||
/** | ||
* Enum for the direction event propagation | ||
* @enum {String} | ||
* @readonly | ||
* @name DIRECTION | ||
* @property {String} CURRENT - the event is only propagated to the current node | ||
* @property {String} DEFAULT - the event bubbles up to root parent(the parent that has no other parents), then traverses **depth-first** to all child nodes | ||
* @property {String} UPSTREAM - The event bubbles up to the root node | ||
* @property {String} DOWNSTREAM - the event traverses **depth-first** all child nodes | ||
*/ | ||
export const DIRECTION = { | ||
@@ -18,2 +29,15 @@ CURRENT: 'CURRENT', | ||
/** | ||
* Enum for decribing the status of a flow node. | ||
* | ||
* Use {@link flow.status}() to get the status of a flow instance. | ||
* @enum | ||
* @readonly | ||
* @property {String} ACTIVE (Default) The node can emit(or be emitted), propagate or listen to events. | ||
* @property {String} FLOWING The node has been emitted(so it is now treated as an event), but it hasn't been delivered to all listeners yet. | ||
* @property {String} STOPPED The event propagation was stopped. See {@link flow.stopPropagation} | ||
* @property {String} COMPLETED The event has been propagated to all listeners. | ||
* @property {String} CANCELLED The event has been cancelled. See {@link flow.cancel} | ||
* @property {String} DISPOSED The event has been disposed. See {@link flow.dispose} | ||
*/ | ||
export const STATUS = { | ||
@@ -20,0 +44,0 @@ IDLE: 'IDLE', |
import behaviours from './behaviours' | ||
export default (defaults, name, data) => { | ||
/* jshint ignore:start */ | ||
/** | ||
* nflow library | ||
* @class flow | ||
* | ||
* @example | ||
* let a = nflow.create('a') | ||
* let b = nflow.create('b') | ||
* | ||
* let c = a.create('c') | ||
* let d = a.create('d') | ||
*/ | ||
/* jshint ignore:end */ | ||
var flow = defaults.factory() | ||
@@ -4,0 +17,0 @@ |
import factory from './factory' | ||
import {DEFAULTS} from './consts' | ||
import logger from './logger' | ||
/** | ||
* The nflow API is designed to be simple and easy to use.<br><br> | ||
* Essentially, the heart of nflow is only 3 methods :<br> | ||
* - {@link flow.create|create},<br> | ||
* - {@link flow.on} and<br> | ||
* - {@link flow.emit}.<br><br> | ||
* Since in nflow there is only one type of object, the same API applies to every object created or emitted.<br> | ||
* Whether they are used as dispatchers, events, stores or controllers - they are the same type of objects and share the same API.<br> | ||
* @namespace | ||
* @type flow | ||
* @name flow | ||
* | ||
*/ | ||
var root = factory(DEFAULTS, 'flow', []) | ||
logger.init(root) | ||
export default root |
@@ -34,3 +34,3 @@ import {isDetached, createMatcher} from '../utils' | ||
var nodes = visited ? [] : [{ flow, route }] | ||
flow.children() | ||
flow.children.value | ||
.forEach(f => { | ||
@@ -37,0 +37,0 @@ nodes = nodes |
import {DIRECTION} from '../consts' | ||
import {createMatcher} from '../utils' | ||
export default (flow) => { | ||
export default (flow, matcher) => { | ||
let match = createMatcher(matcher) | ||
let visitedNodesMap = {} | ||
@@ -19,5 +21,6 @@ let parent = flow.parent() | ||
visitedNodesMap[flow.guid()] = true | ||
if (!match(flow)) return [] | ||
route = route.concat([{flow: flow, direction: dir(flow)}]) | ||
var nodes = [{ flow, route }] | ||
flow.children() | ||
flow.children.value | ||
.forEach(f => (nodes = nodes.concat(getChildren(f, route)))) | ||
@@ -24,0 +27,0 @@ return nodes |
@@ -45,3 +45,3 @@ import factory from '../factory' | ||
/** | ||
/* | ||
* returns the Local Name fragment of a namespaced listener or node name. | ||
@@ -90,3 +90,3 @@ * @example | ||
} | ||
node.children().forEach(f => { | ||
node.children.value.forEach(f => { | ||
for (let key in f.on.listenerCache) { | ||
@@ -102,3 +102,3 @@ node.on.listenerCache[getLocalName(key)] = true | ||
var current = factory(DEFAULTS, 'flow.' + name) | ||
let current = factory(DEFAULTS, 'flow.' + name) | ||
current.name.isInternal = true | ||
@@ -118,3 +118,3 @@ current.data.value = [newData, oldData] | ||
down.data.value = [flow, newData, oldData] | ||
flow.children().forEach(f => f.emit.downstream(down)) | ||
flow.children.value.forEach(f => f.emit.downstream(down)) | ||
@@ -121,0 +121,0 @@ logger.log(flow, name, newData, oldData) |
@@ -128,3 +128,3 @@ /* globals describe, it, beforeEach */ | ||
var route = g.emit.route.DOWNSTREAM(g) | ||
var route = g.emit.route.DOWNSTREAM(g, true) | ||
var map = route.map(f => f.flow.name()) | ||
@@ -145,3 +145,3 @@ expect(map).to.eql(['x', 'x0', 'a', 'b', 'c', 'e']) | ||
var route = f.emit.route.DOWNSTREAM(f) | ||
var route = f.emit.route.DOWNSTREAM(f, true) | ||
var map = route.map(f => f.flow.name()) | ||
@@ -193,3 +193,3 @@ expect(map).to.eql(['c', 'z', 'b1']) | ||
var a = sut.create('a') | ||
var b = a.create('b').on('test', () => console.log('received')) | ||
var b = a.create('b').on('test', () => {}) | ||
var c = b.create('c') | ||
@@ -196,0 +196,0 @@ var d = c.create('d') |
@@ -13,1 +13,2 @@ import './spec/caching.js' | ||
import './spec/stats.js' | ||
import 'file?name=[name].[ext]!./tests.html?' |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 3 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
4352736
87
0
56828
39
30
6
4