@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/README.md +80 -4
- package/dist/index.d.mts +125 -5
- package/dist/index.d.ts +125 -5
- package/dist/index.js +163 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +163 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
/**
|
|
6412
|
-
|
|
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
|
|
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 });
|