@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.d.cts CHANGED
@@ -1,6 +1,43 @@
1
+ /**
2
+ * Internal SDK telemetry engine.
3
+ *
4
+ * Accumulates usage metrics in memory and periodically flushes them to the
5
+ * app service via `POST /api/v1/metrics/bulk`. This module is entirely
6
+ * private — nothing here is exported or documented for customers.
7
+ *
8
+ * @internal
9
+ */
10
+ /** @internal */
11
+ declare class MetricsReporter {
12
+ private readonly _apiKey;
13
+ private readonly _environment;
14
+ private readonly _service;
15
+ private readonly _flushInterval;
16
+ private _counters;
17
+ private _gauges;
18
+ private _timer;
19
+ private _closed;
20
+ constructor(options: {
21
+ apiKey: string;
22
+ environment: string;
23
+ service: string;
24
+ flushInterval?: number;
25
+ });
26
+ record(name: string, value?: number, unit?: string | null, dimensions?: Record<string, string>): void;
27
+ recordGauge(name: string, value: number, unit?: string | null, dimensions?: Record<string, string>): void;
28
+ flush(): void;
29
+ close(): void;
30
+ private _mergeDimensions;
31
+ private _maybeStartTimer;
32
+ private _flush;
33
+ private _buildPayload;
34
+ private _entry;
35
+ }
36
+
1
37
  /**
2
38
  * Shared WebSocket connection for real-time event delivery.
3
39
  */
40
+
4
41
  type EventCallback = (data: Record<string, any>) => void;
5
42
  /**
6
43
  * Manages a WebSocket connection for real-time event delivery.
@@ -8,13 +45,14 @@ type EventCallback = (data: Record<string, any>) => void;
8
45
  declare class SharedWebSocket {
9
46
  private readonly _appBaseUrl;
10
47
  private readonly _apiKey;
48
+ private readonly _metrics;
11
49
  private _listeners;
12
50
  private _connectionStatus;
13
51
  private _closed;
14
52
  private _ws;
15
53
  private _reconnectTimer;
16
54
  private _backoffIndex;
17
- constructor(appBaseUrl: string, apiKey: string);
55
+ constructor(appBaseUrl: string, apiKey: string, metrics?: MetricsReporter | null);
18
56
  /** Register a listener for a specific event type. */
19
57
  on(eventName: string, callback: EventCallback): void;
20
58
  /** Unregister a listener for a specific event type. */
@@ -159,6 +197,7 @@ declare class ConfigClient {
159
197
  _parent: {
160
198
  readonly _environment: string;
161
199
  readonly _service: string | null;
200
+ readonly _metrics: MetricsReporter | null;
162
201
  } | null;
163
202
  private _configCache;
164
203
  private _initialized;
@@ -649,6 +688,7 @@ declare class FlagsClient {
649
688
  _parent: {
650
689
  readonly _environment: string;
651
690
  readonly _service: string | null;
691
+ readonly _metrics: MetricsReporter | null;
652
692
  } | null;
653
693
  /** @internal */
654
694
  constructor(apiKey: string, ensureWs: () => SharedWebSocket, timeout?: number);
@@ -962,6 +1002,7 @@ declare class LoggingClient {
962
1002
  _parent: {
963
1003
  readonly _environment: string;
964
1004
  readonly _service: string | null;
1005
+ readonly _metrics: MetricsReporter | null;
965
1006
  } | null;
966
1007
  private readonly _ensureWs;
967
1008
  private _wsManager;
@@ -1064,6 +1105,12 @@ interface SmplClientOptions {
1064
1105
  * @default 30000
1065
1106
  */
1066
1107
  timeout?: number;
1108
+ /**
1109
+ * Disable SDK telemetry reporting.
1110
+ * When `true`, no usage metrics are collected or sent.
1111
+ * @default false
1112
+ */
1113
+ disableTelemetry?: boolean;
1067
1114
  }
1068
1115
  /**
1069
1116
  * Entry point for the smplkit TypeScript SDK.
@@ -1100,6 +1147,8 @@ declare class SmplClient {
1100
1147
  readonly _environment: string;
1101
1148
  /** @internal */
1102
1149
  readonly _service: string;
1150
+ /** @internal */
1151
+ readonly _metrics: MetricsReporter | null;
1103
1152
  private readonly _timeout;
1104
1153
  private readonly _appHttp;
1105
1154
  constructor(options?: SmplClientOptions);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,43 @@
1
+ /**
2
+ * Internal SDK telemetry engine.
3
+ *
4
+ * Accumulates usage metrics in memory and periodically flushes them to the
5
+ * app service via `POST /api/v1/metrics/bulk`. This module is entirely
6
+ * private — nothing here is exported or documented for customers.
7
+ *
8
+ * @internal
9
+ */
10
+ /** @internal */
11
+ declare class MetricsReporter {
12
+ private readonly _apiKey;
13
+ private readonly _environment;
14
+ private readonly _service;
15
+ private readonly _flushInterval;
16
+ private _counters;
17
+ private _gauges;
18
+ private _timer;
19
+ private _closed;
20
+ constructor(options: {
21
+ apiKey: string;
22
+ environment: string;
23
+ service: string;
24
+ flushInterval?: number;
25
+ });
26
+ record(name: string, value?: number, unit?: string | null, dimensions?: Record<string, string>): void;
27
+ recordGauge(name: string, value: number, unit?: string | null, dimensions?: Record<string, string>): void;
28
+ flush(): void;
29
+ close(): void;
30
+ private _mergeDimensions;
31
+ private _maybeStartTimer;
32
+ private _flush;
33
+ private _buildPayload;
34
+ private _entry;
35
+ }
36
+
1
37
  /**
2
38
  * Shared WebSocket connection for real-time event delivery.
3
39
  */
40
+
4
41
  type EventCallback = (data: Record<string, any>) => void;
5
42
  /**
6
43
  * Manages a WebSocket connection for real-time event delivery.
@@ -8,13 +45,14 @@ type EventCallback = (data: Record<string, any>) => void;
8
45
  declare class SharedWebSocket {
9
46
  private readonly _appBaseUrl;
10
47
  private readonly _apiKey;
48
+ private readonly _metrics;
11
49
  private _listeners;
12
50
  private _connectionStatus;
13
51
  private _closed;
14
52
  private _ws;
15
53
  private _reconnectTimer;
16
54
  private _backoffIndex;
17
- constructor(appBaseUrl: string, apiKey: string);
55
+ constructor(appBaseUrl: string, apiKey: string, metrics?: MetricsReporter | null);
18
56
  /** Register a listener for a specific event type. */
19
57
  on(eventName: string, callback: EventCallback): void;
20
58
  /** Unregister a listener for a specific event type. */
@@ -159,6 +197,7 @@ declare class ConfigClient {
159
197
  _parent: {
160
198
  readonly _environment: string;
161
199
  readonly _service: string | null;
200
+ readonly _metrics: MetricsReporter | null;
162
201
  } | null;
163
202
  private _configCache;
164
203
  private _initialized;
@@ -649,6 +688,7 @@ declare class FlagsClient {
649
688
  _parent: {
650
689
  readonly _environment: string;
651
690
  readonly _service: string | null;
691
+ readonly _metrics: MetricsReporter | null;
652
692
  } | null;
653
693
  /** @internal */
654
694
  constructor(apiKey: string, ensureWs: () => SharedWebSocket, timeout?: number);
@@ -962,6 +1002,7 @@ declare class LoggingClient {
962
1002
  _parent: {
963
1003
  readonly _environment: string;
964
1004
  readonly _service: string | null;
1005
+ readonly _metrics: MetricsReporter | null;
965
1006
  } | null;
966
1007
  private readonly _ensureWs;
967
1008
  private _wsManager;
@@ -1064,6 +1105,12 @@ interface SmplClientOptions {
1064
1105
  * @default 30000
1065
1106
  */
1066
1107
  timeout?: number;
1108
+ /**
1109
+ * Disable SDK telemetry reporting.
1110
+ * When `true`, no usage metrics are collected or sent.
1111
+ * @default false
1112
+ */
1113
+ disableTelemetry?: boolean;
1067
1114
  }
1068
1115
  /**
1069
1116
  * Entry point for the smplkit TypeScript SDK.
@@ -1100,6 +1147,8 @@ declare class SmplClient {
1100
1147
  readonly _environment: string;
1101
1148
  /** @internal */
1102
1149
  readonly _service: string;
1150
+ /** @internal */
1151
+ readonly _metrics: MetricsReporter | null;
1103
1152
  private readonly _timeout;
1104
1153
  private readonly _appHttp;
1105
1154
  constructor(options?: SmplClientOptions);
package/dist/index.js CHANGED
@@ -17182,6 +17182,10 @@ var ConfigClient = class {
17182
17182
  if (values === void 0) {
17183
17183
  throw new SmplNotFoundError(`Config with key '${key}' not found in cache`);
17184
17184
  }
17185
+ const metrics = this._parent?._metrics;
17186
+ if (metrics) {
17187
+ metrics.record("config.resolutions", 1, "resolutions", { config_id: key });
17188
+ }
17185
17189
  if (model) {
17186
17190
  return new model(values);
17187
17191
  }
@@ -17316,6 +17320,10 @@ var ConfigClient = class {
17316
17320
  const oldVal = iKey in oldItems ? oldItems[iKey] : null;
17317
17321
  const newVal = iKey in newItems ? newItems[iKey] : null;
17318
17322
  if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
17323
+ const metrics = this._parent?._metrics;
17324
+ if (metrics) {
17325
+ metrics.record("config.changes", 1, "changes", { config_id: cfgKey });
17326
+ }
17319
17327
  const event = {
17320
17328
  configKey: cfgKey,
17321
17329
  itemKey: iKey,
@@ -18151,8 +18159,18 @@ var FlagsClient = class {
18151
18159
  const cacheKey = `${key}:${ctxHash}`;
18152
18160
  const [hit, cachedValue] = this._cache.get(cacheKey);
18153
18161
  if (hit) {
18162
+ const metrics2 = this._parent?._metrics;
18163
+ if (metrics2) {
18164
+ metrics2.record("flags.cache_hits", 1, "hits");
18165
+ metrics2.record("flags.evaluations", 1, "evaluations", { flag_id: key });
18166
+ }
18154
18167
  return cachedValue;
18155
18168
  }
18169
+ const metrics = this._parent?._metrics;
18170
+ if (metrics) {
18171
+ metrics.record("flags.cache_misses", 1, "misses");
18172
+ metrics.record("flags.evaluations", 1, "evaluations", { flag_id: key });
18173
+ }
18156
18174
  const flagDef = this._flagStore[key];
18157
18175
  if (flagDef === void 0) {
18158
18176
  this._cache.put(cacheKey, defaultValue);
@@ -18796,6 +18814,12 @@ var LoggingClient = class {
18796
18814
  } catch {
18797
18815
  }
18798
18816
  }
18817
+ if (discovered.length > 0) {
18818
+ const metrics = this._parent?._metrics;
18819
+ if (metrics) {
18820
+ metrics.record("logging.loggers_discovered", discovered.length, "loggers");
18821
+ }
18822
+ }
18799
18823
  for (const { name, level } of discovered) {
18800
18824
  try {
18801
18825
  const logger = this.new(name, { managed: true });
@@ -18890,6 +18914,10 @@ var LoggingClient = class {
18890
18914
  effectiveLevel = envOverride.level;
18891
18915
  }
18892
18916
  }
18917
+ const metrics = this._parent?._metrics;
18918
+ if (metrics) {
18919
+ metrics.record("logging.level_changes", 1, "changes", { logger_id: logger.key });
18920
+ }
18893
18921
  for (const adapter of this._adapters) {
18894
18922
  try {
18895
18923
  adapter.applyLevel(logger.key, effectiveLevel);
@@ -18973,15 +19001,17 @@ var BACKOFF_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 32e3, 6e4];
18973
19001
  var SharedWebSocket = class {
18974
19002
  _appBaseUrl;
18975
19003
  _apiKey;
19004
+ _metrics;
18976
19005
  _listeners = /* @__PURE__ */ new Map();
18977
19006
  _connectionStatus = "disconnected";
18978
19007
  _closed = false;
18979
19008
  _ws = null;
18980
19009
  _reconnectTimer = null;
18981
19010
  _backoffIndex = 0;
18982
- constructor(appBaseUrl, apiKey) {
19011
+ constructor(appBaseUrl, apiKey, metrics) {
18983
19012
  this._appBaseUrl = appBaseUrl;
18984
19013
  this._apiKey = apiKey;
19014
+ this._metrics = metrics ?? null;
18985
19015
  }
18986
19016
  // ------------------------------------------------------------------
18987
19017
  // Listener registration
@@ -19080,6 +19110,9 @@ var SharedWebSocket = class {
19080
19110
  if (msg.type === "connected") {
19081
19111
  this._backoffIndex = 0;
19082
19112
  this._connectionStatus = "connected";
19113
+ if (this._metrics) {
19114
+ this._metrics.recordGauge("platform.websocket_connections", 1, "connections");
19115
+ }
19083
19116
  return;
19084
19117
  }
19085
19118
  if (msg.type === "error") {
@@ -19093,6 +19126,9 @@ var SharedWebSocket = class {
19093
19126
  }
19094
19127
  });
19095
19128
  ws.on("close", () => {
19129
+ if (this._metrics) {
19130
+ this._metrics.recordGauge("platform.websocket_connections", 0, "connections");
19131
+ }
19096
19132
  if (!this._closed) {
19097
19133
  this._connectionStatus = "disconnected";
19098
19134
  this._scheduleReconnect();
@@ -19173,8 +19209,145 @@ function resolveApiKey(explicit, environment) {
19173
19209
  throw new SmplError(noApiKeyMessage(environment));
19174
19210
  }
19175
19211
 
19176
- // src/client.ts
19212
+ // src/_metrics.ts
19177
19213
  var APP_BASE_URL2 = "https://app.smplkit.com";
19214
+ function makeCounter(unit) {
19215
+ return { value: 0, unit, windowStart: (/* @__PURE__ */ new Date()).toISOString() };
19216
+ }
19217
+ function makeMapKey(name, dimensions) {
19218
+ const sorted = Object.keys(dimensions).sort().map((k) => `${k}=${dimensions[k]}`).join("&");
19219
+ return `${name}|${sorted}`;
19220
+ }
19221
+ var MetricsReporter = class {
19222
+ _apiKey;
19223
+ _environment;
19224
+ _service;
19225
+ _flushInterval;
19226
+ _counters = /* @__PURE__ */ new Map();
19227
+ _gauges = /* @__PURE__ */ new Map();
19228
+ _timer = null;
19229
+ _closed = false;
19230
+ constructor(options) {
19231
+ this._apiKey = options.apiKey;
19232
+ this._environment = options.environment;
19233
+ this._service = options.service;
19234
+ this._flushInterval = options.flushInterval ?? 60;
19235
+ }
19236
+ // ------------------------------------------------------------------
19237
+ // Public recording API
19238
+ // ------------------------------------------------------------------
19239
+ record(name, value = 1, unit = null, dimensions) {
19240
+ const merged = this._mergeDimensions(dimensions);
19241
+ const key = makeMapKey(name, merged);
19242
+ let entry = this._counters.get(key);
19243
+ if (!entry) {
19244
+ entry = { name, dimensions: merged, counter: makeCounter(unit) };
19245
+ this._counters.set(key, entry);
19246
+ }
19247
+ entry.counter.value += value;
19248
+ if (entry.counter.unit === null && unit !== null) {
19249
+ entry.counter.unit = unit;
19250
+ }
19251
+ this._maybeStartTimer();
19252
+ }
19253
+ recordGauge(name, value, unit = null, dimensions) {
19254
+ const merged = this._mergeDimensions(dimensions);
19255
+ const key = makeMapKey(name, merged);
19256
+ let entry = this._gauges.get(key);
19257
+ if (!entry) {
19258
+ entry = { name, dimensions: merged, counter: makeCounter(unit) };
19259
+ this._gauges.set(key, entry);
19260
+ }
19261
+ entry.counter.value = value;
19262
+ if (entry.counter.unit === null && unit !== null) {
19263
+ entry.counter.unit = unit;
19264
+ }
19265
+ this._maybeStartTimer();
19266
+ }
19267
+ // ------------------------------------------------------------------
19268
+ // Flush / close
19269
+ // ------------------------------------------------------------------
19270
+ flush() {
19271
+ this._flush();
19272
+ }
19273
+ close() {
19274
+ if (this._closed) return;
19275
+ this._closed = true;
19276
+ if (this._timer !== null) {
19277
+ clearInterval(this._timer);
19278
+ this._timer = null;
19279
+ }
19280
+ this._flush();
19281
+ }
19282
+ // ------------------------------------------------------------------
19283
+ // Internal
19284
+ // ------------------------------------------------------------------
19285
+ _mergeDimensions(dimensions) {
19286
+ const merged = {
19287
+ environment: this._environment,
19288
+ service: this._service
19289
+ };
19290
+ if (dimensions) {
19291
+ Object.assign(merged, dimensions);
19292
+ }
19293
+ return merged;
19294
+ }
19295
+ _maybeStartTimer() {
19296
+ if (this._timer === null && !this._closed) {
19297
+ this._timer = setInterval(() => this._flush(), this._flushInterval * 1e3);
19298
+ if (typeof this._timer === "object" && "unref" in this._timer) {
19299
+ this._timer.unref();
19300
+ }
19301
+ }
19302
+ }
19303
+ _flush() {
19304
+ const counters = this._counters;
19305
+ const gauges = this._gauges;
19306
+ this._counters = /* @__PURE__ */ new Map();
19307
+ this._gauges = /* @__PURE__ */ new Map();
19308
+ if (counters.size === 0 && gauges.size === 0) return;
19309
+ const payload = this._buildPayload(counters, gauges);
19310
+ try {
19311
+ fetch(`${APP_BASE_URL2}/api/v1/metrics/bulk`, {
19312
+ method: "POST",
19313
+ headers: {
19314
+ Authorization: `Bearer ${this._apiKey}`,
19315
+ "Content-Type": "application/vnd.api+json",
19316
+ Accept: "application/json"
19317
+ },
19318
+ body: JSON.stringify(payload)
19319
+ }).catch(() => {
19320
+ });
19321
+ } catch {
19322
+ }
19323
+ }
19324
+ _buildPayload(counters, gauges) {
19325
+ const data = [];
19326
+ for (const [, entry] of counters) {
19327
+ data.push(this._entry(entry.name, entry.counter, entry.dimensions));
19328
+ }
19329
+ for (const [, entry] of gauges) {
19330
+ data.push(this._entry(entry.name, entry.counter, entry.dimensions));
19331
+ }
19332
+ return { data };
19333
+ }
19334
+ _entry(name, counter, dimensions) {
19335
+ return {
19336
+ type: "metric",
19337
+ attributes: {
19338
+ name,
19339
+ value: counter.value,
19340
+ unit: counter.unit,
19341
+ period_seconds: this._flushInterval,
19342
+ dimensions,
19343
+ recorded_at: counter.windowStart
19344
+ }
19345
+ };
19346
+ }
19347
+ };
19348
+
19349
+ // src/client.ts
19350
+ var APP_BASE_URL3 = "https://app.smplkit.com";
19178
19351
  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";
19179
19352
  var NO_SERVICE_MESSAGE = "No service provided. Set one of:\n 1. Pass service in options\n 2. Set the SMPLKIT_SERVICE environment variable";
19180
19353
  var SmplClient = class {
@@ -19190,6 +19363,8 @@ var SmplClient = class {
19190
19363
  _environment;
19191
19364
  /** @internal */
19192
19365
  _service;
19366
+ /** @internal */
19367
+ _metrics = null;
19193
19368
  _timeout;
19194
19369
  _appHttp;
19195
19370
  constructor(options = {}) {
@@ -19207,12 +19382,19 @@ var SmplClient = class {
19207
19382
  this._apiKey = apiKey;
19208
19383
  this._timeout = options.timeout ?? 3e4;
19209
19384
  this._appHttp = createClient4({
19210
- baseUrl: APP_BASE_URL2,
19385
+ baseUrl: APP_BASE_URL3,
19211
19386
  headers: {
19212
19387
  Authorization: `Bearer ${apiKey}`,
19213
19388
  Accept: "application/json"
19214
19389
  }
19215
19390
  });
19391
+ if (!options.disableTelemetry) {
19392
+ this._metrics = new MetricsReporter({
19393
+ apiKey,
19394
+ environment: this._environment,
19395
+ service: this._service
19396
+ });
19397
+ }
19216
19398
  this.config = new ConfigClient(apiKey, this._timeout);
19217
19399
  this.flags = new FlagsClient(apiKey, () => this._ensureWs(), this._timeout);
19218
19400
  this.logging = new LoggingClient(apiKey, () => this._ensureWs(), this._timeout);
@@ -19242,13 +19424,16 @@ var SmplClient = class {
19242
19424
  /** Lazily create and start the shared WebSocket. @internal */
19243
19425
  _ensureWs() {
19244
19426
  if (this._wsManager === null) {
19245
- this._wsManager = new SharedWebSocket(APP_BASE_URL2, this._apiKey);
19427
+ this._wsManager = new SharedWebSocket(APP_BASE_URL3, this._apiKey, this._metrics);
19246
19428
  this._wsManager.start();
19247
19429
  }
19248
19430
  return this._wsManager;
19249
19431
  }
19250
19432
  /** Close the shared WebSocket and release resources. */
19251
19433
  close() {
19434
+ if (this._metrics !== null) {
19435
+ this._metrics.close();
19436
+ }
19252
19437
  this.logging._close();
19253
19438
  if (this._wsManager !== null) {
19254
19439
  this._wsManager.stop();