@observertc/observer-js 1.0.0-beta.15 → 1.0.0-beta.16

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.mjs CHANGED
@@ -2632,6 +2632,10 @@ var ObservedClient = class extends EventEmitter2 {
2632
2632
  }
2633
2633
  close() {
2634
2634
  if (this.closed) return;
2635
+ if (this.closeTimer) {
2636
+ clearTimeout(this.closeTimer);
2637
+ this.closeTimer = void 0;
2638
+ }
2635
2639
  this._flushPendingInjections();
2636
2640
  const activeIssues = [...this.activeIssues.values()];
2637
2641
  for (const issue of activeIssues) {
@@ -2794,6 +2798,7 @@ var ObservedClient = class extends EventEmitter2 {
2794
2798
  this.closeTimer = setTimeout(() => {
2795
2799
  this.close();
2796
2800
  }, this.settings.closeClientIfIdleForMs);
2801
+ this.closeTimer.unref?.();
2797
2802
  }
2798
2803
  this._activeSample = void 0;
2799
2804
  try {
@@ -3265,6 +3270,24 @@ var Detectors = class {
3265
3270
  constructor(...detectors) {
3266
3271
  this._detectors = detectors;
3267
3272
  }
3273
+ /**
3274
+ * Every registered detector, in registration order.
3275
+ *
3276
+ * This is **the** way to get hold of an instance: `addDetector` / `addObserverDetector` are
3277
+ * chainable and return the owning entity, so the registry is where instances live. Read it to
3278
+ * inspect a detector's state, or to pick one out and {@link remove} it.
3279
+ *
3280
+ * A copy, not the live array — a caller iterating this while removing would otherwise skip
3281
+ * entries, and that is exactly what "remove the ones that look like X" does.
3282
+ */
3283
+ get instances() {
3284
+ return [...this._detectors];
3285
+ }
3286
+ /** Iterate the registry directly: `for (const detector of call.detectors)`. */
3287
+ [Symbol.iterator]() {
3288
+ return this.instances[Symbol.iterator]();
3289
+ }
3290
+ /** The names in registration order. Duplicates are meaningful — see {@link getAll}. */
3268
3291
  get listOfNames() {
3269
3292
  return this._detectors.map((d) => d.name);
3270
3293
  }
@@ -3274,12 +3297,48 @@ var Detectors = class {
3274
3297
  add(detector) {
3275
3298
  this._detectors.push(detector);
3276
3299
  }
3300
+ /** The first detector registered under `name`. See {@link getAll} when several can share one. */
3277
3301
  get(name) {
3278
3302
  return this._detectors.find((detector) => detector.name === name);
3279
3303
  }
3304
+ /**
3305
+ * Every detector registered under `name`.
3306
+ *
3307
+ * More than one is legitimate: `ClientPopulationIssueDetector` is meant to be added once per
3308
+ * `groupBy` axis, and two instances of it share a name.
3309
+ */
3310
+ getAll(name) {
3311
+ return this._detectors.filter((detector) => detector.name === name);
3312
+ }
3313
+ has(name) {
3314
+ return this._detectors.some((detector) => detector.name === name);
3315
+ }
3316
+ /** Remove one specific instance. Returns `false` if it was not registered here. */
3280
3317
  remove(detector) {
3281
- this._detectors = this._detectors.filter((d) => d !== detector);
3318
+ const remaining = this._detectors.filter((candidate) => candidate !== detector);
3319
+ if (remaining.length === this._detectors.length) return false;
3320
+ this._detectors = remaining;
3282
3321
  this._close(detector);
3322
+ return true;
3323
+ }
3324
+ /**
3325
+ * Remove **every** detector registered under `name`, returning how many were removed.
3326
+ *
3327
+ * All of them rather than the first, because a name can legitimately be registered more than once
3328
+ * (see {@link getAll}) and "remove the `client-population-issue-detector`" cannot sensibly mean
3329
+ * "remove whichever axis happens to be first in the array". Removing all of them is the only
3330
+ * behaviour that leaves the registry in a state the caller can predict from the name alone.
3331
+ *
3332
+ * Each removed detector gets `close()`, so trackers unsubscribe from the issue registry, bus
3333
+ * listeners drop, and timers clear — a detector removed without closing keeps being fed issues
3334
+ * forever.
3335
+ */
3336
+ removeByName(name) {
3337
+ const removed = this._detectors.filter((detector) => detector.name === name);
3338
+ if (removed.length === 0) return 0;
3339
+ this._detectors = this._detectors.filter((detector) => detector.name !== name);
3340
+ for (const detector of removed) this._close(detector);
3341
+ return removed.length;
3283
3342
  }
3284
3343
  update() {
3285
3344
  for (const detector of this._detectors) {
@@ -4181,6 +4240,12 @@ var ObservedCall = class extends EventEmitter3 {
4181
4240
  get score() {
4182
4241
  return this.calculatedScore.value;
4183
4242
  }
4243
+ /**
4244
+ * Build a call-scoped detector onto this call. Chainable.
4245
+ *
4246
+ * To get a handle on what was built — to inspect it, or to remove that exact instance later — read
4247
+ * it back off the registry: `call.detectors.getAll(name)`, or `call.detectors.instances`.
4248
+ */
4184
4249
  addDetector(name, config = {}) {
4185
4250
  if (this.closed) return this;
4186
4251
  let detector;
@@ -4213,6 +4278,27 @@ var ObservedCall = class extends EventEmitter3 {
4213
4278
  this.detectors.add(detector);
4214
4279
  return this;
4215
4280
  }
4281
+ /**
4282
+ * Remove a detector from **this call** by name, returning how many were removed.
4283
+ *
4284
+ * **Every** instance under the name goes — a name can legitimately be registered more than once.
4285
+ * When you want one of them specifically, go through the registry, which deals in instances:
4286
+ *
4287
+ * ```ts
4288
+ * const [ first ] = call.detectors.getAll('issue-fan-out-detector');
4289
+ *
4290
+ * call.detectors.remove(first);
4291
+ * ```
4292
+ *
4293
+ * Either route `close()`s the detector, so it unsubscribes from `activeIssuesRegistry` — without
4294
+ * that the registry keeps feeding a detector nobody is running any more, and its tracked set grows
4295
+ * for the life of the call.
4296
+ *
4297
+ * To stop building it on *future* calls too, use `observer.removeCallDetector(name)`.
4298
+ */
4299
+ removeDetector(name) {
4300
+ return this.detectors.removeByName(name);
4301
+ }
4216
4302
  /**
4217
4303
  * Raise a call-level (server-side) finding; surfaced on the Observer bus as `call-issue`.
4218
4304
  *
@@ -4227,6 +4313,10 @@ var ObservedCall = class extends EventEmitter3 {
4227
4313
  if (this.closed) return;
4228
4314
  this.update();
4229
4315
  this.closed = true;
4316
+ if (this.closeTimer) {
4317
+ clearTimeout(this.closeTimer);
4318
+ this.closeTimer = void 0;
4319
+ }
4230
4320
  let minSampleTimestamps;
4231
4321
  let maxSampleTimestamps;
4232
4322
  const clients = [...this.observedClients.values()];
@@ -4285,6 +4375,7 @@ var ObservedCall = class extends EventEmitter3 {
4285
4375
  this.closeTimer = setTimeout(() => {
4286
4376
  this.close();
4287
4377
  }, this.settings.closeCallIfEmptyForMs);
4378
+ this.closeTimer.unref?.();
4288
4379
  }
4289
4380
  }
4290
4381
  ++this.totalRemovedClients;
@@ -6247,6 +6338,12 @@ var Observer = class extends EventEmitter6 {
6247
6338
  get appData() {
6248
6339
  return this.config.appData;
6249
6340
  }
6341
+ /**
6342
+ * Build a cross-call detector onto `observer.detectors`. Chainable.
6343
+ *
6344
+ * To get a handle on what was built — to inspect it, or to remove that exact instance later — read
6345
+ * it back off the registry: `observer.detectors.getAll(name)`, or `observer.detectors.instances`.
6346
+ */
6250
6347
  addObserverDetector(name, config = {}) {
6251
6348
  if (this.closed) return this;
6252
6349
  let detector;
@@ -6290,10 +6387,41 @@ var Observer = class extends EventEmitter6 {
6290
6387
  this.callDetectorConfigs.set(name, config);
6291
6388
  return this;
6292
6389
  }
6293
- /** Stop building `name` on calls created from now on. Calls already open are untouched. */
6294
- removeCallDetector(name) {
6390
+ /**
6391
+ * Remove an observer-scoped detector by name, returning how many were removed.
6392
+ *
6393
+ * **Every** instance registered under the name goes, since a name can legitimately be registered
6394
+ * more than once (`ClientPopulationIssueDetector` is meant to be added once per `groupBy` axis).
6395
+ * When you want one of them specifically, go through the registry, which deals in instances:
6396
+ *
6397
+ * ```ts
6398
+ * const [ byBrowser, byOs ] = observer.detectors.getAll('client-population-issue-detector');
6399
+ *
6400
+ * observer.detectors.remove(byOs); // keeps the browser axis running
6401
+ * ```
6402
+ *
6403
+ * Either route `close()`s the detector, so it unsubscribes from the issue registry and drops any
6404
+ * timers or bus listeners it held.
6405
+ */
6406
+ removeObserverDetector(name) {
6407
+ return this.detectors.removeByName(name);
6408
+ }
6409
+ /**
6410
+ * Stop building `name` on calls created from now on.
6411
+ *
6412
+ * By default this also removes it from the calls **already open**, so that "remove this detector"
6413
+ * means the same thing whether you say it before or after a call started — the alternative leaves
6414
+ * a fleet where the detector is live on some calls and not others, decided by join time. Pass
6415
+ * `{ includeOpenCalls: false }` to change only what future calls are built with.
6416
+ *
6417
+ * Returns the number of live detector instances removed (`0` when only the config changed).
6418
+ */
6419
+ removeCallDetector(name, { includeOpenCalls = true } = {}) {
6295
6420
  this.callDetectorConfigs.delete(name);
6296
- return this;
6421
+ if (!includeOpenCalls) return 0;
6422
+ let removed = 0;
6423
+ for (const call of this.observedCalls.values()) removed += call.removeDetector(name);
6424
+ return removed;
6297
6425
  }
6298
6426
  /**
6299
6427
  * Start a structural check. It runs on each `observer.update()` until it can decide, reports once
@@ -6332,6 +6460,36 @@ var Observer = class extends EventEmitter6 {
6332
6460
  this.validators.add(validator);
6333
6461
  return this;
6334
6462
  }
6463
+ /**
6464
+ * Stop a running validation, by name or by instance. Returns how many were cancelled.
6465
+ *
6466
+ * Cancelling is **not** silent discarding. The validator finishes with `inconclusive` and the given
6467
+ * `reason`, emits `validation-ready` like any other completion, and removes itself. That matters
6468
+ * because anything waiting on the verdict — a deploy gate, a dashboard, a promise — would otherwise
6469
+ * wait forever, and because "we stopped asking" is a materially different outcome from "we asked
6470
+ * and learned nothing", which is exactly what `inconclusive` with a reason records.
6471
+ *
6472
+ * ```ts
6473
+ * observer.cancelValidator('simulcast-receivers', 'sfu redeployed');
6474
+ *
6475
+ * // or one specific instance — `observer.validators` holds what is running
6476
+ * for (const validator of observer.validators) observer.cancelValidator(validator, 'shutting down');
6477
+ * ```
6478
+ *
6479
+ * Pass a real reason. The default tells the reader nothing they could not already infer.
6480
+ */
6481
+ cancelValidator(target, reason = "cancelled") {
6482
+ const running = [...this.validators];
6483
+ const matching = typeof target === "string" ? running.filter((validator) => validator.name === target) : running.filter((validator) => validator === target);
6484
+ for (const validator of matching) {
6485
+ try {
6486
+ validator.cancel(reason);
6487
+ } catch (err) {
6488
+ logger8.warn("Error cancelling validator %s: %o", validator.name, err);
6489
+ }
6490
+ }
6491
+ return matching.length;
6492
+ }
6335
6493
  getObservedCall(callId) {
6336
6494
  if (this.closed || !this.observedCalls.has(callId)) return;
6337
6495
  return this.observedCalls.get(callId);
@@ -6419,7 +6577,7 @@ var Observer = class extends EventEmitter6 {
6419
6577
  this.closed = true;
6420
6578
  for (const call of [...this.observedCalls.values()]) call.close();
6421
6579
  this.detectors.clear();
6422
- for (const validator of [...this.validators]) validator.cancel();
6580
+ for (const validator of [...this.validators]) validator.cancel("observer closed");
6423
6581
  this.validators.clear();
6424
6582
  this.activeIssuesRegistry.clear();
6425
6583
  this._notify("observer-closed", { ...this.eventScope });