UNPKG

@vendure/core

Version:

A modern, headless ecommerce framework

232 lines 8.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Job = void 0; const generated_types_1 = require("@vendure/common/lib/generated-types"); const shared_utils_1 = require("@vendure/common/lib/shared-utils"); const vendure_logger_1 = require("../config/logger/vendure-logger"); /** * @description * A Job represents a piece of work to be run in the background, i.e. outside the request-response cycle. * It is intended to be used for long-running work triggered by API requests. Jobs should now generally * be directly instantiated. Rather, the {@link JobQueue} `add()` method should be used to create and * add a new Job to a queue. * * @docsCategory JobQueue * @docsPage Job * @docsWeight 0 */ class Job { get name() { return this.queueName; } get data() { return this._data; } get state() { return this._state; } get progress() { return this._progress; } get result() { return this._result; } get error() { return this._error; } get isSettled() { return (!!this._settledAt && (this._state === generated_types_1.JobState.COMPLETED || this._state === generated_types_1.JobState.FAILED || this._state === generated_types_1.JobState.CANCELLED)); } get startedAt() { return this._startedAt; } get settledAt() { return this._settledAt; } get duration() { if (this.state === generated_types_1.JobState.PENDING || this.state === generated_types_1.JobState.RETRYING) { return 0; } const end = this._settledAt || new Date(); return +end - +(this._startedAt || end); } get attempts() { return this._attempts; } constructor(config) { this.eventListeners = { progress: [], }; this.queueName = config.queueName; this._data = this.ensureDataIsSerializable(config.data); this.id = config.id || null; this._state = config.state || generated_types_1.JobState.PENDING; this.retries = config.retries || 0; this._attempts = config.attempts || 0; this._progress = config.progress || 0; this.createdAt = config.createdAt || new Date(); this._result = config.result; this._error = config.error; this._startedAt = config.startedAt; this._settledAt = config.settledAt; } /** * @description * Calling this signifies that the job work has started. This method should be * called in the {@link JobQueueStrategy} `next()` method. */ start() { var _a, _b; if (this._state === generated_types_1.JobState.PENDING || this._state === generated_types_1.JobState.RETRYING) { this._state = generated_types_1.JobState.RUNNING; this._startedAt = new Date(); this._attempts++; vendure_logger_1.Logger.debug(`Job ${(_b = (_a = this.id) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : 'null'} [${this.queueName}] starting (attempt ${this._attempts} of ${this.retries + 1})`); } } /** * @description * Sets the progress (0 - 100) of the job. */ setProgress(percent) { this._progress = Math.min(percent || 0, 100); this.fireEvent('progress'); } /** * @description * Calling this method signifies that the job succeeded. The result * will be stored in the `Job.result` property. */ complete(result) { var _a, _b; this._result = result; this._progress = 100; this._state = generated_types_1.JobState.COMPLETED; this._settledAt = new Date(); vendure_logger_1.Logger.debug(`Job ${(_b = (_a = this.id) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : 'null'} [${this.queueName}] completed`); } /** * @description * Calling this method signifies that the job failed. */ fail(err) { var _a, _b, _c, _d; this._error = (err === null || err === void 0 ? void 0 : err.message) ? err.message : String(err); this._progress = 0; if (this.retries >= this._attempts) { this._state = generated_types_1.JobState.RETRYING; vendure_logger_1.Logger.warn(`Job ${(_b = (_a = this.id) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : 'null'} [${this.queueName}] failed (attempt ${this._attempts} of ${this.retries + 1})`); } else { if (this._state !== generated_types_1.JobState.CANCELLED) { this._state = generated_types_1.JobState.FAILED; vendure_logger_1.Logger.warn(`Job ${(_d = (_c = this.id) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : 'null'} [${this.queueName}] failed and will not retry.`); } this._settledAt = new Date(); } } cancel() { this._settledAt = new Date(); this._state = generated_types_1.JobState.CANCELLED; } /** * @description * Sets a RUNNING job back to PENDING. Should be used when the JobQueue is being * destroyed before the job has been completed. */ defer() { var _a, _b; if (this._state === generated_types_1.JobState.RUNNING) { this._state = generated_types_1.JobState.PENDING; this._attempts = 0; vendure_logger_1.Logger.debug(`Job ${(_b = (_a = this.id) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : 'null'} [${this.queueName}] deferred back to PENDING state`); } } /** * @description * Used to register event handler for job events */ on(eventType, listener) { this.eventListeners[eventType].push(listener); } off(eventType, listener) { const idx = this.eventListeners[eventType].indexOf(listener); if (idx !== -1) { this.eventListeners[eventType].splice(idx, 1); } } fireEvent(eventType) { for (const listener of this.eventListeners[eventType]) { listener(this); } } /** * All data in a job must be serializable. This method handles certain problem cases such as when * the data is a class instance with getters. Even though technically the "data" object should * already be serializable per the TS type, in practice data can slip through due to loss of * type safety. */ ensureDataIsSerializable(data, depth = 0, seen = new WeakMap(), path = []) { if (10 < depth) { return '[max depth reached]'; } if (data === null || data === undefined) { return data; } // Handle Date objects if (data instanceof Date) { return data.toISOString(); } if (typeof data === 'object' && data !== null) { const seenData = seen.get(data); if (seenData && seenData.length < path.length) { return `[circular *${path.join('.')}]`; } seen.set(data, path); } depth++; let output; if ((0, shared_utils_1.isObject)(data)) { output = {}; // If the object has a `.toJSON()` function defined, then // prefer it to any other type of serialization. if (this.hasToJSONFunction(data)) { output = data.toJSON(); } else { for (const key of Object.keys(data)) { output[key] = this.ensureDataIsSerializable(data[key], depth, seen, path.concat(key)); } if ((0, shared_utils_1.isClassInstance)(data)) { const descriptors = Object.getOwnPropertyDescriptors(Object.getPrototypeOf(data)); for (const name of Object.keys(descriptors)) { const descriptor = descriptors[name]; if (typeof descriptor.get === 'function') { output[name] = data[name]; } } } } } else if (Array.isArray(data)) { if (!output) { output = []; } data.forEach((item, i) => { output[i] = this.ensureDataIsSerializable(item, depth, seen, path.concat(i.toString())); }); } else { return data; } return output; } hasToJSONFunction(obj) { return typeof (obj === null || obj === void 0 ? void 0 : obj.toJSON) === 'function'; } } exports.Job = Job; //# sourceMappingURL=job.js.map