@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.js CHANGED
@@ -2750,6 +2750,10 @@ var ObservedClient = class extends import_events2.EventEmitter {
2750
2750
  }
2751
2751
  close() {
2752
2752
  if (this.closed) return;
2753
+ if (this.closeTimer) {
2754
+ clearTimeout(this.closeTimer);
2755
+ this.closeTimer = void 0;
2756
+ }
2753
2757
  this._flushPendingInjections();
2754
2758
  const activeIssues = [...this.activeIssues.values()];
2755
2759
  for (const issue of activeIssues) {
@@ -2912,6 +2916,7 @@ var ObservedClient = class extends import_events2.EventEmitter {
2912
2916
  this.closeTimer = setTimeout(() => {
2913
2917
  this.close();
2914
2918
  }, this.settings.closeClientIfIdleForMs);
2919
+ this.closeTimer.unref?.();
2915
2920
  }
2916
2921
  this._activeSample = void 0;
2917
2922
  try {
@@ -3383,6 +3388,24 @@ var Detectors = class {
3383
3388
  constructor(...detectors) {
3384
3389
  this._detectors = detectors;
3385
3390
  }
3391
+ /**
3392
+ * Every registered detector, in registration order.
3393
+ *
3394
+ * This is **the** way to get hold of an instance: `addDetector` / `addObserverDetector` are
3395
+ * chainable and return the owning entity, so the registry is where instances live. Read it to
3396
+ * inspect a detector's state, or to pick one out and {@link remove} it.
3397
+ *
3398
+ * A copy, not the live array — a caller iterating this while removing would otherwise skip
3399
+ * entries, and that is exactly what "remove the ones that look like X" does.
3400
+ */
3401
+ get instances() {
3402
+ return [...this._detectors];
3403
+ }
3404
+ /** Iterate the registry directly: `for (const detector of call.detectors)`. */
3405
+ [Symbol.iterator]() {
3406
+ return this.instances[Symbol.iterator]();
3407
+ }
3408
+ /** The names in registration order. Duplicates are meaningful — see {@link getAll}. */
3386
3409
  get listOfNames() {
3387
3410
  return this._detectors.map((d) => d.name);
3388
3411
  }
@@ -3392,12 +3415,48 @@ var Detectors = class {
3392
3415
  add(detector) {
3393
3416
  this._detectors.push(detector);
3394
3417
  }
3418
+ /** The first detector registered under `name`. See {@link getAll} when several can share one. */
3395
3419
  get(name) {
3396
3420
  return this._detectors.find((detector) => detector.name === name);
3397
3421
  }
3422
+ /**
3423
+ * Every detector registered under `name`.
3424
+ *
3425
+ * More than one is legitimate: `ClientPopulationIssueDetector` is meant to be added once per
3426
+ * `groupBy` axis, and two instances of it share a name.
3427
+ */
3428
+ getAll(name) {
3429
+ return this._detectors.filter((detector) => detector.name === name);
3430
+ }
3431
+ has(name) {
3432
+ return this._detectors.some((detector) => detector.name === name);
3433
+ }
3434
+ /** Remove one specific instance. Returns `false` if it was not registered here. */
3398
3435
  remove(detector) {
3399
- this._detectors = this._detectors.filter((d) => d !== detector);
3436
+ const remaining = this._detectors.filter((candidate) => candidate !== detector);
3437
+ if (remaining.length === this._detectors.length) return false;
3438
+ this._detectors = remaining;
3400
3439
  this._close(detector);
3440
+ return true;
3441
+ }
3442
+ /**
3443
+ * Remove **every** detector registered under `name`, returning how many were removed.
3444
+ *
3445
+ * All of them rather than the first, because a name can legitimately be registered more than once
3446
+ * (see {@link getAll}) and "remove the `client-population-issue-detector`" cannot sensibly mean
3447
+ * "remove whichever axis happens to be first in the array". Removing all of them is the only
3448
+ * behaviour that leaves the registry in a state the caller can predict from the name alone.
3449
+ *
3450
+ * Each removed detector gets `close()`, so trackers unsubscribe from the issue registry, bus
3451
+ * listeners drop, and timers clear — a detector removed without closing keeps being fed issues
3452
+ * forever.
3453
+ */
3454
+ removeByName(name) {
3455
+ const removed = this._detectors.filter((detector) => detector.name === name);
3456
+ if (removed.length === 0) return 0;
3457
+ this._detectors = this._detectors.filter((detector) => detector.name !== name);
3458
+ for (const detector of removed) this._close(detector);
3459
+ return removed.length;
3401
3460
  }
3402
3461
  update() {
3403
3462
  for (const detector of this._detectors) {
@@ -4299,6 +4358,12 @@ var ObservedCall = class extends import_events3.EventEmitter {
4299
4358
  get score() {
4300
4359
  return this.calculatedScore.value;
4301
4360
  }
4361
+ /**
4362
+ * Build a call-scoped detector onto this call. Chainable.
4363
+ *
4364
+ * To get a handle on what was built — to inspect it, or to remove that exact instance later — read
4365
+ * it back off the registry: `call.detectors.getAll(name)`, or `call.detectors.instances`.
4366
+ */
4302
4367
  addDetector(name, config = {}) {
4303
4368
  if (this.closed) return this;
4304
4369
  let detector;
@@ -4331,6 +4396,27 @@ var ObservedCall = class extends import_events3.EventEmitter {
4331
4396
  this.detectors.add(detector);
4332
4397
  return this;
4333
4398
  }
4399
+ /**
4400
+ * Remove a detector from **this call** by name, returning how many were removed.
4401
+ *
4402
+ * **Every** instance under the name goes — a name can legitimately be registered more than once.
4403
+ * When you want one of them specifically, go through the registry, which deals in instances:
4404
+ *
4405
+ * ```ts
4406
+ * const [ first ] = call.detectors.getAll('issue-fan-out-detector');
4407
+ *
4408
+ * call.detectors.remove(first);
4409
+ * ```
4410
+ *
4411
+ * Either route `close()`s the detector, so it unsubscribes from `activeIssuesRegistry` — without
4412
+ * that the registry keeps feeding a detector nobody is running any more, and its tracked set grows
4413
+ * for the life of the call.
4414
+ *
4415
+ * To stop building it on *future* calls too, use `observer.removeCallDetector(name)`.
4416
+ */
4417
+ removeDetector(name) {
4418
+ return this.detectors.removeByName(name);
4419
+ }
4334
4420
  /**
4335
4421
  * Raise a call-level (server-side) finding; surfaced on the Observer bus as `call-issue`.
4336
4422
  *
@@ -4345,6 +4431,10 @@ var ObservedCall = class extends import_events3.EventEmitter {
4345
4431
  if (this.closed) return;
4346
4432
  this.update();
4347
4433
  this.closed = true;
4434
+ if (this.closeTimer) {
4435
+ clearTimeout(this.closeTimer);
4436
+ this.closeTimer = void 0;
4437
+ }
4348
4438
  let minSampleTimestamps;
4349
4439
  let maxSampleTimestamps;
4350
4440
  const clients = [...this.observedClients.values()];
@@ -4403,6 +4493,7 @@ var ObservedCall = class extends import_events3.EventEmitter {
4403
4493
  this.closeTimer = setTimeout(() => {
4404
4494
  this.close();
4405
4495
  }, this.settings.closeCallIfEmptyForMs);
4496
+ this.closeTimer.unref?.();
4406
4497
  }
4407
4498
  }
4408
4499
  ++this.totalRemovedClients;
@@ -6365,6 +6456,12 @@ var Observer = class extends import_events6.EventEmitter {
6365
6456
  get appData() {
6366
6457
  return this.config.appData;
6367
6458
  }
6459
+ /**
6460
+ * Build a cross-call detector onto `observer.detectors`. Chainable.
6461
+ *
6462
+ * To get a handle on what was built — to inspect it, or to remove that exact instance later — read
6463
+ * it back off the registry: `observer.detectors.getAll(name)`, or `observer.detectors.instances`.
6464
+ */
6368
6465
  addObserverDetector(name, config = {}) {
6369
6466
  if (this.closed) return this;
6370
6467
  let detector;
@@ -6408,10 +6505,41 @@ var Observer = class extends import_events6.EventEmitter {
6408
6505
  this.callDetectorConfigs.set(name, config);
6409
6506
  return this;
6410
6507
  }
6411
- /** Stop building `name` on calls created from now on. Calls already open are untouched. */
6412
- removeCallDetector(name) {
6508
+ /**
6509
+ * Remove an observer-scoped detector by name, returning how many were removed.
6510
+ *
6511
+ * **Every** instance registered under the name goes, since a name can legitimately be registered
6512
+ * more than once (`ClientPopulationIssueDetector` is meant to be added once per `groupBy` axis).
6513
+ * When you want one of them specifically, go through the registry, which deals in instances:
6514
+ *
6515
+ * ```ts
6516
+ * const [ byBrowser, byOs ] = observer.detectors.getAll('client-population-issue-detector');
6517
+ *
6518
+ * observer.detectors.remove(byOs); // keeps the browser axis running
6519
+ * ```
6520
+ *
6521
+ * Either route `close()`s the detector, so it unsubscribes from the issue registry and drops any
6522
+ * timers or bus listeners it held.
6523
+ */
6524
+ removeObserverDetector(name) {
6525
+ return this.detectors.removeByName(name);
6526
+ }
6527
+ /**
6528
+ * Stop building `name` on calls created from now on.
6529
+ *
6530
+ * By default this also removes it from the calls **already open**, so that "remove this detector"
6531
+ * means the same thing whether you say it before or after a call started — the alternative leaves
6532
+ * a fleet where the detector is live on some calls and not others, decided by join time. Pass
6533
+ * `{ includeOpenCalls: false }` to change only what future calls are built with.
6534
+ *
6535
+ * Returns the number of live detector instances removed (`0` when only the config changed).
6536
+ */
6537
+ removeCallDetector(name, { includeOpenCalls = true } = {}) {
6413
6538
  this.callDetectorConfigs.delete(name);
6414
- return this;
6539
+ if (!includeOpenCalls) return 0;
6540
+ let removed = 0;
6541
+ for (const call of this.observedCalls.values()) removed += call.removeDetector(name);
6542
+ return removed;
6415
6543
  }
6416
6544
  /**
6417
6545
  * Start a structural check. It runs on each `observer.update()` until it can decide, reports once
@@ -6450,6 +6578,36 @@ var Observer = class extends import_events6.EventEmitter {
6450
6578
  this.validators.add(validator);
6451
6579
  return this;
6452
6580
  }
6581
+ /**
6582
+ * Stop a running validation, by name or by instance. Returns how many were cancelled.
6583
+ *
6584
+ * Cancelling is **not** silent discarding. The validator finishes with `inconclusive` and the given
6585
+ * `reason`, emits `validation-ready` like any other completion, and removes itself. That matters
6586
+ * because anything waiting on the verdict — a deploy gate, a dashboard, a promise — would otherwise
6587
+ * wait forever, and because "we stopped asking" is a materially different outcome from "we asked
6588
+ * and learned nothing", which is exactly what `inconclusive` with a reason records.
6589
+ *
6590
+ * ```ts
6591
+ * observer.cancelValidator('simulcast-receivers', 'sfu redeployed');
6592
+ *
6593
+ * // or one specific instance — `observer.validators` holds what is running
6594
+ * for (const validator of observer.validators) observer.cancelValidator(validator, 'shutting down');
6595
+ * ```
6596
+ *
6597
+ * Pass a real reason. The default tells the reader nothing they could not already infer.
6598
+ */
6599
+ cancelValidator(target, reason = "cancelled") {
6600
+ const running = [...this.validators];
6601
+ const matching = typeof target === "string" ? running.filter((validator) => validator.name === target) : running.filter((validator) => validator === target);
6602
+ for (const validator of matching) {
6603
+ try {
6604
+ validator.cancel(reason);
6605
+ } catch (err) {
6606
+ logger8.warn("Error cancelling validator %s: %o", validator.name, err);
6607
+ }
6608
+ }
6609
+ return matching.length;
6610
+ }
6453
6611
  getObservedCall(callId) {
6454
6612
  if (this.closed || !this.observedCalls.has(callId)) return;
6455
6613
  return this.observedCalls.get(callId);
@@ -6537,7 +6695,7 @@ var Observer = class extends import_events6.EventEmitter {
6537
6695
  this.closed = true;
6538
6696
  for (const call of [...this.observedCalls.values()]) call.close();
6539
6697
  this.detectors.clear();
6540
- for (const validator of [...this.validators]) validator.cancel();
6698
+ for (const validator of [...this.validators]) validator.cancel("observer closed");
6541
6699
  this.validators.clear();
6542
6700
  this.activeIssuesRegistry.clear();
6543
6701
  this._notify("observer-closed", { ...this.eventScope });