@relaybus/mongodb 0.3.0 → 0.3.1
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/dist/index.cjs +580 -465
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +581 -466
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
var core = require('@relaybus/core');
|
|
4
4
|
var internal = require('@relaybus/core/internal');
|
|
5
5
|
|
|
6
|
-
// src/
|
|
7
|
-
|
|
8
|
-
// src/lanes.ts
|
|
6
|
+
// src/documents.ts
|
|
9
7
|
var LANE_COUNT = 256;
|
|
10
8
|
var FNV_OFFSET_BASIS = 2166136261;
|
|
11
9
|
var FNV_PRIME = 16777619;
|
|
@@ -17,16 +15,20 @@ function laneOf(key, eventId) {
|
|
|
17
15
|
}
|
|
18
16
|
return hash & LANE_COUNT - 1;
|
|
19
17
|
}
|
|
18
|
+
var LEASE_TOKENS = new internal.LaneLeaseTokenFormat({
|
|
19
|
+
laneCount: LANE_COUNT,
|
|
20
|
+
epochDigits: 15,
|
|
21
|
+
claimsDigits: 15
|
|
22
|
+
});
|
|
20
23
|
function formatLeaseToken(laneId, { epoch, claims }) {
|
|
21
|
-
return
|
|
24
|
+
return LEASE_TOKENS.format(laneId, { epoch, claims });
|
|
22
25
|
}
|
|
23
|
-
var LEASE_TOKEN_PATTERN = /^(\d{1,3}):(\d{1,15}):(\d{1,15})$/;
|
|
24
26
|
function parseLeaseToken(token) {
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
+
const parts = LEASE_TOKENS.parse(token);
|
|
28
|
+
if (parts === void 0) {
|
|
27
29
|
return void 0;
|
|
28
30
|
}
|
|
29
|
-
return { laneId:
|
|
31
|
+
return { laneId: parts.lane, epoch: Number(parts.epoch), claims: parts.claims };
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
// src/documents.ts
|
|
@@ -204,9 +206,6 @@ async function runInTransactionWith(state, {
|
|
|
204
206
|
throw error;
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
|
-
function leaseKey(deliveryId, leaseToken) {
|
|
208
|
-
return `${deliveryId}\0${leaseToken}`;
|
|
209
|
-
}
|
|
210
209
|
function assertInTransaction(session, operation) {
|
|
211
210
|
if (!session.inTransaction()) {
|
|
212
211
|
throw new core.ConfigurationError(
|
|
@@ -252,16 +251,8 @@ var MongoAppend = class {
|
|
|
252
251
|
const duplicateEventIds = [];
|
|
253
252
|
const newEvents = [];
|
|
254
253
|
const newDeliveries = [];
|
|
255
|
-
const knownHandlers = /* @__PURE__ */ new Map();
|
|
256
254
|
const addedHandlers = /* @__PURE__ */ new Map();
|
|
257
|
-
const orderingByEventId
|
|
258
|
-
for (const [eventId, stored] of storedEvents) {
|
|
259
|
-
orderingByEventId.set(eventId, {
|
|
260
|
-
key: stored.key ?? null,
|
|
261
|
-
sequence: stored.sequence ?? null
|
|
262
|
-
});
|
|
263
|
-
knownHandlers.set(eventId, new Set(stored.handlers));
|
|
264
|
-
}
|
|
255
|
+
const { orderingByEventId, knownHandlers } = indexStoredEvents(storedEvents);
|
|
265
256
|
for (const { event, deliveries } of entries) {
|
|
266
257
|
let ordering = orderingByEventId.get(event.id);
|
|
267
258
|
if (ordering === void 0) {
|
|
@@ -291,47 +282,23 @@ var MongoAppend = class {
|
|
|
291
282
|
const { session, plan, storedEvents, now } = input;
|
|
292
283
|
const { newEvents, newDeliveries, addedHandlers } = plan;
|
|
293
284
|
if (newEvents.length > 0) {
|
|
294
|
-
await this.#state.events.bulkWrite(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
handlers: addedHandlers.get(event.id) ?? [],
|
|
299
|
-
createdAt: now
|
|
300
|
-
});
|
|
301
|
-
return {
|
|
302
|
-
updateOne: {
|
|
303
|
-
filter: { _id: document._id },
|
|
304
|
-
update: { $setOnInsert: document },
|
|
305
|
-
upsert: true
|
|
306
|
-
}
|
|
307
|
-
};
|
|
308
|
-
}),
|
|
309
|
-
{ session, ordered: true }
|
|
310
|
-
);
|
|
285
|
+
await this.#state.events.bulkWrite(newEventOperations({ newEvents, addedHandlers, now }), {
|
|
286
|
+
session,
|
|
287
|
+
ordered: true
|
|
288
|
+
});
|
|
311
289
|
}
|
|
312
290
|
const extendedEvents = [...addedHandlers].filter(([eventId]) => storedEvents.has(eventId));
|
|
313
291
|
if (extendedEvents.length > 0) {
|
|
314
|
-
await this.#state.events.bulkWrite(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
update: { $inc: { revision: 1 }, $push: { handlers: { $each: handlers } } }
|
|
319
|
-
}
|
|
320
|
-
})),
|
|
321
|
-
{ session, ordered: true }
|
|
322
|
-
);
|
|
292
|
+
await this.#state.events.bulkWrite(extendedEventOperations(extendedEvents), {
|
|
293
|
+
session,
|
|
294
|
+
ordered: true
|
|
295
|
+
});
|
|
323
296
|
}
|
|
324
297
|
if (newDeliveries.length > 0) {
|
|
325
|
-
await this.#state.deliveries.bulkWrite(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
update: { $setOnInsert: document },
|
|
330
|
-
upsert: true
|
|
331
|
-
}
|
|
332
|
-
})),
|
|
333
|
-
{ session, ordered: true }
|
|
334
|
-
);
|
|
298
|
+
await this.#state.deliveries.bulkWrite(newDeliveryOperations(newDeliveries), {
|
|
299
|
+
session,
|
|
300
|
+
ordered: true
|
|
301
|
+
});
|
|
335
302
|
}
|
|
336
303
|
}
|
|
337
304
|
/** Atomically increments the counter of `key`; conflicting appends of the key serialize here. */
|
|
@@ -344,8 +311,52 @@ var MongoAppend = class {
|
|
|
344
311
|
return counter.value;
|
|
345
312
|
}
|
|
346
313
|
};
|
|
347
|
-
|
|
348
|
-
|
|
314
|
+
function indexStoredEvents(storedEvents) {
|
|
315
|
+
const orderingByEventId = /* @__PURE__ */ new Map();
|
|
316
|
+
const knownHandlers = /* @__PURE__ */ new Map();
|
|
317
|
+
for (const [eventId, stored] of storedEvents) {
|
|
318
|
+
orderingByEventId.set(eventId, {
|
|
319
|
+
key: stored.key ?? null,
|
|
320
|
+
sequence: stored.sequence ?? null
|
|
321
|
+
});
|
|
322
|
+
knownHandlers.set(eventId, new Set(stored.handlers));
|
|
323
|
+
}
|
|
324
|
+
return { orderingByEventId, knownHandlers };
|
|
325
|
+
}
|
|
326
|
+
function newEventOperations(input) {
|
|
327
|
+
const { newEvents, addedHandlers, now } = input;
|
|
328
|
+
return newEvents.map(({ event, sequence }) => {
|
|
329
|
+
const document = toEventDocument(event, {
|
|
330
|
+
sequence,
|
|
331
|
+
handlers: addedHandlers.get(event.id) ?? [],
|
|
332
|
+
createdAt: now
|
|
333
|
+
});
|
|
334
|
+
return {
|
|
335
|
+
updateOne: {
|
|
336
|
+
filter: { _id: document._id },
|
|
337
|
+
update: { $setOnInsert: document },
|
|
338
|
+
upsert: true
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
function extendedEventOperations(extendedEvents) {
|
|
344
|
+
return extendedEvents.map(([eventId, handlers]) => ({
|
|
345
|
+
updateOne: {
|
|
346
|
+
filter: { _id: eventId },
|
|
347
|
+
update: { $inc: { revision: 1 }, $push: { handlers: { $each: handlers } } }
|
|
348
|
+
}
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
function newDeliveryOperations(newDeliveries) {
|
|
352
|
+
return newDeliveries.map((document) => ({
|
|
353
|
+
updateOne: {
|
|
354
|
+
filter: { eventId: document.eventId, handler: document.handler },
|
|
355
|
+
update: { $setOnInsert: document },
|
|
356
|
+
upsert: true
|
|
357
|
+
}
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
349
360
|
var ClaimScan = class {
|
|
350
361
|
#keys = /* @__PURE__ */ new Map();
|
|
351
362
|
/** Whether an unfinished delivery this scan knows of comes before the delivery with `sequence`. */
|
|
@@ -389,13 +400,104 @@ var ClaimScan = class {
|
|
|
389
400
|
}
|
|
390
401
|
};
|
|
391
402
|
function orderingIdentity(handler, key) {
|
|
392
|
-
return
|
|
403
|
+
return internal.CompositeKey.of(handler, key);
|
|
393
404
|
}
|
|
405
|
+
var MongoLaneReconciler = class {
|
|
406
|
+
#state;
|
|
407
|
+
constructor(state) {
|
|
408
|
+
this.#state = state;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Takes every delivery this instance does not run out of the lanes `owner`
|
|
412
|
+
* holds on it: every listed delivery whose current claim (lane epoch and
|
|
413
|
+
* claim generation now) was not handed out by this instance, or was settled.
|
|
414
|
+
* One transaction, and the lanes are written first under their epoch and
|
|
415
|
+
* holder: a worker taking a lane over meanwhile conflicts with it, so its
|
|
416
|
+
* claim is never touched. The claim generation of every delivery taken out
|
|
417
|
+
* moves on, so no lease of those claims (a completion that failed and is
|
|
418
|
+
* retried later, say) can ever match a later claim of the same delivery.
|
|
419
|
+
* Registrations of this owner that are not the current claim of a listed
|
|
420
|
+
* delivery are dropped.
|
|
421
|
+
*/
|
|
422
|
+
async reconcile(owner) {
|
|
423
|
+
await runInTransaction(this.#state, async (session) => {
|
|
424
|
+
const held = await this.#state.lanes.find({ owner, holder: this.#state.instanceId }, { session }).toArray();
|
|
425
|
+
const claimsById = await this.#claimGenerations(
|
|
426
|
+
held.flatMap((lane) => lane.inFlight),
|
|
427
|
+
session
|
|
428
|
+
);
|
|
429
|
+
const { unclaimed, current } = this.#splitUnclaimed(held, claimsById);
|
|
430
|
+
this.#state.handedOut.forgetWhere(
|
|
431
|
+
(entry, identity) => entry.owner === owner && !current.has(identity)
|
|
432
|
+
);
|
|
433
|
+
if (held.length === 0) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
await this.#takeOut(unclaimed, session);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/** The claim generation now of every listed delivery that is still pending. */
|
|
440
|
+
async #claimGenerations(listed, session) {
|
|
441
|
+
return new Map(
|
|
442
|
+
(await this.#state.deliveries.find({ _id: { $in: listed }, status: "pending" }, { session, projection: { claims: 1 } }).toArray()).map((document) => [document._id, document.claims ?? 0])
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Per held lane, the listed deliveries this instance did not hand out under their current claim,
|
|
447
|
+
* and the identities of all current claims.
|
|
448
|
+
*/
|
|
449
|
+
#splitUnclaimed(held, claimsById) {
|
|
450
|
+
const current = /* @__PURE__ */ new Set();
|
|
451
|
+
const unclaimed = held.map((lane) => ({
|
|
452
|
+
lane,
|
|
453
|
+
ids: lane.inFlight.filter((id) => {
|
|
454
|
+
const key = internal.DeliveryLeaseIdentity.of(
|
|
455
|
+
id,
|
|
456
|
+
formatLeaseToken(lane._id, { epoch: lane.epoch, claims: claimsById.get(id) ?? 0 })
|
|
457
|
+
);
|
|
458
|
+
current.add(key);
|
|
459
|
+
return !this.#state.handedOut.has(key);
|
|
460
|
+
})
|
|
461
|
+
}));
|
|
462
|
+
return { unclaimed, current };
|
|
463
|
+
}
|
|
464
|
+
/** Takes the unclaimed deliveries out of their lanes, then moves their claim generation on. */
|
|
465
|
+
async #takeOut(unclaimed, session) {
|
|
466
|
+
await this.#state.lanes.bulkWrite(
|
|
467
|
+
unclaimed.map(({ lane, ids }) => ({
|
|
468
|
+
updateOne: {
|
|
469
|
+
filter: { _id: lane._id, epoch: lane.epoch, holder: this.#state.instanceId },
|
|
470
|
+
update: { $pull: { inFlight: { $in: ids } }, $inc: { revision: 1 } }
|
|
471
|
+
}
|
|
472
|
+
})),
|
|
473
|
+
{ session, ordered: false }
|
|
474
|
+
);
|
|
475
|
+
await this.#state.deliveries.updateMany(
|
|
476
|
+
{ _id: { $in: unclaimed.flatMap((entry) => entry.ids) }, status: "pending" },
|
|
477
|
+
{ $inc: { claims: 1 } },
|
|
478
|
+
{ session }
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
394
482
|
|
|
395
483
|
// src/mongo-claim.ts
|
|
396
484
|
var PARK_BACKLOG_THRESHOLD = 32;
|
|
397
485
|
var INITIAL_LANES_PER_ROUND = 4;
|
|
398
486
|
var LANES_PER_CANDIDATE_QUERY = 128;
|
|
487
|
+
function toClaimedDeliveries(claimed, context) {
|
|
488
|
+
const { scope, events, request } = context;
|
|
489
|
+
return claimed.map((document) => ({
|
|
490
|
+
deliveryId: document._id,
|
|
491
|
+
leaseToken: formatLeaseToken(document.laneId, {
|
|
492
|
+
epoch: scope.get(document.laneId).epoch,
|
|
493
|
+
claims: document.claims ?? 0
|
|
494
|
+
}),
|
|
495
|
+
handler: document.handler,
|
|
496
|
+
attempt: document.attempts + 1,
|
|
497
|
+
leaseExpiresAt: request.leaseExpiresAt,
|
|
498
|
+
event: events.get(document.eventId)
|
|
499
|
+
}));
|
|
500
|
+
}
|
|
399
501
|
function scopeOf(held) {
|
|
400
502
|
return new Map(
|
|
401
503
|
held.map((lane) => [
|
|
@@ -409,33 +511,23 @@ function scopeOf(held) {
|
|
|
409
511
|
])
|
|
410
512
|
);
|
|
411
513
|
}
|
|
412
|
-
var MongoClaims = class {
|
|
514
|
+
var MongoClaims = class extends internal.OwnerSerializedClaims {
|
|
413
515
|
#state;
|
|
414
516
|
#laneLeases;
|
|
415
517
|
#ordering;
|
|
416
518
|
#queries;
|
|
519
|
+
#reconciler;
|
|
417
520
|
constructor(state, collaborators) {
|
|
521
|
+
super();
|
|
418
522
|
this.#state = state;
|
|
419
523
|
this.#laneLeases = collaborators.laneLeases;
|
|
420
524
|
this.#ordering = collaborators.ordering;
|
|
421
525
|
this.#queries = collaborators.queries;
|
|
526
|
+
this.#reconciler = new MongoLaneReconciler(state);
|
|
422
527
|
}
|
|
423
|
-
/**
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return [];
|
|
427
|
-
}
|
|
428
|
-
const owner = request.leaseOwner;
|
|
429
|
-
const previous = this.#state.claimsInProgress.get(owner) ?? Promise.resolve();
|
|
430
|
-
const claimed = previous.then(() => this.#claimReconciled(request));
|
|
431
|
-
const settled = Promise.allSettled([claimed]);
|
|
432
|
-
this.#state.claimsInProgress.set(owner, settled);
|
|
433
|
-
void settled.then(() => {
|
|
434
|
-
if (this.#state.claimsInProgress.get(owner) === settled) {
|
|
435
|
-
this.#state.claimsInProgress.delete(owner);
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
return claimed;
|
|
528
|
+
/** Mongo claims nothing for a limit of 0 or below (a limit that is not a number is tried). */
|
|
529
|
+
claimsNothing(request) {
|
|
530
|
+
return request.handlers.length === 0 || request.limit <= 0;
|
|
439
531
|
}
|
|
440
532
|
/**
|
|
441
533
|
* Runs one claim and remembers what it hands out. When the claim fails at any
|
|
@@ -445,17 +537,17 @@ var MongoClaims = class {
|
|
|
445
537
|
* handed out and seen settled, is taken out of them, so nothing stays listed
|
|
446
538
|
* as running that nobody runs.
|
|
447
539
|
*/
|
|
448
|
-
async
|
|
540
|
+
async claimInTurn(request) {
|
|
449
541
|
const owner = request.leaseOwner;
|
|
450
542
|
if (this.#state.unreconciledOwners.has(owner)) {
|
|
451
|
-
await this.#
|
|
543
|
+
await this.#reconciler.reconcile(owner);
|
|
452
544
|
this.#state.unreconciledOwners.delete(owner);
|
|
453
545
|
}
|
|
454
546
|
try {
|
|
455
547
|
const claimed = await this.#claimNow(request);
|
|
456
548
|
for (const delivery of claimed) {
|
|
457
549
|
const { laneId, epoch } = parseLeaseToken(delivery.leaseToken);
|
|
458
|
-
this.#state.handedOut.
|
|
550
|
+
this.#state.handedOut.register(delivery, {
|
|
459
551
|
deliveryId: delivery.deliveryId,
|
|
460
552
|
owner: request.leaseOwner,
|
|
461
553
|
laneId,
|
|
@@ -464,80 +556,15 @@ var MongoClaims = class {
|
|
|
464
556
|
}
|
|
465
557
|
return claimed;
|
|
466
558
|
} catch (error) {
|
|
467
|
-
const [reconciled] = await Promise.allSettled([this.#
|
|
559
|
+
const [reconciled] = await Promise.allSettled([this.#reconciler.reconcile(owner)]);
|
|
468
560
|
if (reconciled.status === "rejected") {
|
|
469
561
|
this.#state.unreconciledOwners.add(owner);
|
|
470
562
|
}
|
|
471
563
|
throw error;
|
|
472
564
|
}
|
|
473
565
|
}
|
|
474
|
-
/**
|
|
475
|
-
* Takes every delivery this instance does not run out of the lanes `owner`
|
|
476
|
-
* holds on it: every listed delivery whose current claim (lane epoch and
|
|
477
|
-
* claim generation now) was not handed out by this instance, or was settled.
|
|
478
|
-
* One transaction, and the lanes are written first under their epoch and
|
|
479
|
-
* holder: a worker taking a lane over meanwhile conflicts with it, so its
|
|
480
|
-
* claim is never touched. The claim generation of every delivery taken out
|
|
481
|
-
* moves on, so no lease of those claims (a completion that failed and is
|
|
482
|
-
* retried later, say) can ever match a later claim of the same delivery.
|
|
483
|
-
* Registrations of this owner that are not the current claim of a listed
|
|
484
|
-
* delivery are dropped.
|
|
485
|
-
*/
|
|
486
|
-
async #reconcileLanes(owner) {
|
|
487
|
-
await runInTransaction(this.#state, async (session) => {
|
|
488
|
-
const held = await this.#state.lanes.find({ owner, holder: this.#state.instanceId }, { session }).toArray();
|
|
489
|
-
const listed = held.flatMap((lane) => lane.inFlight);
|
|
490
|
-
const claimsById = new Map(
|
|
491
|
-
(await this.#state.deliveries.find(
|
|
492
|
-
{ _id: { $in: listed }, status: "pending" },
|
|
493
|
-
{ session, projection: { claims: 1 } }
|
|
494
|
-
).toArray()).map((document) => [document._id, document.claims ?? 0])
|
|
495
|
-
);
|
|
496
|
-
const current = /* @__PURE__ */ new Set();
|
|
497
|
-
const unclaimed = held.map((lane) => ({
|
|
498
|
-
lane,
|
|
499
|
-
ids: lane.inFlight.filter((id) => {
|
|
500
|
-
const key = leaseKey(
|
|
501
|
-
id,
|
|
502
|
-
formatLeaseToken(lane._id, { epoch: lane.epoch, claims: claimsById.get(id) ?? 0 })
|
|
503
|
-
);
|
|
504
|
-
current.add(key);
|
|
505
|
-
return !this.#state.handedOut.has(key);
|
|
506
|
-
})
|
|
507
|
-
}));
|
|
508
|
-
for (const [key, entry] of this.#state.handedOut) {
|
|
509
|
-
if (entry.owner === owner && !current.has(key)) {
|
|
510
|
-
this.#state.handedOut.delete(key);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
if (held.length === 0) {
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
await this.#state.lanes.bulkWrite(
|
|
517
|
-
unclaimed.map(({ lane, ids }) => ({
|
|
518
|
-
updateOne: {
|
|
519
|
-
filter: { _id: lane._id, epoch: lane.epoch, holder: this.#state.instanceId },
|
|
520
|
-
update: { $pull: { inFlight: { $in: ids } }, $inc: { revision: 1 } }
|
|
521
|
-
}
|
|
522
|
-
})),
|
|
523
|
-
{ session, ordered: false }
|
|
524
|
-
);
|
|
525
|
-
await this.#state.deliveries.updateMany(
|
|
526
|
-
{ _id: { $in: unclaimed.flatMap((entry) => entry.ids) }, status: "pending" },
|
|
527
|
-
{ $inc: { claims: 1 } },
|
|
528
|
-
{ session }
|
|
529
|
-
);
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
566
|
async #claimNow(request) {
|
|
533
|
-
const
|
|
534
|
-
this.#state.lanes.find({
|
|
535
|
-
owner: request.leaseOwner,
|
|
536
|
-
holder: this.#state.instanceId,
|
|
537
|
-
leaseExpiresAt: { $gt: request.now }
|
|
538
|
-
}).toArray(),
|
|
539
|
-
this.#ordering.unparkNextDeliveries(request.handlers)
|
|
540
|
-
]);
|
|
567
|
+
const held = await this.#readHeldLanes(request);
|
|
541
568
|
await this.#laneLeases.chargeLost(held);
|
|
542
569
|
const scope = scopeOf(held);
|
|
543
570
|
const running = this.#loadRunning(held);
|
|
@@ -557,17 +584,19 @@ var MongoClaims = class {
|
|
|
557
584
|
scope
|
|
558
585
|
);
|
|
559
586
|
await this.#ordering.parkWaiting(scan.waitingBacklogs(PARK_BACKLOG_THRESHOLD, handedBack));
|
|
560
|
-
return claimed
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
587
|
+
return toClaimedDeliveries(claimed, { scope, events, request });
|
|
588
|
+
}
|
|
589
|
+
/** The lanes the owner holds on this instance, read while the next deliveries are unparked. */
|
|
590
|
+
async #readHeldLanes(request) {
|
|
591
|
+
const [held] = await Promise.all([
|
|
592
|
+
this.#state.lanes.find({
|
|
593
|
+
owner: request.leaseOwner,
|
|
594
|
+
holder: this.#state.instanceId,
|
|
595
|
+
leaseExpiresAt: { $gt: request.now }
|
|
596
|
+
}).toArray(),
|
|
597
|
+
this.#ordering.unparkNextDeliveries(request.handlers)
|
|
598
|
+
]);
|
|
599
|
+
return held;
|
|
571
600
|
}
|
|
572
601
|
/** The deliveries still running in the held lanes, with their (handler, key) identities. */
|
|
573
602
|
#loadRunning(held) {
|
|
@@ -629,14 +658,12 @@ var MongoClaims = class {
|
|
|
629
658
|
#forgetSettled(held, { runningIds, owner }) {
|
|
630
659
|
const gone = new Set(
|
|
631
660
|
held.flatMap(
|
|
632
|
-
(lane) => lane.inFlight.filter((id) => !runningIds.has(id)).map((id) => `${
|
|
661
|
+
(lane) => lane.inFlight.filter((id) => !runningIds.has(id)).map((id) => internal.CompositeKey.of(id, `${lane._id}:${lane.epoch}`))
|
|
633
662
|
)
|
|
634
663
|
);
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
}
|
|
664
|
+
this.#state.handedOut.forgetWhere(
|
|
665
|
+
(entry) => entry.owner === owner && gone.has(internal.CompositeKey.of(entry.deliveryId, `${entry.laneId}:${entry.epoch}`))
|
|
666
|
+
);
|
|
640
667
|
}
|
|
641
668
|
/**
|
|
642
669
|
* Gives back the recorded keyed picks that a delivery requeued during the
|
|
@@ -698,20 +725,34 @@ var MongoClaims = class {
|
|
|
698
725
|
);
|
|
699
726
|
}
|
|
700
727
|
};
|
|
701
|
-
|
|
702
|
-
// src/mongo-completion.ts
|
|
703
728
|
var FINISHED_FIELDS = { dueAt: "", lane: "" };
|
|
704
|
-
var MongoCompletion = class {
|
|
729
|
+
var MongoCompletion = class extends internal.LeaseSettlement {
|
|
705
730
|
#state;
|
|
706
731
|
constructor(state) {
|
|
732
|
+
super();
|
|
707
733
|
this.#state = state;
|
|
708
734
|
}
|
|
709
|
-
/** Implements {@link MongoEventStore.
|
|
710
|
-
async
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
return false;
|
|
735
|
+
/** Implements {@link MongoEventStore.complete}. */
|
|
736
|
+
async complete(lease, { now, session }) {
|
|
737
|
+
if (session !== void 0) {
|
|
738
|
+
assertInTransaction(session, "complete");
|
|
714
739
|
}
|
|
740
|
+
return (await this.#completeLeases([lease], { now, session }))[0];
|
|
741
|
+
}
|
|
742
|
+
/** Implements {@link MongoEventStore.completeMany}. */
|
|
743
|
+
completeMany(leases, now) {
|
|
744
|
+
return this.#completeLeases(leases, { now, session: void 0 });
|
|
745
|
+
}
|
|
746
|
+
/** A lease this store issued; any other token cannot be held. */
|
|
747
|
+
findHeld(lease) {
|
|
748
|
+
return parseLeaseToken(lease.leaseToken);
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Renews the lane lease of a running claim. One transaction: a stale lease must not extend the
|
|
752
|
+
* lane of a newer claim of the delivery. A claim ending or starting meanwhile writes the delivery
|
|
753
|
+
* or the lane, so the two conflict.
|
|
754
|
+
*/
|
|
755
|
+
writeExtension(held, { lease, leaseExpiresAt }) {
|
|
715
756
|
const deliveryId = String(lease.deliveryId);
|
|
716
757
|
return runInTransaction(this.#state, async (session) => {
|
|
717
758
|
const running = await this.#state.deliveries.findOne(ofClaim({ ...held, deliveryId }), {
|
|
@@ -729,41 +770,26 @@ var MongoCompletion = class {
|
|
|
729
770
|
return renewed.matchedCount === 1;
|
|
730
771
|
});
|
|
731
772
|
}
|
|
732
|
-
/**
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
}
|
|
743
|
-
/** Implements {@link MongoEventStore.reschedule}. */
|
|
744
|
-
async reschedule(lease, {
|
|
745
|
-
availableAt,
|
|
746
|
-
error,
|
|
747
|
-
now
|
|
748
|
-
}) {
|
|
749
|
-
return this.#leaveFlight(lease, {
|
|
750
|
-
$set: { availableAt, dueAt: availableAt, lastError: toErrorInfo(error), updatedAt: now },
|
|
751
|
-
$inc: { attempts: 1, claims: 1 }
|
|
773
|
+
/**
|
|
774
|
+
* Applies the change to the running delivery and takes it out of its lane's in-flight list, if
|
|
775
|
+
* the lane still has the lease's epoch.
|
|
776
|
+
*/
|
|
777
|
+
writeChange(held, { lease, change }) {
|
|
778
|
+
const deliveryId = String(lease.deliveryId);
|
|
779
|
+
return this.#leaveFlightNow(held, {
|
|
780
|
+
deliveryId,
|
|
781
|
+
ofThisClaim: ofClaim({ ...held, deliveryId }),
|
|
782
|
+
update: updateFor(change)
|
|
752
783
|
});
|
|
753
784
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
return this.#leaveFlight(lease, {
|
|
757
|
-
$set: { availableAt: now, dueAt: now, updatedAt: now },
|
|
758
|
-
$inc: { claims: 1 }
|
|
759
|
-
});
|
|
785
|
+
forgetHandedOut(lease) {
|
|
786
|
+
this.#forget(lease);
|
|
760
787
|
}
|
|
761
|
-
/**
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
$inc: { attempts: 1, claims: 1 }
|
|
788
|
+
/** Forgets a lease as handed out: its claim ended. */
|
|
789
|
+
#forget(lease) {
|
|
790
|
+
this.#state.handedOut.forget({
|
|
791
|
+
deliveryId: String(lease.deliveryId),
|
|
792
|
+
leaseToken: String(lease.leaseToken)
|
|
767
793
|
});
|
|
768
794
|
}
|
|
769
795
|
/**
|
|
@@ -775,7 +801,7 @@ var MongoCompletion = class {
|
|
|
775
801
|
async #settle(leases, operation) {
|
|
776
802
|
const result = await operation();
|
|
777
803
|
for (const lease of leases) {
|
|
778
|
-
this.#
|
|
804
|
+
this.#forget(lease);
|
|
779
805
|
}
|
|
780
806
|
return result;
|
|
781
807
|
}
|
|
@@ -902,23 +928,7 @@ var MongoCompletion = class {
|
|
|
902
928
|
session === void 0 ? {} : { session }
|
|
903
929
|
);
|
|
904
930
|
}
|
|
905
|
-
/**
|
|
906
|
-
* Applies `update` to a running delivery and takes it out of its lane's
|
|
907
|
-
* in-flight list, if the lane still has the lease's epoch. One transaction:
|
|
908
|
-
* the lane write fences the change against a takeover.
|
|
909
|
-
*/
|
|
910
|
-
async #leaveFlight(lease, update) {
|
|
911
|
-
const held = parseLeaseToken(lease.leaseToken);
|
|
912
|
-
if (held === void 0) {
|
|
913
|
-
return false;
|
|
914
|
-
}
|
|
915
|
-
const deliveryId = String(lease.deliveryId);
|
|
916
|
-
const ofThisClaim = ofClaim({ ...held, deliveryId });
|
|
917
|
-
return this.#settle(
|
|
918
|
-
[lease],
|
|
919
|
-
() => this.#leaveFlightNow(held, { deliveryId, ofThisClaim, update })
|
|
920
|
-
);
|
|
921
|
-
}
|
|
931
|
+
/** One transaction: the lane write fences the change against a takeover. */
|
|
922
932
|
async #leaveFlightNow(held, {
|
|
923
933
|
deliveryId,
|
|
924
934
|
ofThisClaim,
|
|
@@ -959,7 +969,28 @@ function ofClaim(lease) {
|
|
|
959
969
|
return { _id: lease.deliveryId, status: "pending", claims: atCount(lease.claims) };
|
|
960
970
|
}
|
|
961
971
|
function leaseIdentity(lease) {
|
|
962
|
-
return
|
|
972
|
+
return internal.DeliveryLeaseIdentity.of(
|
|
973
|
+
lease.deliveryId,
|
|
974
|
+
formatLeaseToken(lease.laneId, { epoch: lease.epoch, claims: lease.claims })
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
function updateFor(change) {
|
|
978
|
+
if (change.kind === "reschedule") {
|
|
979
|
+
const { availableAt, error, now } = change;
|
|
980
|
+
return {
|
|
981
|
+
$set: { availableAt, dueAt: availableAt, lastError: toErrorInfo(error), updatedAt: now },
|
|
982
|
+
$inc: { attempts: 1, claims: 1 }
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
if (change.kind === "release") {
|
|
986
|
+
const { now } = change;
|
|
987
|
+
return { $set: { availableAt: now, dueAt: now, updatedAt: now }, $inc: { claims: 1 } };
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
$set: { status: "dead", lastError: toErrorInfo(change.error), updatedAt: change.now },
|
|
991
|
+
$unset: FINISHED_FIELDS,
|
|
992
|
+
$inc: { attempts: 1, claims: 1 }
|
|
993
|
+
};
|
|
963
994
|
}
|
|
964
995
|
|
|
965
996
|
// src/mongo-lane-leases.ts
|
|
@@ -1108,10 +1139,36 @@ var MongoLaneLeases = class {
|
|
|
1108
1139
|
request
|
|
1109
1140
|
}) {
|
|
1110
1141
|
const picksByLane = /* @__PURE__ */ new Map();
|
|
1111
|
-
const handedBack = /* @__PURE__ */ new Set();
|
|
1112
1142
|
for (const document of picked) {
|
|
1113
1143
|
picksByLane.set(document.laneId, [...picksByLane.get(document.laneId) ?? [], document._id]);
|
|
1114
1144
|
}
|
|
1145
|
+
const { operations, handedBack } = this.#planLaneRecords(scope, {
|
|
1146
|
+
picksByLane,
|
|
1147
|
+
runningIds,
|
|
1148
|
+
request
|
|
1149
|
+
});
|
|
1150
|
+
if (operations.length === 0) {
|
|
1151
|
+
return { recorded: /* @__PURE__ */ new Set(), handedBack };
|
|
1152
|
+
}
|
|
1153
|
+
const result = await this.#state.lanes.bulkWrite(operations, { ordered: false });
|
|
1154
|
+
if (result.matchedCount === operations.length) {
|
|
1155
|
+
return { recorded: new Set(picksByLane.keys()), handedBack };
|
|
1156
|
+
}
|
|
1157
|
+
return {
|
|
1158
|
+
recorded: await this.#readRecorded(scope, { laneIds: [...picksByLane.keys()], request }),
|
|
1159
|
+
handedBack
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* One conditional write per lane of the claim: a picked lane records its picks, a lane that
|
|
1164
|
+
* yielded nothing and runs nothing is freed (and reported as handed back).
|
|
1165
|
+
*/
|
|
1166
|
+
#planLaneRecords(scope, {
|
|
1167
|
+
picksByLane,
|
|
1168
|
+
runningIds,
|
|
1169
|
+
request
|
|
1170
|
+
}) {
|
|
1171
|
+
const handedBack = /* @__PURE__ */ new Set();
|
|
1115
1172
|
const operations = [];
|
|
1116
1173
|
for (const lane of scope.values()) {
|
|
1117
1174
|
const gone = lane.listed.filter((id) => !runningIds.has(id));
|
|
@@ -1127,17 +1184,7 @@ var MongoLaneLeases = class {
|
|
|
1127
1184
|
handedBack.add(lane.laneId);
|
|
1128
1185
|
}
|
|
1129
1186
|
}
|
|
1130
|
-
|
|
1131
|
-
return { recorded: /* @__PURE__ */ new Set(), handedBack };
|
|
1132
|
-
}
|
|
1133
|
-
const result = await this.#state.lanes.bulkWrite(operations, { ordered: false });
|
|
1134
|
-
if (result.matchedCount === operations.length) {
|
|
1135
|
-
return { recorded: new Set(picksByLane.keys()), handedBack };
|
|
1136
|
-
}
|
|
1137
|
-
return {
|
|
1138
|
-
recorded: await this.#readRecorded(scope, { laneIds: [...picksByLane.keys()], request }),
|
|
1139
|
-
handedBack
|
|
1140
|
-
};
|
|
1187
|
+
return { operations, handedBack };
|
|
1141
1188
|
}
|
|
1142
1189
|
/** The lanes of `laneIds` whose record of this claim applied, read back after a partial bulk write. */
|
|
1143
1190
|
async #readRecorded(scope, { laneIds, request }) {
|
|
@@ -1255,30 +1302,11 @@ var MongoOrdering = class {
|
|
|
1255
1302
|
async pickCandidates(input) {
|
|
1256
1303
|
const { lanes, request, scan, running, picked } = input;
|
|
1257
1304
|
const pageSize = Math.max((request.limit - picked.length) * 2, MINIMUM_CANDIDATE_BATCH_SIZE);
|
|
1258
|
-
const filter =
|
|
1259
|
-
laneId: { $in: lanes.map((lane) => lane.laneId) },
|
|
1260
|
-
handler: { $in: [...request.handlers] },
|
|
1261
|
-
lane: "ready",
|
|
1262
|
-
dueAt: { $lte: request.now },
|
|
1263
|
-
_id: { $nin: lanes.flatMap((lane) => lane.listed) }
|
|
1264
|
-
};
|
|
1305
|
+
const filter = candidateFilter(lanes, request);
|
|
1265
1306
|
let after;
|
|
1266
1307
|
for (; ; ) {
|
|
1267
1308
|
const [page, { keys }] = await Promise.all([
|
|
1268
|
-
this.#state.deliveries.find(
|
|
1269
|
-
after === void 0 ? filter : {
|
|
1270
|
-
$and: [
|
|
1271
|
-
filter,
|
|
1272
|
-
{
|
|
1273
|
-
$or: [
|
|
1274
|
-
{ dueAt: { $gt: after.dueAt } },
|
|
1275
|
-
{ dueAt: after.dueAt, _id: { $gt: after._id } }
|
|
1276
|
-
]
|
|
1277
|
-
}
|
|
1278
|
-
]
|
|
1279
|
-
},
|
|
1280
|
-
{ sort: { dueAt: 1, _id: 1 }, limit: pageSize }
|
|
1281
|
-
).toArray(),
|
|
1309
|
+
this.#state.deliveries.find(pageAfter(filter, after), { sort: { dueAt: 1, _id: 1 }, limit: pageSize }).toArray(),
|
|
1282
1310
|
running
|
|
1283
1311
|
]);
|
|
1284
1312
|
if (await this.#pickFromPage(page, { request, scan, picked, keys })) {
|
|
@@ -1394,17 +1422,7 @@ var MongoOrdering = class {
|
|
|
1394
1422
|
if (anyParked === null) {
|
|
1395
1423
|
return;
|
|
1396
1424
|
}
|
|
1397
|
-
const lowest =
|
|
1398
|
-
[...new Set(handlers)].map(
|
|
1399
|
-
(handler) => this.#state.deliveries.aggregate([
|
|
1400
|
-
{ $match: { handler, lane: "parked" } },
|
|
1401
|
-
{ $sort: { handler: 1, key: 1, sequence: 1 } },
|
|
1402
|
-
{
|
|
1403
|
-
$group: { _id: "$key", id: { $first: "$_id" }, sequence: { $first: "$sequence" } }
|
|
1404
|
-
}
|
|
1405
|
-
]).toArray().then((groups) => groups.map((group) => ({ ...group, handler })))
|
|
1406
|
-
)
|
|
1407
|
-
)).flat();
|
|
1425
|
+
const lowest = await this.#lowestParkedPerKey(handlers);
|
|
1408
1426
|
const next = await Promise.all(
|
|
1409
1427
|
lowest.map(
|
|
1410
1428
|
async (parked) => await this.#findUnfinishedPredecessor(parked.handler, {
|
|
@@ -1421,7 +1439,46 @@ var MongoOrdering = class {
|
|
|
1421
1439
|
);
|
|
1422
1440
|
}
|
|
1423
1441
|
}
|
|
1442
|
+
/**
|
|
1443
|
+
* The lowest parked delivery of every parked key of `handlers`, one aggregation per handler in
|
|
1444
|
+
* parallel.
|
|
1445
|
+
*/
|
|
1446
|
+
async #lowestParkedPerKey(handlers) {
|
|
1447
|
+
return (await Promise.all(
|
|
1448
|
+
[...new Set(handlers)].map(
|
|
1449
|
+
(handler) => this.#state.deliveries.aggregate([
|
|
1450
|
+
{ $match: { handler, lane: "parked" } },
|
|
1451
|
+
{ $sort: { handler: 1, key: 1, sequence: 1 } },
|
|
1452
|
+
{
|
|
1453
|
+
$group: { _id: "$key", id: { $first: "$_id" }, sequence: { $first: "$sequence" } }
|
|
1454
|
+
}
|
|
1455
|
+
]).toArray().then((groups) => groups.map((group) => ({ ...group, handler })))
|
|
1456
|
+
)
|
|
1457
|
+
)).flat();
|
|
1458
|
+
}
|
|
1424
1459
|
};
|
|
1460
|
+
function candidateFilter(lanes, request) {
|
|
1461
|
+
return {
|
|
1462
|
+
laneId: { $in: lanes.map((lane) => lane.laneId) },
|
|
1463
|
+
handler: { $in: [...request.handlers] },
|
|
1464
|
+
lane: "ready",
|
|
1465
|
+
dueAt: { $lte: request.now },
|
|
1466
|
+
_id: { $nin: lanes.flatMap((lane) => lane.listed) }
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
function pageAfter(filter, after) {
|
|
1470
|
+
return after === void 0 ? filter : {
|
|
1471
|
+
$and: [
|
|
1472
|
+
filter,
|
|
1473
|
+
{
|
|
1474
|
+
$or: [
|
|
1475
|
+
{ dueAt: { $gt: after.dueAt } },
|
|
1476
|
+
{ dueAt: after.dueAt, _id: { $gt: after._id } }
|
|
1477
|
+
]
|
|
1478
|
+
}
|
|
1479
|
+
]
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1425
1482
|
function isClaimable(candidate, { scan, blockedKeys }) {
|
|
1426
1483
|
const { key, sequence } = candidate;
|
|
1427
1484
|
if (key === void 0) {
|
|
@@ -1448,6 +1505,21 @@ var MongoPurge = class {
|
|
|
1448
1505
|
/** Implements {@link MongoEventStore.purge}. */
|
|
1449
1506
|
async purge(request) {
|
|
1450
1507
|
const total = { deletedDeliveries: 0, deletedEvents: 0 };
|
|
1508
|
+
const steps = this.#purgeSteps(request);
|
|
1509
|
+
const { completedBefore, deadBefore } = request;
|
|
1510
|
+
for (const step of steps) {
|
|
1511
|
+
const result = await step();
|
|
1512
|
+
total.deletedDeliveries += result.deletedDeliveries;
|
|
1513
|
+
total.deletedEvents += result.deletedEvents;
|
|
1514
|
+
}
|
|
1515
|
+
const orphanCutoff = [completedBefore, deadBefore].filter((cutoff) => cutoff !== void 0).sort((first, second) => first.getTime() - second.getTime())[0];
|
|
1516
|
+
if (orphanCutoff !== void 0) {
|
|
1517
|
+
total.deletedEvents += await this.#purgeOrphanEvents(orphanCutoff, completedBefore);
|
|
1518
|
+
}
|
|
1519
|
+
return total;
|
|
1520
|
+
}
|
|
1521
|
+
/** The purges the request asks for, in the order they run. */
|
|
1522
|
+
#purgeSteps(request) {
|
|
1451
1523
|
const steps = [];
|
|
1452
1524
|
const { completedBefore, deadBefore } = request;
|
|
1453
1525
|
if (completedBefore !== void 0) {
|
|
@@ -1474,16 +1546,7 @@ var MongoPurge = class {
|
|
|
1474
1546
|
})
|
|
1475
1547
|
);
|
|
1476
1548
|
}
|
|
1477
|
-
|
|
1478
|
-
const result = await step();
|
|
1479
|
-
total.deletedDeliveries += result.deletedDeliveries;
|
|
1480
|
-
total.deletedEvents += result.deletedEvents;
|
|
1481
|
-
}
|
|
1482
|
-
const orphanCutoff = [completedBefore, deadBefore].filter((cutoff) => cutoff !== void 0).sort((first, second) => first.getTime() - second.getTime())[0];
|
|
1483
|
-
if (orphanCutoff !== void 0) {
|
|
1484
|
-
total.deletedEvents += await this.#purgeOrphanEvents(orphanCutoff, completedBefore);
|
|
1485
|
-
}
|
|
1486
|
-
return total;
|
|
1549
|
+
return steps;
|
|
1487
1550
|
}
|
|
1488
1551
|
/**
|
|
1489
1552
|
* Deletes batches of matching documents (deliveries, or completion history
|
|
@@ -1501,35 +1564,10 @@ var MongoPurge = class {
|
|
|
1501
1564
|
let deletedDeliveries = 0;
|
|
1502
1565
|
let deletedEvents = 0;
|
|
1503
1566
|
for (; ; ) {
|
|
1504
|
-
const step = await runInTransaction(
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
}
|
|
1509
|
-
const eventIds = [...new Set(batch.map((document) => document.eventId))];
|
|
1510
|
-
const deleted = await collection.deleteMany(
|
|
1511
|
-
{ _id: { $in: batch.map((document) => document._id) } },
|
|
1512
|
-
{ session }
|
|
1513
|
-
);
|
|
1514
|
-
const stillReferenced = await this.#referencedEventIds(eventIds, session);
|
|
1515
|
-
const events = await this.#state.events.deleteMany(
|
|
1516
|
-
{
|
|
1517
|
-
_id: { $in: eventIds.filter((eventId) => !stillReferenced.has(eventId)) },
|
|
1518
|
-
// An event a delivery of which completed stays as deduplication record until
|
|
1519
|
-
// `completedBefore` passes its last completion, whatever this batch purged.
|
|
1520
|
-
$or: [
|
|
1521
|
-
{ completedAt: { $exists: false } },
|
|
1522
|
-
...completedBefore === void 0 ? [] : [{ completedAt: { $lt: completedBefore } }]
|
|
1523
|
-
]
|
|
1524
|
-
},
|
|
1525
|
-
{ session }
|
|
1526
|
-
);
|
|
1527
|
-
return {
|
|
1528
|
-
batchSize: batch.length,
|
|
1529
|
-
deletedDeliveries: deleted.deletedCount,
|
|
1530
|
-
deletedEvents: events.deletedCount
|
|
1531
|
-
};
|
|
1532
|
-
});
|
|
1567
|
+
const step = await runInTransaction(
|
|
1568
|
+
this.#state,
|
|
1569
|
+
(session) => this.#purgeBatch(collection, { filter, completedBefore, session })
|
|
1570
|
+
);
|
|
1533
1571
|
deletedDeliveries += step.deletedDeliveries;
|
|
1534
1572
|
deletedEvents += step.deletedEvents;
|
|
1535
1573
|
if (step.batchSize < this.#state.purgeBatchSize) {
|
|
@@ -1537,6 +1575,40 @@ var MongoPurge = class {
|
|
|
1537
1575
|
}
|
|
1538
1576
|
}
|
|
1539
1577
|
}
|
|
1578
|
+
/** One batch of {@link MongoPurge.#purgeMatching}, inside the caller's transaction. */
|
|
1579
|
+
async #purgeBatch(collection, {
|
|
1580
|
+
filter,
|
|
1581
|
+
completedBefore,
|
|
1582
|
+
session
|
|
1583
|
+
}) {
|
|
1584
|
+
const batch = await collection.find(filter, { session, projection: { eventId: 1 }, limit: this.#state.purgeBatchSize }).toArray();
|
|
1585
|
+
if (batch.length === 0) {
|
|
1586
|
+
return { batchSize: 0, deletedDeliveries: 0, deletedEvents: 0 };
|
|
1587
|
+
}
|
|
1588
|
+
const eventIds = [...new Set(batch.map((document) => document.eventId))];
|
|
1589
|
+
const deleted = await collection.deleteMany(
|
|
1590
|
+
{ _id: { $in: batch.map((document) => document._id) } },
|
|
1591
|
+
{ session }
|
|
1592
|
+
);
|
|
1593
|
+
const stillReferenced = await this.#referencedEventIds(eventIds, session);
|
|
1594
|
+
const events = await this.#state.events.deleteMany(
|
|
1595
|
+
{
|
|
1596
|
+
_id: { $in: eventIds.filter((eventId) => !stillReferenced.has(eventId)) },
|
|
1597
|
+
// An event a delivery of which completed stays as deduplication record until
|
|
1598
|
+
// `completedBefore` passes its last completion, whatever this batch purged.
|
|
1599
|
+
$or: [
|
|
1600
|
+
{ completedAt: { $exists: false } },
|
|
1601
|
+
...completedBefore === void 0 ? [] : [{ completedAt: { $lt: completedBefore } }]
|
|
1602
|
+
]
|
|
1603
|
+
},
|
|
1604
|
+
{ session }
|
|
1605
|
+
);
|
|
1606
|
+
return {
|
|
1607
|
+
batchSize: batch.length,
|
|
1608
|
+
deletedDeliveries: deleted.deletedCount,
|
|
1609
|
+
deletedEvents: events.deletedCount
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1540
1612
|
/** A view of a collection that purge deletes from: deliveries or completion history records. */
|
|
1541
1613
|
#purgeable(name) {
|
|
1542
1614
|
return this.#state.database.collection(name, {
|
|
@@ -1570,33 +1642,12 @@ var MongoPurge = class {
|
|
|
1570
1642
|
for (; ; ) {
|
|
1571
1643
|
const step = await runInTransaction(this.#state, async (session) => {
|
|
1572
1644
|
const orphans = await this.#state.events.aggregate(
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
// Completed: by the time of its last completion, and only under the completed cutoff.
|
|
1580
|
-
...completedBefore === void 0 ? [] : [{ completedAt: { $lt: completedBefore } }]
|
|
1581
|
-
]
|
|
1582
|
-
}
|
|
1583
|
-
},
|
|
1584
|
-
{ $sort: { createdAt: 1, _id: 1 } },
|
|
1585
|
-
...references.flatMap((from) => [
|
|
1586
|
-
{
|
|
1587
|
-
$lookup: {
|
|
1588
|
-
from,
|
|
1589
|
-
localField: "_id",
|
|
1590
|
-
foreignField: "eventId",
|
|
1591
|
-
pipeline: [{ $limit: 1 }, { $project: { _id: 1 } }],
|
|
1592
|
-
as: "references"
|
|
1593
|
-
}
|
|
1594
|
-
},
|
|
1595
|
-
{ $match: { references: { $size: 0 } } }
|
|
1596
|
-
]),
|
|
1597
|
-
{ $limit: this.#state.purgeBatchSize },
|
|
1598
|
-
{ $project: { _id: 1 } }
|
|
1599
|
-
],
|
|
1645
|
+
orphanPipeline({
|
|
1646
|
+
cutoff,
|
|
1647
|
+
completedBefore,
|
|
1648
|
+
references,
|
|
1649
|
+
limit: this.#state.purgeBatchSize
|
|
1650
|
+
}),
|
|
1600
1651
|
{ session }
|
|
1601
1652
|
).toArray();
|
|
1602
1653
|
const deleted = await this.#state.events.deleteMany(
|
|
@@ -1612,6 +1663,36 @@ var MongoPurge = class {
|
|
|
1612
1663
|
}
|
|
1613
1664
|
}
|
|
1614
1665
|
};
|
|
1666
|
+
function orphanPipeline(input) {
|
|
1667
|
+
const { cutoff, completedBefore, references, limit } = input;
|
|
1668
|
+
return [
|
|
1669
|
+
{
|
|
1670
|
+
$match: {
|
|
1671
|
+
$or: [
|
|
1672
|
+
// Never completed (no subscribers, or every delivery dead): by age.
|
|
1673
|
+
{ completedAt: { $exists: false }, createdAt: { $lt: cutoff } },
|
|
1674
|
+
// Completed: by the time of its last completion, and only under the completed cutoff.
|
|
1675
|
+
...completedBefore === void 0 ? [] : [{ completedAt: { $lt: completedBefore } }]
|
|
1676
|
+
]
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
{ $sort: { createdAt: 1, _id: 1 } },
|
|
1680
|
+
...references.flatMap((from) => [
|
|
1681
|
+
{
|
|
1682
|
+
$lookup: {
|
|
1683
|
+
from,
|
|
1684
|
+
localField: "_id",
|
|
1685
|
+
foreignField: "eventId",
|
|
1686
|
+
pipeline: [{ $limit: 1 }, { $project: { _id: 1 } }],
|
|
1687
|
+
as: "references"
|
|
1688
|
+
}
|
|
1689
|
+
},
|
|
1690
|
+
{ $match: { references: { $size: 0 } } }
|
|
1691
|
+
]),
|
|
1692
|
+
{ $limit: limit },
|
|
1693
|
+
{ $project: { _id: 1 } }
|
|
1694
|
+
];
|
|
1695
|
+
}
|
|
1615
1696
|
|
|
1616
1697
|
// src/mongo-queries.ts
|
|
1617
1698
|
var DEFAULT_LIST_LIMIT = 100;
|
|
@@ -1627,14 +1708,7 @@ var MongoQueries = class {
|
|
|
1627
1708
|
const limit = query.limit ?? DEFAULT_LIST_LIMIT;
|
|
1628
1709
|
const lanes = await this.#state.lanes.find({ "inFlight.0": { $exists: true } }, { projection: { epoch: 0 } }).toArray();
|
|
1629
1710
|
const listed = lanes.flatMap((lane) => lane.inFlight);
|
|
1630
|
-
const
|
|
1631
|
-
["processing", { status: "pending", _id: { $in: listed } }],
|
|
1632
|
-
["pending", { status: "pending", _id: { $nin: listed } }]
|
|
1633
|
-
]);
|
|
1634
|
-
const filter = {
|
|
1635
|
-
...common,
|
|
1636
|
-
...status === void 0 ? {} : derivedStatuses.get(status) ?? { status }
|
|
1637
|
-
};
|
|
1711
|
+
const filter = { ...common, ...statusFilter(status, listed) };
|
|
1638
1712
|
const sort = { createdAt: 1, _id: 1 };
|
|
1639
1713
|
const documents = await this.#state.deliveries.find(filter, { sort, limit }).toArray();
|
|
1640
1714
|
const history = this.#state.completionHistory && (status === void 0 || status === "completed") ? await this.#state.completions.find(common, { sort, limit }).toArray() : [];
|
|
@@ -1642,23 +1716,7 @@ var MongoQueries = class {
|
|
|
1642
1716
|
...documents.map((document) => document.eventId),
|
|
1643
1717
|
...history.map((document) => document.eventId)
|
|
1644
1718
|
]);
|
|
1645
|
-
|
|
1646
|
-
const records = [
|
|
1647
|
-
...documents.map((document) => {
|
|
1648
|
-
const lane = laneById.get(document.laneId);
|
|
1649
|
-
const runs = document.status === "pending" && lane?.inFlight.includes(document._id) === true;
|
|
1650
|
-
return toDeliveryRecord(document, {
|
|
1651
|
-
event: events.get(document.eventId),
|
|
1652
|
-
runningIn: runs ? lane : void 0
|
|
1653
|
-
});
|
|
1654
|
-
}),
|
|
1655
|
-
...history.map(
|
|
1656
|
-
(document) => toCompletedDeliveryRecord(document, events.get(document.eventId))
|
|
1657
|
-
)
|
|
1658
|
-
];
|
|
1659
|
-
return records.sort(
|
|
1660
|
-
(left, right) => left.createdAt.getTime() - right.createdAt.getTime() || compareText(left.id, right.id)
|
|
1661
|
-
).slice(0, limit);
|
|
1719
|
+
return toSortedRecords({ documents, history, events, lanes }).slice(0, limit);
|
|
1662
1720
|
}
|
|
1663
1721
|
/** Implements {@link MongoEventStore.requeue}. */
|
|
1664
1722
|
async requeue(deliveryIds, now) {
|
|
@@ -1713,7 +1771,7 @@ var MongoQueries = class {
|
|
|
1713
1771
|
return [];
|
|
1714
1772
|
}
|
|
1715
1773
|
return this.#state.deliveries.find(
|
|
1716
|
-
{ _id: { $in:
|
|
1774
|
+
{ _id: { $in: listed }, status: "pending" },
|
|
1717
1775
|
{ projection: { handler: 1, key: 1, laneId: 1 } }
|
|
1718
1776
|
).toArray();
|
|
1719
1777
|
}
|
|
@@ -1722,6 +1780,36 @@ var MongoQueries = class {
|
|
|
1722
1780
|
return new Map(documents.map((document) => [document._id, toEnvelope(document)]));
|
|
1723
1781
|
}
|
|
1724
1782
|
};
|
|
1783
|
+
function statusFilter(status, listed) {
|
|
1784
|
+
if (status === void 0) {
|
|
1785
|
+
return {};
|
|
1786
|
+
}
|
|
1787
|
+
const derivedStatuses = /* @__PURE__ */ new Map([
|
|
1788
|
+
["processing", { status: "pending", _id: { $in: listed } }],
|
|
1789
|
+
["pending", { status: "pending", _id: { $nin: listed } }]
|
|
1790
|
+
]);
|
|
1791
|
+
return derivedStatuses.get(status) ?? { status };
|
|
1792
|
+
}
|
|
1793
|
+
function toSortedRecords(input) {
|
|
1794
|
+
const { documents, history, events, lanes } = input;
|
|
1795
|
+
const laneById = new Map(lanes.map((lane) => [lane._id, lane]));
|
|
1796
|
+
const records = [
|
|
1797
|
+
...documents.map((document) => {
|
|
1798
|
+
const lane = laneById.get(document.laneId);
|
|
1799
|
+
const runs = document.status === "pending" && lane?.inFlight.includes(document._id) === true;
|
|
1800
|
+
return toDeliveryRecord(document, {
|
|
1801
|
+
event: events.get(document.eventId),
|
|
1802
|
+
runningIn: runs ? lane : void 0
|
|
1803
|
+
});
|
|
1804
|
+
}),
|
|
1805
|
+
...history.map(
|
|
1806
|
+
(document) => toCompletedDeliveryRecord(document, events.get(document.eventId))
|
|
1807
|
+
)
|
|
1808
|
+
];
|
|
1809
|
+
return records.sort(
|
|
1810
|
+
(left, right) => left.createdAt.getTime() - right.createdAt.getTime() || compareText(left.id, right.id)
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1725
1813
|
function compareText(left, right) {
|
|
1726
1814
|
return Number(left > right) - Number(left < right);
|
|
1727
1815
|
}
|
|
@@ -1771,54 +1859,10 @@ var MongoSchema = class {
|
|
|
1771
1859
|
}
|
|
1772
1860
|
/** Creates the indexes of every collection; existing indexes are left as they are. */
|
|
1773
1861
|
async #createIndexes() {
|
|
1774
|
-
await this.#state.deliveries.createIndexes(
|
|
1775
|
-
|
|
1776
|
-
{ key: { eventId: 1, handler: 1 }, name: "event_handler", unique: true },
|
|
1777
|
-
// Claim: due work of a lane in due order. Holds ready deliveries only. The handler is
|
|
1778
|
-
// last, so a query over many lanes and handlers stays a merge of one scan per lane.
|
|
1779
|
-
{ key: CLAIM_INDEX_KEY, name: "claim", partialFilterExpression: { lane: "ready" } },
|
|
1780
|
-
// Ordering: unfinished predecessors of a keyed delivery, parked or not.
|
|
1781
|
-
{
|
|
1782
|
-
key: { handler: 1, key: 1, sequence: 1 },
|
|
1783
|
-
name: "ordering",
|
|
1784
|
-
partialFilterExpression: { dueAt: { $exists: true }, sequence: { $exists: true } }
|
|
1785
|
-
},
|
|
1786
|
-
// Parked deliveries: the lowest parked delivery of each key, to move it back once it is next.
|
|
1787
|
-
{
|
|
1788
|
-
key: { handler: 1, key: 1, sequence: 1 },
|
|
1789
|
-
name: "parked",
|
|
1790
|
-
partialFilterExpression: { lane: "parked" }
|
|
1791
|
-
},
|
|
1792
|
-
// Statistics: oldest due delivery across handlers.
|
|
1793
|
-
{ key: { dueAt: 1 }, name: "due", partialFilterExpression: { dueAt: { $exists: true } } },
|
|
1794
|
-
// Purge of completed deliveries left by 0.1.0.
|
|
1795
|
-
{
|
|
1796
|
-
key: { completedAt: 1 },
|
|
1797
|
-
name: "completed",
|
|
1798
|
-
partialFilterExpression: { completedAt: { $exists: true } }
|
|
1799
|
-
},
|
|
1800
|
-
// Listing and status counts.
|
|
1801
|
-
{ key: { status: 1, createdAt: 1 }, name: "status_created" },
|
|
1802
|
-
// Purge of dead deliveries.
|
|
1803
|
-
{ key: { status: 1, updatedAt: 1 }, name: "status_updated" },
|
|
1804
|
-
{ key: { handler: 1, createdAt: 1 }, name: "handler_created" },
|
|
1805
|
-
{ key: { eventType: 1, createdAt: 1 }, name: "type_created" }
|
|
1806
|
-
]);
|
|
1807
|
-
await this.#state.events.createIndexes([
|
|
1808
|
-
{ key: { createdAt: 1, _id: 1 }, name: "created" },
|
|
1809
|
-
// Purge of events whose deliveries all completed: they are kept until `completedBefore` passes this.
|
|
1810
|
-
{
|
|
1811
|
-
key: { completedAt: 1, _id: 1 },
|
|
1812
|
-
name: "completed",
|
|
1813
|
-
partialFilterExpression: { completedAt: { $exists: true } }
|
|
1814
|
-
}
|
|
1815
|
-
]);
|
|
1862
|
+
await this.#state.deliveries.createIndexes(deliveryIndexes());
|
|
1863
|
+
await this.#state.events.createIndexes(eventIndexes());
|
|
1816
1864
|
if (this.#state.completionHistory) {
|
|
1817
|
-
await this.#state.completions.createIndexes(
|
|
1818
|
-
{ key: { completedAt: 1, _id: 1 }, name: "completed" },
|
|
1819
|
-
{ key: { eventId: 1 }, name: "event" },
|
|
1820
|
-
{ key: { createdAt: 1, _id: 1 }, name: "created" }
|
|
1821
|
-
]);
|
|
1865
|
+
await this.#state.completions.createIndexes(completionIndexes());
|
|
1822
1866
|
}
|
|
1823
1867
|
}
|
|
1824
1868
|
async #createCollectionIfMissing(name) {
|
|
@@ -1881,36 +1925,29 @@ var MongoSchema = class {
|
|
|
1881
1925
|
{ projection: { occurredAt: 1 }, batchSize: UPGRADE_BATCH_SIZE }
|
|
1882
1926
|
),
|
|
1883
1927
|
async (events) => {
|
|
1884
|
-
const known =
|
|
1885
|
-
(await this.#state.deliveries.aggregate([
|
|
1886
|
-
{ $match: { eventId: { $in: events.map((event) => event._id) } } },
|
|
1887
|
-
{
|
|
1888
|
-
$group: {
|
|
1889
|
-
_id: "$eventId",
|
|
1890
|
-
handlers: { $addToSet: "$handler" },
|
|
1891
|
-
createdAt: { $min: "$createdAt" }
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
]).toArray()).map((group) => [group._id, group])
|
|
1895
|
-
);
|
|
1928
|
+
const known = await this.#legacyEventGroups(events.map((event) => event._id));
|
|
1896
1929
|
await this.#state.events.bulkWrite(
|
|
1897
|
-
events.map((event) =>
|
|
1898
|
-
const group = known.get(event._id);
|
|
1899
|
-
return {
|
|
1900
|
-
updateOne: {
|
|
1901
|
-
// Only an event nobody converted yet: another process may have finished and subscribed a handler.
|
|
1902
|
-
filter: { _id: event._id, handlers: { $exists: false } },
|
|
1903
|
-
update: {
|
|
1904
|
-
$set: group === void 0 ? { handlers: [], createdAt: event.occurredAt } : { handlers: group.handlers, createdAt: group.createdAt }
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
};
|
|
1908
|
-
}),
|
|
1930
|
+
events.map((event) => convertEvent(event, known.get(event._id))),
|
|
1909
1931
|
{ ordered: false }
|
|
1910
1932
|
);
|
|
1911
1933
|
}
|
|
1912
1934
|
);
|
|
1913
1935
|
}
|
|
1936
|
+
/** The handlers and first creation time of the deliveries of each of `eventIds`, by event id. */
|
|
1937
|
+
async #legacyEventGroups(eventIds) {
|
|
1938
|
+
return new Map(
|
|
1939
|
+
(await this.#state.deliveries.aggregate([
|
|
1940
|
+
{ $match: { eventId: { $in: [...eventIds] } } },
|
|
1941
|
+
{
|
|
1942
|
+
$group: {
|
|
1943
|
+
_id: "$eventId",
|
|
1944
|
+
handlers: { $addToSet: "$handler" },
|
|
1945
|
+
createdAt: { $min: "$createdAt" }
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
]).toArray()).map((group) => [group._id, group])
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1914
1951
|
/** Creates the lane documents that do not exist yet. */
|
|
1915
1952
|
async #seedLanes() {
|
|
1916
1953
|
await this.#state.lanes.bulkWrite(
|
|
@@ -1951,6 +1988,70 @@ var MongoSchema = class {
|
|
|
1951
1988
|
}
|
|
1952
1989
|
}
|
|
1953
1990
|
};
|
|
1991
|
+
function deliveryIndexes() {
|
|
1992
|
+
return [
|
|
1993
|
+
// Idempotent appends and lookups by event.
|
|
1994
|
+
{ key: { eventId: 1, handler: 1 }, name: "event_handler", unique: true },
|
|
1995
|
+
// Claim: due work of a lane in due order. Holds ready deliveries only. The handler is
|
|
1996
|
+
// last, so a query over many lanes and handlers stays a merge of one scan per lane.
|
|
1997
|
+
{ key: CLAIM_INDEX_KEY, name: "claim", partialFilterExpression: { lane: "ready" } },
|
|
1998
|
+
// Ordering: unfinished predecessors of a keyed delivery, parked or not.
|
|
1999
|
+
{
|
|
2000
|
+
key: { handler: 1, key: 1, sequence: 1 },
|
|
2001
|
+
name: "ordering",
|
|
2002
|
+
partialFilterExpression: { dueAt: { $exists: true }, sequence: { $exists: true } }
|
|
2003
|
+
},
|
|
2004
|
+
// Parked deliveries: the lowest parked delivery of each key, to move it back once it is next.
|
|
2005
|
+
{
|
|
2006
|
+
key: { handler: 1, key: 1, sequence: 1 },
|
|
2007
|
+
name: "parked",
|
|
2008
|
+
partialFilterExpression: { lane: "parked" }
|
|
2009
|
+
},
|
|
2010
|
+
// Statistics: oldest due delivery across handlers.
|
|
2011
|
+
{ key: { dueAt: 1 }, name: "due", partialFilterExpression: { dueAt: { $exists: true } } },
|
|
2012
|
+
// Purge of completed deliveries left by 0.1.0.
|
|
2013
|
+
{
|
|
2014
|
+
key: { completedAt: 1 },
|
|
2015
|
+
name: "completed",
|
|
2016
|
+
partialFilterExpression: { completedAt: { $exists: true } }
|
|
2017
|
+
},
|
|
2018
|
+
// Listing and status counts.
|
|
2019
|
+
{ key: { status: 1, createdAt: 1 }, name: "status_created" },
|
|
2020
|
+
// Purge of dead deliveries.
|
|
2021
|
+
{ key: { status: 1, updatedAt: 1 }, name: "status_updated" },
|
|
2022
|
+
{ key: { handler: 1, createdAt: 1 }, name: "handler_created" },
|
|
2023
|
+
{ key: { eventType: 1, createdAt: 1 }, name: "type_created" }
|
|
2024
|
+
];
|
|
2025
|
+
}
|
|
2026
|
+
function eventIndexes() {
|
|
2027
|
+
return [
|
|
2028
|
+
{ key: { createdAt: 1, _id: 1 }, name: "created" },
|
|
2029
|
+
// Purge of events whose deliveries all completed: they are kept until `completedBefore` passes this.
|
|
2030
|
+
{
|
|
2031
|
+
key: { completedAt: 1, _id: 1 },
|
|
2032
|
+
name: "completed",
|
|
2033
|
+
partialFilterExpression: { completedAt: { $exists: true } }
|
|
2034
|
+
}
|
|
2035
|
+
];
|
|
2036
|
+
}
|
|
2037
|
+
function completionIndexes() {
|
|
2038
|
+
return [
|
|
2039
|
+
{ key: { completedAt: 1, _id: 1 }, name: "completed" },
|
|
2040
|
+
{ key: { eventId: 1 }, name: "event" },
|
|
2041
|
+
{ key: { createdAt: 1, _id: 1 }, name: "created" }
|
|
2042
|
+
];
|
|
2043
|
+
}
|
|
2044
|
+
function convertEvent(event, group) {
|
|
2045
|
+
return {
|
|
2046
|
+
updateOne: {
|
|
2047
|
+
// Only an event nobody converted yet: another process may have finished and subscribed a handler.
|
|
2048
|
+
filter: { _id: event._id, handlers: { $exists: false } },
|
|
2049
|
+
update: {
|
|
2050
|
+
$set: group === void 0 ? { handlers: [], createdAt: event.occurredAt } : { handlers: group.handlers, createdAt: group.createdAt }
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
1954
2055
|
function isObsoleteIndex(index) {
|
|
1955
2056
|
return OBSOLETE_INDEXES.includes(index.name) || index.name === "claim" && Object.keys(index.key).join() !== Object.keys(CLAIM_INDEX_KEY).join();
|
|
1956
2057
|
}
|
|
@@ -1987,13 +2088,48 @@ function assertDatabaseName(name) {
|
|
|
1987
2088
|
}
|
|
1988
2089
|
}
|
|
1989
2090
|
|
|
1990
|
-
// src/mongo-
|
|
2091
|
+
// src/mongo-store-factory.ts
|
|
1991
2092
|
var DEFAULT_TRANSACTION_OPTIONS = {
|
|
1992
2093
|
readConcern: { level: "snapshot" },
|
|
1993
2094
|
writeConcern: { w: "majority" },
|
|
1994
2095
|
readPreference: "primary"
|
|
1995
2096
|
};
|
|
1996
2097
|
var DEFAULT_PURGE_BATCH_SIZE = 500;
|
|
2098
|
+
function createMongoStoreState(options) {
|
|
2099
|
+
const prefix = options.collectionPrefix ?? "relaybus_";
|
|
2100
|
+
assertCollectionPrefix(prefix);
|
|
2101
|
+
if (options.database !== void 0) {
|
|
2102
|
+
assertDatabaseName(options.database);
|
|
2103
|
+
}
|
|
2104
|
+
const purgeBatchSize = internal.PositiveInteger.of(
|
|
2105
|
+
options.purgeBatchSize ?? DEFAULT_PURGE_BATCH_SIZE,
|
|
2106
|
+
"purgeBatchSize"
|
|
2107
|
+
);
|
|
2108
|
+
const database = options.client.db(options.database);
|
|
2109
|
+
const collectionOptions = {
|
|
2110
|
+
readPreference: "primary",
|
|
2111
|
+
writeConcern: { w: "majority" }
|
|
2112
|
+
};
|
|
2113
|
+
const state = {
|
|
2114
|
+
client: options.client,
|
|
2115
|
+
database,
|
|
2116
|
+
events: database.collection(`${prefix}events`, collectionOptions),
|
|
2117
|
+
deliveries: database.collection(`${prefix}deliveries`, collectionOptions),
|
|
2118
|
+
keySequences: database.collection(`${prefix}key_sequences`, collectionOptions),
|
|
2119
|
+
lanes: database.collection(`${prefix}lanes`, collectionOptions),
|
|
2120
|
+
completions: database.collection(`${prefix}completed_deliveries`, collectionOptions),
|
|
2121
|
+
completionHistory: options.completionHistory ?? false,
|
|
2122
|
+
transactionOptions: options.transactionOptions ?? DEFAULT_TRANSACTION_OPTIONS,
|
|
2123
|
+
purgeBatchSize,
|
|
2124
|
+
logger: options.logger ?? console,
|
|
2125
|
+
instanceId: core.createUuidV7(),
|
|
2126
|
+
handedOut: new internal.HandedOutLedger(),
|
|
2127
|
+
unreconciledOwners: /* @__PURE__ */ new Set()
|
|
2128
|
+
};
|
|
2129
|
+
return state;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
// src/mongo-event-store.ts
|
|
1997
2133
|
var MongoEventStore = class {
|
|
1998
2134
|
#state;
|
|
1999
2135
|
#schema;
|
|
@@ -2004,37 +2140,7 @@ var MongoEventStore = class {
|
|
|
2004
2140
|
#queries;
|
|
2005
2141
|
#purger;
|
|
2006
2142
|
constructor(options) {
|
|
2007
|
-
const
|
|
2008
|
-
assertCollectionPrefix(prefix);
|
|
2009
|
-
if (options.database !== void 0) {
|
|
2010
|
-
assertDatabaseName(options.database);
|
|
2011
|
-
}
|
|
2012
|
-
const purgeBatchSize = internal.assertPositiveInteger(
|
|
2013
|
-
options.purgeBatchSize ?? DEFAULT_PURGE_BATCH_SIZE,
|
|
2014
|
-
"purgeBatchSize"
|
|
2015
|
-
);
|
|
2016
|
-
const database = options.client.db(options.database);
|
|
2017
|
-
const collectionOptions = {
|
|
2018
|
-
readPreference: "primary",
|
|
2019
|
-
writeConcern: { w: "majority" }
|
|
2020
|
-
};
|
|
2021
|
-
const state = {
|
|
2022
|
-
client: options.client,
|
|
2023
|
-
database,
|
|
2024
|
-
events: database.collection(`${prefix}events`, collectionOptions),
|
|
2025
|
-
deliveries: database.collection(`${prefix}deliveries`, collectionOptions),
|
|
2026
|
-
keySequences: database.collection(`${prefix}key_sequences`, collectionOptions),
|
|
2027
|
-
lanes: database.collection(`${prefix}lanes`, collectionOptions),
|
|
2028
|
-
completions: database.collection(`${prefix}completed_deliveries`, collectionOptions),
|
|
2029
|
-
completionHistory: options.completionHistory ?? false,
|
|
2030
|
-
transactionOptions: options.transactionOptions ?? DEFAULT_TRANSACTION_OPTIONS,
|
|
2031
|
-
purgeBatchSize,
|
|
2032
|
-
logger: options.logger ?? console,
|
|
2033
|
-
instanceId: core.createUuidV7(),
|
|
2034
|
-
claimsInProgress: /* @__PURE__ */ new Map(),
|
|
2035
|
-
handedOut: /* @__PURE__ */ new Map(),
|
|
2036
|
-
unreconciledOwners: /* @__PURE__ */ new Set()
|
|
2037
|
-
};
|
|
2143
|
+
const state = createMongoStoreState(options);
|
|
2038
2144
|
this.#state = state;
|
|
2039
2145
|
const queries = new MongoQueries(state);
|
|
2040
2146
|
const ordering = new MongoOrdering(state);
|
|
@@ -2117,7 +2223,7 @@ var MongoEventStore = class {
|
|
|
2117
2223
|
*/
|
|
2118
2224
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
2119
2225
|
extendLease(lease, leaseExpiresAt, _now) {
|
|
2120
|
-
return this.#completion.extendLease(lease, leaseExpiresAt);
|
|
2226
|
+
return Promise.resolve(this.#completion.extendLease({ lease, leaseExpiresAt }));
|
|
2121
2227
|
}
|
|
2122
2228
|
/**
|
|
2123
2229
|
* Deletes the delivery (and records it in the completion history, when kept).
|
|
@@ -2141,16 +2247,25 @@ var MongoEventStore = class {
|
|
|
2141
2247
|
/** Returns a running delivery to pending, due at `availableAt`, and counts the attempt that ended. */
|
|
2142
2248
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
2143
2249
|
reschedule(lease, availableAt, error, now) {
|
|
2144
|
-
return
|
|
2250
|
+
return Promise.resolve(
|
|
2251
|
+
this.#completion.changeClaim({
|
|
2252
|
+
lease,
|
|
2253
|
+
change: { kind: "reschedule", availableAt, error, now }
|
|
2254
|
+
})
|
|
2255
|
+
);
|
|
2145
2256
|
}
|
|
2146
2257
|
/** Gives a running delivery back without counting the attempt: pending, due `now`. */
|
|
2147
2258
|
release(lease, now) {
|
|
2148
|
-
return
|
|
2259
|
+
return Promise.resolve(
|
|
2260
|
+
this.#completion.changeClaim({ lease, change: { kind: "release", now } })
|
|
2261
|
+
);
|
|
2149
2262
|
}
|
|
2150
2263
|
/** Moves a running delivery to dead and counts the attempt that ended. */
|
|
2151
2264
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
2152
2265
|
bury(lease, error, now) {
|
|
2153
|
-
return
|
|
2266
|
+
return Promise.resolve(
|
|
2267
|
+
this.#completion.changeClaim({ lease, change: { kind: "bury", error, now } })
|
|
2268
|
+
);
|
|
2154
2269
|
}
|
|
2155
2270
|
/**
|
|
2156
2271
|
* Frees every lane `leaseOwner` holds that runs no delivery, so other workers
|