pg-boss 10.0.0-beta2 → 10.0.0-beta3
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/package.json +1 -1
- package/src/attorney.js +10 -5
- package/src/contractor.js +6 -0
- package/src/db.js +4 -0
- package/src/index.js +81 -60
- package/src/manager.js +5 -3
- package/src/plans.js +51 -10
- package/src/timekeeper.js +1 -1
- package/types.d.ts +1 -1
package/package.json
CHANGED
package/src/attorney.js
CHANGED
|
@@ -8,7 +8,8 @@ module.exports = {
|
|
|
8
8
|
checkWorkArgs,
|
|
9
9
|
checkFetchArgs,
|
|
10
10
|
warnClockSkew,
|
|
11
|
-
assertPostgresObjectName
|
|
11
|
+
assertPostgresObjectName,
|
|
12
|
+
assertQueueName
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const MAX_INTERVAL_HOURS = 24
|
|
@@ -29,8 +30,6 @@ const WARNINGS = {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function checkQueueArgs (name, options = {}) {
|
|
32
|
-
assertPostgresObjectName(name)
|
|
33
|
-
|
|
34
33
|
assert(!('deadLetter' in options) || (typeof options.deadLetter === 'string'), 'deadLetter must be a string')
|
|
35
34
|
|
|
36
35
|
applyRetryConfig(options)
|
|
@@ -178,8 +177,14 @@ function applySchemaConfig (config) {
|
|
|
178
177
|
function assertPostgresObjectName (name) {
|
|
179
178
|
assert(typeof name === 'string', 'Name must be a string')
|
|
180
179
|
assert(name.length <= 50, 'Name cannot exceed 50 characters')
|
|
181
|
-
assert(!/\W/.test(name), 'Name can only contain alphanumeric characters
|
|
182
|
-
assert(
|
|
180
|
+
assert(!/\W/.test(name), 'Name can only contain alphanumeric characters or underscores')
|
|
181
|
+
assert(!/^\d/.test(name), 'Name cannot start with a number')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function assertQueueName (name) {
|
|
185
|
+
assert(typeof name === 'string', 'Name must be a string')
|
|
186
|
+
assert(name.length <= 50, 'Name cannot exceed 50 characters')
|
|
187
|
+
assert(/[\w-]/.test(name), 'Name can only contain alphanumeric characters, underscores, or hyphens')
|
|
183
188
|
}
|
|
184
189
|
|
|
185
190
|
function applyArchiveConfig (config) {
|
package/src/contractor.js
CHANGED
|
@@ -21,6 +21,12 @@ class Contractor {
|
|
|
21
21
|
this.config = config
|
|
22
22
|
this.db = db
|
|
23
23
|
this.migrations = this.config.migrations || migrationStore.getAll(this.config.schema)
|
|
24
|
+
|
|
25
|
+
// exported api to index
|
|
26
|
+
this.functions = [
|
|
27
|
+
this.version,
|
|
28
|
+
this.isInstalled
|
|
29
|
+
]
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
async version () {
|
package/src/db.js
CHANGED
package/src/index.js
CHANGED
|
@@ -13,6 +13,15 @@ const events = {
|
|
|
13
13
|
stopped: 'stopped'
|
|
14
14
|
}
|
|
15
15
|
class PgBoss extends EventEmitter {
|
|
16
|
+
#stoppingOn
|
|
17
|
+
#stopped
|
|
18
|
+
#config
|
|
19
|
+
#db
|
|
20
|
+
#boss
|
|
21
|
+
#contractor
|
|
22
|
+
#manager
|
|
23
|
+
#timekeeper
|
|
24
|
+
|
|
16
25
|
static getConstructionPlans (schema) {
|
|
17
26
|
return Contractor.constructionPlans(schema)
|
|
18
27
|
}
|
|
@@ -26,60 +35,72 @@ class PgBoss extends EventEmitter {
|
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
constructor (value) {
|
|
29
|
-
const config = Attorney.getConfig(value)
|
|
30
|
-
|
|
31
38
|
super()
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
this.#stoppingOn = null
|
|
41
|
+
this.#stopped = true
|
|
42
|
+
|
|
43
|
+
const config = Attorney.getConfig(value)
|
|
44
|
+
this.#config = config
|
|
45
|
+
|
|
46
|
+
const db = this.getDb()
|
|
47
|
+
this.#db = db
|
|
34
48
|
|
|
35
49
|
if (db.isOurs) {
|
|
36
|
-
|
|
50
|
+
this.#promoteEvents(db)
|
|
37
51
|
}
|
|
38
52
|
|
|
39
|
-
const
|
|
40
|
-
Object.keys(manager.events).forEach(event => promoteEvent.call(this, manager, manager.events[event]))
|
|
41
|
-
manager.functions.forEach(func => promoteFunction.call(this, manager, func))
|
|
53
|
+
const contractor = new Contractor(db, config)
|
|
42
54
|
|
|
55
|
+
const manager = new Manager(db, config)
|
|
43
56
|
const bossConfig = { ...config, manager }
|
|
44
57
|
|
|
45
58
|
const boss = new Boss(db, bossConfig)
|
|
46
|
-
Object.keys(boss.events).forEach(event => promoteEvent.call(this, boss, boss.events[event]))
|
|
47
|
-
boss.functions.forEach(func => promoteFunction.call(this, boss, func))
|
|
48
59
|
|
|
49
60
|
const timekeeper = new Timekeeper(db, bossConfig)
|
|
50
|
-
Object.keys(timekeeper.events).forEach(event => promoteEvent.call(this, timekeeper, timekeeper.events[event]))
|
|
51
|
-
timekeeper.functions.forEach(func => promoteFunction.call(this, timekeeper, func))
|
|
52
|
-
|
|
53
61
|
manager.timekeeper = timekeeper
|
|
54
62
|
|
|
55
|
-
this
|
|
56
|
-
this
|
|
57
|
-
this
|
|
58
|
-
this.db = db
|
|
59
|
-
this.boss = boss
|
|
60
|
-
this.contractor = new Contractor(db, config)
|
|
61
|
-
this.manager = manager
|
|
62
|
-
this.timekeeper = timekeeper
|
|
63
|
-
|
|
64
|
-
function getDb (config) {
|
|
65
|
-
if (config.db) {
|
|
66
|
-
return config.db
|
|
67
|
-
}
|
|
63
|
+
this.#promoteEvents(manager)
|
|
64
|
+
this.#promoteEvents(boss)
|
|
65
|
+
this.#promoteEvents(timekeeper)
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
this.#promoteFunctions(boss)
|
|
68
|
+
this.#promoteFunctions(contractor)
|
|
69
|
+
this.#promoteFunctions(manager)
|
|
70
|
+
this.#promoteFunctions(timekeeper)
|
|
71
|
+
|
|
72
|
+
this.#boss = boss
|
|
73
|
+
this.#contractor = contractor
|
|
74
|
+
this.#manager = manager
|
|
75
|
+
this.#timekeeper = timekeeper
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getDb () {
|
|
79
|
+
if (this.#db) {
|
|
80
|
+
return this.#db
|
|
72
81
|
}
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
this
|
|
83
|
+
if (this.#config.db) {
|
|
84
|
+
return this.#config.db
|
|
76
85
|
}
|
|
77
86
|
|
|
78
|
-
|
|
87
|
+
const db = new Db(this.#config)
|
|
88
|
+
db.isOurs = true
|
|
89
|
+
return db
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#promoteEvents (emitter) {
|
|
93
|
+
for (const event of Object.values(emitter?.events)) {
|
|
79
94
|
emitter.on(event, arg => this.emit(event, arg))
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
97
|
|
|
98
|
+
#promoteFunctions (obj) {
|
|
99
|
+
for (const func of obj?.functions) {
|
|
100
|
+
this[func.name] = (...args) => func.apply(obj, args)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
83
104
|
async start () {
|
|
84
105
|
if (this.starting || this.started) {
|
|
85
106
|
return
|
|
@@ -87,39 +108,39 @@ class PgBoss extends EventEmitter {
|
|
|
87
108
|
|
|
88
109
|
this.starting = true
|
|
89
110
|
|
|
90
|
-
if (this
|
|
91
|
-
await this
|
|
111
|
+
if (this.#db.isOurs && !this.#db.opened) {
|
|
112
|
+
await this.#db.open()
|
|
92
113
|
}
|
|
93
114
|
|
|
94
|
-
if (this
|
|
95
|
-
await this
|
|
115
|
+
if (this.#config.migrate) {
|
|
116
|
+
await this.#contractor.start()
|
|
96
117
|
} else {
|
|
97
|
-
await this
|
|
118
|
+
await this.#contractor.check()
|
|
98
119
|
}
|
|
99
120
|
|
|
100
|
-
this
|
|
121
|
+
this.#manager.start()
|
|
101
122
|
|
|
102
|
-
if (this
|
|
103
|
-
await this
|
|
123
|
+
if (this.#config.supervise) {
|
|
124
|
+
await this.#boss.supervise()
|
|
104
125
|
}
|
|
105
126
|
|
|
106
|
-
if (this
|
|
107
|
-
await this
|
|
127
|
+
if (this.#config.monitorStateIntervalSeconds) {
|
|
128
|
+
await this.#boss.monitor()
|
|
108
129
|
}
|
|
109
130
|
|
|
110
|
-
if (this
|
|
111
|
-
await this
|
|
131
|
+
if (this.#config.schedule) {
|
|
132
|
+
await this.#timekeeper.start()
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
this.starting = false
|
|
115
136
|
this.started = true
|
|
116
|
-
this
|
|
137
|
+
this.#stopped = false
|
|
117
138
|
|
|
118
139
|
return this
|
|
119
140
|
}
|
|
120
141
|
|
|
121
142
|
async stop (options = {}) {
|
|
122
|
-
if (this
|
|
143
|
+
if (this.#stoppingOn || this.#stopped) {
|
|
123
144
|
return
|
|
124
145
|
}
|
|
125
146
|
|
|
@@ -127,27 +148,27 @@ class PgBoss extends EventEmitter {
|
|
|
127
148
|
|
|
128
149
|
timeout = Math.max(timeout, 1000)
|
|
129
150
|
|
|
130
|
-
this
|
|
151
|
+
this.#stoppingOn = Date.now()
|
|
131
152
|
|
|
132
|
-
await this
|
|
133
|
-
await this
|
|
134
|
-
await this
|
|
153
|
+
await this.#manager.stop()
|
|
154
|
+
await this.#timekeeper.stop()
|
|
155
|
+
await this.#boss.stop()
|
|
135
156
|
|
|
136
157
|
await new Promise((resolve, reject) => {
|
|
137
158
|
const shutdown = async () => {
|
|
138
159
|
try {
|
|
139
|
-
if (this
|
|
140
|
-
throw new Error(this
|
|
160
|
+
if (this.#config.__test__throw_shutdown) {
|
|
161
|
+
throw new Error(this.#config.__test__throw_shutdown)
|
|
141
162
|
}
|
|
142
163
|
|
|
143
|
-
await this
|
|
164
|
+
await this.#manager.failWip()
|
|
144
165
|
|
|
145
|
-
if (this
|
|
146
|
-
await this
|
|
166
|
+
if (this.#db.isOurs && this.#db.opened && destroy) {
|
|
167
|
+
await this.#db.close()
|
|
147
168
|
}
|
|
148
169
|
|
|
149
|
-
this
|
|
150
|
-
this
|
|
170
|
+
this.#stopped = true
|
|
171
|
+
this.#stoppingOn = null
|
|
151
172
|
this.started = false
|
|
152
173
|
|
|
153
174
|
this.emit(events.stopped)
|
|
@@ -168,13 +189,13 @@ class PgBoss extends EventEmitter {
|
|
|
168
189
|
|
|
169
190
|
setImmediate(async () => {
|
|
170
191
|
try {
|
|
171
|
-
if (this
|
|
172
|
-
throw new Error(this
|
|
192
|
+
if (this.#config.__test__throw_stop_monitor) {
|
|
193
|
+
throw new Error(this.#config.__test__throw_stop_monitor)
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
const isWip = () => this
|
|
196
|
+
const isWip = () => this.#manager.getWipData({ includeInternal: false }).length > 0
|
|
176
197
|
|
|
177
|
-
while ((Date.now() - this
|
|
198
|
+
while ((Date.now() - this.#stoppingOn) < timeout && isWip()) {
|
|
178
199
|
await delay(500)
|
|
179
200
|
}
|
|
180
201
|
|
package/src/manager.js
CHANGED
|
@@ -547,6 +547,8 @@ class Manager extends EventEmitter {
|
|
|
547
547
|
async createQueue (name, options = {}) {
|
|
548
548
|
assert(name, 'Missing queue name argument')
|
|
549
549
|
|
|
550
|
+
Attorney.assertQueueName(name)
|
|
551
|
+
|
|
550
552
|
const { policy = QUEUE_POLICY.standard } = options
|
|
551
553
|
|
|
552
554
|
assert(policy in QUEUE_POLICY, `${policy} is not a valid queue policy`)
|
|
@@ -560,7 +562,7 @@ class Manager extends EventEmitter {
|
|
|
560
562
|
deadLetter
|
|
561
563
|
} = Attorney.checkQueueArgs(name, options)
|
|
562
564
|
|
|
563
|
-
const paritionSql = plans.
|
|
565
|
+
const paritionSql = plans.createPartition(this.config.schema, name)
|
|
564
566
|
|
|
565
567
|
await this.db.executeSql(paritionSql)
|
|
566
568
|
|
|
@@ -646,8 +648,8 @@ class Manager extends EventEmitter {
|
|
|
646
648
|
const result = await this.db.executeSql(queueSql, [name])
|
|
647
649
|
|
|
648
650
|
if (result?.rows?.length) {
|
|
649
|
-
Attorney.
|
|
650
|
-
const sql = plans.
|
|
651
|
+
Attorney.assertQueueName(name)
|
|
652
|
+
const sql = plans.dropPartition(this.config.schema, name)
|
|
651
653
|
await this.db.executeSql(sql)
|
|
652
654
|
}
|
|
653
655
|
|
package/src/plans.js
CHANGED
|
@@ -46,8 +46,8 @@ module.exports = {
|
|
|
46
46
|
countStates,
|
|
47
47
|
createQueue,
|
|
48
48
|
updateQueue,
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
createPartition,
|
|
50
|
+
dropPartition,
|
|
51
51
|
deleteQueueRecords,
|
|
52
52
|
getQueueByName,
|
|
53
53
|
getQueueSize,
|
|
@@ -96,6 +96,10 @@ function create (schema, version) {
|
|
|
96
96
|
createTableSchedule(schema),
|
|
97
97
|
createTableSubscription(schema),
|
|
98
98
|
|
|
99
|
+
getPartitionFunction(schema),
|
|
100
|
+
createPartitionFunction(schema),
|
|
101
|
+
dropPartitionFunction(schema),
|
|
102
|
+
|
|
99
103
|
insertVersion(schema, version)
|
|
100
104
|
]
|
|
101
105
|
|
|
@@ -162,16 +166,53 @@ function createTableJob (schema) {
|
|
|
162
166
|
`
|
|
163
167
|
}
|
|
164
168
|
|
|
165
|
-
function
|
|
169
|
+
function createPartition (schema, name) {
|
|
170
|
+
return `SELECT ${schema}.create_partition('${name}');`
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function getPartitionFunction (schema) {
|
|
174
|
+
return `
|
|
175
|
+
CREATE FUNCTION ${schema}.get_partition(queue_name text, out name text) AS
|
|
176
|
+
$$
|
|
177
|
+
SELECT '${schema}.j' || encode(digest(queue_name, 'sha1'), 'hex');
|
|
178
|
+
$$
|
|
179
|
+
LANGUAGE SQL
|
|
180
|
+
IMMUTABLE
|
|
181
|
+
`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function createPartitionFunction (schema) {
|
|
185
|
+
return `
|
|
186
|
+
CREATE FUNCTION ${schema}.create_partition(queue_name text)
|
|
187
|
+
RETURNS VOID AS
|
|
188
|
+
$$
|
|
189
|
+
DECLARE
|
|
190
|
+
table_name varchar := ${schema}.get_partition(queue_name);
|
|
191
|
+
BEGIN
|
|
192
|
+
EXECUTE format('CREATE TABLE %I (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS)', table_name);
|
|
193
|
+
EXECUTE format('ALTER TABLE %I ADD CHECK (name=%L)', table_name, queue_name);
|
|
194
|
+
EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION %I FOR VALUES IN (%L)', table_name, queue_name);
|
|
195
|
+
END;
|
|
196
|
+
$$
|
|
197
|
+
LANGUAGE plpgsql;
|
|
198
|
+
`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function dropPartitionFunction (schema) {
|
|
166
202
|
return `
|
|
167
|
-
CREATE
|
|
168
|
-
|
|
169
|
-
|
|
203
|
+
CREATE FUNCTION ${schema}.drop_partition(queue_name text)
|
|
204
|
+
RETURNS VOID AS
|
|
205
|
+
$$
|
|
206
|
+
BEGIN
|
|
207
|
+
EXECUTE format('DROP TABLE IF EXISTS %I', ${schema}.get_partition(queue_name));
|
|
208
|
+
END;
|
|
209
|
+
$$
|
|
210
|
+
LANGUAGE plpgsql;
|
|
170
211
|
`
|
|
171
212
|
}
|
|
172
213
|
|
|
173
|
-
function
|
|
174
|
-
return `
|
|
214
|
+
function dropPartition (schema, name) {
|
|
215
|
+
return `SELECT ${schema}.drop_partition('${name}');`
|
|
175
216
|
}
|
|
176
217
|
|
|
177
218
|
function createPrimaryKeyArchive (schema) {
|
|
@@ -723,8 +764,8 @@ function locked (schema, query) {
|
|
|
723
764
|
}
|
|
724
765
|
|
|
725
766
|
function advisoryLock (schema, key) {
|
|
726
|
-
return `SELECT pg_advisory_xact_lock(
|
|
727
|
-
('x' ||
|
|
767
|
+
return `SELECT pg_advisory_xact_lock(
|
|
768
|
+
('x' || encode(digest(current_database() || '.pgboss.${schema}${key || ''}', 'sha256'), 'hex'))::bit(64)::bigint
|
|
728
769
|
)`
|
|
729
770
|
}
|
|
730
771
|
|
package/src/timekeeper.js
CHANGED
package/types.d.ts
CHANGED
|
@@ -348,7 +348,7 @@ declare class PgBoss extends EventEmitter {
|
|
|
348
348
|
getQueueSize(name: string, options?: object): Promise<number>;
|
|
349
349
|
getJobById(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<PgBoss.JobWithMetadata | null>;
|
|
350
350
|
|
|
351
|
-
createQueue(name: string, policy: 'standard' | 'short' | 'singleton' | 'stately'): Promise<void>;
|
|
351
|
+
createQueue(name: string, options?: { policy: 'standard' | 'short' | 'singleton' | 'stately' }): Promise<void>;
|
|
352
352
|
deleteQueue(name: string): Promise<void>;
|
|
353
353
|
purgeQueue(name: string): Promise<void>;
|
|
354
354
|
clearStorage(): Promise<void>;
|