@smplkit/sdk 3.0.1 → 3.0.3

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.d.cts CHANGED
@@ -1429,9 +1429,9 @@ declare class Config {
1429
1429
  */
1430
1430
  _buildChain(configs?: Config[]): Promise<Array<{
1431
1431
  id: string | null;
1432
- items: Record<string, Record<string, unknown>>;
1432
+ items: Record<string, unknown>;
1433
1433
  environments: Record<string, {
1434
- values: Record<string, Record<string, unknown>>;
1434
+ values: Record<string, unknown>;
1435
1435
  }>;
1436
1436
  }>>;
1437
1437
  toString(): string;
@@ -1549,6 +1549,13 @@ declare class FlagsClient {
1549
1549
  * before using `.get()` on flag handles.
1550
1550
  */
1551
1551
  initialize(): Promise<void>;
1552
+ /**
1553
+ * Synchronous teardown — clears the flag-flush interval and unsubscribes
1554
+ * from WebSocket events. Called by `SmplClient.close()`. Does not flush
1555
+ * pending context observations (use {@link disconnect} for that).
1556
+ * @internal
1557
+ */
1558
+ _close(): void;
1552
1559
  /** Disconnect the flags runtime and release resources. */
1553
1560
  disconnect(): Promise<void>;
1554
1561
  /** Refresh all flag definitions from the server. */
@@ -2409,6 +2416,11 @@ declare class ConfigClient {
2409
2416
  /** @internal — resolves the management config sub-client used by lazy-init/refresh. */
2410
2417
  _resolveManagement?: () => SmplManagementClient;
2411
2418
  private _configCache;
2419
+ /** Raw Config objects keyed by id, kept around so a single-config
2420
+ * change (WS event) can refetch one config and rebuild the resolved
2421
+ * cache for everyone (including descendants that inherit from it)
2422
+ * without a full re-list. Mirrors Python's `_raw_config_cache`. */
2423
+ private _configStore;
2412
2424
  private _initialized;
2413
2425
  private _listeners;
2414
2426
  /** @internal */
@@ -2464,12 +2476,10 @@ declare class ConfigClient {
2464
2476
  /** @internal — get resolved config from cache. Used by LiveConfigProxy. */
2465
2477
  _getCachedConfig(key: string): Record<string, unknown> | undefined;
2466
2478
  private _handleConfigChanged;
2467
- private _handleConfigDeleted;
2468
- private _handleConfigsChanged;
2469
2479
  /** Fetch a single config by key. Returns null if not found. @internal */
2470
2480
  private _fetchSingleConfig;
2471
- /** Resolve a config's values for an environment (no parent chain). @internal */
2472
- private _resolveConfigValues;
2481
+ private _handleConfigDeleted;
2482
+ private _handleConfigsChanged;
2473
2483
  /** @internal */
2474
2484
  private _diffAndFire;
2475
2485
  }
@@ -2565,8 +2575,32 @@ declare class LoggingClient {
2565
2575
  * `install()`.
2566
2576
  *
2567
2577
  * Mirrors Python's `client.logging.install()`. There is no `stop()`.
2578
+ *
2579
+ * Adapter coverage:
2580
+ * - **winston**: pre-existing named loggers (`winston.loggers.*`) and the
2581
+ * default logger are auto-discovered.
2582
+ * - **pino**: pino has no global registry, so only loggers created
2583
+ * through `pino()` / `logger.child()` *after* `install()` runs are
2584
+ * tracked. To bring pre-existing pino loggers under management, recreate
2585
+ * them after install or register them explicitly via
2586
+ * `client.manage.loggers.register([...])`.
2587
+ *
2588
+ * After the initial pass, call {@link refresh} to re-fetch managed levels
2589
+ * from the server and re-apply them onto the native loggers (e.g. after
2590
+ * suspecting drift, or to force a manual sync outside the WebSocket).
2568
2591
  */
2569
2592
  install(): Promise<void>;
2593
+ /**
2594
+ * Re-fetch logger and group levels from the server and re-apply them
2595
+ * onto the registered adapters.
2596
+ *
2597
+ * Diff-based: change listeners only fire for loggers whose level
2598
+ * actually changed (added, removed, or different level), with
2599
+ * `source: "manual"`. Mirrors Python's `client.logging.refresh()`.
2600
+ *
2601
+ * @throws SmplError if `install()` has not been called.
2602
+ */
2603
+ refresh(): Promise<void>;
2570
2604
  /**
2571
2605
  * @deprecated Use {@link LoggingClient.install}. Retained as a backwards-
2572
2606
  * compatible alias.
@@ -2596,6 +2630,14 @@ declare class LoggingClient {
2596
2630
  private _handleGroupChanged;
2597
2631
  private _handleGroupDeleted;
2598
2632
  private _handleLoggersChanged;
2633
+ /**
2634
+ * Full refetch of loggers + log_groups, apply resolved levels to
2635
+ * adapters, diff against local stores and fire change listeners
2636
+ * (global once, per-key for each changed id). Shared between the
2637
+ * `loggers_changed` WS handler and the public `refresh()` method.
2638
+ * @internal
2639
+ */
2640
+ private _resolveAndFire;
2599
2641
  /** Fetch a single logger by key. Returns null if not found. @internal */
2600
2642
  private _fetchSingleLogger;
2601
2643
  /** Fetch a single log group by key. Returns null if not found. @internal */
package/dist/index.d.ts CHANGED
@@ -1429,9 +1429,9 @@ declare class Config {
1429
1429
  */
1430
1430
  _buildChain(configs?: Config[]): Promise<Array<{
1431
1431
  id: string | null;
1432
- items: Record<string, Record<string, unknown>>;
1432
+ items: Record<string, unknown>;
1433
1433
  environments: Record<string, {
1434
- values: Record<string, Record<string, unknown>>;
1434
+ values: Record<string, unknown>;
1435
1435
  }>;
1436
1436
  }>>;
1437
1437
  toString(): string;
@@ -1549,6 +1549,13 @@ declare class FlagsClient {
1549
1549
  * before using `.get()` on flag handles.
1550
1550
  */
1551
1551
  initialize(): Promise<void>;
1552
+ /**
1553
+ * Synchronous teardown — clears the flag-flush interval and unsubscribes
1554
+ * from WebSocket events. Called by `SmplClient.close()`. Does not flush
1555
+ * pending context observations (use {@link disconnect} for that).
1556
+ * @internal
1557
+ */
1558
+ _close(): void;
1552
1559
  /** Disconnect the flags runtime and release resources. */
1553
1560
  disconnect(): Promise<void>;
1554
1561
  /** Refresh all flag definitions from the server. */
@@ -2409,6 +2416,11 @@ declare class ConfigClient {
2409
2416
  /** @internal — resolves the management config sub-client used by lazy-init/refresh. */
2410
2417
  _resolveManagement?: () => SmplManagementClient;
2411
2418
  private _configCache;
2419
+ /** Raw Config objects keyed by id, kept around so a single-config
2420
+ * change (WS event) can refetch one config and rebuild the resolved
2421
+ * cache for everyone (including descendants that inherit from it)
2422
+ * without a full re-list. Mirrors Python's `_raw_config_cache`. */
2423
+ private _configStore;
2412
2424
  private _initialized;
2413
2425
  private _listeners;
2414
2426
  /** @internal */
@@ -2464,12 +2476,10 @@ declare class ConfigClient {
2464
2476
  /** @internal — get resolved config from cache. Used by LiveConfigProxy. */
2465
2477
  _getCachedConfig(key: string): Record<string, unknown> | undefined;
2466
2478
  private _handleConfigChanged;
2467
- private _handleConfigDeleted;
2468
- private _handleConfigsChanged;
2469
2479
  /** Fetch a single config by key. Returns null if not found. @internal */
2470
2480
  private _fetchSingleConfig;
2471
- /** Resolve a config's values for an environment (no parent chain). @internal */
2472
- private _resolveConfigValues;
2481
+ private _handleConfigDeleted;
2482
+ private _handleConfigsChanged;
2473
2483
  /** @internal */
2474
2484
  private _diffAndFire;
2475
2485
  }
@@ -2565,8 +2575,32 @@ declare class LoggingClient {
2565
2575
  * `install()`.
2566
2576
  *
2567
2577
  * Mirrors Python's `client.logging.install()`. There is no `stop()`.
2578
+ *
2579
+ * Adapter coverage:
2580
+ * - **winston**: pre-existing named loggers (`winston.loggers.*`) and the
2581
+ * default logger are auto-discovered.
2582
+ * - **pino**: pino has no global registry, so only loggers created
2583
+ * through `pino()` / `logger.child()` *after* `install()` runs are
2584
+ * tracked. To bring pre-existing pino loggers under management, recreate
2585
+ * them after install or register them explicitly via
2586
+ * `client.manage.loggers.register([...])`.
2587
+ *
2588
+ * After the initial pass, call {@link refresh} to re-fetch managed levels
2589
+ * from the server and re-apply them onto the native loggers (e.g. after
2590
+ * suspecting drift, or to force a manual sync outside the WebSocket).
2568
2591
  */
2569
2592
  install(): Promise<void>;
2593
+ /**
2594
+ * Re-fetch logger and group levels from the server and re-apply them
2595
+ * onto the registered adapters.
2596
+ *
2597
+ * Diff-based: change listeners only fire for loggers whose level
2598
+ * actually changed (added, removed, or different level), with
2599
+ * `source: "manual"`. Mirrors Python's `client.logging.refresh()`.
2600
+ *
2601
+ * @throws SmplError if `install()` has not been called.
2602
+ */
2603
+ refresh(): Promise<void>;
2570
2604
  /**
2571
2605
  * @deprecated Use {@link LoggingClient.install}. Retained as a backwards-
2572
2606
  * compatible alias.
@@ -2596,6 +2630,14 @@ declare class LoggingClient {
2596
2630
  private _handleGroupChanged;
2597
2631
  private _handleGroupDeleted;
2598
2632
  private _handleLoggersChanged;
2633
+ /**
2634
+ * Full refetch of loggers + log_groups, apply resolved levels to
2635
+ * adapters, diff against local stores and fire change listeners
2636
+ * (global once, per-key for each changed id). Shared between the
2637
+ * `loggers_changed` WS handler and the public `refresh()` method.
2638
+ * @internal
2639
+ */
2640
+ private _resolveAndFire;
2599
2641
  /** Fetch a single logger by key. Returns null if not found. @internal */
2600
2642
  private _fetchSingleLogger;
2601
2643
  /** Fetch a single log group by key. Returns null if not found. @internal */
package/dist/index.js CHANGED
@@ -16838,6 +16838,13 @@ function environmentsToWire(environments) {
16838
16838
  }
16839
16839
  return out;
16840
16840
  }
16841
+ function environmentsForResolver(environments) {
16842
+ const out = {};
16843
+ for (const [envId, env] of Object.entries(environments)) {
16844
+ out[envId] = { values: env.values };
16845
+ }
16846
+ return out;
16847
+ }
16841
16848
  var Config = class {
16842
16849
  id;
16843
16850
  name;
@@ -17027,8 +17034,8 @@ var Config = class {
17027
17034
  const chain = [
17028
17035
  {
17029
17036
  id: this.id,
17030
- items: this._itemsRaw,
17031
- environments: environmentsToWire(this._environments)
17037
+ items: this.items,
17038
+ environments: environmentsForResolver(this._environments)
17032
17039
  }
17033
17040
  ];
17034
17041
  let current = this;
@@ -17050,8 +17057,8 @@ var Config = class {
17050
17057
  }
17051
17058
  chain.push({
17052
17059
  id: parentConfig.id,
17053
- items: parentConfig._itemsRaw,
17054
- environments: environmentsToWire(parentConfig._environments)
17060
+ items: parentConfig.items,
17061
+ environments: environmentsForResolver(parentConfig._environments)
17055
17062
  });
17056
17063
  current = parentConfig;
17057
17064
  }
@@ -17063,6 +17070,22 @@ var Config = class {
17063
17070
  };
17064
17071
 
17065
17072
  // src/config/proxy.ts
17073
+ function _unflattenDotNotation(flat) {
17074
+ const nested = {};
17075
+ for (const [key, value] of Object.entries(flat)) {
17076
+ const parts = key.split(".");
17077
+ let current = nested;
17078
+ for (let i = 0; i < parts.length - 1; i++) {
17079
+ const part = parts[i];
17080
+ if (current[part] === void 0 || typeof current[part] !== "object" || current[part] === null) {
17081
+ current[part] = {};
17082
+ }
17083
+ current = current[part];
17084
+ }
17085
+ current[parts[parts.length - 1]] = value;
17086
+ }
17087
+ return nested;
17088
+ }
17066
17089
  var LiveConfigProxy = class {
17067
17090
  /** @internal */
17068
17091
  _client;
@@ -17085,7 +17108,8 @@ var LiveConfigProxy = class {
17085
17108
  }
17086
17109
  const values = target._currentValues();
17087
17110
  if (target._model) {
17088
- const instance = new target._model(values);
17111
+ const nested = _unflattenDotNotation(values);
17112
+ const instance = new target._model(nested);
17089
17113
  return instance[prop];
17090
17114
  }
17091
17115
  return values[prop];
@@ -17247,6 +17271,11 @@ var ConfigClient = class {
17247
17271
  /** @internal — resolves the management config sub-client used by lazy-init/refresh. */
17248
17272
  _resolveManagement;
17249
17273
  _configCache = {};
17274
+ /** Raw Config objects keyed by id, kept around so a single-config
17275
+ * change (WS event) can refetch one config and rebuild the resolved
17276
+ * cache for everyone (including descendants that inherit from it)
17277
+ * without a full re-list. Mirrors Python's `_raw_config_cache`. */
17278
+ _configStore = {};
17250
17279
  _initialized = false;
17251
17280
  _listeners = [];
17252
17281
  /** @internal */
@@ -17355,12 +17384,15 @@ var ConfigClient = class {
17355
17384
  }
17356
17385
  const configs = await this._listConfigs();
17357
17386
  const newCache = {};
17387
+ const newStore = {};
17358
17388
  for (const cfg of configs) {
17359
17389
  const chain = await cfg._buildChain(configs);
17360
17390
  newCache[cfg.id] = resolveChain(chain, environment);
17391
+ newStore[cfg.id] = cfg;
17361
17392
  }
17362
17393
  const oldCache = this._configCache;
17363
17394
  this._configCache = newCache;
17395
+ this._configStore = newStore;
17364
17396
  this._diffAndFire(oldCache, newCache, "manual");
17365
17397
  }
17366
17398
  /**
@@ -17402,11 +17434,14 @@ var ConfigClient = class {
17402
17434
  }
17403
17435
  const configs = await this._listConfigs();
17404
17436
  const cache = {};
17437
+ const store = {};
17405
17438
  for (const cfg of configs) {
17406
17439
  const chain = await cfg._buildChain(configs);
17407
17440
  cache[cfg.id] = resolveChain(chain, environment);
17441
+ store[cfg.id] = cfg;
17408
17442
  }
17409
17443
  this._configCache = cache;
17444
+ this._configStore = store;
17410
17445
  this._initialized = true;
17411
17446
  if (this._getSharedWs) {
17412
17447
  const ws = this._getSharedWs();
@@ -17420,11 +17455,14 @@ var ConfigClient = class {
17420
17455
  if (this._initialized) return;
17421
17456
  const configs = await this._listConfigs();
17422
17457
  const cache = {};
17458
+ const store = {};
17423
17459
  for (const cfg of configs) {
17424
17460
  const chain = await cfg._buildChain(configs);
17425
17461
  cache[cfg.id] = resolveChain(chain, environment);
17462
+ store[cfg.id] = cfg;
17426
17463
  }
17427
17464
  this._configCache = cache;
17465
+ this._configStore = store;
17428
17466
  this._initialized = true;
17429
17467
  }
17430
17468
  /** @internal — get resolved config from cache. Used by LiveConfigProxy. */
@@ -17438,26 +17476,29 @@ var ConfigClient = class {
17438
17476
  debug("websocket", `config_changed event received: ${JSON.stringify(data)}`);
17439
17477
  const configKey = data.id;
17440
17478
  if (!configKey) return;
17479
+ const environment = this._parent?._environment;
17480
+ if (!environment) return;
17441
17481
  void this._fetchSingleConfig(configKey).then((newConfig) => {
17442
- const environment = this._parent?._environment;
17443
- if (!environment) return;
17444
- const oldValues = this._configCache[configKey];
17445
- let newValues;
17446
- if (newConfig !== null) {
17447
- newValues = this._resolveConfigValues(newConfig, environment);
17448
- } else {
17449
- newValues = {};
17450
- }
17451
- const oldJson = JSON.stringify(oldValues ?? {});
17452
- const newJson = JSON.stringify(newValues);
17453
- if (oldJson === newJson) return;
17454
- const oldCache = { ...this._configCache };
17455
- if (newConfig !== null) {
17456
- this._configCache[configKey] = newValues;
17482
+ const newStore = { ...this._configStore };
17483
+ if (newConfig === null) {
17484
+ delete newStore[configKey];
17457
17485
  } else {
17458
- delete this._configCache[configKey];
17459
- }
17460
- this._diffAndFire(oldCache, this._configCache, "websocket");
17486
+ newStore[configKey] = newConfig;
17487
+ }
17488
+ const allConfigs = Object.values(newStore);
17489
+ return Promise.all(
17490
+ allConfigs.map(async (cfg) => {
17491
+ const chain = await cfg._buildChain(allConfigs);
17492
+ return [cfg.id, resolveChain(chain, environment)];
17493
+ })
17494
+ ).then((entries) => ({ entries, newStore }));
17495
+ }).then(({ entries, newStore }) => {
17496
+ const newCache = {};
17497
+ for (const [id, values] of entries) newCache[id] = values;
17498
+ const oldCache = this._configCache;
17499
+ this._configCache = newCache;
17500
+ this._configStore = newStore;
17501
+ this._diffAndFire(oldCache, newCache, "websocket");
17461
17502
  }).catch((err) => {
17462
17503
  debug(
17463
17504
  "websocket",
@@ -17465,6 +17506,20 @@ var ConfigClient = class {
17465
17506
  );
17466
17507
  });
17467
17508
  };
17509
+ /** Fetch a single config by key. Returns null if not found. @internal */
17510
+ async _fetchSingleConfig(key) {
17511
+ debug("api", `GET /api/v1/configs/${key}`);
17512
+ try {
17513
+ const result = await this._http.GET("/api/v1/configs/{id}", {
17514
+ params: { path: { id: key } }
17515
+ });
17516
+ if (!result.response.ok) return null;
17517
+ if (!result.data?.data) return null;
17518
+ return resourceToConfig(result.data.data);
17519
+ } catch {
17520
+ return null;
17521
+ }
17522
+ }
17468
17523
  _handleConfigDeleted = (data) => {
17469
17524
  debug("websocket", `config_deleted event received: ${JSON.stringify(data)}`);
17470
17525
  const configKey = data.id;
@@ -17483,27 +17538,6 @@ var ConfigClient = class {
17483
17538
  // ------------------------------------------------------------------
17484
17539
  // Internal: change detection
17485
17540
  // ------------------------------------------------------------------
17486
- /** Fetch a single config by key. Returns null if not found. @internal */
17487
- async _fetchSingleConfig(key) {
17488
- debug("api", `GET /api/v1/configs/${key}`);
17489
- try {
17490
- const result = await this._http.GET("/api/v1/configs/{id}", {
17491
- params: { path: { id: key } }
17492
- });
17493
- if (!result.response.ok) return null;
17494
- if (!result.data?.data) return null;
17495
- return resourceToConfig(result.data.data);
17496
- } catch {
17497
- return null;
17498
- }
17499
- }
17500
- /** Resolve a config's values for an environment (no parent chain). @internal */
17501
- _resolveConfigValues(config, environment) {
17502
- const base = config.items ?? {};
17503
- const envEntry = config.environments?.[environment];
17504
- if (!envEntry?.values) return { ...base };
17505
- return { ...base, ...envEntry.values };
17506
- }
17507
17541
  /** @internal */
17508
17542
  _diffAndFire(oldCache, newCache, source) {
17509
17543
  const allConfigKeys = /* @__PURE__ */ new Set([...Object.keys(oldCache), ...Object.keys(newCache)]);
@@ -20456,6 +20490,30 @@ var FlagsClient = class {
20456
20490
  this._flagFlushTimer = setInterval(() => {
20457
20491
  void this._flushFlags();
20458
20492
  }, FLAG_REGISTRATION_FLUSH_INTERVAL_MS);
20493
+ if (typeof this._flagFlushTimer === "object" && "unref" in this._flagFlushTimer) {
20494
+ this._flagFlushTimer.unref();
20495
+ }
20496
+ }
20497
+ /**
20498
+ * Synchronous teardown — clears the flag-flush interval and unsubscribes
20499
+ * from WebSocket events. Called by `SmplClient.close()`. Does not flush
20500
+ * pending context observations (use {@link disconnect} for that).
20501
+ * @internal
20502
+ */
20503
+ _close() {
20504
+ if (this._flagFlushTimer !== null) {
20505
+ clearInterval(this._flagFlushTimer);
20506
+ this._flagFlushTimer = null;
20507
+ }
20508
+ if (this._wsManager !== null) {
20509
+ this._wsManager.off("flag_changed", this._handleFlagChanged);
20510
+ this._wsManager.off("flag_deleted", this._handleFlagDeleted);
20511
+ this._wsManager.off("flags_changed", this._handleFlagsChanged);
20512
+ this._wsManager = null;
20513
+ }
20514
+ this._cache.clear();
20515
+ this._initialized = false;
20516
+ this._environment = null;
20459
20517
  }
20460
20518
  /** Disconnect the flags runtime and release resources. */
20461
20519
  async disconnect() {
@@ -20612,6 +20670,9 @@ var FlagsClient = class {
20612
20670
  this._flagFlushTimer = setInterval(() => {
20613
20671
  void this._flushFlags();
20614
20672
  }, FLAG_REGISTRATION_FLUSH_INTERVAL_MS);
20673
+ if (typeof this._flagFlushTimer === "object" && "unref" in this._flagFlushTimer) {
20674
+ this._flagFlushTimer.unref();
20675
+ }
20615
20676
  }
20616
20677
  // ------------------------------------------------------------------
20617
20678
  // Internal: event handlers (called by SharedWebSocket)
@@ -20959,10 +21020,39 @@ var LoggingClient = class {
20959
21020
  * `install()`.
20960
21021
  *
20961
21022
  * Mirrors Python's `client.logging.install()`. There is no `stop()`.
21023
+ *
21024
+ * Adapter coverage:
21025
+ * - **winston**: pre-existing named loggers (`winston.loggers.*`) and the
21026
+ * default logger are auto-discovered.
21027
+ * - **pino**: pino has no global registry, so only loggers created
21028
+ * through `pino()` / `logger.child()` *after* `install()` runs are
21029
+ * tracked. To bring pre-existing pino loggers under management, recreate
21030
+ * them after install or register them explicitly via
21031
+ * `client.manage.loggers.register([...])`.
21032
+ *
21033
+ * After the initial pass, call {@link refresh} to re-fetch managed levels
21034
+ * from the server and re-apply them onto the native loggers (e.g. after
21035
+ * suspecting drift, or to force a manual sync outside the WebSocket).
20962
21036
  */
20963
21037
  async install() {
20964
21038
  return this._installInternal();
20965
21039
  }
21040
+ /**
21041
+ * Re-fetch logger and group levels from the server and re-apply them
21042
+ * onto the registered adapters.
21043
+ *
21044
+ * Diff-based: change listeners only fire for loggers whose level
21045
+ * actually changed (added, removed, or different level), with
21046
+ * `source: "manual"`. Mirrors Python's `client.logging.refresh()`.
21047
+ *
21048
+ * @throws SmplError if `install()` has not been called.
21049
+ */
21050
+ async refresh() {
21051
+ if (!this._started) {
21052
+ throw new SmplError("Logging not installed. Call install() first.");
21053
+ }
21054
+ await this._resolveAndFire("manual");
21055
+ }
20966
21056
  /**
20967
21057
  * @deprecated Use {@link LoggingClient.install}. Retained as a backwards-
20968
21058
  * compatible alias.
@@ -21035,6 +21125,9 @@ var LoggingClient = class {
21035
21125
  this._loggerFlushTimer = setInterval(() => {
21036
21126
  void this._flushLoggerBuffer();
21037
21127
  }, 3e4);
21128
+ if (typeof this._loggerFlushTimer === "object" && "unref" in this._loggerFlushTimer) {
21129
+ this._loggerFlushTimer.unref();
21130
+ }
21038
21131
  this._started = true;
21039
21132
  }
21040
21133
  // ------------------------------------------------------------------
@@ -21306,63 +21399,75 @@ var LoggingClient = class {
21306
21399
  };
21307
21400
  _handleLoggersChanged = (_data) => {
21308
21401
  debug("websocket", `loggers_changed event received`);
21309
- void Promise.all([this._listLoggers(), this._listLogGroups()]).then(([serverLoggers, serverGroups]) => {
21310
- debug("resolution", `resolution pass (trigger: loggers_changed event)`);
21311
- const changedLoggerIds = /* @__PURE__ */ new Set();
21312
- const newLoggerKeys = new Set(serverLoggers.map((l) => l.id));
21313
- for (const logger of serverLoggers) {
21314
- const key = logger.id;
21315
- const oldLevel = this._loggerStore[key] ?? null;
21316
- const newLevel = logger.level ?? null;
21317
- if (oldLevel !== newLevel || !(key in this._loggerStore)) {
21318
- changedLoggerIds.add(key);
21319
- this._loggerStore[key] = newLevel;
21320
- }
21321
- }
21322
- for (const key of Object.keys(this._loggerStore)) {
21323
- if (!newLoggerKeys.has(key)) {
21324
- changedLoggerIds.add(key);
21325
- delete this._loggerStore[key];
21326
- }
21327
- }
21328
- for (const group of serverGroups) {
21329
- this._groupStore[group.id] = group.level ?? null;
21330
- }
21331
- this._applyLevels(serverLoggers);
21332
- if (changedLoggerIds.size === 0) return;
21333
- const [firstKey] = changedLoggerIds;
21334
- const firstLogger = serverLoggers.find((l) => l.id === firstKey);
21335
- const globalEvent = new LoggerChangeEvent({
21336
- id: firstKey,
21337
- level: firstLogger?.level ?? null,
21338
- source: "websocket"
21339
- });
21340
- for (const cb of this._globalListeners) {
21341
- try {
21342
- cb(globalEvent);
21343
- } catch {
21344
- }
21402
+ void this._resolveAndFire("websocket").catch(() => {
21403
+ });
21404
+ };
21405
+ /**
21406
+ * Full refetch of loggers + log_groups, apply resolved levels to
21407
+ * adapters, diff against local stores and fire change listeners
21408
+ * (global once, per-key for each changed id). Shared between the
21409
+ * `loggers_changed` WS handler and the public `refresh()` method.
21410
+ * @internal
21411
+ */
21412
+ async _resolveAndFire(source) {
21413
+ const [serverLoggers, serverGroups] = await Promise.all([
21414
+ this._listLoggers(),
21415
+ this._listLogGroups()
21416
+ ]);
21417
+ debug("resolution", `resolution pass (trigger: ${source})`);
21418
+ const changedLoggerIds = /* @__PURE__ */ new Set();
21419
+ const newLoggerKeys = new Set(serverLoggers.map((l) => l.id));
21420
+ for (const logger of serverLoggers) {
21421
+ const key = logger.id;
21422
+ const oldLevel = this._loggerStore[key] ?? null;
21423
+ const newLevel = logger.level ?? null;
21424
+ if (oldLevel !== newLevel || !(key in this._loggerStore)) {
21425
+ changedLoggerIds.add(key);
21426
+ this._loggerStore[key] = newLevel;
21427
+ }
21428
+ }
21429
+ for (const key of Object.keys(this._loggerStore)) {
21430
+ if (!newLoggerKeys.has(key)) {
21431
+ changedLoggerIds.add(key);
21432
+ delete this._loggerStore[key];
21433
+ }
21434
+ }
21435
+ for (const group of serverGroups) {
21436
+ this._groupStore[group.id] = group.level ?? null;
21437
+ }
21438
+ this._applyLevels(serverLoggers);
21439
+ if (changedLoggerIds.size === 0) return;
21440
+ const [firstKey] = changedLoggerIds;
21441
+ const firstLogger = serverLoggers.find((l) => l.id === firstKey);
21442
+ const globalEvent = new LoggerChangeEvent({
21443
+ id: firstKey,
21444
+ level: firstLogger?.level ?? null,
21445
+ source
21446
+ });
21447
+ for (const cb of this._globalListeners) {
21448
+ try {
21449
+ cb(globalEvent);
21450
+ } catch {
21345
21451
  }
21346
- for (const key of changedLoggerIds) {
21347
- const keyCallbacks = this._keyListeners.get(key);
21348
- if (keyCallbacks) {
21349
- const l = serverLoggers.find((x) => x.id === key);
21350
- const keyEvent = new LoggerChangeEvent({
21351
- id: key,
21352
- level: l?.level ?? null,
21353
- source: "websocket"
21354
- });
21355
- for (const cb of keyCallbacks) {
21356
- try {
21357
- cb(keyEvent);
21358
- } catch {
21359
- }
21452
+ }
21453
+ for (const key of changedLoggerIds) {
21454
+ const keyCallbacks = this._keyListeners.get(key);
21455
+ if (keyCallbacks) {
21456
+ const l = serverLoggers.find((x) => x.id === key);
21457
+ const keyEvent = new LoggerChangeEvent({
21458
+ id: key,
21459
+ level: l?.level ?? null,
21460
+ source
21461
+ });
21462
+ for (const cb of keyCallbacks) {
21463
+ try {
21464
+ cb(keyEvent);
21465
+ } catch {
21360
21466
  }
21361
21467
  }
21362
21468
  }
21363
- }).catch(() => {
21364
- });
21365
- };
21469
+ }
21470
+ }
21366
21471
  // ------------------------------------------------------------------
21367
21472
  // Internal: single-resource fetchers
21368
21473
  // ------------------------------------------------------------------
@@ -21905,6 +22010,7 @@ var SmplClient = class {
21905
22010
  if (this._metrics !== null) {
21906
22011
  this._metrics.close();
21907
22012
  }
22013
+ this.flags._close();
21908
22014
  this.logging._close();
21909
22015
  if (this._wsManager !== null) {
21910
22016
  this._wsManager.stop();