pg-boss 12.3.0 → 12.4.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.
@@ -1,160 +0,0 @@
1
- import { getSchedules, getSchedulesByQueue, getTime, schedule, trySetCronTime, unschedule } from "./plans.mjs";
2
- import { assertKey, checkSendArgs } from "./attorney.mjs";
3
- import EventEmitter from "node:events";
4
- import { CronExpressionParser } from "cron-parser";
5
-
6
- //#region src/timekeeper.ts
7
- const QUEUES = { SEND_IT: "__pgboss__send-it" };
8
- const EVENTS = {
9
- error: "error",
10
- schedule: "schedule",
11
- warning: "warning"
12
- };
13
- const WARNINGS = { CLOCK_SKEW: { message: "Warning: Clock skew between this instance and the database server. This will not break scheduling, but is emitted any time the skew exceeds 60 seconds." } };
14
- var Timekeeper = class extends EventEmitter {
15
- db;
16
- config;
17
- manager;
18
- stopped = true;
19
- cronMonitorInterval;
20
- skewMonitorInterval;
21
- timekeeping;
22
- clockSkew = 0;
23
- events = EVENTS;
24
- constructor(db, manager, config) {
25
- super();
26
- this.db = db;
27
- this.config = config;
28
- this.manager = manager;
29
- }
30
- async start() {
31
- this.stopped = false;
32
- await this.cacheClockSkew();
33
- await this.manager.createQueue(QUEUES.SEND_IT);
34
- const options = {
35
- pollingIntervalSeconds: this.config.cronWorkerIntervalSeconds,
36
- batchSize: 50
37
- };
38
- await this.manager.work(QUEUES.SEND_IT, options, (jobs) => this.onSendIt(jobs));
39
- setImmediate(() => this.onCron());
40
- this.cronMonitorInterval = setInterval(async () => await this.onCron(), this.config.cronMonitorIntervalSeconds * 1e3);
41
- this.skewMonitorInterval = setInterval(async () => await this.cacheClockSkew(), this.config.clockMonitorIntervalSeconds * 1e3);
42
- }
43
- async stop() {
44
- if (this.stopped) return;
45
- this.stopped = true;
46
- await this.manager.offWork(QUEUES.SEND_IT, { wait: true });
47
- if (this.skewMonitorInterval) {
48
- clearInterval(this.skewMonitorInterval);
49
- this.skewMonitorInterval = null;
50
- }
51
- if (this.cronMonitorInterval) {
52
- clearInterval(this.cronMonitorInterval);
53
- this.cronMonitorInterval = null;
54
- }
55
- }
56
- async cacheClockSkew() {
57
- let skew = 0;
58
- try {
59
- if (this.config.__test__force_clock_monitoring_error) throw new Error(this.config.__test__force_clock_monitoring_error);
60
- const { rows } = await this.db.executeSql(getTime());
61
- const local = Date.now();
62
- skew = parseFloat(rows[0].time) - local;
63
- const skewSeconds = Math.abs(skew) / 1e3;
64
- if (skewSeconds >= 60 || this.config.__test__force_clock_skew_warning) this.emit(this.events.warning, {
65
- message: WARNINGS.CLOCK_SKEW.message,
66
- data: {
67
- seconds: skewSeconds,
68
- direction: skew > 0 ? "slower" : "faster"
69
- }
70
- });
71
- } catch (err) {
72
- this.emit(this.events.error, err);
73
- } finally {
74
- this.clockSkew = skew;
75
- }
76
- }
77
- async onCron() {
78
- try {
79
- if (this.stopped || this.timekeeping) return;
80
- if (this.config.__test__force_cron_monitoring_error) throw new Error(this.config.__test__force_cron_monitoring_error);
81
- this.timekeeping = true;
82
- const sql = trySetCronTime(this.config.schema, this.config.cronMonitorIntervalSeconds);
83
- if (!this.stopped) {
84
- const { rows } = await this.db.executeSql(sql);
85
- if (!this.stopped && rows.length === 1) await this.cron();
86
- }
87
- } catch (err) {
88
- this.emit(this.events.error, err);
89
- } finally {
90
- this.timekeeping = false;
91
- }
92
- }
93
- async cron() {
94
- const scheduled = (await this.getSchedules()).filter((i) => this.shouldSendIt(i.cron, i.timezone)).map(({ name, key, data, options }) => ({
95
- data: {
96
- name,
97
- data,
98
- options
99
- },
100
- singletonKey: `${name}__${key}`,
101
- singletonSeconds: 60
102
- }));
103
- if (scheduled.length > 0 && !this.stopped) await this.manager.insert(QUEUES.SEND_IT, scheduled);
104
- }
105
- shouldSendIt(cron, tz) {
106
- const prevTime = CronExpressionParser.parse(cron, {
107
- tz,
108
- strict: false
109
- }).prev();
110
- return (Date.now() + this.clockSkew - prevTime.getTime()) / 1e3 < 60;
111
- }
112
- async onSendIt(jobs) {
113
- await Promise.allSettled(jobs.map(({ data }) => this.manager.send(data)));
114
- }
115
- async getSchedules(name, key = "") {
116
- let sql = getSchedules(this.config.schema);
117
- let params = [];
118
- if (name) {
119
- sql = getSchedulesByQueue(this.config.schema);
120
- params = [name, key];
121
- }
122
- const { rows } = await this.db.executeSql(sql, params);
123
- return rows;
124
- }
125
- async schedule(name, cron, data, options = {}) {
126
- const { tz = "UTC", key = "", ...rest } = options;
127
- CronExpressionParser.parse(cron, {
128
- tz,
129
- strict: false
130
- });
131
- checkSendArgs([
132
- name,
133
- data,
134
- { ...rest }
135
- ]);
136
- assertKey(key);
137
- try {
138
- const sql = schedule(this.config.schema);
139
- await this.db.executeSql(sql, [
140
- name,
141
- key,
142
- cron,
143
- tz,
144
- data,
145
- options
146
- ]);
147
- } catch (err) {
148
- if (err.message.includes("foreign key")) err.message = `Queue ${name} not found`;
149
- throw err;
150
- }
151
- }
152
- async unschedule(name, key = "") {
153
- const sql = unschedule(this.config.schema);
154
- await this.db.executeSql(sql, [name, key]);
155
- }
156
- };
157
- var timekeeper_default = Timekeeper;
158
-
159
- //#endregion
160
- export { QUEUES, timekeeper_default as default };
@@ -1,37 +0,0 @@
1
- import { setTimeout } from "node:timers/promises";
2
-
3
- //#region src/tools.ts
4
- /**
5
- * When sql contains multiple queries, result is an array of objects with rows property
6
- * This function unwraps the result into a single object with rows property
7
- */
8
- function unwrapSQLResult(result) {
9
- if (Array.isArray(result)) return { rows: result.flatMap((i) => i.rows) };
10
- return result;
11
- }
12
- function delay(ms, error) {
13
- const ac = new AbortController();
14
- const promise = new Promise((resolve, reject) => {
15
- setTimeout(ms, null, { signal: ac.signal }).then(() => {
16
- if (error) reject(new Error(error));
17
- else resolve();
18
- }).catch(resolve);
19
- });
20
- promise.abort = () => {
21
- if (!ac.signal.aborted) ac.abort();
22
- };
23
- return promise;
24
- }
25
- async function resolveWithinSeconds(promise, seconds, message) {
26
- const reject = delay(Math.max(1, seconds) * 1e3, message);
27
- let result;
28
- try {
29
- result = await Promise.race([promise, reject]);
30
- } finally {
31
- reject.abort();
32
- }
33
- return result;
34
- }
35
-
36
- //#endregion
37
- export { delay, resolveWithinSeconds, unwrapSQLResult };
@@ -1,207 +0,0 @@
1
- //#region src/types.d.ts
2
- type JobStates = {
3
- created: 'created';
4
- retry: 'retry';
5
- active: 'active';
6
- completed: 'completed';
7
- cancelled: 'cancelled';
8
- failed: 'failed';
9
- };
10
- type Events = {
11
- error: 'error';
12
- warning: 'warning';
13
- wip: 'wip';
14
- stopped: 'stopped';
15
- };
16
- interface IDatabase {
17
- executeSql(text: string, values?: unknown[]): Promise<{
18
- rows: any[];
19
- }>;
20
- }
21
- interface DatabaseOptions {
22
- application_name?: string;
23
- database?: string;
24
- user?: string;
25
- password?: string | (() => string) | (() => Promise<string>);
26
- host?: string;
27
- port?: number;
28
- schema?: string;
29
- ssl?: any;
30
- connectionString?: string;
31
- max?: number;
32
- db?: IDatabase;
33
- connectionTimeoutMillis?: number;
34
- }
35
- interface SchedulingOptions {
36
- schedule?: boolean;
37
- clockMonitorIntervalSeconds?: number;
38
- cronWorkerIntervalSeconds?: number;
39
- cronMonitorIntervalSeconds?: number;
40
- }
41
- interface MaintenanceOptions {
42
- supervise?: boolean;
43
- migrate?: boolean;
44
- createSchema?: boolean;
45
- warningSlowQuerySeconds?: number;
46
- warningQueueSize?: number;
47
- superviseIntervalSeconds?: number;
48
- maintenanceIntervalSeconds?: number;
49
- queueCacheIntervalSeconds?: number;
50
- monitorIntervalSeconds?: number;
51
- }
52
- interface ConstructorOptions extends DatabaseOptions, SchedulingOptions, MaintenanceOptions {}
53
- interface QueueOptions {
54
- expireInSeconds?: number;
55
- retentionSeconds?: number;
56
- deleteAfterSeconds?: number;
57
- retryLimit?: number;
58
- retryDelay?: number;
59
- retryBackoff?: boolean;
60
- retryDelayMax?: number;
61
- }
62
- interface JobOptions {
63
- id?: string;
64
- priority?: number;
65
- startAfter?: number | string | Date;
66
- singletonKey?: string;
67
- singletonSeconds?: number;
68
- singletonNextSlot?: boolean;
69
- keepUntil?: number | string | Date;
70
- }
71
- interface ConnectionOptions {
72
- db?: IDatabase;
73
- }
74
- type InsertOptions = ConnectionOptions;
75
- type SendOptions = JobOptions & QueueOptions & ConnectionOptions;
76
- type QueuePolicy = 'standard' | 'short' | 'singleton' | 'stately' | 'exclusive';
77
- interface Queue extends QueueOptions {
78
- name: string;
79
- policy?: QueuePolicy;
80
- partition?: boolean;
81
- deadLetter?: string;
82
- warningQueueSize?: number;
83
- }
84
- interface QueueResult extends Queue {
85
- deferredCount: number;
86
- queuedCount: number;
87
- activeCount: number;
88
- totalCount: number;
89
- table: string;
90
- createdOn: Date;
91
- updatedOn: Date;
92
- singletonsActive: string[] | null;
93
- }
94
- type ScheduleOptions = SendOptions & {
95
- tz?: string;
96
- key?: string;
97
- };
98
- interface JobPollingOptions {
99
- pollingIntervalSeconds?: number;
100
- }
101
- interface JobFetchOptions {
102
- includeMetadata?: boolean;
103
- priority?: boolean;
104
- batchSize?: number;
105
- ignoreStartAfter?: boolean;
106
- }
107
- type WorkOptions = JobFetchOptions & JobPollingOptions;
108
- type FetchOptions = JobFetchOptions & ConnectionOptions;
109
- interface WorkHandler<ReqData> {
110
- (job: Job<ReqData>[]): Promise<any>;
111
- }
112
- interface WorkWithMetadataHandler<ReqData> {
113
- (job: JobWithMetadata<ReqData>[]): Promise<any>;
114
- }
115
- interface Request {
116
- name: string;
117
- data?: object;
118
- options?: SendOptions;
119
- }
120
- interface Schedule {
121
- name: string;
122
- key: string;
123
- cron: string;
124
- timezone: string;
125
- data?: object;
126
- options?: SendOptions;
127
- }
128
- interface Job<T = object> {
129
- id: string;
130
- name: string;
131
- data: T;
132
- expireInSeconds: number;
133
- }
134
- interface JobWithMetadata<T = object> extends Job<T> {
135
- priority: number;
136
- state: 'created' | 'retry' | 'active' | 'completed' | 'cancelled' | 'failed';
137
- retryLimit: number;
138
- retryCount: number;
139
- retryDelay: number;
140
- retryBackoff: boolean;
141
- retryDelayMax?: number;
142
- startAfter: Date;
143
- startedOn: Date;
144
- singletonKey: string | null;
145
- singletonOn: Date | null;
146
- expireInSeconds: number;
147
- deleteAfterSeconds: number;
148
- createdOn: Date;
149
- completedOn: Date | null;
150
- keepUntil: Date;
151
- policy: QueuePolicy;
152
- deadLetter: string;
153
- output: object;
154
- }
155
- interface JobInsert<T = object> {
156
- id?: string;
157
- data?: T;
158
- priority?: number;
159
- retryLimit?: number;
160
- retryDelay?: number;
161
- retryBackoff?: boolean;
162
- retryDelayMax?: number;
163
- startAfter?: number | string | Date;
164
- singletonKey?: string;
165
- singletonSeconds?: number;
166
- expireInSeconds?: number;
167
- deleteAfterSeconds?: number;
168
- retentionSeconds?: number;
169
- }
170
- type WorkerState = 'created' | 'active' | 'stopping' | 'stopped';
171
- interface WipData {
172
- id: string;
173
- name: string;
174
- options: WorkOptions;
175
- state: WorkerState;
176
- count: number;
177
- createdOn: number;
178
- lastFetchedOn: number | null;
179
- lastJobStartedOn: number | null;
180
- lastJobEndedOn: number | null;
181
- lastJobDuration: number | null;
182
- lastError: object | null;
183
- lastErrorOn: number | null;
184
- }
185
- interface StopOptions {
186
- close?: boolean;
187
- graceful?: boolean;
188
- timeout?: number;
189
- }
190
- interface OffWorkOptions {
191
- id?: string;
192
- wait?: boolean;
193
- }
194
- type UpdateQueueOptions = Omit<Queue, 'name' | 'partition' | 'policy'>;
195
- interface Warning {
196
- message: string;
197
- data: object;
198
- }
199
- interface CommandResponse {}
200
- type PgBossEventMap = {
201
- error: [error: Error];
202
- warning: [warning: Warning];
203
- wip: [data: WipData[]];
204
- stopped: [];
205
- };
206
- //#endregion
207
- export { CommandResponse, ConnectionOptions, ConstructorOptions, Events, FetchOptions, IDatabase, InsertOptions, Job, JobFetchOptions, JobInsert, JobPollingOptions, JobStates, JobWithMetadata, MaintenanceOptions, OffWorkOptions, PgBossEventMap, Queue, QueuePolicy, QueueResult, Request, Schedule, ScheduleOptions, SchedulingOptions, SendOptions, StopOptions, UpdateQueueOptions, WipData, WorkHandler, WorkOptions, WorkWithMetadataHandler };
@@ -1,102 +0,0 @@
1
- import { delay } from "./tools.mjs";
2
-
3
- //#region src/worker.ts
4
- const WORKER_STATES = {
5
- created: "created",
6
- active: "active",
7
- stopping: "stopping",
8
- stopped: "stopped"
9
- };
10
- var Worker = class {
11
- id;
12
- name;
13
- options;
14
- fetch;
15
- onFetch;
16
- onError;
17
- interval;
18
- jobs = [];
19
- createdOn = Date.now();
20
- state = WORKER_STATES.created;
21
- lastFetchedOn = null;
22
- lastJobStartedOn = null;
23
- lastJobEndedOn = null;
24
- lastJobDuration = null;
25
- lastError = null;
26
- lastErrorOn = null;
27
- stopping = false;
28
- stopped = false;
29
- loopDelayPromise = null;
30
- beenNotified = false;
31
- constructor({ id, name, options, interval, fetch, onFetch, onError }) {
32
- this.id = id;
33
- this.name = name;
34
- this.options = options;
35
- this.fetch = fetch;
36
- this.onFetch = onFetch;
37
- this.onError = onError;
38
- this.interval = interval;
39
- }
40
- notify() {
41
- this.beenNotified = true;
42
- if (this.loopDelayPromise) this.loopDelayPromise.abort();
43
- }
44
- async start() {
45
- this.state = WORKER_STATES.active;
46
- while (!this.stopping) {
47
- const started = Date.now();
48
- try {
49
- this.beenNotified = false;
50
- const jobs = await this.fetch();
51
- this.lastFetchedOn = Date.now();
52
- if (jobs) {
53
- this.jobs = jobs;
54
- this.lastJobStartedOn = this.lastFetchedOn;
55
- await this.onFetch(jobs);
56
- this.lastJobEndedOn = Date.now();
57
- this.jobs = [];
58
- }
59
- } catch (err) {
60
- this.lastErrorOn = Date.now();
61
- this.lastError = err;
62
- err.message = `${err.message} (Queue: ${this.name}, Worker: ${this.id})`;
63
- this.onError(err);
64
- }
65
- const duration = Date.now() - started;
66
- this.lastJobDuration = duration;
67
- if (!this.stopping && !this.beenNotified && this.interval - duration > 100) {
68
- this.loopDelayPromise = delay(this.interval - duration);
69
- await this.loopDelayPromise;
70
- this.loopDelayPromise = null;
71
- }
72
- }
73
- this.stopping = false;
74
- this.stopped = true;
75
- this.state = WORKER_STATES.stopped;
76
- }
77
- stop() {
78
- this.stopping = true;
79
- this.state = WORKER_STATES.stopping;
80
- if (this.loopDelayPromise) this.loopDelayPromise.abort();
81
- }
82
- toWipData() {
83
- return {
84
- id: this.id,
85
- name: this.name,
86
- options: this.options,
87
- state: this.state,
88
- count: this.jobs.length,
89
- createdOn: this.createdOn,
90
- lastFetchedOn: this.lastFetchedOn,
91
- lastJobStartedOn: this.lastJobStartedOn,
92
- lastJobEndedOn: this.lastJobEndedOn,
93
- lastError: this.lastError,
94
- lastErrorOn: this.lastErrorOn,
95
- lastJobDuration: this.lastJobDuration
96
- };
97
- }
98
- };
99
- var worker_default = Worker;
100
-
101
- //#endregion
102
- export { worker_default as default };