@smplkit/sdk 1.3.26 → 1.3.27

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
@@ -17220,6 +17220,10 @@ var ConfigClient = class {
17220
17220
  if (values === void 0) {
17221
17221
  throw new SmplNotFoundError(`Config with key '${key}' not found in cache`);
17222
17222
  }
17223
+ const metrics = this._parent?._metrics;
17224
+ if (metrics) {
17225
+ metrics.record("config.resolutions", 1, "resolutions", { config_id: key });
17226
+ }
17223
17227
  if (model) {
17224
17228
  return new model(values);
17225
17229
  }
@@ -17354,6 +17358,10 @@ var ConfigClient = class {
17354
17358
  const oldVal = iKey in oldItems ? oldItems[iKey] : null;
17355
17359
  const newVal = iKey in newItems ? newItems[iKey] : null;
17356
17360
  if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
17361
+ const metrics = this._parent?._metrics;
17362
+ if (metrics) {
17363
+ metrics.record("config.changes", 1, "changes", { config_id: cfgKey });
17364
+ }
17357
17365
  const event = {
17358
17366
  configKey: cfgKey,
17359
17367
  itemKey: iKey,
@@ -18189,8 +18197,18 @@ var FlagsClient = class {
18189
18197
  const cacheKey = `${key}:${ctxHash}`;
18190
18198
  const [hit, cachedValue] = this._cache.get(cacheKey);
18191
18199
  if (hit) {
18200
+ const metrics2 = this._parent?._metrics;
18201
+ if (metrics2) {
18202
+ metrics2.record("flags.cache_hits", 1, "hits");
18203
+ metrics2.record("flags.evaluations", 1, "evaluations", { flag_id: key });
18204
+ }
18192
18205
  return cachedValue;
18193
18206
  }
18207
+ const metrics = this._parent?._metrics;
18208
+ if (metrics) {
18209
+ metrics.record("flags.cache_misses", 1, "misses");
18210
+ metrics.record("flags.evaluations", 1, "evaluations", { flag_id: key });
18211
+ }
18194
18212
  const flagDef = this._flagStore[key];
18195
18213
  if (flagDef === void 0) {
18196
18214
  this._cache.put(cacheKey, defaultValue);
@@ -18834,6 +18852,12 @@ var LoggingClient = class {
18834
18852
  } catch {
18835
18853
  }
18836
18854
  }
18855
+ if (discovered.length > 0) {
18856
+ const metrics = this._parent?._metrics;
18857
+ if (metrics) {
18858
+ metrics.record("logging.loggers_discovered", discovered.length, "loggers");
18859
+ }
18860
+ }
18837
18861
  for (const { name, level } of discovered) {
18838
18862
  try {
18839
18863
  const logger = this.new(name, { managed: true });
@@ -18928,6 +18952,10 @@ var LoggingClient = class {
18928
18952
  effectiveLevel = envOverride.level;
18929
18953
  }
18930
18954
  }
18955
+ const metrics = this._parent?._metrics;
18956
+ if (metrics) {
18957
+ metrics.record("logging.level_changes", 1, "changes", { logger_id: logger.key });
18958
+ }
18931
18959
  for (const adapter of this._adapters) {
18932
18960
  try {
18933
18961
  adapter.applyLevel(logger.key, effectiveLevel);
@@ -19011,15 +19039,17 @@ var BACKOFF_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 32e3, 6e4];
19011
19039
  var SharedWebSocket = class {
19012
19040
  _appBaseUrl;
19013
19041
  _apiKey;
19042
+ _metrics;
19014
19043
  _listeners = /* @__PURE__ */ new Map();
19015
19044
  _connectionStatus = "disconnected";
19016
19045
  _closed = false;
19017
19046
  _ws = null;
19018
19047
  _reconnectTimer = null;
19019
19048
  _backoffIndex = 0;
19020
- constructor(appBaseUrl, apiKey) {
19049
+ constructor(appBaseUrl, apiKey, metrics) {
19021
19050
  this._appBaseUrl = appBaseUrl;
19022
19051
  this._apiKey = apiKey;
19052
+ this._metrics = metrics ?? null;
19023
19053
  }
19024
19054
  // ------------------------------------------------------------------
19025
19055
  // Listener registration
@@ -19118,6 +19148,9 @@ var SharedWebSocket = class {
19118
19148
  if (msg.type === "connected") {
19119
19149
  this._backoffIndex = 0;
19120
19150
  this._connectionStatus = "connected";
19151
+ if (this._metrics) {
19152
+ this._metrics.recordGauge("platform.websocket_connections", 1, "connections");
19153
+ }
19121
19154
  return;
19122
19155
  }
19123
19156
  if (msg.type === "error") {
@@ -19131,6 +19164,9 @@ var SharedWebSocket = class {
19131
19164
  }
19132
19165
  });
19133
19166
  ws.on("close", () => {
19167
+ if (this._metrics) {
19168
+ this._metrics.recordGauge("platform.websocket_connections", 0, "connections");
19169
+ }
19134
19170
  if (!this._closed) {
19135
19171
  this._connectionStatus = "disconnected";
19136
19172
  this._scheduleReconnect();
@@ -19211,8 +19247,145 @@ function resolveApiKey(explicit, environment) {
19211
19247
  throw new SmplError(noApiKeyMessage(environment));
19212
19248
  }
19213
19249
 
19214
- // src/client.ts
19250
+ // src/_metrics.ts
19215
19251
  var APP_BASE_URL2 = "https://app.smplkit.com";
19252
+ function makeCounter(unit) {
19253
+ return { value: 0, unit, windowStart: (/* @__PURE__ */ new Date()).toISOString() };
19254
+ }
19255
+ function makeMapKey(name, dimensions) {
19256
+ const sorted = Object.keys(dimensions).sort().map((k) => `${k}=${dimensions[k]}`).join("&");
19257
+ return `${name}|${sorted}`;
19258
+ }
19259
+ var MetricsReporter = class {
19260
+ _apiKey;
19261
+ _environment;
19262
+ _service;
19263
+ _flushInterval;
19264
+ _counters = /* @__PURE__ */ new Map();
19265
+ _gauges = /* @__PURE__ */ new Map();
19266
+ _timer = null;
19267
+ _closed = false;
19268
+ constructor(options) {
19269
+ this._apiKey = options.apiKey;
19270
+ this._environment = options.environment;
19271
+ this._service = options.service;
19272
+ this._flushInterval = options.flushInterval ?? 60;
19273
+ }
19274
+ // ------------------------------------------------------------------
19275
+ // Public recording API
19276
+ // ------------------------------------------------------------------
19277
+ record(name, value = 1, unit = null, dimensions) {
19278
+ const merged = this._mergeDimensions(dimensions);
19279
+ const key = makeMapKey(name, merged);
19280
+ let entry = this._counters.get(key);
19281
+ if (!entry) {
19282
+ entry = { name, dimensions: merged, counter: makeCounter(unit) };
19283
+ this._counters.set(key, entry);
19284
+ }
19285
+ entry.counter.value += value;
19286
+ if (entry.counter.unit === null && unit !== null) {
19287
+ entry.counter.unit = unit;
19288
+ }
19289
+ this._maybeStartTimer();
19290
+ }
19291
+ recordGauge(name, value, unit = null, dimensions) {
19292
+ const merged = this._mergeDimensions(dimensions);
19293
+ const key = makeMapKey(name, merged);
19294
+ let entry = this._gauges.get(key);
19295
+ if (!entry) {
19296
+ entry = { name, dimensions: merged, counter: makeCounter(unit) };
19297
+ this._gauges.set(key, entry);
19298
+ }
19299
+ entry.counter.value = value;
19300
+ if (entry.counter.unit === null && unit !== null) {
19301
+ entry.counter.unit = unit;
19302
+ }
19303
+ this._maybeStartTimer();
19304
+ }
19305
+ // ------------------------------------------------------------------
19306
+ // Flush / close
19307
+ // ------------------------------------------------------------------
19308
+ flush() {
19309
+ this._flush();
19310
+ }
19311
+ close() {
19312
+ if (this._closed) return;
19313
+ this._closed = true;
19314
+ if (this._timer !== null) {
19315
+ clearInterval(this._timer);
19316
+ this._timer = null;
19317
+ }
19318
+ this._flush();
19319
+ }
19320
+ // ------------------------------------------------------------------
19321
+ // Internal
19322
+ // ------------------------------------------------------------------
19323
+ _mergeDimensions(dimensions) {
19324
+ const merged = {
19325
+ environment: this._environment,
19326
+ service: this._service
19327
+ };
19328
+ if (dimensions) {
19329
+ Object.assign(merged, dimensions);
19330
+ }
19331
+ return merged;
19332
+ }
19333
+ _maybeStartTimer() {
19334
+ if (this._timer === null && !this._closed) {
19335
+ this._timer = setInterval(() => this._flush(), this._flushInterval * 1e3);
19336
+ if (typeof this._timer === "object" && "unref" in this._timer) {
19337
+ this._timer.unref();
19338
+ }
19339
+ }
19340
+ }
19341
+ _flush() {
19342
+ const counters = this._counters;
19343
+ const gauges = this._gauges;
19344
+ this._counters = /* @__PURE__ */ new Map();
19345
+ this._gauges = /* @__PURE__ */ new Map();
19346
+ if (counters.size === 0 && gauges.size === 0) return;
19347
+ const payload = this._buildPayload(counters, gauges);
19348
+ try {
19349
+ fetch(`${APP_BASE_URL2}/api/v1/metrics/bulk`, {
19350
+ method: "POST",
19351
+ headers: {
19352
+ Authorization: `Bearer ${this._apiKey}`,
19353
+ "Content-Type": "application/vnd.api+json",
19354
+ Accept: "application/json"
19355
+ },
19356
+ body: JSON.stringify(payload)
19357
+ }).catch(() => {
19358
+ });
19359
+ } catch {
19360
+ }
19361
+ }
19362
+ _buildPayload(counters, gauges) {
19363
+ const data = [];
19364
+ for (const [, entry] of counters) {
19365
+ data.push(this._entry(entry.name, entry.counter, entry.dimensions));
19366
+ }
19367
+ for (const [, entry] of gauges) {
19368
+ data.push(this._entry(entry.name, entry.counter, entry.dimensions));
19369
+ }
19370
+ return { data };
19371
+ }
19372
+ _entry(name, counter, dimensions) {
19373
+ return {
19374
+ type: "metric",
19375
+ attributes: {
19376
+ name,
19377
+ value: counter.value,
19378
+ unit: counter.unit,
19379
+ period_seconds: this._flushInterval,
19380
+ dimensions,
19381
+ recorded_at: counter.windowStart
19382
+ }
19383
+ };
19384
+ }
19385
+ };
19386
+
19387
+ // src/client.ts
19388
+ var APP_BASE_URL3 = "https://app.smplkit.com";
19216
19389
  var NO_ENVIRONMENT_MESSAGE = "No environment provided. Set one of:\n 1. Pass environment to the constructor\n 2. Set the SMPLKIT_ENVIRONMENT environment variable";
19217
19390
  var NO_SERVICE_MESSAGE = "No service provided. Set one of:\n 1. Pass service in options\n 2. Set the SMPLKIT_SERVICE environment variable";
19218
19391
  var SmplClient = class {
@@ -19228,6 +19401,8 @@ var SmplClient = class {
19228
19401
  _environment;
19229
19402
  /** @internal */
19230
19403
  _service;
19404
+ /** @internal */
19405
+ _metrics = null;
19231
19406
  _timeout;
19232
19407
  _appHttp;
19233
19408
  constructor(options = {}) {
@@ -19245,12 +19420,19 @@ var SmplClient = class {
19245
19420
  this._apiKey = apiKey;
19246
19421
  this._timeout = options.timeout ?? 3e4;
19247
19422
  this._appHttp = (0, import_openapi_fetch4.default)({
19248
- baseUrl: APP_BASE_URL2,
19423
+ baseUrl: APP_BASE_URL3,
19249
19424
  headers: {
19250
19425
  Authorization: `Bearer ${apiKey}`,
19251
19426
  Accept: "application/json"
19252
19427
  }
19253
19428
  });
19429
+ if (!options.disableTelemetry) {
19430
+ this._metrics = new MetricsReporter({
19431
+ apiKey,
19432
+ environment: this._environment,
19433
+ service: this._service
19434
+ });
19435
+ }
19254
19436
  this.config = new ConfigClient(apiKey, this._timeout);
19255
19437
  this.flags = new FlagsClient(apiKey, () => this._ensureWs(), this._timeout);
19256
19438
  this.logging = new LoggingClient(apiKey, () => this._ensureWs(), this._timeout);
@@ -19280,13 +19462,16 @@ var SmplClient = class {
19280
19462
  /** Lazily create and start the shared WebSocket. @internal */
19281
19463
  _ensureWs() {
19282
19464
  if (this._wsManager === null) {
19283
- this._wsManager = new SharedWebSocket(APP_BASE_URL2, this._apiKey);
19465
+ this._wsManager = new SharedWebSocket(APP_BASE_URL3, this._apiKey, this._metrics);
19284
19466
  this._wsManager.start();
19285
19467
  }
19286
19468
  return this._wsManager;
19287
19469
  }
19288
19470
  /** Close the shared WebSocket and release resources. */
19289
19471
  close() {
19472
+ if (this._metrics !== null) {
19473
+ this._metrics.close();
19474
+ }
19290
19475
  this.logging._close();
19291
19476
  if (this._wsManager !== null) {
19292
19477
  this._wsManager.stop();