@cedarjs/jobs 7.0.1-next.312 → 7.0.1-next.333

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,7 +49,7 @@ export interface CancelOptions {
49
49
  * be used to configure your custom adapter. If `options.logger` is included
50
50
  * you can access it via `this.logger`
51
51
  */
52
- export declare abstract class BaseAdapter<TOptions extends BaseAdapterOptions = BaseAdapterOptions, TScheduleReturn = void | Promise<void>> {
52
+ export declare abstract class BaseAdapter<TOptions extends BaseAdapterOptions = BaseAdapterOptions, TScheduleReturn = unknown> {
53
53
  options: TOptions;
54
54
  logger: NonNullable<TOptions['logger']>;
55
55
  constructor(options: TOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"BaseAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/BaseAdapter/BaseAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAG3E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,KAAK,EAAE,IAAI,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,cAAc,CAAC,IAAI,SAAS,OAAO,GAAG,OAAO;IAC5D,GAAG,EAAE,IAAI,CAAA;IACT,KAAK,EAAE,IAAI,GAAG,SAAS,CAAA;IACvB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,YAAY,CAAC,IAAI,SAAS,OAAO,GAAG,OAAO;IAC1D,GAAG,EAAE,IAAI,CAAA;IACT,KAAK,EAAE,IAAI,CAAA;IACX,KAAK,EAAE,KAAK,CAAA;CACb;AAED,MAAM,WAAW,cAAc,CAAC,IAAI,SAAS,OAAO,GAAG,OAAO;IAC5D,GAAG,EAAE,IAAI,CAAA;IACT,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,8BAAsB,WAAW,CAC/B,QAAQ,SAAS,kBAAkB,GAAG,kBAAkB,EACxD,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEtC,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAA;gBAE3B,OAAO,EAAE,QAAQ;IAU7B,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe;IAE5D;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IAEzE;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/D;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE3D;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/D;;OAEG;IACH,QAAQ,CAAC,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEtC;;;;;;;;OAQG;IACH,MAAM,CAAC,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CAC5D"}
1
+ {"version":3,"file":"BaseAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/BaseAdapter/BaseAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAG3E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,KAAK,EAAE,IAAI,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,cAAc,CAAC,IAAI,SAAS,OAAO,GAAG,OAAO;IAC5D,GAAG,EAAE,IAAI,CAAA;IACT,KAAK,EAAE,IAAI,GAAG,SAAS,CAAA;IACvB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,YAAY,CAAC,IAAI,SAAS,OAAO,GAAG,OAAO;IAC1D,GAAG,EAAE,IAAI,CAAA;IACT,KAAK,EAAE,IAAI,CAAA;IACX,KAAK,EAAE,KAAK,CAAA;CACb;AAED,MAAM,WAAW,cAAc,CAAC,IAAI,SAAS,OAAO,GAAG,OAAO;IAC5D,GAAG,EAAE,IAAI,CAAA;IACT,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,8BAAsB,WAAW,CAC/B,QAAQ,SAAS,kBAAkB,GAAG,kBAAkB,EACxD,eAAe,GAAG,OAAO;IAEzB,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAA;gBAE3B,OAAO,EAAE,QAAQ;IAY7B,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe;IAE5D;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IAEzE;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/D;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE3D;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/D;;OAEG;IACH,QAAQ,CAAC,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEtC;;;;;;;;OAQG;IACH,MAAM,CAAC,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CAC5D"}
package/dist/consts.d.ts CHANGED
@@ -12,6 +12,15 @@ export declare const DEFAULT_MAX_RUNTIME = 14400;
12
12
  export declare const MAX_RUNTIME_GRACE_PERIOD = 60;
13
13
  /** 5 seconds */
14
14
  export declare const DEFAULT_SLEEP_DELAY = 5;
15
+ /**
16
+ * Upper bound, in milliseconds, for the delay before a failed job is retried
17
+ * (7 days). The backoff grows with the fourth power of the number of attempts,
18
+ * so without a cap a job with a very high `maxAttempts` would get a `runAt` so
19
+ * far in the future that it can't be represented as a `Date`. The cap is above
20
+ * the longest delay reached with the default `maxAttempts` (23 ** 4 seconds,
21
+ * about 3.2 days), so it only affects jobs with a higher `maxAttempts`
22
+ */
23
+ export declare const MAX_BACKOFF_MS: number;
15
24
  export declare const DEFAULT_DELETE_SUCCESSFUL_JOBS = true;
16
25
  export declare const DEFAULT_DELETE_FAILED_JOBS = false;
17
26
  export declare const DEFAULT_LOGGER: Console;
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,KAAK,CAAA;AACtC,yBAAyB;AACzB,eAAO,MAAM,mBAAmB,QAAS,CAAA;AACzC;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAA;AAC1C,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,eAAO,MAAM,8BAA8B,OAAO,CAAA;AAClD,eAAO,MAAM,0BAA0B,QAAQ,CAAA;AAC/C,eAAO,MAAM,cAAc,SAAU,CAAA;AACrC,eAAO,MAAM,aAAa,YAAY,CAAA;AACtC,eAAO,MAAM,kBAAkB,MAAM,CAAA;AACrC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,kBAAkB,MAAO,CAAA;AACtC,eAAO,MAAM,oBAAoB,sBAAsB,CAAA;AAEvD,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAEjD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,YAAY,CAAA;AAC7C;;;GAGG;AACH,eAAO,MAAM,mBAAmB,WAAW,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,KAAK,CAAA;AACtC,yBAAyB;AACzB,eAAO,MAAM,mBAAmB,QAAS,CAAA;AACzC;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAA;AAC1C,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,IAAI,CAAA;AACpC;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,QAA0B,CAAA;AAErD,eAAO,MAAM,8BAA8B,OAAO,CAAA;AAClD,eAAO,MAAM,0BAA0B,QAAQ,CAAA;AAC/C,eAAO,MAAM,cAAc,SAAU,CAAA;AACrC,eAAO,MAAM,aAAa,YAAY,CAAA;AACtC,eAAO,MAAM,kBAAkB,MAAM,CAAA;AACrC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,kBAAkB,MAAO,CAAA;AACtC,eAAO,MAAM,oBAAoB,sBAAsB,CAAA;AAEvD,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAEjD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,YAAY,CAAA;AAC7C;;;GAGG;AACH,eAAO,MAAM,mBAAmB,WAAW,CAAA"}
package/dist/consts.js CHANGED
@@ -3,6 +3,7 @@ const DEFAULT_MAX_ATTEMPTS = 24;
3
3
  const DEFAULT_MAX_RUNTIME = 14400;
4
4
  const MAX_RUNTIME_GRACE_PERIOD = 60;
5
5
  const DEFAULT_SLEEP_DELAY = 5;
6
+ const MAX_BACKOFF_MS = 7 * 24 * 60 * 60 * 1e3;
6
7
  const DEFAULT_DELETE_SUCCESSFUL_JOBS = true;
7
8
  const DEFAULT_DELETE_FAILED_JOBS = false;
8
9
  const DEFAULT_LOGGER = console;
@@ -30,6 +31,7 @@ export {
30
31
  DEFAULT_WAIT,
31
32
  DEFAULT_WAIT_UNTIL,
32
33
  DEFAULT_WORK_QUEUE,
34
+ MAX_BACKOFF_MS,
33
35
  MAX_RUNTIME_GRACE_PERIOD,
34
36
  PROCESS_TITLE_PREFIX
35
37
  };
@@ -24,6 +24,7 @@ export declare const DEFAULTS: {
24
24
  deleteSuccessfulJobs: boolean;
25
25
  };
26
26
  export declare class Executor {
27
+ #private;
27
28
  options: Required<ExecutorOptions>;
28
29
  adapter: ExecutorOptions['adapter'];
29
30
  logger: NonNullable<ExecutorOptions['logger']>;
@@ -34,7 +35,20 @@ export declare class Executor {
34
35
  deleteSuccessfulJobs: NonNullable<ExecutorOptions['deleteSuccessfulJobs']>;
35
36
  constructor(options: ExecutorOptions);
36
37
  get jobIdentifier(): string;
37
- perform(): Promise<void>;
38
+ /**
39
+ * Runs the job and records its outcome with the adapter.
40
+ *
41
+ * Resolves to `false` if the adapter threw while recording the outcome of a
42
+ * failed attempt. The job can then still be locked by this worker, and the
43
+ * adapter may hand the same job straight back, so the caller should wait
44
+ * before looking for its next job
45
+ */
46
+ perform(): Promise<boolean>;
47
+ /**
48
+ * The number of milliseconds to wait before retrying a job that has been
49
+ * attempted `attempts` times: `attempts ** 4` seconds, capped at
50
+ * `MAX_BACKOFF_MS`
51
+ */
38
52
  backoffMilliseconds(attempts: number): number;
39
53
  }
40
54
  //# sourceMappingURL=Executor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Executor.d.ts","sourceRoot":"","sources":["../../src/core/Executor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAczE,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAIvD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,WAAW,CAAA;IACpB,GAAG,EAAE,OAAO,CAAA;IACZ,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,eAAO,MAAM,QAAQ;;;;;;CAMpB,CAAA;AAED,qBAAa,QAAQ;IACnB,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAA;IAClC,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnC,MAAM,EAAE,WAAW,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC9C,GAAG,EAAE,OAAO,CAAA;IACZ,WAAW,EAAE,WAAW,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAAA;IACxD,UAAU,EAAE,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAA;IACtD,gBAAgB,EAAE,WAAW,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC,CAAA;IAClE,oBAAoB,EAAE,WAAW,CAAC,eAAe,CAAC,sBAAsB,CAAC,CAAC,CAAA;gBAE9D,OAAO,EAAE,eAAe;IAsBpC,IAAI,aAAa,WAEhB;IAEK,OAAO;IAuGb,mBAAmB,CAAC,QAAQ,EAAE,MAAM;CAGrC"}
1
+ {"version":3,"file":"Executor.d.ts","sourceRoot":"","sources":["../../src/core/Executor.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAezE,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAKvD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,WAAW,CAAA;IACpB,GAAG,EAAE,OAAO,CAAA;IACZ,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,eAAO,MAAM,QAAQ;;;;;;CAMpB,CAAA;AAED,qBAAa,QAAQ;;IACnB,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAA;IAClC,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnC,MAAM,EAAE,WAAW,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC9C,GAAG,EAAE,OAAO,CAAA;IACZ,WAAW,EAAE,WAAW,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAAA;IACxD,UAAU,EAAE,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAA;IACtD,gBAAgB,EAAE,WAAW,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC,CAAA;IAClE,oBAAoB,EAAE,WAAW,CAAC,eAAe,CAAC,sBAAsB,CAAC,CAAC,CAAA;gBAE9D,OAAO,EAAE,eAAe;IAsBpC,IAAI,aAAa,WAEhB;IAED;;;;;;;OAOG;IACG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAqJjC;;;;OAIG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM;CAGrC"}
@@ -1,11 +1,11 @@
1
1
  import { setTimeout, clearTimeout } from "node:timers";
2
- import { CronExpressionParser } from "cron-parser";
3
2
  import {
4
3
  DEFAULT_MAX_ATTEMPTS,
5
4
  DEFAULT_MAX_RUNTIME,
6
5
  DEFAULT_DELETE_FAILED_JOBS,
7
6
  DEFAULT_DELETE_SUCCESSFUL_JOBS,
8
- DEFAULT_LOGGER
7
+ DEFAULT_LOGGER,
8
+ MAX_BACKOFF_MS
9
9
  } from "../consts.js";
10
10
  import {
11
11
  AdapterRequiredError,
@@ -13,6 +13,7 @@ import {
13
13
  JobTimeoutError
14
14
  } from "../errors.js";
15
15
  import { loadJob } from "../loaders.js";
16
+ import { nextCronRunAt } from "../util.js";
16
17
  import { executionContext } from "./executionContext.js";
17
18
  const DEFAULTS = {
18
19
  logger: DEFAULT_LOGGER,
@@ -49,6 +50,14 @@ class Executor {
49
50
  get jobIdentifier() {
50
51
  return `${this.job.id} (${this.job.path}:${this.job.name})`;
51
52
  }
53
+ /**
54
+ * Runs the job and records its outcome with the adapter.
55
+ *
56
+ * Resolves to `false` if the adapter threw while recording the outcome of a
57
+ * failed attempt. The job can then still be locked by this worker, and the
58
+ * adapter may hand the same job straight back, so the caller should wait
59
+ * before looking for its next job
60
+ */
52
61
  async perform() {
53
62
  this.logger.info(`[CedarJS Jobs] Started job ${this.jobIdentifier}`);
54
63
  const abortController = new AbortController();
@@ -68,12 +77,13 @@ class Executor {
68
77
  );
69
78
  })();
70
79
  await Promise.race([performPromise, timeoutPromise]);
71
- const runAt = this.job.cron ? CronExpressionParser.parse(this.job.cron).next().toDate() : void 0;
80
+ const runAt = this.job.cron ? nextCronRunAt(this.job.cron) : void 0;
72
81
  await this.adapter.success({
73
82
  job: this.job,
74
83
  runAt,
75
84
  deleteJob: !runAt && this.deleteSuccessfulJobs
76
85
  });
86
+ return true;
77
87
  } catch (rawError) {
78
88
  const error = rawError instanceof Error ? rawError : new Error(String(rawError));
79
89
  const timedOut = error instanceof JobTimeoutError;
@@ -85,41 +95,78 @@ class Executor {
85
95
  const errorMessage = `[CedarJS Jobs] Error in job ${this.jobIdentifier}: ${error.message}`;
86
96
  this.logger.error(errorMessage);
87
97
  this.logger.error(error.stack);
88
- if (timedOut) {
89
- this.logger.warn(
90
- this.job,
91
- `[CedarJS Jobs] Failed job ${this.jobIdentifier}: exceeded max runtime (${this.maxRuntime} seconds)`
92
- );
93
- await this.adapter.failure({
98
+ return await this.#recordError(error, timedOut);
99
+ } finally {
100
+ clearTimeout(timeoutId);
101
+ }
102
+ }
103
+ /**
104
+ * Records the outcome of a failed attempt with the adapter. Resolves to
105
+ * `false` if any of the adapter calls threw
106
+ */
107
+ async #recordError(error, timedOut) {
108
+ if (timedOut) {
109
+ this.logger.warn(
110
+ this.job,
111
+ `[CedarJS Jobs] Failed job ${this.jobIdentifier}: exceeded max runtime (${this.maxRuntime} seconds)`
112
+ );
113
+ return this.#adapterWrite(
114
+ () => this.adapter.failure({
94
115
  job: this.job,
95
116
  deleteJob: this.deleteFailedJobs,
96
117
  error
97
- });
98
- } else {
99
- await this.adapter.error({
100
- job: this.job,
101
- runAt: new Date(
102
- (/* @__PURE__ */ new Date()).getTime() + this.backoffMilliseconds(this.job.attempts)
103
- ),
104
- error
105
- });
106
- if (this.job.attempts >= this.maxAttempts) {
107
- this.logger.warn(
108
- this.job,
109
- `[CedarJS Jobs] Failed job ${this.jobIdentifier}: reached max attempts (${this.maxAttempts})`
110
- );
111
- await this.adapter.failure({
112
- job: this.job,
113
- deleteJob: this.deleteFailedJobs
114
- });
115
- }
116
- }
117
- } finally {
118
- clearTimeout(timeoutId);
118
+ })
119
+ );
120
+ }
121
+ const errorRecorded = await this.#adapterWrite(
122
+ () => this.adapter.error({
123
+ job: this.job,
124
+ runAt: new Date(
125
+ (/* @__PURE__ */ new Date()).getTime() + this.backoffMilliseconds(this.job.attempts)
126
+ ),
127
+ error
128
+ })
129
+ );
130
+ if (this.job.attempts < this.maxAttempts) {
131
+ return errorRecorded;
132
+ }
133
+ this.logger.warn(
134
+ this.job,
135
+ `[CedarJS Jobs] Failed job ${this.jobIdentifier}: reached max attempts (${this.maxAttempts})`
136
+ );
137
+ const failureRecorded = await this.#adapterWrite(
138
+ () => this.adapter.failure({
139
+ job: this.job,
140
+ deleteJob: this.deleteFailedJobs
141
+ })
142
+ );
143
+ return errorRecorded && failureRecorded;
144
+ }
145
+ /**
146
+ * Runs a single adapter write. An error thrown by the write is logged
147
+ * rather than rethrown, so a failing write can't crash the worker. Resolves
148
+ * to `false` if the write threw
149
+ */
150
+ async #adapterWrite(write) {
151
+ try {
152
+ await write();
153
+ return true;
154
+ } catch (rawAdapterError) {
155
+ const adapterError = rawAdapterError instanceof Error ? rawAdapterError : new Error(String(rawAdapterError));
156
+ this.logger.error(
157
+ `[CedarJS Jobs] Could not record the error for job ${this.jobIdentifier}: ${adapterError.message}`
158
+ );
159
+ this.logger.error(adapterError.stack);
160
+ return false;
119
161
  }
120
162
  }
163
+ /**
164
+ * The number of milliseconds to wait before retrying a job that has been
165
+ * attempted `attempts` times: `attempts ** 4` seconds, capped at
166
+ * `MAX_BACKOFF_MS`
167
+ */
121
168
  backoffMilliseconds(attempts) {
122
- return 1e3 * attempts ** 4;
169
+ return Math.min(1e3 * attempts ** 4, MAX_BACKOFF_MS);
123
170
  }
124
171
  }
125
172
  export {
@@ -13,7 +13,7 @@ export declare class JobManager<TAdapters extends Adapters, TQueues extends Queu
13
13
  logger: TLogger;
14
14
  workers: WorkerConfig<TAdapters, TQueues>[];
15
15
  constructor(config: JobManagerConfig<TAdapters, TQueues extends string[] ? never : TQueues, TLogger>);
16
- createScheduler<TAdapterName extends keyof TAdapters>(schedulerConfig: CreateSchedulerConfig<TAdapters, TAdapterName>): (<TJob extends Job<TQueues, any[]>>(job: TJob, ...argsAndOptions: CreateSchedulerArgs<TJob>) => Promise<void>) & {
16
+ createScheduler<TAdapterName extends keyof TAdapters>(schedulerConfig: CreateSchedulerConfig<TAdapters, TAdapterName>): (<TJob extends Job<TQueues, any[]>>(job: TJob, ...argsAndOptions: CreateSchedulerArgs<TJob>) => Promise<Awaited<ReturnType<TAdapters[TAdapterName]["schedule"]>>>) & {
17
17
  cancel: (jobId: string | number) => Promise<boolean>;
18
18
  };
19
19
  createJob<TArgs extends unknown[]>(jobDefinition: JobDefinition<TQueues, TArgs>): Job<TQueues, TArgs>;
@@ -21,7 +21,7 @@ export declare class Scheduler<TAdapter extends BaseAdapter> {
21
21
  job: TJob;
22
22
  args: Parameters<TJob['perform']> | never[];
23
23
  options?: ScheduleJobOptions;
24
- }): Promise<void>;
24
+ }): Promise<Awaited<ReturnType<TAdapter['schedule']>>>;
25
25
  /**
26
26
  * Cancel a scheduled job so that it will not be run (if it's still queued)
27
27
  * or not be retried (if it's currently running). Requires an adapter that
@@ -1 +1 @@
1
- {"version":3,"file":"Scheduler.d.ts","sourceRoot":"","sources":["../../src/core/Scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EAChB,MAAM,wCAAwC,CAAA;AAa/C,OAAO,KAAK,EACV,WAAW,EACX,GAAG,EACH,UAAU,EACV,kBAAkB,EACnB,MAAM,aAAa,CAAA;AAEpB,UAAU,eAAe,CAAC,QAAQ,SAAS,WAAW;IACpD,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,qBAAa,SAAS,CAAC,QAAQ,SAAS,WAAW;IACjD,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,EAAE,WAAW,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAA;gBAE5C,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,QAAQ,CAAC;IAS1D,YAAY,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;KAAE;IAU1E,YAAY,CAAC,IAAI,SAAS,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,EACpD,GAAG,EACH,IAAI,EACJ,OAAO,GACR,EAAE;QACD,GAAG,EAAE,IAAI,CAAA;QACT,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,EAAE,CAAA;QAC3C,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAC7B,GAAG,eAAe;IA4Bb,QAAQ,CAAC,IAAI,SAAS,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,EACtD,GAAG,EACH,IAAI,EACJ,OAAO,GACR,EAAE;QACD,GAAG,EAAE,IAAI,CAAA;QACT,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,EAAE,CAAA;QAC3C,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAC7B;IAqBD;;;;;;;;OAQG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CASpC"}
1
+ {"version":3,"file":"Scheduler.d.ts","sourceRoot":"","sources":["../../src/core/Scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EAChB,MAAM,wCAAwC,CAAA;AAa/C,OAAO,KAAK,EACV,WAAW,EACX,GAAG,EACH,UAAU,EACV,kBAAkB,EACnB,MAAM,aAAa,CAAA;AAGpB,UAAU,eAAe,CAAC,QAAQ,SAAS,WAAW;IACpD,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,qBAAa,SAAS,CAAC,QAAQ,SAAS,WAAW;IACjD,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,EAAE,WAAW,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAA;gBAE5C,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,QAAQ,CAAC;IAS1D,YAAY,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;KAAE;IAU1E,YAAY,CAAC,IAAI,SAAS,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,EACpD,GAAG,EACH,IAAI,EACJ,OAAO,GACR,EAAE;QACD,GAAG,EAAE,IAAI,CAAA;QACT,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,EAAE,CAAA;QAC3C,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAC7B,GAAG,eAAe;IAgCb,QAAQ,CAAC,IAAI,SAAS,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,EACtD,GAAG,EACH,IAAI,EACJ,OAAO,GACR,EAAE;QACD,GAAG,EAAE,IAAI,CAAA;QACT,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,EAAE,CAAA;QAC3C,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAC7B,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IA+BtD;;;;;;;;OAQG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CASpC"}
@@ -10,6 +10,7 @@ import {
10
10
  QueueNotDefinedError,
11
11
  SchedulingError
12
12
  } from "../errors.js";
13
+ import { nextCronRunAt } from "../util.js";
13
14
  class Scheduler {
14
15
  adapter;
15
16
  logger;
@@ -52,7 +53,9 @@ class Scheduler {
52
53
  path: job.path,
53
54
  args: args ?? [],
54
55
  cron,
55
- runAt: this.computeRunAt({ wait, waitUntil }),
56
+ // A recurring job's first run happens the next time its cron schedule
57
+ // matches, just like all following runs
58
+ runAt: cron ? nextCronRunAt(cron) : this.computeRunAt({ wait, waitUntil }),
56
59
  queue,
57
60
  priority
58
61
  };
@@ -69,7 +72,8 @@ class Scheduler {
69
72
  });
70
73
  this.logger.info(payload, `[CedarJS Jobs] Scheduling ${job.name}`);
71
74
  try {
72
- return await this.adapter.schedule(payload);
75
+ const scheduled = this.adapter.schedule(payload);
76
+ return await Promise.resolve(scheduled);
73
77
  } catch (e) {
74
78
  const error = e instanceof Error ? e : new Error(String(e));
75
79
  throw new SchedulingError(
@@ -1 +1 @@
1
- {"version":3,"file":"Worker.d.ts","sourceRoot":"","sources":["../../src/core/Worker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAUzE,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAInE,MAAM,WAAW,aAAc,SAAQ,mBAAmB;IAGxD,OAAO,EAAE,WAAW,CAAA;IACpB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAA;IACnB,oEAAoE;IACpE,MAAM,EAAE,MAAM,EAAE,CAAA;IAIhB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,OAAO,CAAA;IAEf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,KAAK,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAA;AAc9C,qBAAa,MAAM;;IACjB,OAAO,EAAE,eAAe,CAAA;IACxB,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnC,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACjC,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;IAC/B,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC,CAAA;IAC3C,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACjC,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC,CAAA;IAC3C,UAAU,EAAE,eAAe,CAAC,YAAY,CAAC,CAAA;IACzC,oBAAoB,EAAE,eAAe,CAAC,sBAAsB,CAAC,CAAA;IAC7D,gBAAgB,EAAE,eAAe,CAAC,kBAAkB,CAAC,CAAA;IACrD,UAAU,EAAE,eAAe,CAAC,YAAY,CAAC,CAAA;IACzC,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnC,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnC,aAAa,EAAE,IAAI,CAAA;gBAEP,OAAO,EAAE,aAAa;IA2ElC,GAAG;IAQH,IAAI,UAAU,WAMb;CAmDF"}
1
+ {"version":3,"file":"Worker.d.ts","sourceRoot":"","sources":["../../src/core/Worker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAUzE,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAInE,MAAM,WAAW,aAAc,SAAQ,mBAAmB;IAGxD,OAAO,EAAE,WAAW,CAAA;IACpB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAA;IACnB,oEAAoE;IACpE,MAAM,EAAE,MAAM,EAAE,CAAA;IAIhB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,OAAO,CAAA;IAEf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,KAAK,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAA;AAc9C,qBAAa,MAAM;;IACjB,OAAO,EAAE,eAAe,CAAA;IACxB,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnC,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACjC,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;IAC/B,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC,CAAA;IAC3C,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACjC,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC,CAAA;IAC3C,UAAU,EAAE,eAAe,CAAC,YAAY,CAAC,CAAA;IACzC,oBAAoB,EAAE,eAAe,CAAC,sBAAsB,CAAC,CAAA;IAC7D,gBAAgB,EAAE,eAAe,CAAC,kBAAkB,CAAC,CAAA;IACrD,UAAU,EAAE,eAAe,CAAC,YAAY,CAAC,CAAA;IACzC,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnC,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnC,aAAa,EAAE,IAAI,CAAA;gBAEP,OAAO,EAAE,aAAa;IA2ElC,GAAG;IAQH,IAAI,UAAU,WAMb;CAyDF"}
@@ -97,8 +97,9 @@ class Worker {
97
97
  maxRuntime: this.maxRuntime,
98
98
  queues: this.queues
99
99
  });
100
+ let outcomeRecorded = true;
100
101
  if (job) {
101
- await new Executor({
102
+ outcomeRecorded = await new Executor({
102
103
  adapter: this.adapter,
103
104
  logger: this.logger,
104
105
  job,
@@ -110,7 +111,7 @@ class Worker {
110
111
  } else if (this.workoff) {
111
112
  break;
112
113
  }
113
- if (!job && this.forever) {
114
+ if ((!job || !outcomeRecorded) && this.forever) {
114
115
  const millsSinceLastCheck = (/* @__PURE__ */ new Date()).getTime() - this.lastCheckTime.getTime();
115
116
  if (millsSinceLastCheck < this.sleepDelay) {
116
117
  await this.#wait(this.sleepDelay - millsSinceLastCheck);
package/dist/util.d.ts CHANGED
@@ -1,2 +1,8 @@
1
1
  export declare function makeFilePath(path: string): string;
2
+ /**
3
+ * Returns the next time after now that the given cron expression matches.
4
+ * Used both when a recurring job is first scheduled and when it's rescheduled
5
+ * after a successful run, so that both line up with its cron schedule.
6
+ */
7
+ export declare function nextCronRunAt(cron: string): Date;
2
8
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,UAExC"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAKA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,UAExC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,QAEzC"}
package/dist/util.js CHANGED
@@ -1,7 +1,12 @@
1
1
  import { pathToFileURL } from "node:url";
2
+ import { CronExpressionParser } from "cron-parser";
2
3
  function makeFilePath(path) {
3
4
  return pathToFileURL(path).href;
4
5
  }
6
+ function nextCronRunAt(cron) {
7
+ return CronExpressionParser.parse(cron).next().toDate();
8
+ }
5
9
  export {
6
- makeFilePath
10
+ makeFilePath,
11
+ nextCronRunAt
7
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/jobs",
3
- "version": "7.0.1-next.312",
3
+ "version": "7.0.1-next.333",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/cedarjs/cedar.git",
@@ -37,13 +37,13 @@
37
37
  "test:watch": "vitest"
38
38
  },
39
39
  "dependencies": {
40
- "@cedarjs/cli-helpers": "7.0.1-next.312",
41
- "@cedarjs/project-config": "7.0.1-next.312",
40
+ "@cedarjs/cli-helpers": "7.0.1-next.333",
41
+ "@cedarjs/project-config": "7.0.1-next.333",
42
42
  "cron-parser": "5.10.1",
43
43
  "type-fest": "5.10.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@cedarjs/framework-tools": "7.0.1-next.312",
46
+ "@cedarjs/framework-tools": "7.0.1-next.333",
47
47
  "@prisma/client": "7.10.0",
48
48
  "concurrently": "9.2.4",
49
49
  "publint": "0.3.24",