-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathrun-instantiated-machine.js
206 lines (168 loc) · 6.17 KB
/
run-instantiated-machine.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// TODO: move this into mp-localmachinepacks
module.exports = require('machine').build({
friendlyName: 'Run instantiated machine',
description: 'Run a machine which is already instantiated using the provided input values.',
inputs: {
machineInstance: {
example: '===',
description: 'The already-instantiated machine instance.',
required: true
},
argins: {
description: 'A set of input name/value pairs.',
example: [{
name: 'someInput',
value: '==='
}],
protect: true
}
},
exits: {
error: {
description: 'Unexpected error occurred.'
},
unknownInput: {
description: 'A configured input value does not correspond with a real input in this machine.'
},
cantStringifyOutput: {
description: 'The return value could not be stringified into JSON - perhaps it contains circular references?',
outputExample: {
outcome: 'success',
output: '===',
inspectedOutput: '{ stuff: "things" }',
duration: 2948
}
},
success: {
outputFriendlyName: 'What happened',
outputDescription: 'A dictionary reporting the outcome, output, duration, etc.',
extendedDescription:
'Note that we use the `===` exemplar here. This is necessary because it is the simplest '+
'way to represent `output: undefined`. Even if we set the `output` facet to `===`, since '+
'the base value for the ref type is `null` as of [email protected], that wouldn\'t work either.',
outputExample: '===',
// {
// outcome: 'success',
// output: '===?' (but also could be `undefined`),
// jsonStringifiedOutput: '{"stuff": "things"}',
// inspectedOutput: '{ stuff: "things" }',
// duration: 2948
// }
}
},
fn: function(inputs, exits) {
// Dependencies
var util = require('util');
var _ = require('@sailshq/lodash');
var async = require('async');
// Determine whether this machine instance is from prior than [email protected]
var isLegacyMachineInstance;
if (!inputs.machineInstance.getDef) {
isLegacyMachineInstance = true;
}
// Configure machine with input values
var liveDeferred = inputs.machineInstance((function (){
// Compress all the provided input values into the simple object
// format you would normally use when configuring a machine.
return _.reduce(inputs.argins, function (memo, configuredInput) {
var inputDef;
if (isLegacyMachineInstance) {
inputDef = inputs.machineInstance.inputs[configuredInput.name];
}
else {
inputDef = inputs.machineInstance.getDef().inputs[configuredInput.name];
}
// If `inputDef` does not exist, then `configuredInput.name` is wrong.
if (!inputDef) {
var _err = new Error('Configured input `'+configuredInput.name+'` does not correspond with a real input in this machine.');
_err.exit = 'unknownInput';
throw _err;
}
memo[configuredInput.name] = configuredInput.value;
// console.log('for %s, got: %s, a %s',configuredInput.name, memo[configuredInput.name], typeof memo[configuredInput.name]);
return memo;
}, {});
})());
// Build an empty `exitsTraversed` array that will track which exit was traversed,
// and its return value (if applicable).
var exitsTraversed = [ /* e.g. {
returnValue: {some: 'stuff'}
exitName: 'whateverExit'
} */ ];
// Loop through each of the machine's declared exits and set up
// a handler for it so we know which exit was traversed.
var callbacks = {};
var exitDefs;
if (isLegacyMachineInstance) {
exitDefs = liveDeferred.exits;
}
else {
exitDefs = inputs.machineInstance.getDef().exits;
}
_.each(exitDefs, function (exitDef, exitName){
callbacks[exitName] = function (result){
exitsTraversed.push({
returnValue: result,
exitName: exitName
});
if (exitsTraversed.length > 1) {
// This should never happen (log a warning)
console.warn('Invalid machine; exited multiple times:', exitsTraversed);
}
};
});
var startedAt = Date.now();
// Now start executing the machine
if (isLegacyMachineInstance) {
liveDeferred.exec(callbacks);
}
else {
liveDeferred.switch(callbacks);
}
// And set up a `whilst` loop that checks to see if the machine has
// halted every 50ms.
//
// (we can safely do this AFTER calling .exec() on the machine since we know there
// will always be at least a setTimeout(0) before the `fn` runs-- compare with
// `.execSync()`, where we wouldn't have such a guarantee)
async.whilst(
function check() {
return exitsTraversed.length < 1;
},
function lap(next){
setTimeout(function (){
next();
}, 25);
},
function afterwards(err) {
if (err) return exits.error(err);
// Build up result metadata obj
var whatHappened = {
outcome: exitsTraversed[0].exitName,
output: exitsTraversed[0].returnValue,
inspectedOutput: (function (){
var rawReturnValue = exitsTraversed[0].returnValue;
// Send back the `stack` property if this is an error instance
// (don't worry about util.inspecting it)
if (_.isError(rawReturnValue)) {
return rawReturnValue.stack;
}
return util.inspect(rawReturnValue, false, null);
})(),
duration: isLegacyMachineInstance ? liveDeferred._msElapsed : (Date.now() - startedAt)
};
// Attempt to stringify return value
try {
whatHappened.jsonStringifiedOutput = JSON.stringify(exitsTraversed[0].returnValue);
}
catch (e){
// If it doesnt work, call the `cantStringifyOutput` exit with all of the
// other result metadata that we were able to put together.
return exits.cantStringifyOutput(whatHappened);
}
// Otherwise we call `success`.
return exits.success(whatHappened);
}
);
}
});