react 0.2.1 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -242,7 +242,8 @@ fn(2, 3, function (err, m, s) {
242
242
 
243
243
  ## Status
244
244
 
245
- - 2012-01-10 - Create default DSL for react()
245
+ - 2012-01-11 - Provide warning/error when name is skipped in default DSL, literal check in validate (v0.2.5)
246
+ - 2012-01-10 - Create default DSL for react(), create error for missing variables, list remaining tasks when flow won't complete
246
247
  - 2011-12-21 - Refactor from ground up with tests, changes to the interfaces
247
248
  - 2011-10-26 - React is in active development and interface may change frequently in these early stages. Current code is functional but does not perform validation yet. Additional interfaces are planned to make it easy to define flows in a variety of ways. Documentation and examples forthcoming.
248
249
 
@@ -253,7 +254,7 @@ ok ast.test.js .................... 10/10
253
254
  ok cb-task.test.js ................ 31/31
254
255
  ok chain.test.js .................. 56/56
255
256
  ok core.test.js ................... 98/98
256
- ok dsl.test.js .................... 58/58
257
+ ok dsl.test.js .................... 63/63
257
258
  ok event-manager.test.js .......... 13/13
258
259
  ok exec-options.test.js ............. 3/3
259
260
  ok finalcb-task.test.js ............. 5/5
@@ -265,9 +266,9 @@ ok ret-task.test.js ............... 31/31
265
266
  ok task.test.js ..................... 1/1
266
267
  ok validate-cb-task.test.js ......... 6/6
267
268
  ok validate-ret-task.test.js ........ 7/7
268
- ok validate.test.js ............... 26/26
269
+ ok validate.test.js ............... 31/31
269
270
  ok vcon.test.js ................... 42/42
270
- total ........................... 613/613
271
+ total ........................... 623/623
271
272
 
272
273
  ok
273
274
  ```
@@ -24,7 +24,7 @@ function markdown(filedata) { return 'html'+filedata; }
24
24
  function prepareDirectory(outDirname, cb){ setTimeout(cb, 200, null, 'dircreated-'+outDirname); }
25
25
  function writeOutput(html, user, cb){ setTimeout(cb, 300, null, html+'_bytesWritten'); }
26
26
  function loadEmailTemplate(cb) { setTimeout(cb, 50, null, 'emailmd'); }
27
- function customizeEmail(user, emailHtml, cb) { return 'cust-'+user+emailHtml; }
27
+ function customizeEmail(user, emailHtml) { return 'cust-'+user+emailHtml; }
28
28
  function deliverEmail(custEmailHtml, cb) { setTimeout(cb, 100, null, 'delivered-'+custEmailHtml); }
29
29
 
30
30
  function useHtml(err, html, user, bytesWritten) {
@@ -43,9 +43,9 @@ var loadAndSave = react('loadAndSave', 'filename, uid, outDirname, cb -> err, ht
43
43
  writeOutput, 'html, user, cb -> err, bytesWritten', { after: prepareDirectory }, // only after prepareDirectory done
44
44
  loadEmailTemplate, 'cb -> err, emailmd',
45
45
  markdown, 'emailmd -> emailHtml', // using a sync function
46
- customizeEmail, 'user, emailHtml, cb -> err, custEmailHtml',
46
+ customizeEmail, 'user, emailHtml -> err, custEmailHtml', // sync fn
47
47
  deliverEmail, 'custEmailHtml, cb -> err, deliveredEmail', { after: writeOutput } // only after writeOutput is done
48
- );
48
+ );
49
49
 
50
50
  loadAndSave('file.md', 100, '/tmp/foo', useHtml); // executing the flow
51
51
 
@@ -9,12 +9,13 @@ function markdown(filedata) { return 'html'+filedata; }
9
9
  function prepareDirectory(outDirname, cb){ setTimeout(cb, 200, null, 'dircreated-'+outDirname); }
10
10
  function writeOutput(html, user, cb){ setTimeout(cb, 300, null, html+'_bytesWritten'); }
11
11
  function loadEmailTemplate(cb) { setTimeout(cb, 50, null, 'emailmd'); }
12
- function customizeEmail(user, emailHtml, cb) { return 'cust-'+user+emailHtml; }
12
+ function customizeEmail(user, emailHtml) { return 'cust-'+user+emailHtml; }
13
13
  function deliverEmail(custEmailHtml, cb) { setTimeout(cb, 100, null, 'delivered-'+custEmailHtml); }
14
14
 
15
15
  function useHtml(err, html, user, bytesWritten) {
16
16
  if (err) {
17
17
  console.log('***Error: %s', err);
18
+ console.error(err.stack);
18
19
  return;
19
20
  }
20
21
  console.log('final result: %s, user: %s, written:%s', html, user, bytesWritten);
@@ -22,15 +23,15 @@ function useHtml(err, html, user, bytesWritten) {
22
23
 
23
24
  // define fn, glue together with react, it will parallelize
24
25
  // starts with name and in/out params, then the tasks
25
- var loadAndSave = react('loadAndSave', 'fName, uid, outDir, cb -> err, html, user, bytes',
26
+ var loadAndSave = react('loadAndSave', 'filename, uid, outDir, cb -> err, html, user, bytes',
26
27
  loadUser, 'uid, cb -> err, user', // calling async loadUser with uid, cb called with err and user
27
28
  loadFile, 'filename, cb -> err, filedata',
28
29
  markdown, 'filedata -> html', // using a sync function
29
- prepareDirectory, 'outDirname, cb -> err, dircreated',
30
- writeOutput, 'html, user, cb -> err, bytesWritten', { after: prepareDirectory }, // only after prepareDirectory done
30
+ prepareDirectory, 'outDir, cb -> err, dircreated',
31
+ writeOutput, 'html, user, cb -> err, bytes', { after: prepareDirectory }, // only after prepareDirectory done
31
32
  loadEmailTemplate, 'cb -> err, emailmd',
32
33
  markdown, 'emailmd -> emailHtml', // using a sync function
33
- customizeEmail, 'user, emailHtml, cb -> custEmailHtml',
34
+ customizeEmail, 'user, emailHtml -> custEmailHtml', // sync function
34
35
  deliverEmail, 'custEmailHtml, cb -> err, deliveredEmail', { after: writeOutput } // only after writeOutput is done
35
36
  );
36
37
 
@@ -0,0 +1 @@
1
+ barczewskij@ELSSTLM-184670.local.99357
package/lib/core.js CHANGED
@@ -13,10 +13,10 @@ var inputParser = require('./input-parser.js');
13
13
  var idGenerator = require('./id.js');
14
14
 
15
15
  var reactOptions = {
16
- stackTraceLimitMin: 30
16
+ stackTraceLimitMin: 30
17
17
  };
18
18
 
19
- var reactEmitter = EventManager.create(); // the top emitter
19
+ var reactEmitter = EventManager.globalEventManager; // the top emitter
20
20
 
21
21
  /**
22
22
  Creates react function which the AST can be manipulated and then
@@ -39,7 +39,7 @@ var reactEmitter = EventManager.create(); // the top emitter
39
39
  */
40
40
  function reactFactory() {
41
41
  if (arguments.length) throw new Error('react() takes no args, check API');
42
-
42
+
43
43
  error.ensureStackTraceLimitSet(reactOptions.stackTraceLimitMin);
44
44
  var flowEmitter = EventManager.create();
45
45
  flowEmitter.parent = reactEmitter;
@@ -75,7 +75,7 @@ function reactFactory() {
75
75
  if (outTask.isReady()) return outTask.exec(); // all tasks done, exec cb, return
76
76
  tskutil.findReadyAndExec(vCon, tasks, tasksByName, handleError, contExec); //exec tasks that ready to run
77
77
  }
78
-
78
+
79
79
  tasks.forEach(function (t) {
80
80
  t.id = idGenerator.createUniqueId();
81
81
  t.flowEmitter = flowEmitter;
package/lib/dsl.js CHANGED
@@ -6,9 +6,11 @@ var core = require('./core.js');
6
6
  var parse = require('./parse.js');
7
7
  var tutil = require('./task.js');
8
8
 
9
+ var MISSING_NAME = 'param[0] should be the flow name, instead found in/out def: %s';
9
10
  var INOUT_PARAMS_NO_MATCH = 'params in wrong format, wanted "foo, bar cb -> err, baz" - found: %s';
10
11
  var EXTRA_TASKARG = 'extra unmatched task arg: %s';
11
12
 
13
+ var INOUT_RE = /\->/; // used to detect missing name, in/out as first arg
12
14
  var CB_NAMES_RE = /^cb|callback$/i; //cb, Cb, CB, callback, Callback
13
15
  var ERR_NAMES_RE = /^err$/i; // err, ERR, Err, ...
14
16
 
@@ -81,6 +83,7 @@ function parseVargs(vargs) {
81
83
 
82
84
  function dslDefine(name, arg1, arg2, argN) {
83
85
  var reactFn = core();
86
+ if (name && INOUT_RE.test(name)) throw new Error(sprintf(MISSING_NAME, name));
84
87
  var defObj = parseVargs(Array.prototype.slice.call(arguments, 1)); // name, already used
85
88
  var inOutDef = parseInOutParams(defObj.inOutParamStr);
86
89
  var ast = {
package/lib/error.js CHANGED
@@ -9,19 +9,29 @@ function ensureStackTraceLimitSet(stackTraceLimit) {
9
9
  }
10
10
 
11
11
  function fName(fn) {
12
- return (typeof(fn) === 'string') ? fn : fn.name;
12
+ if (!fn) return 'undefined';
13
+ return (fn && fn.name) ? fn.name : fn;
13
14
  }
14
15
 
15
16
  function formatErrorMeta(err) {
16
17
  if (!err.meta) return;
17
18
  var vcon = err.meta.vcon;
18
19
  var task = err.meta.task;
19
- return '\n\n' +
20
- 'Error occurs in Task function: ' + fName(task.f) + '(' + task.a.join(',') + ')\n\n' +
21
- 'Variable Context: \n' +
22
- util.inspect(vcon) + '\n\n' +
23
- 'Task Source:\n\n' +
24
- task.f.toString() + '\n\n'; //TODO need to pretty print function, gets collapsed
20
+ var errString = '\n\n';
21
+ if (task && task.f && task.a) {
22
+ errString += ('Error occurs in Task function: ' + fName(task.f) + '(' + task.a.join(',') + ')\n\n');
23
+ }
24
+ if (vcon) {
25
+ errString += 'Variable Context: \n';
26
+ errString += util.inspect(vcon);
27
+ errString += '\n\n';
28
+ }
29
+ if (task && task.f) {
30
+ errString += 'Task Source:\n\n';
31
+ errString += task.f.toString(); //TODO need to pretty print function, gets collapsed
32
+ errString += '\n\n';
33
+ }
34
+ return errString;
25
35
  }
26
36
 
27
37
  function augmentError(err, meta) {
@@ -54,4 +54,5 @@ EventManager.prototype.emitObject = function (event, object) {
54
54
  };
55
55
 
56
56
  module.exports = EventManager;
57
- module.exports.TYPES = TYPES;
57
+ module.exports.TYPES = TYPES;
58
+ module.exports.globalEventManager = EventManager.create(); // create one top level emitter
package/lib/task.js CHANGED
@@ -25,7 +25,7 @@ function outTaskTypeKeys() { return Object.keys(OUT_TASK_TYPES); }
25
25
 
26
26
  var LOCAL_FN_MISSING = 'function: %s not found in locals or input params - task[%s]';
27
27
  var TASKDEF_IS_OBJECT = 'task must be an object';
28
- var NO_TASKS_RUNNING_WONT_COMPLETE = 'no tasks running, flow will not complete';
28
+ var NO_TASKS_RUNNING_WONT_COMPLETE = 'no tasks running, flow will not complete, remaining tasks: %s';
29
29
  var TASK_TYPE_SHOULD_MATCH = 'task.type should match one of ' +
30
30
  Object.keys(TASK_TYPES).join(', ');
31
31
 
@@ -201,7 +201,12 @@ function checkIfTasksRunning(vCon, tasks, handleError) {
201
201
  var tasksRunning = tasks.filter(function (t) {
202
202
  return (t.status === STATUS.RUNNING || t.status === STATUS.READY);
203
203
  });
204
- if (!tasksRunning.length) handleError({}, new Error(NO_TASKS_RUNNING_WONT_COMPLETE));
204
+ if (!tasksRunning.length) {
205
+ var remainingTasks = tasks.filter(function (t) { return (!t.status); });
206
+ var remainingTNames = remainingTasks.map(function (t) { return t.name; });
207
+ var errMsg = sprintf(NO_TASKS_RUNNING_WONT_COMPLETE, remainingTNames.join(', '));
208
+ handleError({}, new Error(errMsg));
209
+ }
205
210
  }
206
211
 
207
212
  function findReadyAndExec(vCon, tasks, tasksByName, handleError, contExec) {
package/lib/validate.js CHANGED
@@ -11,14 +11,27 @@ var TASKS_ARR = 'ast.tasks must be an array of tasks';
11
11
  var NAMES_UNIQUE = 'ast.tasks that specify name need to be unique, duplicate:';
12
12
  var LOCALS_NOTNULL = 'ast.locals should not be null';
13
13
  var DUP_OUTPUTS = 'multiple tasks output the same param, must be unique. param';
14
+ var MISSING_INPUTS = 'missing or mispelled variable referenced in flow definition: %s';
15
+
16
+ // match any of our literals true, false, int, float, quoted strings, or is property (has dot)
17
+ var LITERAL_OR_PROP_RE = /^(true|false|\-?[0-9\.]+)$|'|"|\./i;
14
18
 
15
19
  var validateInParams, validateTasks, validateOutTask, validateTaskNamesUnique;
16
- var validateLocals, validateOuputsUnique;
20
+ var validateLocals, validateOuputsUnique, validateNoMissingNames;
17
21
 
18
22
  function format_error(errmsg, obj) {
19
23
  return sprintf('%s - %s', errmsg, util.inspect(obj));
20
24
  }
21
25
 
26
+ /**
27
+ true if is a literal name
28
+ */
29
+ function isLiteralOrProp(name) { // need to match what is in vcon.js, TODO consolidate?
30
+ return LITERAL_OR_PROP_RE.test(name);
31
+ }
32
+
33
+
34
+
22
35
  /**
23
36
  validate the AST return Errors
24
37
  @example
@@ -37,6 +50,7 @@ function validate(ast) {
37
50
  if (errors.length === 0) { // if no errors do additional validation
38
51
  if (ast.outTask.type !== 'finalcbFirst') errors = errors.concat(validateOuputsUnique(ast.tasks));
39
52
  errors = errors.concat(tutil.validateLocalFunctions(ast.inParams, ast.tasks, ast.locals));
53
+ errors = errors.concat(validateNoMissingNames(ast));
40
54
  }
41
55
  return errors;
42
56
  }
@@ -99,4 +113,47 @@ function validateOuputsUnique(taskDefs) {
99
113
  return errors;
100
114
  }
101
115
 
116
+
117
+ /**
118
+ validate there are no missing or mispelled param names in any task inputs
119
+ or the final task output
120
+
121
+ @return array of errors, or empty array if none
122
+ */
123
+ function validateNoMissingNames(ast) {
124
+ var errors = [];
125
+ var names = {};
126
+ if (ast.locals) {
127
+ names = Object.keys(ast.locals).reduce(function (accum, k) { // start with locals
128
+ accum[k] = true;
129
+ return accum;
130
+ }, names);
131
+ }
132
+ ast.inParams.reduce(function (accum, p) { // add input params
133
+ accum[p] = true;
134
+ return accum;
135
+ }, names);
136
+ ast.tasks.reduce(function (accum, t) { // add task outputs
137
+ return t.out.reduce(function (innerAccum, p) {
138
+ innerAccum[p] = true;
139
+ return innerAccum;
140
+ }, accum);
141
+ }, names);
142
+
143
+ // now we have all possible provided vars, check task inputs are accounted for
144
+ ast.tasks.reduce(function (accum, t) { // for all tasks
145
+ return t.a.reduce(function (innerAccum, p) { // for all in params, except property
146
+ if (!isLiteralOrProp(p) && !names[p]) innerAccum.push(sprintf(MISSING_INPUTS, p)); // add error if missing
147
+ return innerAccum;
148
+ }, accum);
149
+ }, errors);
150
+
151
+ // now check the final task outputs
152
+ ast.outTask.a.reduce(function (accum, p) { // for final task out params
153
+ if (!isLiteralOrProp(p) && !names[p]) accum.push(sprintf(MISSING_INPUTS, p)); // add error if missing
154
+ return accum;
155
+ }, errors);
156
+ return errors;
157
+ }
158
+
102
159
  module.exports = validate;
package/lib/vcon.js CHANGED
@@ -9,9 +9,11 @@ VContext.prototype.getLastResults = function () { return this.getVar(LAST_RESULT
9
9
  VContext.prototype.setLastResults = function (args) { this.setVar(LAST_RESULTS_KEY, args); };
10
10
 
11
11
  VContext.prototype.getVar = function (name) { //name might be simple or obj.prop, also literals
12
+ /*jshint regexp: false */
12
13
  var vConValues = this.values;
13
14
  if (typeof(name) !== 'string') return name; // literal boolean or number
14
15
  name = name.trim();
16
+ // literal checks need to match what is in validate.js
15
17
  if (name === 'true') return true;
16
18
  if (name === 'false') return false;
17
19
  if (/^-?[0-9]+$/.test(name)) return parseInt(name, 10); //int
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react",
3
3
  "description": "React is a javascript module to make it easier to work with asynchronous code, by reducing boilerplate code and improving error and exception handling while allowing variable and task dependencies when defining flow.",
4
- "version": "0.2.1",
4
+ "version": "0.2.5",
5
5
  "author": "Jeff Barczewski <jeff.barczewski@gmail.com>",
6
6
  "repository": { "type": "git", "url": "http://github.com/jeffbski/react.git" },
7
7
  "bugs" : { "url": "http://github.com/jeffbski/react/issues" },
@@ -63,10 +63,11 @@ test('in triple first param -> inParams["foo", "bar", "baz"]', function (t) {
63
63
 
64
64
  test('single task, single out params', function (t) {
65
65
  var fn = chainDefine()
66
+ .in('a', 'b')
66
67
  .out('c')
67
68
  .async(falpha).in('a', 'b').out('c')
68
69
  .end();
69
- t.deepEqual(fn.ast.inParams, []);
70
+ t.deepEqual(fn.ast.inParams, ['a', 'b']);
70
71
  t.deepEqual(fn.ast.tasks, [
71
72
  { f: falpha, type: 'cb', a: ['a', 'b'], out: ['c'], name: 'falpha' }
72
73
  ]);
@@ -77,11 +78,11 @@ test('single task, single out params', function (t) {
77
78
  test('single task, err and out params', function (t) {
78
79
  var fn = chainDefine()
79
80
  .out('err', 'c')
80
- .async(falpha).in('a', 'b').out('err', 'c')
81
+ .async(falpha).in().out('err', 'c')
81
82
  .end();
82
83
  t.deepEqual(fn.ast.inParams, []);
83
84
  t.deepEqual(fn.ast.tasks, [
84
- { f: falpha, type: 'cb', a: ['a', 'b'], out: ['c'], name: 'falpha' }
85
+ { f: falpha, type: 'cb', a: [], out: ['c'], name: 'falpha' }
85
86
  ]);
86
87
  t.deepEqual(fn.ast.outTask, { a: ['c'], type: 'finalcb' });
87
88
  t.end();
@@ -90,11 +91,11 @@ test('single task, err and out params', function (t) {
90
91
  test('single task, ERR and out params', function (t) {
91
92
  var fn = chainDefine()
92
93
  .out('ERR', 'c')
93
- .async(falpha).in('a', 'b').out('ERR', 'c')
94
+ .async(falpha).in().out('ERR', 'c')
94
95
  .end();
95
96
  t.deepEqual(fn.ast.inParams, []);
96
97
  t.deepEqual(fn.ast.tasks, [
97
- { f: falpha, type: 'cb', a: ['a', 'b'], out: ['c'], name: 'falpha' }
98
+ { f: falpha, type: 'cb', a: [], out: ['c'], name: 'falpha' }
98
99
  ]);
99
100
  t.deepEqual(fn.ast.outTask, { a: ['c'], type: 'finalcb' });
100
101
  t.end();
package/test/core.test.js CHANGED
@@ -198,14 +198,15 @@ test('error when cant complete', function (t) {
198
198
  inParams: ['a', 'b', 'c'],
199
199
  tasks: [
200
200
  { f: multiply, a: ['a', 'b'], out: ['c.mult'] },
201
- { f: fnRetsSum, a: ['c.bad', 'b'], out: ['c.sum'], type: 'ret' }
201
+ { f: fnRetsSum, a: ['c.bad', 'b'], out: ['c.sum'], type: 'ret' },
202
+ { f: add, a: ['c.sum', 'a'], out: ['d']}
202
203
  ],
203
- outTask: { a: ['c.mult', 'c.sum', 'c'] }
204
+ outTask: { a: ['c.mult', 'c.sum', 'd'] }
204
205
  });
205
206
  t.deepEqual(errors, [], 'no validation errors');
206
207
 
207
- fn(2, 3, { foo: 1 }, function (err, cmult, csum, c) {
208
- t.equal(err.message, 'no tasks running, flow will not complete');
208
+ fn(2, 3, { foo: 1 }, function (err, cmult, csum, d) {
209
+ t.equal(err.message, 'no tasks running, flow will not complete, remaining tasks: fnRetsSum, add');
209
210
  t.end();
210
211
  });
211
212
  });
package/test/dsl.test.js CHANGED
@@ -55,11 +55,11 @@ test('triple first string -> inParams["foo", "bar", "baz"], empty tasks, outTask
55
55
 
56
56
  test('single task, single out params', function (t) {
57
57
  var r = react('myName', 'cb -> err, c',
58
- falpha, 'a, b, cb -> err, c'
58
+ falpha, 'cb -> err, c'
59
59
  );
60
60
  t.deepEqual(r.ast.inParams, []);
61
61
  t.deepEqual(r.ast.tasks, [
62
- { f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
62
+ { f: falpha, a: [], out: ['c'], type: 'cb', name: 'falpha'}
63
63
  ]);
64
64
  t.deepEqual(r.ast.outTask, { a: ['c'], type: 'finalcb' });
65
65
  t.end();
@@ -67,11 +67,11 @@ test('single task, single out params', function (t) {
67
67
 
68
68
  test('single task, err and out params', function (t) {
69
69
  var r = react('myName', 'cb -> err, c',
70
- falpha, 'a, b, cb -> err, c'
70
+ falpha, 'cb -> err, c'
71
71
  );
72
72
  t.deepEqual(r.ast.inParams, []);
73
73
  t.deepEqual(r.ast.tasks, [
74
- { f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
74
+ { f: falpha, a: [], out: ['c'], type: 'cb', name: 'falpha'}
75
75
  ]);
76
76
  t.deepEqual(r.ast.outTask, { a: ['c'], type: 'finalcb' });
77
77
  t.end();
@@ -79,11 +79,11 @@ test('single task, err and out params', function (t) {
79
79
 
80
80
  test('single task, ERR and out params', function (t) {
81
81
  var r = react('myName', 'cb -> ERR, c',
82
- falpha, 'a, b, cb -> ERR, c'
82
+ falpha, 'cb -> ERR, c'
83
83
  );
84
84
  t.deepEqual(r.ast.inParams, []);
85
85
  t.deepEqual(r.ast.tasks, [
86
- { f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
86
+ { f: falpha, a: [], out: ['c'], type: 'cb', name: 'falpha'}
87
87
  ]);
88
88
  t.deepEqual(r.ast.outTask, { a: ['c'], type: 'finalcb' });
89
89
  t.end();
@@ -191,6 +191,19 @@ test('object prop task params', function (t) {
191
191
  t.end();
192
192
  });
193
193
 
194
+ // Errors
195
+
196
+ test('missing name, throws error', function (t) {
197
+ var fn = function () {
198
+ var r = react('cb -> err, c',
199
+ falpha, 'cb -> err, c'
200
+ );
201
+ };
202
+ t.throws(fn, new Error('param[0] should be the flow name, instead found in/out def: cb -> err, c'));
203
+ t.end();
204
+ });
205
+
206
+
194
207
  test('extra arg throws error', function (t) {
195
208
  var fn = function () {
196
209
  var r = react('myName', 'a, b, cb -> err, c, d',
@@ -214,7 +227,35 @@ test('not enough args throws error', function (t) {
214
227
  t.end();
215
228
  });
216
229
 
217
-
230
+ test('long example', function (t) {
231
+ t.plan(4);
232
+ function loadUser(uid, cb){ setTimeout(cb, 100, null, "User"+uid); }
233
+ function loadFile(filename, cb){ setTimeout(cb, 100, null, 'Filedata'+filename); }
234
+ function markdown(filedata) { return 'html'+filedata; }
235
+ function prepareDirectory(outDirname, cb){ setTimeout(cb, 200, null, 'dircreated-'+outDirname); }
236
+ function writeOutput(html, user, cb){ setTimeout(cb, 300, null, html+'_bytesWritten'); }
237
+ function loadEmailTemplate(cb) { setTimeout(cb, 50, null, 'emailmd'); }
238
+ function customizeEmail(user, emailHtml) { return 'cust-'+user+emailHtml; }
239
+ function deliverEmail(custEmailHtml, cb) { setTimeout(cb, 100, null, 'delivered-'+custEmailHtml); }
240
+ var loadAndSave = react('loadAndSave', 'filename, uid, outDirname, cb -> err, html, user, bytesWritten', // name, in/out params
241
+ loadUser, 'uid, cb -> err, user', // calling async fn loadUser with uid, callback is called with err and user
242
+ loadFile, 'filename, cb -> err, filedata',
243
+ markdown, 'filedata -> html', // using a sync function
244
+ prepareDirectory, 'outDirname, cb -> err, dircreated',
245
+ writeOutput, 'html, user, cb -> err, bytesWritten', { after: prepareDirectory }, // only after prepareDirectory done
246
+ loadEmailTemplate, 'cb -> err, emailmd',
247
+ markdown, 'emailmd -> emailHtml', // using a sync function
248
+ customizeEmail, 'user, emailHtml -> custEmailHtml',
249
+ deliverEmail, 'custEmailHtml, cb -> err, deliveredEmail', { after: writeOutput } // only after writeOutput is done
250
+ );
251
+ loadAndSave('file.md', 100, '/tmp/foo', function (err, html, user, bytesWritten) { // executing the flow
252
+ t.equal(err, null);
253
+ t.equal(html, 'htmlFiledatafile.md');
254
+ t.equal(user, 'User100');
255
+ t.equal(bytesWritten, 'htmlFiledatafile.md_bytesWritten');
256
+ t.end();
257
+ });
258
+ });
218
259
 
219
260
  // selectFirst
220
261
 
package/test/fstr.test.js CHANGED
@@ -48,10 +48,10 @@ test('triple first string -> inParams["foo", "bar", "baz"], empty tasks, outTask
48
48
  });
49
49
 
50
50
  test('single task, single out params', function (t) {
51
- var r = fstrDefine('', [
51
+ var r = fstrDefine('a, b', [
52
52
  falpha, 'a, b -> err, c'
53
53
  ], 'c');
54
- t.deepEqual(r.ast.inParams, []);
54
+ t.deepEqual(r.ast.inParams, ['a', 'b']);
55
55
  t.deepEqual(r.ast.tasks, [
56
56
  { f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
57
57
  ]);
@@ -60,10 +60,10 @@ test('single task, single out params', function (t) {
60
60
  });
61
61
 
62
62
  test('single task, err and out params', function (t) {
63
- var r = fstrDefine('', [
63
+ var r = fstrDefine('a, b', [
64
64
  falpha, 'a, b -> err, c'
65
65
  ], 'err, c');
66
- t.deepEqual(r.ast.inParams, []);
66
+ t.deepEqual(r.ast.inParams, ['a', 'b']);
67
67
  t.deepEqual(r.ast.tasks, [
68
68
  { f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
69
69
  ]);
@@ -72,10 +72,10 @@ test('single task, err and out params', function (t) {
72
72
  });
73
73
 
74
74
  test('single task, ERR and out params', function (t) {
75
- var r = fstrDefine('', [
75
+ var r = fstrDefine('a, b', [
76
76
  falpha, 'a, b -> ERR, c'
77
77
  ], 'ERR, c');
78
- t.deepEqual(r.ast.inParams, []);
78
+ t.deepEqual(r.ast.inParams, ['a', 'b']);
79
79
  t.deepEqual(r.ast.tasks, [
80
80
  { f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
81
81
  ]);
@@ -50,11 +50,11 @@ test('triple first string -> inParams["foo", "bar", "baz"], empty tasks, outTask
50
50
 
51
51
  test('single task, single out params', function (t) {
52
52
  var locals = { falpha: falpha };
53
- var r = pcode('', [
53
+ var r = pcode('a, b', [
54
54
  'c := falpha(a, b)',
55
55
  'cb(err, c)'
56
56
  ], locals);
57
- t.deepEqual(r.ast.inParams, []);
57
+ t.deepEqual(r.ast.inParams, ['a', 'b']);
58
58
  t.deepEqual(r.ast.tasks, [
59
59
  { f: 'falpha', a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
60
60
  ]);
@@ -64,11 +64,11 @@ test('single task, single out params', function (t) {
64
64
 
65
65
  test('single task, err and out params', function (t) {
66
66
  var locals = { falpha: falpha };
67
- var r = pcode('', [
67
+ var r = pcode('a, b', [
68
68
  'c := falpha(a, b)',
69
69
  'cb(err, c)'
70
70
  ], locals);
71
- t.deepEqual(r.ast.inParams, []);
71
+ t.deepEqual(r.ast.inParams, ['a', 'b']);
72
72
  t.deepEqual(r.ast.tasks, [
73
73
  { f: 'falpha', a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
74
74
  ]);
@@ -210,9 +210,83 @@ test('multiple tasks output the same param, must be unique', function (t) {
210
210
  { f: foo, a: [], out: ['baz', 'c'] },
211
211
  { f: bar, a: [], out: ['c'] }
212
212
  ],
213
- outTask: { a: ['bar'] }
213
+ outTask: { a: ['baz'] }
214
214
  };
215
215
  var msg = 'multiple tasks output the same param, must be unique. param: c';
216
216
  t.deepEqual(validate(ast), [msg]);
217
217
  t.end();
218
- });
218
+ });
219
+
220
+ test('missing or mispelled input variable', function (t) {
221
+ var ast = {
222
+ inParams: [],
223
+ tasks: [
224
+ { f: foo, a: [], out: [] },
225
+ { f: bar, a: ['abc'], out: [] }
226
+ ],
227
+ outTask: { a: [] }
228
+ };
229
+ var msg = 'missing or mispelled variable referenced in flow definition: abc';
230
+ t.deepEqual(validate(ast), [msg]);
231
+ t.end();
232
+ });
233
+
234
+ test('missing or mispelled input variables', function (t) {
235
+ var ast = {
236
+ inParams: ['aaa', 'bbb'],
237
+ tasks: [
238
+ { f: foo, a: ['aaa', 'cat'], out: ['ccc'] },
239
+ { f: bar, a: ['abc', 'bbb', 'ccc'], out: [] }
240
+ ],
241
+ outTask: { a: [] }
242
+ };
243
+ var messages = [
244
+ 'missing or mispelled variable referenced in flow definition: cat',
245
+ 'missing or mispelled variable referenced in flow definition: abc'
246
+ ];
247
+ t.deepEqual(validate(ast), messages);
248
+ t.end();
249
+ });
250
+
251
+ test('missing or mispelled final output variables', function (t) {
252
+ var ast = {
253
+ inParams: ['aaa'],
254
+ tasks: [
255
+ { f: foo, a: ['aaa'], out: ['bbb'] },
256
+ { f: bar, a: ['bbb'], out: ['ccc'] }
257
+ ],
258
+ outTask: { a: ['ccc', 'ddd', 'eee'] }
259
+ };
260
+ var messages = [
261
+ 'missing or mispelled variable referenced in flow definition: ddd',
262
+ 'missing or mispelled variable referenced in flow definition: eee'
263
+ ];
264
+ t.deepEqual(validate(ast), messages);
265
+ t.end();
266
+ });
267
+
268
+ test('missing or mispelled validation ignores properties', function (t) {
269
+ var ast = {
270
+ inParams: ['obj'],
271
+ tasks: [
272
+ { f: foo, a: ['obj.foo'], out: [] },
273
+ { f: bar, a: ['obj.bar'], out: [] }
274
+ ],
275
+ outTask: { a: ['obj.cat'] }
276
+ };
277
+ t.deepEqual(validate(ast), []);
278
+ t.end();
279
+ });
280
+
281
+ test('missing or mispelled validation ignores literals', function (t) {
282
+ var ast = {
283
+ inParams: [],
284
+ tasks: [
285
+ { f: foo, a: ['true', 'false', '123', '123.1'], out: [] },
286
+ { f: bar, a: ['-123', '-123.4', '"wow"', "'hey'"], out: [] }
287
+ ],
288
+ outTask: { a: [] }
289
+ };
290
+ t.deepEqual(validate(ast), []);
291
+ t.end();
292
+ });