vulpes
Version:
Job management framework
1,176 lines (965 loc) • 66.3 kB
JavaScript
"use strict";
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var EventEmitter = require("eventemitter3");
var merge = require("merge");
var VError = require("verror");
var ChannelQueue = require("@buttercup/channel-queue");
var Scheduler = require("./Scheduler.js");
var Tracker = require("./Tracker.js");
var Storage = require("./storage/Storage.js");
var MemoryStorage = require("./storage/MemoryStorage.js");
var Helper = require("./helper/Helper.js");
var ArtifactManager = require("./ArtifactManager.js");
var _require = require("./jobGeneration.js"),
filterJobInitObject = _require.filterJobInitObject,
generateEmptyJob = _require.generateEmptyJob,
validateJobProperties = _require.validateJobProperties;
var _require2 = require("./jobQuery.js"),
jobMatches = _require2.jobMatches;
var _require3 = require("./jobMediation.js"),
addJobBatch = _require3.addJobBatch,
ensureParentsComplete = _require3.ensureParentsComplete,
jobCanBeRestarted = _require3.jobCanBeRestarted,
jobSatisfiesPredicates = _require3.jobSatisfiesPredicates,
pickFirstJob = _require3.pickFirstJob,
prepareJobForWorker = _require3.prepareJobForWorker;
var _require4 = require("./time.js"),
getTimestamp = _require4.getTimestamp;
var _require5 = require("./jobSorting.js"),
filterDuplicateJobs = _require5.filterDuplicateJobs,
sortJobs = _require5.sortJobs,
sortJobsByPriority = _require5.sortJobsByPriority;
var _require6 = require("./jobStats.js"),
updateStatsForJob = _require6.updateStatsForJob;
var _require7 = require("./migrations.js"),
removeAllLegacyLogs = _require7.removeAllLegacyLogs;
var _require8 = require("./attachments.js"),
extractAttachments = _require8.extractAttachments;
var _require9 = require("./streams.js"),
waitForStream = _require9.waitForStream;
var _require10 = require("./symbols.js"),
ERROR_CODE_ALREADY_INIT = _require10.ERROR_CODE_ALREADY_INIT,
ERROR_CODE_ALREADY_SUCCEEDED = _require10.ERROR_CODE_ALREADY_SUCCEEDED,
ERROR_CODE_CANNOT_RESTART = _require10.ERROR_CODE_CANNOT_RESTART,
ERROR_CODE_HELPER_INVALID = _require10.ERROR_CODE_HELPER_INVALID,
ERROR_CODE_INVALID_JOB_RESULT = _require10.ERROR_CODE_INVALID_JOB_RESULT,
ERROR_CODE_INVALID_JOB_STATUS = _require10.ERROR_CODE_INVALID_JOB_STATUS,
ERROR_CODE_NO_JOB_FOR_ID = _require10.ERROR_CODE_NO_JOB_FOR_ID,
ERROR_CODE_NOT_INIT = _require10.ERROR_CODE_NOT_INIT,
ERROR_CODE_PREDICATE_NOT_SATISFIED = _require10.ERROR_CODE_PREDICATE_NOT_SATISFIED,
ITEM_TYPE = _require10.ITEM_TYPE,
ITEM_TYPE_JOB = _require10.ITEM_TYPE_JOB,
JOB_PRIORITY_HIGH = _require10.JOB_PRIORITY_HIGH,
JOB_PRIORITY_LOW = _require10.JOB_PRIORITY_LOW,
JOB_PRIORITY_NORMAL = _require10.JOB_PRIORITY_NORMAL,
JOB_RESULT_TYPE_FAILURE = _require10.JOB_RESULT_TYPE_FAILURE,
JOB_RESULT_TYPE_FAILURE_SOFT = _require10.JOB_RESULT_TYPE_FAILURE_SOFT,
JOB_RESULT_TYPE_SUCCESS = _require10.JOB_RESULT_TYPE_SUCCESS,
JOB_RESULT_TYPE_TIMEOUT = _require10.JOB_RESULT_TYPE_TIMEOUT,
JOB_RESULT_TYPES_REXP = _require10.JOB_RESULT_TYPES_REXP,
JOB_STATUS_PENDING = _require10.JOB_STATUS_PENDING,
JOB_STATUS_RUNNING = _require10.JOB_STATUS_RUNNING,
JOB_STATUS_STOPPED = _require10.JOB_STATUS_STOPPED,
JOB_TIMELIMIT_DEFAULT = _require10.JOB_TIMELIMIT_DEFAULT;
var ITEM_JOB_PREFIX = /^job\//;
/**
* Job priorities
* @name JobPriorities
* @readonly
* @enum {String}
*/
var JOB_PRIORITIES = {
High: JOB_PRIORITY_HIGH,
Normal: JOB_PRIORITY_NORMAL,
Low: JOB_PRIORITY_LOW
};
/**
* Job result types
* @name JobResultTypes
* @readonly
* @enum {String}
*/
var JOB_RESULTS = {
Failure: JOB_RESULT_TYPE_FAILURE,
SoftFailure: JOB_RESULT_TYPE_FAILURE_SOFT,
Success: JOB_RESULT_TYPE_SUCCESS,
Timeout: JOB_RESULT_TYPE_TIMEOUT
};
/**
* Job statuses
* @name JobStatuses
* @readonly
* @enum {String}
*/
var JOB_STATUSES = {
Pending: JOB_STATUS_PENDING,
Running: JOB_STATUS_RUNNING,
Stopped: JOB_STATUS_STOPPED
};
var newNotInitialisedError = function newNotInitialisedError() {
return new VError({ info: { code: ERROR_CODE_NOT_INIT } }, "Service not initialised");
};
/**
* Service for managing jobs
* @augments EventEmitter
* @memberof module:Vulpes
*/
var Service = function (_EventEmitter) {
_inherits(Service, _EventEmitter);
/**
* @typedef {Object} ServiceOptions
* @property {ArtifactManager=} artifactManager - Override for the ArtifactManager instance
* @property {Boolean=} enableScheduling - Control whether or not the scheduling piece of the Service
* is enabled or not. Default is true.
*/
/**
* Contrsuctor for the Service class
* @param {ServiceOptions=} param0 Options for the new service
*/
function Service() {
_classCallCheck(this, Service);
var _this = _possibleConstructorReturn(this, (Service.__proto__ || Object.getPrototypeOf(Service)).call(this));
var storage = void 0,
options = {};
if (arguments[0] instanceof Storage) {
storage = arguments[0];
options = arguments[1] || {};
} else if (arguments[0] && _typeof(arguments[0]) === "object") {
options = arguments[0];
}
storage = storage || options.storage || new MemoryStorage();
var _options = options,
_options$artifactMana = _options.artifactManager,
artifactManager = _options$artifactMana === undefined ? new ArtifactManager() : _options$artifactMana,
_options$enableSchedu = _options.enableScheduling,
enableScheduling = _options$enableSchedu === undefined ? true : _options$enableSchedu;
if (storage instanceof Storage !== true) {
throw new Error("Failed instantiating Service: Provided storage is of invalid type");
}
_this._storage = storage;
_this._artifactManager = artifactManager;
_this._timeLimit = JOB_TIMELIMIT_DEFAULT;
_this._channelQueue = new ChannelQueue();
_this._scheduler = new Scheduler(_this);
if (!enableScheduling) {
_this._scheduler.enabled = false;
}
_this._tracker = new Tracker(_this);
_this._helpers = [];
_this._initialised = false;
_this._shutdown = false;
return _this;
}
/**
* Check that the instance is alive and not shut down
* @type {Boolean}
* @readonly
* @memberof Service
*/
_createClass(Service, [{
key: "addJob",
/**
* Add a new job
* @param {NewJob=} properties The new job's properties
* @returns {Promise.<String>} A promise that resolves with the job's ID
* @memberof Service
*/
value: function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
var _this2 = this;
var properties = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
if (this._initialised) {
_context2.next = 2;
break;
}
return _context2.abrupt("return", Promise.reject(newNotInitialisedError()));
case 2:
return _context2.abrupt("return", this.jobQueue.enqueue(_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var initProps, job;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
initProps = filterJobInitObject(properties);
job = validateJobProperties(merge.recursive(generateEmptyJob(), { timeLimit: _this2.timeLimit }, initProps));
_context.next = 4;
return _this2.storage.setItem(job.id, job);
case 4:
_this2.emit("jobAdded", { id: job.id });
return _context.abrupt("return", job.id);
case 6:
case "end":
return _context.stop();
}
}
}, _callee, _this2);
}))));
case 3:
case "end":
return _context2.stop();
}
}
}, _callee2, this);
}));
function addJob() {
return _ref.apply(this, arguments);
}
return addJob;
}()
/**
* Add an array of new jobs (a batch)
* @param {NewJob[]} jobs An array of new job objects
* @returns {Promise.<Job[]>} An array of newly created jobs
* @memberof Service
*/
}, {
key: "addJobs",
value: function () {
var _ref3 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(jobs) {
return regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
if (this._initialised) {
_context3.next = 2;
break;
}
return _context3.abrupt("return", Promise.reject(newNotInitialisedError()));
case 2:
_context3.next = 4;
return addJobBatch(this, jobs);
case 4:
return _context3.abrupt("return", _context3.sent);
case 5:
case "end":
return _context3.stop();
}
}
}, _callee3, this);
}));
function addJobs(_x2) {
return _ref3.apply(this, arguments);
}
return addJobs;
}()
/**
* Archive a job so it will be removed from queries
* @param {String} jobID The job ID
* @returns {Promise}
* @memberof Service
*/
}, {
key: "archiveJob",
value: function () {
var _ref4 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4(jobID) {
return regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
_context4.next = 2;
return this.updateJob(jobID, { archived: true, times: { archived: getTimestamp() } }, { filterProps: false });
case 2:
this.emit("jobArchived", { id: jobID });
case 3:
case "end":
return _context4.stop();
}
}
}, _callee4, this);
}));
function archiveJob(_x3) {
return _ref4.apply(this, arguments);
}
return archiveJob;
}()
/**
* Get a job by its ID
* @param {String} jobID The job ID
* @returns {Promise.<Object|null>} A promise that resolves with the job
* or null if not found
* @memberof Service
*/
}, {
key: "getJob",
value: function () {
var _ref5 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee5(jobID) {
return regeneratorRuntime.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
if (this._initialised) {
_context5.next = 2;
break;
}
return _context5.abrupt("return", Promise.reject(newNotInitialisedError()));
case 2:
_context5.next = 4;
return this.storage.getItem(jobID);
case 4:
return _context5.abrupt("return", _context5.sent);
case 5:
case "end":
return _context5.stop();
}
}
}, _callee5, this);
}));
function getJob(_x4) {
return _ref5.apply(this, arguments);
}
return getJob;
}()
/**
* Options for fetching job children
* @typedef {Object} GetJobChildrenOptions
* @property {Boolean=} fullProgeny - Fetch the full progeny of the job
* (all of the children and their children)
*/
/**
* Get a job's children (shallow)
* @param {String} jobID The job ID
* @param {GetJobChildrenOptions=} options Options for fetching
* @returns {Promise.<Array.<Job>>} A promise that resolves with an array
* of child jobs
* @memberof Service
*/
}, {
key: "getJobChildren",
value: function () {
var _ref6 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee7(jobID) {
var _this3 = this;
var _ref7 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref7$fullProgeny = _ref7.fullProgeny,
fullProgeny = _ref7$fullProgeny === undefined ? false : _ref7$fullProgeny;
var children,
_arguments,
options,
_args7 = arguments;
return regeneratorRuntime.wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
_context7.next = 2;
return this.queryJobs({
parents: function parents(_parents) {
return _parents.includes(jobID);
}
});
case 2:
children = _context7.sent;
if (!fullProgeny) {
_context7.next = 7;
break;
}
_arguments = Array.prototype.slice.call(_args7), options = _arguments[1];
_context7.next = 7;
return Promise.all(children.map(function () {
var _ref8 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee6(child) {
var childJobs;
return regeneratorRuntime.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
_context6.next = 2;
return _this3.getJobChildren(child.id, options);
case 2:
childJobs = _context6.sent;
children.push.apply(children, _toConsumableArray(childJobs));
case 4:
case "end":
return _context6.stop();
}
}
}, _callee6, _this3);
}));
return function (_x7) {
return _ref8.apply(this, arguments);
};
}()));
case 7:
return _context7.abrupt("return", filterDuplicateJobs(children));
case 8:
case "end":
return _context7.stop();
}
}
}, _callee7, this);
}));
function getJobChildren(_x5) {
return _ref6.apply(this, arguments);
}
return getJobChildren;
}()
/**
* Options for fetching job parents
* @typedef {Object} GetJobParentsOptions
* @property {Boolean=} fullAncestry - Fetch the full fullAncestry of the job
* (all of the parents and their parents)
*/
/**
* Get the parents of a job
* @param {String} jobID The ID of the job
* @param {GetJobParentsOptions=} options Job fetching options
* @returns {Promise.<Array.<Job>>} A promise that resolves with an array of
* jobs
* @memberof Service
*/
}, {
key: "getJobParents",
value: function () {
var _ref9 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee8(jobID) {
var _this4 = this;
var _ref10 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref10$fullAncestry = _ref10.fullAncestry,
fullAncestry = _ref10$fullAncestry === undefined ? false : _ref10$fullAncestry;
var job,
parents,
_arguments2,
options,
parentsParents,
_args8 = arguments;
return regeneratorRuntime.wrap(function _callee8$(_context8) {
while (1) {
switch (_context8.prev = _context8.next) {
case 0:
_context8.next = 2;
return this.getJob(jobID);
case 2:
job = _context8.sent;
if (!(job.parents.length <= 0)) {
_context8.next = 5;
break;
}
return _context8.abrupt("return", Promise.resolve([]));
case 5:
_context8.next = 7;
return Promise.all(job.parents.map(function (parentID) {
return _this4.getJob(parentID);
}));
case 7:
parents = _context8.sent;
if (!fullAncestry) {
_context8.next = 14;
break;
}
_arguments2 = Array.prototype.slice.call(_args8), options = _arguments2[1];
_context8.next = 12;
return Promise.all(job.parents.map(function (parentID) {
return _this4.getJobParents(parentID, options);
}));
case 12:
parentsParents = _context8.sent;
parentsParents.forEach(function (jobs) {
parents.push.apply(parents, _toConsumableArray(jobs));
});
case 14:
return _context8.abrupt("return", filterDuplicateJobs(parents));
case 15:
case "end":
return _context8.stop();
}
}
}, _callee8, this);
}));
function getJobParents(_x8) {
return _ref9.apply(this, arguments);
}
return getJobParents;
}()
/**
* Options for fetching a job tree
* @typedef {Object} GetJobTreeOptions
* @property {Boolean=} resolveParents - Fetch the ancestry of the specified
* job, not just the children. Defaults to true (full tree).
*/
/**
* Get a job tree
* Fetches an array of jobs that form the relationship tree
* (parents-children) of a certain job.
* @param {String} jobID The job ID to branch from
* @param {GetJobTreeOptions=} options Fetch options for the tree
* processing
* @returns {Promise.<Array.<Job>>} A deduplicated array of jobs containing,
* if configured, all of the job's ancestry and progeny. Will also contain
* the job itself.
* @memberof Service
*/
}, {
key: "getJobTree",
value: function () {
var _ref11 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee9(jobID) {
var _ref12 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref12$resolveParents = _ref12.resolveParents,
resolveParents = _ref12$resolveParents === undefined ? true : _ref12$resolveParents;
var job, tree, parents, children;
return regeneratorRuntime.wrap(function _callee9$(_context9) {
while (1) {
switch (_context9.prev = _context9.next) {
case 0:
_context9.next = 2;
return this.getJob(jobID);
case 2:
job = _context9.sent;
if (job) {
_context9.next = 5;
break;
}
return _context9.abrupt("return", []);
case 5:
tree = [job];
if (!resolveParents) {
_context9.next = 11;
break;
}
_context9.next = 9;
return this.getJobParents(jobID, { fullAncestry: true });
case 9:
parents = _context9.sent;
tree.push.apply(tree, _toConsumableArray(parents));
case 11:
_context9.next = 13;
return this.getJobChildren(jobID, { fullProgeny: true });
case 13:
children = _context9.sent;
tree.push.apply(tree, _toConsumableArray(children));
return _context9.abrupt("return", filterDuplicateJobs(tree));
case 16:
case "end":
return _context9.stop();
}
}
}, _callee9, this);
}));
function getJobTree(_x10) {
return _ref11.apply(this, arguments);
}
return getJobTree;
}()
/**
* Get the next job that should be started
* This method is expensive as it sorts available jobs by priority first,
* before returning the very next job that should be started.
* @returns {Promise.<Object|null>} A promise that resolves with the job
* or null if none available
* @memberof Service
*/
}, {
key: "getNextJob",
value: function getNextJob() {
var _this5 = this;
return this.queryJobs({
status: function status(_status) {
return [JOB_STATUS_PENDING, JOB_STATUS_STOPPED].includes(_status);
},
"result.type": function resultType(type) {
return !type || type === JOB_RESULT_TYPE_FAILURE_SOFT;
}
}).then(sortJobsByPriority).then(function (jobs) {
return pickFirstJob(_this5, jobs);
}).then(function (job) {
return job || null;
});
}
/**
* Initialise the Service instance
* Must be called before any other operation
* @returns {Promise} A promise that resolves once initialisation
* has been completed
* @memberof Service
*/
}, {
key: "initialise",
value: function () {
var _ref13 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee10() {
var _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, helper;
return regeneratorRuntime.wrap(function _callee10$(_context10) {
while (1) {
switch (_context10.prev = _context10.next) {
case 0:
if (!this._initialised) {
_context10.next = 2;
break;
}
return _context10.abrupt("return", Promise.reject(new VError({ info: { code: ERROR_CODE_ALREADY_INIT } }, "Service already initialised")));
case 2:
_context10.next = 4;
return this.storage.initialise();
case 4:
_context10.next = 6;
return this.artifactManager.initialise(this);
case 6:
_context10.next = 8;
return this.scheduler.initialise();
case 8:
this._initialised = true;
_context10.next = 11;
return removeAllLegacyLogs(this);
case 11:
_iteratorNormalCompletion = true;
_didIteratorError = false;
_iteratorError = undefined;
_context10.prev = 14;
_iterator = this.helpers[Symbol.iterator]();
case 16:
if (_iteratorNormalCompletion = (_step = _iterator.next()).done) {
_context10.next = 23;
break;
}
helper = _step.value;
_context10.next = 20;
return helper.initialise();
case 20:
_iteratorNormalCompletion = true;
_context10.next = 16;
break;
case 23:
_context10.next = 29;
break;
case 25:
_context10.prev = 25;
_context10.t0 = _context10["catch"](14);
_didIteratorError = true;
_iteratorError = _context10.t0;
case 29:
_context10.prev = 29;
_context10.prev = 30;
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
case 32:
_context10.prev = 32;
if (!_didIteratorError) {
_context10.next = 35;
break;
}
throw _iteratorError;
case 35:
return _context10.finish(32);
case 36:
return _context10.finish(29);
case 37:
this.emit("initialised");
case 38:
case "end":
return _context10.stop();
}
}
}, _callee10, this, [[14, 25, 29, 37], [30,, 32, 36]]);
}));
function initialise() {
return _ref13.apply(this, arguments);
}
return initialise;
}()
/**
* @typedef {Object} QueryJobsOptions
* @property {Number=} limit - Limit the number of jobs that are returned by the
* query. Defaults to Infinity.
* @property {String=} sort - Property to sort by. Defaults to "created". Can be
* set to created/status/priority/type.
* @property {String=} order - Sorting order: asc/desc (default "desc")
* @property {Number=} start - The starting offset (index) for when to start
* collecting search results. Should be used together with `limit` to perform
* pagination. Defaults to 0.
*/
/**
* Perform a jobs query
* Query for an array of jobs by the job's properties. This method streams all
* jobs from storage, testing each individually against the query. Once a group
* of jobs is collected, further sorting and limiting are applied before once
* again streaming the jobs to find the full matches to return.
* @param {Object=} query The object query to perform
* @param {QueryJobsOptions=} options Options for querying jobs, like sorting
* @returns {Promise.<Array.<Job>>} Returns a promise that resolves with
* an array of jobs
* @memberof Service
*/
}, {
key: "queryJobs",
value: function () {
var _ref14 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee11() {
var query = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _ref15 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref15$start = _ref15.start,
start = _ref15$start === undefined ? 0 : _ref15$start,
_ref15$limit = _ref15.limit,
limit = _ref15$limit === undefined ? Infinity : _ref15$limit,
_ref15$sort = _ref15.sort,
sort = _ref15$sort === undefined ? "created" : _ref15$sort,
_ref15$order = _ref15.order,
order = _ref15$order === undefined ? "desc" : _ref15$order;
var jobStream, stats, allMatchedJobs, sortedJobs, output;
return regeneratorRuntime.wrap(function _callee11$(_context11) {
while (1) {
switch (_context11.prev = _context11.next) {
case 0:
if (this._initialised) {
_context11.next = 2;
break;
}
throw newNotInitialisedError();
case 2:
query.archived = typeof query.archived === "boolean" ? query.archived : function (archived) {
return archived === false || archived === undefined;
};
_context11.next = 5;
return this.storage.streamItems();
case 5:
jobStream = _context11.sent;
stats = this.tracker.statsTemplate;
// First build an index of all job results
allMatchedJobs = [];
jobStream.on("data", function (job) {
if (job[ITEM_TYPE] === ITEM_TYPE_JOB) {
// Handle query check first
if (jobMatches(job, query)) {
allMatchedJobs.push(job);
}
// Process jobs for stats updates
updateStatsForJob(stats, job);
}
});
_context11.next = 11;
return waitForStream(jobStream);
case 11:
// Update stats
this.tracker.updateStats(stats);
// Sort jobs
sortedJobs = sortJobs(allMatchedJobs, [{
property: sort,
direction: order
}]);
// Select range
output = sortedJobs.slice(start, limit === Infinity ? limit : start + limit);
// Record total matched
output.total = sortedJobs.length;
return _context11.abrupt("return", output);
case 16:
case "end":
return _context11.stop();
}
}
}, _callee11, this);
}));
function queryJobs() {
return _ref14.apply(this, arguments);
}
return queryJobs;
}()
/**
* Completely delete a job
* @param {String} jobID The ID of the job to reset
* @returns {Promise} A promise that resolves once the job has
* been removed
* @memberof Service
*/
}, {
key: "removeJob",
value: function () {
var _ref16 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee12(jobID) {
return regeneratorRuntime.wrap(function _callee12$(_context12) {
while (1) {
switch (_context12.prev = _context12.next) {
case 0:
_context12.next = 2;
return this.storage.removeItem(jobID);
case 2:
this.emit("jobDeleted", { id: jobID });
case 3:
case "end":
return _context12.stop();
}
}
}, _callee12, this);
}));
function removeJob(_x14) {
return _ref16.apply(this, arguments);
}
return removeJob;
}()
/**
* Reset a failed job
* @param {String} jobID The ID of the job to reset
* @returns {Promise} A promise that resolves once the job has
* been reset
* @memberof Service
*/
}, {
key: "resetJob",
value: function () {
var _ref17 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee13(jobID) {
var job;
return regeneratorRuntime.wrap(function _callee13$(_context13) {
while (1) {
switch (_context13.prev = _context13.next) {
case 0:
if (this._initialised) {
_context13.next = 2;
break;
}
return _context13.abrupt("return", Promise.reject(newNotInitialisedError()));
case 2:
_context13.next = 4;
return this.getJob(jobID);
case 4:
job = _context13.sent;
if (!(job.status !== JOB_STATUS_STOPPED)) {
_context13.next = 9;
break;
}
throw new VError({ info: { code: ERROR_CODE_INVALID_JOB_STATUS } }, "Invalid job status (" + job.status + "): " + job.id);
case 9:
if (!(job.result.type === JOB_RESULT_TYPE_SUCCESS)) {
_context13.next = 11;
break;
}
throw new VError({ info: { code: ERROR_CODE_ALREADY_SUCCEEDED } }, "Job already succeeded: " + job.id);
case 11:
if (typeof job.predicate.attemptsMax === "number" && job.attempts >= job.predicate.attemptsMax) {
job.predicate.attemptsMax = job.attempts + 1;
}
job.status = JOB_STATUS_PENDING;
job.result.type = null;
_context13.next = 16;
return this.storage.setItem(job.id, job);
case 16:
this.emit("jobReset", { id: job.id });
case 17:
case "end":
return _context13.stop();
}
}
}, _callee13, this);
}));
function resetJob(_x15) {
return _ref17.apply(this, arguments);
}
return resetJob;
}()
/**
* Shutdown the instance
* @returns {Promise}
* @memberof Service
*/
}, {
key: "shutdown",
value: function () {
var _ref18 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee14() {
return regeneratorRuntime.wrap(function _callee14$(_context14) {
while (1) {
switch (_context14.prev = _context14.next) {
case 0:
this.scheduler.shutdown();
this.helpers.forEach(function (helper) {
helper.shutdown();
});
_context14.next = 4;
return this.artifactManager.shutdown();
case 4:
_context14.next = 6;
return this.storage.shutdown();
case 6:
this._helpers = [];
this._shutdown = true;
this._initialised = false;
case 9:
case "end":
return _context14.stop();
}
}
}, _callee14, this);
}));
function shutdown() {
return _ref18.apply(this, arguments);
}
return shutdown;
}()
/**
* Start job options
* @typedef {Object} StartJobOptions
* @property {Boolean=} executePredicate - Execute the predicate function
* before running the task
*/
/**
* Start a job
* @param {String=} jobID The job ID to start. If none provided the Service
* will attempt to start the next job by priority. If none is found it
* will simply resolve with null. If the job ID is specified by not found
* an exception will be thrown.
* @param {Object=} options Configuration options
* @returns {Promise.<Object>} A promise that resolves with job data for a
* worker
* @memberof Service
*/
}, {
key: "startJob",
value: function () {
var _ref19 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee16() {
var _this6 = this;
var jobID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var _ref20 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref20$executePredica = _ref20.executePredicate,
executePredicate = _ref20$executePredica === undefined ? true : _ref20$executePredica;
return regeneratorRuntime.wrap(function _callee16$(_context16) {
while (1) {
switch (_context16.prev = _context16.next) {
case 0:
if (this._initialised) {
_context16.next = 2;
break;
}
return _context16.abrupt("return", Promise.reject(newNotInitialisedError()));
case 2:
return _context16.abrupt("return", this.jobQueue.enqueue(_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee15() {
var job, _ref22, satisfies, predicate;
return regeneratorRuntime.wrap(function _callee15$(_context15) {
while (1) {
switch (_context15.prev = _context15.next) {
case 0:
_context15.next = 2;
return jobID === null || jobID === undefined ? _this6.getNextJob() : _this6.getJob(jobID);
case 2:
job = _context15.sent;
if (!(!job && !jobID)) {
_context15.next = 7;
break;
}
return _context15.abrupt("return", Promise.resolve(null));
case 7:
if (job) {
_context15.next = 9;
break;
}
throw new VError({ info: { code: ERROR_CODE_NO_JOB_FOR_ID } }, "No job found for ID: " + jobID);
case 9:
if (!(job.status === JOB_STATUS_STOPPED && jobCanBeRestarted(job) === false)) {
_context15.next = 13;
break;
}
throw new VError({ info: { code: ERROR_CODE_CANNOT_RESTART } }, "Job not valid to restart: " + job.id);
case 13:
if (!(job.status !== JOB_STATUS_PENDING && job.status !== JOB_STATUS_STOPPED)) {
_context15.next = 15;
break;
}
throw new VError({ info: { code: ERROR_CODE_INVALID_JOB_STATUS } }, "Invalid job status (" + job.status + "): " + job.id);
case 15:
_context15.next = 17;
return ensureParentsComplete(_this6, job);
case 17:
if (!executePredicate) {
_context15.next = 25;
break;
}
_context15.next = 20;
return jo