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'))