pg-boss 11.1.2 → 12.0.0-beta2

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/src/timekeeper.js DELETED
@@ -1,203 +0,0 @@
1
- const EventEmitter = require('node:events')
2
- const plans = require('./plans')
3
- const { CronExpressionParser } = require('cron-parser')
4
- const Attorney = require('./attorney')
5
-
6
- const QUEUES = {
7
- SEND_IT: '__pgboss__send-it'
8
- }
9
-
10
- const EVENTS = {
11
- error: 'error',
12
- schedule: 'schedule',
13
- warning: 'warning'
14
- }
15
-
16
- const WARNINGS = {
17
- CLOCK_SKEW: {
18
- 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.'
19
- }
20
- }
21
-
22
- class Timekeeper extends EventEmitter {
23
- constructor (db, config) {
24
- super()
25
-
26
- this.db = db
27
- this.config = config
28
- this.manager = config.manager
29
- this.clockSkew = 0
30
- this.events = EVENTS
31
-
32
- this.functions = [
33
- this.schedule,
34
- this.unschedule,
35
- this.getSchedules
36
- ]
37
-
38
- this.stopped = true
39
- }
40
-
41
- async start () {
42
- this.stopped = false
43
-
44
- await this.cacheClockSkew()
45
- await this.manager.createQueue(QUEUES.SEND_IT)
46
-
47
- const options = {
48
- pollingIntervalSeconds: this.config.cronWorkerIntervalSeconds,
49
- batchSize: 50
50
- }
51
-
52
- await this.manager.work(QUEUES.SEND_IT, options, (jobs) => this.onSendIt(jobs))
53
-
54
- setImmediate(() => this.onCron())
55
-
56
- this.cronMonitorInterval = setInterval(async () => await this.onCron(), this.config.cronMonitorIntervalSeconds * 1000)
57
- this.skewMonitorInterval = setInterval(async () => await this.cacheClockSkew(), this.config.clockMonitorIntervalSeconds * 1000)
58
- }
59
-
60
- async stop () {
61
- if (this.stopped) {
62
- return
63
- }
64
-
65
- this.stopped = true
66
-
67
- await this.manager.offWork(QUEUES.SEND_IT)
68
-
69
- if (this.skewMonitorInterval) {
70
- clearInterval(this.skewMonitorInterval)
71
- this.skewMonitorInterval = null
72
- }
73
-
74
- if (this.cronMonitorInterval) {
75
- clearInterval(this.cronMonitorInterval)
76
- this.cronMonitorInterval = null
77
- }
78
- }
79
-
80
- async cacheClockSkew () {
81
- let skew = 0
82
-
83
- try {
84
- if (this.config.__test__force_clock_monitoring_error) {
85
- throw new Error(this.config.__test__force_clock_monitoring_error)
86
- }
87
-
88
- const { rows } = await this.db.executeSql(plans.getTime())
89
-
90
- const local = Date.now()
91
-
92
- const dbTime = parseFloat(rows[0].time)
93
-
94
- skew = dbTime - local
95
-
96
- const skewSeconds = Math.abs(skew) / 1000
97
-
98
- if (skewSeconds >= 60 || this.config.__test__force_clock_skew_warning) {
99
- this.emit(this.events.warning, { message: WARNINGS.CLOCK_SKEW.message, data: { seconds: skewSeconds, direction: skew > 0 ? 'slower' : 'faster' } })
100
- }
101
- } catch (err) {
102
- this.emit(this.events.error, err)
103
- } finally {
104
- this.clockSkew = skew
105
- }
106
- }
107
-
108
- async onCron () {
109
- try {
110
- if (this.stopped || this.timekeeping) return
111
-
112
- if (this.config.__test__force_cron_monitoring_error) {
113
- throw new Error(this.config.__test__force_cron_monitoring_error)
114
- }
115
-
116
- this.timekeeping = true
117
-
118
- const sql = plans.trySetCronTime(this.config.schema, this.config.cronMonitorIntervalSeconds)
119
-
120
- if (!this.stopped) {
121
- const { rows } = await this.db.executeSql(sql)
122
-
123
- if (!this.stopped && rows.length === 1) {
124
- await this.cron()
125
- }
126
- }
127
- } catch (err) {
128
- this.emit(this.events.error, err)
129
- } finally {
130
- this.timekeeping = false
131
- }
132
- }
133
-
134
- async cron () {
135
- const schedules = await this.getSchedules()
136
-
137
- const scheduled = schedules
138
- .filter(i => this.shouldSendIt(i.cron, i.timezone))
139
- .map(({ name, key, data, options }) => ({ data: { name, data, options }, singletonKey: `${name}__${key}`, singletonSeconds: 60 }))
140
-
141
- if (scheduled.length > 0 && !this.stopped) {
142
- await this.manager.insert(QUEUES.SEND_IT, scheduled)
143
- }
144
- }
145
-
146
- shouldSendIt (cron, tz) {
147
- const interval = CronExpressionParser.parse(cron, { tz, strict: false })
148
-
149
- const prevTime = interval.prev()
150
-
151
- const databaseTime = Date.now() + this.clockSkew
152
-
153
- const prevDiff = (databaseTime - prevTime.getTime()) / 1000
154
-
155
- return prevDiff < 60
156
- }
157
-
158
- async onSendIt (jobs) {
159
- await Promise.allSettled(jobs.map(({ data }) => this.manager.send(data)))
160
- }
161
-
162
- async getSchedules (name, key = '') {
163
- let sql = plans.getSchedules(this.config.schema)
164
- let params = []
165
-
166
- if (name) {
167
- sql = plans.getSchedulesByQueue(this.config.schema)
168
- params = [name, key]
169
- }
170
-
171
- const { rows } = await this.db.executeSql(sql, params)
172
-
173
- return rows
174
- }
175
-
176
- async schedule (name, cron, data, options = {}) {
177
- const { tz = 'UTC', key = '', ...rest } = options
178
-
179
- CronExpressionParser.parse(cron, { tz, strict: false })
180
-
181
- Attorney.checkSendArgs([name, data, { ...rest }])
182
- Attorney.assertKey(key)
183
-
184
- try {
185
- const sql = plans.schedule(this.config.schema)
186
- await this.db.executeSql(sql, [name, key, cron, tz, data, options])
187
- } catch (err) {
188
- if (err.message.includes('foreign key')) {
189
- err.message = `Queue ${name} not found`
190
- }
191
-
192
- throw err
193
- }
194
- }
195
-
196
- async unschedule (name, key = '') {
197
- const sql = plans.unschedule(this.config.schema)
198
- await this.db.executeSql(sql, [name, key])
199
- }
200
- }
201
-
202
- module.exports = Timekeeper
203
- module.exports.QUEUES = QUEUES
package/src/tools.js DELETED
@@ -1,60 +0,0 @@
1
- const { setTimeout } = require('node:timers/promises')
2
-
3
- module.exports = {
4
- delay,
5
- resolveWithinSeconds,
6
- unwrapSQLResult
7
- }
8
-
9
- /**
10
- * When sql contains multiple queries, result is an array of objects with rows property
11
- * This function unwraps the result into a single object with rows property
12
- * @param {{rows: Array<Object>} | Array<{rows: Array<Object>}>} result
13
- * @returns {{rows: Array<Object>}}
14
- */
15
- function unwrapSQLResult (result) {
16
- if (result instanceof Array) {
17
- return { rows: result.flatMap(i => i.rows) }
18
- }
19
-
20
- return result
21
- }
22
-
23
- function delay (ms, error) {
24
- const ac = new AbortController()
25
-
26
- const promise = new Promise((resolve, reject) => {
27
- setTimeout(ms, null, { signal: ac.signal })
28
- .then(() => {
29
- if (error) {
30
- reject(new Error(error))
31
- } else {
32
- resolve()
33
- }
34
- })
35
- .catch(resolve)
36
- })
37
-
38
- promise.abort = () => {
39
- if (!ac.signal.aborted) {
40
- ac.abort()
41
- }
42
- }
43
-
44
- return promise
45
- }
46
-
47
- async function resolveWithinSeconds (promise, seconds, message) {
48
- const timeout = Math.max(1, seconds) * 1000
49
- const reject = delay(timeout, message)
50
-
51
- let result
52
-
53
- try {
54
- result = await Promise.race([promise, reject])
55
- } finally {
56
- reject.abort()
57
- }
58
-
59
- return result
60
- }
package/src/worker.js DELETED
@@ -1,99 +0,0 @@
1
- const { delay } = require('./tools')
2
-
3
- const WORKER_STATES = {
4
- created: 'created',
5
- active: 'active',
6
- stopping: 'stopping',
7
- stopped: 'stopped'
8
- }
9
-
10
- class Worker {
11
- constructor ({ id, name, options, interval, fetch, onFetch, onError }) {
12
- this.id = id
13
- this.name = name
14
- this.options = options
15
- this.fetch = fetch
16
- this.onFetch = onFetch
17
- this.onError = onError
18
- this.interval = interval
19
- this.jobs = []
20
- this.createdOn = Date.now()
21
- this.lastFetchedOn = null
22
- this.lastJobStartedOn = null
23
- this.lastJobEndedOn = null
24
- this.lastError = null
25
- this.lastErrorOn = null
26
- this.state = WORKER_STATES.created
27
- this.stopping = false
28
- this.stopped = false
29
- this.loopDelayPromise = null
30
- this.beenNotified = false
31
- }
32
-
33
- notify () {
34
- this.beenNotified = true
35
-
36
- if (this.loopDelayPromise) {
37
- this.loopDelayPromise.abort()
38
- }
39
- }
40
-
41
- async start () {
42
- this.state = WORKER_STATES.active
43
-
44
- while (!this.stopping) {
45
- const started = Date.now()
46
-
47
- try {
48
- this.beenNotified = false
49
- const jobs = await this.fetch()
50
-
51
- this.lastFetchedOn = Date.now()
52
-
53
- if (jobs) {
54
- this.jobs = jobs
55
-
56
- this.lastJobStartedOn = this.lastFetchedOn
57
-
58
- await this.onFetch(jobs)
59
-
60
- this.lastJobEndedOn = Date.now()
61
-
62
- this.jobs = []
63
- }
64
- } catch (err) {
65
- this.lastErrorOn = Date.now()
66
- this.lastError = err
67
-
68
- err.message = `${err.message} (Queue: ${this.name}, Worker: ${this.id})`
69
-
70
- this.onError(err)
71
- }
72
-
73
- const duration = Date.now() - started
74
-
75
- this.lastJobDuration = duration
76
-
77
- if (!this.stopping && !this.beenNotified && (this.interval - duration) > 100) {
78
- this.loopDelayPromise = delay(this.interval - duration)
79
- await this.loopDelayPromise
80
- this.loopDelayPromise = null
81
- }
82
- }
83
-
84
- this.stopping = false
85
- this.stopped = true
86
- this.state = WORKER_STATES.stopped
87
- }
88
-
89
- stop () {
90
- this.stopping = true
91
- this.state = WORKER_STATES.stopping
92
-
93
- if (this.loopDelayPromise) {
94
- this.loopDelayPromise.abort()
95
- }
96
- }
97
- }
98
-
99
- module.exports = Worker
package/types.d.ts DELETED
@@ -1,323 +0,0 @@
1
- import { EventEmitter } from 'events'
2
-
3
- declare namespace PgBoss {
4
-
5
- type JobStates = {
6
- created: 'created',
7
- retry: 'retry',
8
- active: 'active',
9
- completed: 'completed',
10
- cancelled: 'cancelled',
11
- failed: 'failed'
12
- }
13
-
14
- type QueuePolicies = {
15
- standard: 'standard'
16
- short: 'short',
17
- singleton: 'singleton',
18
- stately: 'stately'
19
- }
20
-
21
- interface Db {
22
- executeSql(text: string, values: any[]): Promise<{ rows: any[] }>;
23
- }
24
-
25
- interface DatabaseOptions {
26
- application_name?: string;
27
- database?: string;
28
- user?: string;
29
- password?: string | (() => string) | (() => Promise<string>);
30
- host?: string;
31
- port?: number;
32
- schema?: string;
33
- ssl?: any;
34
- connectionString?: string;
35
- max?: number;
36
- db?: Db;
37
- }
38
-
39
- interface SchedulingOptions {
40
- schedule?: boolean;
41
- clockMonitorIntervalSeconds?: number;
42
- cronWorkerIntervalSeconds?: number;
43
- cronMonitorIntervalSeconds?: number;
44
- }
45
-
46
- interface MaintenanceOptions {
47
- supervise?: boolean;
48
- migrate?: boolean;
49
- warningSlowQuerySeconds?: number;
50
- warningQueueSize?: number;
51
- superviseIntervalSeconds?: number;
52
- maintenanceIntervalSeconds?: number;
53
- queueCacheIntervalSeconds?: number;
54
- monitorIntervalSeconds?: number;
55
- }
56
-
57
- type ConstructorOptions = DatabaseOptions & SchedulingOptions & MaintenanceOptions
58
-
59
- interface QueueOptions {
60
- expireInSeconds?: number;
61
- retentionSeconds?: number;
62
- deleteAfterSeconds?: number;
63
- retryLimit?: number;
64
- retryDelay?: number;
65
- retryBackoff?: boolean;
66
- retryDelayMax?: number;
67
- }
68
-
69
- interface JobOptions {
70
- id?: string;
71
- priority?: number;
72
- startAfter?: number | string | Date;
73
- singletonKey?: string;
74
- singletonSeconds?: number;
75
- singletonNextSlot?: boolean;
76
- }
77
-
78
- interface ConnectionOptions {
79
- db?: Db;
80
- }
81
-
82
- type InsertOptions = ConnectionOptions
83
-
84
- type SendOptions = JobOptions & QueueOptions & ConnectionOptions
85
-
86
- type QueuePolicy = 'standard' | 'short' | 'singleton' | 'stately' | 'exclusive'
87
-
88
- type Queue = {
89
- name: string;
90
- policy?: QueuePolicy;
91
- partition?: boolean;
92
- deadLetter?: string;
93
- warningQueueSize?: number;
94
- } & QueueOptions
95
-
96
- type QueueResult = Queue & {
97
- deferredCount: number;
98
- queuedCount: number;
99
- activeCount: number;
100
- totalCount: number
101
- table: number;
102
- createdOn: Date;
103
- updatedOn: Date;
104
- }
105
-
106
- type ScheduleOptions = SendOptions & { tz?: string, key?: string }
107
-
108
- interface JobPollingOptions {
109
- pollingIntervalSeconds?: number;
110
- }
111
-
112
- interface JobFetchOptions {
113
- includeMetadata?: boolean;
114
- priority?: boolean;
115
- batchSize?: number;
116
- ignoreStartAfter?: boolean;
117
- }
118
-
119
- type WorkOptions = JobFetchOptions & JobPollingOptions
120
- type FetchOptions = JobFetchOptions & ConnectionOptions
121
-
122
- interface WorkHandler<ReqData> {
123
- (job: PgBoss.Job<ReqData>[]): Promise<any>;
124
- }
125
-
126
- interface WorkWithMetadataHandler<ReqData> {
127
- (job: PgBoss.JobWithMetadata<ReqData>[]): Promise<any>;
128
- }
129
-
130
- interface Request {
131
- name: string;
132
- data?: object;
133
- options?: SendOptions;
134
- }
135
-
136
- interface Schedule {
137
- name: string;
138
- key: string;
139
- cron: string;
140
- timezone: string;
141
- data?: object;
142
- options?: SendOptions;
143
- }
144
-
145
- interface Job<T = object> {
146
- id: string;
147
- name: string;
148
- data: T;
149
- expireInSeconds: number;
150
- }
151
-
152
- interface JobWithMetadata<T = object> extends Job<T> {
153
- priority: number;
154
- state: 'created' | 'retry' | 'active' | 'completed' | 'cancelled' | 'failed';
155
- retryLimit: number;
156
- retryCount: number;
157
- retryDelay: number;
158
- retryBackoff: boolean;
159
- retryDelayMax?: number;
160
- startAfter: Date;
161
- startedOn: Date;
162
- singletonKey: string | null;
163
- singletonOn: Date | null;
164
- expireInSeconds: number;
165
- deleteAfterSeconds: number;
166
- createdOn: Date;
167
- completedOn: Date | null;
168
- keepUntil: Date;
169
- policy: QueuePolicy;
170
- deadLetter: string;
171
- output: object;
172
- }
173
-
174
- interface JobInsert<T = object> {
175
- id?: string;
176
- name: string;
177
- data?: T;
178
- priority?: number;
179
- retryLimit?: number;
180
- retryDelay?: number;
181
- retryBackoff?: boolean;
182
- retryDelayMax?: number;
183
- startAfter?: Date | string;
184
- singletonKey?: string;
185
- singletonSeconds?: number;
186
- expireInSeconds?: number;
187
- deleteAfterSeconds?: number;
188
- retentionSeconds?: number;
189
- }
190
-
191
- interface Worker {
192
- id: string;
193
- name: string;
194
- options: WorkOptions;
195
- state: 'created' | 'active' | 'stopping' | 'stopped';
196
- count: number;
197
- createdOn: Date;
198
- lastFetchedOn: Date;
199
- lastJobStartedOn: Date;
200
- lastJobEndedOn: Date;
201
- lastJobDuration: number;
202
- lastError: object;
203
- lastErrorOn: Date;
204
- }
205
-
206
- interface StopOptions {
207
- close?: boolean;
208
- graceful?: boolean;
209
- timeout?: number;
210
- wait?: boolean;
211
- }
212
-
213
- interface OffWorkOptions {
214
- id: string
215
- }
216
-
217
- }
218
-
219
- declare class PgBoss extends EventEmitter {
220
- constructor (connectionString: string)
221
- constructor (options: PgBoss.ConstructorOptions)
222
-
223
- static getConstructionPlans (schema?: string): string
224
- static getMigrationPlans (schema?: string, version?: string): string
225
- static getRollbackPlans (schema?: string, version?: string): string
226
-
227
- static states: PgBoss.JobStates
228
- static policies: PgBoss.QueuePolicies
229
-
230
- on (event: 'error', handler: (error: Error) => void): this
231
- off (event: 'error', handler: (error: Error) => void): this
232
-
233
- on (event: 'warning', handler: (warning: { message: string, data: object }) => void): this
234
- off (event: 'warning', handler: (warning: { message: string, data: object }) => void): this
235
-
236
- on (event: 'wip', handler: (data: PgBoss.Worker[]) => void): this
237
- off (event: 'wip', handler: (data: PgBoss.Worker[]) => void): this
238
-
239
- on (event: 'stopped', handler: () => void): this
240
- off (event: 'stopped', handler: () => void): this
241
-
242
- start (): Promise<PgBoss>
243
- stop (options?: PgBoss.StopOptions): Promise<void>
244
-
245
- send (request: PgBoss.Request): Promise<string | null>
246
- send (name: string, data: object): Promise<string | null>
247
- send (name: string, data: object, options: PgBoss.SendOptions): Promise<string | null>
248
-
249
- sendAfter (name: string, data: object, options: PgBoss.SendOptions, date: Date): Promise<string | null>
250
- sendAfter (name: string, data: object, options: PgBoss.SendOptions, dateString: string): Promise<string | null>
251
- sendAfter (name: string, data: object, options: PgBoss.SendOptions, seconds: number): Promise<string | null>
252
-
253
- sendThrottled (name: string, data: object, options: PgBoss.SendOptions, seconds: number, key?: string): Promise<string | null>
254
- sendDebounced (name: string, data: object, options: PgBoss.SendOptions, seconds: number, key?: string): Promise<string | null>
255
-
256
- insert (name: string, jobs: PgBoss.JobInsert[]): Promise<void>
257
- insert (name: string, jobs: PgBoss.JobInsert[], options: PgBoss.InsertOptions): Promise<void>
258
-
259
- fetch<T>(name: string): Promise<PgBoss.Job<T>[]>
260
- fetch<T>(name: string, options: PgBoss.FetchOptions & { includeMetadata: true }): Promise<PgBoss.JobWithMetadata<T>[]>
261
- fetch<T>(name: string, options: PgBoss.FetchOptions): Promise<PgBoss.Job<T>[]>
262
-
263
- work<ReqData>(name: string, handler: PgBoss.WorkHandler<ReqData>): Promise<string>
264
- work<ReqData>(name: string, options: PgBoss.WorkOptions & { includeMetadata: true }, handler: PgBoss.WorkWithMetadataHandler<ReqData>): Promise<string>
265
- work<ReqData>(name: string, options: PgBoss.WorkOptions, handler: PgBoss.WorkHandler<ReqData>): Promise<string>
266
-
267
- offWork (name: string): Promise<void>
268
- offWork (options: PgBoss.OffWorkOptions): Promise<void>
269
-
270
- notifyWorker (workerId: string): void
271
-
272
- subscribe (event: string, name: string): Promise<void>
273
- unsubscribe (event: string, name: string): Promise<void>
274
- publish (event: string): Promise<void>
275
- publish (event: string, data: object): Promise<void>
276
- publish (event: string, data: object, options: PgBoss.SendOptions): Promise<void>
277
-
278
- cancel (name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>
279
- cancel (name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>
280
-
281
- resume (name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>
282
- resume (name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>
283
-
284
- retry (name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>
285
- retry (name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>
286
-
287
- deleteJob (name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>
288
- deleteJob (name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>
289
- deleteQueuedJobs (name: string): Promise<void>
290
- deleteStoredJobs (name: string): Promise<void>
291
- deleteAllJobs (name: string): Promise<void>
292
-
293
- complete (name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>
294
- complete (name: string, id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>
295
- complete (name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>
296
-
297
- fail (name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>
298
- fail (name: string, id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>
299
- fail (name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>
300
-
301
- getJobById<T>(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<PgBoss.JobWithMetadata<T> | null>
302
-
303
- createQueue (name: string, options?: Omit<PgBoss.Queue, 'name'>): Promise<void>
304
- createQueue (options: PgBoss.Queue): Promise<void>
305
- updateQueue (name: string, options?: Omit<PgBoss.Queue, 'name', 'partition', 'policy'>): Promise<void>
306
- updateQueue (options: Omit<PgBoss.Queue, 'partition', 'policy'>): Promise<void>
307
- deleteQueue (name: string): Promise<void>
308
- getQueues (): Promise<PgBoss.QueueResult[]>
309
- getQueue (name: string): Promise<PgBoss.QueueResult | null>
310
- getQueueStats (name: string): Promise<PgBoss.QueueResult>
311
-
312
- supervise (name?: string): Promise<void>
313
- isInstalled (): Promise<boolean>
314
- schemaVersion (): Promise<number>
315
-
316
- schedule (name: string, cron: string, data?: object, options?: PgBoss.ScheduleOptions): Promise<void>
317
- unschedule (name: string, key?: string): Promise<void>
318
- getSchedules (name?: string, key?: string): Promise<PgBoss.Schedule[]>
319
-
320
- getDb (): PgBoss.Db
321
- }
322
-
323
- export = PgBoss
package/version.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "schema": 26
3
- }