@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.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
|
-
|
|
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
|
-
/**
|
|
6294
|
-
|
|
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
|
|
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 });
|