@smplkit/sdk 3.0.48 → 3.0.50

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.cjs CHANGED
@@ -17597,6 +17597,9 @@ function _parseDebugEnv(value) {
17597
17597
  return v === "1" || v === "true" || v === "yes";
17598
17598
  }
17599
17599
  var _DEBUG_ENABLED = _parseDebugEnv(process.env.SMPLKIT_DEBUG ?? "");
17600
+ function isDebugEnabled() {
17601
+ return _DEBUG_ENABLED;
17602
+ }
17600
17603
  function enableDebug() {
17601
17604
  _DEBUG_ENABLED = true;
17602
17605
  }
@@ -17812,18 +17815,43 @@ var ConfigClient = class {
17812
17815
  * (set via `_resolveManagement`) so runtime + management share one HTTP
17813
17816
  * client; falls back to a direct GET when running without `SmplClient`
17814
17817
  * bootstrap (e.g. unit tests that construct `ConfigClient` directly).
17818
+ *
17819
+ * Pages through the server until a short page (less than the requested
17820
+ * size) is returned — accounts with more than 1000 configs would
17821
+ * otherwise silently lose everything past page one.
17815
17822
  */
17816
17823
  async _listConfigs() {
17817
- if (this._resolveManagement) {
17818
- return this._resolveManagement().config.list();
17819
- }
17820
- const result = await this._http.GET("/api/v1/configs", {});
17821
- if (!result.response.ok) {
17822
- throw new SmplError(`Failed to list configs: ${result.response.status}`);
17824
+ const PAGE_SIZE = 1e3;
17825
+ const all = [];
17826
+ let page = 1;
17827
+ let lastPageWasFull = true;
17828
+ while (lastPageWasFull) {
17829
+ let rows;
17830
+ if (this._resolveManagement) {
17831
+ rows = await this._resolveManagement().config.list({
17832
+ pageNumber: page,
17833
+ pageSize: PAGE_SIZE
17834
+ });
17835
+ } else {
17836
+ const result = await this._http.GET("/api/v1/configs", {
17837
+ params: {
17838
+ query: {
17839
+ "page[number]": page,
17840
+ "page[size]": PAGE_SIZE
17841
+ }
17842
+ }
17843
+ });
17844
+ if (!result.response.ok) {
17845
+ throw new SmplError(`Failed to list configs: ${result.response.status}`);
17846
+ }
17847
+ const data = result.data;
17848
+ rows = data ? data.data.map((r) => resourceToConfig(r)) : [];
17849
+ }
17850
+ all.push(...rows);
17851
+ lastPageWasFull = rows.length === PAGE_SIZE;
17852
+ page++;
17823
17853
  }
17824
- const data = result.data;
17825
- if (!data) return [];
17826
- return data.data.map((r) => resourceToConfig(r));
17854
+ return all;
17827
17855
  }
17828
17856
  // ------------------------------------------------------------------
17829
17857
  // Runtime: lazy initialization
@@ -18817,11 +18845,23 @@ var ManagementConfigClient = class {
18817
18845
  updatedAt: null
18818
18846
  });
18819
18847
  }
18820
- /** List all configs. */
18821
- async list() {
18848
+ /**
18849
+ * List configs.
18850
+ *
18851
+ * Server defaults are `pageNumber=1`, `pageSize=1000` (capped at 1000).
18852
+ * Omit both to fetch the first page; pass them through to walk further
18853
+ * pages. The wrapper does not loop on the customer's behalf — the
18854
+ * customer chooses how to paginate.
18855
+ */
18856
+ async list(params = {}) {
18857
+ const query = {};
18858
+ if (params.pageNumber !== void 0) query["page[number]"] = params.pageNumber;
18859
+ if (params.pageSize !== void 0) query["page[size]"] = params.pageSize;
18822
18860
  let data;
18823
18861
  try {
18824
- const result = await this._http.GET("/api/v1/configs", {});
18862
+ const result = await this._http.GET("/api/v1/configs", {
18863
+ params: { query }
18864
+ });
18825
18865
  if (!result.response.ok) await checkError(result.response);
18826
18866
  data = result.data;
18827
18867
  } catch (err) {
@@ -19115,11 +19155,23 @@ var ManagementFlagsClient = class {
19115
19155
  }
19116
19156
  return resourceToFlag(data.data, this);
19117
19157
  }
19118
- /** List all flags. */
19119
- async list() {
19158
+ /**
19159
+ * List flags.
19160
+ *
19161
+ * Server defaults are `pageNumber=1`, `pageSize=1000` (capped at 1000).
19162
+ * Omit both to fetch the first page; pass them through to walk further
19163
+ * pages. The wrapper does not loop on the customer's behalf — the
19164
+ * customer chooses how to paginate.
19165
+ */
19166
+ async list(params = {}) {
19167
+ const query = {};
19168
+ if (params.pageNumber !== void 0) query["page[number]"] = params.pageNumber;
19169
+ if (params.pageSize !== void 0) query["page[size]"] = params.pageSize;
19120
19170
  let data;
19121
19171
  try {
19122
- const result = await this._http.GET("/api/v1/flags", {});
19172
+ const result = await this._http.GET("/api/v1/flags", {
19173
+ params: { query }
19174
+ });
19123
19175
  if (!result.response.ok) await checkError2(result.response);
19124
19176
  data = result.data;
19125
19177
  } catch (err) {
@@ -19644,10 +19696,23 @@ var LoggersClient = class {
19644
19696
  updatedAt: null
19645
19697
  });
19646
19698
  }
19647
- async list() {
19699
+ /**
19700
+ * List loggers.
19701
+ *
19702
+ * Server defaults are `pageNumber=1`, `pageSize=1000` (capped at 1000).
19703
+ * Omit both to fetch the first page; pass them through to walk further
19704
+ * pages. The wrapper does not loop on the customer's behalf — the
19705
+ * customer chooses how to paginate.
19706
+ */
19707
+ async list(params = {}) {
19708
+ const query = {};
19709
+ if (params.pageNumber !== void 0) query["page[number]"] = params.pageNumber;
19710
+ if (params.pageSize !== void 0) query["page[size]"] = params.pageSize;
19648
19711
  let data;
19649
19712
  try {
19650
- const result = await this._http.GET("/api/v1/loggers", {});
19713
+ const result = await this._http.GET("/api/v1/loggers", {
19714
+ params: { query }
19715
+ });
19651
19716
  if (!result.response.ok) await checkError3(result.response);
19652
19717
  data = result.data;
19653
19718
  } catch (err) {
@@ -19749,10 +19814,23 @@ var LogGroupsClient = class {
19749
19814
  updatedAt: null
19750
19815
  });
19751
19816
  }
19752
- async list() {
19817
+ /**
19818
+ * List log groups.
19819
+ *
19820
+ * Server defaults are `pageNumber=1`, `pageSize=1000` (capped at 1000).
19821
+ * Omit both to fetch the first page; pass them through to walk further
19822
+ * pages. The wrapper does not loop on the customer's behalf — the
19823
+ * customer chooses how to paginate.
19824
+ */
19825
+ async list(params = {}) {
19826
+ const query = {};
19827
+ if (params.pageNumber !== void 0) query["page[number]"] = params.pageNumber;
19828
+ if (params.pageSize !== void 0) query["page[size]"] = params.pageSize;
19753
19829
  let data;
19754
19830
  try {
19755
- const result = await this._http.GET("/api/v1/log_groups", {});
19831
+ const result = await this._http.GET("/api/v1/log_groups", {
19832
+ params: { query }
19833
+ });
19756
19834
  if (!result.response.ok) await checkError3(result.response);
19757
19835
  data = result.data;
19758
19836
  } catch (err) {
@@ -20237,10 +20315,23 @@ var EnvironmentsClient = class {
20237
20315
  updatedAt: null
20238
20316
  });
20239
20317
  }
20240
- async list() {
20318
+ /**
20319
+ * List environments.
20320
+ *
20321
+ * Server defaults are `pageNumber=1`, `pageSize=1000` (capped at 1000).
20322
+ * Omit both to fetch the first page; pass them through to walk further
20323
+ * pages. The wrapper does not loop on the customer's behalf — the
20324
+ * customer chooses how to paginate.
20325
+ */
20326
+ async list(params = {}) {
20327
+ const query = {};
20328
+ if (params.pageNumber !== void 0) query["page[number]"] = params.pageNumber;
20329
+ if (params.pageSize !== void 0) query["page[size]"] = params.pageSize;
20241
20330
  let data;
20242
20331
  try {
20243
- const result = await this._http.GET("/api/v1/environments", {});
20332
+ const result = await this._http.GET("/api/v1/environments", {
20333
+ params: { query }
20334
+ });
20244
20335
  if (!result.response.ok) await checkError5(result.response);
20245
20336
  data = result.data;
20246
20337
  } catch (err) {
@@ -20348,10 +20439,23 @@ var ContextTypesClient = class {
20348
20439
  updatedAt: null
20349
20440
  });
20350
20441
  }
20351
- async list() {
20442
+ /**
20443
+ * List context types.
20444
+ *
20445
+ * Server defaults are `pageNumber=1`, `pageSize=1000` (capped at 1000).
20446
+ * Omit both to fetch the first page; pass them through to walk further
20447
+ * pages. The wrapper does not loop on the customer's behalf — the
20448
+ * customer chooses how to paginate.
20449
+ */
20450
+ async list(params = {}) {
20451
+ const query = {};
20452
+ if (params.pageNumber !== void 0) query["page[number]"] = params.pageNumber;
20453
+ if (params.pageSize !== void 0) query["page[size]"] = params.pageSize;
20352
20454
  let data;
20353
20455
  try {
20354
- const result = await this._http.GET("/api/v1/context_types", {});
20456
+ const result = await this._http.GET("/api/v1/context_types", {
20457
+ params: { query }
20458
+ });
20355
20459
  if (!result.response.ok) await checkError5(result.response);
20356
20460
  data = result.data;
20357
20461
  } catch (err) {
@@ -20517,12 +20621,22 @@ var ContextsClient = class {
20517
20621
  get pendingCount() {
20518
20622
  return this._buffer.pendingCount;
20519
20623
  }
20520
- /** List all contexts of a given type. */
20521
- async list(type) {
20624
+ /**
20625
+ * List contexts of a given type.
20626
+ *
20627
+ * Server defaults are `pageNumber=1`, `pageSize=1000` (capped at 1000).
20628
+ * Omit both to fetch the first page; pass them through to walk further
20629
+ * pages. The wrapper does not loop on the customer's behalf — the
20630
+ * customer chooses how to paginate.
20631
+ */
20632
+ async list(type, params = {}) {
20633
+ const query = { "filter[context_type]": type };
20634
+ if (params.pageNumber !== void 0) query["page[number]"] = params.pageNumber;
20635
+ if (params.pageSize !== void 0) query["page[size]"] = params.pageSize;
20522
20636
  let data;
20523
20637
  try {
20524
20638
  const result = await this._http.GET("/api/v1/contexts", {
20525
- params: { query: { "filter[context_type]": type } }
20639
+ params: { query }
20526
20640
  });
20527
20641
  if (!result.response.ok) await checkError5(result.response);
20528
20642
  data = result.data;
@@ -21553,19 +21667,34 @@ var FlagsClient = class {
21553
21667
  }
21554
21668
  }
21555
21669
  async _fetchFlagsList() {
21556
- debug("api", "GET /api/v1/flags");
21557
- let data;
21558
- try {
21559
- const result = await this._http.GET("/api/v1/flags", {});
21560
- if (!result.response.ok) await checkError6(result.response, "Failed to list flags");
21561
- data = result.data;
21562
- } catch (err) {
21563
- wrapFetchError6(err);
21670
+ const PAGE_SIZE = 1e3;
21671
+ const all = [];
21672
+ let page = 1;
21673
+ let lastPageWasFull = true;
21674
+ while (lastPageWasFull) {
21675
+ debug("api", `GET /api/v1/flags?page[number]=${page}&page[size]=${PAGE_SIZE}`);
21676
+ let data;
21677
+ try {
21678
+ const result = await this._http.GET("/api/v1/flags", {
21679
+ params: {
21680
+ query: {
21681
+ "page[number]": page,
21682
+ "page[size]": PAGE_SIZE
21683
+ }
21684
+ }
21685
+ });
21686
+ if (!result.response.ok) await checkError6(result.response, "Failed to list flags");
21687
+ data = result.data;
21688
+ } catch (err) {
21689
+ wrapFetchError6(err);
21690
+ }
21691
+ const rows = data?.data ?? [];
21692
+ for (const r of rows) all.push(this._resourceToPlainDict(r));
21693
+ lastPageWasFull = rows.length === PAGE_SIZE;
21694
+ page++;
21564
21695
  }
21565
- if (!data) return [];
21566
- const flags = data.data.map((r) => this._resourceToPlainDict(r));
21567
- debug("api", `GET /api/v1/flags -> ${flags.length} flag(s)`);
21568
- return flags;
21696
+ debug("api", `GET /api/v1/flags -> ${all.length} flag(s)`);
21697
+ return all;
21569
21698
  }
21570
21699
  // ------------------------------------------------------------------
21571
21700
  // Internal: change listeners
@@ -21648,6 +21777,72 @@ var FlagsClient = class {
21648
21777
 
21649
21778
  // src/logging/client.ts
21650
21779
  var import_openapi_fetch5 = __toESM(require("openapi-fetch"), 1);
21780
+
21781
+ // src/logging/_resolution.ts
21782
+ var FALLBACK_LEVEL = "INFO";
21783
+ function resolveLevel(loggerId, environment, loggers, groups) {
21784
+ const direct = _resolveForEntry(loggerId, environment, loggers, groups);
21785
+ if (direct !== null) {
21786
+ if (isDebugEnabled()) {
21787
+ const source = _findResolutionSource(loggerId, environment, loggers, groups);
21788
+ debug("resolution", `${loggerId} -> ${direct} (source: ${source})`);
21789
+ }
21790
+ return direct;
21791
+ }
21792
+ const parts = loggerId.split(".");
21793
+ for (let i = parts.length - 1; i > 0; i--) {
21794
+ const ancestorId = parts.slice(0, i).join(".");
21795
+ const result = _resolveForEntry(ancestorId, environment, loggers, groups);
21796
+ if (result !== null) {
21797
+ debug("resolution", `${loggerId} -> ${result} (source: ancestor "${ancestorId}")`);
21798
+ return result;
21799
+ }
21800
+ }
21801
+ debug("resolution", `${loggerId} -> ${FALLBACK_LEVEL} (source: system default)`);
21802
+ return FALLBACK_LEVEL;
21803
+ }
21804
+ function _resolveForEntry(loggerId, environment, loggers, groups) {
21805
+ const entry = loggers[loggerId];
21806
+ if (entry === void 0) return null;
21807
+ const envLevel = _envLevel(entry.environments, environment);
21808
+ if (envLevel !== null) return envLevel;
21809
+ if (entry.level !== null && entry.level !== void 0) return entry.level;
21810
+ return _resolveGroupChain(entry.group, environment, groups);
21811
+ }
21812
+ function _findResolutionSource(loggerId, environment, loggers, groups) {
21813
+ const entry = loggers[loggerId];
21814
+ if (entry === void 0) return "not found";
21815
+ const envLevel = _envLevel(entry.environments, environment);
21816
+ if (envLevel !== null) return `env override "${environment}"`;
21817
+ if (entry.level !== null && entry.level !== void 0) return "base level";
21818
+ const groupResult = _resolveGroupChain(entry.group, environment, groups);
21819
+ if (groupResult !== null) return `group "${entry.group}"`;
21820
+ return "unknown";
21821
+ }
21822
+ function _resolveGroupChain(groupId, environment, groups) {
21823
+ const visited = /* @__PURE__ */ new Set();
21824
+ let currentId = groupId;
21825
+ while (currentId !== null && currentId !== void 0 && !visited.has(currentId)) {
21826
+ visited.add(currentId);
21827
+ const group = groups[currentId];
21828
+ if (group === void 0) break;
21829
+ const envLevel = _envLevel(group.environments, environment);
21830
+ if (envLevel !== null) return envLevel;
21831
+ if (group.level !== null && group.level !== void 0) return group.level;
21832
+ currentId = group.group;
21833
+ }
21834
+ return null;
21835
+ }
21836
+ function _envLevel(envs, environment) {
21837
+ if (!envs || typeof envs !== "object") return null;
21838
+ const envData = envs[environment];
21839
+ if (!envData || typeof envData !== "object") return null;
21840
+ const level = envData.level;
21841
+ if (level === null || level === void 0) return null;
21842
+ return level;
21843
+ }
21844
+
21845
+ // src/logging/client.ts
21651
21846
  var LOGGING_BASE_URL = "https://logging.smplkit.com";
21652
21847
  var LoggerRegistrationBuffer2 = class {
21653
21848
  _seen = /* @__PURE__ */ new Set();
@@ -21689,11 +21884,20 @@ var LoggingClient = class {
21689
21884
  _explicitAdapters = false;
21690
21885
  _loggerBuffer = new LoggerRegistrationBuffer2();
21691
21886
  _loggerFlushTimer = null;
21692
- // Local stores for diff-based listener firing
21693
- _loggerStore = {};
21694
- // key -> level
21695
- _groupStore = {};
21696
- // key -> level
21887
+ // Caches consulted by the resolution algorithm. The runtime client mutates
21888
+ // these from install(), refresh(), and the WebSocket handlers; resolveLevel
21889
+ // reads them on every apply.
21890
+ _loggersCache = {};
21891
+ _groupsCache = {};
21892
+ // Resolved-level snapshot, used to decide whether to fire change listeners.
21893
+ // Keyed by logger id; storing the *resolved* level (not raw `logger.level`)
21894
+ // means a group-driven level shift fires listeners even when the logger's
21895
+ // own level is untouched.
21896
+ _resolvedLevelStore = {};
21897
+ // Adapter-known logger names. The adapter knows loggers by their original
21898
+ // name (e.g. `com.acme.payments`); the cache is keyed by the same string
21899
+ // since the TypeScript SDK does not normalize.
21900
+ _knownLoggerNames = /* @__PURE__ */ new Set();
21697
21901
  /** @internal */
21698
21902
  constructor(apiKey, ensureWs, timeout, baseUrl, extraHeaders) {
21699
21903
  this._apiKey = apiKey;
@@ -21748,29 +21952,75 @@ var LoggingClient = class {
21748
21952
  * (set via `_resolveManagement`); falls back to a direct GET when running
21749
21953
  * without `SmplClient` bootstrap (e.g. unit tests that construct
21750
21954
  * `LoggingClient` directly).
21955
+ *
21956
+ * Pages through the server until a short page (less than the requested
21957
+ * size) is returned — accounts with more than 1000 loggers would
21958
+ * otherwise silently lose everything past page one.
21751
21959
  */
21752
21960
  async _listLoggers() {
21753
- if (this._resolveManagement) {
21754
- return this._resolveManagement().loggers.list();
21755
- }
21756
- const result = await this._http.GET("/api/v1/loggers", {});
21757
- if (result.error !== void 0) {
21758
- throw new SmplError(`Failed to list loggers: ${result.response.status}`);
21961
+ const PAGE_SIZE = 1e3;
21962
+ const all = [];
21963
+ let page = 1;
21964
+ let lastPageWasFull = true;
21965
+ while (lastPageWasFull) {
21966
+ let rows;
21967
+ if (this._resolveManagement) {
21968
+ rows = await this._resolveManagement().loggers.list({
21969
+ pageNumber: page,
21970
+ pageSize: PAGE_SIZE
21971
+ });
21972
+ } else {
21973
+ const result = await this._http.GET("/api/v1/loggers", {
21974
+ params: {
21975
+ query: {
21976
+ "page[number]": page,
21977
+ "page[size]": PAGE_SIZE
21978
+ }
21979
+ }
21980
+ });
21981
+ if (result.error !== void 0) {
21982
+ throw new SmplError(`Failed to list loggers: ${result.response.status}`);
21983
+ }
21984
+ rows = result.data ? result.data.data.map((r) => this._loggerToModel(r)) : [];
21985
+ }
21986
+ all.push(...rows);
21987
+ lastPageWasFull = rows.length === PAGE_SIZE;
21988
+ page++;
21759
21989
  }
21760
- if (!result.data) return [];
21761
- return result.data.data.map((r) => this._loggerToModel(r));
21990
+ return all;
21762
21991
  }
21763
21992
  /** @internal — see {@link _listLoggers}. */
21764
21993
  async _listLogGroups() {
21765
- if (this._resolveManagement) {
21766
- return this._resolveManagement().logGroups.list();
21767
- }
21768
- const result = await this._http.GET("/api/v1/log_groups", {});
21769
- if (result.error !== void 0) {
21770
- throw new SmplError(`Failed to list log groups: ${result.response.status}`);
21994
+ const PAGE_SIZE = 1e3;
21995
+ const all = [];
21996
+ let page = 1;
21997
+ let lastPageWasFull = true;
21998
+ while (lastPageWasFull) {
21999
+ let rows;
22000
+ if (this._resolveManagement) {
22001
+ rows = await this._resolveManagement().logGroups.list({
22002
+ pageNumber: page,
22003
+ pageSize: PAGE_SIZE
22004
+ });
22005
+ } else {
22006
+ const result = await this._http.GET("/api/v1/log_groups", {
22007
+ params: {
22008
+ query: {
22009
+ "page[number]": page,
22010
+ "page[size]": PAGE_SIZE
22011
+ }
22012
+ }
22013
+ });
22014
+ if (result.error !== void 0) {
22015
+ throw new SmplError(`Failed to list log groups: ${result.response.status}`);
22016
+ }
22017
+ rows = result.data ? result.data.data.map((r) => this._groupToModel(r)) : [];
22018
+ }
22019
+ all.push(...rows);
22020
+ lastPageWasFull = rows.length === PAGE_SIZE;
22021
+ page++;
21771
22022
  }
21772
- if (!result.data) return [];
21773
- return result.data.data.map((r) => this._groupToModel(r));
22023
+ return all;
21774
22024
  }
21775
22025
  // ------------------------------------------------------------------
21776
22026
  // Runtime: install
@@ -21840,6 +22090,7 @@ var LoggingClient = class {
21840
22090
  const loggers = adapter.discover();
21841
22091
  for (const { name, level } of loggers) {
21842
22092
  this._loggerBuffer.add(name, level, level, service, environment);
22093
+ this._knownLoggerNames.add(name);
21843
22094
  discoveredCount++;
21844
22095
  }
21845
22096
  } catch {
@@ -21872,13 +22123,9 @@ var LoggingClient = class {
21872
22123
  "api",
21873
22124
  `fetched ${serverLoggers.length} logger(s) and ${serverGroups.length} group(s) from server`
21874
22125
  );
21875
- this._applyLevels(serverLoggers);
21876
- for (const l of serverLoggers) {
21877
- this._loggerStore[l.id] = l.level;
21878
- }
21879
- for (const g of serverGroups) {
21880
- this._groupStore[g.id] = g.level;
21881
- }
22126
+ this._loggersCache = this._buildLoggersCache(serverLoggers);
22127
+ this._groupsCache = this._buildGroupsCache(serverGroups);
22128
+ this._applyLevels();
21882
22129
  } catch {
21883
22130
  }
21884
22131
  this._wsManager = this._ensureWs();
@@ -21970,41 +22217,107 @@ var LoggingClient = class {
21970
22217
  }
21971
22218
  return adapters;
21972
22219
  }
21973
- /** Apply resolved levels from server loggers to all adapters. */
21974
- _applyLevels(serverLoggers) {
21975
- for (const logger of serverLoggers) {
21976
- if (!logger.level) continue;
21977
- const env = this._parent?._environment;
21978
- let effectiveLevel = logger.level;
21979
- if (env && logger.environments) {
21980
- const envOverride = logger.environments[env];
21981
- if (envOverride?.level) {
21982
- effectiveLevel = envOverride.level;
21983
- }
22220
+ /**
22221
+ * Refresh resolved levels and apply them to adapter-known loggers.
22222
+ *
22223
+ * Computes a resolved level (via {@link resolveLevel}) for every entry
22224
+ * currently in `_loggersCache` *and* every adapter-known logger name —
22225
+ * even loggers whose own `level` is `null`, because they may inherit
22226
+ * via group chain or dot-notation ancestry.
22227
+ *
22228
+ * The full resolved-level snapshot is stored in `_resolvedLevelStore`
22229
+ * so callers can diff pre-vs-post and fire change listeners on actual
22230
+ * effective-level deltas (group-driven changes included). Adapter pushes
22231
+ * only happen for adapter-known names where `managed !== false`.
22232
+ * @internal
22233
+ */
22234
+ _applyLevels() {
22235
+ const environment = this._parent?._environment ?? "";
22236
+ const newResolved = {};
22237
+ for (const id of Object.keys(this._loggersCache)) {
22238
+ newResolved[id] = resolveLevel(id, environment, this._loggersCache, this._groupsCache);
22239
+ }
22240
+ for (const name of this._knownLoggerNames) {
22241
+ if (!(name in newResolved)) {
22242
+ newResolved[name] = resolveLevel(name, environment, this._loggersCache, this._groupsCache);
21984
22243
  }
21985
- debug("resolution", `${logger.id} -> ${effectiveLevel}`);
22244
+ }
22245
+ this._resolvedLevelStore = newResolved;
22246
+ for (const name of this._knownLoggerNames) {
22247
+ const resolved = newResolved[name];
21986
22248
  const metrics = this._parent?._metrics;
21987
22249
  if (metrics) {
21988
- metrics.record("logging.level_changes", 1, "changes", { logger: logger.id });
22250
+ metrics.record("logging.level_changes", 1, "changes", { logger: name });
21989
22251
  }
21990
22252
  for (const adapter of this._adapters) {
21991
22253
  try {
21992
- debug("adapter", `setLevel(${logger.id}, ${effectiveLevel})`);
21993
- adapter.applyLevel(logger.id, effectiveLevel);
22254
+ debug("adapter", `setLevel(${name}, ${resolved})`);
22255
+ adapter.applyLevel(name, resolved);
21994
22256
  } catch {
21995
22257
  }
21996
22258
  }
21997
22259
  }
21998
22260
  }
22261
+ /** @internal Convert a server Logger list into the resolution cache. */
22262
+ _buildLoggersCache(loggers) {
22263
+ const out = {};
22264
+ for (const l of loggers) {
22265
+ if (l.id === null) continue;
22266
+ out[l.id] = this._loggerToCacheEntry(l);
22267
+ }
22268
+ return out;
22269
+ }
22270
+ /** @internal Convert a server LogGroup list into the resolution cache. */
22271
+ _buildGroupsCache(groups) {
22272
+ const out = {};
22273
+ for (const g of groups) {
22274
+ if (g.id === null) continue;
22275
+ out[g.id] = this._groupToCacheEntry(g);
22276
+ }
22277
+ return out;
22278
+ }
22279
+ /** @internal */
22280
+ _loggerToCacheEntry(l) {
22281
+ return {
22282
+ level: l.level ?? null,
22283
+ group: l.group ?? null,
22284
+ managed: l.managed ?? null,
22285
+ environments: l.environments ?? null
22286
+ };
22287
+ }
22288
+ /** @internal */
22289
+ _groupToCacheEntry(g) {
22290
+ return {
22291
+ level: g.level ?? null,
22292
+ group: g.group ?? null,
22293
+ environments: g.environments ?? null
22294
+ };
22295
+ }
21999
22296
  /** Called by adapter hooks when a new logger is created in the framework. */
22000
22297
  _onAdapterNewLogger(name, level) {
22001
22298
  debug("discovery", `new logger intercepted at runtime: ${name}`);
22002
22299
  const service = this._parent?._service ?? null;
22003
22300
  const environment = this._parent?._environment ?? null;
22004
22301
  this._loggerBuffer.add(name, level, level, service, environment);
22302
+ this._knownLoggerNames.add(name);
22005
22303
  if (this._loggerBuffer.pendingCount >= 50) {
22006
22304
  void this._flushLoggerBuffer();
22007
22305
  }
22306
+ if (this._started) {
22307
+ const resolved = resolveLevel(
22308
+ name,
22309
+ this._parent?._environment ?? "",
22310
+ this._loggersCache,
22311
+ this._groupsCache
22312
+ );
22313
+ this._resolvedLevelStore[name] = resolved;
22314
+ for (const adapter of this._adapters) {
22315
+ try {
22316
+ adapter.applyLevel(name, resolved);
22317
+ } catch {
22318
+ }
22319
+ }
22320
+ }
22008
22321
  }
22009
22322
  /** Flush buffered loggers to the bulk-register endpoint. */
22010
22323
  async _flushLoggerBuffer() {
@@ -22038,33 +22351,14 @@ var LoggingClient = class {
22038
22351
  const id = data.id;
22039
22352
  if (!id) return;
22040
22353
  void this._fetchSingleLogger(id).then((logger) => {
22041
- const oldLevel = this._loggerStore[id] ?? null;
22042
- const newLevel = logger?.level ?? null;
22043
- if (oldLevel === newLevel) return;
22044
- this._loggerStore[id] = newLevel;
22045
- if (logger) {
22046
- this._applyLevels([logger]);
22047
- }
22048
- const event = new LoggerChangeEvent({
22049
- id,
22050
- level: newLevel,
22051
- source: "websocket"
22052
- });
22053
- for (const cb of this._globalListeners) {
22054
- try {
22055
- cb(event);
22056
- } catch {
22057
- }
22058
- }
22059
- const idCallbacks = this._keyListeners.get(id);
22060
- if (idCallbacks) {
22061
- for (const cb of idCallbacks) {
22062
- try {
22063
- cb(event);
22064
- } catch {
22065
- }
22066
- }
22354
+ const preResolved = { ...this._resolvedLevelStore };
22355
+ if (logger !== null) {
22356
+ this._loggersCache[id] = this._loggerToCacheEntry(logger);
22357
+ } else {
22358
+ delete this._loggersCache[id];
22067
22359
  }
22360
+ this._applyLevels();
22361
+ this._fireDeltas(preResolved, this._resolvedLevelStore, "websocket");
22068
22362
  }).catch((err) => {
22069
22363
  debug(
22070
22364
  "websocket",
@@ -22076,50 +22370,25 @@ var LoggingClient = class {
22076
22370
  debug("websocket", `logger_deleted event received: ${JSON.stringify(data)}`);
22077
22371
  const id = data.id;
22078
22372
  if (!id) return;
22079
- delete this._loggerStore[id];
22080
- const event = new LoggerChangeEvent({
22081
- id,
22082
- level: null,
22083
- source: "websocket",
22084
- deleted: true
22085
- });
22086
- for (const cb of this._globalListeners) {
22087
- try {
22088
- cb(event);
22089
- } catch (err) {
22090
- debug(
22091
- "websocket",
22092
- `logger_deleted listener error: ${err instanceof Error ? err.message : String(err)}`
22093
- );
22094
- }
22095
- }
22096
- const idCallbacks = this._keyListeners.get(id);
22097
- if (idCallbacks) {
22098
- for (const cb of idCallbacks) {
22099
- try {
22100
- cb(event);
22101
- } catch (err) {
22102
- debug(
22103
- "websocket",
22104
- `logger_deleted key listener error: ${err instanceof Error ? err.message : String(err)}`
22105
- );
22106
- }
22107
- }
22108
- }
22373
+ delete this._loggersCache[id];
22374
+ const preResolved = { ...this._resolvedLevelStore };
22375
+ this._applyLevels();
22376
+ this._emitDeletedEvent(id, "websocket");
22377
+ this._fireDeltas(preResolved, this._resolvedLevelStore, "websocket", /* @__PURE__ */ new Set([id]));
22109
22378
  };
22110
22379
  _handleGroupChanged = (data) => {
22111
22380
  debug("websocket", `group_changed event received: ${JSON.stringify(data)}`);
22112
22381
  const id = data.id;
22113
22382
  if (!id) return;
22114
22383
  void this._fetchSingleGroup(id).then((group) => {
22115
- const oldLevel = this._groupStore[id] ?? null;
22116
- const newLevel = group?.level ?? null;
22117
- if (oldLevel === newLevel) return;
22118
- this._groupStore[id] = newLevel;
22119
- void this._listLoggers().then((loggers) => {
22120
- this._applyLevels(loggers);
22121
- }).catch(() => {
22122
- });
22384
+ const preResolved = { ...this._resolvedLevelStore };
22385
+ if (group !== null) {
22386
+ this._groupsCache[id] = this._groupToCacheEntry(group);
22387
+ } else {
22388
+ delete this._groupsCache[id];
22389
+ }
22390
+ this._applyLevels();
22391
+ this._fireDeltas(preResolved, this._resolvedLevelStore, "websocket");
22123
22392
  }).catch((err) => {
22124
22393
  debug(
22125
22394
  "websocket",
@@ -22131,11 +22400,68 @@ var LoggingClient = class {
22131
22400
  debug("websocket", `group_deleted event received: ${JSON.stringify(data)}`);
22132
22401
  const id = data.id;
22133
22402
  if (!id) return;
22134
- delete this._groupStore[id];
22403
+ delete this._groupsCache[id];
22404
+ const preResolved = { ...this._resolvedLevelStore };
22405
+ this._applyLevels();
22406
+ this._emitDeletedEvent(id, "websocket");
22407
+ this._fireDeltas(preResolved, this._resolvedLevelStore, "websocket", /* @__PURE__ */ new Set([id]));
22408
+ };
22409
+ /**
22410
+ * @internal Fire change listeners for every logger whose resolved level
22411
+ * changed between `pre` and `post`. Stores resolved levels, not raw
22412
+ * `logger.level`, so a group-driven change actually fires. Keys that
22413
+ * disappeared from `post` are emitted with `deleted: true`.
22414
+ */
22415
+ _fireDeltas(pre, post, source, excludeIds) {
22416
+ const changed = [];
22417
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(pre), ...Object.keys(post)]);
22418
+ for (const k of allKeys) {
22419
+ if (excludeIds?.has(k)) continue;
22420
+ if (pre[k] !== post[k]) changed.push(k);
22421
+ }
22422
+ if (changed.length === 0) return;
22423
+ const buildEvent = (id) => {
22424
+ const isDeletion = id in pre && !(id in post);
22425
+ const fields = {
22426
+ id,
22427
+ source,
22428
+ level: post[id] ?? null
22429
+ };
22430
+ if (isDeletion) fields.deleted = true;
22431
+ return new LoggerChangeEvent(fields);
22432
+ };
22433
+ const firstKey = changed[0];
22434
+ const globalEvent = buildEvent(firstKey);
22435
+ for (const cb of this._globalListeners) {
22436
+ try {
22437
+ cb(globalEvent);
22438
+ } catch {
22439
+ }
22440
+ }
22441
+ for (const k of changed) {
22442
+ const keyCallbacks = this._keyListeners.get(k);
22443
+ if (keyCallbacks) {
22444
+ const event = buildEvent(k);
22445
+ for (const cb of keyCallbacks) {
22446
+ try {
22447
+ cb(event);
22448
+ } catch {
22449
+ }
22450
+ }
22451
+ }
22452
+ }
22453
+ }
22454
+ /**
22455
+ * @internal Always emit a deleted event for `id` on global and per-key
22456
+ * listeners. Used by `logger_deleted` / `group_deleted` handlers to
22457
+ * preserve the explicit notification contract even when nothing in the
22458
+ * resolved-level store changed (e.g. server removed an unknown logger).
22459
+ */
22460
+ _emitDeletedEvent(id, source) {
22135
22461
  const event = new LoggerChangeEvent({
22136
22462
  id,
22137
22463
  level: null,
22138
- source: "websocket",
22464
+ source,
22139
22465
  deleted: true
22140
22466
  });
22141
22467
  for (const cb of this._globalListeners) {
@@ -22144,7 +22470,7 @@ var LoggingClient = class {
22144
22470
  } catch (err) {
22145
22471
  debug(
22146
22472
  "websocket",
22147
- `group_deleted listener error: ${err instanceof Error ? err.message : String(err)}`
22473
+ `deleted listener error: ${err instanceof Error ? err.message : String(err)}`
22148
22474
  );
22149
22475
  }
22150
22476
  }
@@ -22156,22 +22482,22 @@ var LoggingClient = class {
22156
22482
  } catch (err) {
22157
22483
  debug(
22158
22484
  "websocket",
22159
- `group_deleted key listener error: ${err instanceof Error ? err.message : String(err)}`
22485
+ `deleted key listener error: ${err instanceof Error ? err.message : String(err)}`
22160
22486
  );
22161
22487
  }
22162
22488
  }
22163
22489
  }
22164
- };
22490
+ }
22165
22491
  _handleLoggersChanged = (_data) => {
22166
22492
  debug("websocket", `loggers_changed event received`);
22167
22493
  void this._resolveAndFire("websocket").catch(() => {
22168
22494
  });
22169
22495
  };
22170
22496
  /**
22171
- * Full refetch of loggers + log_groups, apply resolved levels to
22172
- * adapters, diff against local stores and fire change listeners
22173
- * (global once, per-key for each changed id). Shared between the
22174
- * `loggers_changed` WS handler and the public `refresh()` method.
22497
+ * Full refetch of loggers + log_groups, rebuild the caches, re-resolve
22498
+ * every adapter-known logger, and fire change listeners on resolved-level
22499
+ * deltas. Shared between the `loggers_changed` WS handler and the
22500
+ * public `refresh()` method.
22175
22501
  * @internal
22176
22502
  */
22177
22503
  async _resolveAndFire(source) {
@@ -22180,58 +22506,12 @@ var LoggingClient = class {
22180
22506
  this._listLogGroups()
22181
22507
  ]);
22182
22508
  debug("resolution", `resolution pass (trigger: ${source})`);
22183
- const changedLoggerIds = /* @__PURE__ */ new Set();
22184
- const newLoggerKeys = new Set(serverLoggers.map((l) => l.id));
22185
- for (const logger of serverLoggers) {
22186
- const key = logger.id;
22187
- const oldLevel = this._loggerStore[key] ?? null;
22188
- const newLevel = logger.level ?? null;
22189
- if (oldLevel !== newLevel || !(key in this._loggerStore)) {
22190
- changedLoggerIds.add(key);
22191
- this._loggerStore[key] = newLevel;
22192
- }
22193
- }
22194
- for (const key of Object.keys(this._loggerStore)) {
22195
- if (!newLoggerKeys.has(key)) {
22196
- changedLoggerIds.add(key);
22197
- delete this._loggerStore[key];
22198
- }
22199
- }
22200
- for (const group of serverGroups) {
22201
- this._groupStore[group.id] = group.level ?? null;
22202
- }
22203
- this._applyLevels(serverLoggers);
22204
- if (changedLoggerIds.size === 0) return;
22205
- const [firstKey] = changedLoggerIds;
22206
- const firstLogger = serverLoggers.find((l) => l.id === firstKey);
22207
- const globalEvent = new LoggerChangeEvent({
22208
- id: firstKey,
22209
- level: firstLogger?.level ?? null,
22210
- source
22211
- });
22212
- for (const cb of this._globalListeners) {
22213
- try {
22214
- cb(globalEvent);
22215
- } catch {
22216
- }
22217
- }
22218
- for (const key of changedLoggerIds) {
22219
- const keyCallbacks = this._keyListeners.get(key);
22220
- if (keyCallbacks) {
22221
- const l = serverLoggers.find((x) => x.id === key);
22222
- const keyEvent = new LoggerChangeEvent({
22223
- id: key,
22224
- level: l?.level ?? null,
22225
- source
22226
- });
22227
- for (const cb of keyCallbacks) {
22228
- try {
22229
- cb(keyEvent);
22230
- } catch {
22231
- }
22232
- }
22233
- }
22234
- }
22509
+ const preResolved = { ...this._resolvedLevelStore };
22510
+ this._loggersCache = this._buildLoggersCache(serverLoggers);
22511
+ this._groupsCache = this._buildGroupsCache(serverGroups);
22512
+ this._resolvedLevelStore = {};
22513
+ this._applyLevels();
22514
+ this._fireDeltas(preResolved, this._resolvedLevelStore, source);
22235
22515
  }
22236
22516
  // ------------------------------------------------------------------
22237
22517
  // Internal: single-resource fetchers