@evanp/activitypub-bot 0.45.12 → 0.45.14
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/CHANGELOG.md +15 -0
- package/lib/activitydeliverer.js +14 -2
- package/lib/activitydistributor.js +71 -2
- package/lib/activityhandler.js +22 -39
- package/lib/app.js +1 -7
- package/lib/fanoutworker.js +14 -2
- package/lib/intakeworker.js +14 -2
- package/lib/jobqueue.js +13 -5
- package/lib/migrations/011-job-last-error.js +12 -0
- package/lib/remoteobjectcache.js +9 -1
- package/lib/worker.js +3 -3
- package/package.json +1 -1
- package/lib/objectcache.js +0 -51
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,21 @@ and this project adheres to
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.45.14] - 2026-05-10
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Depend on inbox forwarding for distribution to remote collections.
|
|
17
|
+
- More robust handling of client request failures in delivery and distribution.
|
|
18
|
+
- More robust handling of intake, fanout throttle errors.
|
|
19
|
+
- Store error stack for failing jobs in queue job entries.
|
|
20
|
+
|
|
21
|
+
## [0.45.13] - 2026-05-10
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Replace in-memory ObjectCache with persistent and shared RemoteObjectCache.
|
|
26
|
+
|
|
12
27
|
## [0.45.12] - 2026-05-09
|
|
13
28
|
|
|
14
29
|
### Changed
|
package/lib/activitydeliverer.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as ttlcachePkg from '@isaacs/ttlcache'
|
|
|
4
4
|
|
|
5
5
|
import as2 from './activitystreams.js'
|
|
6
6
|
import BotMaker from './botmaker.js'
|
|
7
|
+
import { ThrottleError } from './requestthrottler.js'
|
|
7
8
|
|
|
8
9
|
const TTLCache =
|
|
9
10
|
ttlcachePkg.TTLCache ?? ttlcachePkg.default ?? ttlcachePkg
|
|
@@ -128,6 +129,7 @@ export class ActivityDeliverer {
|
|
|
128
129
|
const deliveredTo = new Set()
|
|
129
130
|
const actor = this.getActor(activity)
|
|
130
131
|
const recipients = this.getRecipients(activity)
|
|
132
|
+
let fullActor
|
|
131
133
|
|
|
132
134
|
for (const recipient of recipients) {
|
|
133
135
|
this.#logger.debug({ recipient: recipient.id }, 'Checking recipient')
|
|
@@ -152,8 +154,18 @@ export class ActivityDeliverer {
|
|
|
152
154
|
)
|
|
153
155
|
}
|
|
154
156
|
} else {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
fullActor = fullActor ?? await this.#client.get(actor.id)
|
|
158
|
+
let fullRecipient
|
|
159
|
+
try {
|
|
160
|
+
fullRecipient = await this.#client.get(recipient.id)
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (err instanceof ThrottleError) throw err
|
|
163
|
+
this.#logger.warn(
|
|
164
|
+
{ recipient: recipient.id },
|
|
165
|
+
'Unreachable remote recipient'
|
|
166
|
+
)
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
157
169
|
if (await this.#isRemoteActor(fullRecipient)) {
|
|
158
170
|
this.#logger.warn({ recipient: recipient.id }, 'Skipping remote actor')
|
|
159
171
|
} else if (await this.#isRemoteFollowersCollection(fullActor, fullRecipient)) {
|
|
@@ -164,6 +164,14 @@ export class ActivityDistributor {
|
|
|
164
164
|
yield id
|
|
165
165
|
} else if (this.#isRemoteCollection(obj)) {
|
|
166
166
|
this.#logger.debug({ id }, 'Remote collection')
|
|
167
|
+
const owner = await this.#getCollectionOwner(obj, username)
|
|
168
|
+
if (owner && this.#isRecipient(activity, owner)) {
|
|
169
|
+
this.#logger.debug(
|
|
170
|
+
{ id, owner: owner.id },
|
|
171
|
+
'Leaving remote collection up to inbox forwarding'
|
|
172
|
+
)
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
167
175
|
try {
|
|
168
176
|
for await (const item of this.#client.items(obj.id, username)) {
|
|
169
177
|
this.#logger.debug({ id: item.id }, 'Remote collection member')
|
|
@@ -194,7 +202,16 @@ export class ActivityDistributor {
|
|
|
194
202
|
return sharedInbox
|
|
195
203
|
}
|
|
196
204
|
|
|
197
|
-
|
|
205
|
+
let obj
|
|
206
|
+
try {
|
|
207
|
+
obj = await this.#client.get(actorId, username)
|
|
208
|
+
} catch (err) {
|
|
209
|
+
this.#logger.warn(
|
|
210
|
+
{ actorId, username, err },
|
|
211
|
+
'Could not get actor in #getInbox'
|
|
212
|
+
)
|
|
213
|
+
return null
|
|
214
|
+
}
|
|
198
215
|
|
|
199
216
|
// Get the shared inbox if it exists
|
|
200
217
|
|
|
@@ -232,7 +249,16 @@ export class ActivityDistributor {
|
|
|
232
249
|
return directInbox
|
|
233
250
|
}
|
|
234
251
|
|
|
235
|
-
|
|
252
|
+
let obj
|
|
253
|
+
try {
|
|
254
|
+
obj = await this.#client.get(actorId, username)
|
|
255
|
+
} catch (err) {
|
|
256
|
+
this.#logger.warn(
|
|
257
|
+
{ actorId, username, err },
|
|
258
|
+
'Could not get actor in #getDirectInbox'
|
|
259
|
+
)
|
|
260
|
+
return null
|
|
261
|
+
}
|
|
236
262
|
|
|
237
263
|
if (!obj.inbox) {
|
|
238
264
|
return null
|
|
@@ -274,4 +300,47 @@ export class ActivityDistributor {
|
|
|
274
300
|
? obj.type.some(t => COLLECTION_TYPES.includes(t))
|
|
275
301
|
: COLLECTION_TYPES.includes(obj.type)
|
|
276
302
|
}
|
|
303
|
+
|
|
304
|
+
async #getCollectionOwner (obj, username) {
|
|
305
|
+
if (obj.attributedTo) {
|
|
306
|
+
return obj.attributedTo.first
|
|
307
|
+
} else {
|
|
308
|
+
let ownerId
|
|
309
|
+
let owner
|
|
310
|
+
|
|
311
|
+
if (obj.id.endsWith('/followers')) {
|
|
312
|
+
ownerId = obj.id.replace('/followers', '')
|
|
313
|
+
} else if (obj.id.endsWith('/following')) {
|
|
314
|
+
ownerId = obj.id.replace('/following', '')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (ownerId) {
|
|
318
|
+
try {
|
|
319
|
+
owner = await this.#client.get(ownerId, username)
|
|
320
|
+
} catch (err) {
|
|
321
|
+
this.#logger.debug(
|
|
322
|
+
{ ownerId },
|
|
323
|
+
'Owner unreachable'
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return owner
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#isRecipient (activity, obj) {
|
|
333
|
+
const props = ['to', 'cc', 'audience', 'bto', 'bcc']
|
|
334
|
+
for (const prop of props) {
|
|
335
|
+
const p = activity.get(prop)
|
|
336
|
+
if (p) {
|
|
337
|
+
for (const value of p) {
|
|
338
|
+
if (value.id === obj.id) {
|
|
339
|
+
return true
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
277
346
|
}
|
package/lib/activityhandler.js
CHANGED
|
@@ -47,6 +47,12 @@ export class ActivityHandler {
|
|
|
47
47
|
'activity parameter must be an object'
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
await this.#cache.set(
|
|
51
|
+
activity.id,
|
|
52
|
+
bot.username,
|
|
53
|
+
await activity.export({ useOriginalContext: true })
|
|
54
|
+
)
|
|
55
|
+
|
|
50
56
|
let handled = false
|
|
51
57
|
|
|
52
58
|
try {
|
|
@@ -96,9 +102,11 @@ export class ActivityHandler {
|
|
|
96
102
|
return
|
|
97
103
|
}
|
|
98
104
|
if (await this.#authz.sameOrigin(activity, object)) {
|
|
99
|
-
await this.#cache.
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
await this.#cache.set(
|
|
106
|
+
object.id,
|
|
107
|
+
bot.username,
|
|
108
|
+
await object.export({ useOriginalContext: true })
|
|
109
|
+
)
|
|
102
110
|
}
|
|
103
111
|
await this.#handleCreateReplies(bot, activity, actor, object)
|
|
104
112
|
await this.#handleCreateThread(bot, activity, actor, object)
|
|
@@ -183,6 +191,7 @@ export class ActivityHandler {
|
|
|
183
191
|
'Not a local thread',
|
|
184
192
|
{ thread: thread.id }
|
|
185
193
|
)
|
|
194
|
+
await this.#cache.clear(thread.id)
|
|
186
195
|
return
|
|
187
196
|
}
|
|
188
197
|
const root = await this.#threadRoot(thread)
|
|
@@ -231,52 +240,22 @@ export class ActivityHandler {
|
|
|
231
240
|
|
|
232
241
|
async #handleUpdate (bot, activity) {
|
|
233
242
|
const object = this.#getObject(activity)
|
|
234
|
-
|
|
235
|
-
await this.#cache.save(object)
|
|
236
|
-
} else {
|
|
237
|
-
await this.#cache.saveReceived(object)
|
|
238
|
-
}
|
|
243
|
+
await this.#cache.clear(object.id)
|
|
239
244
|
}
|
|
240
245
|
|
|
241
246
|
async #handleDelete (bot, activity) {
|
|
242
247
|
const object = this.#getObject(activity)
|
|
243
|
-
await this.#cache.clear(object)
|
|
248
|
+
await this.#cache.clear(object.id)
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
async #handleAdd (bot, activity) {
|
|
247
|
-
const actor = this.#getActor(activity)
|
|
248
252
|
const target = this.#getTarget(activity)
|
|
249
|
-
|
|
250
|
-
if (await this.#authz.sameOrigin(actor, object)) {
|
|
251
|
-
await this.#cache.save(object)
|
|
252
|
-
} else {
|
|
253
|
-
await this.#cache.saveReceived(object)
|
|
254
|
-
}
|
|
255
|
-
if (await this.#authz.sameOrigin(actor, target)) {
|
|
256
|
-
await this.#cache.save(target)
|
|
257
|
-
await this.#cache.saveMembership(target, object)
|
|
258
|
-
} else {
|
|
259
|
-
await this.#cache.saveReceived(target)
|
|
260
|
-
await this.#cache.saveMembershipReceived(target, object)
|
|
261
|
-
}
|
|
253
|
+
await this.#cache.clear(target.id)
|
|
262
254
|
}
|
|
263
255
|
|
|
264
256
|
async #handleRemove (bot, activity) {
|
|
265
|
-
const actor = this.#getActor(activity)
|
|
266
257
|
const target = this.#getTarget(activity)
|
|
267
|
-
|
|
268
|
-
if (await this.#authz.sameOrigin(actor, object)) {
|
|
269
|
-
await this.#cache.save(object)
|
|
270
|
-
} else {
|
|
271
|
-
await this.#cache.saveReceived(object)
|
|
272
|
-
}
|
|
273
|
-
if (await this.#authz.sameOrigin(actor, target)) {
|
|
274
|
-
await this.#cache.save(target)
|
|
275
|
-
await this.#cache.saveMembership(target, object, false)
|
|
276
|
-
} else {
|
|
277
|
-
await this.#cache.saveReceived(target)
|
|
278
|
-
await this.#cache.saveMembershipReceived(target, object, false)
|
|
279
|
-
}
|
|
258
|
+
await this.#cache.clear(target.id)
|
|
280
259
|
}
|
|
281
260
|
|
|
282
261
|
async #handleFollow (bot, activity) {
|
|
@@ -993,7 +972,7 @@ export class ActivityHandler {
|
|
|
993
972
|
// Check it from cache
|
|
994
973
|
const id = object.id
|
|
995
974
|
this.#logger.debug({ msg: 'Checking cache', object: id })
|
|
996
|
-
object = await this.#cache.get(id)
|
|
975
|
+
object = await as2.import((await this.#cache.get(id, bot.username)).object)
|
|
997
976
|
if (object &&
|
|
998
977
|
(!required.includes('type') || object.type) &&
|
|
999
978
|
!others.find((prop) => !object.has(prop))) {
|
|
@@ -1004,7 +983,11 @@ export class ActivityHandler {
|
|
|
1004
983
|
this.#logger.debug({ msg: 'Checking remote', object: id })
|
|
1005
984
|
object = await this.#client.get(id, bot.username)
|
|
1006
985
|
this.#logger.debug({ msg: 'Object fetched from remote', object: object.id, objectText: await object.write() })
|
|
1007
|
-
this.#cache.
|
|
986
|
+
await this.#cache.set(
|
|
987
|
+
object.id,
|
|
988
|
+
bot.username,
|
|
989
|
+
await object.export({ useOriginalContext: true })
|
|
990
|
+
)
|
|
1008
991
|
if (object &&
|
|
1009
992
|
(!required.includes('type') || object.type) &&
|
|
1010
993
|
!others.find((prop) => !object.has(prop))) {
|
package/lib/app.js
CHANGED
|
@@ -23,7 +23,6 @@ import { HTTPSignature } from './httpsignature.js'
|
|
|
23
23
|
import { Authorizer } from './authorizer.js'
|
|
24
24
|
import { RemoteKeyStorage } from './remotekeystorage.js'
|
|
25
25
|
import { ActivityHandler } from './activityhandler.js'
|
|
26
|
-
import { ObjectCache } from '../lib/objectcache.js'
|
|
27
26
|
import serverRouter from './routes/server.js'
|
|
28
27
|
import userRouter from './routes/user.js'
|
|
29
28
|
import objectRouter from './routes/object.js'
|
|
@@ -116,17 +115,12 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
|
|
|
116
115
|
endpointCache
|
|
117
116
|
)
|
|
118
117
|
const authorizer = new Authorizer(actorStorage, formatter, client)
|
|
119
|
-
const cache = new ObjectCache({
|
|
120
|
-
longTTL: 3600 * 1000,
|
|
121
|
-
shortTTL: 300 * 1000,
|
|
122
|
-
maxItems: 1000
|
|
123
|
-
})
|
|
124
118
|
const activityHandler = new ActivityHandler(
|
|
125
119
|
actorStorage,
|
|
126
120
|
objectStorage,
|
|
127
121
|
distributor,
|
|
128
122
|
formatter,
|
|
129
|
-
|
|
123
|
+
remoteObjectCache,
|
|
130
124
|
authorizer,
|
|
131
125
|
logger,
|
|
132
126
|
client
|
package/lib/fanoutworker.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
|
|
3
3
|
import as2 from './activitystreams.js'
|
|
4
|
-
import { Worker } from './worker.js'
|
|
4
|
+
import { Worker, RecoverableError } from './worker.js'
|
|
5
|
+
import { ThrottleError } from './requestthrottler.js'
|
|
5
6
|
|
|
6
7
|
export class FanoutWorker extends Worker {
|
|
7
8
|
#distributor
|
|
@@ -15,6 +16,17 @@ export class FanoutWorker extends Worker {
|
|
|
15
16
|
async doJob (payload, attempts) {
|
|
16
17
|
const activity = await as2.import(payload.activity)
|
|
17
18
|
const { username } = payload
|
|
18
|
-
|
|
19
|
+
try {
|
|
20
|
+
await this.#distributor.fanout(activity, username)
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (err instanceof ThrottleError) {
|
|
23
|
+
this._logger.warn(
|
|
24
|
+
{ err, activity: activity.id, username },
|
|
25
|
+
'Throttled fanout, waiting to retry')
|
|
26
|
+
throw new RecoverableError(err.message, err.waitTime)
|
|
27
|
+
} else {
|
|
28
|
+
throw err
|
|
29
|
+
}
|
|
30
|
+
}
|
|
19
31
|
}
|
|
20
32
|
}
|
package/lib/intakeworker.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
|
|
3
3
|
import as2 from './activitystreams.js'
|
|
4
|
-
import { Worker } from './worker.js'
|
|
4
|
+
import { Worker, RecoverableError } from './worker.js'
|
|
5
|
+
import { ThrottleError } from './requestthrottler.js'
|
|
5
6
|
|
|
6
7
|
export class IntakeWorker extends Worker {
|
|
7
8
|
#deliverer
|
|
@@ -17,6 +18,17 @@ export class IntakeWorker extends Worker {
|
|
|
17
18
|
async doJob (payload, attempts) {
|
|
18
19
|
const raw = payload.activity
|
|
19
20
|
const activity = await as2.import(raw)
|
|
20
|
-
|
|
21
|
+
try {
|
|
22
|
+
await this.#deliverer.deliverToAll(activity, this.#bots)
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (err instanceof ThrottleError) {
|
|
25
|
+
this._logger.warn(
|
|
26
|
+
{ err, activity: activity.id },
|
|
27
|
+
'Throttled intake, waiting to retry')
|
|
28
|
+
throw new RecoverableError(err.message, err.waitTime)
|
|
29
|
+
} else {
|
|
30
|
+
throw err
|
|
31
|
+
}
|
|
32
|
+
}
|
|
21
33
|
}
|
|
22
34
|
}
|
package/lib/jobqueue.js
CHANGED
|
@@ -87,12 +87,11 @@ export class JobQueue {
|
|
|
87
87
|
this.#logger.debug({ method: 'release', jobRunnerId, jobId }, 'released job')
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
async retryAfter (jobId, jobRunnerId, delay) {
|
|
90
|
+
async retryAfter (jobId, jobRunnerId, delay, lastError = null) {
|
|
91
91
|
assert.ok(jobId)
|
|
92
92
|
assert.strictEqual(typeof jobId, 'string')
|
|
93
93
|
assert.ok(jobRunnerId)
|
|
94
94
|
assert.strictEqual(typeof jobRunnerId, 'string')
|
|
95
|
-
assert.ok(delay)
|
|
96
95
|
assert.strictEqual(typeof delay, 'number')
|
|
97
96
|
|
|
98
97
|
const retryAfter = new Date(Date.now() + delay)
|
|
@@ -102,9 +101,10 @@ export class JobQueue {
|
|
|
102
101
|
SET claimed_by = NULL,
|
|
103
102
|
claimed_at = NULL,
|
|
104
103
|
updated_at = CURRENT_TIMESTAMP,
|
|
105
|
-
retry_after =
|
|
104
|
+
retry_after = ?,
|
|
105
|
+
last_error = ?
|
|
106
106
|
WHERE job_id = ? AND claimed_by = ?;`,
|
|
107
|
-
{ replacements: [retryAfter, jobId, jobRunnerId] }
|
|
107
|
+
{ replacements: [retryAfter, lastError, jobId, jobRunnerId] }
|
|
108
108
|
)
|
|
109
109
|
this.#logger.debug(
|
|
110
110
|
{ method: 'retry', jobRunnerId, jobId, delay },
|
|
@@ -157,7 +157,7 @@ export class JobQueue {
|
|
|
157
157
|
this.#ac.abort()
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
async fail (jobId, jobRunnerId) {
|
|
160
|
+
async fail (jobId, jobRunnerId, lastError = null) {
|
|
161
161
|
await this.#connection.query(`
|
|
162
162
|
INSERT INTO failed_job
|
|
163
163
|
(job_id, queue_id, priority, payload, claimed_at, claimed_by, attempts, retry_after, created_at, updated_at)
|
|
@@ -170,6 +170,14 @@ export class JobQueue {
|
|
|
170
170
|
DELETE FROM job
|
|
171
171
|
WHERE job_id = ? AND claimed_by = ?;`,
|
|
172
172
|
{ replacements: [jobId, jobRunnerId] })
|
|
173
|
+
|
|
174
|
+
if (lastError) {
|
|
175
|
+
await this.#connection.query(`
|
|
176
|
+
UPDATE failed_job
|
|
177
|
+
SET last_error = ?
|
|
178
|
+
WHERE job_id = ?;`,
|
|
179
|
+
{ replacements: [lastError, jobId] })
|
|
180
|
+
}
|
|
173
181
|
this.#logger.debug({ method: 'complete', jobRunnerId, jobId }, 'completed job')
|
|
174
182
|
}
|
|
175
183
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const id = '011-job-last-error'
|
|
2
|
+
|
|
3
|
+
export async function up (connection, queryOptions = {}) {
|
|
4
|
+
await connection.query(`
|
|
5
|
+
ALTER TABLE job
|
|
6
|
+
ADD COLUMN last_error TEXT;
|
|
7
|
+
`, queryOptions)
|
|
8
|
+
await connection.query(`
|
|
9
|
+
ALTER TABLE failed_job
|
|
10
|
+
ADD COLUMN last_error TEXT;
|
|
11
|
+
`, queryOptions)
|
|
12
|
+
}
|
package/lib/remoteobjectcache.js
CHANGED
|
@@ -59,7 +59,7 @@ export class RemoteObjectCache {
|
|
|
59
59
|
return result
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
async set (id, username, object, headers) {
|
|
62
|
+
async set (id, username, object, headers = new Headers()) {
|
|
63
63
|
assert.ok(id)
|
|
64
64
|
assert.strictEqual(typeof id, 'string')
|
|
65
65
|
assert.ok(username)
|
|
@@ -95,6 +95,14 @@ export class RemoteObjectCache {
|
|
|
95
95
|
)
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
async clear (id) {
|
|
99
|
+
await this.#connection.query(
|
|
100
|
+
`DELETE FROM remote_object_cache
|
|
101
|
+
WHERE id = ?`,
|
|
102
|
+
{ replacements: [id] }
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
98
106
|
async #getExpiry (object, headers) {
|
|
99
107
|
assert.ok(object)
|
|
100
108
|
assert.strictEqual(typeof object, 'object')
|
package/lib/worker.js
CHANGED
|
@@ -55,7 +55,7 @@ export class Worker {
|
|
|
55
55
|
this.#logger.warn({ err, jobId }, 'error before job, retrying')
|
|
56
56
|
if (jobId) {
|
|
57
57
|
const delay = this.#retryDelay(attempts ?? 1)
|
|
58
|
-
await this.#jobQueue.retryAfter(jobId, this.#workerId, delay)
|
|
58
|
+
await this.#jobQueue.retryAfter(jobId, this.#workerId, delay, err.stack)
|
|
59
59
|
}
|
|
60
60
|
continue
|
|
61
61
|
}
|
|
@@ -69,9 +69,9 @@ export class Worker {
|
|
|
69
69
|
await this.#jobQueue.complete(jobId, this.#workerId)
|
|
70
70
|
} catch (err) {
|
|
71
71
|
if (err instanceof RecoverableError) {
|
|
72
|
-
await this.#jobQueue.retryAfter(jobId, this.#workerId, err.delay)
|
|
72
|
+
await this.#jobQueue.retryAfter(jobId, this.#workerId, err.delay, err.stack)
|
|
73
73
|
} else {
|
|
74
|
-
await this.#jobQueue.fail(jobId, this.#workerId)
|
|
74
|
+
await this.#jobQueue.fail(jobId, this.#workerId, err.stack)
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
package/package.json
CHANGED
package/lib/objectcache.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import * as ttlcachePkg from '@isaacs/ttlcache'
|
|
2
|
-
|
|
3
|
-
const TTLCache =
|
|
4
|
-
ttlcachePkg.TTLCache ?? ttlcachePkg.default ?? ttlcachePkg
|
|
5
|
-
|
|
6
|
-
export class ObjectCache {
|
|
7
|
-
#objects = null
|
|
8
|
-
#members = null
|
|
9
|
-
constructor ({ longTTL, shortTTL, maxItems }) {
|
|
10
|
-
this.#objects = new TTLCache({ ttl: shortTTL, max: maxItems })
|
|
11
|
-
this.#members = new TTLCache({ ttl: shortTTL, max: maxItems })
|
|
12
|
-
this.longTTL = longTTL
|
|
13
|
-
this.shortTTL = shortTTL
|
|
14
|
-
this.maxItems = maxItems
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async initialize () {
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async get (id) {
|
|
21
|
-
return this.#objects.get(id)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async save (object) {
|
|
25
|
-
return this.#objects.set(object.id, object, { ttl: this.longTTL })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async saveReceived (object) {
|
|
29
|
-
return this.#objects.set(object.id, object, { ttl: this.shortTTL })
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async clear (object) {
|
|
33
|
-
return this.#objects.delete(object.id)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
membershipKey (collection, object) {
|
|
37
|
-
return `${collection.id}:${object.id}`
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async saveMembership (collection, object, isMember = true) {
|
|
41
|
-
return this.#members.set(this.membershipKey(collection, object), isMember, { ttl: this.longTTL })
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async saveMembershipReceived (collection, object, isMember = true) {
|
|
45
|
-
return this.#members.set(this.membershipKey(collection, object), isMember, { ttl: this.shortTTL })
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async isMember (collection, object) {
|
|
49
|
-
return this.#members.get(this.membershipKey(collection, object))
|
|
50
|
-
}
|
|
51
|
-
}
|