noflo-assembly
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -10,2 +10,3 @@ class BuildBodyBase extends Component { | ||
} | ||
relay(msg, output) { | ||
@@ -12,0 +13,0 @@ msg.body = { |
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -11,2 +11,3 @@ class BuildFrame extends Component { | ||
} | ||
relay(msg, output) { | ||
@@ -13,0 +14,0 @@ msg.chassis = { |
@@ -1,5 +0,3 @@ | ||
const Component = require('../../index'); | ||
const { Component, merge, fail } = require('../../index'); | ||
const { merge, fail } = Component; | ||
class CombineAssemblies extends Component { | ||
@@ -12,2 +10,3 @@ constructor() { | ||
} | ||
handle(input, output) { | ||
@@ -14,0 +13,0 @@ if (!input.hasData('b', 'c')) { return null; } |
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -24,2 +24,3 @@ class MountBodyParts extends Component { | ||
} | ||
handle(input, output) { | ||
@@ -26,0 +27,0 @@ if (!input.hasData('in', 'partname')) { return null; } |
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -21,2 +21,3 @@ class MountEngine extends Component { | ||
} | ||
handle(input, output) { | ||
@@ -23,0 +24,0 @@ if (!input.hasData('in', 'engine')) { return null; } |
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -10,2 +10,3 @@ class MountPanels extends Component { | ||
} | ||
relay(msg, output) { | ||
@@ -12,0 +13,0 @@ msg.body.panels = 'Steel Panels'; |
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -3,0 +3,0 @@ class MountSuspension extends Component { |
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -9,2 +9,3 @@ class MountTransmission extends Component { | ||
} | ||
relay(msg, output) { | ||
@@ -11,0 +12,0 @@ msg.chassis.transmission = 'ZF Automatic 6-speed'; |
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -21,2 +21,3 @@ class MountWheels extends Component { | ||
} | ||
handle(input, output) { | ||
@@ -23,0 +24,0 @@ if (!input.hasData('in', 'count')) { return null; } |
const { IP } = require('noflo'); | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -11,2 +11,3 @@ class Order extends Component { | ||
} | ||
handle(input, output) { | ||
@@ -13,0 +14,0 @@ this.counter += 1; |
@@ -1,5 +0,3 @@ | ||
const Component = require('../../index'); | ||
const { Component, failed } = require('../../index'); | ||
const { failed } = Component; | ||
class Release extends Component { | ||
@@ -12,6 +10,7 @@ constructor() { | ||
} | ||
relay(msg, output) { | ||
// Display errors at this point | ||
if (failed(msg)) { | ||
msg.errors.forEach(e => console.error(e)); | ||
msg.errors.forEach((e) => console.error(e)); | ||
} else { | ||
@@ -18,0 +17,0 @@ if (msg.parts) { |
@@ -1,5 +0,3 @@ | ||
const Component = require('../../index'); | ||
const { Component, fork } = require('../../index'); | ||
const { fork } = Component; | ||
class SplitAssemblies extends Component { | ||
@@ -13,2 +11,3 @@ constructor() { | ||
} | ||
handle(input, output) { | ||
@@ -15,0 +14,0 @@ if (!input.hasData('in')) { return null; } |
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -44,2 +44,3 @@ class SupplyBodyParts extends Component { | ||
} | ||
handle(input, output) { | ||
@@ -46,0 +47,0 @@ if (!input.hasData('in', 'interior', 'doortype', 'doornum')) { return null; } |
@@ -1,2 +0,2 @@ | ||
const Component = require('../../index'); | ||
const { Component } = require('../../index'); | ||
@@ -11,2 +11,3 @@ class WireElectrics extends Component { | ||
} | ||
handle(input, output) { | ||
@@ -13,0 +14,0 @@ if (!input.hasData('main', 'aux')) { return null; } |
381
index.js
@@ -1,170 +0,253 @@ | ||
const NoFloComponent = require('noflo').Component; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.merge = exports.fork = exports.Component = exports.failed = exports.fail = void 0; | ||
const noflo_1 = require("noflo"); | ||
/** | ||
* @typedef {{ errors: Error[], [key: string]: any }} AssemblyMessage | ||
*/ | ||
/** | ||
* @typedef {{ [key: string]: string}} AssemblyValidators | ||
*/ | ||
/** | ||
* @typedef {{ [key: string]: (val: any) => boolean}} AssemblyValidatorFunctions | ||
*/ | ||
/** | ||
* @typedef {{ [key: string]: (key: string, val?: any) => string}} AssemblyErrorMessages | ||
*/ | ||
/** | ||
* @callback RelayFunction | ||
* @param {AssemblyMessage} msg | ||
* @param {import("noflo/lib/ProcessOutput").default} output | ||
*/ | ||
/** @type {AssemblyValidatorFunctions} */ | ||
const validators = { | ||
def: val => val !== undefined, | ||
set: val => (val !== undefined) && (val !== null), | ||
ok: val => !!val, | ||
num: val => typeof val === 'number', | ||
str: val => typeof val === 'string', | ||
obj: val => (typeof val === 'object') && (val !== null), | ||
func: val => typeof val === 'function', | ||
'>0': val => val > 0, | ||
def: (val) => val !== undefined, | ||
set: (val) => (val !== undefined) && (val !== null), | ||
ok: (val) => !!val, | ||
num: (val) => typeof val === 'number', | ||
str: (val) => typeof val === 'string', | ||
obj: (val) => (typeof val === 'object') && (val !== null), | ||
func: (val) => typeof val === 'function', | ||
'>0': (val) => val > 0, | ||
}; | ||
/** @type {AssemblyErrorMessages} */ | ||
const errorMessages = { | ||
def: key => `${key} is undefined`, | ||
set: key => `${key} is not set`, | ||
ok: key => `${key} is false or empty`, | ||
num: (key, val) => `${key} is not a number: ${val}`, | ||
str: (key, val) => `${key} is not a string: ${val}`, | ||
obj: (key, val) => `${key} is not an object: ${val}`, | ||
func: (key, val) => `${key} is not a function: ${val}`, | ||
'>0': (key, val) => `${key} is not positive: ${val}`, | ||
def: (key) => `${key} is undefined`, | ||
set: (key) => `${key} is not set`, | ||
ok: (key) => `${key} is false or empty`, | ||
num: (key, val) => `${key} is not a number: ${val}`, | ||
str: (key, val) => `${key} is not a string: ${val}`, | ||
obj: (key, val) => `${key} is not an object: ${val}`, | ||
func: (key, val) => `${key} is not a function: ${val}`, | ||
'>0': (key, val) => `${key} is not positive: ${val}`, | ||
}; | ||
/** | ||
* @param {AssemblyMessage} msg | ||
* @param {Error|Error[]} err | ||
* @returns {AssemblyMessage} | ||
*/ | ||
function fail(msg, err) { | ||
if (!Array.isArray(msg.errors)) { | ||
throw new Error('Message.errors is not an array'); | ||
} | ||
const errs = Array.isArray(err) ? err : [err]; | ||
errs.forEach(e => msg.errors.push(e)); | ||
return msg; | ||
if (!Array.isArray(msg.errors)) { | ||
throw new Error('Message.errors is not an array'); | ||
} | ||
const errs = Array.isArray(err) ? err : [err]; | ||
errs.forEach((e) => msg.errors.push(e)); | ||
return msg; | ||
} | ||
exports.fail = fail; | ||
/** | ||
* @param {AssemblyMessage} msg | ||
* @returns {boolean} | ||
*/ | ||
function failed(msg) { | ||
return msg.errors && Array.isArray(msg.errors) > 0 && msg.errors.length > 0; | ||
return msg.errors && Array.isArray(msg.errors) && msg.errors.length > 0; | ||
} | ||
// Converts shortened ports definition to standard NoFlo ports definition | ||
exports.failed = failed; | ||
/** | ||
* Converts shortened ports definition to standard NoFlo ports definition | ||
* @param {Object<key, any>} options | ||
* @param {string} direction | ||
* @returns {Object<key, any>} | ||
*/ | ||
function normalizePorts(options, direction) { | ||
const key = `${direction}Ports`; | ||
const result = options; | ||
if (key in options) { | ||
if (Array.isArray(options[key])) { | ||
// Convert array to all-typed ports | ||
const tmp = {}; | ||
options[key].forEach((name) => { | ||
tmp[name] = { datatype: 'all' }; | ||
}); | ||
result[key] = tmp; | ||
} // else is normal NoFlo ports object | ||
} else { | ||
// Default to single port | ||
const dir = direction === 'out' ? 'Outgoing' : 'Incoming'; | ||
result[key] = { | ||
[direction]: { | ||
datatype: 'object', | ||
description: `${dir} message`, | ||
required: true, | ||
}, | ||
}; | ||
} | ||
return result; | ||
const key = `${direction}Ports`; | ||
const result = options; | ||
if (key in options) { | ||
if (Array.isArray(options[key])) { | ||
// Convert array to all-typed ports | ||
/** @type {Object<key, any>} */ | ||
const tmp = {}; | ||
const portsArray = /** @type {Array<string>} */ (options[key]); | ||
portsArray.forEach((name) => { | ||
tmp[name] = { datatype: 'all' }; | ||
}); | ||
result[key] = tmp; | ||
} // else is normal NoFlo ports object | ||
} | ||
else { | ||
// Default to single port | ||
const dir = direction === 'out' ? 'Outgoing' : 'Incoming'; | ||
result[key] = { | ||
[direction]: { | ||
datatype: 'object', | ||
description: `${dir} message`, | ||
required: true, | ||
}, | ||
}; | ||
} | ||
return result; | ||
} | ||
/** | ||
* @param {AssemblyValidators|Array<string>} rules | ||
* @returns {AssemblyValidators} | ||
*/ | ||
function normalizeValidators(rules) { | ||
if (Array.isArray(rules)) { | ||
// Normalize array to hashmap | ||
const res = {}; | ||
rules.forEach((f) => { | ||
res[f] = 'ok'; | ||
}); | ||
return res; | ||
} | ||
return rules; | ||
if (Array.isArray(rules)) { | ||
// Normalize array to hashmap | ||
/** @type {AssemblyValidators} */ | ||
const res = {}; | ||
rules.forEach((f) => { | ||
res[f] = 'ok'; | ||
}); | ||
return res; | ||
} | ||
return rules; | ||
} | ||
class Component extends NoFloComponent { | ||
constructor(options = {}) { | ||
let opts = normalizePorts(options, 'in'); | ||
opts = normalizePorts(opts, 'out'); | ||
super(opts); | ||
if (options.validates) { | ||
this.validates = normalizeValidators(options.validates); | ||
/** | ||
* @typedef {Object} AssemblyComponentOptions | ||
* @property {AssemblyValidators|Array<string>} [validates] | ||
*/ | ||
class Component extends noflo_1.Component { | ||
/** | ||
* @param {import("noflo/lib/Component").ComponentOptions & AssemblyComponentOptions} [options] | ||
*/ | ||
constructor(options = {}) { | ||
let opts = normalizePorts(options, 'in'); | ||
opts = normalizePorts(opts, 'out'); | ||
super(opts); | ||
/** @type {RelayFunction|null} */ | ||
this.relay = this.relay || null; | ||
if (options.validates) { | ||
this.validates = normalizeValidators(options.validates); | ||
} | ||
if (typeof this.relay === 'function') { | ||
const func = /** @type {RelayFunction} */ (this.relay); | ||
this.process((input, output) => { | ||
if (!input.hasData('in')) { | ||
return; | ||
} | ||
const msg = input.getData('in'); | ||
if (!this.validate(msg)) { | ||
output.sendDone(msg); | ||
return; | ||
} | ||
func(msg, output); | ||
}); | ||
} | ||
if (typeof this.handle === 'function') { | ||
this.process(this.handle); | ||
} | ||
} | ||
if (typeof this.relay === 'function') { | ||
this.process((input, output) => { | ||
if (!input.hasData('in')) { return; } | ||
const msg = input.getData('in'); | ||
if (!this.validate(msg)) { | ||
output.sendDone(msg); | ||
return; | ||
/** | ||
* @param {AssemblyMessage} msg | ||
* @param {AssemblyValidators} rules | ||
* @returns {Array<Error>} | ||
*/ | ||
checkFields(msg, rules) { | ||
/** @type {Array<Error>} */ | ||
const errors = []; | ||
/** | ||
* @param {AssemblyMessage|any} obj | ||
* @param {string} objPath | ||
* @param {string[]} path | ||
* @param {string} validator | ||
*/ | ||
function checkField(obj, objPath, path, validator) { | ||
if (!obj || (path.length <= 0)) { | ||
return; | ||
} | ||
const key = /** @type {string} */ (path.shift()); | ||
const v = path.length === 0 ? validator : 'obj'; | ||
if (!validators[v](obj[key])) { | ||
errors.push(new Error(errorMessages[v](`${objPath}.${key}`, obj[key]))); | ||
return; | ||
} | ||
if (path.length > 0) { | ||
checkField(obj[key], `${objPath}.${key}`, path, validator); | ||
} | ||
} | ||
this.relay(msg, output); | ||
}); | ||
Object.keys(rules).forEach((f) => { | ||
const path = f.indexOf('.') > 0 ? f.split('.') : [f]; | ||
let v = rules[f]; | ||
if (!(v in validators)) { | ||
v = 'ok'; | ||
} | ||
checkField(msg, 'msg', path, v); | ||
}); | ||
return errors; | ||
} | ||
if (typeof this.handle === 'function') { | ||
this.process(this.handle); | ||
/** | ||
* @param {AssemblyMessage} msg | ||
* @param {AssemblyValidators|Array<string>} [rules] | ||
*/ | ||
validate(msg, rules = this.validates) { | ||
if (failed(msg)) { | ||
return false; | ||
} | ||
if (rules && typeof rules === 'object') { | ||
rules = normalizeValidators(rules); | ||
const errs = this.checkFields(msg, rules); | ||
if (errs.length > 0) { | ||
fail(msg, errs); | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
} | ||
checkFields(msg, rules) { | ||
const errors = []; | ||
function checkField(obj, objPath, path, validator) { | ||
if (!obj || (path.length <= 0)) { return; } | ||
const key = path.shift(); | ||
const v = path.length === 0 ? validator : 'obj'; | ||
if (!validators[v](obj[key])) { | ||
errors.push(new Error(errorMessages[v](`${objPath}.${key}`, obj[key]))); | ||
return; | ||
} | ||
if (path.length > 0) { | ||
checkField(obj[key], `${objPath}.${key}`, path, validator); | ||
} | ||
} | ||
Object.keys(rules).forEach((f) => { | ||
const path = f.indexOf('.') > 0 ? f.split('.') : [f]; | ||
let v = rules[f]; | ||
if (!(v in validators)) { v = 'ok'; } | ||
checkField(msg, 'msg', path, v); | ||
}); | ||
return errors; | ||
} | ||
validate(msg, rules = this.validates) { | ||
if (failed(msg)) { | ||
return false; | ||
} | ||
if (rules && typeof rules === 'object') { | ||
rules = normalizeValidators(rules); | ||
const errs = this.checkFields(msg, rules); | ||
if (errs.length > 0) { | ||
fail(msg, errs); | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
} | ||
exports.Component = Component; | ||
/** | ||
* @param {AssemblyMessage} msg | ||
* @param {Array<string>} [excludeKeys] | ||
* @param {Array<string>} [cloneKeys] | ||
* @returns {AssemblyMessage} | ||
*/ | ||
function fork(msg, excludeKeys = [], cloneKeys = []) { | ||
const newMsg = {}; | ||
Object.keys(msg).forEach((key) => { | ||
if (excludeKeys.includes(key)) { return; } | ||
if (cloneKeys.includes(key)) { | ||
newMsg[key] = JSON.parse(JSON.stringify(msg[key])); | ||
} else { | ||
newMsg[key] = msg[key]; | ||
} | ||
}); | ||
return newMsg; | ||
/** @type {AssemblyMessage} */ | ||
const newMsg = { | ||
errors: cloneKeys.includes('error') ? msg.errors.slice(0) : msg.errors, | ||
}; | ||
Object.keys(msg).forEach((key) => { | ||
if (key === 'errors') { | ||
return; | ||
} | ||
if (excludeKeys.includes(key)) { | ||
return; | ||
} | ||
if (cloneKeys.includes(key)) { | ||
newMsg[key] = JSON.parse(JSON.stringify(msg[key])); | ||
} | ||
else { | ||
newMsg[key] = msg[key]; | ||
} | ||
}); | ||
return newMsg; | ||
} | ||
exports.fork = fork; | ||
/** | ||
* @param {AssemblyMessage} base | ||
* @param {Object<string, any>} extra | ||
* @returns {AssemblyMessage} | ||
*/ | ||
function merge(base, extra) { | ||
const combined = base; | ||
const baseKeys = Object.keys(base); | ||
Object.keys(extra).forEach((key) => { | ||
if ((baseKeys.indexOf(key) === -1 || base[key] === undefined) && extra[key] !== undefined) { | ||
combined[key] = extra[key]; | ||
} | ||
}); | ||
return combined; | ||
const combined = base; | ||
const baseKeys = Object.keys(base); | ||
Object.keys(extra).forEach((key) => { | ||
if ((baseKeys.indexOf(key) === -1 || base[key] === undefined) && extra[key] !== undefined) { | ||
combined[key] = extra[key]; | ||
} | ||
}); | ||
return combined; | ||
} | ||
module.exports = Component; | ||
module.exports.default = Component; | ||
module.exports.fail = fail; | ||
module.exports.failed = failed; | ||
module.exports.fork = fork; | ||
module.exports.merge = merge; | ||
exports.merge = merge; | ||
exports.default = Component; |
{ | ||
"name": "noflo-assembly", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Industrial approach to writing NoFlo applications", | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"repository": { | ||
@@ -11,7 +12,11 @@ "type": "git", | ||
"scripts": { | ||
"pretest": "eslint index.js example/components/*.js spec/*.js", | ||
"lint": "eslint src/*.js example/components/*.js spec/*.js", | ||
"build": "tsc", | ||
"pretest": "npm run build && npm run lint", | ||
"test": "mocha -R spec spec/*.js" | ||
}, | ||
"keywords": [ | ||
"noflo" | ||
"noflo", | ||
"ecosystem:noflo", | ||
"ecosystem:noflo-assembly" | ||
], | ||
@@ -23,3 +28,3 @@ "author": "Vladimir Sibirov", | ||
}, | ||
"homepage": "https://github.com/noflo/noflo-assembly", | ||
"homepage": "https://github.com/noflo/noflo-assembly/wiki", | ||
"dependencies": { | ||
@@ -29,9 +34,12 @@ "noflo": "^1.0.0" | ||
"devDependencies": { | ||
"@types/node": "^14.14.12", | ||
"chai": "^4.1.2", | ||
"eslint": "^4.12.0", | ||
"eslint-config-airbnb-base": "^12.1.0", | ||
"eslint-plugin-import": "^2.2.0", | ||
"mocha": "^4.0.1", | ||
"noflo-wrapper": "^0.2.1" | ||
"eslint": "^7.7.0", | ||
"eslint-config-airbnb-base": "^14.2.0", | ||
"eslint-plugin-import": "^2.22.0", | ||
"mocha": "^8.1.3", | ||
"noflo": "^1.4.2", | ||
"noflo-wrapper": "^0.4.1", | ||
"typescript": "^4.1.2" | ||
} | ||
} |
# NoFlo Assembly Line | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/noflo/noflo-assembly.svg)](https://greenkeeper.io/) [![Build Status](https://travis-ci.org/noflo/noflo-assembly.svg?branch=master)](https://travis-ci.org/noflo/noflo-assembly) | ||
Industrial approach to writing NoFlo applications | ||
@@ -45,3 +43,3 @@ | ||
```javascript | ||
import Component from 'noflo-assembly'; | ||
import { Component } from 'noflo-assembly'; | ||
``` | ||
@@ -48,0 +46,0 @@ |
/* eslint-env node, mocha */ | ||
const { expect } = require('chai'); | ||
const Wrapper = require('noflo-wrapper'); | ||
const { ComponentLoader } = require('noflo'); | ||
const { resolve } = require('path'); | ||
@@ -9,22 +8,13 @@ | ||
this.timeout(5000); | ||
let loader; | ||
let c; | ||
before((done) => { | ||
// A bit of magic with custom loader for subfolder project to work | ||
loader = new ComponentLoader(resolve(__dirname, '../example')); | ||
loader.listComponents((err) => { | ||
if (err) { done(err); return; } | ||
done(); | ||
}); | ||
}); | ||
describe('A simple pipeline graph', () => { | ||
before((done) => { | ||
c = new Wrapper('', { loader }); | ||
c.component = 'example/BuildChassis'; | ||
c.start((err) => { | ||
if (err) { done(err); return; } | ||
done(); | ||
c = new Wrapper('example/BuildChassis', { | ||
baseDir: resolve(__dirname, '../example'), | ||
debug: true, | ||
}); | ||
c.start(done); | ||
}); | ||
after(() => c.dumpTrace()); | ||
@@ -59,9 +49,9 @@ it('should build a car chassis', (done) => { | ||
before((done) => { | ||
c = new Wrapper('', { loader }); | ||
c.component = 'example/BuildBody'; | ||
c.start((err) => { | ||
if (err) { done(err); return; } | ||
done(); | ||
c = new Wrapper('example/BuildBody', { | ||
baseDir: resolve(__dirname, '../example'), | ||
debug: true, | ||
}); | ||
c.start(done); | ||
}); | ||
after(() => c.dumpTrace()); | ||
@@ -94,9 +84,9 @@ it('should build a car body', (done) => { | ||
before((done) => { | ||
c = new Wrapper('', { loader }); | ||
c.component = 'example/BuildCar'; | ||
c.start((err) => { | ||
if (err) { done(err); return; } | ||
done(); | ||
c = new Wrapper('example/BuildCar', { | ||
baseDir: resolve(__dirname, '../example'), | ||
debug: true, | ||
}); | ||
c.start(done); | ||
}); | ||
after(() => c.dumpTrace()); | ||
@@ -103,0 +93,0 @@ it('should build a car', (done) => { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47897
29
1246
9
318
1