@rotorsoft/act 1.23.38 → 1.23.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2308,7 +2308,7 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
2308
2308
  );
2309
2309
  } catch (error) {
2310
2310
  if (error instanceof ConcurrencyError) {
2311
- await cache().invalidate(stream);
2311
+ cache().invalidate(stream).catch((err) => log().error(err));
2312
2312
  }
2313
2313
  throw error;
2314
2314
  }
@@ -2616,13 +2616,6 @@ var EMPTY_DRAIN = {
2616
2616
  acked: [],
2617
2617
  blocked: []
2618
2618
  };
2619
- var contain = (logger, sink, fn) => {
2620
- try {
2621
- fn();
2622
- } catch (error) {
2623
- logger.error(error, `${sink} listener threw`);
2624
- }
2625
- };
2626
2619
  var DrainController = class {
2627
2620
  _armed = false;
2628
2621
  _locked = false;
@@ -2731,7 +2724,7 @@ var DrainController = class {
2731
2724
  leading,
2732
2725
  eventLimit,
2733
2726
  leaseMillis,
2734
- (b) => contain(this._deps.logger, "blocked", () => this._deps.on_blocked(b)),
2727
+ (b) => this._deps.on_blocked(b),
2735
2728
  this._deps.lane
2736
2729
  );
2737
2730
  if (!cycle) {
@@ -2749,8 +2742,7 @@ var DrainController = class {
2749
2742
  if (next !== void 0) this._defer.set(h.lease.stream, next);
2750
2743
  }
2751
2744
  if (this._defer.size > 0) this._defer.schedule();
2752
- if (acked.length)
2753
- contain(this._deps.logger, "acked", () => this._deps.on_acked(acked));
2745
+ if (acked.length) this._deps.on_acked(acked);
2754
2746
  if (closeable.length) await this._deps.on_close(closeable);
2755
2747
  this._deps.breaker.passed();
2756
2748
  const has_errors = handled.some(({ error }) => error);
@@ -3029,8 +3021,7 @@ var SettleLoop = class {
3029
3021
  const made_progress = subscribed > 0 || drain.acked.length > 0 || drain.blocked.length > 0 || last_id > after_before;
3030
3022
  if (!made_progress) break;
3031
3023
  }
3032
- if (settled_drain)
3033
- contain(log(), "settled", () => this._deps.on_settled(settled_drain));
3024
+ if (settled_drain) this._deps.on_settled(settled_drain);
3034
3025
  })().catch((err) => {
3035
3026
  this._deps.breaker.failed(Date.now(), err);
3036
3027
  }).finally(() => {
@@ -3102,9 +3093,41 @@ var Act = class {
3102
3093
  /**
3103
3094
  * Emit a lifecycle event. The payload type is inferred from the event name
3104
3095
  * via {@link ActLifecycleEvents}.
3096
+ *
3097
+ * **Every listener is contained individually.** Lifecycle listeners are
3098
+ * observers — `observability.md` promises that a throwing one is "contained,
3099
+ * not fatal" — and containment belongs here rather than at each call site,
3100
+ * for two reasons the previous arrangement got wrong (#1437):
3101
+ *
3102
+ * - Wrapping the *emit* rather than each *listener* still let the first
3103
+ * thrower abort the rest: `EventEmitter.emit` stops dispatching on the
3104
+ * first exception, so a second `app.on("acked", …)` never ran. The
3105
+ * documented "the remaining sinks still fire" was false whenever an event
3106
+ * had more than one listener. Same lesson as #1423, where guarding the
3107
+ * loop instead of each callback left every later SSE subscriber unserved.
3108
+ * - Only *some* call sites wrapped at all. The drain contained `acked` and
3109
+ * `blocked`; `committed`, `forgotten` and `close()`'s `closed` did not, so
3110
+ * a throwing listener rejected `do()`, `forget()` and `close()` **after**
3111
+ * their durable work had already landed. A caller retrying a "failed"
3112
+ * `do()` writes the event twice, since the framework has no dedup by
3113
+ * design.
3114
+ *
3115
+ * Containing here makes it a property of emitting, so a new lifecycle event
3116
+ * cannot reintroduce the gap by omission.
3117
+ *
3118
+ * Uses `rawListeners` so `once` wrappers still de-register themselves, and
3119
+ * returns "had listeners" to preserve the `EventEmitter.emit` contract.
3105
3120
  */
3106
3121
  emit(event, args) {
3107
- return this._emitter.emit(event, args);
3122
+ const listeners = this._emitter.rawListeners(event);
3123
+ for (const listener of listeners) {
3124
+ try {
3125
+ listener(args);
3126
+ } catch (error) {
3127
+ this._logger.error(error, `${String(event)} listener threw`);
3128
+ }
3129
+ }
3130
+ return listeners.length > 0;
3108
3131
  }
3109
3132
  /**
3110
3133
  * The single store-failure handler: log it, then emit the `error`
@@ -3346,11 +3369,7 @@ var Act = class {
3346
3369
  with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
3347
3370
  });
3348
3371
  this._forget_closed_subscriptions(result);
3349
- try {
3350
- this.emit("closed", result);
3351
- } catch (err) {
3352
- this._logger.error(err, "closed listener threw");
3353
- }
3372
+ this.emit("closed", result);
3354
3373
  },
3355
3374
  breaker: this._breaker,
3356
3375
  // Re-scope the per-lane worker's auto-start ticks so their drain