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