@ibm-functions/composer
Advanced tools
Comparing version 0.6.1 to 0.7.0
1129
composer.js
@@ -17,360 +17,323 @@ /* | ||
function main() { | ||
'use strict' | ||
'use strict' | ||
const fs = require('fs') | ||
const os = require('os') | ||
const path = require('path') | ||
const semver = require('semver') | ||
const util = require('util') | ||
const fs = require('fs') | ||
const os = require('os') | ||
const path = require('path') | ||
const semver = require('semver') | ||
const util = require('util') | ||
// read composer version number | ||
const version = require('./package.json').version | ||
// read composer version number | ||
const version = require('./package.json').version | ||
const isObject = obj => typeof obj === 'object' && obj !== null && !Array.isArray(obj) | ||
const isObject = obj => typeof obj === 'object' && obj !== null && !Array.isArray(obj) | ||
// combinator signatures | ||
const combinators = {} | ||
// combinator signatures | ||
const combinators = {} | ||
// error class | ||
class ComposerError extends Error { | ||
constructor(message, argument) { | ||
super(message + (argument !== undefined ? '\nArgument: ' + util.inspect(argument) : '')) | ||
} | ||
// error class | ||
class ComposerError extends Error { | ||
constructor(message, argument) { | ||
super(message + (argument !== undefined ? '\nArgument: ' + util.inspect(argument) : '')) | ||
} | ||
} | ||
// registered plugins | ||
const plugins = [] | ||
// registered plugins | ||
const plugins = [] | ||
const composer = {} | ||
Object.assign(composer, { | ||
// detect task type and create corresponding composition object | ||
task(task) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
if (task === null) return composer.empty() | ||
if (task instanceof Composition) return task | ||
if (typeof task === 'function') return composer.function(task) | ||
if (typeof task === 'string') return composer.action(task) | ||
throw new ComposerError('Invalid argument', task) | ||
}, | ||
const composer = {} | ||
Object.assign(composer, { | ||
// detect task type and create corresponding composition object | ||
task(task) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
if (task === null) return composer.empty() | ||
if (task instanceof Composition) return task | ||
if (typeof task === 'function') return composer.function(task) | ||
if (typeof task === 'string') return composer.action(task) | ||
throw new ComposerError('Invalid argument', task) | ||
}, | ||
// function combinator: stringify function code | ||
function(fun) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
if (typeof fun === 'function') { | ||
fun = `${fun}` | ||
if (fun.indexOf('[native code]') !== -1) throw new ComposerError('Cannot capture native function', fun) | ||
} | ||
if (typeof fun === 'string') { | ||
fun = { kind: 'nodejs:default', code: fun } | ||
} | ||
if (!isObject(fun)) throw new ComposerError('Invalid argument', fun) | ||
return new Composition({ type: 'function', function: { exec: fun } }) | ||
}, | ||
// function combinator: stringify function code | ||
function(fun) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
if (typeof fun === 'function') { | ||
fun = `${fun}` | ||
if (fun.indexOf('[native code]') !== -1) throw new ComposerError('Cannot capture native function', fun) | ||
} | ||
if (typeof fun === 'string') { | ||
fun = { kind: 'nodejs:default', code: fun } | ||
} | ||
if (!isObject(fun)) throw new ComposerError('Invalid argument', fun) | ||
return new Composition({ type: 'function', function: { exec: fun } }) | ||
}, | ||
// action combinator | ||
action(name, options = {}) { | ||
if (arguments.length > 2) throw new ComposerError('Too many arguments') | ||
if (!isObject(options)) throw new ComposerError('Invalid argument', options) | ||
name = composer.util.canonical(name) // throws ComposerError if name is not valid | ||
let exec | ||
if (Array.isArray(options.sequence)) { // native sequence | ||
exec = { kind: 'sequence', components: options.sequence.map(canonical) } | ||
} else if (typeof options.filename === 'string') { // read action code from file | ||
exec = fs.readFileSync(options.filename, { encoding: 'utf8' }) | ||
} else if (typeof options.action === 'function') { // capture function | ||
exec = `const main = ${options.action}` | ||
if (exec.indexOf('[native code]') !== -1) throw new ComposerError('Cannot capture native function', options.action) | ||
} else if (typeof options.action === 'string' || isObject(options.action)) { | ||
exec = options.action | ||
} | ||
if (typeof exec === 'string') { | ||
exec = { kind: 'nodejs:default', code: exec } | ||
} | ||
const composition = { type: 'action', name } | ||
if (exec) composition.action = { exec } | ||
return new Composition(composition) | ||
}, | ||
}) | ||
// action combinator | ||
action(name, options = {}) { | ||
if (arguments.length > 2) throw new ComposerError('Too many arguments') | ||
if (!isObject(options)) throw new ComposerError('Invalid argument', options) | ||
name = composer.util.canonical(name) // throws ComposerError if name is not valid | ||
let exec | ||
if (Array.isArray(options.sequence)) { // native sequence | ||
exec = { kind: 'sequence', components: options.sequence.map(canonical) } | ||
} else if (typeof options.filename === 'string') { // read action code from file | ||
exec = fs.readFileSync(options.filename, { encoding: 'utf8' }) | ||
} else if (typeof options.action === 'function') { // capture function | ||
exec = `const main = ${options.action}` | ||
if (exec.indexOf('[native code]') !== -1) throw new ComposerError('Cannot capture native function', options.action) | ||
} else if (typeof options.action === 'string' || isObject(options.action)) { | ||
exec = options.action | ||
} | ||
if (typeof exec === 'string') { | ||
exec = { kind: 'nodejs:default', code: exec } | ||
} | ||
const composition = { type: 'action', name } | ||
if (exec) composition.action = { exec } | ||
return new Composition(composition) | ||
}, | ||
}) | ||
const lowerer = { | ||
empty() { | ||
return composer.sequence() | ||
}, | ||
const lowerer = { | ||
empty() { | ||
return composer.sequence() | ||
}, | ||
seq({ components }) { | ||
return composer.sequence(...components) | ||
}, | ||
seq({ components }) { | ||
return composer.sequence(...components) | ||
}, | ||
value({ value }) { | ||
return composer.literal(value) | ||
}, | ||
value({ value }) { | ||
return composer.literal(value) | ||
}, | ||
literal({ value }) { | ||
return composer.let({ value }, composer.function('() => value')) | ||
}, | ||
literal({ value }) { | ||
return composer.let({ value }, composer.function('() => value')) | ||
}, | ||
retain({ components }) { | ||
return composer.let( | ||
{ params: null }, | ||
retain({ components }) { | ||
return composer.let( | ||
{ params: null }, | ||
composer.finally( | ||
args => { params = args }, | ||
composer.seq(composer.mask(...components), | ||
result => ({ params, result })))) | ||
}, | ||
retain_catch({ components }) { | ||
return composer.seq( | ||
composer.retain( | ||
composer.finally( | ||
args => { params = args }, | ||
composer.seq(composer.mask(...components), | ||
result => ({ params, result })))) | ||
}, | ||
composer.seq(...components), | ||
result => ({ result }))), | ||
({ params, result }) => ({ params, result: result.result })) | ||
}, | ||
retain_catch({ components }) { | ||
return composer.seq( | ||
composer.retain( | ||
composer.finally( | ||
composer.seq(...components), | ||
result => ({ result }))), | ||
({ params, result }) => ({ params, result: result.result })) | ||
}, | ||
if({ test, consequent, alternate }) { | ||
return composer.let( | ||
{ params: null }, | ||
composer.finally( | ||
args => { params = args }, | ||
composer.if_nosave( | ||
composer.mask(test), | ||
composer.finally(() => params, composer.mask(consequent)), | ||
composer.finally(() => params, composer.mask(alternate))))) | ||
}, | ||
if({ test, consequent, alternate }) { | ||
return composer.let( | ||
{ params: null }, | ||
composer.finally( | ||
args => { params = args }, | ||
composer.if_nosave( | ||
composer.mask(test), | ||
composer.finally(() => params, composer.mask(consequent)), | ||
composer.finally(() => params, composer.mask(alternate))))) | ||
}, | ||
while({ test, body }) { | ||
return composer.let( | ||
{ params: null }, | ||
composer.finally( | ||
args => { params = args }, | ||
composer.seq(composer.while_nosave( | ||
composer.mask(test), | ||
composer.finally(() => params, composer.seq(composer.mask(body), args => { params = args }))), | ||
() => params))) | ||
}, | ||
while({ test, body }) { | ||
return composer.let( | ||
{ params: null }, | ||
composer.finally( | ||
args => { params = args }, | ||
composer.seq(composer.while_nosave( | ||
composer.mask(test), | ||
composer.finally(() => params, composer.seq(composer.mask(body), args => { params = args }))), | ||
() => params))) | ||
}, | ||
dowhile({ body, test }) { | ||
return composer.let( | ||
{ params: null }, | ||
composer.finally( | ||
args => { params = args }, | ||
composer.seq(composer.dowhile_nosave( | ||
composer.finally(() => params, composer.seq(composer.mask(body), args => { params = args })), | ||
composer.mask(test)), | ||
() => params))) | ||
}, | ||
dowhile({ body, test }) { | ||
return composer.let( | ||
{ params: null }, | ||
composer.finally( | ||
args => { params = args }, | ||
composer.seq(composer.dowhile_nosave( | ||
composer.finally(() => params, composer.seq(composer.mask(body), args => { params = args })), | ||
composer.mask(test)), | ||
() => params))) | ||
}, | ||
repeat({ count, components }) { | ||
return composer.let( | ||
{ count }, | ||
composer.while( | ||
composer.function('() => count-- > 0'), | ||
composer.mask(...components))) | ||
}, | ||
repeat({ count, components }) { | ||
return composer.let( | ||
{ count }, | ||
composer.while( | ||
composer.function('() => count-- > 0'), | ||
composer.mask(...components))) | ||
}, | ||
retry({ count, components }) { | ||
return composer.let( | ||
{ count }, | ||
params => ({ params }), | ||
composer.dowhile( | ||
composer.finally(({ params }) => params, composer.mask(composer.retain_catch(...components))), | ||
composer.function('({ result }) => result.error !== undefined && count-- > 0')), | ||
({ result }) => result) | ||
}, | ||
} | ||
retry({ count, components }) { | ||
return composer.let( | ||
{ count }, | ||
params => ({ params }), | ||
composer.dowhile( | ||
composer.finally(({ params }) => params, composer.mask(composer.retain_catch(...components))), | ||
composer.function('({ result }) => result.error !== undefined && count-- > 0')), | ||
({ result }) => result) | ||
}, | ||
} | ||
// recursively compile composition composition into { composition, actions } | ||
function flatten(composition) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
if (!(composition instanceof Composition)) throw new ComposerError('Invalid argument', composition) | ||
// recursively flatten composition into { composition, actions } by extracting embedded action definitions | ||
function flatten(composition) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
if (!(composition instanceof Composition)) throw new ComposerError('Invalid argument', composition) | ||
const actions = [] | ||
const actions = [] | ||
const flatten = composition => { | ||
composition = new Composition(composition) // copy | ||
composition.visit(flatten) | ||
if (composition.type === 'action' && composition.action) { | ||
actions.push({ name: composition.name, action: composition.action }) | ||
delete composition.action | ||
} | ||
return composition | ||
const flatten = composition => { | ||
composition = new Composition(composition) // copy | ||
composition.visit(flatten) | ||
if (composition.type === 'action' && composition.action) { | ||
actions.push({ name: composition.name, action: composition.action }) | ||
delete composition.action | ||
} | ||
return composition | ||
} | ||
composition = flatten(composition) | ||
return { composition, actions } | ||
composition = flatten(composition) | ||
return { composition, actions } | ||
} | ||
// synthesize conductor action code from composition | ||
function synthesize(composition) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
if (!(composition instanceof Composition)) throw new ComposerError('Invalid argument', composition) | ||
let code = `const main=(${main})(` | ||
for (let plugin of plugins) { | ||
code += `{plugin:new(${plugin.constructor})()` | ||
if (plugin.configure) code += `,config:${JSON.stringify(plugin.configure())}` | ||
code += '},' | ||
} | ||
code = require('uglify-es').minify(`${code})`, { output: { max_line_len: 127 } }).code | ||
code = `// generated by composer v${composer.util.version}\n\nconst composition = ${JSON.stringify(composer.util.lower(label(composition)), null, 4)}\n\n// do not edit below this point\n\n${code}` // invoke conductor on composition | ||
return { exec: { kind: 'nodejs:default', code }, annotations: [{ key: 'conductor', value: composition }, { key: 'composer', value: version }] } | ||
} | ||
// synthesize composition code | ||
function synthesize(composition) { | ||
composer.util = { | ||
// return the signatures of the combinators | ||
get combinators() { | ||
return combinators | ||
}, | ||
// recursively deserialize composition | ||
deserialize(composition) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
composition = new Composition(composition) // copy | ||
composition.visit(composition => composer.util.deserialize(composition)) | ||
return composition | ||
}, | ||
// recursively lower combinators to the desired set of combinators (including primitive combinators) | ||
lower(composition, combinators = []) { | ||
if (arguments.length > 2) throw new ComposerError('Too many arguments') | ||
if (!(composition instanceof Composition)) throw new ComposerError('Invalid argument', composition) | ||
let code = `const main=(${main})().runtime(` | ||
for (let plugin of plugins) { | ||
code += `{plugin:new(${plugin.constructor})()` | ||
if (plugin.configure) code += `,config:${JSON.stringify(plugin.configure())}` | ||
code += '},' | ||
if (typeof combinators === 'string') { // lower to combinators of specific composer version | ||
combinators = Object.keys(composer.util.combinators).filter(key => semver.gte(combinators, composer.util.combinators[key].since)) | ||
} | ||
code = require('uglify-es').minify(`${code})`, { output: { max_line_len: 127 } }).code | ||
code = `// generated by composer v${version}\n\nconst composition = ${JSON.stringify(composition, null, 4)}\n\n// do not edit below this point\n\n${code}` // invoke conductor on composition | ||
return { exec: { kind: 'nodejs:default', code }, annotations: [{ key: 'conductor', value: composition }, { key: 'composer', value: version }] } | ||
} | ||
if (!Array.isArray(combinators)) throw new ComposerError('Invalid argument', combinators) | ||
composer.util = { | ||
// return the signatures of the combinators | ||
get combinators() { | ||
return combinators | ||
}, | ||
// recursively deserialize composition | ||
deserialize(composition) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
const lower = composition => { | ||
composition = new Composition(composition) // copy | ||
composition.visit(composition => composer.util.deserialize(composition)) | ||
// repeatedly lower root combinator | ||
while (combinators.indexOf(composition.type) < 0 && lowerer[composition.type]) { | ||
const path = composition.path | ||
composition = lowerer[composition.type](composition) | ||
if (path !== undefined) composition.path = path // preserve path | ||
} | ||
// lower nested combinators | ||
composition.visit(lower) | ||
return composition | ||
}, | ||
} | ||
// recursively lower combinators to the desired set of combinators (including primitive combinators) | ||
lower(composition, combinators = []) { | ||
if (arguments.length > 2) throw new ComposerError('Too many arguments') | ||
if (!(composition instanceof Composition)) throw new ComposerError('Invalid argument', composition) | ||
if (typeof combinators === 'string') { // lower to combinators of specific composer version | ||
combinators = Object.keys(composer.util.combinators).filter(key => semver.gte(combinators, composer.util.combinators[key].since)) | ||
} | ||
if (!Array.isArray(combinators)) throw new ComposerError('Invalid argument', combinators) | ||
return lower(composition) | ||
}, | ||
const lower = composition => { | ||
composition = new Composition(composition) // copy | ||
// repeatedly lower root combinator | ||
while (combinators.indexOf(composition.type) < 0 && lowerer[composition.type]) { | ||
const path = composition.path | ||
composition = lowerer[composition.type](composition) | ||
if (path !== undefined) composition.path = path // preserve path | ||
} | ||
// lower nested combinators | ||
composition.visit(lower) | ||
return composition | ||
} | ||
// register plugin | ||
register(plugin) { | ||
plugins.push(plugin) | ||
}, | ||
return lower(composition) | ||
}, | ||
/** | ||
* Parses a (possibly fully qualified) resource name and validates it. If it's not a fully qualified name, | ||
* then attempts to qualify it. | ||
* | ||
* Examples string to namespace, [package/]action name | ||
* foo => /_/foo | ||
* pkg/foo => /_/pkg/foo | ||
* /ns/foo => /ns/foo | ||
* /ns/pkg/foo => /ns/pkg/foo | ||
*/ | ||
canonical(name) { | ||
if (typeof name !== 'string') throw new ComposerError('Name must be a string') | ||
if (name.trim().length == 0) throw new ComposerError('Name is not valid') | ||
name = name.trim() | ||
const delimiter = '/' | ||
const parts = name.split(delimiter) | ||
const n = parts.length | ||
const leadingSlash = name[0] == delimiter | ||
// no more than /ns/p/a | ||
if (n < 1 || n > 4 || (leadingSlash && n == 2) || (!leadingSlash && n == 4)) throw new ComposerError('Name is not valid') | ||
// skip leading slash, all parts must be non empty (could tighten this check to match EntityName regex) | ||
parts.forEach(function (part, i) { if (i > 0 && part.trim().length == 0) throw new ComposerError('Name is not valid') }) | ||
const newName = parts.join(delimiter) | ||
if (leadingSlash) return newName | ||
else if (n < 3) return `${delimiter}_${delimiter}${newName}` | ||
else return `${delimiter}${newName}` | ||
}, | ||
// register plugin | ||
register(plugin) { | ||
if (plugin.combinators) init(plugin.combinators()) | ||
if (plugin.composer) Object.assign(composer, plugin.composer({ composer, ComposerError, Composition })) | ||
if (plugin.lowerer) Object.assign(lowerer, plugin.lowerer({ composer, ComposerError, Composition })) | ||
plugins.push(plugin) | ||
return composer | ||
}, | ||
/** | ||
* Parses a (possibly fully qualified) resource name and validates it. If it's not a fully qualified name, | ||
* then attempts to qualify it. | ||
* | ||
* Examples string to namespace, [package/]action name | ||
* foo => /_/foo | ||
* pkg/foo => /_/pkg/foo | ||
* /ns/foo => /ns/foo | ||
* /ns/pkg/foo => /ns/pkg/foo | ||
*/ | ||
canonical(name) { | ||
if (typeof name !== 'string') throw new ComposerError('Name must be a string') | ||
if (name.trim().length == 0) throw new ComposerError('Name is not valid') | ||
name = name.trim() | ||
const delimiter = '/' | ||
const parts = name.split(delimiter) | ||
const n = parts.length | ||
const leadingSlash = name[0] == delimiter | ||
// no more than /ns/p/a | ||
if (n < 1 || n > 4 || (leadingSlash && n == 2) || (!leadingSlash && n == 4)) throw new ComposerError('Name is not valid') | ||
// skip leading slash, all parts must be non empty (could tighten this check to match EntityName regex) | ||
parts.forEach(function (part, i) { if (i > 0 && part.trim().length == 0) throw new ComposerError('Name is not valid') }) | ||
const newName = parts.join(delimiter) | ||
if (leadingSlash) return newName | ||
else if (n < 3) return `${delimiter}_${delimiter}${newName}` | ||
else return `${delimiter}${newName}` | ||
}, | ||
// encode composition as an action table | ||
encode(name, composition, combinators) { | ||
if (arguments.length > 3) throw new ComposerError('Too many arguments') | ||
name = composer.util.canonical(name) // throws ComposerError if name is not valid | ||
if (!(composition instanceof Composition)) throw new ComposerError('Invalid argument', composition) | ||
if (combinators) composition = composer.util.lower(composition, combinators) | ||
const table = flatten(composition) | ||
table.actions.push({ name, action: synthesize(table.composition) }) | ||
return table.actions | ||
}, | ||
// encode composition as an action table | ||
encode(name, composition, combinators) { | ||
if (arguments.length > 3) throw new ComposerError('Too many arguments') | ||
name = composer.util.canonical(name) // throws ComposerError if name is not valid | ||
if (!(composition instanceof Composition)) throw new ComposerError('Invalid argument', composition) | ||
if (combinators) composition = composer.util.lower(composition, combinators) | ||
const table = flatten(composition) | ||
table.actions.push({ name, action: synthesize(table.composition) }) | ||
return table.actions | ||
}, | ||
// return composer version | ||
get version() { | ||
return version | ||
}, | ||
// return composer version | ||
get version() { | ||
return version | ||
}, | ||
// return enhanced openwhisk client capable of deploying compositions | ||
openwhisk(options) { | ||
// try to extract apihost and key first from whisk property file file and then from process.env | ||
let apihost | ||
let api_key | ||
// return enhanced openwhisk client capable of deploying compositions | ||
openwhisk(options) { | ||
// try to extract apihost and key first from whisk property file file and then from process.env | ||
let apihost | ||
let api_key | ||
try { | ||
const wskpropsPath = process.env.WSK_CONFIG_FILE || path.join(os.homedir(), '.wskprops') | ||
const lines = fs.readFileSync(wskpropsPath, { encoding: 'utf8' }).split('\n') | ||
try { | ||
const wskpropsPath = process.env.WSK_CONFIG_FILE || path.join(os.homedir(), '.wskprops') | ||
const lines = fs.readFileSync(wskpropsPath, { encoding: 'utf8' }).split('\n') | ||
for (let line of lines) { | ||
let parts = line.trim().split('=') | ||
if (parts.length === 2) { | ||
if (parts[0] === 'APIHOST') { | ||
apihost = parts[1] | ||
} else if (parts[0] === 'AUTH') { | ||
api_key = parts[1] | ||
} | ||
for (let line of lines) { | ||
let parts = line.trim().split('=') | ||
if (parts.length === 2) { | ||
if (parts[0] === 'APIHOST') { | ||
apihost = parts[1] | ||
} else if (parts[0] === 'AUTH') { | ||
api_key = parts[1] | ||
} | ||
} | ||
} catch (error) { } | ||
} | ||
} catch (error) { } | ||
if (process.env.__OW_API_HOST) apihost = process.env.__OW_API_HOST | ||
if (process.env.__OW_API_KEY) api_key = process.env.__OW_API_KEY | ||
if (process.env.__OW_API_HOST) apihost = process.env.__OW_API_HOST | ||
if (process.env.__OW_API_KEY) api_key = process.env.__OW_API_KEY | ||
const wsk = require('openwhisk')(Object.assign({ apihost, api_key }, options)) | ||
wsk.compositions = new Compositions(wsk) | ||
return wsk | ||
}, | ||
} | ||
const wsk = require('openwhisk')(Object.assign({ apihost, api_key }, options)) | ||
wsk.compositions = new Compositions(wsk) | ||
return wsk | ||
}, | ||
// composition class | ||
class Composition { | ||
// weaker instanceof to tolerate multiple instances of this class | ||
static [Symbol.hasInstance](instance) { | ||
return instance.constructor && instance.constructor.name === Composition.name | ||
} | ||
// construct a composition object with the specified fields | ||
constructor(composition) { | ||
if (!isObject(composition) || composer.util.combinators[composition.type] === undefined) throw new ComposerError('Invalid argument', composition) | ||
const combinator = composer.util.combinators[composition.type] | ||
if (combinator.components && composition.components === undefined)throw new ComposerError('Invalid argument', composition) | ||
for (let arg of combinator.args || []) { | ||
if (!arg.optional && composition[arg._] === undefined) throw new ComposerError('Invalid argument', composition) | ||
} | ||
return Object.assign(this, composition) | ||
} | ||
// apply f to all fields of type composition | ||
visit(f) { | ||
const combinator = composer.util.combinators[this.type] | ||
if (combinator.components) { | ||
this.components = this.components.map(f) | ||
} | ||
for (let arg of combinator.args || []) { | ||
if (arg.type === undefined && this[arg._] !== undefined) { | ||
this[arg._] = f(this[arg._], arg._) | ||
} | ||
} | ||
} | ||
} | ||
// derive combinator methods from combinator table | ||
function init(combinators) { | ||
declare(combinators) { | ||
Object.assign(composer.util.combinators, combinators) | ||
@@ -411,314 +374,352 @@ for (let type in combinators) { | ||
} | ||
}, | ||
get lowerer() { | ||
return lowerer | ||
}, | ||
} | ||
composer.util.frontend = composer.util.openwhisk | ||
// composition class | ||
class Composition { | ||
// weaker instanceof to tolerate multiple instances of this class | ||
static [Symbol.hasInstance](instance) { | ||
return instance.constructor && instance.constructor.name === Composition.name | ||
} | ||
init({ | ||
empty: { since: '0.4.0' }, | ||
seq: { components: true, since: '0.4.0' }, | ||
sequence: { components: true, since: '0.4.0' }, | ||
if: { args: [{ _: 'test' }, { _: 'consequent' }, { _: 'alternate', optional: true }], since: '0.4.0' }, | ||
if_nosave: { args: [{ _: 'test' }, { _: 'consequent' }, { _: 'alternate', optional: true }], since: '0.4.0' }, | ||
while: { args: [{ _: 'test' }, { _: 'body' }], since: '0.4.0' }, | ||
while_nosave: { args: [{ _: 'test' }, { _: 'body' }], since: '0.4.0' }, | ||
dowhile: { args: [{ _: 'body' }, { _: 'test' }], since: '0.4.0' }, | ||
dowhile_nosave: { args: [{ _: 'body' }, { _: 'test' }], since: '0.4.0' }, | ||
try: { args: [{ _: 'body' }, { _: 'handler' }], since: '0.4.0' }, | ||
finally: { args: [{ _: 'body' }, { _: 'finalizer' }], since: '0.4.0' }, | ||
retain: { components: true, since: '0.4.0' }, | ||
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' }, | ||
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' }, | ||
literal: { args: [{ _: 'value', type: 'value' }], since: '0.4.0' }, | ||
function: { args: [{ _: 'function', type: 'object' }], since: '0.4.0' }, | ||
async: { args: [{ _: 'body' }], since: '0.6.0' }, | ||
}) | ||
// management class for compositions | ||
class Compositions { | ||
constructor(wsk) { | ||
this.actions = wsk.actions | ||
// construct a composition object with the specified fields | ||
constructor(composition) { | ||
if (!isObject(composition) || composer.util.combinators[composition.type] === undefined) throw new ComposerError('Invalid argument', composition) | ||
const combinator = composer.util.combinators[composition.type] | ||
if (combinator.components && composition.components === undefined) throw new ComposerError('Invalid argument', composition) | ||
for (let arg of combinator.args || []) { | ||
if (!arg.optional && composition[arg._] === undefined) throw new ComposerError('Invalid argument', composition) | ||
} | ||
return Object.assign(this, composition) | ||
} | ||
deploy(name, composition, combinators) { | ||
const actions = composer.util.encode(name, composition, combinators) | ||
return actions.reduce((promise, action) => promise.then(() => this.actions.delete(action).catch(() => { })) | ||
.then(() => this.actions.update(action)), Promise.resolve()) | ||
.then(() => actions) | ||
// apply f to all fields of type composition | ||
visit(f) { | ||
const combinator = composer.util.combinators[this.type] | ||
if (combinator.components) { | ||
this.components = this.components.map(f) | ||
} | ||
for (let arg of combinator.args || []) { | ||
if (arg.type === undefined && this[arg._] !== undefined) { | ||
this[arg._] = f(this[arg._], arg._) | ||
} | ||
} | ||
} | ||
} | ||
// runtime stuff | ||
function runtime() { | ||
// recursively label combinators with the json path | ||
function label(composition) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
if (!(composition instanceof Composition)) throw new ComposerError('Invalid argument', composition) | ||
Composition.composer = composer | ||
const label = path => (composition, name, array) => { | ||
composition = new Composition(composition) // copy | ||
composition.path = path + (name !== undefined ? (array === undefined ? `.${name}` : `[${name}]`) : '') | ||
// label nested combinators | ||
composition.visit(label(composition.path)) | ||
return composition | ||
} | ||
composer.util.declare({ | ||
empty: { since: '0.4.0' }, | ||
seq: { components: true, since: '0.4.0' }, | ||
sequence: { components: true, since: '0.4.0' }, | ||
if: { args: [{ _: 'test' }, { _: 'consequent' }, { _: 'alternate', optional: true }], since: '0.4.0' }, | ||
if_nosave: { args: [{ _: 'test' }, { _: 'consequent' }, { _: 'alternate', optional: true }], since: '0.4.0' }, | ||
while: { args: [{ _: 'test' }, { _: 'body' }], since: '0.4.0' }, | ||
while_nosave: { args: [{ _: 'test' }, { _: 'body' }], since: '0.4.0' }, | ||
dowhile: { args: [{ _: 'body' }, { _: 'test' }], since: '0.4.0' }, | ||
dowhile_nosave: { args: [{ _: 'body' }, { _: 'test' }], since: '0.4.0' }, | ||
try: { args: [{ _: 'body' }, { _: 'handler' }], since: '0.4.0' }, | ||
finally: { args: [{ _: 'body' }, { _: 'finalizer' }], since: '0.4.0' }, | ||
retain: { components: true, since: '0.4.0' }, | ||
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' }, | ||
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' }, | ||
literal: { args: [{ _: 'value', type: 'value' }], since: '0.4.0' }, | ||
function: { args: [{ _: 'function', type: 'object' }], since: '0.4.0' }, | ||
async: { args: [{ _: 'body' }], since: '0.6.0' }, | ||
}) | ||
return label('')(composition) | ||
} | ||
// management class for compositions | ||
class Compositions { | ||
constructor(wsk) { | ||
this.actions = wsk.actions | ||
} | ||
// compile ast to fsm | ||
const compiler = { | ||
sequence(node) { | ||
return [{ type: 'pass', path: node.path }, ...compile(...node.components)] | ||
}, | ||
deploy({ name, composition, combinators }) { | ||
const actions = composer.util.encode(name, composition, combinators) | ||
return actions.reduce((promise, action) => promise.then(() => this.actions.delete(action).catch(() => { })) | ||
.then(() => this.actions.update(action)), Promise.resolve()) | ||
.then(() => actions) | ||
} | ||
} | ||
action(node) { | ||
return [{ type: 'action', name: node.name, path: node.path }] | ||
}, | ||
// recursively label combinators with the json path | ||
function label(composition) { | ||
if (arguments.length > 1) throw new ComposerError('Too many arguments') | ||
if (!(composition instanceof Composition)) throw new ComposerError('Invalid argument', composition) | ||
const label = path => (composition, name, array) => { | ||
composition = new Composition(composition) // copy | ||
composition.path = path + (name !== undefined ? (array === undefined ? `.${name}` : `[${name}]`) : '') | ||
// label nested combinators | ||
composition.visit(label(composition.path)) | ||
return composition | ||
} | ||
async(node) { | ||
const body = compile(node.body) | ||
return [{ type: 'async', path: node.path, return: body.length + 2 }, ...body, { type: 'stop' }, { type: 'pass' }] | ||
}, | ||
return label('')(composition) | ||
} | ||
function(node) { | ||
return [{ type: 'function', exec: node.function.exec, path: node.path }] | ||
}, | ||
// runtime code | ||
function main() { | ||
const isObject = obj => typeof obj === 'object' && obj !== null && !Array.isArray(obj) | ||
finally(node) { | ||
const finalizer = compile(node.finalizer) | ||
const fsm = [{ type: 'try', path: node.path }, ...compile(node.body), { type: 'exit' }, ...finalizer] | ||
fsm[0].catch = fsm.length - finalizer.length | ||
return fsm | ||
}, | ||
// compile ast to fsm | ||
const compiler = { | ||
sequence(node) { | ||
return [{ type: 'pass', path: node.path }, ...compile(...node.components)] | ||
}, | ||
let(node) { | ||
return [{ type: 'let', let: node.declarations, path: node.path }, ...compile(...node.components), { type: 'exit' }] | ||
}, | ||
action(node) { | ||
return [{ type: 'action', name: node.name, path: node.path }] | ||
}, | ||
mask(node) { | ||
return [{ type: 'let', let: null, path: node.path }, ...compile(...node.components), { type: 'exit' }] | ||
}, | ||
async(node) { | ||
const body = compile(node.body) | ||
return [{ type: 'async', path: node.path, return: body.length + 2 }, ...body, { type: 'stop' }, { type: 'pass' }] | ||
}, | ||
try(node) { | ||
const handler = [...compile(node.handler), { type: 'pass' }] | ||
const fsm = [{ type: 'try', path: node.path }, ...compile(node.body), { type: 'exit' }, ...handler] | ||
fsm[0].catch = fsm.length - handler.length | ||
fsm[fsm.length - handler.length - 1].next = handler.length | ||
return fsm | ||
}, | ||
function(node) { | ||
return [{ type: 'function', exec: node.function.exec, path: node.path }] | ||
}, | ||
if_nosave(node) { | ||
const consequent = compile(node.consequent) | ||
const alternate = [...compile(node.alternate), { type: 'pass' }] | ||
const fsm = [{ type: 'pass', path: node.path }, ...compile(node.test), { type: 'choice', then: 1, else: consequent.length + 1 }, ...consequent, ...alternate] | ||
fsm[fsm.length - alternate.length - 1].next = alternate.length | ||
return fsm | ||
}, | ||
finally(node) { | ||
const finalizer = compile(node.finalizer) | ||
const fsm = [{ type: 'try', path: node.path }, ...compile(node.body), { type: 'exit' }, ...finalizer] | ||
fsm[0].catch = fsm.length - finalizer.length | ||
return fsm | ||
}, | ||
while_nosave(node) { | ||
const body = compile(node.body) | ||
const fsm = [{ type: 'pass', path: node.path }, ...compile(node.test), { type: 'choice', then: 1, else: body.length + 1 }, ...body, { type: 'pass' }] | ||
fsm[fsm.length - 2].next = 2 - fsm.length | ||
return fsm | ||
}, | ||
let(node) { | ||
return [{ type: 'let', let: node.declarations, path: node.path }, ...compile(...node.components), { type: 'exit' }] | ||
}, | ||
dowhile_nosave(node) { | ||
const fsm = [{ type: 'pass', path: node.path }, ...compile(node.body), ...compile(node.test), { type: 'choice', else: 1 }, { type: 'pass' }] | ||
fsm[fsm.length - 2].then = 2 - fsm.length | ||
return fsm | ||
}, | ||
} | ||
mask(node) { | ||
return [{ type: 'let', let: null, path: node.path }, ...compile(...node.components), { type: 'exit' }] | ||
}, | ||
function compile(node) { | ||
if (arguments.length === 0) return [{ type: 'empty' }] | ||
if (arguments.length === 1) return compiler[node.type](node) | ||
return Array.prototype.reduce.call(arguments, (fsm, node) => { fsm.push(...compile(node)); return fsm }, []) | ||
} | ||
try(node) { | ||
const handler = [...compile(node.handler), { type: 'pass' }] | ||
const fsm = [{ type: 'try', path: node.path }, ...compile(node.body), { type: 'exit' }, ...handler] | ||
fsm[0].catch = fsm.length - handler.length | ||
fsm[fsm.length - handler.length - 1].next = handler.length | ||
return fsm | ||
}, | ||
const openwhisk = require('openwhisk') | ||
let wsk | ||
if_nosave(node) { | ||
const consequent = compile(node.consequent) | ||
const alternate = [...compile(node.alternate), { type: 'pass' }] | ||
const fsm = [{ type: 'pass', path: node.path }, ...compile(node.test), { type: 'choice', then: 1, else: consequent.length + 1 }, ...consequent, ...alternate] | ||
fsm[fsm.length - alternate.length - 1].next = alternate.length | ||
return fsm | ||
}, | ||
const conductor = { | ||
choice({ p, node, index }) { | ||
p.s.state = index + (p.params.value ? node.then : node.else) | ||
}, | ||
while_nosave(node) { | ||
const body = compile(node.body) | ||
const fsm = [{ type: 'pass', path: node.path }, ...compile(node.test), { type: 'choice', then: 1, else: body.length + 1 }, ...body, { type: 'pass' }] | ||
fsm[fsm.length - 2].next = 2 - fsm.length | ||
return fsm | ||
}, | ||
try({ p, node, index }) { | ||
p.s.stack.unshift({ catch: index + node.catch }) | ||
}, | ||
dowhile_nosave(node) { | ||
const fsm = [{ type: 'pass', path: node.path }, ...compile(node.body), ...compile(node.test), { type: 'choice', else: 1 }, { type: 'pass' }] | ||
fsm[fsm.length - 2].then = 2 - fsm.length | ||
return fsm | ||
}, | ||
} | ||
let({ p, node, index }) { | ||
p.s.stack.unshift({ let: JSON.parse(JSON.stringify(node.let)) }) | ||
}, | ||
function compile(node) { | ||
if (arguments.length === 0) return [{ type: 'empty' }] | ||
if (arguments.length === 1) return compiler[node.type](node) | ||
return Array.prototype.reduce.call(arguments, (fsm, node) => { fsm.push(...compile(node)); return fsm }, []) | ||
} | ||
exit({ p, node, index }) { | ||
if (p.s.stack.length === 0) return internalError(`State ${index} attempted to pop from an empty stack`) | ||
p.s.stack.shift() | ||
}, | ||
const openwhisk = require('openwhisk') | ||
let wsk | ||
action({ p, node, index }) { | ||
return { action: node.name, params: p.params, state: { $resume: p.s } } | ||
}, | ||
const conductor = { | ||
choice({ p, node, index }) { | ||
p.s.state = index + (p.params.value ? node.then : node.else) | ||
}, | ||
function({ p, node, index }) { | ||
return Promise.resolve().then(() => run(node.exec.code, p)) | ||
.catch(error => { | ||
console.error(error) | ||
return { error: `An exception was caught at state ${index} (see log for details)` } | ||
}) | ||
.then(result => { | ||
if (typeof result === 'function') result = { error: `State ${index} evaluated to a function` } | ||
// if a function has only side effects and no return value, return params | ||
p.params = JSON.parse(JSON.stringify(result === undefined ? p.params : result)) | ||
inspect(p) | ||
return step(p) | ||
}) | ||
}, | ||
try({ p, node, index }) { | ||
p.s.stack.unshift({ catch: index + node.catch }) | ||
}, | ||
empty({ p, node, index }) { | ||
inspect(p) | ||
}, | ||
let({ p, node, index }) { | ||
p.s.stack.unshift({ let: JSON.parse(JSON.stringify(node.let)) }) | ||
}, | ||
pass({ p, node, index }) { | ||
}, | ||
exit({ p, node, index }) { | ||
if (p.s.stack.length === 0) return internalError(`State ${index} attempted to pop from an empty stack`) | ||
p.s.stack.shift() | ||
}, | ||
async({ p, node, index, inspect, step }) { | ||
if (!wsk) wsk = openwhisk({ ignore_certs: true }) | ||
p.params.$resume = { state: p.s.state } | ||
p.s.state = index + node.return | ||
return wsk.actions.invoke({ name: process.env.__OW_ACTION_NAME, params: p.params }) | ||
.catch(error => { | ||
console.error(error) | ||
return { error: `An exception was caught at state ${index} (see log for details)` } | ||
}) | ||
.then(result => { | ||
p.params = result | ||
inspect(p) | ||
return step(p) | ||
}) | ||
}, | ||
action({ p, node, index }) { | ||
return { action: node.name, params: p.params, state: { $resume: p.s } } | ||
}, | ||
stop({ p, node, index, inspect, step }) { | ||
p.s.state = -1 | ||
}, | ||
} | ||
function({ p, node, index }) { | ||
return Promise.resolve().then(() => run(node.exec.code, p)) | ||
.catch(error => { | ||
console.error(error) | ||
return { error: `An exception was caught at state ${index} (see log for details)` } | ||
}) | ||
.then(result => { | ||
if (typeof result === 'function') result = { error: `State ${index} evaluated to a function` } | ||
// if a function has only side effects and no return value, return params | ||
p.params = JSON.parse(JSON.stringify(result === undefined ? p.params : result)) | ||
inspect(p) | ||
return step(p) | ||
}) | ||
}, | ||
const finishers = [] | ||
empty({ p, node, index }) { | ||
inspect(p) | ||
}, | ||
for (let { plugin, config } of arguments) { | ||
composer.util.register(plugin) | ||
if (plugin.compiler) Object.assign(compiler, plugin.compiler({ compile })) | ||
if (plugin.conductor) { | ||
Object.assign(conductor, plugin.conductor(config)) | ||
if (conductor._finish) { | ||
finishers.push(conductor._finish) | ||
delete conductor._finish | ||
} | ||
pass({ p, node, index }) { | ||
}, | ||
async({ p, node, index, inspect, step }) { | ||
if (!wsk) wsk = openwhisk({ ignore_certs: true }) | ||
p.params.$resume = { state: p.s.state } | ||
p.s.state = index + node.return | ||
return wsk.actions.invoke({ name: process.env.__OW_ACTION_NAME, params: p.params }) | ||
.catch(error => { | ||
console.error(error) | ||
return { error: `An exception was caught at state ${index} (see log for details)` } | ||
}) | ||
.then(result => { | ||
p.params = result | ||
inspect(p) | ||
return step(p) | ||
}) | ||
}, | ||
stop({ p, node, index, inspect, step }) { | ||
p.s.state = -1 | ||
}, | ||
} | ||
const finishers = [] | ||
for (let { plugin, config } of arguments) { | ||
if (plugin.compiler) Object.assign(compiler, plugin.compiler({ compile })) | ||
if (plugin.conductor) { | ||
Object.assign(conductor, plugin.conductor(config)) | ||
if (conductor._finish) { | ||
finishers.push(conductor._finish) | ||
delete conductor._finish | ||
} | ||
} | ||
} | ||
const fsm = compile(composer.util.lower(label(composer.util.deserialize(composition)))) | ||
const fsm = compile(composition) | ||
// encode error object | ||
const encodeError = error => ({ | ||
code: typeof error.code === 'number' && error.code || 500, | ||
error: (typeof error.error === 'string' && error.error) || error.message || (typeof error === 'string' && error) || 'An internal error occurred' | ||
}) | ||
// encode error object | ||
const encodeError = error => ({ | ||
code: typeof error.code === 'number' && error.code || 500, | ||
error: (typeof error.error === 'string' && error.error) || error.message || (typeof error === 'string' && error) || 'An internal error occurred' | ||
}) | ||
// error status codes | ||
const badRequest = error => Promise.reject({ code: 400, error }) | ||
const internalError = error => Promise.reject(encodeError(error)) | ||
// error status codes | ||
const badRequest = error => Promise.reject({ code: 400, error }) | ||
const internalError = error => Promise.reject(encodeError(error)) | ||
// wrap params if not a dictionary, branch to error handler if error | ||
function inspect(p) { | ||
if (!isObject(p.params)) p.params = { value: p.params } | ||
if (p.params.error !== undefined) { | ||
p.params = { error: p.params.error } // discard all fields but the error field | ||
p.s.state = -1 // abort unless there is a handler in the stack | ||
while (p.s.stack.length > 0) { | ||
if ((p.s.state = p.s.stack.shift().catch || -1) >= 0) break | ||
} | ||
// wrap params if not a dictionary, branch to error handler if error | ||
function inspect(p) { | ||
if (!isObject(p.params)) p.params = { value: p.params } | ||
if (p.params.error !== undefined) { | ||
p.params = { error: p.params.error } // discard all fields but the error field | ||
p.s.state = -1 // abort unless there is a handler in the stack | ||
while (p.s.stack.length > 0) { | ||
if ((p.s.state = p.s.stack.shift().catch || -1) >= 0) break | ||
} | ||
} | ||
} | ||
// run function f on current stack | ||
function run(f, p) { | ||
// handle let/mask pairs | ||
const view = [] | ||
let n = 0 | ||
for (let frame of p.s.stack) { | ||
if (frame.let === null) { | ||
n++ | ||
} else if (frame.let !== undefined) { | ||
if (n === 0) { | ||
view.push(frame) | ||
} else { | ||
n-- | ||
} | ||
// run function f on current stack | ||
function run(f, p) { | ||
// handle let/mask pairs | ||
const view = [] | ||
let n = 0 | ||
for (let frame of p.s.stack) { | ||
if (frame.let === null) { | ||
n++ | ||
} else if (frame.let !== undefined) { | ||
if (n === 0) { | ||
view.push(frame) | ||
} else { | ||
n-- | ||
} | ||
} | ||
} | ||
// update value of topmost matching symbol on stack if any | ||
function set(symbol, value) { | ||
const element = view.find(element => element.let !== undefined && element.let[symbol] !== undefined) | ||
if (element !== undefined) element.let[symbol] = JSON.parse(JSON.stringify(value)) | ||
} | ||
// update value of topmost matching symbol on stack if any | ||
function set(symbol, value) { | ||
const element = view.find(element => element.let !== undefined && element.let[symbol] !== undefined) | ||
if (element !== undefined) element.let[symbol] = JSON.parse(JSON.stringify(value)) | ||
} | ||
// collapse stack for invocation | ||
const env = view.reduceRight((acc, cur) => cur.let ? Object.assign(acc, cur.let) : acc, {}) | ||
let main = '(function(){try{' | ||
for (const name in env) main += `var ${name}=arguments[1]['${name}'];` | ||
main += `return eval((${f}))(arguments[0])}finally{` | ||
for (const name in env) main += `arguments[1]['${name}']=${name};` | ||
main += '}})' | ||
try { | ||
return (1, eval)(main)(p.params, env) | ||
} finally { | ||
for (const name in env) set(name, env[name]) | ||
} | ||
// collapse stack for invocation | ||
const env = view.reduceRight((acc, cur) => cur.let ? Object.assign(acc, cur.let) : acc, {}) | ||
let main = '(function(){try{' | ||
for (const name in env) main += `var ${name}=arguments[1]['${name}'];` | ||
main += `return eval((${f}))(arguments[0])}finally{` | ||
for (const name in env) main += `arguments[1]['${name}']=${name};` | ||
main += '}})' | ||
try { | ||
return (1, eval)(main)(p.params, env) | ||
} finally { | ||
for (const name in env) set(name, env[name]) | ||
} | ||
} | ||
function step(p) { | ||
// final state, return composition result | ||
if (p.s.state < 0 || p.s.state >= fsm.length) { | ||
console.log(`Entering final state`) | ||
console.log(JSON.stringify(p.params)) | ||
return finishers.reduce((promise, _finish) => promise.then(() => _finish(p)), Promise.resolve()) | ||
.then(() => p.params.error ? p.params : { params: p.params }) | ||
} | ||
// process one state | ||
const node = fsm[p.s.state] // json definition for index state | ||
if (node.path !== undefined) console.log(`Entering composition${node.path}`) | ||
const index = p.s.state // current state | ||
p.s.state = p.s.state + (node.next || 1) // default next state | ||
return conductor[node.type]({ p, index, node, inspect, step }) || step(p) | ||
function step(p) { | ||
// final state, return composition result | ||
if (p.s.state < 0 || p.s.state >= fsm.length) { | ||
console.log(`Entering final state`) | ||
console.log(JSON.stringify(p.params)) | ||
return finishers.reduce((promise, _finish) => promise.then(() => _finish(p)), Promise.resolve()) | ||
.then(() => p.params.error ? p.params : { params: p.params }) | ||
} | ||
return params => Promise.resolve().then(() => invoke(params)).catch(internalError) | ||
// process one state | ||
const node = fsm[p.s.state] // json definition for index state | ||
if (node.path !== undefined) console.log(`Entering composition${node.path}`) | ||
const index = p.s.state // current state | ||
p.s.state = p.s.state + (node.next || 1) // default next state | ||
return conductor[node.type]({ p, index, node, inspect, step }) || step(p) | ||
} | ||
// do invocation | ||
function invoke(params) { | ||
const p = { s: { state: 0, stack: [] }, params } // initial state | ||
return params => Promise.resolve().then(() => invoke(params)).catch(internalError) | ||
if (params.$resume !== undefined) { | ||
if (!isObject(params.$resume)) return badRequest('The type of optional $resume parameter must be object') | ||
const resuming = params.$resume.stack | ||
Object.assign(p.s, params.$resume) | ||
if (typeof p.s.state !== 'number') return badRequest('The type of optional $resume.state parameter must be number') | ||
if (!Array.isArray(p.s.stack)) return badRequest('The type of optional $resume.stack parameter must be an array') | ||
delete params.$resume | ||
if (resuming) inspect(p) // handle error objects when resuming | ||
} | ||
// do invocation | ||
function invoke(params) { | ||
const p = { s: { state: 0, stack: [] }, params } // initial state | ||
return step(p) | ||
if (params.$resume !== undefined) { | ||
if (!isObject(params.$resume)) return badRequest('The type of optional $resume parameter must be object') | ||
const resuming = params.$resume.stack | ||
Object.assign(p.s, params.$resume) | ||
if (typeof p.s.state !== 'number') return badRequest('The type of optional $resume.state parameter must be number') | ||
if (!Array.isArray(p.s.stack)) return badRequest('The type of optional $resume.stack parameter must be an array') | ||
delete params.$resume | ||
if (resuming) inspect(p) // handle error objects when resuming | ||
} | ||
return step(p) | ||
} | ||
return { composer, runtime } | ||
} | ||
module.exports = main().composer | ||
module.exports = composer |
@@ -202,3 +202,3 @@ # Compose Command | ||
``` | ||
ok: created actions /_/authenticate,/_/success,/_/failure,/_/demo | ||
ok: created /_/authenticate,/_/success,/_/failure,/_/demo | ||
``` | ||
@@ -211,3 +211,3 @@ Or: | ||
``` | ||
ok: created actions /_/authenticate,/_/success,/_/failure,/_/demo | ||
ok: created /_/authenticate,/_/success,/_/failure,/_/demo | ||
``` | ||
@@ -214,0 +214,0 @@ The `compose` command synthesizes and deploys a conductor action that implements the |
@@ -30,3 +30,3 @@ # Composer Module | ||
wsk.compositions.deploy('demo', composition) // deploy composition | ||
wsk.compositions.deploy({ name: 'demo', composition }) // deploy composition | ||
.then(() => wsk.actions.invoke({ name: 'demo', params: { password: 'abc123' }, blocking: true })) // invoke composition | ||
@@ -96,3 +96,3 @@ .then(({ response }) => console.log(JSON.stringify(response.result, null, 4)), console.error) | ||
`wsk.compositions.deploy(name, composition, [combinators])` optionally lowers, encodes, and deploys the composition `composition`. More precisely, it successively deploys all the actions defined in `composition` as well as `composition` itself (encoded as a conductor action). | ||
`wsk.compositions.deploy({ name, composition, [combinators] })` optionally lowers, encodes, and deploys the composition `composition`. More precisely, it successively deploys all the actions defined in `composition` as well as `composition` itself (encoded as a conductor action). | ||
@@ -99,0 +99,0 @@ The optional `combinators` parameter controls the optional lowering. See [lower](#lower) for details. |
@@ -80,3 +80,3 @@ # Composition Templates | ||
compose example.js --deploy example | ||
ok: created action /_/example | ||
ok: created /_/example | ||
``` | ||
@@ -83,0 +83,0 @@ ``` |
{ | ||
"name": "@ibm-functions/composer", | ||
"version": "0.6.1", | ||
"version": "0.7.0", | ||
"description": "Composer is an IBM Cloud Functions programming model for composing individual functions into larger applications.", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/ibm-functions/composer", |
@@ -93,3 +93,3 @@ # @ibm-functions/composer | ||
``` | ||
ok: created actions /_/authenticate,/_/success,/_/failure,/_/demo | ||
ok: created /_/authenticate,/_/success,/_/failure,/_/demo | ||
``` | ||
@@ -96,0 +96,0 @@ The `compose` command synthesizes and deploys an action named `demo` that |
@@ -29,4 +29,4 @@ /* | ||
wsk.compositions.deploy('demo', composition) // deploy composition | ||
wsk.compositions.deploy({ name: 'demo', composition }) // deploy composition | ||
.then(() => wsk.actions.invoke({ name: 'demo', params: { password: 'abc123' }, blocking: true })) // invoke composition | ||
.then(({ response }) => console.log(JSON.stringify(response.result, null, 4)), console.error) |
@@ -10,3 +10,3 @@ const assert = require('assert') | ||
// deploy and invoke composition | ||
const invoke = (composition, params = {}, blocking = true) => wsk.compositions.deploy(name, composition).then(() => wsk.actions.invoke({ name, params, blocking })) | ||
const invoke = (composition, params = {}, blocking = true) => wsk.compositions.deploy({ name, composition }).then(() => wsk.actions.invoke({ name, params, blocking })) | ||
@@ -13,0 +13,0 @@ describe('composer', function () { |
Sorry, the diff of this file is not supported yet
144084
1288