@cedarjs/jobs 8.0.0-canary.3421 → 8.0.0-canary.3423

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.
@@ -7,6 +7,38 @@ export interface SchedulePayload {
7
7
  runAt: Date;
8
8
  queue: string;
9
9
  priority: number;
10
+ /**
11
+ * The job's unique key, if it was scheduled with one. Only passed to
12
+ * adapters that set `supportsUniqueKeys`. While a job with this key is
13
+ * still queued, the adapter must not add another one (see
14
+ * `ScheduleJobOptions['key']`)
15
+ */
16
+ key?: string | undefined;
17
+ }
18
+ /** A recurring schedule declared in code, as passed to `syncSchedules()` */
19
+ export interface ScheduleDefinition extends SchedulePayload {
20
+ cron: string;
21
+ /**
22
+ * The schedule's identity: `SCHEDULE_KEY_PREFIX`, the job's name and the
23
+ * schedule's own key, like `cedar:schedule:ReportJob:nightly`
24
+ */
25
+ key: string;
26
+ }
27
+ export interface SyncSchedulesOptions {
28
+ /** Every schedule declared in the app's code */
29
+ schedules: ScheduleDefinition[];
30
+ /**
31
+ * Every schedule key starts with this prefix. Stored schedules with this
32
+ * prefix that are not in `schedules` must be removed
33
+ */
34
+ keyPrefix: string;
35
+ }
36
+ /** The keys of the schedules `syncSchedules()` touched, by what it did */
37
+ export interface SyncSchedulesResult {
38
+ created: string[];
39
+ updated: string[];
40
+ removed: string[];
41
+ unchanged: string[];
10
42
  }
11
43
  export interface FindArgs {
12
44
  processName: string;
@@ -108,6 +140,12 @@ export interface ListedJob {
108
140
  export declare abstract class BaseAdapter<TOptions extends BaseAdapterOptions = BaseAdapterOptions, TScheduleReturn = unknown> {
109
141
  options: TOptions;
110
142
  logger: NonNullable<TOptions['logger']>;
143
+ /**
144
+ * Whether `schedule()` honors `payload.key` (see `SchedulePayload['key']`).
145
+ * Scheduling a job with a `key` through an adapter that doesn't throws a
146
+ * `UniqueKeysNotSupportedError`, so a key is never silently ignored
147
+ */
148
+ readonly supportsUniqueKeys: boolean;
111
149
  constructor(options: TOptions);
112
150
  abstract schedule(payload: SchedulePayload): TScheduleReturn;
113
151
  /**
@@ -151,6 +189,22 @@ export declare abstract class BaseAdapter<TOptions extends BaseAdapterOptions =
151
189
  * cancellation and `later.cancel()` will throw a `CancelNotImplementedError`
152
190
  */
153
191
  cancel?(options: CancelOptions): boolean | Promise<boolean>;
192
+ /**
193
+ * Makes the stored recurring schedules match `options.schedules`, the ones
194
+ * declared in code. Called by every job worker when it starts, so several
195
+ * calls can run at the same time and must not create duplicates:
196
+ * - a declared schedule without a stored job is created, with its `runAt`
197
+ * at the next match of its `cron`
198
+ * - a stored schedule whose job name, path, args, queue, priority or cron
199
+ * differ from the declaration is updated
200
+ * - a stored schedule (key starting with `options.keyPrefix`) that is not
201
+ * declared any more is removed
202
+ *
203
+ * This method is optional: declaring `schedules` on a job with an adapter
204
+ * that doesn't implement it makes the workers throw a
205
+ * `SchedulesNotSupportedError` when they start
206
+ */
207
+ syncSchedules?(options: SyncSchedulesOptions): SyncSchedulesResult | Promise<SyncSchedulesResult>;
154
208
  /**
155
209
  * Pause a job, so that no worker picks it up until it's resumed. A run in
156
210
  * progress finishes, and a recurring job's next run stays paused: `success()`
@@ -1 +1 @@
1
- {"version":3,"file":"BaseAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/BaseAdapter/BaseAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BaseAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/BaseAdapter/BaseAdapter.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,12 @@ import { DEFAULT_LOGGER } from "../../consts.js";
2
2
  class BaseAdapter {
3
3
  options;
4
4
  logger;
5
+ /**
6
+ * Whether `schedule()` honors `payload.key` (see `SchedulePayload['key']`).
7
+ * Scheduling a job with a `key` through an adapter that doesn't throws a
8
+ * `UniqueKeysNotSupportedError`, so a key is never silently ignored
9
+ */
10
+ supportsUniqueKeys = false;
5
11
  constructor(options) {
6
12
  this.options = options;
7
13
  this.logger = options?.logger ?? DEFAULT_LOGGER;
@@ -1,5 +1,5 @@
1
1
  import type { BaseJob } from '../../types.js';
2
- import type { BaseAdapterOptions, ListedJob, ListJobsOptions, PauseOptions, PauseResult, ResumeOptions, ResumeResult, SchedulePayload, FindArgs, SuccessOptions, ErrorOptions, FailureOptions, CancelOptions } from '../BaseAdapter/BaseAdapter.js';
2
+ import type { BaseAdapterOptions, ListedJob, ListJobsOptions, PauseOptions, PauseResult, ResumeOptions, ResumeResult, SchedulePayload, SyncSchedulesOptions, SyncSchedulesResult, FindArgs, SuccessOptions, ErrorOptions, FailureOptions, CancelOptions } from '../BaseAdapter/BaseAdapter.js';
3
3
  import { BaseAdapter } from '../BaseAdapter/BaseAdapter.js';
4
4
  export interface PrismaJob extends BaseJob {
5
5
  id: number;
@@ -22,6 +22,20 @@ export interface PrismaJob extends BaseJob {
22
22
  pausedAt?: Date | null;
23
23
  createdAt: Date;
24
24
  updatedAt: Date;
25
+ /**
26
+ * The job's unique key. Only queued jobs and active recurring jobs hold
27
+ * their key, it's null once a worker has picked up a non-recurring job.
28
+ * Undefined when the model has no `key` column
29
+ */
30
+ key?: string | null;
31
+ }
32
+ /** What `schedule()` returns: the stored job */
33
+ export interface ScheduledPrismaJob extends PrismaJob {
34
+ /**
35
+ * `true` when the job was scheduled with a `key` and a job with that key
36
+ * was already queued. No new job was added, and this is the queued job
37
+ */
38
+ alreadyQueued: boolean;
25
39
  }
26
40
  interface PrismaAdapterDelegate {
27
41
  findFirst(args: Record<string, unknown>): Promise<any>;
@@ -32,7 +46,7 @@ interface PrismaAdapterDelegate {
32
46
  update(args: Record<string, unknown>): Promise<unknown>;
33
47
  create(args: Record<string, unknown>): Promise<unknown>;
34
48
  deleteMany(args?: Record<string, unknown>): Promise<unknown>;
35
- /** Every Prisma delegate has it. Only `list()` uses it */
49
+ /** Every Prisma delegate has it. Only `syncSchedules()` and `list()` use it */
36
50
  findMany?(args: Record<string, unknown>): Promise<any[]>;
37
51
  }
38
52
  interface DelegateLike {
@@ -96,8 +110,9 @@ export interface PrismaAdapterOptions<TDb extends object = object> extends BaseA
96
110
  * }
97
111
  * ```
98
112
  */
99
- export declare class PrismaAdapter<TDb extends object = object> extends BaseAdapter<PrismaAdapterOptions<TDb>, Promise<PrismaJob>> {
113
+ export declare class PrismaAdapter<TDb extends object = object> extends BaseAdapter<PrismaAdapterOptions<TDb>, Promise<ScheduledPrismaJob>> {
100
114
  #private;
115
+ readonly supportsUniqueKeys = true;
101
116
  db: TDb;
102
117
  model: DelegateKey<TDb> | string;
103
118
  accessor: PrismaAdapterDelegate;
@@ -113,7 +128,24 @@ export declare class PrismaAdapter<TDb extends object = object> extends BaseAdap
113
128
  success({ job, runAt, deleteJob }: SuccessOptions<PrismaJob>): Promise<void>;
114
129
  error({ job, runAt, error }: ErrorOptions<PrismaJob>): Promise<void>;
115
130
  failure({ job, deleteJob, error }: FailureOptions<PrismaJob>): Promise<void>;
116
- schedule({ name, path, args, runAt, cron, queue, priority, }: SchedulePayload): Promise<PrismaJob>;
131
+ /**
132
+ * Schedules a job by creating a new record in the background job table.
133
+ * Returns the created job so that callers get access to its `id` (to cancel
134
+ * it later, poll its status, etc.)
135
+ *
136
+ * With a `key`, the `key` column's unique constraint makes sure only one
137
+ * queued job holds the key, also when several processes schedule at once.
138
+ * If the insert hits the constraint, the job that holds the key is
139
+ * returned with `alreadyQueued: true` instead
140
+ */
141
+ schedule({ name, path, args, runAt, cron, queue, priority, key, }: SchedulePayload): Promise<ScheduledPrismaJob>;
142
+ /**
143
+ * Makes the stored declared schedules match `schedules`. Each schedule is
144
+ * one row, identified by its unique `key`, so concurrent syncs can't create
145
+ * duplicates: an insert that hits the unique constraint compares against
146
+ * (and if needed updates) the row the other sync created instead
147
+ */
148
+ syncSchedules({ schedules, keyPrefix, }: SyncSchedulesOptions): Promise<SyncSchedulesResult>;
117
149
  /**
118
150
  * Cancels a job by marking it as permanently failed so no worker will pick
119
151
  * it up (or retry it, if it's currently being worked on).
@@ -1 +1 @@
1
- {"version":3,"file":"PrismaAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/PrismaAdapter/PrismaAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PrismaAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/PrismaAdapter/PrismaAdapter.ts"],"names":[],"mappings":"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"}
@@ -4,10 +4,19 @@ import {
4
4
  DEFAULT_MODEL_NAME,
5
5
  MAX_RUNTIME_GRACE_PERIOD
6
6
  } from "../../consts.js";
7
- import { JobIdNotFoundError, JobNotActiveError } from "../../errors.js";
7
+ import {
8
+ CedarJSJobsError,
9
+ JobIdNotFoundError,
10
+ JobNotActiveError
11
+ } from "../../errors.js";
8
12
  import { nextCronRunAt } from "../../util.js";
9
13
  import { BaseAdapter } from "../BaseAdapter/BaseAdapter.js";
10
- import { ModelNameError, PausedAtColumnMissingError } from "./errors.js";
14
+ import {
15
+ KeyColumnMissingError,
16
+ ModelNameError,
17
+ PausedAtColumnMissingError
18
+ } from "./errors.js";
19
+ const MAX_KEY_RETRIES = 5;
11
20
  function isPrismaAdapterDelegate(value) {
12
21
  const candidate = value;
13
22
  return candidate !== null && typeof candidate === "object" && typeof candidate.findFirst === "function" && typeof candidate.updateMany === "function" && typeof candidate.delete === "function" && typeof candidate.update === "function" && typeof candidate.create === "function" && typeof candidate.deleteMany === "function";
@@ -29,10 +38,24 @@ function getActiveProvider(db) {
29
38
  }
30
39
  return provider;
31
40
  }
41
+ function isUniqueViolation(e) {
42
+ return typeof e === "object" && e !== null && "code" in e && e.code === "P2002";
43
+ }
44
+ function isMissingKeyColumn(e) {
45
+ return e instanceof Error && e.name === "PrismaClientValidationError" && /Unknown (argument|field) `key`/.test(e.message);
46
+ }
47
+ function releaseKey(job) {
48
+ return job.key != null ? { key: null } : {};
49
+ }
50
+ function rowToJob(row) {
51
+ const { name, path, args } = JSON.parse(row.handler);
52
+ return { ...row, name, path, args };
53
+ }
32
54
  function isMissingPausedAtColumn(e) {
33
55
  return e instanceof Error && e.name === "PrismaClientValidationError" && /Unknown (argument|field) `pausedAt`/.test(e.message);
34
56
  }
35
57
  class PrismaAdapter extends BaseAdapter {
58
+ supportsUniqueKeys = true;
36
59
  db;
37
60
  model;
38
61
  accessor;
@@ -93,7 +116,6 @@ class PrismaAdapter extends BaseAdapter {
93
116
  ...queues.length > 1 || queues[0] !== "*" ? [{ queue: { in: queues } }] : []
94
117
  ];
95
118
  const findCandidate = (withPausedAt) => this.accessor.findFirst({
96
- select: { id: true, attempts: true },
97
119
  where: { AND: conditions(withPausedAt) },
98
120
  orderBy: [{ priority: "asc" }, { runAt: "asc" }],
99
121
  take: 1
@@ -122,13 +144,15 @@ class PrismaAdapter extends BaseAdapter {
122
144
  data: {
123
145
  lockedAt: /* @__PURE__ */ new Date(),
124
146
  lockedBy: processName,
125
- attempts: job.attempts + 1
147
+ attempts: job.attempts + 1,
148
+ ...job.cron ? {} : releaseKey(job)
126
149
  }
127
150
  });
128
151
  if (count) {
129
152
  const data = await this.accessor.findFirst({ where: { id: job.id } });
130
- const { name, path, args } = JSON.parse(data.handler);
131
- return { ...data, name, path, args };
153
+ if (data) {
154
+ return rowToJob(data);
155
+ }
132
156
  }
133
157
  }
134
158
  return void 0;
@@ -216,7 +240,8 @@ ${error.stack}`,
216
240
  } else {
217
241
  const data = {
218
242
  failedAt: /* @__PURE__ */ new Date(),
219
- runAt: null
243
+ runAt: null,
244
+ ...releaseKey(job)
220
245
  };
221
246
  if (error) {
222
247
  data.lastError = `${error.message}
@@ -229,9 +254,16 @@ ${error.stack}`;
229
254
  });
230
255
  }
231
256
  }
232
- // Schedules a job by creating a new record in the background job table.
233
- // Returns the created job so that callers get access to its `id` (to cancel
234
- // it later, poll its status, etc.)
257
+ /**
258
+ * Schedules a job by creating a new record in the background job table.
259
+ * Returns the created job so that callers get access to its `id` (to cancel
260
+ * it later, poll its status, etc.)
261
+ *
262
+ * With a `key`, the `key` column's unique constraint makes sure only one
263
+ * queued job holds the key, also when several processes schedule at once.
264
+ * If the insert hits the constraint, the job that holds the key is
265
+ * returned with `alreadyQueued: true` instead
266
+ */
235
267
  async schedule({
236
268
  name,
237
269
  path,
@@ -239,19 +271,151 @@ ${error.stack}`;
239
271
  runAt,
240
272
  cron,
241
273
  queue,
242
- priority
274
+ priority,
275
+ key
243
276
  }) {
244
- const data = await this.accessor.create({
245
- data: {
246
- handler: JSON.stringify({ name, path, args }),
247
- runAt,
248
- cron,
249
- queue,
250
- priority
277
+ const job = { name, path, args };
278
+ const data = {
279
+ handler: JSON.stringify(job),
280
+ runAt,
281
+ cron,
282
+ queue,
283
+ priority,
284
+ ...key !== void 0 ? { key } : {}
285
+ };
286
+ if (key === void 0) {
287
+ return { ...await this.#create(data, job), alreadyQueued: false };
288
+ }
289
+ for (let attempt = 0; attempt <= MAX_KEY_RETRIES; attempt++) {
290
+ try {
291
+ return { ...await this.#create(data, job), alreadyQueued: false };
292
+ } catch (e) {
293
+ if (!isUniqueViolation(e)) {
294
+ throw e;
295
+ }
251
296
  }
252
- });
253
- const row = data;
254
- return { ...row, name, path, args };
297
+ const queued = await this.accessor.findFirst({ where: { key } });
298
+ if (queued) {
299
+ return { ...rowToJob(queued), alreadyQueued: true };
300
+ }
301
+ }
302
+ throw new CedarJSJobsError(
303
+ `Could not schedule ${name} with the key \`${key}\`: the key was taken and released again ${MAX_KEY_RETRIES + 1} times in a row`
304
+ );
305
+ }
306
+ async #create(data, job) {
307
+ let row;
308
+ try {
309
+ row = await this.accessor.create({ data });
310
+ } catch (e) {
311
+ if (isMissingKeyColumn(e)) {
312
+ throw new KeyColumnMissingError(String(this.model));
313
+ }
314
+ throw e;
315
+ }
316
+ return { ...row, ...job };
317
+ }
318
+ /**
319
+ * Makes the stored declared schedules match `schedules`. Each schedule is
320
+ * one row, identified by its unique `key`, so concurrent syncs can't create
321
+ * duplicates: an insert that hits the unique constraint compares against
322
+ * (and if needed updates) the row the other sync created instead
323
+ */
324
+ async syncSchedules({
325
+ schedules,
326
+ keyPrefix
327
+ }) {
328
+ const result = {
329
+ created: [],
330
+ updated: [],
331
+ removed: [],
332
+ unchanged: []
333
+ };
334
+ if (!this.accessor.findMany) {
335
+ throw new ModelNameError(String(this.model));
336
+ }
337
+ let stored;
338
+ try {
339
+ stored = await this.accessor.findMany({
340
+ where: { key: { startsWith: keyPrefix } }
341
+ });
342
+ } catch (e) {
343
+ if (!isMissingKeyColumn(e)) {
344
+ throw e;
345
+ }
346
+ if (schedules.length === 0) {
347
+ return result;
348
+ }
349
+ throw new KeyColumnMissingError(String(this.model));
350
+ }
351
+ const storedByKey = new Map(stored.map((row) => [row.key, row]));
352
+ for (const schedule of schedules) {
353
+ const outcome = await this.#syncSchedule(
354
+ schedule,
355
+ storedByKey.get(schedule.key)
356
+ );
357
+ result[outcome].push(schedule.key);
358
+ }
359
+ const declared = new Set(schedules.map((schedule) => schedule.key));
360
+ const undeclared = stored.map((row) => row.key).filter((key) => !!key && !declared.has(key));
361
+ if (undeclared.length > 0) {
362
+ await this.accessor.deleteMany({ where: { key: { in: undeclared } } });
363
+ result.removed.push(...undeclared);
364
+ }
365
+ return result;
366
+ }
367
+ async #syncSchedule(schedule, stored) {
368
+ const { name, path, args, cron, runAt, queue, priority, key } = schedule;
369
+ const handler = JSON.stringify({ name, path, args });
370
+ let row = stored;
371
+ for (let attempt = 0; attempt <= MAX_KEY_RETRIES; attempt++) {
372
+ if (!row) {
373
+ try {
374
+ await this.accessor.create({
375
+ data: { handler, runAt, cron, queue, priority, key }
376
+ });
377
+ return "created";
378
+ } catch (e) {
379
+ if (!isUniqueViolation(e)) {
380
+ throw e;
381
+ }
382
+ }
383
+ row = await this.accessor.findFirst({ where: { key } });
384
+ if (!row) {
385
+ continue;
386
+ }
387
+ }
388
+ const changes = {};
389
+ if (row.handler !== handler) {
390
+ changes.handler = handler;
391
+ }
392
+ if (row.queue !== queue) {
393
+ changes.queue = queue;
394
+ }
395
+ if (row.priority !== priority) {
396
+ changes.priority = priority;
397
+ }
398
+ if (row.cron !== cron) {
399
+ changes.cron = cron;
400
+ if (!row.lockedAt) {
401
+ changes.runAt = runAt;
402
+ }
403
+ }
404
+ if (Object.keys(changes).length === 0) {
405
+ return "unchanged";
406
+ }
407
+ const { count } = await this.accessor.updateMany({
408
+ where: { id: row.id },
409
+ data: changes
410
+ });
411
+ if (count > 0) {
412
+ return "updated";
413
+ }
414
+ row = void 0;
415
+ }
416
+ throw new CedarJSJobsError(
417
+ `Could not sync the schedule \`${key}\`: it was created and removed again ${MAX_KEY_RETRIES + 1} times in a row`
418
+ );
255
419
  }
256
420
  /**
257
421
  * Cancels a job by marking it as permanently failed so no worker will pick
@@ -264,12 +428,16 @@ ${error.stack}`;
264
428
  */
265
429
  async cancel({ jobId }) {
266
430
  this.logger.debug(`[CedarJS Jobs] Cancelling job ${jobId}`);
431
+ const job = await this.accessor.findFirst({
432
+ where: { id: jobId }
433
+ });
267
434
  const { count } = await this.accessor.updateMany({
268
435
  where: { id: jobId, failedAt: null, runAt: { not: null } },
269
436
  data: {
270
437
  failedAt: /* @__PURE__ */ new Date(),
271
438
  lastError: "Job cancelled by user",
272
- runAt: null
439
+ runAt: null,
440
+ ...releaseKey(job ?? {})
273
441
  }
274
442
  });
275
443
  return count > 0;
@@ -5,6 +5,13 @@ import { CedarJSJobsError } from '../../errors.js';
5
5
  export declare class ModelNameError extends CedarJSJobsError {
6
6
  constructor(name: string);
7
7
  }
8
+ /**
9
+ * Thrown when a job is scheduled with a unique `key`, or jobs declare
10
+ * `schedules`, but the jobs model has no `key` column to store the key in
11
+ */
12
+ export declare class KeyColumnMissingError extends CedarJSJobsError {
13
+ constructor(model: string);
14
+ }
8
15
  /**
9
16
  * Thrown when a job is paused or resumed, but the jobs model has no
10
17
  * `pausedAt` column to store the pause in
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/adapters/PrismaAdapter/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAElD;;GAEG;AACH,qBAAa,cAAe,SAAQ,gBAAgB;gBACtC,IAAI,EAAE,MAAM;CAGzB;AAED;;;GAGG;AACH,qBAAa,0BAA2B,SAAQ,gBAAgB;gBAClD,KAAK,EAAE,MAAM;CAO1B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/adapters/PrismaAdapter/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAElD;;GAEG;AACH,qBAAa,cAAe,SAAQ,gBAAgB;gBACtC,IAAI,EAAE,MAAM;CAGzB;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;gBAC7C,KAAK,EAAE,MAAM;CAQ1B;AAED;;;GAGG;AACH,qBAAa,0BAA2B,SAAQ,gBAAgB;gBAClD,KAAK,EAAE,MAAM;CAO1B"}
@@ -4,6 +4,13 @@ class ModelNameError extends CedarJSJobsError {
4
4
  super(`Model \`${name}\` not found in PrismaClient`);
5
5
  }
6
6
  }
7
+ class KeyColumnMissingError extends CedarJSJobsError {
8
+ constructor(model) {
9
+ super(
10
+ `The \`${model}\` model has no \`key\` column, which unique job keys and declared schedules need. Add \`key String? @unique\` to the model in your Prisma schema and create a migration with \`yarn cedar prisma migrate dev\``
11
+ );
12
+ }
13
+ }
7
14
  class PausedAtColumnMissingError extends CedarJSJobsError {
8
15
  constructor(model) {
9
16
  super(
@@ -12,6 +19,7 @@ class PausedAtColumnMissingError extends CedarJSJobsError {
12
19
  }
13
20
  }
14
21
  export {
22
+ KeyColumnMissingError,
15
23
  ModelNameError,
16
24
  PausedAtColumnMissingError
17
25
  };
@@ -1 +1 @@
1
- {"version":3,"file":"cedar-jobs-worker.d.ts","sourceRoot":"","sources":["../../src/bins/cedar-jobs-worker.ts"],"names":[],"mappings":";AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAwC/C,eAAO,MAAM,WAAW,GAAI,iBAGzB;IACD,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAC1B,WAEA,CAAA;AA0BD,eAAO,MAAM,SAAS,GAAU,gCAK7B;IACD,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;CACjB,oBAqBA,CAAA"}
1
+ {"version":3,"file":"cedar-jobs-worker.d.ts","sourceRoot":"","sources":["../../src/bins/cedar-jobs-worker.ts"],"names":[],"mappings":";AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAwC/C,eAAO,MAAM,WAAW,GAAI,iBAGzB;IACD,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAC1B,WAEA,CAAA;AA0BD,eAAO,MAAM,SAAS,GAAU,gCAK7B;IACD,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;CACjB,oBA+BA,CAAA"}
@@ -65,6 +65,9 @@ const getWorker = async ({
65
65
  if (!workerConfig) {
66
66
  throw new WorkerConfigIndexNotFoundError(index);
67
67
  }
68
+ if (!clear && !workoff) {
69
+ await manager.syncSchedules();
70
+ }
68
71
  return manager.createWorker({
69
72
  index,
70
73
  clear,
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}' })\``