Support composer.action('foo', { async: true }).
Support composer.composition(name, composition, { async: true }).
Fixes #5.
diff --git a/composer.js b/composer.js
index 86ac671..bd6b948 100644
--- a/composer.js
+++ b/composer.js
@@ -39,8 +39,8 @@
         retain_catch: { components: true, since: '0.4.0' },
         let: { args: [{ _: 'declarations', type: 'object' }], components: true, since: '0.4.0' },
         mask: { components: true, since: '0.4.0' },
-        action: { args: [{ _: 'name', type: 'string' }, { _: 'action', type: 'object', optional: true }], since: '0.4.0' },
-        composition: { args: [{ _: 'name', type: 'string' }, { _: 'composition' }], since: '0.4.0' },
+        action: { args: [{ _: 'name', type: 'string' }, { _: 'options', type: 'object', optional: true }], since: '0.4.0' },
+        composition: { args: [{ _: 'name', type: 'string' }, { _: 'composition' }, { _: 'options', type: 'object', optional: true }], since: '0.4.0' },
         repeat: { args: [{ _: 'count', type: 'number' }], components: true, since: '0.4.0' },
         retry: { args: [{ _: 'count', type: 'number' }], components: true, since: '0.4.0' },
         value: { args: [{ _: 'value', type: 'value' }], since: '0.4.0' },
@@ -358,6 +358,7 @@
         // enhanced action combinator: mangle name, capture code
         action(name, options = {}) {
             if (arguments.length > 2) throw new ComposerError('Too many arguments')
+            if (typeof options !== 'object') throw new ComposerError('Invalid argument', options)
             name = parseActionName(name) // throws ComposerError if name is not valid
             let exec
             if (Array.isArray(options.sequence)) { // native sequence
@@ -378,15 +379,18 @@
             }
             const composition = { type: 'action', name }
             if (exec) composition.action = { exec }
+            if (options.async) composition.async = true
             return new Composition(composition)
         }
 
         // enhanced composition combinator: mangle name
-        composition(name, composition) {
-            if (arguments.length > 2) throw new ComposerError('Too many arguments')
-            if (typeof name !== 'string') throw new ComposerError('Invalid argument', name)
+        composition(name, composition, options = {}) {
+            if (arguments.length > 3) throw new ComposerError('Too many arguments')
+            if (typeof options !== 'object') throw new ComposerError('Invalid argument', options)
             name = parseActionName(name)
-            return new Composition({ type: 'composition', name, composition: this.task(composition) })
+            const obj = { type: 'composition', name, composition: this.task(composition) }
+            if (options.async) obj.async = true
+            return new Composition(obj)
         }
 
         // return enhanced openwhisk client capable of deploying compositions
@@ -464,6 +468,8 @@
     const compiler = new Compiler()
 
     this.require = require
+    const openwhisk = require('openwhisk')
+    let wsk
 
     function chain(front, back) {
         front.slice(-1)[0].next = 1
@@ -482,7 +488,7 @@
             case 'sequence':
                 return chain([{ type: 'pass', path }], sequence(json.components))
             case 'action':
-                return [{ type: 'action', name: json.name, path }]
+                return [{ type: 'action', name: json.name, async: json.async, path }]
             case 'function':
                 return [{ type: 'function', exec: json.function.exec, path }]
             case 'finally':
@@ -642,7 +648,20 @@
                     stack.shift()
                     break
                 case 'action':
-                    return { action: json.name, params, state: { $resume: { state, stack } } } // invoke continuation
+                    if (json.async) {
+                        if (!wsk) wsk = openwhisk({ ignore_certs: true })
+                        return wsk.actions.invoke({ name: json.name, params })
+                            .catch(error => {
+                                console.error(error)
+                                return { error: `An exception was caught at state ${current} (see log for details)` }
+                            })
+                            .then(result => {
+                                params = result
+                                inspect()
+                            }).then(step)
+                    } else {
+                        return { action: json.name, params, state: { $resume: { state, stack } } } // invoke continuation
+                    }
                     break
                 case 'function':
                     return Promise.resolve().then(() => run(json.exec.code))
diff --git a/test/test.js b/test/test.js
index 613c6f8..ece8237 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1,6 +1,7 @@
 const assert = require('assert')
 const composer = require('../composer')
 const name = 'TestAction'
+const compositionName = 'TestComposition'
 const wsk = composer.openwhisk({ ignore_certs: process.env.IGNORE_CERTS && process.env.IGNORE_CERTS !== 'false' && process.env.IGNORE_CERTS !== '0' })
 
 // deploy action
@@ -31,6 +32,10 @@
                 return invoke(composer.action('isNotOne'), { n: 1 }).then(activation => assert.deepEqual(activation.response.result, { value: false }))
             })
 
+            it('action must return activationId', function () {
+                return invoke(composer.action('isNotOne', { async: true }), { n: 1 }).then(activation => assert.ok(activation.response.result.activationId))
+            })
+
             it('action name must parse to fully qualified', function () {
                 let combos = [
                     { n: 42, s: false, e: 'Name must be a string' },
@@ -65,9 +70,9 @@
                 })
             })
 
-            it('invalid argument', function () {
+            it('invalid options', function () {
                 try {
-                    invoke(composer.function(42))
+                    invoke(composer.action('foo', 42))
                     assert.fail()
                 } catch (error) {
                     assert.ok(error.message.startsWith('Invalid argument'))
@@ -76,7 +81,44 @@
 
             it('too many arguments', function () {
                 try {
-                    invoke(composer.function('foo', 'foo'))
+                    invoke(composer.action('foo', {}, 'foo'))
+                    assert.fail()
+                } catch (error) {
+                    assert.ok(error.message.startsWith('Too many arguments'))
+                }
+            })
+        })
+
+        describe('compositions', function () {
+            it('composition must return true', function () {
+                return invoke(composer.composition(compositionName, composer.action('isNotOne')), { n: 0 }).then(activation => assert.deepEqual(activation.response.result, { value: true }))
+            })
+
+            it('action must return activationId', function () {
+                return invoke(composer.composition(compositionName, composer.action('isNotOne'), { async: true }), { n: 1 }).then(activation => assert.ok(activation.response.result.activationId))
+            })
+
+            it('invalid argument', function () {
+                try {
+                    invoke(composer.composition(compositionName, 42))
+                    assert.fail()
+                } catch (error) {
+                    assert.ok(error.message.startsWith('Invalid argument'))
+                }
+            })
+
+            it('invalid options', function () {
+                try {
+                    invoke(composer.composition(compositionName, 'foo', 42))
+                    assert.fail()
+                } catch (error) {
+                    assert.ok(error.message.startsWith('Invalid argument'))
+                }
+            })
+
+            it('too many arguments', function () {
+                try {
+                    invoke(composer.composition(compositionName, 'foo', {}, 'foo'))
                     assert.fail()
                 } catch (error) {
                     assert.ok(error.message.startsWith('Too many arguments'))