@savvagent/sdk 1.0.0 → 1.1.0

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.js CHANGED
@@ -149,15 +149,23 @@ var TelemetryService = class {
149
149
  }
150
150
  /**
151
151
  * Send evaluation events to backend
152
+ * Per SDK Developer Guide: POST /api/telemetry/evaluations with { "evaluations": [...] }
152
153
  */
153
154
  async sendEvaluations(events) {
155
+ const evaluations = events.map((e) => ({
156
+ flag_key: e.flagKey,
157
+ result: e.result,
158
+ user_id: e.context?.user_id,
159
+ context: e.context,
160
+ timestamp: Math.floor(new Date(e.timestamp).getTime() / 1e3)
161
+ }));
154
162
  const response = await fetch(`${this.baseUrl}/api/telemetry/evaluations`, {
155
163
  method: "POST",
156
164
  headers: {
157
165
  "Content-Type": "application/json",
158
166
  Authorization: `Bearer ${this.apiKey}`
159
167
  },
160
- body: JSON.stringify({ events })
168
+ body: JSON.stringify({ evaluations })
161
169
  });
162
170
  if (!response.ok) {
163
171
  throw new Error(`Failed to send evaluations: ${response.status}`);
@@ -165,15 +173,25 @@ var TelemetryService = class {
165
173
  }
166
174
  /**
167
175
  * Send error events to backend
176
+ * Per SDK Developer Guide: POST /api/telemetry/errors with { "errors": [...] }
168
177
  */
169
178
  async sendErrors(events) {
179
+ const errors = events.map((e) => ({
180
+ flag_key: e.flagKey,
181
+ flag_enabled: e.flagEnabled,
182
+ error_type: e.errorType,
183
+ error_message: e.errorMessage,
184
+ stack_trace: e.stackTrace,
185
+ context: e.context,
186
+ timestamp: Math.floor(new Date(e.timestamp).getTime() / 1e3)
187
+ }));
170
188
  const response = await fetch(`${this.baseUrl}/api/telemetry/errors`, {
171
189
  method: "POST",
172
190
  headers: {
173
191
  "Content-Type": "application/json",
174
192
  Authorization: `Bearer ${this.apiKey}`
175
193
  },
176
- body: JSON.stringify({ events })
194
+ body: JSON.stringify({ errors })
177
195
  });
178
196
  if (!response.ok) {
179
197
  throw new Error(`Failed to send errors: ${response.status}`);
@@ -198,88 +216,144 @@ var TelemetryService = class {
198
216
  };
199
217
 
200
218
  // src/realtime.ts
219
+ var import_fetch_event_source = require("@microsoft/fetch-event-source");
220
+ var FatalError = class extends Error {
221
+ constructor(message) {
222
+ super(message);
223
+ this.name = "FatalError";
224
+ }
225
+ };
226
+ var RetriableError = class extends Error {
227
+ constructor(message) {
228
+ super(message);
229
+ this.name = "RetriableError";
230
+ }
231
+ };
201
232
  var RealtimeService = class {
233
+ // Track auth failures to prevent reconnection attempts
202
234
  constructor(baseUrl, apiKey, onConnectionChange) {
203
- this.eventSource = null;
235
+ this.abortController = null;
204
236
  this.reconnectAttempts = 0;
205
237
  this.maxReconnectAttempts = 10;
206
- // Increased from 5 to 10
207
238
  this.reconnectDelay = 1e3;
208
239
  // Start with 1 second
209
240
  this.maxReconnectDelay = 3e4;
210
241
  // Cap at 30 seconds
211
242
  this.listeners = /* @__PURE__ */ new Map();
243
+ this.connected = false;
244
+ this.authFailed = false;
212
245
  this.baseUrl = baseUrl;
213
246
  this.apiKey = apiKey;
214
247
  this.onConnectionChange = onConnectionChange;
215
248
  }
216
249
  /**
217
- * Connect to SSE stream
250
+ * Connect to SSE stream using header-based authentication
251
+ * Per SDK Developer Guide: "Never pass API keys as query parameters"
218
252
  */
219
253
  connect() {
220
- if (this.eventSource) {
254
+ if (this.abortController) {
221
255
  return;
222
256
  }
223
- const url = `${this.baseUrl}/api/flags/stream?apiKey=${encodeURIComponent(this.apiKey)}`;
224
- try {
225
- this.eventSource = new EventSource(url);
226
- this.eventSource.onopen = () => {
227
- console.log("[Savvagent] Real-time connection established");
228
- this.reconnectAttempts = 0;
229
- this.reconnectDelay = 1e3;
230
- this.onConnectionChange?.call(null, true);
231
- };
232
- this.eventSource.onerror = (error) => {
233
- console.error("[Savvagent] SSE connection error:", error);
234
- this.handleDisconnect();
235
- };
236
- this.eventSource.addEventListener("heartbeat", () => {
237
- });
238
- this.eventSource.addEventListener("flag.updated", (e) => {
239
- this.handleMessage("flag.updated", e);
240
- });
241
- this.eventSource.addEventListener("flag.deleted", (e) => {
242
- this.handleMessage("flag.deleted", e);
243
- });
244
- this.eventSource.addEventListener("flag.created", (e) => {
245
- this.handleMessage("flag.created", e);
246
- });
247
- } catch (error) {
248
- console.error("[Savvagent] Failed to create EventSource:", error);
249
- this.handleDisconnect();
257
+ if (this.authFailed) {
258
+ return;
250
259
  }
260
+ this.abortController = new AbortController();
261
+ const url = `${this.baseUrl}/api/flags/stream`;
262
+ (0, import_fetch_event_source.fetchEventSource)(url, {
263
+ method: "GET",
264
+ headers: {
265
+ "Authorization": `Bearer ${this.apiKey}`
266
+ },
267
+ signal: this.abortController.signal,
268
+ // Disable built-in retry behavior - we handle it ourselves
269
+ openWhenHidden: false,
270
+ onopen: async (response) => {
271
+ if (response.ok) {
272
+ console.log("[Savvagent] Real-time connection established");
273
+ this.reconnectAttempts = 0;
274
+ this.reconnectDelay = 1e3;
275
+ this.connected = true;
276
+ this.onConnectionChange?.(true);
277
+ } else if (response.status === 401 || response.status === 403) {
278
+ this.authFailed = true;
279
+ console.error(`[Savvagent] SSE authentication failed (${response.status}). Check your API key. Reconnection disabled.`);
280
+ throw new FatalError(`SSE authentication failed: ${response.status}`);
281
+ } else {
282
+ console.error(`[Savvagent] SSE connection failed: ${response.status}`);
283
+ throw new RetriableError(`SSE connection failed: ${response.status}`);
284
+ }
285
+ },
286
+ onmessage: (event) => {
287
+ this.handleMessage(event);
288
+ },
289
+ onerror: (err) => {
290
+ if (this.authFailed) {
291
+ throw err;
292
+ }
293
+ console.error("[Savvagent] SSE connection error:", err);
294
+ this.handleDisconnect();
295
+ },
296
+ onclose: () => {
297
+ console.log("[Savvagent] SSE connection closed");
298
+ if (!this.authFailed) {
299
+ this.handleDisconnect();
300
+ }
301
+ }
302
+ }).catch((error) => {
303
+ if (error.name !== "AbortError" && !(error instanceof FatalError)) {
304
+ console.error("[Savvagent] SSE connection error:", error);
305
+ if (!this.authFailed) {
306
+ this.handleDisconnect();
307
+ }
308
+ }
309
+ });
251
310
  }
252
311
  /**
253
312
  * Handle incoming SSE messages
254
313
  */
255
- handleMessage(type, event) {
314
+ handleMessage(event) {
315
+ if (event.event === "heartbeat") {
316
+ return;
317
+ }
318
+ if (event.event === "connected") {
319
+ return;
320
+ }
321
+ const eventType = event.event;
322
+ if (!["flag.updated", "flag.deleted", "flag.created"].includes(eventType)) {
323
+ return;
324
+ }
256
325
  try {
257
326
  const data = JSON.parse(event.data);
258
327
  const updateEvent = {
259
- type,
328
+ type: eventType,
260
329
  flagKey: data.key,
261
330
  data
262
331
  };
263
- const flagListeners = this.listeners.get(updateEvent.flagKey);
264
- if (flagListeners) {
265
- flagListeners.forEach((listener) => listener(updateEvent));
266
- }
267
332
  const wildcardListeners = this.listeners.get("*");
268
333
  if (wildcardListeners) {
269
334
  wildcardListeners.forEach((listener) => listener(updateEvent));
270
335
  }
336
+ const flagListeners = this.listeners.get(updateEvent.flagKey);
337
+ if (flagListeners) {
338
+ flagListeners.forEach((listener) => listener(updateEvent));
339
+ }
271
340
  } catch (error) {
272
341
  console.error("[Savvagent] Failed to parse SSE message:", error);
273
342
  }
274
343
  }
275
344
  /**
276
- * Handle disconnection and attempt reconnect
345
+ * Handle disconnection and attempt reconnect with exponential backoff
277
346
  */
278
347
  handleDisconnect() {
279
- this.onConnectionChange?.call(null, false);
280
- if (this.eventSource) {
281
- this.eventSource.close();
282
- this.eventSource = null;
348
+ this.connected = false;
349
+ this.onConnectionChange?.(false);
350
+ if (this.abortController) {
351
+ this.abortController.abort();
352
+ this.abortController = null;
353
+ }
354
+ if (this.authFailed) {
355
+ console.warn("[Savvagent] Authentication failed. Reconnection disabled.");
356
+ return;
283
357
  }
284
358
  if (this.reconnectAttempts < this.maxReconnectAttempts) {
285
359
  this.reconnectAttempts++;
@@ -317,39 +391,48 @@ var RealtimeService = class {
317
391
  * Disconnect from SSE stream
318
392
  */
319
393
  disconnect() {
320
- if (this.eventSource) {
321
- this.eventSource.close();
322
- this.eventSource = null;
323
- }
324
394
  this.reconnectAttempts = this.maxReconnectAttempts;
325
- this.onConnectionChange?.call(null, false);
395
+ if (this.abortController) {
396
+ this.abortController.abort();
397
+ this.abortController = null;
398
+ }
399
+ this.connected = false;
400
+ this.onConnectionChange?.(false);
326
401
  }
327
402
  /**
328
403
  * Check if connected
329
404
  */
330
405
  isConnected() {
331
- return this.eventSource !== null && this.eventSource.readyState === EventSource.OPEN;
406
+ return this.connected;
332
407
  }
333
408
  };
334
409
 
335
410
  // src/client.ts
336
411
  var FlagClient = class {
412
+ // Track auth failures to prevent request spam
337
413
  constructor(config) {
338
414
  this.realtime = null;
339
415
  this.anonymousId = null;
340
416
  this.userId = null;
341
417
  this.detectedLanguage = null;
418
+ this.overrides = /* @__PURE__ */ new Map();
419
+ this.overrideListeners = /* @__PURE__ */ new Set();
420
+ this.authFailed = false;
342
421
  this.config = {
343
422
  apiKey: config.apiKey,
344
423
  applicationId: config.applicationId || "",
345
424
  baseUrl: config.baseUrl || "http://localhost:8080",
425
+ environment: config.environment || "production",
346
426
  enableRealtime: config.enableRealtime ?? true,
347
427
  cacheTtl: config.cacheTtl || 6e4,
348
428
  enableTelemetry: config.enableTelemetry ?? true,
349
429
  defaults: config.defaults || {},
350
430
  onError: config.onError || ((error) => console.error("[Savvagent]", error)),
351
431
  defaultLanguage: config.defaultLanguage || "",
352
- disableLanguageDetection: config.disableLanguageDetection ?? false
432
+ disableLanguageDetection: config.disableLanguageDetection ?? false,
433
+ retryAttempts: config.retryAttempts ?? 3,
434
+ retryDelay: config.retryDelay ?? 1e3,
435
+ retryBackoff: config.retryBackoff ?? "exponential"
353
436
  };
354
437
  if (!this.config.disableLanguageDetection && typeof navigator !== "undefined") {
355
438
  this.detectedLanguage = this.config.defaultLanguage || navigator.language || navigator.userLanguage || null;
@@ -363,7 +446,7 @@ var FlagClient = class {
363
446
  this.config.apiKey,
364
447
  this.config.enableTelemetry
365
448
  );
366
- if (this.config.enableRealtime && typeof EventSource !== "undefined") {
449
+ if (this.config.enableRealtime && typeof fetch !== "undefined") {
367
450
  this.realtime = new RealtimeService(
368
451
  this.config.baseUrl,
369
452
  this.config.apiKey,
@@ -428,6 +511,21 @@ var FlagClient = class {
428
511
  getUserId() {
429
512
  return this.userId;
430
513
  }
514
+ /**
515
+ * Set the environment for flag evaluation
516
+ * Useful for dynamically switching environments (e.g., dev tools)
517
+ * @param environment - The environment name (e.g., "development", "staging", "production", "beta")
518
+ */
519
+ setEnvironment(environment) {
520
+ this.config.environment = environment;
521
+ this.cache.clear();
522
+ }
523
+ /**
524
+ * Get the current environment
525
+ */
526
+ getEnvironment() {
527
+ return this.config.environment;
528
+ }
431
529
  /**
432
530
  * Get the current anonymous ID
433
531
  */
@@ -442,8 +540,7 @@ var FlagClient = class {
442
540
  const context = {
443
541
  user_id: this.userId || void 0,
444
542
  anonymous_id: this.anonymousId || void 0,
445
- environment: "production",
446
- // TODO: Make configurable
543
+ environment: this.config.environment,
447
544
  ...overrides
448
545
  };
449
546
  if (!context.application_id && this.config.applicationId) {
@@ -454,6 +551,86 @@ var FlagClient = class {
454
551
  }
455
552
  return context;
456
553
  }
554
+ /**
555
+ * Check if an error is retryable (transient failure)
556
+ * @param error - The error to check
557
+ * @param status - HTTP status code (if available)
558
+ */
559
+ isRetryableError(error, status) {
560
+ if (status === 401 || status === 403) {
561
+ return false;
562
+ }
563
+ if (status && status >= 400 && status < 500 && status !== 408 && status !== 429) {
564
+ return false;
565
+ }
566
+ if (status && status >= 500) {
567
+ return true;
568
+ }
569
+ if (error.name === "AbortError" || error.name === "TypeError" || error.message.includes("network")) {
570
+ return true;
571
+ }
572
+ return false;
573
+ }
574
+ /**
575
+ * Calculate delay for retry attempt
576
+ * @param attempt - Current attempt number (1-based)
577
+ */
578
+ getRetryDelay(attempt) {
579
+ const baseDelay = this.config.retryDelay;
580
+ if (this.config.retryBackoff === "linear") {
581
+ return baseDelay * attempt;
582
+ }
583
+ const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
584
+ const jitter = Math.random() * 0.3 * exponentialDelay;
585
+ return exponentialDelay + jitter;
586
+ }
587
+ /**
588
+ * Execute a fetch request with retry logic
589
+ * @param requestFn - Function that returns a fetch promise
590
+ * @param operationName - Name of the operation for logging
591
+ */
592
+ async fetchWithRetry(requestFn, operationName) {
593
+ let lastError = null;
594
+ let lastStatus;
595
+ for (let attempt = 1; attempt <= this.config.retryAttempts; attempt++) {
596
+ try {
597
+ const response = await requestFn();
598
+ if (response.status === 401 || response.status === 403) {
599
+ this.authFailed = true;
600
+ this.realtime?.disconnect();
601
+ console.error(`[Savvagent] Authentication failed (${response.status}). Check your API key. Further requests disabled.`);
602
+ throw new Error(`Authentication failed: ${response.status}`);
603
+ }
604
+ if (response.ok) {
605
+ return response;
606
+ }
607
+ lastStatus = response.status;
608
+ lastError = new Error(`${operationName} failed: ${response.status}`);
609
+ if (!this.isRetryableError(lastError, response.status)) {
610
+ throw lastError;
611
+ }
612
+ if (attempt < this.config.retryAttempts) {
613
+ const delay = this.getRetryDelay(attempt);
614
+ console.warn(`[Savvagent] ${operationName} failed (${response.status}), retrying in ${Math.round(delay)}ms (attempt ${attempt}/${this.config.retryAttempts})`);
615
+ await new Promise((resolve) => setTimeout(resolve, delay));
616
+ }
617
+ } catch (error) {
618
+ lastError = error;
619
+ if (lastError.message.includes("Authentication failed")) {
620
+ throw lastError;
621
+ }
622
+ if (!this.isRetryableError(lastError, lastStatus)) {
623
+ throw lastError;
624
+ }
625
+ if (attempt < this.config.retryAttempts) {
626
+ const delay = this.getRetryDelay(attempt);
627
+ console.warn(`[Savvagent] ${operationName} error: ${lastError.message}, retrying in ${Math.round(delay)}ms (attempt ${attempt}/${this.config.retryAttempts})`);
628
+ await new Promise((resolve) => setTimeout(resolve, delay));
629
+ }
630
+ }
631
+ }
632
+ throw lastError || new Error(`${operationName} failed after ${this.config.retryAttempts} attempts`);
633
+ }
457
634
  /**
458
635
  * Check if a feature flag is enabled
459
636
  * @param flagKey - The flag key to evaluate
@@ -474,6 +651,29 @@ var FlagClient = class {
474
651
  const startTime = Date.now();
475
652
  const traceId = TelemetryService.generateTraceId();
476
653
  try {
654
+ if (this.overrides.has(flagKey)) {
655
+ const overrideValue = this.overrides.get(flagKey);
656
+ return {
657
+ key: flagKey,
658
+ value: overrideValue,
659
+ reason: "default",
660
+ // Using 'default' to indicate override
661
+ metadata: {
662
+ description: "Local override active"
663
+ }
664
+ };
665
+ }
666
+ if (this.authFailed) {
667
+ const defaultValue = this.config.defaults[flagKey] ?? false;
668
+ return {
669
+ key: flagKey,
670
+ value: defaultValue,
671
+ reason: "error",
672
+ metadata: {
673
+ description: "Authentication failed - using default value"
674
+ }
675
+ };
676
+ }
477
677
  const cachedValue = this.cache.get(flagKey);
478
678
  if (cachedValue !== null) {
479
679
  return {
@@ -486,17 +686,26 @@ var FlagClient = class {
486
686
  const requestBody = {
487
687
  context: evaluationContext
488
688
  };
489
- const response = await fetch(`${this.config.baseUrl}/api/flags/${flagKey}/evaluate`, {
490
- method: "POST",
491
- headers: {
492
- "Content-Type": "application/json",
493
- Authorization: `Bearer ${this.config.apiKey}`
689
+ const response = await this.fetchWithRetry(
690
+ () => {
691
+ const controller = new AbortController();
692
+ const timeoutId = setTimeout(() => {
693
+ controller.abort();
694
+ }, 1e4);
695
+ return fetch(`${this.config.baseUrl}/api/flags/${flagKey}/evaluate`, {
696
+ method: "POST",
697
+ headers: {
698
+ "Content-Type": "application/json",
699
+ Authorization: `Bearer ${this.config.apiKey}`
700
+ },
701
+ body: JSON.stringify(requestBody),
702
+ signal: controller.signal
703
+ }).finally(() => {
704
+ clearTimeout(timeoutId);
705
+ });
494
706
  },
495
- body: JSON.stringify(requestBody)
496
- });
497
- if (!response.ok) {
498
- throw new Error(`Flag evaluation failed: ${response.status}`);
499
- }
707
+ `Flag evaluation (${flagKey})`
708
+ );
500
709
  const data = await response.json();
501
710
  const value = data.enabled || false;
502
711
  this.cache.set(flagKey, value, data.key);
@@ -621,6 +830,217 @@ var FlagClient = class {
621
830
  this.realtime?.disconnect();
622
831
  this.cache.clear();
623
832
  }
833
+ // =====================
834
+ // Local Override Methods
835
+ // =====================
836
+ /**
837
+ * Set a local override for a flag.
838
+ * Overrides take precedence over server values and cache.
839
+ *
840
+ * @param flagKey - The flag key to override
841
+ * @param value - The override value (true/false)
842
+ *
843
+ * @example
844
+ * ```typescript
845
+ * // Force a flag to be enabled locally
846
+ * client.setOverride('new-feature', true);
847
+ * ```
848
+ */
849
+ setOverride(flagKey, value) {
850
+ this.overrides.set(flagKey, value);
851
+ this.notifyOverrideListeners();
852
+ }
853
+ /**
854
+ * Clear a local override for a flag.
855
+ * The flag will return to using server/cached values.
856
+ *
857
+ * @param flagKey - The flag key to clear override for
858
+ */
859
+ clearOverride(flagKey) {
860
+ this.overrides.delete(flagKey);
861
+ this.notifyOverrideListeners();
862
+ }
863
+ /**
864
+ * Clear all local overrides.
865
+ */
866
+ clearAllOverrides() {
867
+ this.overrides.clear();
868
+ this.notifyOverrideListeners();
869
+ }
870
+ /**
871
+ * Check if a flag has a local override.
872
+ *
873
+ * @param flagKey - The flag key to check
874
+ * @returns true if the flag has an override
875
+ */
876
+ hasOverride(flagKey) {
877
+ return this.overrides.has(flagKey);
878
+ }
879
+ /**
880
+ * Get the override value for a flag.
881
+ *
882
+ * @param flagKey - The flag key to get override for
883
+ * @returns The override value, or undefined if not set
884
+ */
885
+ getOverride(flagKey) {
886
+ return this.overrides.get(flagKey);
887
+ }
888
+ /**
889
+ * Get all current overrides.
890
+ *
891
+ * @returns Record of flag keys to override values
892
+ */
893
+ getOverrides() {
894
+ const result = {};
895
+ this.overrides.forEach((value, key) => {
896
+ result[key] = value;
897
+ });
898
+ return result;
899
+ }
900
+ /**
901
+ * Set multiple overrides at once.
902
+ *
903
+ * @param overrides - Record of flag keys to override values
904
+ */
905
+ setOverrides(overrides) {
906
+ Object.entries(overrides).forEach(([key, value]) => {
907
+ this.overrides.set(key, value);
908
+ });
909
+ this.notifyOverrideListeners();
910
+ }
911
+ /**
912
+ * Subscribe to override changes.
913
+ * Useful for React components to re-render when overrides change.
914
+ *
915
+ * @param callback - Function to call when overrides change
916
+ * @returns Unsubscribe function
917
+ */
918
+ onOverrideChange(callback) {
919
+ this.overrideListeners.add(callback);
920
+ return () => {
921
+ this.overrideListeners.delete(callback);
922
+ };
923
+ }
924
+ /**
925
+ * Notify all override listeners of a change.
926
+ */
927
+ notifyOverrideListeners() {
928
+ this.overrideListeners.forEach((callback) => {
929
+ try {
930
+ callback();
931
+ } catch (e) {
932
+ console.error("[Savvagent] Override listener error:", e);
933
+ }
934
+ });
935
+ }
936
+ /**
937
+ * Get all flags for the application (and enterprise-scoped flags).
938
+ * Per SDK Developer Guide: GET /api/sdk/flags
939
+ *
940
+ * Use cases:
941
+ * - Local override UI: Display all available flags for developers to toggle
942
+ * - Offline mode: Pre-fetch flags for mobile/desktop apps
943
+ * - SDK initialization: Bootstrap SDK with all flag values on startup
944
+ * - DevTools integration: Show available flags in browser dev panels
945
+ *
946
+ * @param environment - Environment to evaluate enabled state for (default: 'development')
947
+ * @returns Promise<FlagDefinition[]> - List of flag definitions
948
+ *
949
+ * @example
950
+ * ```typescript
951
+ * // Fetch all flags for development
952
+ * const flags = await client.getAllFlags('development');
953
+ *
954
+ * // Bootstrap local cache
955
+ * flags.forEach(flag => {
956
+ * console.log(`${flag.key}: ${flag.enabled}`);
957
+ * });
958
+ * ```
959
+ */
960
+ async getAllFlags(environment = "development") {
961
+ if (this.authFailed) {
962
+ return [];
963
+ }
964
+ try {
965
+ const response = await this.fetchWithRetry(
966
+ () => {
967
+ const controller = new AbortController();
968
+ const timeoutId = setTimeout(() => {
969
+ controller.abort();
970
+ }, 1e4);
971
+ return fetch(
972
+ `${this.config.baseUrl}/api/sdk/flags?environment=${encodeURIComponent(environment)}`,
973
+ {
974
+ method: "GET",
975
+ headers: {
976
+ Authorization: `Bearer ${this.config.apiKey}`
977
+ },
978
+ signal: controller.signal
979
+ }
980
+ ).finally(() => {
981
+ clearTimeout(timeoutId);
982
+ });
983
+ },
984
+ "Get all flags"
985
+ );
986
+ const data = await response.json();
987
+ data.flags.forEach((flag) => {
988
+ this.cache.set(flag.key, flag.enabled, flag.key);
989
+ });
990
+ return data.flags;
991
+ } catch (error) {
992
+ this.config.onError(error);
993
+ return [];
994
+ }
995
+ }
996
+ /**
997
+ * Get only enterprise-scoped flags for the organization.
998
+ * Per SDK Developer Guide: GET /api/sdk/enterprise-flags
999
+ *
1000
+ * Enterprise flags are shared across all applications in the organization.
1001
+ *
1002
+ * @param environment - Environment to evaluate enabled state for (default: 'development')
1003
+ * @returns Promise<FlagDefinition[]> - List of enterprise flag definitions
1004
+ *
1005
+ * @example
1006
+ * ```typescript
1007
+ * // Fetch enterprise-only flags
1008
+ * const enterpriseFlags = await client.getEnterpriseFlags('production');
1009
+ * ```
1010
+ */
1011
+ async getEnterpriseFlags(environment = "development") {
1012
+ if (this.authFailed) {
1013
+ return [];
1014
+ }
1015
+ try {
1016
+ const response = await this.fetchWithRetry(
1017
+ () => {
1018
+ const controller = new AbortController();
1019
+ const timeoutId = setTimeout(() => {
1020
+ controller.abort();
1021
+ }, 1e4);
1022
+ return fetch(
1023
+ `${this.config.baseUrl}/api/sdk/enterprise-flags?environment=${encodeURIComponent(environment)}`,
1024
+ {
1025
+ method: "GET",
1026
+ headers: {
1027
+ Authorization: `Bearer ${this.config.apiKey}`
1028
+ },
1029
+ signal: controller.signal
1030
+ }
1031
+ ).finally(() => {
1032
+ clearTimeout(timeoutId);
1033
+ });
1034
+ },
1035
+ "Get enterprise flags"
1036
+ );
1037
+ const data = await response.json();
1038
+ return data.flags;
1039
+ } catch (error) {
1040
+ this.config.onError(error);
1041
+ return [];
1042
+ }
1043
+ }
624
1044
  };
625
1045
  // Annotate the CommonJS export names for ESM import in node:
626
1046
  0 && (module.exports = {