@relaybus/postgres 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -10
- package/dist/chunk-NNZJR5AB.js +12 -0
- package/dist/chunk-NNZJR5AB.js.map +1 -0
- package/dist/drizzle.cjs +13 -6
- package/dist/drizzle.cjs.map +1 -1
- package/dist/drizzle.d.cts +3 -2
- package/dist/drizzle.d.ts +3 -2
- package/dist/drizzle.js +6 -5
- package/dist/drizzle.js.map +1 -1
- package/dist/index.cjs +457 -404
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +458 -405
- package/dist/index.js.map +1 -1
- package/dist/knex.cjs +14 -5
- package/dist/knex.cjs.map +1 -1
- package/dist/knex.d.cts +3 -2
- package/dist/knex.d.ts +3 -2
- package/dist/knex.js +6 -5
- package/dist/knex.js.map +1 -1
- package/dist/kysely.cjs +13 -6
- package/dist/kysely.cjs.map +1 -1
- package/dist/kysely.d.cts +3 -2
- package/dist/kysely.d.ts +3 -2
- package/dist/kysely.js +6 -6
- package/dist/kysely.js.map +1 -1
- package/dist/prisma.cjs +14 -5
- package/dist/prisma.cjs.map +1 -1
- package/dist/prisma.d.cts +4 -4
- package/dist/prisma.d.ts +4 -4
- package/dist/prisma.js +7 -5
- package/dist/prisma.js.map +1 -1
- package/dist/typeorm.cjs +14 -5
- package/dist/typeorm.cjs.map +1 -1
- package/dist/typeorm.d.cts +3 -2
- package/dist/typeorm.d.ts +3 -2
- package/dist/typeorm.js +7 -5
- package/dist/typeorm.js.map +1 -1
- package/package.json +19 -12
package/dist/index.cjs
CHANGED
|
@@ -189,6 +189,26 @@ var DEFAULT_SCHEMA = "public";
|
|
|
189
189
|
var MAXIMUM_CHANNEL_LENGTH = 63;
|
|
190
190
|
var DEFAULT_TABLE_PREFIX = "relaybus_";
|
|
191
191
|
var TABLE_PREFIX_PATTERN = /^[a-z0-9_]*$/;
|
|
192
|
+
function indexNames(index) {
|
|
193
|
+
return {
|
|
194
|
+
deliveryEventHandler: index("deliveries_event_handler_unique"),
|
|
195
|
+
deliveryLaneDue: index("deliveries_lane_due_idx"),
|
|
196
|
+
deliveryKeyPending: index("deliveries_key_pending_idx"),
|
|
197
|
+
eventCreated: index("events_created_idx"),
|
|
198
|
+
deliveryCompleted: index("deliveries_completed_idx"),
|
|
199
|
+
deliveryDead: index("deliveries_dead_idx"),
|
|
200
|
+
completionCompleted: index("completed_deliveries_completed_idx"),
|
|
201
|
+
eventCompletionsCompleted: index("event_completions_completed_idx"),
|
|
202
|
+
obsolete: [
|
|
203
|
+
index("deliveries_due_idx"),
|
|
204
|
+
index("deliveries_due_unkeyed_idx"),
|
|
205
|
+
index("deliveries_handler_created_idx"),
|
|
206
|
+
index("deliveries_key_unfinished_idx"),
|
|
207
|
+
index("deliveries_key_processing_idx"),
|
|
208
|
+
index("deliveries_status_created_idx")
|
|
209
|
+
]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
192
212
|
function resolvePostgresNames(options = {}) {
|
|
193
213
|
const schema = options.schema ?? DEFAULT_SCHEMA;
|
|
194
214
|
const tablePrefix = options.tablePrefix ?? DEFAULT_TABLE_PREFIX;
|
|
@@ -218,24 +238,7 @@ function resolvePostgresNames(options = {}) {
|
|
|
218
238
|
completions: table("completed_deliveries"),
|
|
219
239
|
completionHistory: options.completionHistory ?? false,
|
|
220
240
|
notificationChannel: channelName(`${schema}.${tablePrefix}deliveries`),
|
|
221
|
-
indexes:
|
|
222
|
-
deliveryEventHandler: index("deliveries_event_handler_unique"),
|
|
223
|
-
deliveryLaneDue: index("deliveries_lane_due_idx"),
|
|
224
|
-
deliveryKeyPending: index("deliveries_key_pending_idx"),
|
|
225
|
-
eventCreated: index("events_created_idx"),
|
|
226
|
-
deliveryCompleted: index("deliveries_completed_idx"),
|
|
227
|
-
deliveryDead: index("deliveries_dead_idx"),
|
|
228
|
-
completionCompleted: index("completed_deliveries_completed_idx"),
|
|
229
|
-
eventCompletionsCompleted: index("event_completions_completed_idx"),
|
|
230
|
-
obsolete: [
|
|
231
|
-
index("deliveries_due_idx"),
|
|
232
|
-
index("deliveries_due_unkeyed_idx"),
|
|
233
|
-
index("deliveries_handler_created_idx"),
|
|
234
|
-
index("deliveries_key_unfinished_idx"),
|
|
235
|
-
index("deliveries_key_processing_idx"),
|
|
236
|
-
index("deliveries_status_created_idx")
|
|
237
|
-
]
|
|
238
|
-
}
|
|
241
|
+
indexes: indexNames(index)
|
|
239
242
|
};
|
|
240
243
|
}
|
|
241
244
|
var CHANNEL_HASH_DIGITS = 32;
|
|
@@ -266,6 +269,9 @@ function createPostgresSchemaStatements(options = {}) {
|
|
|
266
269
|
return statements;
|
|
267
270
|
}
|
|
268
271
|
function eventAndDeliveryStatements(names) {
|
|
272
|
+
return [...eventAndKeySequenceStatements(names), ...deliveryStatements(names)];
|
|
273
|
+
}
|
|
274
|
+
function eventAndKeySequenceStatements(names) {
|
|
269
275
|
return [
|
|
270
276
|
`CREATE TABLE IF NOT EXISTS ${names.events} (
|
|
271
277
|
id text PRIMARY KEY,
|
|
@@ -285,7 +291,11 @@ function eventAndDeliveryStatements(names) {
|
|
|
285
291
|
`CREATE TABLE IF NOT EXISTS ${names.keySequences} (
|
|
286
292
|
key text PRIMARY KEY,
|
|
287
293
|
sequence bigint NOT NULL
|
|
288
|
-
)
|
|
294
|
+
)`
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
function deliveryStatements(names) {
|
|
298
|
+
return [
|
|
289
299
|
`CREATE TABLE IF NOT EXISTS ${names.deliveries} (
|
|
290
300
|
id text COLLATE "C" PRIMARY KEY,
|
|
291
301
|
event_id text NOT NULL REFERENCES ${names.events} (id) ON DELETE CASCADE,
|
|
@@ -377,6 +387,16 @@ function createPostgresSchemaSql(options = {}) {
|
|
|
377
387
|
`;
|
|
378
388
|
}
|
|
379
389
|
var RECONNECT_DELAY_MS = 1e3;
|
|
390
|
+
function endOnce(connection, ended) {
|
|
391
|
+
let ending = Promise.resolve();
|
|
392
|
+
return () => {
|
|
393
|
+
if (!ended.value) {
|
|
394
|
+
ended.value = true;
|
|
395
|
+
ending = connection.end().catch(() => void 0);
|
|
396
|
+
}
|
|
397
|
+
return ending;
|
|
398
|
+
};
|
|
399
|
+
}
|
|
380
400
|
var NotificationListener = class {
|
|
381
401
|
#connect;
|
|
382
402
|
#channel;
|
|
@@ -395,15 +415,8 @@ var NotificationListener = class {
|
|
|
395
415
|
/** Connects and listens. Rejects when that fails; a connection lost later is replaced. */
|
|
396
416
|
async start() {
|
|
397
417
|
const connection = await this.#connect();
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const end = () => {
|
|
401
|
-
if (!ended) {
|
|
402
|
-
ended = true;
|
|
403
|
-
ending = connection.end().catch(() => void 0);
|
|
404
|
-
}
|
|
405
|
-
return ending;
|
|
406
|
-
};
|
|
418
|
+
const ended = { value: false };
|
|
419
|
+
const end = endOnce(connection, ended);
|
|
407
420
|
if (this.#closed) {
|
|
408
421
|
await end();
|
|
409
422
|
return;
|
|
@@ -411,7 +424,7 @@ var NotificationListener = class {
|
|
|
411
424
|
this.#drop = end;
|
|
412
425
|
let listening = false;
|
|
413
426
|
const onLost = (error) => {
|
|
414
|
-
if (!listening || ended) {
|
|
427
|
+
if (!listening || ended.value) {
|
|
415
428
|
return;
|
|
416
429
|
}
|
|
417
430
|
void end();
|
|
@@ -427,14 +440,8 @@ var NotificationListener = class {
|
|
|
427
440
|
try {
|
|
428
441
|
await connection.query(`LISTEN "${this.#channel}"`);
|
|
429
442
|
} catch (error) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
await end();
|
|
434
|
-
if (this.#closed) {
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
throw error;
|
|
443
|
+
await this.#abandonFailedListen(end, error);
|
|
444
|
+
return;
|
|
438
445
|
}
|
|
439
446
|
if (this.#closed) {
|
|
440
447
|
await end();
|
|
@@ -442,6 +449,17 @@ var NotificationListener = class {
|
|
|
442
449
|
}
|
|
443
450
|
listening = true;
|
|
444
451
|
}
|
|
452
|
+
/** Forgets and ends a connection whose LISTEN failed; rethrows the failure unless closing caused it. */
|
|
453
|
+
async #abandonFailedListen(end, error) {
|
|
454
|
+
if (this.#drop === end) {
|
|
455
|
+
this.#drop = void 0;
|
|
456
|
+
}
|
|
457
|
+
await end();
|
|
458
|
+
if (this.#closed) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
445
463
|
#scheduleReconnect() {
|
|
446
464
|
this.#reconnectTimer = internal.schedule(core.systemClock, {
|
|
447
465
|
run: () => {
|
|
@@ -476,7 +494,18 @@ var NotificationListener = class {
|
|
|
476
494
|
}
|
|
477
495
|
};
|
|
478
496
|
|
|
479
|
-
// src/
|
|
497
|
+
// src/root-pool-executor.ts
|
|
498
|
+
function withRootPool(executor, root) {
|
|
499
|
+
return {
|
|
500
|
+
...executor.sessionTimeouts === void 0 ? {} : { sessionTimeouts: executor.sessionTimeouts },
|
|
501
|
+
transaction: (work) => executor.transaction(work),
|
|
502
|
+
...executor.transactionWithBudget === void 0 ? {} : {
|
|
503
|
+
transactionWithBudget: (work, timeoutMs) => executor.transactionWithBudget(work, timeoutMs)
|
|
504
|
+
},
|
|
505
|
+
// eslint-disable-next-line relaybus/max-two-params -- PostgresExecutor port signature
|
|
506
|
+
query: (sql, parameters, transaction, options) => transaction === void 0 ? root.query(sql, parameters, void 0, options) : executor.query(sql, parameters, transaction, options)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
480
509
|
function text(row, column) {
|
|
481
510
|
return row[column];
|
|
482
511
|
}
|
|
@@ -503,16 +532,16 @@ function mapEventEnvelope(row) {
|
|
|
503
532
|
occurredAt: new Date(text(row, "event_occurred_at"))
|
|
504
533
|
};
|
|
505
534
|
}
|
|
535
|
+
var LEASE_TOKENS = new internal.LaneLeaseTokenFormat({
|
|
536
|
+
laneCount: LANE_COUNT,
|
|
537
|
+
epochDigits: 18,
|
|
538
|
+
claimsDigits: 9
|
|
539
|
+
});
|
|
506
540
|
function formatLeaseToken(lane, { epoch, claims }) {
|
|
507
|
-
return
|
|
541
|
+
return LEASE_TOKENS.format(lane, { epoch, claims });
|
|
508
542
|
}
|
|
509
|
-
var LEASE_TOKEN_PATTERN = /^(\d{1,3}):(\d{1,18}):(\d{1,9})$/;
|
|
510
543
|
function parseLeaseToken(token) {
|
|
511
|
-
|
|
512
|
-
if (match === null || Number(match[1]) >= LANE_COUNT) {
|
|
513
|
-
return void 0;
|
|
514
|
-
}
|
|
515
|
-
return { lane: Number(match[1]), epoch: match[2], claims: Number(match[3]) };
|
|
544
|
+
return LEASE_TOKENS.parse(token);
|
|
516
545
|
}
|
|
517
546
|
function mapClaimedDelivery(row, leaseExpiresAt) {
|
|
518
547
|
return {
|
|
@@ -564,22 +593,11 @@ var PostgresAppend = class {
|
|
|
564
593
|
const createdAt = now.toISOString();
|
|
565
594
|
const wakeAt = this.#wakeAt(plan.deliveries, { now, transaction });
|
|
566
595
|
const handlersByEvent = handlersByEventOf(plan.deliveries);
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
plan.events.map((event, ordinal) => ({
|
|
573
|
-
ordinal,
|
|
574
|
-
...toEventRow(event, { sequence: null, handlers: handlersByEvent.get(event.id) ?? [] })
|
|
575
|
-
}))
|
|
576
|
-
),
|
|
577
|
-
createdAt,
|
|
578
|
-
JSON.stringify(plan.deliveries),
|
|
579
|
-
JSON.stringify([...keyCounts].map(([key, count]) => ({ key, count }))),
|
|
580
|
-
wakeAt
|
|
581
|
-
],
|
|
582
|
-
transaction
|
|
596
|
+
const fresh = await this.#insertFreshEvents(transaction, {
|
|
597
|
+
plan,
|
|
598
|
+
handlersByEvent,
|
|
599
|
+
createdAt,
|
|
600
|
+
wakeAt
|
|
583
601
|
});
|
|
584
602
|
const insertedEventIds = new Set(
|
|
585
603
|
fresh.filter((row) => row["inserted"] === true).map((row) => row["id"])
|
|
@@ -601,6 +619,40 @@ var PostgresAppend = class {
|
|
|
601
619
|
}
|
|
602
620
|
return { ...splitOutcomes(plan, insertedEventIds), appendedDeliveryCount };
|
|
603
621
|
}
|
|
622
|
+
/** Runs the publishing statement that inserts the events not stored yet, with their deliveries. */
|
|
623
|
+
async #insertFreshEvents(transaction, {
|
|
624
|
+
plan,
|
|
625
|
+
handlersByEvent,
|
|
626
|
+
createdAt,
|
|
627
|
+
wakeAt
|
|
628
|
+
}) {
|
|
629
|
+
const keyCounts = keyCountsOf(plan.events);
|
|
630
|
+
return this.#state.query({
|
|
631
|
+
sql: this.#state.statements.append.insertFreshEvents,
|
|
632
|
+
parameters: [
|
|
633
|
+
JSON.stringify(
|
|
634
|
+
plan.events.map((event, ordinal) => ({
|
|
635
|
+
ordinal,
|
|
636
|
+
...toEventRow(event, { sequence: null, handlers: handlersByEvent.get(event.id) ?? [] })
|
|
637
|
+
}))
|
|
638
|
+
),
|
|
639
|
+
createdAt,
|
|
640
|
+
JSON.stringify(plan.deliveries),
|
|
641
|
+
JSON.stringify([...keyCounts].map(([key, count]) => ({ key, count }))),
|
|
642
|
+
wakeAt
|
|
643
|
+
],
|
|
644
|
+
transaction
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
/** Locks the given events and returns the ids of those that still exist. */
|
|
648
|
+
async #lockExistingEvents(transaction, existing) {
|
|
649
|
+
const locked = await this.#state.query({
|
|
650
|
+
sql: this.#state.statements.append.lockExistingEvents,
|
|
651
|
+
parameters: [JSON.stringify(existing.map((row) => row.id).sort())],
|
|
652
|
+
transaction
|
|
653
|
+
});
|
|
654
|
+
return new Set(locked.map((row) => row["id"]));
|
|
655
|
+
}
|
|
604
656
|
/**
|
|
605
657
|
* The time the publishing statements notify the dispatchers for (deliveries due by then wake them),
|
|
606
658
|
* or `null` for no notification. An append in a transaction of the caller's always notifies, at its
|
|
@@ -668,13 +720,7 @@ var PostgresAppend = class {
|
|
|
668
720
|
})
|
|
669
721
|
);
|
|
670
722
|
while (existing.length > 0) {
|
|
671
|
-
const locked =
|
|
672
|
-
(await this.#state.query({
|
|
673
|
-
sql: this.#state.statements.append.lockExistingEvents,
|
|
674
|
-
parameters: [JSON.stringify(existing.map((row) => row.id).sort())],
|
|
675
|
-
transaction
|
|
676
|
-
})).map((row) => row["id"])
|
|
677
|
-
);
|
|
723
|
+
const locked = await this.#lockExistingEvents(transaction, existing);
|
|
678
724
|
const gone = existing.filter((row) => !locked.has(row.id));
|
|
679
725
|
if (gone.length > 0) {
|
|
680
726
|
const inserted2 = await this.#state.query({
|
|
@@ -711,7 +757,7 @@ function planAppend(entries) {
|
|
|
711
757
|
events.push(event);
|
|
712
758
|
}
|
|
713
759
|
for (const delivery of newDeliveries) {
|
|
714
|
-
const identity =
|
|
760
|
+
const identity = internal.CompositeKey.of(event.id, delivery.handler);
|
|
715
761
|
if (!seenDeliveries.has(identity)) {
|
|
716
762
|
seenDeliveries.add(identity);
|
|
717
763
|
deliveries.push({
|
|
@@ -776,49 +822,46 @@ var ReconcileFailedError = class extends core.RelayBusError {
|
|
|
776
822
|
name = "ReconcileFailedError";
|
|
777
823
|
code = "RELAYBUS_POSTGRES_RECONCILE_FAILED";
|
|
778
824
|
};
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
825
|
+
function leftWorkBehind(row) {
|
|
826
|
+
return row["takeover_waiting"] === true || row["unexamined_free_lanes"] === true || row["stale"] === true;
|
|
827
|
+
}
|
|
828
|
+
function keepKeyHeads(picked, limit) {
|
|
829
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
830
|
+
const kept = [...picked].sort((left, right) => compareSequence(left, right)).filter((row) => {
|
|
831
|
+
if (row["delivery_key"] === null) {
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
const group = internal.CompositeKey.of(row["handler"], row["delivery_key"]);
|
|
835
|
+
return !seenKeys.has(group) && Boolean(seenKeys.add(group));
|
|
836
|
+
});
|
|
837
|
+
kept.sort(compareDue);
|
|
838
|
+
return kept.slice(0, limit);
|
|
839
|
+
}
|
|
840
|
+
function compareSequence(left, right) {
|
|
841
|
+
const first = sequenceOf(left);
|
|
842
|
+
const second = sequenceOf(right);
|
|
843
|
+
return Number(first > second) - Number(first < second);
|
|
844
|
+
}
|
|
845
|
+
function sequenceOf(row) {
|
|
846
|
+
return BigInt(row["sequence"] ?? 0);
|
|
847
|
+
}
|
|
848
|
+
function compareDue(left, right) {
|
|
849
|
+
return compareText(left["due_at"], right["due_at"]) || compareText(left["delivery_id"], right["delivery_id"]);
|
|
850
|
+
}
|
|
851
|
+
function compareText(left, right) {
|
|
852
|
+
return Number(left > right) - Number(left < right);
|
|
783
853
|
}
|
|
784
854
|
|
|
785
855
|
// src/postgres-claim.ts
|
|
786
856
|
var INITIAL_LANES_PER_ROUND = 4;
|
|
787
857
|
var MAXIMUM_STALE_CLAIM_ATTEMPTS = 3;
|
|
788
|
-
var PostgresClaims = class {
|
|
858
|
+
var PostgresClaims = class extends internal.OwnerSerializedClaims {
|
|
789
859
|
#state;
|
|
790
860
|
constructor(state) {
|
|
861
|
+
super();
|
|
791
862
|
this.#state = state;
|
|
792
863
|
}
|
|
793
|
-
|
|
794
|
-
claim(request) {
|
|
795
|
-
if (request.handlers.length === 0 || !(request.limit > 0)) {
|
|
796
|
-
return Promise.resolve([]);
|
|
797
|
-
}
|
|
798
|
-
return this.#serialized(request.leaseOwner, () => this.#claim(request));
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* Runs `work` after the earlier claims of the same owner on this store settled. The
|
|
802
|
-
* bookkeeping of what was handed out and the reconciliation of a failed claim need
|
|
803
|
-
* one claim at a time per owner (the dispatcher claims one batch at a time anyway;
|
|
804
|
-
* `runOnce` next to a running dispatcher is what this guards against).
|
|
805
|
-
*/
|
|
806
|
-
#serialized(owner, work) {
|
|
807
|
-
const previous = this.#state.ownerQueues.get(owner) ?? Promise.resolve();
|
|
808
|
-
const run = previous.then(work, work);
|
|
809
|
-
const tail = run.then(
|
|
810
|
-
() => void 0,
|
|
811
|
-
() => void 0
|
|
812
|
-
);
|
|
813
|
-
this.#state.ownerQueues.set(owner, tail);
|
|
814
|
-
void tail.then(() => {
|
|
815
|
-
if (this.#state.ownerQueues.get(owner) === tail) {
|
|
816
|
-
this.#state.ownerQueues.delete(owner);
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
return run;
|
|
820
|
-
}
|
|
821
|
-
async #claim(request) {
|
|
864
|
+
async claimInTurn(request) {
|
|
822
865
|
await this.#assertReconciled(request.leaseOwner);
|
|
823
866
|
const heldFirst = this.#state.drainingOwners.has(request.leaseOwner);
|
|
824
867
|
let rows = await this.#claimFirst(request, heldFirst);
|
|
@@ -990,7 +1033,7 @@ var PostgresClaims = class {
|
|
|
990
1033
|
#handOut(rows, leaseOwner) {
|
|
991
1034
|
for (const row of rows) {
|
|
992
1035
|
const claim = mapClaimedDelivery(row, /* @__PURE__ */ new Date(0));
|
|
993
|
-
this.#state.handedOut.
|
|
1036
|
+
this.#state.handedOut.register(claim, {
|
|
994
1037
|
owner: leaseOwner,
|
|
995
1038
|
deliveryId: claim.deliveryId,
|
|
996
1039
|
...parseLeaseToken(claim.leaseToken)
|
|
@@ -1011,11 +1054,9 @@ var PostgresClaims = class {
|
|
|
1011
1054
|
...JSON.parse(row["running_ids"]),
|
|
1012
1055
|
...claimed.map((picked) => picked["delivery_id"])
|
|
1013
1056
|
]);
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1057
|
+
this.#state.handedOut.forgetWhere(
|
|
1058
|
+
(entry) => entry.owner === leaseOwner && heldLanes.has(entry.lane) && !running.has(entry.deliveryId)
|
|
1059
|
+
);
|
|
1019
1060
|
}
|
|
1020
1061
|
/**
|
|
1021
1062
|
* After a claim failed, possibly after the database recorded its batch: gives back the recorded
|
|
@@ -1025,7 +1066,7 @@ var PostgresClaims = class {
|
|
|
1025
1066
|
*/
|
|
1026
1067
|
async #reconcile(leaseOwner) {
|
|
1027
1068
|
const generation = this.#generation(leaseOwner, true);
|
|
1028
|
-
const handedOut =
|
|
1069
|
+
const handedOut = this.#state.handedOut.entriesOf(leaseOwner).map(({ deliveryId, lane, epoch, claims }) => ({ id: deliveryId, lane, epoch, claims }));
|
|
1029
1070
|
try {
|
|
1030
1071
|
await this.#state.query({
|
|
1031
1072
|
sql: this.#state.statements.claim.reconcileClaims,
|
|
@@ -1062,16 +1103,7 @@ var PostgresClaims = class {
|
|
|
1062
1103
|
(row) => row["delivery_id"] !== null && !pickedIds.has(row["delivery_id"])
|
|
1063
1104
|
)
|
|
1064
1105
|
);
|
|
1065
|
-
|
|
1066
|
-
const kept = [...picked].sort((left, right) => compareSequence(left, right)).filter((row) => {
|
|
1067
|
-
if (row["delivery_key"] === null) {
|
|
1068
|
-
return true;
|
|
1069
|
-
}
|
|
1070
|
-
const group = `${row["handler"]}\0${row["delivery_key"]}`;
|
|
1071
|
-
return !seenKeys.has(group) && Boolean(seenKeys.add(group));
|
|
1072
|
-
});
|
|
1073
|
-
kept.sort(compareDue);
|
|
1074
|
-
return kept.slice(0, limit);
|
|
1106
|
+
return keepKeyHeads(picked, limit);
|
|
1075
1107
|
}
|
|
1076
1108
|
return picked;
|
|
1077
1109
|
}
|
|
@@ -1103,43 +1135,12 @@ var PostgresClaims = class {
|
|
|
1103
1135
|
}
|
|
1104
1136
|
};
|
|
1105
1137
|
var GENERATION_STEP = 1n;
|
|
1106
|
-
|
|
1107
|
-
return row["takeover_waiting"] === true || row["unexamined_free_lanes"] === true || row["stale"] === true;
|
|
1108
|
-
}
|
|
1109
|
-
function compareSequence(left, right) {
|
|
1110
|
-
const first = sequenceOf(left);
|
|
1111
|
-
const second = sequenceOf(right);
|
|
1112
|
-
return Number(first > second) - Number(first < second);
|
|
1113
|
-
}
|
|
1114
|
-
function sequenceOf(row) {
|
|
1115
|
-
return BigInt(row["sequence"] ?? 0);
|
|
1116
|
-
}
|
|
1117
|
-
function compareDue(left, right) {
|
|
1118
|
-
return compareText(left["due_at"], right["due_at"]) || compareText(left["delivery_id"], right["delivery_id"]);
|
|
1119
|
-
}
|
|
1120
|
-
function compareText(left, right) {
|
|
1121
|
-
return Number(left > right) - Number(left < right);
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
// src/postgres-completion.ts
|
|
1125
|
-
var PostgresCompletion = class {
|
|
1138
|
+
var PostgresCompletion = class extends internal.LeaseSettlement {
|
|
1126
1139
|
#state;
|
|
1127
1140
|
constructor(state) {
|
|
1141
|
+
super();
|
|
1128
1142
|
this.#state = state;
|
|
1129
1143
|
}
|
|
1130
|
-
/** Implements {@link PostgresEventStore.extendLease}. */
|
|
1131
|
-
extendLease(lease, leaseExpiresAt) {
|
|
1132
|
-
return this.#whenIssued(
|
|
1133
|
-
lease,
|
|
1134
|
-
(held) => this.#changed(this.#state.statements.completion.extendLease, [
|
|
1135
|
-
lease.deliveryId,
|
|
1136
|
-
held.lane,
|
|
1137
|
-
held.epoch,
|
|
1138
|
-
held.claims,
|
|
1139
|
-
leaseExpiresAt.toISOString()
|
|
1140
|
-
])
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
1144
|
/** Implements {@link PostgresEventStore.complete}. */
|
|
1144
1145
|
async complete(lease, { now, transaction }) {
|
|
1145
1146
|
return (await this.#completeLeases([lease], { now, transaction }))[0];
|
|
@@ -1148,76 +1149,44 @@ var PostgresCompletion = class {
|
|
|
1148
1149
|
completeMany(leases, now) {
|
|
1149
1150
|
return this.#completeLeases(leases, { now, transaction: void 0 });
|
|
1150
1151
|
}
|
|
1151
|
-
/**
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
error,
|
|
1155
|
-
now
|
|
1156
|
-
}) {
|
|
1157
|
-
return this.#settled(
|
|
1158
|
-
lease,
|
|
1159
|
-
this.#whenIssued(
|
|
1160
|
-
lease,
|
|
1161
|
-
(held) => this.#changed(this.#state.statements.completion.reschedule, [
|
|
1162
|
-
lease.deliveryId,
|
|
1163
|
-
held.lane,
|
|
1164
|
-
held.epoch,
|
|
1165
|
-
held.claims,
|
|
1166
|
-
availableAt.toISOString(),
|
|
1167
|
-
JSON.stringify(error),
|
|
1168
|
-
now.toISOString()
|
|
1169
|
-
])
|
|
1170
|
-
)
|
|
1171
|
-
);
|
|
1152
|
+
/** A lease this store issued; any other token cannot be held. */
|
|
1153
|
+
findHeld(lease) {
|
|
1154
|
+
return parseLeaseToken(lease.leaseToken);
|
|
1172
1155
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
lease,
|
|
1179
|
-
(held) => this.#changed(this.#state.statements.completion.bury, [
|
|
1180
|
-
lease.deliveryId,
|
|
1181
|
-
held.lane,
|
|
1182
|
-
held.epoch,
|
|
1183
|
-
held.claims,
|
|
1184
|
-
JSON.stringify(error),
|
|
1185
|
-
now.toISOString()
|
|
1186
|
-
])
|
|
1187
|
-
)
|
|
1188
|
-
);
|
|
1156
|
+
writeExtension(held, { lease, leaseExpiresAt }) {
|
|
1157
|
+
return this.#changed(this.#state.statements.completion.extendLease, [
|
|
1158
|
+
...fenceOf(lease, held),
|
|
1159
|
+
leaseExpiresAt.toISOString()
|
|
1160
|
+
]);
|
|
1189
1161
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
this.#
|
|
1195
|
-
|
|
1196
|
-
(
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1162
|
+
writeChange(held, { lease, change }) {
|
|
1163
|
+
const { completion } = this.#state.statements;
|
|
1164
|
+
const fence = fenceOf(lease, held);
|
|
1165
|
+
if (change.kind === "reschedule") {
|
|
1166
|
+
return this.#changed(completion.reschedule, [
|
|
1167
|
+
...fence,
|
|
1168
|
+
change.availableAt.toISOString(),
|
|
1169
|
+
JSON.stringify(change.error),
|
|
1170
|
+
change.now.toISOString()
|
|
1171
|
+
]);
|
|
1172
|
+
}
|
|
1173
|
+
if (change.kind === "bury") {
|
|
1174
|
+
return this.#changed(completion.bury, [
|
|
1175
|
+
...fence,
|
|
1176
|
+
JSON.stringify(change.error),
|
|
1177
|
+
change.now.toISOString()
|
|
1178
|
+
]);
|
|
1179
|
+
}
|
|
1180
|
+
return this.#changed(completion.release, [...fence, change.now.toISOString()]);
|
|
1205
1181
|
}
|
|
1206
1182
|
/** Forgets a handed-out lease once the store answered about it (any answer: the claim is over or fenced out). */
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
this.#state.handedOut.delete(handedOutKey(lease.deliveryId, lease.leaseToken));
|
|
1210
|
-
return result;
|
|
1183
|
+
forgetHandedOut(lease) {
|
|
1184
|
+
this.#state.handedOut.forget(lease);
|
|
1211
1185
|
}
|
|
1212
1186
|
async #changed(sql, parameters) {
|
|
1213
1187
|
const rows = await this.#state.query({ sql, parameters });
|
|
1214
1188
|
return readCount(rows[0], "count") > 0;
|
|
1215
1189
|
}
|
|
1216
|
-
/** Runs `operation` for a lease this store issued; any other token cannot be held. */
|
|
1217
|
-
async #whenIssued(lease, operation) {
|
|
1218
|
-
const held = parseLeaseToken(lease.leaseToken);
|
|
1219
|
-
return held !== void 0 && await operation(held);
|
|
1220
|
-
}
|
|
1221
1190
|
/**
|
|
1222
1191
|
* Deletes the delivery of every lease that is still the current claim of its
|
|
1223
1192
|
* delivery. Returns one flag per lease, in order. Results are matched to leases
|
|
@@ -1225,16 +1194,7 @@ var PostgresCompletion = class {
|
|
|
1225
1194
|
* their own answers; a lease listed twice completes once, at its first position.
|
|
1226
1195
|
*/
|
|
1227
1196
|
async #completeLeases(leases, { now, transaction }) {
|
|
1228
|
-
const issued =
|
|
1229
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1230
|
-
leases.forEach((lease, ord) => {
|
|
1231
|
-
const held = parseLeaseToken(lease.leaseToken);
|
|
1232
|
-
const identity = `${lease.deliveryId}\0${lease.leaseToken}`;
|
|
1233
|
-
if (held !== void 0 && !seen.has(identity)) {
|
|
1234
|
-
seen.add(identity);
|
|
1235
|
-
issued.push({ ord, id: lease.deliveryId, ...held });
|
|
1236
|
-
}
|
|
1237
|
-
});
|
|
1197
|
+
const issued = issuedLeases(leases);
|
|
1238
1198
|
const completed = /* @__PURE__ */ new Set();
|
|
1239
1199
|
if (issued.length > 0) {
|
|
1240
1200
|
const rows = await this.#state.query({
|
|
@@ -1248,12 +1208,28 @@ var PostgresCompletion = class {
|
|
|
1248
1208
|
}
|
|
1249
1209
|
if (transaction === void 0) {
|
|
1250
1210
|
for (const lease of leases) {
|
|
1251
|
-
this.#state.handedOut.
|
|
1211
|
+
this.#state.handedOut.forget(lease);
|
|
1252
1212
|
}
|
|
1253
1213
|
}
|
|
1254
1214
|
return leases.map((_, ord) => completed.has(ord));
|
|
1255
1215
|
}
|
|
1256
1216
|
};
|
|
1217
|
+
function fenceOf(lease, held) {
|
|
1218
|
+
return [lease.deliveryId, held.lane, held.epoch, held.claims];
|
|
1219
|
+
}
|
|
1220
|
+
function issuedLeases(leases) {
|
|
1221
|
+
const issued = [];
|
|
1222
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1223
|
+
leases.forEach((lease, ord) => {
|
|
1224
|
+
const held = parseLeaseToken(lease.leaseToken);
|
|
1225
|
+
const identity = internal.DeliveryLeaseIdentity.of(lease.deliveryId, lease.leaseToken);
|
|
1226
|
+
if (held !== void 0 && !seen.has(identity)) {
|
|
1227
|
+
seen.add(identity);
|
|
1228
|
+
issued.push({ ord, id: lease.deliveryId, ...held });
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
return issued;
|
|
1232
|
+
}
|
|
1257
1233
|
var OwnedConnections = class {
|
|
1258
1234
|
#clients = /* @__PURE__ */ new Set();
|
|
1259
1235
|
/**
|
|
@@ -1352,6 +1328,24 @@ var PostgresMaintenance = class {
|
|
|
1352
1328
|
/** Implements {@link PostgresEventStore.purge}. */
|
|
1353
1329
|
async purge(request) {
|
|
1354
1330
|
const total = { deletedDeliveries: 0, deletedEvents: 0 };
|
|
1331
|
+
const batches = this.#purgeBatchPlan(request);
|
|
1332
|
+
for (const [sql, cutoff, deleteOrphans] of batches) {
|
|
1333
|
+
if (cutoff === void 0) {
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
let deletedInBatch = this.#state.purgeBatchSize;
|
|
1337
|
+
while (deletedInBatch === this.#state.purgeBatchSize) {
|
|
1338
|
+
const batch = await this.#purgeBatch(sql, { cutoff, deleteOrphans });
|
|
1339
|
+
deletedInBatch = batch.deletedDeliveries;
|
|
1340
|
+
total.deletedDeliveries += batch.deletedDeliveries;
|
|
1341
|
+
total.deletedEvents += batch.deletedEvents;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
total.deletedEvents += await this.#purgeAllOrphanEvents(request);
|
|
1345
|
+
return total;
|
|
1346
|
+
}
|
|
1347
|
+
/** The purge statements to run in order: SQL, cutoff (undefined skips it) and the orphan-event delete. */
|
|
1348
|
+
#purgeBatchPlan(request) {
|
|
1355
1349
|
const batches = [
|
|
1356
1350
|
[
|
|
1357
1351
|
this.#state.statements.purge.purgeCompletedBatch(),
|
|
@@ -1371,21 +1365,14 @@ var PostgresMaintenance = class {
|
|
|
1371
1365
|
this.#state.statements.purge.deleteOrphanEventsWithoutCompletion
|
|
1372
1366
|
]);
|
|
1373
1367
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
while (deletedInBatch === this.#state.purgeBatchSize) {
|
|
1380
|
-
const batch = await this.#purgeBatch(sql, { cutoff, deleteOrphans });
|
|
1381
|
-
deletedInBatch = batch.deletedDeliveries;
|
|
1382
|
-
total.deletedDeliveries += batch.deletedDeliveries;
|
|
1383
|
-
total.deletedEvents += batch.deletedEvents;
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1368
|
+
return batches;
|
|
1369
|
+
}
|
|
1370
|
+
/** Removes the events left without deliveries, in two passes; returns how many events were deleted. */
|
|
1371
|
+
async #purgeAllOrphanEvents(request) {
|
|
1372
|
+
let deletedEvents = 0;
|
|
1386
1373
|
const orphanCutoff = [request.completedBefore, request.deadBefore].filter((cutoff) => cutoff !== void 0).sort((first, second) => first.getTime() - second.getTime())[0];
|
|
1387
1374
|
if (orphanCutoff !== void 0) {
|
|
1388
|
-
|
|
1375
|
+
deletedEvents += await this.#purgeOrphanEvents(
|
|
1389
1376
|
this.#state.statements.purge.lockOrphanEventsBatch,
|
|
1390
1377
|
{
|
|
1391
1378
|
deleteStatement: this.#state.statements.purge.deleteOrphanEventsWithoutCompletion,
|
|
@@ -1395,7 +1382,7 @@ var PostgresMaintenance = class {
|
|
|
1395
1382
|
);
|
|
1396
1383
|
}
|
|
1397
1384
|
if (request.completedBefore !== void 0) {
|
|
1398
|
-
|
|
1385
|
+
deletedEvents += await this.#purgeOrphanEvents(
|
|
1399
1386
|
this.#state.statements.purge.lockCompletedOrphanEventsBatch,
|
|
1400
1387
|
{
|
|
1401
1388
|
deleteStatement: this.#state.statements.purge.deleteExpiredCompletedOrphanEvents,
|
|
@@ -1404,7 +1391,7 @@ var PostgresMaintenance = class {
|
|
|
1404
1391
|
}
|
|
1405
1392
|
);
|
|
1406
1393
|
}
|
|
1407
|
-
return
|
|
1394
|
+
return deletedEvents;
|
|
1408
1395
|
}
|
|
1409
1396
|
async #purgeOrphanEvents(lockStatement, {
|
|
1410
1397
|
deleteStatement,
|
|
@@ -1459,6 +1446,37 @@ var PostgresMaintenance = class {
|
|
|
1459
1446
|
});
|
|
1460
1447
|
}
|
|
1461
1448
|
};
|
|
1449
|
+
var WAKE_UPS = ["notify", "poll"];
|
|
1450
|
+
var DEFAULT_PURGE_BATCH_SIZE = 1e3;
|
|
1451
|
+
function resolveStoreSettings(options) {
|
|
1452
|
+
const sources = [options.executor ?? options.pool, options.connectionString].filter(
|
|
1453
|
+
(source) => source !== void 0
|
|
1454
|
+
);
|
|
1455
|
+
if (sources.length !== 1) {
|
|
1456
|
+
throw new core.ConfigurationError(
|
|
1457
|
+
"PostgresEventStore needs exactly one of pool, connectionString or executor"
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
const purgeBatchSize = internal.PositiveInteger.of(
|
|
1461
|
+
options.purgeBatchSize ?? DEFAULT_PURGE_BATCH_SIZE,
|
|
1462
|
+
"purgeBatchSize"
|
|
1463
|
+
);
|
|
1464
|
+
const wakeUp = options.wakeUp ?? "notify";
|
|
1465
|
+
if (!WAKE_UPS.includes(wakeUp)) {
|
|
1466
|
+
throw new core.ConfigurationError(`wakeUp must be 'notify' or 'poll', received ${String(wakeUp)}`);
|
|
1467
|
+
}
|
|
1468
|
+
const preparedStatements = options.preparedStatements ?? true;
|
|
1469
|
+
if (typeof preparedStatements !== "boolean") {
|
|
1470
|
+
throw new core.ConfigurationError(
|
|
1471
|
+
`preparedStatements must be a boolean, received ${String(preparedStatements)}`
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
return {
|
|
1475
|
+
purgeBatchSize,
|
|
1476
|
+
wakeUp,
|
|
1477
|
+
queryOptions: preparedStatements ? { prepare: true } : void 0
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1462
1480
|
|
|
1463
1481
|
// src/statements-append.ts
|
|
1464
1482
|
var AppendStatements = class {
|
|
@@ -1476,15 +1494,27 @@ var AppendStatements = class {
|
|
|
1476
1494
|
* inserted. Deliveries of events that already existed are left to {@link insertDeliveries}.
|
|
1477
1495
|
*/
|
|
1478
1496
|
get insertFreshEvents() {
|
|
1479
|
-
|
|
1480
|
-
|
|
1497
|
+
return `WITH ${this.#reservedSequences()}, ${this.#incomingEvents()}, ${this.#insertedEvents()}, ${this.#insertedDeliveries()}, ${this.#wakeUp("inserted_deliveries", "$5")}
|
|
1498
|
+
SELECT incoming.id, incoming.sequence::text AS sequence,
|
|
1499
|
+
incoming.id IN (SELECT inserted_events.id FROM inserted_events) AS inserted,
|
|
1500
|
+
(SELECT count(*) FROM inserted_deliveries)::int AS deliveries
|
|
1501
|
+
FROM incoming
|
|
1502
|
+
WHERE (SELECT count(*) FROM woken) >= 0`;
|
|
1503
|
+
}
|
|
1504
|
+
/** The CTE `reserved` of {@link insertFreshEvents}: the sequences the keys of `$4` reserve. */
|
|
1505
|
+
#reservedSequences() {
|
|
1506
|
+
return `reserved AS (
|
|
1481
1507
|
INSERT INTO ${this.#names.keySequences} AS reserved (key, sequence)
|
|
1482
1508
|
SELECT requested.key, requested.count
|
|
1483
1509
|
FROM jsonb_to_recordset($4::jsonb) AS requested (key text, count bigint)
|
|
1484
1510
|
ORDER BY requested.key
|
|
1485
1511
|
ON CONFLICT (key) DO UPDATE SET sequence = reserved.sequence + EXCLUDED.sequence
|
|
1486
1512
|
RETURNING reserved.key AS key, reserved.sequence AS sequence
|
|
1487
|
-
)
|
|
1513
|
+
)`;
|
|
1514
|
+
}
|
|
1515
|
+
/** The CTE `incoming` of {@link insertFreshEvents}: the events of `$1` with their key sequences. */
|
|
1516
|
+
#incomingEvents() {
|
|
1517
|
+
return `incoming AS (
|
|
1488
1518
|
SELECT incoming.*,
|
|
1489
1519
|
CASE WHEN incoming.key IS NULL THEN NULL ELSE
|
|
1490
1520
|
reserved.sequence - count(*) OVER (PARTITION BY incoming.key)
|
|
@@ -1494,8 +1524,12 @@ var AppendStatements = class {
|
|
|
1494
1524
|
ordinal int, id text, type text, source text, key text, payload jsonb, headers jsonb,
|
|
1495
1525
|
correlation_id text, causation_id text, occurred_at timestamptz, handlers text[])
|
|
1496
1526
|
LEFT JOIN reserved ON reserved.key = incoming.key
|
|
1497
|
-
)
|
|
1498
|
-
|
|
1527
|
+
)`;
|
|
1528
|
+
}
|
|
1529
|
+
/** The CTE `inserted_events` of {@link insertFreshEvents}: the events of `$1` that do not exist yet. */
|
|
1530
|
+
#insertedEvents() {
|
|
1531
|
+
return `inserted_events AS (
|
|
1532
|
+
INSERT INTO ${this.#names.events}
|
|
1499
1533
|
(id, type, source, key, sequence, payload, headers, correlation_id, causation_id, occurred_at, created_at, handlers)
|
|
1500
1534
|
SELECT incoming.id, incoming.type, incoming.source, incoming.key, incoming.sequence,
|
|
1501
1535
|
COALESCE(incoming.payload, 'null'::jsonb), COALESCE(incoming.headers, '{}'::jsonb),
|
|
@@ -1505,7 +1539,11 @@ var AppendStatements = class {
|
|
|
1505
1539
|
ORDER BY incoming.id
|
|
1506
1540
|
ON CONFLICT (id) DO NOTHING
|
|
1507
1541
|
RETURNING id, key, sequence
|
|
1508
|
-
)
|
|
1542
|
+
)`;
|
|
1543
|
+
}
|
|
1544
|
+
/** The CTE `inserted_deliveries` of {@link insertFreshEvents}: the deliveries of `$3` of the inserted events. */
|
|
1545
|
+
#insertedDeliveries() {
|
|
1546
|
+
return `inserted_deliveries AS (
|
|
1509
1547
|
INSERT INTO ${this.#names.deliveries}
|
|
1510
1548
|
(id, event_id, handler, lane, status, attempts, available_at, key, sequence, created_at, updated_at)
|
|
1511
1549
|
SELECT delivery.id, delivery.event_id, delivery.handler, ${laneExpression("event.key", "event.id")}, 'pending', 0,
|
|
@@ -1515,16 +1553,15 @@ var AppendStatements = class {
|
|
|
1515
1553
|
ORDER BY delivery.event_id, delivery.handler
|
|
1516
1554
|
ON CONFLICT (event_id, handler) DO NOTHING
|
|
1517
1555
|
RETURNING available_at
|
|
1518
|
-
)
|
|
1519
|
-
SELECT incoming.id, incoming.sequence::text AS sequence,
|
|
1520
|
-
incoming.id IN (SELECT inserted_events.id FROM inserted_events) AS inserted,
|
|
1521
|
-
(SELECT count(*) FROM inserted_deliveries)::int AS deliveries
|
|
1522
|
-
FROM incoming
|
|
1523
|
-
WHERE (SELECT count(*) FROM woken) >= 0`;
|
|
1556
|
+
)`;
|
|
1524
1557
|
}
|
|
1525
|
-
/**
|
|
1558
|
+
/**
|
|
1559
|
+
* One wake-up notification on {@link PostgresNames.notificationChannel}, outside any publishing
|
|
1560
|
+
* transaction. Returns a boolean column rather than the `void` of `pg_notify`, which some libraries
|
|
1561
|
+
* cannot read (Prisma's raw queries reject a `void` column).
|
|
1562
|
+
*/
|
|
1526
1563
|
get notify() {
|
|
1527
|
-
return `SELECT pg_notify('${this.#names.notificationChannel}', '')`;
|
|
1564
|
+
return `SELECT true AS notified FROM pg_notify('${this.#names.notificationChannel}', '')`;
|
|
1528
1565
|
}
|
|
1529
1566
|
/**
|
|
1530
1567
|
* A CTE `woken` that sends one notification on {@link PostgresNames.notificationChannel} when a row of
|
|
@@ -2047,11 +2084,146 @@ SELECT count(*)::int AS count FROM changed`;
|
|
|
2047
2084
|
}
|
|
2048
2085
|
};
|
|
2049
2086
|
|
|
2050
|
-
// src/statements-
|
|
2051
|
-
var FREE_LANE_SURPLUS = 16;
|
|
2087
|
+
// src/statements-candidates.ts
|
|
2052
2088
|
function notRunning(running, alias) {
|
|
2053
2089
|
return running === null ? "true" : `NOT EXISTS (SELECT 1 FROM ${running} AS running WHERE running.handler = ${alias}.handler AND running.key = ${alias}.key)`;
|
|
2054
2090
|
}
|
|
2091
|
+
var CandidateStatements = class {
|
|
2092
|
+
#names;
|
|
2093
|
+
constructor(names) {
|
|
2094
|
+
this.#names = names;
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* CTEs that pick up to `limit` claimable deliveries from the lanes of the CTE `scope` (columns `lane`,
|
|
2098
|
+
* `epoch`, `in_flight`), named after `prefix`; the result is `<prefix>_candidates (id, lane, available_at)`.
|
|
2099
|
+
* The rules are those of `ClaimStatements.claim`. The fast window reads the first {@link LANE_SCAN_WINDOW} due
|
|
2100
|
+
* deliveries of each lane (at most {@link CLAIM_SCAN_WINDOW} together); of a key, only the lowest sequence
|
|
2101
|
+
* in the window can be its head, so only that one is checked for an earlier pending delivery. Only when
|
|
2102
|
+
* that yields fewer than `limit` are the lanes whose window filled up looked at again with a loose index
|
|
2103
|
+
* scan over the heads of all their keys. `running` names the CTE of the keyed deliveries running in the
|
|
2104
|
+
* lanes, or is null when nothing runs there.
|
|
2105
|
+
*/
|
|
2106
|
+
candidates(prefix, {
|
|
2107
|
+
scope,
|
|
2108
|
+
running,
|
|
2109
|
+
limit
|
|
2110
|
+
}) {
|
|
2111
|
+
const spec = { prefix, scope, running, limit };
|
|
2112
|
+
return this.#fastCandidates(spec) + this.#slowCandidates(spec);
|
|
2113
|
+
}
|
|
2114
|
+
/** The fast window of each lane and the claimable deliveries in it (`<prefix>_window`, `<prefix>_fast`). */
|
|
2115
|
+
#fastCandidates({ prefix, scope, running, limit }) {
|
|
2116
|
+
const deliveries = this.#names.deliveries;
|
|
2117
|
+
return `${prefix}_window AS MATERIALIZED (
|
|
2118
|
+
SELECT recent.id, recent.lane, recent.available_at, recent.handler, recent.key, recent.sequence
|
|
2119
|
+
FROM ${scope}
|
|
2120
|
+
CROSS JOIN LATERAL (
|
|
2121
|
+
SELECT delivery.id, delivery.lane, delivery.available_at, delivery.handler, delivery.key, delivery.sequence
|
|
2122
|
+
FROM ${deliveries} AS delivery
|
|
2123
|
+
WHERE delivery.lane = ${scope}.lane AND delivery.status = 'pending'
|
|
2124
|
+
AND delivery.available_at <= $1::timestamptz AND ${HANDLER_PREDICATE}
|
|
2125
|
+
AND delivery.id <> ALL (${scope}.in_flight)
|
|
2126
|
+
ORDER BY delivery.available_at, delivery.id
|
|
2127
|
+
LIMIT ${LANE_SCAN_WINDOW}
|
|
2128
|
+
) AS recent
|
|
2129
|
+
ORDER BY recent.available_at, recent.id
|
|
2130
|
+
LIMIT ${CLAIM_SCAN_WINDOW}
|
|
2131
|
+
), ${prefix}_fast AS MATERIALIZED (
|
|
2132
|
+
SELECT picked.id, picked.lane, picked.available_at FROM (
|
|
2133
|
+
SELECT unkeyed.id, unkeyed.lane, unkeyed.available_at FROM ${prefix}_window AS unkeyed WHERE unkeyed.key IS NULL
|
|
2134
|
+
UNION ALL
|
|
2135
|
+
SELECT head.id, head.lane, head.available_at
|
|
2136
|
+
FROM (
|
|
2137
|
+
SELECT DISTINCT ON (keyed.handler, keyed.key) keyed.id, keyed.lane, keyed.available_at, keyed.handler, keyed.key, keyed.sequence
|
|
2138
|
+
FROM ${prefix}_window AS keyed
|
|
2139
|
+
WHERE keyed.key IS NOT NULL
|
|
2140
|
+
ORDER BY keyed.handler, keyed.key, keyed.sequence
|
|
2141
|
+
) AS head
|
|
2142
|
+
WHERE NOT EXISTS (
|
|
2143
|
+
SELECT 1 FROM ${deliveries} AS earlier
|
|
2144
|
+
WHERE earlier.lane = head.lane AND earlier.handler = head.handler AND earlier.key = head.key
|
|
2145
|
+
AND earlier.status = 'pending' AND earlier.key IS NOT NULL AND earlier.sequence < head.sequence
|
|
2146
|
+
) AND ${notRunning(running, "head")}
|
|
2147
|
+
) AS picked
|
|
2148
|
+
ORDER BY picked.available_at, picked.id
|
|
2149
|
+
LIMIT ${limit}
|
|
2150
|
+
`;
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* For lanes whose window filled up while the fast pick came up short: the heads of all their keys and
|
|
2154
|
+
* their unkeyed deliveries, then the union with the fast pick (`<prefix>_candidates`).
|
|
2155
|
+
*/
|
|
2156
|
+
#slowCandidates(spec) {
|
|
2157
|
+
return this.#saturatedLanes(spec) + this.#keyHeads(spec) + this.#keyedHeads(spec) + this.#unkeyedRecent(spec) + this.#unionCandidates(spec);
|
|
2158
|
+
}
|
|
2159
|
+
/** `<prefix>_saturated`: the lanes whose window filled up while the fast pick came up short. */
|
|
2160
|
+
#saturatedLanes({ prefix, scope, limit }) {
|
|
2161
|
+
return `), ${prefix}_saturated AS MATERIALIZED (
|
|
2162
|
+
SELECT ${scope}.lane, ${scope}.in_flight FROM ${scope}
|
|
2163
|
+
WHERE (SELECT count(*) FROM ${prefix}_fast) < ${limit}
|
|
2164
|
+
AND ((SELECT count(*) FROM ${prefix}_window) >= ${CLAIM_SCAN_WINDOW}
|
|
2165
|
+
OR ${scope}.lane IN (
|
|
2166
|
+
SELECT full_lane.lane FROM ${prefix}_window AS full_lane
|
|
2167
|
+
GROUP BY full_lane.lane HAVING count(*) >= ${LANE_SCAN_WINDOW}
|
|
2168
|
+
))
|
|
2169
|
+
`;
|
|
2170
|
+
}
|
|
2171
|
+
/** `<prefix>_heads`: the head of every key of the saturated lanes. */
|
|
2172
|
+
#keyHeads({ prefix }) {
|
|
2173
|
+
return `), ${prefix}_heads AS MATERIALIZED (
|
|
2174
|
+
SELECT head.id, head.lane, head.available_at, head.handler, head.key
|
|
2175
|
+
FROM ${prefix}_saturated AS scope
|
|
2176
|
+
CROSS JOIN LATERAL (${keyHeads(this.#names, "scope")}) AS head
|
|
2177
|
+
`;
|
|
2178
|
+
}
|
|
2179
|
+
/** `<prefix>_keyed`: the due heads that are not in flight and whose key runs nowhere. */
|
|
2180
|
+
#keyedHeads({ prefix, running, limit }) {
|
|
2181
|
+
return `), ${prefix}_keyed AS MATERIALIZED (
|
|
2182
|
+
SELECT heads.id, heads.lane, heads.available_at
|
|
2183
|
+
FROM ${prefix}_heads AS heads
|
|
2184
|
+
JOIN ${prefix}_saturated AS scope ON scope.lane = heads.lane
|
|
2185
|
+
WHERE heads.available_at <= $1::timestamptz AND heads.id <> ALL (scope.in_flight) AND ${notRunning(running, "heads")}
|
|
2186
|
+
ORDER BY heads.available_at, heads.id
|
|
2187
|
+
LIMIT ${limit}
|
|
2188
|
+
`;
|
|
2189
|
+
}
|
|
2190
|
+
/** `<prefix>_unkeyed`: the due unkeyed deliveries of the saturated lanes that are not in flight. */
|
|
2191
|
+
#unkeyedRecent({ prefix, limit }) {
|
|
2192
|
+
const deliveries = this.#names.deliveries;
|
|
2193
|
+
return `), ${prefix}_unkeyed AS MATERIALIZED (
|
|
2194
|
+
SELECT recent.id, recent.lane, recent.available_at
|
|
2195
|
+
FROM ${prefix}_saturated AS scope
|
|
2196
|
+
CROSS JOIN LATERAL (
|
|
2197
|
+
SELECT delivery.id, delivery.lane, delivery.available_at
|
|
2198
|
+
FROM ${deliveries} AS delivery
|
|
2199
|
+
WHERE delivery.lane = scope.lane AND delivery.key IS NULL AND delivery.status = 'pending'
|
|
2200
|
+
AND delivery.available_at <= $1::timestamptz AND ${HANDLER_PREDICATE}
|
|
2201
|
+
AND delivery.id <> ALL (scope.in_flight)
|
|
2202
|
+
ORDER BY delivery.available_at, delivery.id
|
|
2203
|
+
LIMIT ${limit}
|
|
2204
|
+
) AS recent
|
|
2205
|
+
ORDER BY recent.available_at, recent.id
|
|
2206
|
+
LIMIT ${limit}
|
|
2207
|
+
`;
|
|
2208
|
+
}
|
|
2209
|
+
/** `<prefix>_candidates`: the union of the fast pick and the slow picks, oldest first, at most `limit`. */
|
|
2210
|
+
#unionCandidates({ prefix, limit }) {
|
|
2211
|
+
return `), ${prefix}_candidates AS MATERIALIZED (
|
|
2212
|
+
SELECT chosen.id, chosen.lane, chosen.available_at FROM (
|
|
2213
|
+
SELECT fast.id, fast.lane, fast.available_at FROM ${prefix}_fast AS fast
|
|
2214
|
+
UNION
|
|
2215
|
+
SELECT keyed.id, keyed.lane, keyed.available_at FROM ${prefix}_keyed AS keyed
|
|
2216
|
+
UNION
|
|
2217
|
+
SELECT unkeyed.id, unkeyed.lane, unkeyed.available_at FROM ${prefix}_unkeyed AS unkeyed
|
|
2218
|
+
) AS chosen
|
|
2219
|
+
ORDER BY chosen.available_at, chosen.id
|
|
2220
|
+
LIMIT ${limit}
|
|
2221
|
+
)`;
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
|
|
2225
|
+
// src/statements-lanes.ts
|
|
2226
|
+
var FREE_LANE_SURPLUS = 16;
|
|
2055
2227
|
var freeLane = (alias) => `(${alias}.lease_expires_at IS NULL OR ${alias}.lease_expires_at <= $1::timestamptz
|
|
2056
2228
|
OR (${alias}.lease_owner = $4::text AND ${alias}.claim_floor < $6::bigint))`;
|
|
2057
2229
|
var NO_FREE_LANES = `), free_lanes AS (
|
|
@@ -2063,8 +2235,10 @@ var NO_FREE_LANES = `), free_lanes AS (
|
|
|
2063
2235
|
)`;
|
|
2064
2236
|
var LaneStatements = class {
|
|
2065
2237
|
#names;
|
|
2238
|
+
#candidateStatements;
|
|
2066
2239
|
constructor(names) {
|
|
2067
2240
|
this.#names = names;
|
|
2241
|
+
this.#candidateStatements = new CandidateStatements(names);
|
|
2068
2242
|
}
|
|
2069
2243
|
/**
|
|
2070
2244
|
* A claim in one statement, the only one in the common case. Parameters:
|
|
@@ -2139,7 +2313,7 @@ var LaneStatements = class {
|
|
|
2139
2313
|
CROSS JOIN LATERAL unnest(held_scope.in_flight) AS flying (id)
|
|
2140
2314
|
JOIN ${deliveries} AS delivery ON delivery.id = flying.id
|
|
2141
2315
|
WHERE delivery.status = 'pending' AND delivery.key IS NOT NULL
|
|
2142
|
-
), ${this.#candidates("held", { scope: "held_scope", running: "held_running", limit: "$3::int" })}, wanted AS MATERIALIZED (
|
|
2316
|
+
), ${this.#candidateStatements.candidates("held", { scope: "held_scope", running: "held_running", limit: "$3::int" })}, wanted AS MATERIALIZED (
|
|
2143
2317
|
SELECT CASE WHEN (SELECT value FROM stale) THEN 0
|
|
2144
2318
|
ELSE $3::int - (SELECT count(*) FROM held_candidates)::int END AS count
|
|
2145
2319
|
`;
|
|
@@ -2228,7 +2402,7 @@ var LaneStatements = class {
|
|
|
2228
2402
|
WHERE locked.epoch = seen.epoch AND locked.in_flight = seen.in_flight
|
|
2229
2403
|
AND locked.lease_owner IS NOT DISTINCT FROM seen.lease_owner
|
|
2230
2404
|
AND locked.lease_expires_at IS NOT DISTINCT FROM seen.lease_expires_at
|
|
2231
|
-
), ${this.#candidates("free", { scope: "free_scope", running: null, limit: "(SELECT count FROM wanted)" })}`;
|
|
2405
|
+
), ${this.#candidateStatements.candidates("free", { scope: "free_scope", running: null, limit: "(SELECT count FROM wanted)" })}`;
|
|
2232
2406
|
}
|
|
2233
2407
|
/** The picked deliveries of held and taken lanes with their events, and the picks per lane. */
|
|
2234
2408
|
#claimPicks() {
|
|
@@ -2317,113 +2491,6 @@ SELECT (SELECT value FROM stale) AS stale,
|
|
|
2317
2491
|
FROM (SELECT 1) AS anchor
|
|
2318
2492
|
LEFT JOIN picked_rows AS picked ON true
|
|
2319
2493
|
ORDER BY picked.due_at, picked.delivery_id`;
|
|
2320
|
-
}
|
|
2321
|
-
/**
|
|
2322
|
-
* CTEs that pick up to `limit` claimable deliveries from the lanes of the CTE `scope` (columns `lane`,
|
|
2323
|
-
* `epoch`, `in_flight`), named after `prefix`; the result is `<prefix>_candidates (id, lane, available_at)`.
|
|
2324
|
-
* The rules are those of `ClaimStatements.claim`. The fast window reads the first {@link LANE_SCAN_WINDOW} due
|
|
2325
|
-
* deliveries of each lane (at most {@link CLAIM_SCAN_WINDOW} together); of a key, only the lowest sequence
|
|
2326
|
-
* in the window can be its head, so only that one is checked for an earlier pending delivery. Only when
|
|
2327
|
-
* that yields fewer than `limit` are the lanes whose window filled up looked at again with a loose index
|
|
2328
|
-
* scan over the heads of all their keys. `running` names the CTE of the keyed deliveries running in the
|
|
2329
|
-
* lanes, or is null when nothing runs there.
|
|
2330
|
-
*/
|
|
2331
|
-
#candidates(prefix, {
|
|
2332
|
-
scope,
|
|
2333
|
-
running,
|
|
2334
|
-
limit
|
|
2335
|
-
}) {
|
|
2336
|
-
const spec = { prefix, scope, running, limit };
|
|
2337
|
-
return this.#fastCandidates(spec) + this.#slowCandidates(spec);
|
|
2338
|
-
}
|
|
2339
|
-
/** The fast window of each lane and the claimable deliveries in it (`<prefix>_window`, `<prefix>_fast`). */
|
|
2340
|
-
#fastCandidates({ prefix, scope, running, limit }) {
|
|
2341
|
-
const deliveries = this.#names.deliveries;
|
|
2342
|
-
return `${prefix}_window AS MATERIALIZED (
|
|
2343
|
-
SELECT recent.id, recent.lane, recent.available_at, recent.handler, recent.key, recent.sequence
|
|
2344
|
-
FROM ${scope}
|
|
2345
|
-
CROSS JOIN LATERAL (
|
|
2346
|
-
SELECT delivery.id, delivery.lane, delivery.available_at, delivery.handler, delivery.key, delivery.sequence
|
|
2347
|
-
FROM ${deliveries} AS delivery
|
|
2348
|
-
WHERE delivery.lane = ${scope}.lane AND delivery.status = 'pending'
|
|
2349
|
-
AND delivery.available_at <= $1::timestamptz AND ${HANDLER_PREDICATE}
|
|
2350
|
-
AND delivery.id <> ALL (${scope}.in_flight)
|
|
2351
|
-
ORDER BY delivery.available_at, delivery.id
|
|
2352
|
-
LIMIT ${LANE_SCAN_WINDOW}
|
|
2353
|
-
) AS recent
|
|
2354
|
-
ORDER BY recent.available_at, recent.id
|
|
2355
|
-
LIMIT ${CLAIM_SCAN_WINDOW}
|
|
2356
|
-
), ${prefix}_fast AS MATERIALIZED (
|
|
2357
|
-
SELECT picked.id, picked.lane, picked.available_at FROM (
|
|
2358
|
-
SELECT unkeyed.id, unkeyed.lane, unkeyed.available_at FROM ${prefix}_window AS unkeyed WHERE unkeyed.key IS NULL
|
|
2359
|
-
UNION ALL
|
|
2360
|
-
SELECT head.id, head.lane, head.available_at
|
|
2361
|
-
FROM (
|
|
2362
|
-
SELECT DISTINCT ON (keyed.handler, keyed.key) keyed.id, keyed.lane, keyed.available_at, keyed.handler, keyed.key, keyed.sequence
|
|
2363
|
-
FROM ${prefix}_window AS keyed
|
|
2364
|
-
WHERE keyed.key IS NOT NULL
|
|
2365
|
-
ORDER BY keyed.handler, keyed.key, keyed.sequence
|
|
2366
|
-
) AS head
|
|
2367
|
-
WHERE NOT EXISTS (
|
|
2368
|
-
SELECT 1 FROM ${deliveries} AS earlier
|
|
2369
|
-
WHERE earlier.lane = head.lane AND earlier.handler = head.handler AND earlier.key = head.key
|
|
2370
|
-
AND earlier.status = 'pending' AND earlier.key IS NOT NULL AND earlier.sequence < head.sequence
|
|
2371
|
-
) AND ${notRunning(running, "head")}
|
|
2372
|
-
) AS picked
|
|
2373
|
-
ORDER BY picked.available_at, picked.id
|
|
2374
|
-
LIMIT ${limit}
|
|
2375
|
-
`;
|
|
2376
|
-
}
|
|
2377
|
-
/**
|
|
2378
|
-
* For lanes whose window filled up while the fast pick came up short: the heads of all their keys and
|
|
2379
|
-
* their unkeyed deliveries, then the union with the fast pick (`<prefix>_candidates`).
|
|
2380
|
-
*/
|
|
2381
|
-
#slowCandidates({ prefix, scope, running, limit }) {
|
|
2382
|
-
const deliveries = this.#names.deliveries;
|
|
2383
|
-
return `), ${prefix}_saturated AS MATERIALIZED (
|
|
2384
|
-
SELECT ${scope}.lane, ${scope}.in_flight FROM ${scope}
|
|
2385
|
-
WHERE (SELECT count(*) FROM ${prefix}_fast) < ${limit}
|
|
2386
|
-
AND ((SELECT count(*) FROM ${prefix}_window) >= ${CLAIM_SCAN_WINDOW}
|
|
2387
|
-
OR ${scope}.lane IN (
|
|
2388
|
-
SELECT full_lane.lane FROM ${prefix}_window AS full_lane
|
|
2389
|
-
GROUP BY full_lane.lane HAVING count(*) >= ${LANE_SCAN_WINDOW}
|
|
2390
|
-
))
|
|
2391
|
-
), ${prefix}_heads AS MATERIALIZED (
|
|
2392
|
-
SELECT head.id, head.lane, head.available_at, head.handler, head.key
|
|
2393
|
-
FROM ${prefix}_saturated AS scope
|
|
2394
|
-
CROSS JOIN LATERAL (${keyHeads(this.#names, "scope")}) AS head
|
|
2395
|
-
), ${prefix}_keyed AS MATERIALIZED (
|
|
2396
|
-
SELECT heads.id, heads.lane, heads.available_at
|
|
2397
|
-
FROM ${prefix}_heads AS heads
|
|
2398
|
-
JOIN ${prefix}_saturated AS scope ON scope.lane = heads.lane
|
|
2399
|
-
WHERE heads.available_at <= $1::timestamptz AND heads.id <> ALL (scope.in_flight) AND ${notRunning(running, "heads")}
|
|
2400
|
-
ORDER BY heads.available_at, heads.id
|
|
2401
|
-
LIMIT ${limit}
|
|
2402
|
-
), ${prefix}_unkeyed AS MATERIALIZED (
|
|
2403
|
-
SELECT recent.id, recent.lane, recent.available_at
|
|
2404
|
-
FROM ${prefix}_saturated AS scope
|
|
2405
|
-
CROSS JOIN LATERAL (
|
|
2406
|
-
SELECT delivery.id, delivery.lane, delivery.available_at
|
|
2407
|
-
FROM ${deliveries} AS delivery
|
|
2408
|
-
WHERE delivery.lane = scope.lane AND delivery.key IS NULL AND delivery.status = 'pending'
|
|
2409
|
-
AND delivery.available_at <= $1::timestamptz AND ${HANDLER_PREDICATE}
|
|
2410
|
-
AND delivery.id <> ALL (scope.in_flight)
|
|
2411
|
-
ORDER BY delivery.available_at, delivery.id
|
|
2412
|
-
LIMIT ${limit}
|
|
2413
|
-
) AS recent
|
|
2414
|
-
ORDER BY recent.available_at, recent.id
|
|
2415
|
-
LIMIT ${limit}
|
|
2416
|
-
), ${prefix}_candidates AS MATERIALIZED (
|
|
2417
|
-
SELECT chosen.id, chosen.lane, chosen.available_at FROM (
|
|
2418
|
-
SELECT fast.id, fast.lane, fast.available_at FROM ${prefix}_fast AS fast
|
|
2419
|
-
UNION
|
|
2420
|
-
SELECT keyed.id, keyed.lane, keyed.available_at FROM ${prefix}_keyed AS keyed
|
|
2421
|
-
UNION
|
|
2422
|
-
SELECT unkeyed.id, unkeyed.lane, unkeyed.available_at FROM ${prefix}_unkeyed AS unkeyed
|
|
2423
|
-
) AS chosen
|
|
2424
|
-
ORDER BY chosen.available_at, chosen.id
|
|
2425
|
-
LIMIT ${limit}
|
|
2426
|
-
)`;
|
|
2427
2494
|
}
|
|
2428
2495
|
/**
|
|
2429
2496
|
* Takes up to `$3` free lanes (no lease, or an expired one) that have a due
|
|
@@ -2784,8 +2851,12 @@ var SetupStatements = class {
|
|
|
2784
2851
|
WHERE n.nspname = '${this.#names.schema}' AND c.relname = '${this.#names.lanesTableName}' AND d.description = $1::text
|
|
2785
2852
|
) AS current`;
|
|
2786
2853
|
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Takes the setup lock. Returns a boolean column rather than the `void` of the lock function, which
|
|
2856
|
+
* some libraries cannot read (Prisma's raw queries reject a `void` column).
|
|
2857
|
+
*/
|
|
2787
2858
|
get setupLock() {
|
|
2788
|
-
return "SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))";
|
|
2859
|
+
return "SELECT true AS locked FROM pg_advisory_xact_lock(hashtextextended($1::text, 0))";
|
|
2789
2860
|
}
|
|
2790
2861
|
};
|
|
2791
2862
|
|
|
@@ -2810,8 +2881,6 @@ var PostgresStatements = class {
|
|
|
2810
2881
|
};
|
|
2811
2882
|
|
|
2812
2883
|
// src/postgres-event-store.ts
|
|
2813
|
-
var WAKE_UPS = ["notify", "poll"];
|
|
2814
|
-
var DEFAULT_PURGE_BATCH_SIZE = 1e3;
|
|
2815
2884
|
var PostgresEventStore = class {
|
|
2816
2885
|
#state;
|
|
2817
2886
|
#appender;
|
|
@@ -2838,10 +2907,9 @@ var PostgresEventStore = class {
|
|
|
2838
2907
|
queryOptions,
|
|
2839
2908
|
notifies: wakeUp === "notify",
|
|
2840
2909
|
query: ({ sql, parameters, transaction }) => executor.query(sql, parameters, transaction, queryOptions),
|
|
2841
|
-
handedOut:
|
|
2910
|
+
handedOut: new internal.HandedOutLedger(),
|
|
2842
2911
|
unreconciled: /* @__PURE__ */ new Set(),
|
|
2843
2912
|
generations: /* @__PURE__ */ new Map(),
|
|
2844
|
-
ownerQueues: /* @__PURE__ */ new Map(),
|
|
2845
2913
|
drainingOwners: /* @__PURE__ */ new Set(),
|
|
2846
2914
|
ownedPool,
|
|
2847
2915
|
pool,
|
|
@@ -2948,7 +3016,7 @@ var PostgresEventStore = class {
|
|
|
2948
3016
|
return this.#claims.claim(request);
|
|
2949
3017
|
}
|
|
2950
3018
|
extendLease(lease, leaseExpiresAt) {
|
|
2951
|
-
return this.#completion.extendLease(lease, leaseExpiresAt);
|
|
3019
|
+
return Promise.resolve(this.#completion.extendLease({ lease, leaseExpiresAt }));
|
|
2952
3020
|
}
|
|
2953
3021
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
2954
3022
|
complete(lease, now, transaction) {
|
|
@@ -2959,15 +3027,24 @@ var PostgresEventStore = class {
|
|
|
2959
3027
|
}
|
|
2960
3028
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
2961
3029
|
reschedule(lease, availableAt, error, now) {
|
|
2962
|
-
return
|
|
3030
|
+
return Promise.resolve(
|
|
3031
|
+
this.#completion.changeClaim({
|
|
3032
|
+
lease,
|
|
3033
|
+
change: { kind: "reschedule", availableAt, error, now }
|
|
3034
|
+
})
|
|
3035
|
+
);
|
|
2963
3036
|
}
|
|
2964
3037
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
2965
3038
|
bury(lease, error, now) {
|
|
2966
|
-
return
|
|
3039
|
+
return Promise.resolve(
|
|
3040
|
+
this.#completion.changeClaim({ lease, change: { kind: "bury", error, now } })
|
|
3041
|
+
);
|
|
2967
3042
|
}
|
|
2968
3043
|
/** Returns a leased delivery to pending, due now, without counting the attempt. */
|
|
2969
3044
|
release(lease, now) {
|
|
2970
|
-
return
|
|
3045
|
+
return Promise.resolve(
|
|
3046
|
+
this.#completion.changeClaim({ lease, change: { kind: "release", now } })
|
|
3047
|
+
);
|
|
2971
3048
|
}
|
|
2972
3049
|
/**
|
|
2973
3050
|
* Calls `listener` soon after a transaction that published deliveries due at once commits, in any
|
|
@@ -3046,38 +3123,14 @@ var PostgresEventStore = class {
|
|
|
3046
3123
|
}
|
|
3047
3124
|
}
|
|
3048
3125
|
};
|
|
3049
|
-
function resolveStoreSettings(options) {
|
|
3050
|
-
const sources = [options.pool, options.connectionString, options.executor].filter(
|
|
3051
|
-
(source) => source !== void 0
|
|
3052
|
-
);
|
|
3053
|
-
if (sources.length !== 1) {
|
|
3054
|
-
throw new core.ConfigurationError(
|
|
3055
|
-
"PostgresEventStore needs exactly one of pool, connectionString or executor"
|
|
3056
|
-
);
|
|
3057
|
-
}
|
|
3058
|
-
const purgeBatchSize = internal.assertPositiveInteger(
|
|
3059
|
-
options.purgeBatchSize ?? DEFAULT_PURGE_BATCH_SIZE,
|
|
3060
|
-
"purgeBatchSize"
|
|
3061
|
-
);
|
|
3062
|
-
const wakeUp = options.wakeUp ?? "notify";
|
|
3063
|
-
if (!WAKE_UPS.includes(wakeUp)) {
|
|
3064
|
-
throw new core.ConfigurationError(`wakeUp must be 'notify' or 'poll', received ${String(wakeUp)}`);
|
|
3065
|
-
}
|
|
3066
|
-
const preparedStatements = options.preparedStatements ?? true;
|
|
3067
|
-
if (typeof preparedStatements !== "boolean") {
|
|
3068
|
-
throw new core.ConfigurationError(
|
|
3069
|
-
`preparedStatements must be a boolean, received ${String(preparedStatements)}`
|
|
3070
|
-
);
|
|
3071
|
-
}
|
|
3072
|
-
return {
|
|
3073
|
-
purgeBatchSize,
|
|
3074
|
-
wakeUp,
|
|
3075
|
-
queryOptions: preparedStatements ? { prepare: true } : void 0
|
|
3076
|
-
};
|
|
3077
|
-
}
|
|
3078
3126
|
function connect(options, { logger, owned }) {
|
|
3079
3127
|
if (options.executor !== void 0) {
|
|
3080
|
-
|
|
3128
|
+
const { executor: executor2, pool: pool2 } = options;
|
|
3129
|
+
return {
|
|
3130
|
+
executor: pool2 === void 0 ? executor2 : withRootPool(executor2, new PgExecutor(pool2, { logger })),
|
|
3131
|
+
pool: pool2,
|
|
3132
|
+
ownedPool: void 0
|
|
3133
|
+
};
|
|
3081
3134
|
}
|
|
3082
3135
|
const pool = options.pool ?? new pg.Pool({
|
|
3083
3136
|
...options.poolConfig,
|