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.
Files changed (86) hide show
  1. package/README.md +686 -0
  2. package/dist/api.d.ts +172 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +679 -0
  5. package/dist/backtrace.d.ts +3 -0
  6. package/dist/backtrace.d.ts.map +1 -0
  7. package/dist/backtrace.js +16 -0
  8. package/dist/cli-helpers.d.ts +22 -0
  9. package/dist/cli-helpers.d.ts.map +1 -0
  10. package/dist/cli-helpers.js +152 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +143 -0
  14. package/dist/client.d.ts +25 -0
  15. package/dist/client.d.ts.map +1 -0
  16. package/dist/client.js +212 -0
  17. package/dist/config-loader.d.ts +16 -0
  18. package/dist/config-loader.d.ts.map +1 -0
  19. package/dist/config-loader.js +37 -0
  20. package/dist/config.d.ts +59 -0
  21. package/dist/config.d.ts.map +1 -0
  22. package/dist/config.js +155 -0
  23. package/dist/context.d.ts +10 -0
  24. package/dist/context.d.ts.map +1 -0
  25. package/dist/context.js +29 -0
  26. package/dist/cron.d.ts +44 -0
  27. package/dist/cron.d.ts.map +1 -0
  28. package/dist/cron.js +173 -0
  29. package/dist/index.d.ts +16 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +14 -0
  32. package/dist/interrupt-handler.d.ts +8 -0
  33. package/dist/interrupt-handler.d.ts.map +1 -0
  34. package/dist/interrupt-handler.js +24 -0
  35. package/dist/iterable-constants.d.ts +3 -0
  36. package/dist/iterable-constants.d.ts.map +1 -0
  37. package/dist/iterable-constants.js +2 -0
  38. package/dist/iterable-errors.d.ts +10 -0
  39. package/dist/iterable-errors.d.ts.map +1 -0
  40. package/dist/iterable-errors.js +18 -0
  41. package/dist/iterable.d.ts +44 -0
  42. package/dist/iterable.d.ts.map +1 -0
  43. package/dist/iterable.js +298 -0
  44. package/dist/job-logger.d.ts +12 -0
  45. package/dist/job-logger.d.ts.map +1 -0
  46. package/dist/job-logger.js +64 -0
  47. package/dist/job-util.d.ts +8 -0
  48. package/dist/job-util.d.ts.map +1 -0
  49. package/dist/job-util.js +158 -0
  50. package/dist/job.d.ts +73 -0
  51. package/dist/job.d.ts.map +1 -0
  52. package/dist/job.js +200 -0
  53. package/dist/json.d.ts +3 -0
  54. package/dist/json.d.ts.map +1 -0
  55. package/dist/json.js +2 -0
  56. package/dist/leader.d.ts +63 -0
  57. package/dist/leader.d.ts.map +1 -0
  58. package/dist/leader.js +193 -0
  59. package/dist/logger.d.ts +53 -0
  60. package/dist/logger.d.ts.map +1 -0
  61. package/dist/logger.js +143 -0
  62. package/dist/middleware.d.ts +23 -0
  63. package/dist/middleware.d.ts.map +1 -0
  64. package/dist/middleware.js +92 -0
  65. package/dist/periodic.d.ts +80 -0
  66. package/dist/periodic.d.ts.map +1 -0
  67. package/dist/periodic.js +205 -0
  68. package/dist/redis.d.ts +3 -0
  69. package/dist/redis.d.ts.map +1 -0
  70. package/dist/redis.js +1 -0
  71. package/dist/registry.d.ts +11 -0
  72. package/dist/registry.d.ts.map +1 -0
  73. package/dist/registry.js +8 -0
  74. package/dist/runner.d.ts +81 -0
  75. package/dist/runner.d.ts.map +1 -0
  76. package/dist/runner.js +791 -0
  77. package/dist/sidekiq.d.ts +43 -0
  78. package/dist/sidekiq.d.ts.map +1 -0
  79. package/dist/sidekiq.js +189 -0
  80. package/dist/testing.d.ts +32 -0
  81. package/dist/testing.d.ts.map +1 -0
  82. package/dist/testing.js +112 -0
  83. package/dist/types.d.ts +116 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +1 -0
  86. package/package.json +42 -0
@@ -0,0 +1,59 @@
1
+ import type { RedisClientOptions } from "redis";
2
+ import { type Logger } from "./logger.js";
3
+ import { MiddlewareChain } from "./middleware.js";
4
+ import type { RedisClient } from "./redis.js";
5
+ import type { ConfigOptions, DeathHandler, ErrorHandler, JobLogger, JobPayload, LeaderElectionConfig, LifecycleEvents, StrictArgsMode } from "./types.js";
6
+ export declare class Config {
7
+ redis: RedisClientOptions;
8
+ concurrency: number;
9
+ queues: string[] | [string, number][];
10
+ timeout: number;
11
+ pollIntervalAverage: number | null;
12
+ averageScheduledPollInterval: number;
13
+ heartbeatInterval: number;
14
+ tag: string;
15
+ labels: string[];
16
+ maxRetries: number;
17
+ deadMaxJobs: number;
18
+ deadTimeoutInSeconds: number;
19
+ backtraceCleaner: (backtrace: string[]) => string[];
20
+ maxIterationRuntime: number | null;
21
+ skipDefaultJobLogging: boolean;
22
+ loggedJobAttributes: string[];
23
+ profiler?: (payload: JobPayload, fn: () => Promise<void>) => Promise<void>;
24
+ strictArgs: StrictArgsMode;
25
+ errorHandlers: ErrorHandler[];
26
+ deathHandlers: DeathHandler[];
27
+ lifecycleEvents: LifecycleEvents;
28
+ logger: Logger;
29
+ redisIdleTimeout: number | null;
30
+ jobLogger: JobLogger;
31
+ clientMiddleware: MiddlewareChain<[
32
+ string | unknown,
33
+ JobPayload,
34
+ string,
35
+ RedisClient
36
+ ], JobPayload | false | null | undefined>;
37
+ serverMiddleware: MiddlewareChain<[unknown, JobPayload, string], unknown>;
38
+ leaderElection: LeaderElectionConfig;
39
+ private redisClient?;
40
+ constructor(options?: ConfigOptions);
41
+ getRedisClient(): Promise<RedisClient>;
42
+ close(): Promise<void>;
43
+ queueNames(): string[];
44
+ /**
45
+ * Fire a lifecycle event, calling all registered handlers.
46
+ *
47
+ * @param event - The event name to fire
48
+ * @param options - Options for firing the event
49
+ * @param options.oneshot - If true, clear handlers after firing (default: true)
50
+ * @param options.reverse - If true, call handlers in reverse order
51
+ * @param options.reraise - If true, re-throw any errors from handlers
52
+ */
53
+ fireEvent(event: keyof LifecycleEvents, options?: {
54
+ oneshot?: boolean;
55
+ reverse?: boolean;
56
+ reraise?: boolean;
57
+ }): Promise<void>;
58
+ }
59
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAIhD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,UAAU,EACV,oBAAoB,EACpB,eAAe,EACf,cAAc,EACf,MAAM,YAAY,CAAC;AAapB,qBAAa,MAAM;IACjB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,4BAA4B,EAAE,MAAM,CAAC;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;IACpD,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,UAAU,EAAE,cAAc,CAAC;IAC3B,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,SAAS,CAAC;IACrB,gBAAgB,EAAE,eAAe,CAC/B;QAAC,MAAM,GAAG,OAAO;QAAE,UAAU;QAAE,MAAM;QAAE,WAAW;KAAC,EACnD,UAAU,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,CACtC,CAAC;IACF,gBAAgB,EAAE,eAAe,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IAC1E,cAAc,EAAE,oBAAoB,CAAC;IACrC,OAAO,CAAC,WAAW,CAAC,CAAc;gBAGtB,OAAO,GAAE,aAAkB;IAgDjC,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAatC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,UAAU,IAAI,MAAM,EAAE;IAatB;;;;;;;;OAQG;IACG,SAAS,CACb,KAAK,EAAE,MAAM,eAAe,EAC5B,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO,GACxE,OAAO,CAAC,IAAI,CAAC;CA+BjB"}
package/dist/config.js ADDED
@@ -0,0 +1,155 @@
1
+ import { createClient } from "redis";
2
+ import { Context } from "./context.js";
3
+ import { DefaultJobLogger } from "./job-logger.js";
4
+ import { createLogger } from "./logger.js";
5
+ import { MiddlewareChain } from "./middleware.js";
6
+ const DEFAULT_LIFECYCLE_EVENTS = {
7
+ startup: [],
8
+ quiet: [],
9
+ shutdown: [],
10
+ exit: [],
11
+ heartbeat: [],
12
+ beat: [],
13
+ leader: [],
14
+ follower: [],
15
+ };
16
+ export class Config {
17
+ redis;
18
+ concurrency;
19
+ queues;
20
+ timeout;
21
+ pollIntervalAverage;
22
+ averageScheduledPollInterval;
23
+ heartbeatInterval;
24
+ tag;
25
+ labels;
26
+ maxRetries;
27
+ deadMaxJobs;
28
+ deadTimeoutInSeconds;
29
+ backtraceCleaner;
30
+ maxIterationRuntime;
31
+ skipDefaultJobLogging;
32
+ loggedJobAttributes;
33
+ profiler;
34
+ strictArgs;
35
+ errorHandlers;
36
+ deathHandlers;
37
+ lifecycleEvents;
38
+ logger;
39
+ redisIdleTimeout;
40
+ jobLogger;
41
+ clientMiddleware;
42
+ serverMiddleware;
43
+ leaderElection;
44
+ redisClient;
45
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: config initialization requires many defaults
46
+ constructor(options = {}) {
47
+ this.redis = options.redis ?? {
48
+ url: process.env.REDIS_URL ?? "redis://localhost:6379/0",
49
+ };
50
+ this.concurrency = options.concurrency ?? 5;
51
+ this.queues = options.queues ?? ["default"];
52
+ this.timeout = options.timeout ?? 25;
53
+ this.pollIntervalAverage = options.pollIntervalAverage ?? null;
54
+ this.averageScheduledPollInterval =
55
+ options.averageScheduledPollInterval ?? 5;
56
+ this.heartbeatInterval = options.heartbeatInterval ?? 10;
57
+ this.tag = options.tag ?? "";
58
+ this.labels = options.labels ?? [];
59
+ this.maxRetries = options.maxRetries ?? 25;
60
+ this.deadMaxJobs = options.deadMaxJobs ?? 10_000;
61
+ this.deadTimeoutInSeconds =
62
+ options.deadTimeoutInSeconds ?? 180 * 24 * 60 * 60;
63
+ this.backtraceCleaner =
64
+ options.backtraceCleaner ?? ((backtrace) => backtrace);
65
+ this.maxIterationRuntime = options.maxIterationRuntime ?? null;
66
+ this.skipDefaultJobLogging = options.skipDefaultJobLogging ?? false;
67
+ this.loggedJobAttributes = options.loggedJobAttributes ?? ["tags"];
68
+ this.profiler = options.profiler;
69
+ this.strictArgs = options.strictArgs ?? "raise";
70
+ this.errorHandlers = options.errorHandlers ?? [];
71
+ this.deathHandlers = options.deathHandlers ?? [];
72
+ this.lifecycleEvents = {
73
+ ...DEFAULT_LIFECYCLE_EVENTS,
74
+ ...(options.lifecycleEvents ?? {}),
75
+ };
76
+ this.logger = options.logger ?? createLogger();
77
+ this.redisIdleTimeout = options.redisIdleTimeout ?? null;
78
+ this.jobLogger = options.jobLogger ?? new DefaultJobLogger(this);
79
+ this.clientMiddleware = new MiddlewareChain(this);
80
+ this.serverMiddleware = new MiddlewareChain(this);
81
+ this.leaderElection = options.leaderElection ?? {};
82
+ if (this.errorHandlers.length === 0) {
83
+ this.errorHandlers.push((error, context) => {
84
+ Context.with(context, () => {
85
+ const message = error.stack ?? `${error.name}: ${error.message ?? "Unknown error"}`;
86
+ this.logger.info(() => message);
87
+ });
88
+ });
89
+ }
90
+ }
91
+ async getRedisClient() {
92
+ if (this.redisClient?.isOpen) {
93
+ return this.redisClient;
94
+ }
95
+ const client = createClient(this.redis);
96
+ client.on("error", (error) => {
97
+ this.logger.error(() => `Redis error: ${error.message}`);
98
+ });
99
+ await client.connect();
100
+ this.redisClient = client;
101
+ return client;
102
+ }
103
+ async close() {
104
+ if (this.redisClient?.isOpen) {
105
+ await this.redisClient.quit();
106
+ }
107
+ }
108
+ queueNames() {
109
+ const names = new Set();
110
+ for (const entry of this.queues) {
111
+ if (Array.isArray(entry)) {
112
+ names.add(entry[0]);
113
+ }
114
+ else {
115
+ const [name] = entry.split(",", 1);
116
+ names.add(name);
117
+ }
118
+ }
119
+ return Array.from(names);
120
+ }
121
+ /**
122
+ * Fire a lifecycle event, calling all registered handlers.
123
+ *
124
+ * @param event - The event name to fire
125
+ * @param options - Options for firing the event
126
+ * @param options.oneshot - If true, clear handlers after firing (default: true)
127
+ * @param options.reverse - If true, call handlers in reverse order
128
+ * @param options.reraise - If true, re-throw any errors from handlers
129
+ */
130
+ async fireEvent(event, options = {}) {
131
+ const { oneshot = true, reverse = false, reraise = false } = options;
132
+ if (oneshot) {
133
+ this.logger.debug(() => `Firing ${event} event`);
134
+ }
135
+ const handlers = [...this.lifecycleEvents[event]];
136
+ if (reverse) {
137
+ handlers.reverse();
138
+ }
139
+ for (const handler of handlers) {
140
+ try {
141
+ await handler();
142
+ }
143
+ catch (error) {
144
+ const err = error instanceof Error ? error : new Error(String(error));
145
+ this.logger.error(() => `Exception during Sidekiq lifecycle event ${event}: ${err.message}`);
146
+ if (reraise) {
147
+ throw err;
148
+ }
149
+ }
150
+ }
151
+ if (oneshot) {
152
+ this.lifecycleEvents[event] = [];
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,10 @@
1
+ export type ContextValue = unknown;
2
+ export type ContextMap = Record<string, ContextValue>;
3
+ export declare class Context {
4
+ private static storage;
5
+ static current(): ContextMap;
6
+ static peek(): ContextMap | undefined;
7
+ static with<T>(values: ContextMap, fn: () => Promise<T> | T): Promise<T> | T;
8
+ static add(key: string, value: ContextValue): void;
9
+ }
10
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC;AAEnC,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAGtD,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAC,OAAO,CAAuC;IAE7D,MAAM,CAAC,OAAO,IAAI,UAAU;IAU5B,MAAM,CAAC,IAAI,IAAI,UAAU,GAAG,SAAS;IAIrC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAK5E,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;CAQnD"}
@@ -0,0 +1,29 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ // biome-ignore lint/complexity/noStaticOnlyClass: Context class provides namespace for async context management
3
+ export class Context {
4
+ static storage = new AsyncLocalStorage();
5
+ static current() {
6
+ const store = this.storage.getStore();
7
+ if (store) {
8
+ return store;
9
+ }
10
+ const initial = {};
11
+ this.storage.enterWith(initial);
12
+ return initial;
13
+ }
14
+ static peek() {
15
+ return this.storage.getStore();
16
+ }
17
+ static with(values, fn) {
18
+ const merged = { ...this.current(), ...values };
19
+ return this.storage.run(merged, fn);
20
+ }
21
+ static add(key, value) {
22
+ const store = this.storage.getStore();
23
+ if (store) {
24
+ store[key] = value;
25
+ return;
26
+ }
27
+ this.storage.enterWith({ [key]: value });
28
+ }
29
+ }
package/dist/cron.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Cron expression parser for standard 5-field cron syntax.
3
+ *
4
+ * Format: minute hour day-of-month month day-of-week
5
+ *
6
+ * Supported syntax:
7
+ * - `*` - any value
8
+ * - `N` - specific value
9
+ * - `N-M` - range from N to M (inclusive)
10
+ * - `N,M,O` - list of values
11
+ * - `*\/N` or `N/M` - step values (every N starting at 0, or every M starting at N)
12
+ */
13
+ export interface CronSchedule {
14
+ minute: number[];
15
+ hour: number[];
16
+ dayOfMonth: number[];
17
+ month: number[];
18
+ dayOfWeek: number[];
19
+ }
20
+ /**
21
+ * Parse a cron expression into a schedule object.
22
+ *
23
+ * @param expression - Standard 5-field cron expression (minute hour day month weekday)
24
+ * @returns Parsed schedule with arrays of valid values for each field
25
+ * @throws Error if the expression is invalid
26
+ */
27
+ export declare function parseCron(expression: string): CronSchedule;
28
+ /**
29
+ * Check if a schedule should run at the given date/time.
30
+ *
31
+ * @param schedule - Parsed cron schedule
32
+ * @param at - Date to check (defaults to now)
33
+ * @returns True if the schedule matches the given time
34
+ */
35
+ export declare function shouldRunAt(schedule: CronSchedule, at?: Date): boolean;
36
+ /**
37
+ * Calculate the next run time after a given date.
38
+ *
39
+ * @param schedule - Parsed cron schedule
40
+ * @param after - Start searching from this date (defaults to now)
41
+ * @returns The next date/time when the schedule will run
42
+ */
43
+ export declare function nextRun(schedule: CronSchedule, after?: Date): Date;
44
+ //# sourceMappingURL=cron.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["../src/cron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAwHD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAiB1D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,YAAY,EACtB,EAAE,GAAE,IAAiB,GACpB,OAAO,CAcT;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CACrB,QAAQ,EAAE,YAAY,EACtB,KAAK,GAAE,IAAiB,GACvB,IAAI,CAoBN"}
package/dist/cron.js ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Cron expression parser for standard 5-field cron syntax.
3
+ *
4
+ * Format: minute hour day-of-month month day-of-week
5
+ *
6
+ * Supported syntax:
7
+ * - `*` - any value
8
+ * - `N` - specific value
9
+ * - `N-M` - range from N to M (inclusive)
10
+ * - `N,M,O` - list of values
11
+ * - `*\/N` or `N/M` - step values (every N starting at 0, or every M starting at N)
12
+ */
13
+ const FIELD_RANGES = {
14
+ minute: { min: 0, max: 59 },
15
+ hour: { min: 0, max: 23 },
16
+ dayOfMonth: { min: 1, max: 31 },
17
+ month: { min: 1, max: 12 },
18
+ dayOfWeek: { min: 0, max: 6 },
19
+ };
20
+ const MONTH_NAMES = {
21
+ jan: 1,
22
+ feb: 2,
23
+ mar: 3,
24
+ apr: 4,
25
+ may: 5,
26
+ jun: 6,
27
+ jul: 7,
28
+ aug: 8,
29
+ sep: 9,
30
+ oct: 10,
31
+ nov: 11,
32
+ dec: 12,
33
+ };
34
+ const DAY_NAMES = {
35
+ sun: 0,
36
+ mon: 1,
37
+ tue: 2,
38
+ wed: 3,
39
+ thu: 4,
40
+ fri: 5,
41
+ sat: 6,
42
+ };
43
+ const WHITESPACE_REGEX = /\s+/;
44
+ function parseValue(value, range, names) {
45
+ const lower = value.toLowerCase();
46
+ if (names && lower in names) {
47
+ return names[lower];
48
+ }
49
+ const num = Number.parseInt(value, 10);
50
+ if (Number.isNaN(num) || num < range.min || num > range.max) {
51
+ throw new Error(`Invalid cron value: ${value} (expected ${range.min}-${range.max})`);
52
+ }
53
+ return num;
54
+ }
55
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: cron parsing requires handling multiple syntax forms
56
+ function parseField(field, range, names) {
57
+ const values = new Set();
58
+ for (const part of field.split(",")) {
59
+ if (part.includes("/")) {
60
+ // Step value: */N or N/M or N-M/S
61
+ const [rangeStr, stepStr] = part.split("/");
62
+ const step = Number.parseInt(stepStr, 10);
63
+ if (Number.isNaN(step) || step <= 0) {
64
+ throw new Error(`Invalid cron step: ${stepStr}`);
65
+ }
66
+ let start;
67
+ let end;
68
+ if (rangeStr === "*") {
69
+ start = range.min;
70
+ end = range.max;
71
+ }
72
+ else if (rangeStr.includes("-")) {
73
+ const [startStr, endStr] = rangeStr.split("-");
74
+ start = parseValue(startStr, range, names);
75
+ end = parseValue(endStr, range, names);
76
+ }
77
+ else {
78
+ start = parseValue(rangeStr, range, names);
79
+ end = range.max;
80
+ }
81
+ for (let i = start; i <= end; i += step) {
82
+ values.add(i);
83
+ }
84
+ }
85
+ else if (part.includes("-")) {
86
+ // Range: N-M
87
+ const [startStr, endStr] = part.split("-");
88
+ const start = parseValue(startStr, range, names);
89
+ const end = parseValue(endStr, range, names);
90
+ if (start > end) {
91
+ throw new Error(`Invalid cron range: ${part}`);
92
+ }
93
+ for (let i = start; i <= end; i += 1) {
94
+ values.add(i);
95
+ }
96
+ }
97
+ else if (part === "*") {
98
+ // Any value
99
+ for (let i = range.min; i <= range.max; i += 1) {
100
+ values.add(i);
101
+ }
102
+ }
103
+ else {
104
+ // Single value
105
+ values.add(parseValue(part, range, names));
106
+ }
107
+ }
108
+ return Array.from(values).sort((a, b) => a - b);
109
+ }
110
+ /**
111
+ * Parse a cron expression into a schedule object.
112
+ *
113
+ * @param expression - Standard 5-field cron expression (minute hour day month weekday)
114
+ * @returns Parsed schedule with arrays of valid values for each field
115
+ * @throws Error if the expression is invalid
116
+ */
117
+ export function parseCron(expression) {
118
+ const parts = expression.trim().split(WHITESPACE_REGEX);
119
+ if (parts.length !== 5) {
120
+ throw new Error(`Invalid cron expression: expected 5 fields, got ${parts.length}`);
121
+ }
122
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
123
+ return {
124
+ minute: parseField(minute, FIELD_RANGES.minute),
125
+ hour: parseField(hour, FIELD_RANGES.hour),
126
+ dayOfMonth: parseField(dayOfMonth, FIELD_RANGES.dayOfMonth),
127
+ month: parseField(month, FIELD_RANGES.month, MONTH_NAMES),
128
+ dayOfWeek: parseField(dayOfWeek, FIELD_RANGES.dayOfWeek, DAY_NAMES),
129
+ };
130
+ }
131
+ /**
132
+ * Check if a schedule should run at the given date/time.
133
+ *
134
+ * @param schedule - Parsed cron schedule
135
+ * @param at - Date to check (defaults to now)
136
+ * @returns True if the schedule matches the given time
137
+ */
138
+ export function shouldRunAt(schedule, at = new Date()) {
139
+ const minute = at.getMinutes();
140
+ const hour = at.getHours();
141
+ const dayOfMonth = at.getDate();
142
+ const month = at.getMonth() + 1; // JS months are 0-indexed
143
+ const dayOfWeek = at.getDay();
144
+ return (schedule.minute.includes(minute) &&
145
+ schedule.hour.includes(hour) &&
146
+ schedule.dayOfMonth.includes(dayOfMonth) &&
147
+ schedule.month.includes(month) &&
148
+ schedule.dayOfWeek.includes(dayOfWeek));
149
+ }
150
+ /**
151
+ * Calculate the next run time after a given date.
152
+ *
153
+ * @param schedule - Parsed cron schedule
154
+ * @param after - Start searching from this date (defaults to now)
155
+ * @returns The next date/time when the schedule will run
156
+ */
157
+ export function nextRun(schedule, after = new Date()) {
158
+ // Start from the next minute
159
+ const next = new Date(after);
160
+ next.setSeconds(0);
161
+ next.setMilliseconds(0);
162
+ next.setMinutes(next.getMinutes() + 1);
163
+ // Search up to 4 years ahead (handles leap years and edge cases)
164
+ const maxIterations = 4 * 366 * 24 * 60;
165
+ for (let i = 0; i < maxIterations; i += 1) {
166
+ if (shouldRunAt(schedule, next)) {
167
+ return next;
168
+ }
169
+ // Increment by 1 minute
170
+ next.setMinutes(next.getMinutes() + 1);
171
+ }
172
+ throw new Error("Could not find next run time within 4 years");
173
+ }
@@ -0,0 +1,16 @@
1
+ export { DeadSet, JobRecord, ProcessSet, Queue, RetrySet, ScheduledSet, SortedEntry, Stats, StatsHistory, Workers, } from "./api.js";
2
+ export { Client } from "./client.js";
3
+ export { Config } from "./config.js";
4
+ export { loadConfigFile } from "./config-loader.js";
5
+ export { Context } from "./context.js";
6
+ export { InterruptHandler } from "./interrupt-handler.js";
7
+ export { IterableJob } from "./iterable.js";
8
+ export { IterableAbort, IterableInterrupted, JobSkipError, } from "./iterable-errors.js";
9
+ export { Job } from "./job.js";
10
+ export { DefaultJobLogger } from "./job-logger.js";
11
+ export { createLogger, Formatters, SidekiqLogger } from "./logger.js";
12
+ export { Runner } from "./runner.js";
13
+ export { Sidekiq } from "./sidekiq.js";
14
+ export { EmptyQueueError, Queues, Testing } from "./testing.js";
15
+ export type * from "./types.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,SAAS,EACT,UAAU,EACV,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,KAAK,EACL,YAAY,EACZ,OAAO,GACR,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,YAAY,GACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAChE,mBAAmB,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ export { DeadSet, JobRecord, ProcessSet, Queue, RetrySet, ScheduledSet, SortedEntry, Stats, StatsHistory, Workers, } from "./api.js";
2
+ export { Client } from "./client.js";
3
+ export { Config } from "./config.js";
4
+ export { loadConfigFile } from "./config-loader.js";
5
+ export { Context } from "./context.js";
6
+ export { InterruptHandler } from "./interrupt-handler.js";
7
+ export { IterableJob } from "./iterable.js";
8
+ export { IterableAbort, IterableInterrupted, JobSkipError, } from "./iterable-errors.js";
9
+ export { Job } from "./job.js";
10
+ export { DefaultJobLogger } from "./job-logger.js";
11
+ export { createLogger, Formatters, SidekiqLogger } from "./logger.js";
12
+ export { Runner } from "./runner.js";
13
+ export { Sidekiq } from "./sidekiq.js";
14
+ export { EmptyQueueError, Queues, Testing } from "./testing.js";
@@ -0,0 +1,8 @@
1
+ import type { Config } from "./config.js";
2
+ import type { JobPayload } from "./types.js";
3
+ export declare class InterruptHandler {
4
+ config?: Config;
5
+ call(_instance: unknown, payload: JobPayload, _queue: string, next: () => Promise<unknown> | unknown): Promise<unknown>;
6
+ }
7
+ export declare const ensureInterruptHandler: (config: Config) => void;
8
+ //# sourceMappingURL=interrupt-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interrupt-handler.d.ts","sourceRoot":"","sources":["../src/interrupt-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,qBAAa,gBAAgB;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAEV,IAAI,CACR,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GACrC,OAAO,CAAC,OAAO,CAAC;CAcpB;AAED,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,IAIvD,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { IterableInterrupted, JobSkipError } from "./iterable-errors.js";
2
+ export class InterruptHandler {
3
+ config;
4
+ async call(_instance, payload, _queue, next) {
5
+ try {
6
+ return await next();
7
+ }
8
+ catch (error) {
9
+ if (error instanceof IterableInterrupted) {
10
+ this.config?.logger.debug("Interrupted, re-queueing...");
11
+ const { Client } = await import("./client.js");
12
+ const client = new Client({ config: this.config });
13
+ await client.push(payload);
14
+ throw new JobSkipError();
15
+ }
16
+ throw error;
17
+ }
18
+ }
19
+ }
20
+ export const ensureInterruptHandler = (config) => {
21
+ if (!config.serverMiddleware.exists(InterruptHandler)) {
22
+ config.serverMiddleware.add(InterruptHandler);
23
+ }
24
+ };
@@ -0,0 +1,3 @@
1
+ export declare const ITERATION_STATE_FLUSH_INTERVAL_SECONDS = 5;
2
+ export declare const ITERATION_STATE_TTL_SECONDS: number;
3
+ //# sourceMappingURL=iterable-constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iterable-constants.d.ts","sourceRoot":"","sources":["../src/iterable-constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sCAAsC,IAAI,CAAC;AACxD,eAAO,MAAM,2BAA2B,QAAoB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export const ITERATION_STATE_FLUSH_INTERVAL_SECONDS = 5;
2
+ export const ITERATION_STATE_TTL_SECONDS = 30 * 24 * 60 * 60;
@@ -0,0 +1,10 @@
1
+ export declare class IterableInterrupted extends Error {
2
+ constructor(message?: string);
3
+ }
4
+ export declare class JobSkipError extends Error {
5
+ constructor(message?: string);
6
+ }
7
+ export declare class IterableAbort extends Error {
8
+ constructor();
9
+ }
10
+ //# sourceMappingURL=iterable-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iterable-errors.d.ts","sourceRoot":"","sources":["../src/iterable-errors.ts"],"names":[],"mappings":"AAAA,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,SAA6B;CAIjD;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,SAAgB;CAIpC;AAED,qBAAa,aAAc,SAAQ,KAAK;;CAKvC"}
@@ -0,0 +1,18 @@
1
+ export class IterableInterrupted extends Error {
2
+ constructor(message = "Iterable job interrupted") {
3
+ super(message);
4
+ this.name = "IterableInterrupted";
5
+ }
6
+ }
7
+ export class JobSkipError extends Error {
8
+ constructor(message = "Job skipped") {
9
+ super(message);
10
+ this.name = "JobSkipError";
11
+ }
12
+ }
13
+ export class IterableAbort extends Error {
14
+ constructor() {
15
+ super("Iterable job aborted");
16
+ this.name = "IterableAbort";
17
+ }
18
+ }
@@ -0,0 +1,44 @@
1
+ import { Job } from "./job.js";
2
+ type IterableEntry<TItem, TCursor> = [TItem, TCursor];
3
+ type IterableSource<TItem, TCursor> = Iterator<IterableEntry<TItem, TCursor>> | Iterable<IterableEntry<TItem, TCursor>>;
4
+ export declare class IterableJob<TArgs extends unknown[] = unknown[], TItem = unknown, TCursor = unknown> extends Job<TArgs> {
5
+ private executions;
6
+ private cursorValue;
7
+ private startTime;
8
+ private runtimeSeconds;
9
+ private argsValue;
10
+ private cancelledValue;
11
+ private currentObjectValue;
12
+ static abort(): never;
13
+ get currentObject(): TItem | null;
14
+ arguments(): Readonly<TArgs> | null;
15
+ cursor(): TCursor | null;
16
+ cancel(): Promise<boolean>;
17
+ cancelled(): boolean;
18
+ onStart(): Promise<void>;
19
+ onResume(): Promise<void>;
20
+ onStop(): Promise<void>;
21
+ onCancel(): Promise<void>;
22
+ onComplete(): Promise<void>;
23
+ aroundIteration(fn: () => Promise<void> | void): Promise<void>;
24
+ buildEnumerator(..._args: [...TArgs, {
25
+ cursor: TCursor | null;
26
+ }]): IterableSource<TItem, TCursor> | null;
27
+ eachIteration(_item: TItem, ..._args: TArgs): Promise<void>;
28
+ protected arrayEnumerator(array: TItem[], cursor: number | null | undefined): IterableSource<TItem, number>;
29
+ protected iterationKey(): string;
30
+ perform(...args: TArgs): Promise<void>;
31
+ private isCancelled;
32
+ private fetchPreviousIterationState;
33
+ private iterateWithEnumerator;
34
+ private verifyIterationTime;
35
+ private reenqueueIterationJob;
36
+ private assertEnumerator;
37
+ private shouldInterrupt;
38
+ private flushState;
39
+ private cleanup;
40
+ private handleCompleted;
41
+ private monoNow;
42
+ }
43
+ export {};
44
+ //# sourceMappingURL=iterable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iterable.d.ts","sourceRoot":"","sources":["../src/iterable.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAI/B,KAAK,aAAa,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAEtD,KAAK,cAAc,CAAC,KAAK,EAAE,OAAO,IAC9B,QAAQ,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,GACvC,QAAQ,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAS5C,qBAAa,WAAW,CACtB,KAAK,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,EACnC,KAAK,GAAG,OAAO,EACf,OAAO,GAAG,OAAO,CACjB,SAAQ,GAAG,CAAC,KAAK,CAAC;IAClB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,kBAAkB,CAAsB;IAEhD,MAAM,CAAC,KAAK,IAAI,KAAK;IAIrB,IAAI,aAAa,IAAI,KAAK,GAAG,IAAI,CAEhC;IAED,SAAS,IAAI,QAAQ,CAAC,KAAK,CAAC,GAAG,IAAI;IAInC,MAAM,IAAI,OAAO,GAAG,IAAI;IAIlB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAqBhC,SAAS,IAAI,OAAO;IAKd,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,eAAe,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE,eAAe,CACb,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE;QAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,GAC/C,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI;IAMxC,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3D,SAAS,CAAC,eAAe,CACvB,KAAK,EAAE,KAAK,EAAE,EACd,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC;IAuBhC,SAAS,CAAC,YAAY,IAAI,MAAM;IAI1B,OAAO,CAAC,GAAG,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;YA6C9B,WAAW;YAUX,2BAA2B;YAc3B,qBAAqB;YA8DrB,mBAAmB;YAiBnB,qBAAqB;IAQnC,OAAO,CAAC,gBAAgB;IA4BxB,OAAO,CAAC,eAAe;YAKT,UAAU;YAqBV,OAAO;IAcrB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,OAAO;CAGhB"}