@relaybus/core 0.3.1 → 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 CHANGED
@@ -311,7 +311,12 @@ deploy, are still claimed and sent by the new route; the store rows keep their o
311
311
  ## Operations
312
312
 
313
313
  `listDeliveries(query)`, `requeue(ids)`, `statistics()`, `purgeExpired()`,
314
- `handlers()`, `runOnce()` and `drain()` (tests), `start()` and `stop()`.
314
+ `handlers()`, `runOnce()` and `drain()` (tests), `start()`, `stop()` and `wake()`.
315
+
316
+ **`wake()` after your own commit.** A publish in relaybus' own transaction wakes the dispatcher right after its
317
+ commit. A publish in your transaction commits at a time the bus cannot see: unless the store reports the commit
318
+ (PostgreSQL with `LISTEN/NOTIFY`), the dispatcher is woken 25, 100 and 250 ms after the latest publish and otherwise
319
+ finds the delivery at its next poll. Call `bus.wake()` right after such a commit to claim at once.
315
320
 
316
321
  **`stop({ timeout })` is bounded.** The whole shutdown (stop claiming, wait for running
317
322
  handlers, hand back leases, close transports and the store) takes at most `timeout`, default
package/dist/index.cjs CHANGED
@@ -2993,7 +2993,7 @@ var EventPublisher = class {
2993
2993
  context,
2994
2994
  delayMs
2995
2995
  }) {
2996
- const result = await this.#appendInTransaction(entries, context);
2996
+ const { result, committed } = await this.#appendInTransaction(entries, context);
2997
2997
  const appended = new Set(result.appendedEventIds);
2998
2998
  const published = entries.map((entry) => entry.event).filter((envelope) => appended.has(envelope.id));
2999
2999
  if (published.length > 0) {
@@ -3002,22 +3002,26 @@ var EventPublisher = class {
3002
3002
  deliveries: result.appendedDeliveryCount
3003
3003
  });
3004
3004
  if (delayMs === 0) {
3005
- this.#parts.scheduleWake();
3005
+ this.#parts.scheduleWake(committed);
3006
3006
  }
3007
3007
  }
3008
3008
  }
3009
+ /** Appends in the caller's transaction, else in one of the store's; `committed` tells which. */
3009
3010
  async #appendInTransaction(entries, { options, scope, now }) {
3010
3011
  const { store, transactionMode, transactionResolver } = this.#parts;
3011
3012
  const transaction = options.transaction ?? scope?.transaction ?? transactionResolver?.();
3012
3013
  if (transaction !== void 0) {
3013
- return store.append(transaction, entries, now);
3014
+ return { result: await store.append(transaction, entries, now), committed: false };
3014
3015
  }
3015
3016
  if (transactionMode === "required") {
3016
3017
  throw new chunkJ76M7XTS_cjs.TransactionRequiredError(
3017
3018
  "publish found no transaction. Pass { transaction }, publish inside bus.transaction(), configure transactionResolver, or set transactionMode to optional"
3018
3019
  );
3019
3020
  }
3020
- return store.transaction((ownTransaction) => store.append(ownTransaction, entries, now));
3021
+ const result = await store.transaction(
3022
+ (ownTransaction) => store.append(ownTransaction, entries, now)
3023
+ );
3024
+ return { result, committed: true };
3021
3025
  }
3022
3026
  async #createEnvelope(input) {
3023
3027
  const { event, options, id, scope, now, correlationId } = input;
@@ -3114,7 +3118,7 @@ var EventReceiver = class {
3114
3118
  const status = result.duplicateEventIds.length > 0 ? "duplicate" : "accepted";
3115
3119
  notifier.notify("onReceived", { envelope: normalized, status });
3116
3120
  if (result.appendedDeliveryCount > 0) {
3117
- scheduleWake();
3121
+ scheduleWake(true);
3118
3122
  }
3119
3123
  return status;
3120
3124
  }
@@ -3204,6 +3208,58 @@ function createInboxAndOutbox(core, hooks) {
3204
3208
  };
3205
3209
  }
3206
3210
 
3211
+ // src/bus/publish-wake-ups.ts
3212
+ var FIRST_WAKE_MS = 25;
3213
+ var SECOND_WAKE_MS = 100;
3214
+ var LAST_WAKE_MS = 250;
3215
+ var WAKE_DELAYS_MS = [FIRST_WAKE_MS, SECOND_WAKE_MS, LAST_WAKE_MS];
3216
+ var PublishWakeUps = class {
3217
+ #dependencies;
3218
+ #lastPublishAt = 0;
3219
+ #cancel;
3220
+ /** When the pending timer fires. */
3221
+ #dueAt = 0;
3222
+ constructor(dependencies) {
3223
+ this.#dependencies = dependencies;
3224
+ }
3225
+ /**
3226
+ * Wakes the dispatcher for a publish: at once when its transaction has `committed` (one of relaybus'
3227
+ * own), else at the wake-ups after it.
3228
+ */
3229
+ afterPublish(committed) {
3230
+ if (committed) {
3231
+ this.#dependencies.wake();
3232
+ return;
3233
+ }
3234
+ this.#lastPublishAt = this.#dependencies.clock.now().getTime();
3235
+ if (this.#cancel === void 0 || this.#dueAt > this.#lastPublishAt + FIRST_WAKE_MS) {
3236
+ this.cancel();
3237
+ this.#cancel = this.#arm(FIRST_WAKE_MS);
3238
+ }
3239
+ }
3240
+ cancel() {
3241
+ this.#cancel?.();
3242
+ this.#cancel = void 0;
3243
+ }
3244
+ #fire() {
3245
+ this.#cancel = void 0;
3246
+ this.#dependencies.wake();
3247
+ const elapsed = this.#dependencies.clock.now().getTime() - this.#lastPublishAt;
3248
+ const next = WAKE_DELAYS_MS.find((delay) => delay > elapsed);
3249
+ if (next !== void 0) {
3250
+ this.#cancel = this.#arm(next - elapsed);
3251
+ }
3252
+ }
3253
+ #arm(delayMs) {
3254
+ this.#dueAt = this.#dependencies.clock.now().getTime() + delayMs;
3255
+ return chunkJ76M7XTS_cjs.schedule(this.#dependencies.clock, {
3256
+ run: () => this.#fire(),
3257
+ delayMs,
3258
+ unref: true
3259
+ });
3260
+ }
3261
+ };
3262
+
3207
3263
  // src/transaction-budget.ts
3208
3264
  async function runBudgetedTransaction(store, { clock, timeoutMs, work }) {
3209
3265
  const startedAt = clock.now().getTime();
@@ -3236,20 +3292,24 @@ function createTimeoutError(timeoutMs, cause) {
3236
3292
 
3237
3293
  // src/relay-bus.ts
3238
3294
  var DEFAULT_DRAIN_BATCHES = 1e3;
3239
- var WAKE_DELAY_MS = 25;
3240
3295
  var RelayBus = class {
3241
3296
  #parts;
3242
3297
  #lifecycle;
3298
+ #wakeUps;
3243
3299
  constructor(options) {
3244
3300
  this.#parts = assembleRelayBus(options, {
3245
3301
  reportUnrouted: (envelope) => this.#reportUnrouted(envelope),
3246
- scheduleWake: () => this.#scheduleWake(),
3302
+ scheduleWake: (committed) => this.#scheduleWake(committed),
3247
3303
  statistics: () => this.statistics()
3248
3304
  });
3249
3305
  this.#lifecycle = new BusLifecycle({
3250
3306
  ...this.#parts,
3251
3307
  closeTransports: (deadline) => this.#closeTransports(deadline)
3252
3308
  });
3309
+ this.#wakeUps = new PublishWakeUps({
3310
+ clock: this.#parts.clock,
3311
+ wake: () => this.#parts.dispatcher.wake()
3312
+ });
3253
3313
  }
3254
3314
  get store() {
3255
3315
  return this.#parts.store;
@@ -3376,6 +3436,7 @@ var RelayBus = class {
3376
3436
  if (!first) {
3377
3437
  return stopping;
3378
3438
  }
3439
+ this.#wakeUps.cancel();
3379
3440
  const result = await stopping;
3380
3441
  if (!result.graceful) {
3381
3442
  this.#parts.logger.warn(
@@ -3409,6 +3470,13 @@ var RelayBus = class {
3409
3470
  listDeliveries(query = {}) {
3410
3471
  return this.#parts.store.listDeliveries(query);
3411
3472
  }
3473
+ /**
3474
+ * Asks the dispatcher to claim now. Call it right after committing your own transaction that published,
3475
+ * when the store cannot report the commit (see Tuning, "Wake-up after your transaction commits").
3476
+ */
3477
+ wake() {
3478
+ this.#parts.dispatcher.wake();
3479
+ }
3412
3480
  /** Moves dead deliveries back to pending with a fresh attempt budget. */
3413
3481
  async requeue(deliveryIds) {
3414
3482
  const moved = await this.#parts.store.requeue(deliveryIds, this.#parts.clock.now());
@@ -3480,15 +3548,11 @@ var RelayBus = class {
3480
3548
  const routes = registrations.filter((registration) => registration.forwardsToTransport).length;
3481
3549
  return { handlers: registrations.length - routes, routes };
3482
3550
  }
3483
- #scheduleWake() {
3551
+ #scheduleWake(committed) {
3484
3552
  if (this.#lifecycle.state !== "running" || this.#parts.dispatcher.watchesNewDeliveries) {
3485
3553
  return;
3486
3554
  }
3487
- chunkJ76M7XTS_cjs.schedule(this.#parts.clock, {
3488
- run: () => this.#parts.dispatcher.wake(),
3489
- delayMs: WAKE_DELAY_MS,
3490
- unref: true
3491
- });
3555
+ this.#wakeUps.afterPublish(committed);
3492
3556
  }
3493
3557
  /** Closes every transport, each at most until the deadline; past it they are asked to force. */
3494
3558
  async #closeTransports(deadline) {