[Pkg-javascript-commits] [node-seq] 03/09: Imported Upstream version 0.3.5

Ross Gammon ross-guest at moszumanska.debian.org
Sun Mar 13 16:12:14 UTC 2016


This is an automated email from the git hooks/post-receive script.

ross-guest pushed a commit to branch master
in repository node-seq.

commit 8178eee3e2b2723526255193fb13b237259a6975
Author: Ross Gammon <rossgammon at mail.dk>
Date:   Sun Mar 13 11:22:15 2016 +0100

    Imported Upstream version 0.3.5
---
 .npmignore               |   1 +
 README.markdown          | 442 ++++++++++++++++++++++
 examples/join.js         |  18 +
 examples/parseq.coffee   |  12 +
 examples/parseq.js       |  19 +
 examples/stat_all.coffee |  16 +
 examples/stat_all.js     |  17 +
 index.js                 | 520 ++++++++++++++++++++++++++
 package.json             |  33 ++
 test/readdir.js          |  35 ++
 test/seq.js              | 946 +++++++++++++++++++++++++++++++++++++++++++++++
 test/seq_.js             | 149 ++++++++
 12 files changed, 2208 insertions(+)

diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1 @@
+node_modules
diff --git a/README.markdown b/README.markdown
new file mode 100644
index 0000000..3689261
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,442 @@
+Seq
+===
+
+Seq is an asynchronous flow control library with a chainable interface for
+sequential and parallel actions. Even the error handling is chainable.
+
+Each action in the chain operates on a stack of values.
+There is also a variables hash for storing values by name.
+
+[TOC]
+
+
+
+Examples
+========
+
+stat_all.js
+-----------
+
+````javascript
+var fs = require('fs');
+var Hash = require('hashish');
+var Seq = require('seq');
+
+Seq()
+    .seq(function () {
+        fs.readdir(__dirname, this);
+    })
+    .flatten()
+    .parEach(function (file) {
+        fs.stat(__dirname + '/' + file, this.into(file));
+    })
+    .seq(function () {
+        var sizes = Hash.map(this.vars, function (s) { return s.size })
+        console.dir(sizes);
+    })
+;
+````
+
+Output:
+
+    { 'stat_all.js': 404, 'parseq.js': 464 }
+
+parseq.js
+---------
+
+````javascript
+var fs = require('fs');
+var exec = require('child_process').exec;
+
+var Seq = require('seq');
+Seq()
+    .seq(function () {
+        exec('whoami', this)
+    })
+    .par(function (who) {
+        exec('groups ' + who, this);
+    })
+    .par(function (who) {
+        fs.readFile(__filename, 'ascii', this);
+    })
+    .seq(function (groups, src) {
+        console.log('Groups: ' + groups.trim());
+        console.log('This file has ' + src.length + ' bytes');
+    })
+;
+````
+
+Output:
+
+    Groups: substack : substack dialout cdrom floppy audio src video plugdev games netdev fuse www
+    This file has 464 bytes
+
+
+
+
+API
+===
+
+Each method executes callbacks with a context (its `this`) described in the next
+section. Every method returns `this`.
+
+Whenever `this()` is called with a non-falsy first argument, the error value
+propagates down to the first `catch` it sees, skipping over all actions in
+between. There is an implicit `catch` at the end of all chains that prints the
+error stack if available and otherwise just prints the error.
+
+
+
+Seq(xs=[])
+----------
+
+The constructor function creates a new `Seq` chain with the methods described
+below. The optional array argument becomes the new context stack.
+
+Array argument is new in 0.3. `Seq()` now behaves like `Seq.ap()`.
+
+
+.seq(cb)
+--------
+.seq(key, cb, *args)
+--------------------
+
+This eponymous function executes actions sequentially.
+Once all running parallel actions are finished executing,
+the supplied callback is `apply()`'d with the context stack.
+
+To execute the next action in the chain, call `this()`. The first
+argument must be the error value. The rest of the values will become the stack
+for the next action in the chain and are also available at `this.args`.
+
+If `key` is specified, the second argument sent to `this` goes to
+`this.vars[key]` in addition to the stack and `this.args`.
+`this.vars` persists across all requests unless it is overwritten.
+
+All arguments after `cb` will be bound to `cb`, which is useful because
+`.bind()` makes you set `this`. If you pass in `Seq` in the arguments list,
+it'll get transformed into `this` so that you can do:
+
+````javascript
+Seq()
+    .seq(fs.readdir, __dirname, Seq)
+    .seq(function (files) { console.dir(files) })
+;
+````
+
+which prints an array of files in `__dirname`.
+
+
+.par(cb)
+--------
+.par(key, cb, *args)
+--------------------
+
+Use `par` to execute actions in parallel.
+Chain multiple parallel actions together and collect all the responses on the
+stack with a sequential operation like `seq`.
+
+Each `par` sets one element in the stack with the second argument to `this()` in
+the order in which it appears, so multiple `par`s can be chained together.
+
+Like with `seq`, the first argument to `this()` should be the error value and
+the second will get pushed to the stack. Further arguments are available in
+`this.args`.
+
+If `key` is specified, the result from the second argument send to `this()` goes
+to `this.vars[key]`.
+`this.vars` persists across all requests unless it is overwritten.
+
+All arguments after `cb` will be bound to `cb`, which is useful because
+`.bind()` makes you set `this`. Like `.seq()`, you can pass along `Seq` in these
+bound arguments and it will get tranformed into `this`.
+
+
+.catch(cb)
+----------
+
+Catch errors. Whenever a function calls `this` with a non-falsy first argument,
+the message propagates down the chain to the first `catch` it sees.
+The callback `cb` fires with the error object as its first argument and the key
+that the action that caused the error was populating, which may be undefined.
+
+`catch` is a sequential action and further actions may appear after a `catch` in
+a chain. If the execution reaches a `catch` in a chain and no error has occured,
+the `catch` is skipped over.
+
+For convenience, there is a default error handler at the end of all chains.
+This default error handler looks like this:
+
+````javascript
+.catch(function (err) {
+    console.error(err.stack ? err.stack : err)
+})
+````
+
+
+.forEach(cb)
+------------
+
+Execute each action in the stack under the context of the chain object.
+`forEach` does not wait for any of the actions to finish and does not itself
+alter the stack, but the callback may alter the stack itself by modifying
+`this.stack`.
+
+The callback is executed `cb(x,i)` where `x` is the element and `i` is the
+index. 
+
+`forEach` is a sequential operation like `seq` and won't run until all pending
+parallel requests yield results.
+
+
+.seqEach(cb)
+------------
+
+Like `forEach`, call `cb` for each element on the stack, but unlike `forEach`,
+`seqEach` waits for the callback to yield with `this` before moving on to the
+next element in the stack.
+
+The callback is executed `cb(x,i)` where `x` is the element and `i` is the
+index. 
+
+If `this()` is supplied non-falsy error, the error propagates downward but any
+other arguments are ignored. `seqEach` does not modify the stack itself.
+
+
+.parEach(cb)
+------------
+.parEach(limit, cb)
+-------------------
+
+Like `forEach`, calls cb for each element in the stack and doesn't wait for the
+callback to yield a result with `this()` before moving on to the next iteration.
+Unlike `forEach`, `parEach` waits for all actions to call `this()` before moving
+along to the next action in the chain.
+
+The callback is executed `cb(x,i)` where `x` is the element and `i` is the
+index. 
+
+`parEach` does not modify the stack itself and errors supplied to `this()`
+propagate.
+
+Optionally, if limit is supplied to `parEach`, at most `limit` callbacks will be
+active at a time.
+
+
+.seqMap(cb)
+-----------
+
+Like `seqEach`, but collect the values supplied to `this` and set the stack to
+these values.
+
+
+.parMap(cb)
+-----------
+.parMap(limit, cb)
+------------------
+
+Like `parEach`, but collect the values supplied to `this` and set the stack to
+these values.
+
+
+.seqFilter(cb)
+-----------
+
+Executes the callback `cb(x, idx)` against each element on the stack, waiting for the
+callback to yield with `this` before moving on to the next element. If the callback 
+returns an error or a falsey value, the element will not be included in the resulting
+stack.
+
+Any errors from the callback are consumed and **do not** propagate.
+
+Calls to `this.into(i)` will place the value, if accepted by the callback, at the index in
+the results as if it were ordered at i-th index on the stack before filtering (with ties
+broken by the values). This implies `this.into` will never override another stack value
+even if their indices collide. Finally, the value will only actually appear at `i` if the
+callback accepts or moves enough values before `i`.
+
+
+.parFilter(cb)
+-----------
+.parFilter(limit, cb)
+------------------
+
+Executes the callback `cb(x, idx)` against each element on the stack, but **does not**
+wait for it to yield before moving on to the next element. If the callback returns an
+error or a falsey value, the element will not be included in the resulting stack.
+
+Any errors from the callback are consumed and **do not** propagate.
+
+Calls to `this.into(i)` will place the value, if accepted by the callback, at the index in
+the results as if it were ordered at i-th index on the stack before filtering (with ties
+broken by the values). This implies `this.into` will never override another stack value
+even if their indices collide. Finally, the value will only actually appear at `i` if the
+callback accepts or moves enough values before `i`.
+
+Optionally, if limit is supplied to `parEach`, at most `limit` callbacks will be
+active at a time.
+
+
+.do(cb)
+-------
+Create a new nested context. `cb`'s first argument is the previous context, and `this`
+is the nested `Seq` object.
+
+
+.flatten(fully=true)
+--------------------
+
+Recursively flatten all the arrays in the stack. Set `fully=false` to flatten
+only one level.
+
+
+.unflatten()
+------------
+
+Turn the contents of the stack into a single array item. You can think of it
+as the inverse of `flatten(false)`.
+
+
+.extend([x,y...])
+-----------------
+
+Like `push`, but takes an array. This is like python's `[].extend()`.
+
+
+.set(xs)
+--------
+
+Set the stack to a new array. This assigns the reference, it does not copy.
+
+
+.empty()
+--------
+
+Set the stack to [].
+
+
+.push(x,y...), .pop(), .shift(), .unshift(x), .splice(...), reverse()
+---------------------------------------------------------------------
+.map(...), .filter(...), .reduce(...)
+-------------------------------------
+
+Executes an array operation on the stack.
+
+The methods `map`, `filter`, and `reduce` are also proxies to their Array counterparts:
+they have identical signatures to the Array methods, operate synchronously on the context
+stack, and do not pass a Context object (unlike `seqMap` and `parMap`).
+
+The result of the transformation is assigned to the context stack; in the case of `reduce`,
+if you do not return an array, the value will be wrapped in one.
+
+````javascript
+Seq([1, 2, 3])
+    .reduce(function(sum, x){ return sum + x; }, 0)
+    .seq(function(sum){
+        console.log('sum: %s', sum);
+        // sum: 6
+        console.log('stack is Array?', Array.isArray(this.stack));
+        // stack is Array: true
+        console.log('stack:', this.stack);
+        // stack: [6]
+    })
+;
+````
+
+
+
+
+Explicit Parameters
+-------------------
+
+For environments like coffee-script or nested logic where threading `this` is
+bothersome, you can use:
+
+* seq_
+* par_
+* forEach_
+* seqEach_
+* parEach_
+* seqMap_
+* parMap_
+
+which work exactly like their un-underscored counterparts except for the first
+parameter to the supplied callback is set to the context, `this`.
+
+
+
+Context Object
+==============
+
+Each callback gets executed with its `this` set to a function in order to yield
+results, error values, and control. The function also has these useful fields:
+
+this.stack
+----------
+
+The execution stack.
+
+this.stack_
+-----------
+
+The previous stack value, mostly used internally for hackish purposes.
+
+this.vars
+---------
+
+A hash of key/values populated with `par(key, ...)`, `seq(key, ...)` and
+`this.into(key)`.
+
+this.into(key)
+--------------
+
+Instead of sending values to the stack, sets a key and returns `this`.
+Use `this.into(key)` interchangeably with `this` for yielding keyed results.
+`into` overrides the optional key set by `par(key, ...)` and `seq(key, ...)`.
+
+this.ok
+-------
+
+Set the `err` to null. Equivalent to `this.bind(this, null)`.
+
+this.args
+---------
+
+`this.args` is like `this.stack`, but it contains all the arguments to `this()`
+past the error value, not just the first. `this.args` is an array with the same
+indices as `this.stack` but also stores keyed values for the last sequential
+operation. Each element in `this.array` is set to `[].slice.call(arguments, 1)`
+from inside `this()`.
+
+this.error
+----------
+
+This is used for error propagation. You probably shouldn't mess with it.
+
+
+
+Installation
+============
+
+With [npm](http://github.com/isaacs/npm), just do:
+
+    npm install seq
+
+or clone this project on github:
+
+    git clone http://github.com/substack/node-seq.git
+
+To run the tests with [expresso](http://github.com/visionmedia/expresso),
+just do:
+
+    expresso
+
+
+
+Dependencies
+------------
+
+This module uses [chainsaw](http://github.com/substack/node-chainsaw)
+When you `npm install seq` this dependency will automatically be installed.
+
+
diff --git a/examples/join.js b/examples/join.js
new file mode 100644
index 0000000..cc05c4f
--- /dev/null
+++ b/examples/join.js
@@ -0,0 +1,18 @@
+var Seq = require('seq');
+Seq()
+    .par(function () {
+        var that = this;
+        setTimeout(function () { that(null, 'a') }, 300);
+    })
+    .par(function () {
+        var that = this;
+        setTimeout(function () { that(null, 'b') }, 200);
+    })
+    .par(function () {
+        var that = this;
+        setTimeout(function () { that(null, 'c') }, 100);
+    })
+    .seq(function (a, b, c) {
+        console.dir([ a, b, c ])
+    })
+;
diff --git a/examples/parseq.coffee b/examples/parseq.coffee
new file mode 100644
index 0000000..d4ca0ab
--- /dev/null
+++ b/examples/parseq.coffee
@@ -0,0 +1,12 @@
+fs = require 'fs'
+exec = require('child_process').exec
+Seq = require 'seq'
+
+Seq()
+    .seq_((next) -> exec 'whoami', next)
+    .par_((next, who) -> exec('groups ' + who, next))
+    .par_((next, who) -> fs.readFile(__filename, 'utf8', next))
+    .seq_((next, groups, src) ->
+        console.log('Groups: ' + groups.trim())
+        console.log('This file has ' + src.length + ' bytes')
+    )
diff --git a/examples/parseq.js b/examples/parseq.js
new file mode 100644
index 0000000..2cdcf21
--- /dev/null
+++ b/examples/parseq.js
@@ -0,0 +1,19 @@
+var fs = require('fs');
+var exec = require('child_process').exec;
+
+var Seq = require('seq');
+Seq()
+    .seq(function () {
+        exec('whoami', this)
+    })
+    .par(function (who) {
+        exec('groups ' + who, this);
+    })
+    .par(function (who) {
+        fs.readFile(__filename, 'utf8', this);
+    })
+    .seq(function (groups, src) {
+        console.log('Groups: ' + groups.trim());
+        console.log('This file has ' + src.length + ' bytes');
+    })
+;
diff --git a/examples/stat_all.coffee b/examples/stat_all.coffee
new file mode 100644
index 0000000..83ec0b2
--- /dev/null
+++ b/examples/stat_all.coffee
@@ -0,0 +1,16 @@
+fs = require 'fs'
+Hash = require 'hashish'
+Seq = require 'seq'
+
+Seq()
+    .seq_((next) ->
+        fs.readdir(__dirname, next)
+    )
+    .flatten()
+    .parEach_((next, file) ->
+        fs.stat(__dirname + '/' + file, next.into(file))
+    )
+    .seq_((next) ->
+        sizes = Hash.map(next.vars, (s) -> s.size)
+        console.dir sizes
+    )
diff --git a/examples/stat_all.js b/examples/stat_all.js
new file mode 100644
index 0000000..b9962eb
--- /dev/null
+++ b/examples/stat_all.js
@@ -0,0 +1,17 @@
+var fs = require('fs');
+var Hash = require('hashish');
+var Seq = require('seq');
+
+Seq()
+    .seq(function () {
+        fs.readdir(__dirname, this);
+    })
+    .flatten()
+    .parEach(function (file) {
+        fs.stat(__dirname + '/' + file, this.into(file));
+    })
+    .seq(function () {
+        var sizes = Hash.map(this.vars, function (s) { return s.size })
+        console.dir(sizes);
+    })
+;
diff --git a/index.js b/index.js
new file mode 100755
index 0000000..4e0888b
--- /dev/null
+++ b/index.js
@@ -0,0 +1,520 @@
+var EventEmitter = require('events').EventEmitter;
+var Hash = require('hashish');
+var Chainsaw = require('chainsaw');
+
+module.exports = Seq;
+function Seq (xs) {
+    if (xs && !Array.isArray(xs) || arguments.length > 1) {
+        throw new Error('Optional argument to Seq() is exactly one Array');
+    }
+    
+    var ch = Chainsaw(function (saw) {
+        builder.call(this, saw, xs || []);
+    });
+    
+    process.nextTick(function () {
+        ch['catch'](function (err) {
+            console.error(err.stack ? err.stack : err)
+        });
+    });
+    return ch;
+}
+
+Seq.ap = Seq; // for compatability with versions <0.3
+
+function builder (saw, xs) {
+    var context = {
+        vars : {},
+        args : {},
+        stack : xs,
+        error : null
+    };
+    context.stack_ = context.stack;
+    
+    function action (step, key, f, g) {
+        var cb = function (err) {
+            var args = [].slice.call(arguments, 1);
+            if (err) {
+                context.error = { message : err, key : key };
+                saw.jump(lastPar);
+                saw.down('catch');
+                g();
+            }
+            else {
+                if (typeof key == 'number') {
+                    context.stack_[key] = args[0];
+                    context.args[key] = args;
+                }
+                else {
+                    context.stack_.push.apply(context.stack_, args);
+                    if (key !== undefined) {
+                        context.vars[key] = args[0];
+                        context.args[key] = args;
+                    }
+                }
+                if (g) g(args, key);
+            }
+        };
+        Hash(context).forEach(function (v,k) { cb[k] = v });
+        
+        cb.into = function (k) {
+            key = k;
+            return cb;
+        };
+        
+        cb.next = function (err, xs) {
+            context.stack_.push.apply(context.stack_, xs);
+            cb.apply(cb, [err].concat(context.stack));
+        };
+        
+        cb.pass = function (err) {
+            cb.apply(cb, [err].concat(context.stack));
+        };
+        
+        cb.ok = cb.bind(cb, null);
+        
+        f.apply(cb, context.stack);
+    }
+    
+    var running = 0;
+    var errors = 0;
+    
+    this.seq = function (key, cb) {
+        var bound = [].slice.call(arguments, 2);
+        
+        if (typeof key === 'function') {
+            if (arguments.length > 1) bound.unshift(cb);
+            cb = key;
+            key = undefined;
+        }
+        
+        if (context.error) saw.next()
+        else if (running === 0) {
+            action(saw.step, key,
+                function () {
+                    context.stack_ = [];
+                    var args = [].slice.call(arguments);
+                    args.unshift.apply(args, bound.map(function (arg) {
+                        return arg === Seq ? this : arg
+                    }, this));
+                    
+                    cb.apply(this, args);
+                }, function () {
+                    context.stack = context.stack_;
+                    saw.next()
+                }
+            );
+        }
+    };
+    
+    var lastPar = null;
+    this.par = function (key, cb) {
+        lastPar = saw.step;
+        
+        if (running == 0) {
+            // empty the active stack for the first par() in a chain
+            context.stack_ = [];
+        }
+        
+        var bound = [].slice.call(arguments, 2);
+        if (typeof key === 'function') {
+            if (arguments.length > 1) bound.unshift(cb);
+            cb = key;
+            key = context.stack_.length;
+            context.stack_.push(null);
+        }
+        var cb_ = function () {
+            var args = [].slice.call(arguments);
+            args.unshift.apply(args, bound.map(function (arg) {
+                return arg === Seq ? this : arg
+            }, this));
+            
+            cb.apply(this, args);
+        };
+        
+        running ++;
+        
+        var step = saw.step;
+        process.nextTick(function () {
+            action(step, key, cb_, function (args) {
+                if (!args) errors ++;
+                
+                running --;
+                if (running == 0) {
+                    context.stack = context.stack_.slice();
+                    saw.step = lastPar;
+                    if (errors > 0) saw.down('catch');
+                    errors = 0;
+                    saw.next();
+                }
+            });
+        });
+        saw.next();
+    };
+    
+    [ 'seq', 'par' ].forEach(function (name) {
+        this[name + '_'] = function (key) {
+            var args = [].slice.call(arguments);
+            
+            var cb = typeof key === 'function'
+                ? args[0] : args[1];
+            
+            var fn = function () {
+                var argv = [].slice.call(arguments);
+                argv.unshift(this);
+                cb.apply(this, argv);
+            };
+            
+            if (typeof key === 'function') {
+                args[0] = fn;
+            }
+            else {
+                args[1] = fn;
+            }
+            
+            this[name].apply(this, args);
+        };
+    }, this);
+    
+    this['catch'] = function (cb) {
+        if (context.error) {
+            cb.call(context, context.error.message, context.error.key);
+            context.error = null;
+        }
+        saw.next();
+    };
+    
+    this.forEach = function (cb) {
+        this.seq(function () {
+            context.stack_ = context.stack.slice();
+            var end = context.stack.length;
+            
+            if (end === 0) this(null)
+            else context.stack.forEach(function (x, i) {
+                action(saw.step, i, function () {
+                    cb.call(this, x, i);
+                    if (i == end - 1) saw.next();
+                });
+            });
+        });
+    };
+    
+    this.seqEach = function (cb) {
+        this.seq(function () {
+            context.stack_ = context.stack.slice();
+            var xs = context.stack.slice();
+            if (xs.length === 0) this(null);
+            else (function next (i) {
+                action(
+                    saw.step, i,
+                    function () { cb.call(this, xs[i], i) },
+                    function (args) {
+                        if (!args || i === xs.length - 1) saw.next();
+                        else next(i + 1);
+                    }
+                );
+            }).bind(this)(0);
+        });
+    };
+    
+    this.parEach = function (limit, cb) {
+        var xs = context.stack.slice();
+        if (cb === undefined) { cb = limit; limit = xs.length }
+        context.stack_ = [];
+        
+        var active = 0;
+        var finished = 0;
+        var queue = [];
+        
+        if (xs.length === 0) saw.next()
+        else xs.forEach(function call (x, i) {
+            if (active >= limit) {
+                queue.push(call.bind(this, x, i));
+            }
+            else {
+                active ++;
+                action(saw.step, i,
+                    function () {
+                        cb.call(this, x, i);
+                    },
+                    function () {
+                        active --;
+                        finished ++;
+                        if (queue.length > 0) queue.shift()();
+                        else if (finished === xs.length) {
+                            saw.next();
+                        }
+                    }
+                );
+            }
+        });
+    };
+    
+    this.parMap = function (limit, cb) {
+        var res = [];
+        var len = context.stack.length;
+        if (cb === undefined) { cb = limit; limit = len }
+        var res = [];
+        
+        Seq()
+            .extend(context.stack)
+            .parEach(limit, function (x, i) {
+                var self = this;
+                
+                var next = function () {
+                    res[i] = arguments[1];
+                    self.apply(self, arguments);
+                };
+                
+                next.stack = self.stack;
+                next.stack_ = self.stack_;
+                next.vars = self.vars;
+                next.args = self.args;
+                next.error = self.error;
+                
+                next.into = function (key) {
+                    return function () {
+                        res[key] = arguments[1];
+                        self.apply(self, arguments);
+                    };
+                };
+                
+                next.ok = function () {
+                    var args = [].slice.call(arguments);
+                    args.unshift(null);
+                    return next.apply(next, args);
+                };
+                
+                cb.apply(next, arguments);
+            })
+            .seq(function () {
+                context.stack = res;
+                saw.next();
+            })
+        ;
+    };
+    
+    this.seqMap = function (cb) {
+        var res = [];
+        var lastIdx = context.stack.length - 1;
+        
+        this.seqEach(function (x, i) {
+            var self = this;
+            
+            var next = function () {
+                res[i] = arguments[1];
+                if (i === lastIdx)
+                    context.stack = res;
+                self.apply(self, arguments);
+            };
+            
+            next.stack = self.stack;
+            next.stack_ = self.stack_;
+            next.vars = self.vars;
+            next.args = self.args;
+            next.error = self.error;
+            
+            next.into = function (key) {
+                return function () {
+                    res[key] = arguments[1];
+                    if (i === lastIdx)
+                        context.stack = res;
+                    self.apply(self, arguments);
+                };
+            };
+            
+            next.ok = function () {
+                var args = [].slice.call(arguments);
+                args.unshift(null);
+                return next.apply(next, args);
+            };
+            
+            cb.apply(next, arguments);
+        });
+    };
+    
+    /**
+     * Consumes any errors that occur in `cb`. Calls to `this.into(i)` will place
+     * that value, if accepted by the filter, at the index in the results as
+     * if it were the i-th index before filtering. (This means it will never 
+     * override another value, and will only actually appear at i if the filter
+     * accepts all values before i.)
+     */
+    this.parFilter = function (limit, cb) {
+        var res = [];
+        var len = context.stack.length;
+        if (cb === undefined) { cb = limit; limit = len }
+        var res = [];
+        
+        Seq()
+            .extend(context.stack)
+            .parEach(limit, function (x, i) {
+                var self = this;
+                
+                var next = function (err, ok) {
+                    if (!err && ok)
+                        res.push([i, x]);
+                    arguments[0] = null; // discard errors
+                    self.apply(self, arguments);
+                };
+                
+                next.stack = self.stack;
+                next.stack_ = self.stack_;
+                next.vars = self.vars;
+                next.args = self.args;
+                next.error = self.error;
+                
+                next.into = function (key) {
+                    return function (err, ok) {
+                        if (!err && ok)
+                            res.push([key, x]);
+                        arguments[0] = null; // discard errors
+                        self.apply(self, arguments);
+                    };
+                };
+                
+                next.ok = function () {
+                    var args = [].slice.call(arguments);
+                    args.unshift(null);
+                    return next.apply(next, args);
+                };
+                
+                cb.apply(next, arguments);
+            })
+            .seq(function () {
+                context.stack = res.sort().map(function(pair){ return pair[1]; });
+                saw.next();
+            })
+        ;
+    };
+    
+    /**
+     * Consumes any errors that occur in `cb`. Calls to `this.into(i)` will place
+     * that value, if accepted by the filter, at the index in the results as
+     * if it were the i-th index before filtering. (This means it will never 
+     * override another value, and will only actually appear at i if the filter
+     * accepts all values before i.)
+     */
+    this.seqFilter = function (cb) {
+        var res = [];
+        var lastIdx = context.stack.length - 1;
+        
+        this.seqEach(function (x, i) {
+            var self = this;
+            
+            var next = function (err, ok) {
+                if (!err && ok)
+                    res.push([i, x]);
+                if (i === lastIdx)
+                    context.stack = res.sort().map(function(pair){ return pair[1]; });
+                arguments[0] = null; // discard errors
+                self.apply(self, arguments);
+            };
+            
+            next.stack = self.stack;
+            next.stack_ = self.stack_;
+            next.vars = self.vars;
+            next.args = self.args;
+            next.error = self.error;
+            
+            next.into = function (key) {
+                return function (err, ok) {
+                    if (!err && ok)
+                        res.push([key, x]);
+                    if (i === lastIdx)
+                        context.stack = res.sort().map(function(pair){ return pair[1]; });
+                    arguments[0] = null; // discard errors
+                    self.apply(self, arguments);
+                };
+            };
+            
+            next.ok = function () {
+                var args = [].slice.call(arguments);
+                args.unshift(null);
+                return next.apply(next, args);
+            };
+            
+            cb.apply(next, arguments);
+        });
+    };
+    
+    [ 'forEach', 'seqEach', 'parEach', 'seqMap', 'parMap', 'seqFilter', 'parFilter' ]
+        .forEach(function (name) {
+            this[name + '_'] = function (cb) {
+                this[name].call(this, function () {
+                    var args = [].slice.call(arguments);
+                    args.unshift(this);
+                    cb.apply(this, args);
+                });
+            };
+        }, this)
+    ;
+    
+    ['push','pop','shift','unshift','splice','reverse']
+        .forEach(function (name) {
+            this[name] = function () {
+                context.stack[name].apply(
+                    context.stack,
+                    [].slice.call(arguments)
+                );
+                saw.next();
+                return this;
+            };
+        }, this)
+    ;
+    
+    [ 'map', 'filter', 'reduce' ]
+        .forEach(function (name) {
+            this[name] = function () {
+                var res = context.stack[name].apply(
+                    context.stack,
+                    [].slice.call(arguments)
+                );
+                // stack must be an array, or bad things happen
+                context.stack = (Array.isArray(res) ? res : [res]);
+                saw.next();
+                return this;
+            };
+        }, this)
+    ;
+    
+    this.extend = function (xs) {
+        if (!Array.isArray(xs)) {
+            throw new Error('argument to .extend() is not an Array');
+        }
+        context.stack.push.apply(context.stack, xs);
+        saw.next();
+    };
+    
+    this.flatten = function (pancake) {
+        var xs = [];
+        // should we fully flatten this array? (default: true)
+        if (pancake === undefined) { pancake = true; }
+        context.stack.forEach(function f (x) {
+            if (Array.isArray(x) && pancake) x.forEach(f);
+            else if (Array.isArray(x)) xs = xs.concat(x);
+            else xs.push(x);
+        });
+        context.stack = xs;
+        saw.next();
+    };
+    
+    this.unflatten = function () {
+        context.stack = [context.stack];
+        saw.next();
+    };
+    
+    this.empty = function () {
+        context.stack = [];
+        saw.next();
+    };
+    
+    this.set = function (stack) {
+        context.stack = stack;
+        saw.next();
+    };
+    
+    this['do'] = function (cb) {
+        saw.nest(cb, context);
+    };
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0c971d6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,33 @@
+{
+    "name" : "seq",
+    "version" : "0.3.5",
+    "description" : "Chainable asynchronous flow control with sequential and parallel primitives and pipeline-style error handling",
+    "main" : "./index.js",
+    "repository" : {
+        "type" : "git",
+        "url" : "http://github.com/substack/node-seq.git"
+    },
+    "dependencies" : {
+        "chainsaw" : ">=0.0.7 <0.1",
+        "hashish" : ">=0.0.2 <0.1"
+    },
+    "devDependencies" : {
+        "expresso" : ">=0.7.x"
+    },
+    "script" : {
+        "test" : "expresso"
+    },
+    "keywords" : [
+        "flow-control", "flow", "control", "async", "asynchronous", "chain",
+        "pipeline", "sequence", "sequential", "parallel", "error"
+    ],
+    "author" : {
+        "name" : "James Halliday",
+        "email" : "mail at substack.net",
+        "url" : "http://substack.net"
+    },
+    "license" : "MIT/X11",
+    "engine" : {
+        "node" : ">=0.4.0"
+    }
+}
diff --git a/test/readdir.js b/test/readdir.js
new file mode 100644
index 0000000..fe1a38b
--- /dev/null
+++ b/test/readdir.js
@@ -0,0 +1,35 @@
+var assert = require('assert');
+var Seq = require('seq');
+var fs = require('fs');
+
+exports.readdir = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 500);
+    
+    Seq()
+        .seq(fs.readdir, __dirname, Seq)
+        .seq(function (files) {
+            clearTimeout(to);
+            assert.ok(files.length >= 2);
+        })
+        .catch(assert.fail)
+    ;
+}; 
+
+exports.readdirs = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 500);
+    
+    Seq()
+        .par(fs.readdir, __dirname, Seq)
+        .par(fs.readdir, __dirname + '/../examples', Seq)
+        .seq(function (tests, examples) {
+            clearTimeout(to);
+            assert.ok(tests.length >= 2);
+            assert.ok(examples.length >= 2);
+        })
+        .catch(assert.fail)
+    ;
+}; 
diff --git a/test/seq.js b/test/seq.js
new file mode 100644
index 0000000..2e34aec
--- /dev/null
+++ b/test/seq.js
@@ -0,0 +1,946 @@
+var Seq = require('seq');
+var assert = require('assert');
+
+exports.seq = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 100);
+    
+    Seq([0])
+        .seq('pow', function (n) {
+            this(null, 1);
+        })
+        .seq(function (n) {
+            assert.eql(n, 1);
+            assert.eql(n, this.vars.pow);
+            var seq = this;
+            setTimeout(function () { seq(null, 2) }, 25);
+            assert.eql(this.stack, [n]);
+        })
+        .seq(function (n) {
+            assert.eql(n, 2);
+            assert.eql(this.stack, [n]);
+            this(null, 5, 6, 7);
+        })
+        .seq(function (x, y, z) {
+            clearTimeout(to);
+            assert.eql([x,y,z], [5,6,7]);
+        })
+    ;
+};
+
+exports.into = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 10);
+    var calls = 0;
+    
+    Seq([3,4,5])
+        .seq(function () {
+            this.into('w')(null, 5);
+        })
+        .seq(function (w) {
+            clearTimeout(to);
+            assert.eql(w, this.vars.w);
+            assert.eql(arguments.length, 1);
+            assert.eql(w, 5);
+        })
+    ;
+};
+
+exports.catchSeq = function () {
+    var to = setTimeout(function () {
+        assert.fail('never caught the error');
+    }, 100);
+    
+    var tf = setTimeout(function () {
+        assert.fail('final action never executed');
+    }, 100);
+    
+    var calls = {};
+    Seq([1])
+        .seq(function (n) {
+            assert.eql(n, 1);
+            calls.before = true;
+            this('pow!');
+            calls.after = true;
+        })
+        .seq(function (n) {
+            calls.next = true;
+            assert.fail('should have skipped this');
+        })
+        .catch(function (err) {
+            assert.eql(err, 'pow!');
+            assert.ok(calls.before);
+            assert.ok(!calls.after);
+            assert.ok(!calls.next);
+            clearTimeout(to);
+        })
+        .do(function () {
+            //assert.ok(calls.after);
+            clearTimeout(tf);
+        })
+    ;
+};
+
+exports.par = function () {
+    var to = setTimeout(function () {
+        assert.fail('seq never fired');
+    }, 1000);
+    
+    Seq()
+        .seq(function () {
+            this(null, 'mew');
+        })
+        .par(function () {
+            var seq = this;
+            setTimeout(function () { seq(null, 'x') }, 50);
+        })
+        .par(function () {
+            var seq = this;
+            setTimeout(function () { seq(null, 'y') }, 25);
+        })
+        .par('z', function () {
+            this(null, 42);
+        })
+        .seq(function (x, y, z) {
+            clearTimeout(to);
+            assert.eql(x, 'x');
+            assert.eql(y, 'y');
+            assert.eql(z, 42);
+            assert.eql(this.args, { 0 : ['x'], 1 : ['y'], z : [42] });
+            assert.eql(this.stack, [ 'x', 'y', 42 ]);
+            assert.eql(this.vars, { z : 42 });
+        })
+    ;
+};
+
+exports.catchPar = function () {
+    var done = false, caught = false;
+    var tc = setTimeout(function () {
+        assert.fail('error not caught');
+    }, 1000);
+    
+    Seq()
+        .par('one', function () {
+            setTimeout(this.bind({}, 'rawr'), 25);
+        })
+        .par('two', function () {
+            setTimeout(this.bind({}, null, 'y'), 50);
+        })
+        .seq(function (x, y) {
+            assert.fail('seq fired with error above');
+        })
+        .catch(function (err, key) {
+            clearTimeout(tc);
+            assert.eql(err, 'rawr');
+            assert.eql(key, 'one');
+        })
+    ;
+};
+
+exports.catchParWithoutSeq = function () {
+    var done = false, caught = false;
+    var tc = setTimeout(function () {
+        assert.fail('error not caught');
+    }, 5000);
+    
+    Seq()
+        .par('one', function () {
+            setTimeout(this.bind({}, 'rawr'), 25);
+        })
+        .par('two', function () {
+            setTimeout(this.bind({}, null, 'y'), 50);
+        })
+        .catch(function (err, key) {
+            clearTimeout(tc);
+            assert.eql(err, 'rawr');
+            assert.eql(key, 'one');
+        })
+    ;    
+}
+
+exports.catchParMultipleErrors = function() {
+    var caught={};
+    var to = setTimeout(function() {
+        assert.fail('Never finished');
+    }, 1000);
+    var times = 0;
+    
+    Seq()
+        .par('one', function() {
+            setTimeout(this.bind({}, 'rawr1'), 25);
+        })
+        .par('two', function() {
+            setTimeout(this.bind({}, 'rawr2'), 50);
+        })
+        .catch(function(err,key) {
+            caught[key] = err;
+        })
+        .seq(function() {
+            clearTimeout(to);
+            times ++;
+            assert.eql(times, 1);
+            assert.eql(caught, { one:'rawr1', two:'rawr2' });
+        })
+    ;
+};
+
+exports.catchParThenSeq = function () {
+    var tc = setTimeout(function () {
+        assert.fail('error not caught');
+    }, 1000);
+    var tf = setTimeout(function () {
+        assert.fail('final seq not run');
+    }, 500);
+    var times = 0;
+    var errs = [
+        { key : 'one', msg : 'rawr' },
+        { key : 'four', msg : 'pow' },
+    ];
+    
+    Seq()
+        .par('one', function () {
+            setTimeout(this.bind({}, 'rawr'), 25);
+        })
+        .par('two', function () {
+            setTimeout(this.bind({}, null, 'y'), 50);
+        })
+        .par('three', function () {
+            setTimeout(this.bind({}, null, 'z'), 30);
+        })
+        .par('four', function () {
+            setTimeout(this.bind({}, 'pow'), 45);
+        })
+        .seq(function (x, y) {
+            assert.fail('seq fired with error above');
+        })
+        .catch(function (err, key) {
+            clearTimeout(tc);
+            var e = errs.shift();
+            assert.eql(err, e.msg);
+            assert.eql(key, e.key);
+        })
+        .seq(function () {
+            clearTimeout(tf);
+            times ++;
+            assert.eql(times, 1);
+        })
+    ;    
+}
+
+exports.forEach = function () {
+    var to = setTimeout(function () {
+        assert.fail('seq never fired after forEach');
+    }, 25);
+    
+    var count = 0;
+    Seq([1,2,3])
+        .push(4)
+        .forEach(function (x, i) {
+            assert.eql(x - 1, i);
+            count ++;
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(count, 4);
+        })
+    ;
+};
+
+exports.seqEach = function () {
+    var to = setTimeout(function () {
+        assert.fail('seqEach never finished');
+    }, 25);
+    
+    var count = 0;
+    var ii = 0;
+    Seq([1,2,3])
+        .seqEach(function (x, i) {
+            assert.eql(i, ii++);
+            assert.eql(x, [1,2,3][i]);
+            count ++;
+            this(null);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(count, 3);
+        })
+    ;
+};
+
+exports.seqEachCatch = function () {
+    var to = setTimeout(function () {
+        assert.fail('never caught the error');
+    }, 25);
+    var tf = setTimeout(function () {
+        assert.fail('never resumed afterwards');
+    }, 25);
+    
+    var meows = [];
+    
+    var values = [];
+    Seq([1,2,3,4])
+        .seqEach(function (x, i) {
+            values.push([i,x]);
+            assert.eql(x - 1, i);
+            if (i >= 2) this('meow ' + i)
+            else this(null, x * 10);
+        })
+        .seq(function (xs) {
+            assert.fail('should fail before this action');
+        })
+        .catch(function (err) {
+            clearTimeout(to);
+            meows.push(err);
+            assert.eql(err, 'meow 2');
+            assert.eql(values, [[0,1],[1,2],[2,3]]);
+        })
+        .seq(function () {
+            clearTimeout(tf);
+        })
+    ;
+};
+
+exports.parEach = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 100);
+    
+    var values = [];
+    Seq([1,2,3,4])
+        .parEach(function (x, i) {
+            values.push([i,x]);
+            setTimeout(this.bind({}, null), 20);
+        })
+        .seq(function () {
+            assert.deepEqual(this.stack, [1,2,3,4])
+            assert.deepEqual(values, [[0,1],[1,2],[2,3],[3,4]]);
+            clearTimeout(to);
+        })
+    ;
+};
+
+exports.parEachVars = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 1000);
+    var values = [];
+    
+    Seq()
+        .seq('abc', function () {
+            this(null, 'a', 'b', 'c');
+        })
+        .parEach(function (x) {
+            values.push(x);
+            setTimeout(this.bind(this, null), Math.floor(Math.random() * 50));
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(values, ['a','b','c']);
+            assert.eql(this.stack, ['a','b','c']);
+            assert.eql(this.vars.abc, 'a');
+        })
+    ;
+};
+
+exports.parEachInto = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 100);
+    
+    Seq([1,2,3,4])
+        .parEach(function (x, i) {
+            setTimeout((function () {
+                this.into('abcd'.charAt(i))(null, x);
+            }).bind(this), 20);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.deepEqual(this.stack, [1,2,3,4])
+            assert.deepEqual(this.vars, { a : 1, b : 2, c : 3, d : 4 });
+        })
+    ;
+};
+
+exports.parEachCatch = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 100);
+    
+    var values = [];
+    Seq([1,2,3,4])
+        .parEach(function (x, i) {
+            values.push([i,x]);
+            setTimeout(this.bind({}, 'zing'), 10);
+        })
+        .seq(function () {
+            assert.fail('should have errored before this point')
+        })
+        .catch(function (err) {
+            clearTimeout(to);
+            assert.eql(err, 'zing');
+            assert.deepEqual(values, [[0,1],[1,2],[2,3],[3,4]]);
+        })
+    ;
+};
+
+exports.parEachLimited = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var running = 0;
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .parEach(3, function (x, i) {
+            running ++;
+            
+            assert.ok(running <= 3);
+            
+            values.push([i,x]);
+            setTimeout((function () {
+                running --;
+                this(null);
+            }).bind(this), 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(values,
+                [[0,1],[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8],[8,9],[9,10]]
+            );
+        })
+    ;
+};
+
+exports.parMap = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var running = 0;
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .parMap(2, function (x, i) {
+            running ++;
+            
+            assert.ok(running <= 2);
+            
+            setTimeout((function () {
+                running --;
+                this(null, x * 10);
+            }).bind(this), Math.floor(Math.random() * 100));
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(this.stack, [10,20,30,40,50,60,70,80,90,100]);
+            assert.eql(this.stack, [].slice.call(arguments));
+        })
+    ;
+};
+
+exports.parMapFast = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .parMap(function (x, i) {
+            this(null, x * 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(this.stack, [10,20,30,40,50,60,70,80,90,100]);
+            assert.eql(this.stack, [].slice.call(arguments));
+        })
+    ;
+};
+
+exports.parMapInto = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .parMap(function (x, i) {
+            this.into(9 - i)(null, x * 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(this.stack, [100, 90, 80, 70, 60, 50, 40, 30, 20, 10]);
+            assert.eql(this.stack, [].slice.call(arguments));
+        })
+    ;
+};
+
+exports.seqMap = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var running = 0;
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .seqMap(function (x, i) {
+            running ++;
+            
+            assert.eql(running, 1);
+            
+            setTimeout((function () {
+                running --;
+                this(null, x * 10);
+            }).bind(this), 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(this.stack, [10,20,30,40,50,60,70,80,90,100]);
+        })
+    ;
+};
+
+
+exports.seqMapInto = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var running = 0;
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .seqMap(function (x, i) {
+            running ++;
+            
+            assert.eql(running, 1);
+            
+            setTimeout((function () {
+                running --;
+                this.into(9 - i)(null, x * 10);
+            }).bind(this), 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(this.stack, [100, 90, 80, 70, 60, 50, 40, 30, 20, 10]);
+        })
+    ;
+};
+
+exports.parFilter = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var running = 0;
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .parFilter(2, function (x, i) {
+            running ++;
+            
+            assert.ok(running <= 2);
+            
+            setTimeout((function () {
+                running --;
+                this(null, x % 2 === 0);
+            }).bind(this), Math.floor(Math.random() * 100));
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(this.stack, [2,4,6,8,10]);
+            assert.eql(this.stack, [].slice.call(arguments));
+        })
+    ;
+};
+
+exports.seqFilter = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var running = 0;
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .seqFilter(function (x, i) {
+            running ++;
+            
+            assert.eql(running, 1);
+            
+            setTimeout((function () {
+                running --;
+                this(null, x % 2 === 0);
+            }).bind(this), 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(this.stack, [2,4,6,8,10]);
+        })
+    ;
+};
+
+exports.parFilterInto = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var running = 0;
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .parFilter(2, function (x, i) {
+            running ++;
+            
+            assert.ok(running <= 2);
+            
+            setTimeout((function () {
+                running --;
+                this.into(x % 3)(null, x % 2 === 0);
+            }).bind(this), Math.floor(Math.random() * 100));
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(this.stack, [ 6, 10, 4, 2, 8 ]);
+            assert.eql(this.stack, [].slice.call(arguments));
+        })
+    ;
+};
+
+exports.seqFilterInto = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 500);
+    
+    var running = 0;
+    var values = [];
+    Seq([1,2,3,4,5,6,7,8,9,10])
+        .seqFilter(function (x, i) {
+            running ++;
+            
+            assert.eql(running, 1);
+            
+            setTimeout((function () {
+                running --;
+                this.into(x % 3)(null, x % 2 === 0);
+            }).bind(this), 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(this.stack, [ 6, 10, 4, 2, 8 ]);
+        })
+    ;
+};
+
+exports.stack = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 100);
+    
+    Seq([4,5,6])
+        .seq(function (x, y, z) {
+            assert.eql(arguments.length, 3);
+            assert.eql([x,y,z], [4,5,6]);
+            assert.eql(this.stack, [4,5,6]);
+            this(null);
+        })
+        .set([3,4])
+        .seq(function (x, y) {
+            assert.eql(arguments.length, 2);
+            assert.eql([x,y], [3,4]);
+            assert.eql(this.stack, [3,4]);
+            this(null);
+        })
+        .empty()
+        .seq(function () {
+            assert.eql(arguments.length, 0);
+            assert.eql(this.stack, []);
+            this.next(null, ['a']);
+        })
+        .extend(['b','c'])
+        .seq(function (a, b, c) {
+            assert.eql(arguments.length, 3);
+            assert.eql([a,b,c], ['a','b','c']);
+            assert.eql(this.stack, ['a','b','c']);
+            this.pass(null);
+        })
+        .pop()
+        .push('c', 'd', 'e')
+        .seq(function (a, b, c, d, e) {
+            assert.eql(arguments.length, 5);
+            assert.eql([a,b,c,d,e], ['a','b','c','d','e']);
+            assert.eql(this.stack, ['a','b','c','d','e']);
+            this.pass(null);
+        })
+        .shift()
+        .shift()
+        .seq(function (c, d, e) {
+            assert.eql(arguments.length, 3);
+            assert.eql([c,d,e], ['c','d','e']);
+            assert.eql(this.stack, ['c','d','e']);
+            this.pass(null);
+        })
+        .set([['a',['b']],['c','d',['e']]])
+        .flatten(false) // only flatten one level
+        .seq(function (a, b, c, d, e) {
+            assert.eql(arguments.length, 5);
+            assert.eql([a,b,c,d,e], ['a',['b'],'c','d',['e']]);
+            assert.eql(this.stack,  ['a',['b'],'c','d',['e']]);
+            this.pass(null);
+        })
+        .set([['a','b'],['c','d',['e']]])
+        .flatten()
+        .seq(function (a, b, c, d, e) {
+            assert.eql(arguments.length, 5);
+            assert.eql([a,b,c,d,e], ['a','b','c','d','e']);
+            assert.eql(this.stack, ['a','b','c','d','e']);
+            this.pass(null);
+        })
+        .splice(2, 2)
+        .seq(function (a, b, e) {
+            assert.eql(arguments.length, 3);
+            assert.eql([a,b,e], ['a','b','e']);
+            assert.eql(this.stack, ['a','b','e']);
+            this.pass(null);
+        })
+        .reverse()
+        .seq(function (a, b, e){
+            assert.eql(arguments.length, 3);
+            assert.eql([a,b,e], ['e','b','a']);
+            assert.eql(this.stack, ['e','b','a']);
+            this.pass(null);
+        })
+        .map(function(ch){ return ch.toUpperCase(); })
+        .seq(function (A, B, E){
+            assert.eql(arguments.length, 3);
+            assert.eql([A,B,E], ['E','B','A']);
+            assert.eql(this.stack, ['E','B','A']);
+            this.pass(null);
+        })
+        .reduce(function(s, ch){ return s + ':' + ch; })
+        .seq(function (acc){
+            assert.eql(arguments.length, 1);
+            assert.eql(acc, 'E:B:A');
+            assert.eql(this.stack, ['E:B:A']);
+            this.pass(null);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            this(null);
+        })
+    ;
+};
+
+exports.ap = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 100);
+    
+    var cmp = [1,2,3];
+    Seq.ap([1,2,3])
+        .seqEach(function (x) {
+            assert.eql(cmp.shift(), x);
+            this(null);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(cmp, []);
+        })
+    ;
+    
+    assert.throws(function () {
+        Seq.ap({ a : 1, b : 2 });
+    });
+};
+
+exports.seqBind = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 100);
+    
+    Seq([4,5])
+        .seq(function (a, b, c, d) {
+            assert.eql(a, 2);
+            assert.eql(b, 3);
+            assert.eql(c, 4);
+            assert.eql(d, 5);
+            this(null);
+        }, 2, 3)
+        .seq(function () {
+            clearTimeout(to);
+        })
+    ;
+};
+
+exports.parBind = function () {
+    var t1 = setTimeout(function () {
+        assert.fail('1 never finished');
+    }, 500);
+    var t2 = setTimeout(function () {
+        assert.fail('2 never finished');
+    }, 500);
+    var t3 = setTimeout(function () {
+        assert.fail('3 never finished');
+    }, 500);
+    
+    Seq(['c'])
+        .par(function (a, b, c) {
+            clearTimeout(t1);
+            assert.eql(a, 'a');
+            assert.eql(b, 'b');
+            assert.eql(c, 'c');
+            this(null);
+        }, 'a', 'b')
+        .par(function (x, c) {
+            clearTimeout(t2);
+            assert.eql(x, 'x');
+            assert.eql(c, 'c');
+            this(null);
+        }, 'x')
+        .seq(function () {
+            clearTimeout(t3);
+        })
+    ;
+};
+
+exports.emptySeqEach = function () {
+    var to = setTimeout(function () {
+        assert.fail('never finished');
+    }, 100);
+    
+    Seq()
+        .seqEach(function (x) {
+            assert.fail('no elements');
+        })
+        .seq(function () {
+            clearTimeout(to);
+        })
+    ;
+};
+
+exports.emptyForEach = function () {
+    var to = setTimeout(function () {
+        assert.fail('seq never fired');
+    }, 500);
+    
+    Seq()
+        .forEach(function () {
+            assert.fail('non-empty stack');
+        })
+        .seq(function () {
+            clearTimeout(to);
+        })
+    ;
+};
+
+exports.emptyParEach = function () {
+    var to = setTimeout(function () {
+        assert.fail('seq never fired');
+    }, 500);
+    
+    Seq()
+        .parEach(function () {
+            assert.fail('non-empty stack');
+        })
+        .seq(function () {
+            clearTimeout(to);
+        })
+    ;
+};
+
+exports.emptyParMap = function () {
+    var to = setTimeout(function () {
+        assert.fail('seq never fired');
+    }, 500);
+    
+    Seq()
+        .parMap(function () {
+            assert.fail('non-empty stack');
+        })
+        .seq(function () {
+            clearTimeout(to);
+        })
+    ;
+};
+
+exports.emptySeqMap = function () {
+    var to = setTimeout(function () {
+        assert.fail('seq never fired');
+    }, 500);
+    
+    Seq()
+        .seqMap(function () {
+            assert.fail('non-empty stack');
+        })
+        .seq(function () {
+            clearTimeout(to);
+        })
+    ;
+};
+
+exports.ok = function () {
+    var to = setTimeout(function () {
+        assert.fail('seq never fired');
+    }, 500);
+    
+    function moo1 (cb) { cb(3) }
+    function moo2 (cb) { cb(4) }
+    
+    Seq()
+        .par(function () { moo1(this.ok) })
+        .par(function () { moo2(this.ok) })
+        .seq(function (x, y) {
+            clearTimeout(to);
+            assert.eql(x, 3);
+            assert.eql(y, 4);
+        })
+    ;
+};
+
+exports.nextOk = function () {
+    var to = setTimeout(function () {
+        assert.fail('seq never fired');
+    }, 500);
+    
+    function moo1 (cb) { cb(3) }
+    function moo2 (cb) { cb(4) }
+    
+    Seq()
+        .par_(function (next) { moo1(next.ok) })
+        .par_(function (next) { moo2(next.ok) })
+        .seq_(function (next, x, y) {
+            assert.eql(x, 3);
+            assert.eql(y, 4);
+            next.ok([ 1, 2, 3 ])
+        })
+        .flatten()
+        .parMap_(function (next, x) {
+            next.ok(x * 100)
+        })
+        .seq_(function (next) {
+            clearTimeout(to);
+            assert.deepEqual(next.stack, [ 100, 200, 300 ]);
+        })
+    ;
+};
+
+exports.regressionTestForAccidentalDeepTraversalOfTheContext = function () {
+    // Create a single-item stack with a bunch of references to other objects:
+    var stack = [{}];
+    for (var i = 0 ; i < 10000 ; i += 1) {
+        stack[0][i] = stack[0];
+    }
+
+    var startTime = new Date(),
+        numCalled = 0,
+        to = setTimeout(function () {
+            assert.fail('never got to the end of the chain');
+        }, 1000);
+
+    Seq(stack)
+        .parEach(function (item) {
+            numCalled += 1;
+            this();
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(numCalled, 1);
+            assert.ok((new Date().getTime() - startTime) < 1000, 'if this test takes longer than a second, the bug must have been reintroduced');
+        });
+};
diff --git a/test/seq_.js b/test/seq_.js
new file mode 100644
index 0000000..369cc86
--- /dev/null
+++ b/test/seq_.js
@@ -0,0 +1,149 @@
+var Seq = require('seq');
+var assert = require('assert');
+
+exports.seq_ = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 5000);
+    
+    Seq(['xxx'])
+        .seq_('pow', function (next, x) {
+            assert.eql(next, this);
+            assert.eql(x, 'xxx');
+            next(null, 'yyy');
+        })
+        .seq(function (y) {
+            clearTimeout(to);
+            assert.eql(y, 'yyy');
+            assert.eql(this.vars.pow, 'yyy');
+        })
+    ;
+};
+
+exports.par_ = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 5000);
+    
+    Seq()
+        .par_(function (next) {
+            assert.eql(next, this);
+            next(null, 111);
+        })
+        .par_(function (next) {
+            assert.eql(next, this);
+            next(null, 222);
+        })
+        .seq(function (x, y) {
+            clearTimeout(to);
+            assert.eql(x, 111);
+            assert.eql(y, 222);
+        })
+    ;
+};
+
+exports.forEach_ = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 5000);
+    
+    var acc = [];
+    Seq([7,8,9])
+        .forEach_(function (next, x) {
+            assert.eql(next, this);
+            acc.push(x);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(acc, [ 7, 8, 9 ]);
+        })
+    ;
+};
+
+exports.seqEach_ = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 5000);
+    
+    var acc = [];
+    Seq([7,8,9])
+        .seqEach_(function (next, x) {
+            assert.eql(next, this);
+            acc.push(x);
+            setTimeout(function () {
+                next(null, x);
+            }, Math.random() * 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(acc, [ 7, 8, 9 ]);
+            assert.eql(this.stack, [ 7, 8, 9 ]);
+        })
+    ;
+};
+
+exports.parEach_ = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 5000);
+    
+    var acc = [];
+    Seq([7,8,9])
+        .parEach_(function (next, x) {
+            assert.eql(next, this);
+            acc.push(x);
+            setTimeout(function () {
+                next(null, x);
+            }, Math.random() * 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(acc, [ 7, 8, 9 ]);
+            assert.eql(this.stack, [ 7, 8, 9 ]);
+        })
+    ;
+};
+
+exports.seqMap_ = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 5000);
+    
+    var acc = [];
+    Seq([7,8,9])
+        .seqMap_(function (next, x) {
+            assert.eql(next, this);
+            acc.push(x);
+            setTimeout(function () {
+                next(null, x * 10);
+            }, Math.random() * 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(acc, [ 7, 8, 9 ]);
+            assert.eql(this.stack, [ 70, 80, 90 ]);
+        })
+    ;
+};
+
+exports.parMap_ = function () {
+    var to = setTimeout(function () {
+        assert.fail('never got to the end of the chain');
+    }, 5000);
+    
+    var acc = [];
+    Seq([7,8,9])
+        .parMap_(function (next, x) {
+            assert.eql(next, this);
+            acc.push(x);
+            setTimeout(function () {
+                next(null, x * 10);
+            }, Math.random() * 10);
+        })
+        .seq(function () {
+            clearTimeout(to);
+            assert.eql(acc, [ 7, 8, 9 ]);
+            assert.eql(this.stack, [ 70, 80, 90 ]);
+        })
+    ;
+};

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-seq.git



More information about the Pkg-javascript-commits mailing list