UNPKG

vulpes

Version:
1,176 lines (965 loc) 66.3 kB
"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