@cedarjs/jobs 7.1.0 → 7.1.1-next.469

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.
Files changed (40) hide show
  1. package/dist/adapters/BaseAdapter/BaseAdapter.d.ts +133 -0
  2. package/dist/adapters/BaseAdapter/BaseAdapter.d.ts.map +1 -1
  3. package/dist/adapters/BaseAdapter/BaseAdapter.js +6 -0
  4. package/dist/adapters/PrismaAdapter/PrismaAdapter.d.ts +67 -3
  5. package/dist/adapters/PrismaAdapter/PrismaAdapter.d.ts.map +1 -1
  6. package/dist/adapters/PrismaAdapter/PrismaAdapter.js +378 -59
  7. package/dist/adapters/PrismaAdapter/errors.d.ts +14 -0
  8. package/dist/adapters/PrismaAdapter/errors.d.ts.map +1 -1
  9. package/dist/adapters/PrismaAdapter/errors.js +17 -1
  10. package/dist/bins/cedar-jobs-worker.d.ts +11 -0
  11. package/dist/bins/cedar-jobs-worker.d.ts.map +1 -1
  12. package/dist/bins/cedar-jobs-worker.js +12 -1
  13. package/dist/bins/cedar-jobs.d.ts +25 -1
  14. package/dist/bins/cedar-jobs.d.ts.map +1 -1
  15. package/dist/bins/cedar-jobs.js +104 -3
  16. package/dist/consts.d.ts +9 -0
  17. package/dist/consts.d.ts.map +1 -1
  18. package/dist/consts.js +7 -1
  19. package/dist/core/Executor.d.ts.map +1 -1
  20. package/dist/core/Executor.js +11 -4
  21. package/dist/core/JobManager.d.ts +76 -0
  22. package/dist/core/JobManager.d.ts.map +1 -1
  23. package/dist/core/JobManager.js +210 -1
  24. package/dist/core/Scheduler.d.ts.map +1 -1
  25. package/dist/core/Scheduler.js +16 -3
  26. package/dist/core/Worker.d.ts +8 -0
  27. package/dist/core/Worker.d.ts.map +1 -1
  28. package/dist/core/Worker.js +46 -1
  29. package/dist/errors.d.ts +54 -0
  30. package/dist/errors.d.ts.map +1 -1
  31. package/dist/errors.js +60 -0
  32. package/dist/index.d.ts +5 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +16 -1
  35. package/dist/loaders.d.ts +29 -0
  36. package/dist/loaders.d.ts.map +1 -1
  37. package/dist/loaders.js +48 -1
  38. package/dist/types.d.ts +101 -2
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +15 -9
@@ -19,19 +19,100 @@ const parseArgs = (argv) => {
19
19
  }
20
20
  const parsed = yargs(commandString).usage(
21
21
  "Starts the CedarJS Jobs runner to process background jobs\n\nUsage: cedar jobs <command> [options]"
22
- ).command("work", "Start a worker and process jobs").command("workoff", "Start a worker and exit after all jobs processed").command("start", "Start workers in daemon mode").command("stop", "Stop any daemonized job workers").command("restart", "Stop and start any daemonized job workers").command("clear", "Clear the job queue").demandCommand(1, "You must specify a mode to start in").example(
22
+ ).command("work", "Start a worker and process jobs").command("workoff", "Start a worker and exit after all jobs processed").command("start", "Start workers in daemon mode").command("stop", "Stop any daemonized job workers").command("restart", "Stop and start any daemonized job workers").command("clear", "Clear the job queue").command("pause <id>", "Pause a job, so no worker picks it up").command("resume <id>", "Resume a paused job").command(
23
+ "list",
24
+ "List the jobs that are waiting to run, running or paused, with their ids"
25
+ ).option("adapter", {
26
+ type: "string",
27
+ description: "pause, resume, list: the adapter that stores the jobs, when the JobManager has several"
28
+ }).option("limit", {
29
+ type: "number",
30
+ default: 100,
31
+ description: "list: the most jobs to list"
32
+ }).demandCommand(1, "You must specify a mode to start in").example(
23
33
  "cedar jobs work",
24
34
  "Start the job workers using the job config and work on jobs until manually stopped"
25
35
  ).example(
26
36
  "cedar jobs start",
27
37
  "Start the job workers using the job config and detach, running in daemon mode"
38
+ ).example(
39
+ "cedar jobs pause 42",
40
+ "Stop job 42 from running until `cedar jobs resume 42`"
28
41
  ).help().parse(commandString, (_err, _argv, output) => {
29
42
  if (output) {
30
43
  const newOutput = output.replaceAll("cedar-jobs.js", "cedar jobs");
31
44
  console.log(newOutput);
32
45
  }
33
46
  });
34
- return { command: parsed._[0] };
47
+ return {
48
+ command: parsed._[0],
49
+ id: parseJobId(parsed.id),
50
+ adapter: typeof parsed.adapter === "string" ? parsed.adapter : void 0,
51
+ limit: typeof parsed.limit === "number" ? parsed.limit : 100
52
+ };
53
+ };
54
+ const parseJobId = (value) => {
55
+ if (typeof value === "number") {
56
+ return value;
57
+ }
58
+ if (typeof value !== "string" || value === "") {
59
+ return void 0;
60
+ }
61
+ const number = Number(value);
62
+ return /^\d+$/.test(value) && Number.isSafeInteger(number) && String(number) === value ? number : value;
63
+ };
64
+ const runPauseCommand = async ({
65
+ command,
66
+ id,
67
+ adapter,
68
+ limit = 100,
69
+ manager
70
+ }) => {
71
+ if (command === "list") {
72
+ const listed = await manager.listJobs({ adapter, limit });
73
+ if (listed.length === 0) {
74
+ return "No jobs are waiting to run";
75
+ }
76
+ const rows = [
77
+ ["ID", "JOB", "QUEUE", "CRON", "STATUS"],
78
+ ...listed.map((job) => [
79
+ String(job.id),
80
+ job.name,
81
+ job.queue,
82
+ job.cron ?? "-",
83
+ [
84
+ job.pausedAt ? `paused since ${job.pausedAt.toISOString()}` : "",
85
+ job.running ? "running" : "",
86
+ !job.pausedAt && !job.running ? `next run ${job.runAt?.toISOString() ?? "-"}` : ""
87
+ ].filter(Boolean).join(", ")
88
+ ])
89
+ ];
90
+ const widths = rows[0].map(
91
+ (_, column) => Math.max(...rows.map((row) => row[column].length))
92
+ );
93
+ const lines = rows.map(
94
+ (row) => row.map(
95
+ (cell, column) => column === row.length - 1 ? cell : cell.padEnd(widths[column])
96
+ ).join(" ")
97
+ );
98
+ if (listed.length === limit) {
99
+ lines.push(`(the first ${limit}, use --limit to see more)`);
100
+ }
101
+ return lines.join("\n");
102
+ }
103
+ if (id === void 0) {
104
+ throw new Error(`Say which job to ${command} by its id`);
105
+ }
106
+ if (command === "pause") {
107
+ const result2 = await manager.pauseJob(id, { adapter });
108
+ return result2.alreadyPaused ? `Job ${id} was already paused (since ${result2.pausedAt.toISOString()})` : `Paused job ${id}. It doesn't run until you resume it with \`yarn cedar jobs resume ${id}\``;
109
+ }
110
+ const result = await manager.resumeJob(id, { adapter });
111
+ const nextRun = result.runAt?.toISOString() ?? "-";
112
+ if (result.wasPaused && !result.runAt) {
113
+ return `Resumed job ${id}. Its next run time is set when the current run finishes`;
114
+ }
115
+ return result.wasPaused ? `Resumed job ${id}. Its next run is at ${nextRun}` : `Job ${id} isn't paused. Its next run is at ${nextRun}`;
35
116
  };
36
117
  const buildNumWorkers = (config) => {
37
118
  const workers = [];
@@ -145,7 +226,7 @@ const findWorkerProcesses = async (id) => {
145
226
  });
146
227
  };
147
228
  const main = async () => {
148
- const { command } = parseArgs(process.argv);
229
+ const { command, id, adapter, limit } = parseArgs(process.argv);
149
230
  let jobsConfig;
150
231
  try {
151
232
  jobsConfig = await loadJobsManager();
@@ -153,6 +234,23 @@ const main = async () => {
153
234
  console.error(e);
154
235
  process.exit(1);
155
236
  }
237
+ if (command === "pause" || command === "resume" || command === "list") {
238
+ try {
239
+ console.log(
240
+ await runPauseCommand({
241
+ command,
242
+ id,
243
+ adapter,
244
+ limit,
245
+ manager: jobsConfig
246
+ })
247
+ );
248
+ } catch (e) {
249
+ console.error(e instanceof Error ? e.message : String(e));
250
+ process.exit(1);
251
+ }
252
+ return process.exit(0);
253
+ }
156
254
  const workerConfig = jobsConfig.workers;
157
255
  const numWorkers = buildNumWorkers(workerConfig);
158
256
  const logger = jobsConfig.logger ?? DEFAULT_LOGGER;
@@ -206,6 +304,9 @@ if (process.env.NODE_ENV !== "test") {
206
304
  export {
207
305
  buildNumWorkers,
208
306
  clearQueue,
307
+ parseArgs,
308
+ parseJobId,
309
+ runPauseCommand,
209
310
  startWorkers,
210
311
  stopWorkers
211
312
  };
package/dist/consts.d.ts CHANGED
@@ -30,6 +30,15 @@ export declare const DEFAULT_PRIORITY = 50;
30
30
  export declare const DEFAULT_WAIT = 0;
31
31
  export declare const DEFAULT_WAIT_UNTIL: null;
32
32
  export declare const PROCESS_TITLE_PREFIX = "cedar-jobs-worker";
33
+ /** Job keys starting with this are reserved for keys Cedar creates itself */
34
+ export declare const RESERVED_KEY_PREFIX = "cedar:";
35
+ /**
36
+ * Keys of stored declared schedules start with this, followed by the job's
37
+ * name and the schedule's own key: `cedar:schedule:ReportJob:nightly`
38
+ */
39
+ export declare const SCHEDULE_KEY_PREFIX = "cedar:schedule:";
40
+ /** The key of a job's schedule when the job has one and it has no `key` */
41
+ export declare const DEFAULT_SCHEDULE_KEY = "default";
33
42
  export declare const DEFAULT_MODEL_NAME = "BackgroundJob";
34
43
  /**
35
44
  * The name of the exported variable from the jobs config file that contains
@@ -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;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"}
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;AACvD,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,WAAW,CAAA;AAC3C;;;GAGG;AACH,eAAO,MAAM,mBAAmB,oBAAoC,CAAA;AACpE,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,YAAY,CAAA;AAE7C,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
@@ -13,6 +13,9 @@ const DEFAULT_PRIORITY = 50;
13
13
  const DEFAULT_WAIT = 0;
14
14
  const DEFAULT_WAIT_UNTIL = null;
15
15
  const PROCESS_TITLE_PREFIX = "cedar-jobs-worker";
16
+ const RESERVED_KEY_PREFIX = "cedar:";
17
+ const SCHEDULE_KEY_PREFIX = `${RESERVED_KEY_PREFIX}schedule:`;
18
+ const DEFAULT_SCHEDULE_KEY = "default";
16
19
  const DEFAULT_MODEL_NAME = "BackgroundJob";
17
20
  const DEFAULT_ADAPTER_NAME = "adapter";
18
21
  const DEFAULT_LOGGER_NAME = "logger";
@@ -27,11 +30,14 @@ export {
27
30
  DEFAULT_MODEL_NAME,
28
31
  DEFAULT_PRIORITY,
29
32
  DEFAULT_QUEUE,
33
+ DEFAULT_SCHEDULE_KEY,
30
34
  DEFAULT_SLEEP_DELAY,
31
35
  DEFAULT_WAIT,
32
36
  DEFAULT_WAIT_UNTIL,
33
37
  DEFAULT_WORK_QUEUE,
34
38
  MAX_BACKOFF_MS,
35
39
  MAX_RUNTIME_GRACE_PERIOD,
36
- PROCESS_TITLE_PREFIX
40
+ PROCESS_TITLE_PREFIX,
41
+ RESERVED_KEY_PREFIX,
42
+ SCHEDULE_KEY_PREFIX
37
43
  };
@@ -1 +1 @@
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;IAgKjC;;;;OAIG;IACH,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;AAgBzE,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;IAuKjC;;;;OAIG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM;CAGrC"}
@@ -5,7 +5,8 @@ import {
5
5
  DEFAULT_DELETE_FAILED_JOBS,
6
6
  DEFAULT_DELETE_SUCCESSFUL_JOBS,
7
7
  DEFAULT_LOGGER,
8
- MAX_BACKOFF_MS
8
+ MAX_BACKOFF_MS,
9
+ SCHEDULE_KEY_PREFIX
9
10
  } from "../consts.js";
10
11
  import {
11
12
  AdapterRequiredError,
@@ -134,12 +135,18 @@ class Executor {
134
135
  * called only after `failure()` has recorded that (a throwing `failure()`
135
136
  * is logged by `#adapterWrite()` instead). A permanently failed recurring
136
137
  * (cron) job is not rescheduled, so its schedule stops until the job is
137
- * scheduled again. That is logged at error level, together with how to
138
- * restart the schedule
138
+ * scheduled again (a declared schedule: until the workers restart and sync
139
+ * it). That is logged at error level, together with how to restart the
140
+ * schedule
139
141
  */
140
142
  #logPermanentFailure(reason) {
141
143
  const message = `[CedarJS Jobs] Failed job ${this.jobIdentifier}: ${reason}`;
142
- if (this.job.cron) {
144
+ if (this.job.cron && this.job.key?.startsWith(SCHEDULE_KEY_PREFIX)) {
145
+ this.logger.error(
146
+ this.job,
147
+ `${message}. Its declared schedule (cron: '${this.job.cron}') has stopped until the job workers restart, which recreates it`
148
+ );
149
+ } else if (this.job.cron) {
143
150
  this.logger.error(
144
151
  this.job,
145
152
  `${message}. Its recurring schedule (cron: '${this.job.cron}') has stopped and the job will not run again. To restart it, schedule the job again with \`later(job, args, { cron: '${this.job.cron}' })\``
@@ -1,6 +1,15 @@
1
+ import type { ListedJob, PauseResult, ResumeResult, SyncSchedulesResult } from '../adapters/BaseAdapter/BaseAdapter.js';
1
2
  import type { Adapters, BasicLogger, CreateSchedulerArgs, CreateSchedulerConfig, Job, JobDefinition, JobManagerConfig, QueueNames, WorkerConfig } from '../types.js';
2
3
  import type { WorkerOptions } from './Worker.js';
3
4
  import { Worker } from './Worker.js';
5
+ /** Which adapter `pauseJob()`, `resumeJob()` and `listJobs()` use */
6
+ export interface JobAdapterOptions<TAdapters extends Adapters> {
7
+ /**
8
+ * The name of the adapter that stores the job. Can be left out when the
9
+ * `JobManager` has a single adapter
10
+ */
11
+ adapter?: keyof TAdapters;
12
+ }
4
13
  export interface CreateWorkerArgs {
5
14
  index: number;
6
15
  workoff: WorkerOptions['workoff'];
@@ -8,15 +17,82 @@ export interface CreateWorkerArgs {
8
17
  processName: string;
9
18
  }
10
19
  export declare class JobManager<TAdapters extends Adapters, TQueues extends QueueNames, TLogger extends BasicLogger> {
20
+ #private;
11
21
  adapters: TAdapters;
12
22
  queues: TQueues;
13
23
  logger: TLogger;
14
24
  workers: WorkerConfig<TAdapters, TQueues>[];
25
+ scheduleAdapter: keyof TAdapters | undefined;
15
26
  constructor(config: JobManagerConfig<TAdapters, TQueues extends string[] ? never : TQueues, TLogger>);
16
27
  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
28
  cancel: (jobId: string | number) => Promise<boolean>;
18
29
  };
30
+ /**
31
+ * Pauses a job, without a deploy: no worker picks it up until it's resumed
32
+ * with `resumeJob()`. Works for any job that's waiting to run, recurring
33
+ * (`cron`) or not. A run that's in progress finishes, and a recurring job's
34
+ * next run stays paused.
35
+ *
36
+ * The pause is stored on the job's row. Code that deletes and recreates
37
+ * its recurring jobs (like a script that schedules them on every deploy)
38
+ * creates new rows, which aren't paused.
39
+ *
40
+ * Also available as `yarn cedar jobs pause <id>`.
41
+ *
42
+ * @param jobId The job's id, as returned by `later()` (or listed by
43
+ * `listJobs()` and `yarn cedar jobs list`)
44
+ *
45
+ * @returns Whether the job was paused already, and since when it's paused
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * await jobs.pauseJob(42)
50
+ * ```
51
+ */
52
+ pauseJob(jobId: string | number, options?: JobAdapterOptions<TAdapters>): Promise<PauseResult>;
53
+ /**
54
+ * Resumes a job paused with `pauseJob()`.
55
+ *
56
+ * - A recurring (`cron`) job runs next the next time its cron matches from
57
+ * now. Runs missed while it was paused are not made up for.
58
+ * - A one-off job keeps its run time, so it runs as soon as a worker is
59
+ * free when that time has passed.
60
+ *
61
+ * Resuming a job that isn't paused changes nothing. Also available as
62
+ * `yarn cedar jobs resume <id>`.
63
+ *
64
+ * @returns Whether the job was paused, and when it runs next
65
+ */
66
+ resumeJob(jobId: string | number, options?: JobAdapterOptions<TAdapters>): Promise<ResumeResult>;
67
+ /**
68
+ * Lists the jobs that are waiting to run, running or paused, the ones that
69
+ * run next first, to find a job's id. Completed and failed jobs are left
70
+ * out. Also available as `yarn cedar jobs list`.
71
+ *
72
+ * @param options.limit The most jobs to list. Default: 100
73
+ */
74
+ listJobs(options?: JobAdapterOptions<TAdapters> & {
75
+ limit?: number;
76
+ }): Promise<ListedJob[]>;
19
77
  createJob<TArgs extends unknown[]>(jobDefinition: JobDefinition<TQueues, TArgs>): Job<TQueues, TArgs>;
78
+ /**
79
+ * Makes the recurring schedules stored by the schedule adapter match the
80
+ * `schedules` the jobs declare in `createJob()`: adds missing ones, updates
81
+ * changed ones and removes the ones that are no longer declared. Safe to
82
+ * run from several processes at once.
83
+ *
84
+ * Every job worker calls this when it starts, so there's usually no need
85
+ * to call it yourself.
86
+ *
87
+ * @param jobs The jobs to read declared schedules from. Defaults to every
88
+ * job in the project (see `loadAllJobs()`), which is what the sync has to
89
+ * see to know which stored schedules are no longer declared.
90
+ *
91
+ * @returns What the sync did, or `undefined` if there was nothing to sync:
92
+ * no job declares schedules, and either the adapter can't store them or
93
+ * there are several adapters and no `scheduleAdapter` names one
94
+ */
95
+ syncSchedules(jobs?: Job<TQueues, any[]>[]): Promise<SyncSchedulesResult | undefined>;
20
96
  createWorker({ index, workoff, clear, processName }: CreateWorkerArgs): Worker;
21
97
  }
22
98
  //# sourceMappingURL=JobManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"JobManager.d.ts","sourceRoot":"","sources":["../../src/core/JobManager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,GAAG,EACH,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,YAAY,EACb,MAAM,aAAa,CAAA;AAGpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAA;IACjC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,qBAAa,UAAU,CACrB,SAAS,SAAS,QAAQ,EAC1B,OAAO,SAAS,UAAU,EAC1B,OAAO,SAAS,WAAW;IAE3B,QAAQ,EAAE,SAAS,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,EAAE,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAA;gBAGzC,MAAM,EAAE,gBAAgB,CACtB,SAAS,EAGT,OAAO,SAAS,MAAM,EAAE,GAAG,KAAK,GAAG,OAAO,EAC1C,OAAO,CACR;IAQH,eAAe,CAAC,YAAY,SAAS,MAAM,SAAS,EAClD,eAAe,EAAE,qBAAqB,CAAC,SAAS,EAAE,YAAY,CAAC,KAO7C,IAAI,SAAS,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAC3C,IAAI,qBACU,mBAAmB,CAAC,IAAI,CAAC;wBAc5B,MAAM,GAAG,MAAM;;IAInC,SAAS,CAAC,KAAK,SAAS,OAAO,EAAE,EAC/B,aAAa,EAAE,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,GAC3C,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC;IAOtB,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,gBAAgB;CAqBtE"}
1
+ {"version":3,"file":"JobManager.d.ts","sourceRoot":"","sources":["../../src/core/JobManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EACT,WAAW,EACX,YAAY,EAEZ,mBAAmB,EACpB,MAAM,wCAAwC,CAAA;AAU/C,OAAO,KAAK,EACV,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,GAAG,EACH,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,YAAY,EACb,MAAM,aAAa,CAAA;AAIpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,qEAAqE;AACrE,MAAM,WAAW,iBAAiB,CAAC,SAAS,SAAS,QAAQ;IAC3D;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,SAAS,CAAA;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAA;IACjC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,qBAAa,UAAU,CACrB,SAAS,SAAS,QAAQ,EAC1B,OAAO,SAAS,UAAU,EAC1B,OAAO,SAAS,WAAW;;IAE3B,QAAQ,EAAE,SAAS,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,EAAE,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAA;IAC3C,eAAe,EAAE,MAAM,SAAS,GAAG,SAAS,CAAA;gBAG1C,MAAM,EAAE,gBAAgB,CACtB,SAAS,EAGT,OAAO,SAAS,MAAM,EAAE,GAAG,KAAK,GAAG,OAAO,EAC1C,OAAO,CACR;IASH,eAAe,CAAC,YAAY,SAAS,MAAM,SAAS,EAClD,eAAe,EAAE,qBAAqB,CAAC,SAAS,EAAE,YAAY,CAAC,KAO7C,IAAI,SAAS,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAC3C,IAAI,qBACU,mBAAmB,CAAC,IAAI,CAAC;wBAc5B,MAAM,GAAG,MAAM;;IAInC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,QAAQ,CACZ,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,OAAO,GAAE,iBAAiB,CAAC,SAAS,CAAM,GACzC,OAAO,CAAC,WAAW,CAAC;IAmBvB;;;;;;;;;;;;OAYG;IACG,SAAS,CACb,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,OAAO,GAAE,iBAAiB,CAAC,SAAS,CAAM,GACzC,OAAO,CAAC,YAAY,CAAC;IAsBxB;;;;;;OAMG;IACG,QAAQ,CACZ,OAAO,GAAE,iBAAiB,CAAC,SAAS,CAAC,GAAG;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAC9D,OAAO,CAAC,SAAS,EAAE,CAAC;IA2BvB,SAAS,CAAC,KAAK,SAAS,OAAO,EAAE,EAC/B,aAAa,EAAE,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,GAC3C,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC;IAOtB;;;;;;;;;;;;;;;;OAgBG;IACG,aAAa,CACjB,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,GAC3B,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAgJ3C,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,gBAAgB;CAqBtE"}
@@ -1,4 +1,13 @@
1
- import { AdapterNotFoundError } from "../errors.js";
1
+ import { DEFAULT_SCHEDULE_KEY, SCHEDULE_KEY_PREFIX } from "../consts.js";
2
+ import {
3
+ AdapterNameRequiredError,
4
+ AdapterNotFoundError,
5
+ PauseNotImplementedError,
6
+ ScheduleConfigError,
7
+ SchedulesNotSupportedError
8
+ } from "../errors.js";
9
+ import { loadAllJobs } from "../loaders.js";
10
+ import { nextCronRunAt } from "../util.js";
2
11
  import { Scheduler } from "./Scheduler.js";
3
12
  import { Worker } from "./Worker.js";
4
13
  class JobManager {
@@ -6,11 +15,13 @@ class JobManager {
6
15
  queues;
7
16
  logger;
8
17
  workers;
18
+ scheduleAdapter;
9
19
  constructor(config) {
10
20
  this.adapters = config.adapters;
11
21
  this.queues = config.queues;
12
22
  this.logger = config.logger;
13
23
  this.workers = config.workers;
24
+ this.scheduleAdapter = config.scheduleAdapter;
14
25
  }
15
26
  createScheduler(schedulerConfig) {
16
27
  const scheduler = new Scheduler({
@@ -28,9 +39,207 @@ class JobManager {
28
39
  cancel: (jobId) => scheduler.cancel(jobId)
29
40
  });
30
41
  }
42
+ /**
43
+ * Pauses a job, without a deploy: no worker picks it up until it's resumed
44
+ * with `resumeJob()`. Works for any job that's waiting to run, recurring
45
+ * (`cron`) or not. A run that's in progress finishes, and a recurring job's
46
+ * next run stays paused.
47
+ *
48
+ * The pause is stored on the job's row. Code that deletes and recreates
49
+ * its recurring jobs (like a script that schedules them on every deploy)
50
+ * creates new rows, which aren't paused.
51
+ *
52
+ * Also available as `yarn cedar jobs pause <id>`.
53
+ *
54
+ * @param jobId The job's id, as returned by `later()` (or listed by
55
+ * `listJobs()` and `yarn cedar jobs list`)
56
+ *
57
+ * @returns Whether the job was paused already, and since when it's paused
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * await jobs.pauseJob(42)
62
+ * ```
63
+ */
64
+ async pauseJob(jobId, options = {}) {
65
+ const adapter = this.#adapter(options.adapter);
66
+ if (!adapter.pause) {
67
+ throw new PauseNotImplementedError("pause");
68
+ }
69
+ const result = await adapter.pause({ jobId });
70
+ this.logger.info(
71
+ result.alreadyPaused ? `[CedarJS Jobs] Job ${jobId} was already paused (since ${result.pausedAt.toISOString()})` : `[CedarJS Jobs] Paused job ${jobId}`
72
+ );
73
+ return result;
74
+ }
75
+ /**
76
+ * Resumes a job paused with `pauseJob()`.
77
+ *
78
+ * - A recurring (`cron`) job runs next the next time its cron matches from
79
+ * now. Runs missed while it was paused are not made up for.
80
+ * - A one-off job keeps its run time, so it runs as soon as a worker is
81
+ * free when that time has passed.
82
+ *
83
+ * Resuming a job that isn't paused changes nothing. Also available as
84
+ * `yarn cedar jobs resume <id>`.
85
+ *
86
+ * @returns Whether the job was paused, and when it runs next
87
+ */
88
+ async resumeJob(jobId, options = {}) {
89
+ const adapter = this.#adapter(options.adapter);
90
+ if (!adapter.resume) {
91
+ throw new PauseNotImplementedError("resume");
92
+ }
93
+ const result = await adapter.resume({ jobId });
94
+ const nextRun = result.runAt ? `next run at ${result.runAt.toISOString()}` : "next run time set when the current run finishes";
95
+ this.logger.info(
96
+ result.wasPaused ? `[CedarJS Jobs] Resumed job ${jobId}, ${nextRun}` : `[CedarJS Jobs] Job ${jobId} isn't paused`
97
+ );
98
+ return result;
99
+ }
100
+ /**
101
+ * Lists the jobs that are waiting to run, running or paused, the ones that
102
+ * run next first, to find a job's id. Completed and failed jobs are left
103
+ * out. Also available as `yarn cedar jobs list`.
104
+ *
105
+ * @param options.limit The most jobs to list. Default: 100
106
+ */
107
+ async listJobs(options = {}) {
108
+ const adapter = this.#adapter(options.adapter);
109
+ if (!adapter.list) {
110
+ throw new PauseNotImplementedError("list");
111
+ }
112
+ return adapter.list({ limit: options.limit ?? 100 });
113
+ }
114
+ #adapter(name) {
115
+ const names = Object.keys(this.adapters);
116
+ if (name === void 0 && names.length !== 1) {
117
+ throw new AdapterNameRequiredError(names);
118
+ }
119
+ const adapterName = name ?? names[0];
120
+ const adapter = this.adapters[adapterName];
121
+ if (!adapter) {
122
+ throw new AdapterNotFoundError(String(adapterName));
123
+ }
124
+ return adapter;
125
+ }
31
126
  createJob(jobDefinition) {
32
127
  return jobDefinition;
33
128
  }
129
+ /**
130
+ * Makes the recurring schedules stored by the schedule adapter match the
131
+ * `schedules` the jobs declare in `createJob()`: adds missing ones, updates
132
+ * changed ones and removes the ones that are no longer declared. Safe to
133
+ * run from several processes at once.
134
+ *
135
+ * Every job worker calls this when it starts, so there's usually no need
136
+ * to call it yourself.
137
+ *
138
+ * @param jobs The jobs to read declared schedules from. Defaults to every
139
+ * job in the project (see `loadAllJobs()`), which is what the sync has to
140
+ * see to know which stored schedules are no longer declared.
141
+ *
142
+ * @returns What the sync did, or `undefined` if there was nothing to sync:
143
+ * no job declares schedules, and either the adapter can't store them or
144
+ * there are several adapters and no `scheduleAdapter` names one
145
+ */
146
+ async syncSchedules(jobs) {
147
+ const allJobs = jobs ?? await loadAllJobs();
148
+ const declaresSchedules = allJobs.some(
149
+ (job) => (job.schedules?.length ?? 0) > 0
150
+ );
151
+ if (!declaresSchedules && this.scheduleAdapter === void 0 && Object.keys(this.adapters).length !== 1) {
152
+ return void 0;
153
+ }
154
+ const adapterName = this.#scheduleAdapterName();
155
+ const adapter = this.adapters[adapterName];
156
+ if (!adapter) {
157
+ throw new AdapterNotFoundError(String(adapterName));
158
+ }
159
+ const schedules = this.#scheduleDefinitions(allJobs, adapter);
160
+ if (!adapter.syncSchedules) {
161
+ if (schedules.length > 0) {
162
+ throw new SchedulesNotSupportedError(String(adapterName));
163
+ }
164
+ return void 0;
165
+ }
166
+ const result = await adapter.syncSchedules({
167
+ schedules,
168
+ keyPrefix: SCHEDULE_KEY_PREFIX
169
+ });
170
+ const changes = [
171
+ ...result.created.map((key) => `created ${key}`),
172
+ ...result.updated.map((key) => `updated ${key}`),
173
+ ...result.removed.map((key) => `removed ${key}`)
174
+ ];
175
+ if (changes.length > 0) {
176
+ this.logger.info(
177
+ `[CedarJS Jobs] Synced declared schedules: ${changes.join(", ")}`
178
+ );
179
+ } else {
180
+ this.logger.debug(
181
+ `[CedarJS Jobs] Declared schedules are up to date (${result.unchanged.length})`
182
+ );
183
+ }
184
+ return result;
185
+ }
186
+ #scheduleAdapterName() {
187
+ if (this.scheduleAdapter !== void 0) {
188
+ return this.scheduleAdapter;
189
+ }
190
+ const names = Object.keys(this.adapters);
191
+ if (names.length !== 1) {
192
+ throw new ScheduleConfigError(
193
+ `Set \`scheduleAdapter\` in your JobManager config to the adapter that stores the schedules jobs declare. It can only be left out when there is exactly one adapter (found: ${names.join(", ") || "none"})`
194
+ );
195
+ }
196
+ return names[0];
197
+ }
198
+ /**
199
+ * Turns the jobs' declared schedules into what the adapter stores, and
200
+ * checks them: every schedule needs a key when its job has more than one,
201
+ * identities must be unique, and every cron expression must parse
202
+ */
203
+ #scheduleDefinitions(jobs, adapter) {
204
+ const scheduler = new Scheduler({ adapter, logger: this.logger });
205
+ const definitions = [];
206
+ const owners = /* @__PURE__ */ new Map();
207
+ for (const job of jobs) {
208
+ const jobSchedules = job.schedules ?? [];
209
+ for (const schedule of jobSchedules) {
210
+ const scheduleKey = schedule.key ?? DEFAULT_SCHEDULE_KEY;
211
+ if (schedule.key === void 0 && jobSchedules.length > 1) {
212
+ throw new ScheduleConfigError(
213
+ `${job.name} declares ${jobSchedules.length} schedules, so each of them needs a \`key\` to tell them apart`
214
+ );
215
+ }
216
+ const key = `${SCHEDULE_KEY_PREFIX}${job.name}:${scheduleKey}`;
217
+ const owner = `${job.path} (${job.name})`;
218
+ const otherOwner = owners.get(key);
219
+ if (otherOwner) {
220
+ throw new ScheduleConfigError(
221
+ otherOwner === owner ? `${job.name} declares more than one schedule with the key \`${scheduleKey}\`` : `Both ${otherOwner} and ${owner} declare a schedule with the key \`${scheduleKey}\`. Schedules are identified by the job's name and the schedule's key, so jobs with schedules need unique names`
222
+ );
223
+ }
224
+ owners.set(key, owner);
225
+ try {
226
+ nextCronRunAt(schedule.cron);
227
+ } catch (e) {
228
+ const message = e instanceof Error ? e.message : String(e);
229
+ throw new ScheduleConfigError(
230
+ `The \`${scheduleKey}\` schedule of ${job.name} has an invalid cron expression \`${schedule.cron}\`: ${message}`
231
+ );
232
+ }
233
+ const payload = scheduler.buildPayload({
234
+ job,
235
+ args: schedule.args ?? [],
236
+ options: { cron: schedule.cron }
237
+ });
238
+ definitions.push({ ...payload, cron: schedule.cron, key });
239
+ }
240
+ }
241
+ return definitions;
242
+ }
34
243
  createWorker({ index, workoff, clear, processName }) {
35
244
  const config = this.workers[index];
36
245
  const adapter = this.adapters[config.adapter];
@@ -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;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"}
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;AAgB/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;IAyCb,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;IAmCtD;;;;;;;;OAQG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CASpC"}
@@ -2,13 +2,16 @@ import {
2
2
  DEFAULT_LOGGER,
3
3
  DEFAULT_PRIORITY,
4
4
  DEFAULT_WAIT,
5
- DEFAULT_WAIT_UNTIL
5
+ DEFAULT_WAIT_UNTIL,
6
+ RESERVED_KEY_PREFIX
6
7
  } from "../consts.js";
7
8
  import {
8
9
  AdapterNotConfiguredError,
9
10
  CancelNotImplementedError,
10
11
  QueueNotDefinedError,
11
- SchedulingError
12
+ ReservedKeyError,
13
+ SchedulingError,
14
+ UniqueKeysNotSupportedError
12
15
  } from "../errors.js";
13
16
  import { nextCronRunAt } from "../util.js";
14
17
  class Scheduler {
@@ -40,6 +43,7 @@ class Scheduler {
40
43
  const wait = options?.wait ?? DEFAULT_WAIT;
41
44
  const waitUntil = options?.waitUntil ?? DEFAULT_WAIT_UNTIL;
42
45
  const cron = options?.cron;
46
+ const key = options?.key;
43
47
  if (!queue) {
44
48
  throw new QueueNotDefinedError();
45
49
  }
@@ -48,6 +52,9 @@ class Scheduler {
48
52
  "Cannot schedule a cron job with wait or waitUntil options"
49
53
  );
50
54
  }
55
+ if (key?.toLowerCase().startsWith(RESERVED_KEY_PREFIX)) {
56
+ throw new ReservedKeyError(key, RESERVED_KEY_PREFIX);
57
+ }
51
58
  return {
52
59
  name: job.name,
53
60
  path: job.path,
@@ -57,7 +64,10 @@ class Scheduler {
57
64
  // matches, just like all following runs
58
65
  runAt: cron ? nextCronRunAt(cron) : this.computeRunAt({ wait, waitUntil }),
59
66
  queue,
60
- priority
67
+ priority,
68
+ // Only set when given, so adapters without unique key support never
69
+ // see the property
70
+ ...key !== void 0 ? { key } : {}
61
71
  };
62
72
  }
63
73
  async schedule({
@@ -70,6 +80,9 @@ class Scheduler {
70
80
  args,
71
81
  options
72
82
  });
83
+ if (payload.key !== void 0 && !this.adapter.supportsUniqueKeys) {
84
+ throw new UniqueKeysNotSupportedError();
85
+ }
73
86
  this.logger.info(payload, `[CedarJS Jobs] Scheduling ${job.name}`);
74
87
  try {
75
88
  const scheduled = this.adapter.schedule(payload);
@@ -43,6 +43,14 @@ export declare class Worker {
43
43
  lastCheckTime: Date;
44
44
  constructor(options: WorkerOptions);
45
45
  run(): Promise<void>;
46
+ /**
47
+ * Logs a warning listing the paused jobs in this worker's queues, so a
48
+ * pause that was forgotten shows up whenever a long-running worker starts.
49
+ * Does nothing when the adapter can't list jobs or there are no paused
50
+ * ones, and never throws: a failed query is logged and the worker starts
51
+ * anyway
52
+ */
53
+ logPausedJobs(): Promise<void>;
46
54
  get queueNames(): string;
47
55
  }
48
56
  export {};