sidekiq-ts 1.0.0
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.
- package/README.md +686 -0
- package/dist/api.d.ts +172 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +679 -0
- package/dist/backtrace.d.ts +3 -0
- package/dist/backtrace.d.ts.map +1 -0
- package/dist/backtrace.js +16 -0
- package/dist/cli-helpers.d.ts +22 -0
- package/dist/cli-helpers.d.ts.map +1 -0
- package/dist/cli-helpers.js +152 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +143 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +212 -0
- package/dist/config-loader.d.ts +16 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +37 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +155 -0
- package/dist/context.d.ts +10 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +29 -0
- package/dist/cron.d.ts +44 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +173 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/interrupt-handler.d.ts +8 -0
- package/dist/interrupt-handler.d.ts.map +1 -0
- package/dist/interrupt-handler.js +24 -0
- package/dist/iterable-constants.d.ts +3 -0
- package/dist/iterable-constants.d.ts.map +1 -0
- package/dist/iterable-constants.js +2 -0
- package/dist/iterable-errors.d.ts +10 -0
- package/dist/iterable-errors.d.ts.map +1 -0
- package/dist/iterable-errors.js +18 -0
- package/dist/iterable.d.ts +44 -0
- package/dist/iterable.d.ts.map +1 -0
- package/dist/iterable.js +298 -0
- package/dist/job-logger.d.ts +12 -0
- package/dist/job-logger.d.ts.map +1 -0
- package/dist/job-logger.js +64 -0
- package/dist/job-util.d.ts +8 -0
- package/dist/job-util.d.ts.map +1 -0
- package/dist/job-util.js +158 -0
- package/dist/job.d.ts +73 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +200 -0
- package/dist/json.d.ts +3 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +2 -0
- package/dist/leader.d.ts +63 -0
- package/dist/leader.d.ts.map +1 -0
- package/dist/leader.js +193 -0
- package/dist/logger.d.ts +53 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +143 -0
- package/dist/middleware.d.ts +23 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +92 -0
- package/dist/periodic.d.ts +80 -0
- package/dist/periodic.d.ts.map +1 -0
- package/dist/periodic.js +205 -0
- package/dist/redis.d.ts +3 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +1 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +8 -0
- package/dist/runner.d.ts +81 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +791 -0
- package/dist/sidekiq.d.ts +43 -0
- package/dist/sidekiq.d.ts.map +1 -0
- package/dist/sidekiq.js +189 -0
- package/dist/testing.d.ts +32 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +112 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
export type MiddlewareNext<TResult> = () => Promise<TResult> | TResult;
|
|
3
|
+
export type MiddlewareCall<TArgs extends unknown[], TResult> = (...args: [...TArgs, MiddlewareNext<TResult>]) => Promise<TResult> | TResult;
|
|
4
|
+
export type MiddlewareConstructor<TArgs extends unknown[], TResult> = new (...args: unknown[]) => {
|
|
5
|
+
call: MiddlewareCall<TArgs, TResult>;
|
|
6
|
+
config?: Config;
|
|
7
|
+
setConfig?: (config: Config) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare class MiddlewareChain<TArgs extends unknown[], TResult> {
|
|
10
|
+
private readonly config?;
|
|
11
|
+
private entries;
|
|
12
|
+
constructor(config?: Config);
|
|
13
|
+
add(klass: MiddlewareConstructor<TArgs, TResult>, ...args: unknown[]): void;
|
|
14
|
+
use(klass: MiddlewareConstructor<TArgs, TResult>, ...args: unknown[]): void;
|
|
15
|
+
prepend(klass: MiddlewareConstructor<TArgs, TResult>, ...args: unknown[]): void;
|
|
16
|
+
insertBefore(oldklass: MiddlewareConstructor<TArgs, TResult>, newklass: MiddlewareConstructor<TArgs, TResult>, ...args: unknown[]): void;
|
|
17
|
+
insertAfter(oldklass: MiddlewareConstructor<TArgs, TResult>, newklass: MiddlewareConstructor<TArgs, TResult>, ...args: unknown[]): void;
|
|
18
|
+
remove(klass: MiddlewareConstructor<TArgs, TResult>): void;
|
|
19
|
+
exists(klass: MiddlewareConstructor<TArgs, TResult>): boolean;
|
|
20
|
+
clear(): void;
|
|
21
|
+
invoke(...args: [...TArgs, MiddlewareNext<TResult>]): Promise<TResult> | TResult;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,cAAc,CAAC,OAAO,IAAI,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAEvE,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,IAAI,CAC7D,GAAG,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,KACzC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAEhC,MAAM,MAAM,qBAAqB,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,IAAI,KACpE,GAAG,IAAI,EAAE,OAAO,EAAE,KACf;IACH,IAAI,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,CAAC;AA8BF,qBAAa,eAAe,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,OAAO,CAAyC;gBAE5C,MAAM,CAAC,EAAE,MAAM;IAI3B,GAAG,CAAC,KAAK,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAK3E,GAAG,CAAC,KAAK,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAI3E,OAAO,CACL,KAAK,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,EAC5C,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,IAAI;IAKP,YAAY,CACV,QAAQ,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,EAC/C,QAAQ,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,EAC/C,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,IAAI;IAgBP,WAAW,CACT,QAAQ,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,EAC/C,QAAQ,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,EAC/C,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,IAAI;IAgBP,MAAM,CAAC,KAAK,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI;IAI1D,MAAM,CAAC,KAAK,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO;IAI7D,KAAK,IAAI,IAAI;IAIb,MAAM,CACJ,GAAG,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,GAC3C,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;CAmB9B"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
class MiddlewareEntry {
|
|
2
|
+
config;
|
|
3
|
+
klass;
|
|
4
|
+
args;
|
|
5
|
+
constructor(config, klass, args) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.klass = klass;
|
|
8
|
+
this.args = args;
|
|
9
|
+
}
|
|
10
|
+
makeNew() {
|
|
11
|
+
const instance = new this.klass(...this.args);
|
|
12
|
+
if (this.config) {
|
|
13
|
+
if (typeof instance.setConfig === "function") {
|
|
14
|
+
instance.setConfig(this.config);
|
|
15
|
+
}
|
|
16
|
+
else if ("config" in instance) {
|
|
17
|
+
instance.config = this.config;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return instance;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class MiddlewareChain {
|
|
24
|
+
config;
|
|
25
|
+
entries = [];
|
|
26
|
+
constructor(config) {
|
|
27
|
+
this.config = config;
|
|
28
|
+
}
|
|
29
|
+
add(klass, ...args) {
|
|
30
|
+
this.remove(klass);
|
|
31
|
+
this.entries.push(new MiddlewareEntry(this.config, klass, args));
|
|
32
|
+
}
|
|
33
|
+
use(klass, ...args) {
|
|
34
|
+
this.add(klass, ...args);
|
|
35
|
+
}
|
|
36
|
+
prepend(klass, ...args) {
|
|
37
|
+
this.remove(klass);
|
|
38
|
+
this.entries.unshift(new MiddlewareEntry(this.config, klass, args));
|
|
39
|
+
}
|
|
40
|
+
insertBefore(oldklass, newklass, ...args) {
|
|
41
|
+
const existingIndex = this.entries.findIndex((entry) => entry.klass === newklass);
|
|
42
|
+
const entry = existingIndex === -1
|
|
43
|
+
? new MiddlewareEntry(this.config, newklass, args)
|
|
44
|
+
: this.entries.splice(existingIndex, 1)[0];
|
|
45
|
+
const index = this.entries.findIndex((e) => e.klass === oldklass);
|
|
46
|
+
if (index === -1) {
|
|
47
|
+
this.entries.unshift(entry);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.entries.splice(index, 0, entry);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
insertAfter(oldklass, newklass, ...args) {
|
|
54
|
+
const existingIndex = this.entries.findIndex((entry) => entry.klass === newklass);
|
|
55
|
+
const entry = existingIndex === -1
|
|
56
|
+
? new MiddlewareEntry(this.config, newklass, args)
|
|
57
|
+
: this.entries.splice(existingIndex, 1)[0];
|
|
58
|
+
const index = this.entries.findIndex((e) => e.klass === oldklass);
|
|
59
|
+
if (index === -1) {
|
|
60
|
+
this.entries.push(entry);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.entries.splice(index + 1, 0, entry);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
remove(klass) {
|
|
67
|
+
this.entries = this.entries.filter((entry) => entry.klass !== klass);
|
|
68
|
+
}
|
|
69
|
+
exists(klass) {
|
|
70
|
+
return this.entries.some((entry) => entry.klass === klass);
|
|
71
|
+
}
|
|
72
|
+
clear() {
|
|
73
|
+
this.entries = [];
|
|
74
|
+
}
|
|
75
|
+
invoke(...args) {
|
|
76
|
+
if (this.entries.length === 0) {
|
|
77
|
+
const last = args.at(-1);
|
|
78
|
+
return last();
|
|
79
|
+
}
|
|
80
|
+
const chain = this.entries.map((entry) => entry.makeNew());
|
|
81
|
+
const callNext = (index) => {
|
|
82
|
+
if (index >= chain.length) {
|
|
83
|
+
const last = args.at(-1);
|
|
84
|
+
return last();
|
|
85
|
+
}
|
|
86
|
+
const middleware = chain[index];
|
|
87
|
+
const next = () => callNext(index + 1);
|
|
88
|
+
return middleware.call(...args.slice(0, -1), next);
|
|
89
|
+
};
|
|
90
|
+
return callNext(0);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic job scheduler for Sidekiq.
|
|
3
|
+
*
|
|
4
|
+
* Allows registering jobs that run on a cron schedule.
|
|
5
|
+
* Only the leader process enqueues periodic jobs to prevent duplicates.
|
|
6
|
+
*/
|
|
7
|
+
import type { Config } from "./config.js";
|
|
8
|
+
import type { JobConstructor } from "./job.js";
|
|
9
|
+
import type { LeaderElector } from "./leader.js";
|
|
10
|
+
import type { JobOptions } from "./types.js";
|
|
11
|
+
export interface PeriodicJobOptions extends JobOptions {
|
|
12
|
+
/** Job arguments */
|
|
13
|
+
args?: unknown[];
|
|
14
|
+
}
|
|
15
|
+
export interface PeriodicJobConfig {
|
|
16
|
+
/** Unique identifier for this periodic job */
|
|
17
|
+
lid: string;
|
|
18
|
+
/** Cron expression */
|
|
19
|
+
cron: string;
|
|
20
|
+
/** Job class name */
|
|
21
|
+
class: string;
|
|
22
|
+
/** Target queue */
|
|
23
|
+
queue: string;
|
|
24
|
+
/** Job arguments */
|
|
25
|
+
args: unknown[];
|
|
26
|
+
/** Additional job options */
|
|
27
|
+
options: PeriodicJobOptions;
|
|
28
|
+
}
|
|
29
|
+
export declare class PeriodicScheduler {
|
|
30
|
+
private readonly config;
|
|
31
|
+
private readonly leaderElector;
|
|
32
|
+
private readonly jobs;
|
|
33
|
+
private readonly client;
|
|
34
|
+
private redis?;
|
|
35
|
+
private running;
|
|
36
|
+
private loopPromise?;
|
|
37
|
+
constructor(config: Config, leaderElector: LeaderElector);
|
|
38
|
+
/**
|
|
39
|
+
* Register a periodic job.
|
|
40
|
+
*
|
|
41
|
+
* @param cron - Standard 5-field cron expression
|
|
42
|
+
* @param jobClass - Job class to execute
|
|
43
|
+
* @param options - Additional job options
|
|
44
|
+
* @returns The unique lid for this periodic job
|
|
45
|
+
*/
|
|
46
|
+
register(cron: string, jobClass: JobConstructor, options?: PeriodicJobOptions): string;
|
|
47
|
+
/**
|
|
48
|
+
* Unregister a periodic job by its lid.
|
|
49
|
+
*/
|
|
50
|
+
unregister(lid: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* List all registered periodic jobs.
|
|
53
|
+
*/
|
|
54
|
+
list(): PeriodicJobConfig[];
|
|
55
|
+
/**
|
|
56
|
+
* Start the periodic job loop.
|
|
57
|
+
*/
|
|
58
|
+
start(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Stop the periodic job loop.
|
|
61
|
+
*/
|
|
62
|
+
stop(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Generate a deterministic lid for a periodic job.
|
|
65
|
+
*/
|
|
66
|
+
private generateLid;
|
|
67
|
+
/**
|
|
68
|
+
* Sync registered jobs to Redis for visibility.
|
|
69
|
+
*/
|
|
70
|
+
private syncToRedis;
|
|
71
|
+
/**
|
|
72
|
+
* Background loop for periodic job scheduling.
|
|
73
|
+
*/
|
|
74
|
+
private periodicLoop;
|
|
75
|
+
/**
|
|
76
|
+
* Enqueue a periodic job if it hasn't been enqueued recently.
|
|
77
|
+
*/
|
|
78
|
+
private enqueueIfNotRecent;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=periodic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"periodic.d.ts","sourceRoot":"","sources":["../src/periodic.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAQ7C,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,oBAAoB;IACpB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,6BAA6B;IAC7B,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AAQD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAC,CAAgB;gBAExB,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa;IAMxD;;;;;;;OAOG;IACH,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE,kBAAuB,GAC/B,MAAM;IA2BT;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAQhC;;OAEG;IACH,IAAI,IAAI,iBAAiB,EAAE;IAI3B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAe5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB3B;;OAEG;IACH,OAAO,CAAC,WAAW;IAiBnB;;OAEG;YACW,WAAW;IAoBzB;;OAEG;YAEW,YAAY;IA2C1B;;OAEG;YACW,kBAAkB;CAmCjC"}
|
package/dist/periodic.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic job scheduler for Sidekiq.
|
|
3
|
+
*
|
|
4
|
+
* Allows registering jobs that run on a cron schedule.
|
|
5
|
+
* Only the leader process enqueues periodic jobs to prevent duplicates.
|
|
6
|
+
*/
|
|
7
|
+
import { Client } from "./client.js";
|
|
8
|
+
import { parseCron, shouldRunAt } from "./cron.js";
|
|
9
|
+
import { dumpJson } from "./json.js";
|
|
10
|
+
const CRON_KEY = "cron";
|
|
11
|
+
const CRON_LOCK_PREFIX = "cron:lock:";
|
|
12
|
+
const CRON_LOCK_TTL_SECONDS = 60;
|
|
13
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
+
export class PeriodicScheduler {
|
|
15
|
+
config;
|
|
16
|
+
leaderElector;
|
|
17
|
+
jobs = new Map();
|
|
18
|
+
client;
|
|
19
|
+
redis;
|
|
20
|
+
running = false;
|
|
21
|
+
loopPromise;
|
|
22
|
+
constructor(config, leaderElector) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.leaderElector = leaderElector;
|
|
25
|
+
this.client = new Client({ config });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register a periodic job.
|
|
29
|
+
*
|
|
30
|
+
* @param cron - Standard 5-field cron expression
|
|
31
|
+
* @param jobClass - Job class to execute
|
|
32
|
+
* @param options - Additional job options
|
|
33
|
+
* @returns The unique lid for this periodic job
|
|
34
|
+
*/
|
|
35
|
+
register(cron, jobClass, options = {}) {
|
|
36
|
+
const schedule = parseCron(cron);
|
|
37
|
+
const queue = options.queue ?? jobClass.getSidekiqOptions().queue ?? "default";
|
|
38
|
+
const args = options.args ?? [];
|
|
39
|
+
// Generate a deterministic lid based on class name and cron
|
|
40
|
+
const lid = this.generateLid(jobClass.name, cron, queue, args);
|
|
41
|
+
const config = {
|
|
42
|
+
lid,
|
|
43
|
+
cron,
|
|
44
|
+
class: jobClass.name,
|
|
45
|
+
queue,
|
|
46
|
+
args,
|
|
47
|
+
options,
|
|
48
|
+
};
|
|
49
|
+
this.jobs.set(lid, { config, schedule, jobClass });
|
|
50
|
+
this.config.logger.debug(() => `Registered periodic job: ${jobClass.name} (${cron})`);
|
|
51
|
+
return lid;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Unregister a periodic job by its lid.
|
|
55
|
+
*/
|
|
56
|
+
unregister(lid) {
|
|
57
|
+
const existed = this.jobs.delete(lid);
|
|
58
|
+
if (existed) {
|
|
59
|
+
this.config.logger.debug(() => `Unregistered periodic job: ${lid}`);
|
|
60
|
+
}
|
|
61
|
+
return existed;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* List all registered periodic jobs.
|
|
65
|
+
*/
|
|
66
|
+
list() {
|
|
67
|
+
return Array.from(this.jobs.values()).map((job) => job.config);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Start the periodic job loop.
|
|
71
|
+
*/
|
|
72
|
+
async start() {
|
|
73
|
+
if (this.running) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.redis = await this.config.getRedisClient();
|
|
77
|
+
this.running = true;
|
|
78
|
+
// Persist job configs to Redis for visibility
|
|
79
|
+
await this.syncToRedis();
|
|
80
|
+
// Start the background loop
|
|
81
|
+
this.loopPromise = this.periodicLoop();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Stop the periodic job loop.
|
|
85
|
+
*/
|
|
86
|
+
async stop() {
|
|
87
|
+
if (!this.running) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.running = false;
|
|
91
|
+
// Remove job configs from Redis
|
|
92
|
+
if (this.redis && this.jobs.size > 0) {
|
|
93
|
+
try {
|
|
94
|
+
const lids = Array.from(this.jobs.keys());
|
|
95
|
+
await this.redis.hDel(CRON_KEY, lids);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Ignore errors during shutdown
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Wait for loop to finish
|
|
102
|
+
if (this.loopPromise) {
|
|
103
|
+
await this.loopPromise;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generate a deterministic lid for a periodic job.
|
|
108
|
+
*/
|
|
109
|
+
generateLid(className, cron, queue, args) {
|
|
110
|
+
// Create a simple hash of the job configuration
|
|
111
|
+
const base = `${className}:${cron}:${queue}:${dumpJson(args)}`;
|
|
112
|
+
let hash = 0;
|
|
113
|
+
for (let i = 0; i < base.length; i += 1) {
|
|
114
|
+
const char = base.charCodeAt(i);
|
|
115
|
+
// biome-ignore lint/suspicious/noBitwiseOperators: intentional hash computation
|
|
116
|
+
hash = ((hash << 5) - hash + char) | 0;
|
|
117
|
+
}
|
|
118
|
+
return `${className.toLowerCase()}-${Math.abs(hash).toString(36)}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Sync registered jobs to Redis for visibility.
|
|
122
|
+
*/
|
|
123
|
+
async syncToRedis() {
|
|
124
|
+
if (!this.redis || this.jobs.size === 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const entries = {};
|
|
128
|
+
for (const [lid, job] of this.jobs) {
|
|
129
|
+
entries[lid] = dumpJson(job.config);
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
await this.redis.hSet(CRON_KEY, entries);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
this.config.logger.error(() => `Failed to sync periodic jobs to Redis: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Background loop for periodic job scheduling.
|
|
140
|
+
*/
|
|
141
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: scheduler loop requires multiple conditional checks
|
|
142
|
+
async periodicLoop() {
|
|
143
|
+
// Wait for initial sync with jitter to avoid thundering herd
|
|
144
|
+
await sleep(Math.random() * 5000);
|
|
145
|
+
while (this.running) {
|
|
146
|
+
// Wait until the next minute boundary plus jitter
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
const nextMinute = Math.ceil(now / 60_000) * 60_000;
|
|
149
|
+
const jitter = Math.random() * 2000; // 0-2 seconds
|
|
150
|
+
const waitMs = Math.max(0, nextMinute - now + jitter);
|
|
151
|
+
await sleep(waitMs);
|
|
152
|
+
if (!this.running) {
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
// Only leader enqueues jobs
|
|
156
|
+
if (!this.leaderElector.leader()) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Check each registered job
|
|
160
|
+
const checkTime = new Date();
|
|
161
|
+
for (const [lid, job] of this.jobs) {
|
|
162
|
+
if (!this.running) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
if (shouldRunAt(job.schedule, checkTime)) {
|
|
167
|
+
await this.enqueueIfNotRecent(lid, job);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
this.config.logger.error(() => `Error checking periodic job ${lid}: ${error.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Enqueue a periodic job if it hasn't been enqueued recently.
|
|
178
|
+
*/
|
|
179
|
+
async enqueueIfNotRecent(lid, job) {
|
|
180
|
+
if (!this.redis) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Try to acquire the lock (prevents duplicate enqueue)
|
|
184
|
+
const lockKey = `${CRON_LOCK_PREFIX}${lid}`;
|
|
185
|
+
const result = await this.redis.set(lockKey, "1", {
|
|
186
|
+
NX: true,
|
|
187
|
+
EX: CRON_LOCK_TTL_SECONDS,
|
|
188
|
+
});
|
|
189
|
+
if (result !== "OK") {
|
|
190
|
+
// Lock already held, job was already enqueued this minute
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Enqueue the job
|
|
194
|
+
const { queue, retry, tags, ...restOptions } = job.config.options;
|
|
195
|
+
const jid = await this.client.push({
|
|
196
|
+
class: job.jobClass,
|
|
197
|
+
args: job.config.args,
|
|
198
|
+
queue: job.config.queue,
|
|
199
|
+
retry,
|
|
200
|
+
tags,
|
|
201
|
+
...restOptions,
|
|
202
|
+
});
|
|
203
|
+
this.config.logger.info(() => `Enqueued periodic job ${job.config.class} (${lid}) with jid ${jid}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
package/dist/redis.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../src/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAE1C,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC"}
|
package/dist/redis.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type RegisteredJobClass = new () => {
|
|
2
|
+
perform: (...args: any[]) => Promise<void> | void;
|
|
3
|
+
jid?: string;
|
|
4
|
+
_context?: {
|
|
5
|
+
stopping: () => boolean;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export declare const registerJob: (klass: RegisteredJobClass) => void;
|
|
9
|
+
export declare const resolveJob: (name: string) => RegisteredJobClass | undefined;
|
|
10
|
+
export declare const registeredJob: (name: string) => RegisteredJobClass | undefined;
|
|
11
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,UAAU;IAEzC,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;KAAE,CAAC;CACxC,CAAC;AAIF,eAAO,MAAM,WAAW,GAAI,OAAO,kBAAkB,KAAG,IAIvD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,kBAAkB,GAAG,SAC3C,CAAC;AAErB,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,kBAAkB,GAAG,SAC9C,CAAC"}
|
package/dist/registry.js
ADDED
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
import { PeriodicScheduler } from "./periodic.js";
|
|
3
|
+
import type { JobPayload } from "./types.js";
|
|
4
|
+
export interface WorkSnapshot {
|
|
5
|
+
workerId: string;
|
|
6
|
+
queue: string;
|
|
7
|
+
payloadRaw: string;
|
|
8
|
+
payload?: JobPayload;
|
|
9
|
+
runAt: number;
|
|
10
|
+
elapsed: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class Runner {
|
|
13
|
+
private readonly config;
|
|
14
|
+
private quieting;
|
|
15
|
+
private stopping;
|
|
16
|
+
private readonly workers;
|
|
17
|
+
private schedulerHandle?;
|
|
18
|
+
private schedulerRunning;
|
|
19
|
+
private heartbeatHandle?;
|
|
20
|
+
private readonly queueStrategy;
|
|
21
|
+
private baseRedis?;
|
|
22
|
+
private readonly workerRedis;
|
|
23
|
+
private readonly identity;
|
|
24
|
+
private readonly startedAt;
|
|
25
|
+
private readonly workState;
|
|
26
|
+
private readonly inProgress;
|
|
27
|
+
private lastCleanupAt;
|
|
28
|
+
private readonly rttReadings;
|
|
29
|
+
private readonly jobLogger;
|
|
30
|
+
private leaderElector?;
|
|
31
|
+
private _periodicScheduler?;
|
|
32
|
+
constructor(config: Config);
|
|
33
|
+
start(): Promise<void>;
|
|
34
|
+
quiet(): Promise<void>;
|
|
35
|
+
stop(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Returns true if this process is currently the leader.
|
|
38
|
+
*/
|
|
39
|
+
leader(): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Get the periodic scheduler for registering cron jobs.
|
|
42
|
+
*/
|
|
43
|
+
get periodicScheduler(): PeriodicScheduler | undefined;
|
|
44
|
+
snapshotWork(): WorkSnapshot[];
|
|
45
|
+
private startScheduler;
|
|
46
|
+
private runSchedulerLoop;
|
|
47
|
+
private initialWait;
|
|
48
|
+
private processCount;
|
|
49
|
+
private scaledPollInterval;
|
|
50
|
+
private randomPollInterval;
|
|
51
|
+
private stopScheduler;
|
|
52
|
+
private heartbeat;
|
|
53
|
+
private handleSignal;
|
|
54
|
+
private dumpWorkState;
|
|
55
|
+
private clearHeartbeat;
|
|
56
|
+
private processInfo;
|
|
57
|
+
private checkRtt;
|
|
58
|
+
private recordRtt;
|
|
59
|
+
private waitForDrain;
|
|
60
|
+
private waitForWorkers;
|
|
61
|
+
private requeueInProgress;
|
|
62
|
+
private sendRawToMorgue;
|
|
63
|
+
private runWithProfiler;
|
|
64
|
+
private cleanupProcesses;
|
|
65
|
+
private startHeartbeat;
|
|
66
|
+
private stopHeartbeat;
|
|
67
|
+
private enqueueScheduled;
|
|
68
|
+
private workLoop;
|
|
69
|
+
private fetchWork;
|
|
70
|
+
private processJob;
|
|
71
|
+
private handleFailure;
|
|
72
|
+
private safeRetryIn;
|
|
73
|
+
private retriesExhausted;
|
|
74
|
+
private sendToMorgue;
|
|
75
|
+
private runDeathHandlers;
|
|
76
|
+
private runErrorHandlers;
|
|
77
|
+
private buildErrorContext;
|
|
78
|
+
private safeErrorMessage;
|
|
79
|
+
private updateStat;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAU1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAqG7C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,SAAS,CAAC,CAAgD;IAClE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAEnB;IACT,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAGtB;IACJ,OAAO,CAAC,QAAQ,CAAC,UAAU,CAGvB;IACJ,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,kBAAkB,CAAC,CAAoB;gBAEnC,MAAM,EAAE,MAAM;IAQpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B3B;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,IAAI,iBAAiB,IAAI,iBAAiB,GAAG,SAAS,CAErD;IAED,YAAY,IAAI,YAAY,EAAE;IAsB9B,OAAO,CAAC,cAAc;YAKR,gBAAgB;YAWhB,WAAW;YAoBX,YAAY;IAM1B,OAAO,CAAC,kBAAkB;YAIZ,kBAAkB;IAqBhC,OAAO,CAAC,aAAa;YAQP,SAAS;YA0DT,YAAY;IAoB1B,OAAO,CAAC,aAAa;YAkBP,cAAc;IAa5B,OAAO,CAAC,WAAW;YAcL,QAAQ;IAWtB,OAAO,CAAC,SAAS;YAoBH,YAAY;YAMZ,cAAc;YAQd,iBAAiB;YAkBjB,eAAe;YAWf,eAAe;YAWf,gBAAgB;IA8B9B,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,aAAa;YAOP,gBAAgB;YA6BhB,QAAQ;YAiCR,SAAS;YA4BT,UAAU;YAwEV,aAAa;IA2H3B,OAAO,CAAC,WAAW;YAoBL,gBAAgB;YA8BhB,YAAY;YAYZ,gBAAgB;YAmBhB,gBAAgB;IAsB9B,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,gBAAgB;YAQV,UAAU;CAczB"}
|