@rolloutly/core 0.1.1 → 0.1.2

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
@@ -9,6 +9,7 @@ var CACHE_KEY = "rolloutly_flags";
9
9
  var RolloutlyClient = class {
10
10
  constructor(config) {
11
11
  this.flags = {};
12
+ this.flagValuesCache = {};
12
13
  this.status = "initializing";
13
14
  this.error = null;
14
15
  this.listeners = /* @__PURE__ */ new Set();
@@ -56,6 +57,13 @@ var RolloutlyClient = class {
56
57
  getFlags() {
57
58
  return { ...this.flags };
58
59
  }
60
+ /**
61
+ * Get all flag values as a stable object (for React hooks)
62
+ * Returns a cached object that only changes when flags change
63
+ */
64
+ getFlagValues() {
65
+ return this.flagValuesCache;
66
+ }
59
67
  /**
60
68
  * Check if a boolean flag is enabled
61
69
  */
@@ -212,6 +220,7 @@ var RolloutlyClient = class {
212
220
  const cached = localStorage.getItem(CACHE_KEY);
213
221
  if (cached) {
214
222
  this.flags = JSON.parse(cached);
223
+ this.updateFlagValuesCache();
215
224
  this.log("Loaded cached flags");
216
225
  }
217
226
  } catch {
@@ -224,7 +233,14 @@ var RolloutlyClient = class {
224
233
  } catch {
225
234
  }
226
235
  }
236
+ updateFlagValuesCache() {
237
+ this.flagValuesCache = Object.entries(this.flags).reduce((acc, [key, flag]) => {
238
+ acc[key] = flag.value;
239
+ return acc;
240
+ }, {});
241
+ }
227
242
  notifyListeners() {
243
+ this.updateFlagValuesCache();
228
244
  this.listeners.forEach((listener) => listener());
229
245
  }
230
246
  log(...args) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"names":["ref","off","initializeApp","getDatabase","onValue"],"mappings":"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","file":"index.cjs","sourcesContent":["import { initializeApp, type FirebaseApp } from 'firebase/app';\nimport {\n getDatabase,\n ref,\n onValue,\n off,\n type Database,\n type Unsubscribe,\n} from 'firebase/database';\n\nimport type {\n ClientStatus,\n Flag,\n FlagChangeListener,\n FlagMap,\n FlagValue,\n ParsedToken,\n RolloutlyConfig,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://rolloutly.com';\nconst CACHE_KEY = 'rolloutly_flags';\n\nexport class RolloutlyClient {\n private config: Required<\n Omit<RolloutlyConfig, 'defaultFlags'> & {\n defaultFlags: Record<string, FlagValue>;\n }\n >;\n private flags: FlagMap = {};\n private status: ClientStatus = 'initializing';\n private error: Error | null = null;\n private listeners: Set<FlagChangeListener> = new Set();\n private parsedToken: ParsedToken;\n private firebaseApp: FirebaseApp | null = null;\n private database: Database | null = null;\n private realtimeUnsubscribe: Unsubscribe | null = null;\n private initPromise: Promise<void>;\n private initResolve!: () => void;\n private initReject!: (error: Error) => void;\n\n constructor(config: RolloutlyConfig) {\n this.config = {\n token: config.token,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n realtimeEnabled: config.realtimeEnabled ?? true,\n defaultFlags: config.defaultFlags ?? {},\n debug: config.debug ?? false,\n };\n\n // Parse the token\n const parsed = this.parseToken(config.token);\n\n if (!parsed) {\n throw new Error('Invalid SDK token format');\n }\n\n this.parsedToken = parsed;\n\n // Create init promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve;\n this.initReject = reject;\n });\n\n // Load cached flags first\n this.loadCachedFlags();\n\n // Start initialization\n void this.initialize();\n }\n\n /**\n * Wait for the client to be initialized\n */\n async waitForInit(): Promise<void> {\n return this.initPromise;\n }\n\n /**\n * Get a single flag value\n */\n getFlag<T extends FlagValue = FlagValue>(key: string): T | undefined {\n const flag = this.flags[key];\n\n if (flag) {\n return (flag.enabled ? flag.value : this.config.defaultFlags[key]) as T;\n }\n\n return this.config.defaultFlags[key] as T;\n }\n\n /**\n * Get all flags\n */\n getFlags(): FlagMap {\n return { ...this.flags };\n }\n\n /**\n * Check if a boolean flag is enabled\n */\n isEnabled(key: string): boolean {\n const flag = this.flags[key];\n\n if (!flag) {\n return Boolean(this.config.defaultFlags[key]);\n }\n\n if (!flag.enabled) {\n return Boolean(this.config.defaultFlags[key]);\n }\n\n return flag.type === 'boolean' ? Boolean(flag.value) : true;\n }\n\n /**\n * Get current client status\n */\n getStatus(): ClientStatus {\n return this.status;\n }\n\n /**\n * Get the last error if any\n */\n getError(): Error | null {\n return this.error;\n }\n\n /**\n * Subscribe to flag changes (for React useSyncExternalStore)\n */\n subscribe(listener: FlagChangeListener): () => void {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Cleanup and disconnect\n */\n close(): void {\n if (this.realtimeUnsubscribe) {\n this.realtimeUnsubscribe();\n this.realtimeUnsubscribe = null;\n }\n\n if (this.database) {\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n off(flagsRef);\n }\n\n this.listeners.clear();\n this.log('Client closed');\n }\n\n // ==================== Private Methods ====================\n\n private parseToken(token: string): ParsedToken | null {\n const parts = token.split('_');\n\n // Format: rly_{projectId}_{environmentKey}_{randomString}\n if (parts.length < 4 || parts[0] !== 'rly') {\n return null;\n }\n\n return {\n projectId: parts[1],\n environmentKey: parts[2],\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n // Fetch initial flags from API\n await this.fetchFlags();\n\n // Set up real-time updates if enabled\n if (this.config.realtimeEnabled) {\n await this.setupRealtime();\n }\n\n this.status = 'ready';\n this.initResolve();\n this.log('Client initialized');\n } catch (err) {\n this.status = 'error';\n this.error = err instanceof Error ? err : new Error(String(err));\n this.initReject(this.error);\n this.log('Initialization failed:', this.error.message);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n const url = `${this.config.baseUrl}/api/sdk/flags`;\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status}`);\n }\n\n const data = (await response.json()) as { flags: FlagMap };\n this.flags = data.flags;\n this.cacheFlags();\n this.notifyListeners();\n this.log('Flags fetched:', Object.keys(this.flags).length);\n }\n\n private async setupRealtime(): Promise<void> {\n // Initialize Firebase with minimal config for Realtime DB\n // The database URL is derived from the API response or configured\n const databaseURL = await this.getDatabaseUrl();\n\n if (!databaseURL) {\n this.log('Realtime DB URL not available, skipping real-time updates');\n\n return;\n }\n\n this.firebaseApp = initializeApp(\n {\n databaseURL,\n },\n `rolloutly-${Date.now()}`,\n );\n\n this.database = getDatabase(this.firebaseApp);\n\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n\n this.realtimeUnsubscribe = onValue(\n flagsRef,\n (snapshot) => {\n const data = snapshot.val() as Record<string, Flag> | null;\n\n if (data) {\n // Convert realtime format to our flag format\n this.flags = Object.entries(data).reduce<FlagMap>(\n (acc, [key, flag]) => {\n acc[key] = { ...flag, key };\n\n return acc;\n },\n {},\n );\n this.cacheFlags();\n this.notifyListeners();\n this.log('Realtime update received');\n }\n },\n (error) => {\n this.log('Realtime error:', error.message);\n },\n );\n }\n\n private async getDatabaseUrl(): Promise<string | null> {\n // Try to get the database URL from the API\n try {\n const url = `${this.config.baseUrl}/api/sdk/config`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (response.ok) {\n const data = (await response.json()) as { databaseUrl?: string };\n\n return data.databaseUrl ?? null;\n }\n } catch {\n // Ignore - we'll try without realtime\n }\n\n return null;\n }\n\n private loadCachedFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n const cached = localStorage.getItem(CACHE_KEY);\n\n if (cached) {\n this.flags = JSON.parse(cached) as FlagMap;\n this.log('Loaded cached flags');\n }\n } catch {\n // Ignore cache errors\n }\n }\n\n private cacheFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify(this.flags));\n } catch {\n // Ignore cache errors\n }\n }\n\n private notifyListeners(): void {\n this.listeners.forEach((listener) => listener());\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Rolloutly]', ...args);\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/client.ts"],"names":["ref","off","initializeApp","getDatabase","onValue"],"mappings":"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","file":"index.cjs","sourcesContent":["import { initializeApp, type FirebaseApp } from 'firebase/app';\nimport {\n getDatabase,\n off,\n onValue,\n ref,\n type Database,\n type Unsubscribe,\n} from 'firebase/database';\n\nimport type {\n ClientStatus,\n Flag,\n FlagChangeListener,\n FlagMap,\n FlagValue,\n ParsedToken,\n RolloutlyConfig,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://rolloutly.com';\nconst CACHE_KEY = 'rolloutly_flags';\n\nexport class RolloutlyClient {\n private config: Required<\n Omit<RolloutlyConfig, 'defaultFlags'> & {\n defaultFlags: Record<string, FlagValue>;\n }\n >;\n private flags: FlagMap = {};\n private flagValuesCache: Record<string, FlagValue | undefined> = {};\n private status: ClientStatus = 'initializing';\n private error: Error | null = null;\n private listeners: Set<FlagChangeListener> = new Set();\n private parsedToken: ParsedToken;\n private firebaseApp: FirebaseApp | null = null;\n private database: Database | null = null;\n private realtimeUnsubscribe: Unsubscribe | null = null;\n private initPromise: Promise<void>;\n private initResolve!: () => void;\n private initReject!: (error: Error) => void;\n\n constructor(config: RolloutlyConfig) {\n this.config = {\n token: config.token,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n realtimeEnabled: config.realtimeEnabled ?? true,\n defaultFlags: config.defaultFlags ?? {},\n debug: config.debug ?? false,\n };\n\n // Parse the token\n const parsed = this.parseToken(config.token);\n\n if (!parsed) {\n throw new Error('Invalid SDK token format');\n }\n\n this.parsedToken = parsed;\n\n // Create init promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve;\n this.initReject = reject;\n });\n\n // Load cached flags first\n this.loadCachedFlags();\n\n // Start initialization\n void this.initialize();\n }\n\n /**\n * Wait for the client to be initialized\n */\n async waitForInit(): Promise<void> {\n return this.initPromise;\n }\n\n /**\n * Get a single flag value\n */\n getFlag<T extends FlagValue = FlagValue>(key: string): T | undefined {\n const flag = this.flags[key];\n\n if (flag) {\n return (flag.enabled ? flag.value : this.config.defaultFlags[key]) as T;\n }\n\n return this.config.defaultFlags[key] as T;\n }\n\n /**\n * Get all flags\n */\n getFlags(): FlagMap {\n return { ...this.flags };\n }\n\n /**\n * Get all flag values as a stable object (for React hooks)\n * Returns a cached object that only changes when flags change\n */\n getFlagValues(): Record<string, FlagValue | undefined> {\n return this.flagValuesCache;\n }\n\n /**\n * Check if a boolean flag is enabled\n */\n isEnabled(key: string): boolean {\n const flag = this.flags[key];\n\n if (!flag) {\n return Boolean(this.config.defaultFlags[key]);\n }\n\n if (!flag.enabled) {\n return Boolean(this.config.defaultFlags[key]);\n }\n\n return flag.type === 'boolean' ? Boolean(flag.value) : true;\n }\n\n /**\n * Get current client status\n */\n getStatus(): ClientStatus {\n return this.status;\n }\n\n /**\n * Get the last error if any\n */\n getError(): Error | null {\n return this.error;\n }\n\n /**\n * Subscribe to flag changes (for React useSyncExternalStore)\n */\n subscribe(listener: FlagChangeListener): () => void {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Cleanup and disconnect\n */\n close(): void {\n if (this.realtimeUnsubscribe) {\n this.realtimeUnsubscribe();\n this.realtimeUnsubscribe = null;\n }\n\n if (this.database) {\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n off(flagsRef);\n }\n\n this.listeners.clear();\n this.log('Client closed');\n }\n\n // ==================== Private Methods ====================\n\n private parseToken(token: string): ParsedToken | null {\n const parts = token.split('_');\n\n // Format: rly_{projectId}_{environmentKey}_{randomString}\n if (parts.length < 4 || parts[0] !== 'rly') {\n return null;\n }\n\n return {\n projectId: parts[1],\n environmentKey: parts[2],\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n // Fetch initial flags from API\n await this.fetchFlags();\n\n // Set up real-time updates if enabled\n if (this.config.realtimeEnabled) {\n await this.setupRealtime();\n }\n\n this.status = 'ready';\n this.initResolve();\n this.log('Client initialized');\n } catch (err) {\n this.status = 'error';\n this.error = err instanceof Error ? err : new Error(String(err));\n this.initReject(this.error);\n this.log('Initialization failed:', this.error.message);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n const url = `${this.config.baseUrl}/api/sdk/flags`;\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status}`);\n }\n\n const data = (await response.json()) as { flags: FlagMap };\n this.flags = data.flags;\n this.cacheFlags();\n this.notifyListeners();\n this.log('Flags fetched:', Object.keys(this.flags).length);\n }\n\n private async setupRealtime(): Promise<void> {\n // Initialize Firebase with minimal config for Realtime DB\n // The database URL is derived from the API response or configured\n const databaseURL = await this.getDatabaseUrl();\n\n if (!databaseURL) {\n this.log('Realtime DB URL not available, skipping real-time updates');\n\n return;\n }\n\n this.firebaseApp = initializeApp(\n {\n databaseURL,\n },\n `rolloutly-${Date.now()}`,\n );\n\n this.database = getDatabase(this.firebaseApp);\n\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n\n this.realtimeUnsubscribe = onValue(\n flagsRef,\n (snapshot) => {\n const data = snapshot.val() as Record<string, Flag> | null;\n\n if (data) {\n // Convert realtime format to our flag format\n this.flags = Object.entries(data).reduce<FlagMap>(\n (acc, [key, flag]) => {\n acc[key] = { ...flag, key };\n\n return acc;\n },\n {},\n );\n this.cacheFlags();\n this.notifyListeners();\n this.log('Realtime update received');\n }\n },\n (error) => {\n this.log('Realtime error:', error.message);\n },\n );\n }\n\n private async getDatabaseUrl(): Promise<string | null> {\n // Try to get the database URL from the API\n try {\n const url = `${this.config.baseUrl}/api/sdk/config`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (response.ok) {\n const data = (await response.json()) as { databaseUrl?: string };\n\n return data.databaseUrl ?? null;\n }\n } catch {\n // Ignore - we'll try without realtime\n }\n\n return null;\n }\n\n private loadCachedFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n const cached = localStorage.getItem(CACHE_KEY);\n\n if (cached) {\n this.flags = JSON.parse(cached) as FlagMap;\n this.updateFlagValuesCache();\n this.log('Loaded cached flags');\n }\n } catch {\n // Ignore cache errors\n }\n }\n\n private cacheFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify(this.flags));\n } catch {\n // Ignore cache errors\n }\n }\n\n private updateFlagValuesCache(): void {\n // Create a new cache object only when flags change\n this.flagValuesCache = Object.entries(this.flags).reduce<\n Record<string, FlagValue | undefined>\n >((acc, [key, flag]) => {\n acc[key] = flag.value;\n\n return acc;\n }, {});\n }\n\n private notifyListeners(): void {\n this.updateFlagValuesCache();\n this.listeners.forEach((listener) => listener());\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Rolloutly]', ...args);\n }\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -42,6 +42,7 @@ type FlagChangeListener = () => void;
42
42
  declare class RolloutlyClient {
43
43
  private config;
44
44
  private flags;
45
+ private flagValuesCache;
45
46
  private status;
46
47
  private error;
47
48
  private listeners;
@@ -65,6 +66,11 @@ declare class RolloutlyClient {
65
66
  * Get all flags
66
67
  */
67
68
  getFlags(): FlagMap;
69
+ /**
70
+ * Get all flag values as a stable object (for React hooks)
71
+ * Returns a cached object that only changes when flags change
72
+ */
73
+ getFlagValues(): Record<string, FlagValue | undefined>;
68
74
  /**
69
75
  * Check if a boolean flag is enabled
70
76
  */
@@ -92,6 +98,7 @@ declare class RolloutlyClient {
92
98
  private getDatabaseUrl;
93
99
  private loadCachedFlags;
94
100
  private cacheFlags;
101
+ private updateFlagValuesCache;
95
102
  private notifyListeners;
96
103
  private log;
97
104
  }
package/dist/index.d.ts CHANGED
@@ -42,6 +42,7 @@ type FlagChangeListener = () => void;
42
42
  declare class RolloutlyClient {
43
43
  private config;
44
44
  private flags;
45
+ private flagValuesCache;
45
46
  private status;
46
47
  private error;
47
48
  private listeners;
@@ -65,6 +66,11 @@ declare class RolloutlyClient {
65
66
  * Get all flags
66
67
  */
67
68
  getFlags(): FlagMap;
69
+ /**
70
+ * Get all flag values as a stable object (for React hooks)
71
+ * Returns a cached object that only changes when flags change
72
+ */
73
+ getFlagValues(): Record<string, FlagValue | undefined>;
68
74
  /**
69
75
  * Check if a boolean flag is enabled
70
76
  */
@@ -92,6 +98,7 @@ declare class RolloutlyClient {
92
98
  private getDatabaseUrl;
93
99
  private loadCachedFlags;
94
100
  private cacheFlags;
101
+ private updateFlagValuesCache;
95
102
  private notifyListeners;
96
103
  private log;
97
104
  }
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ var CACHE_KEY = "rolloutly_flags";
7
7
  var RolloutlyClient = class {
8
8
  constructor(config) {
9
9
  this.flags = {};
10
+ this.flagValuesCache = {};
10
11
  this.status = "initializing";
11
12
  this.error = null;
12
13
  this.listeners = /* @__PURE__ */ new Set();
@@ -54,6 +55,13 @@ var RolloutlyClient = class {
54
55
  getFlags() {
55
56
  return { ...this.flags };
56
57
  }
58
+ /**
59
+ * Get all flag values as a stable object (for React hooks)
60
+ * Returns a cached object that only changes when flags change
61
+ */
62
+ getFlagValues() {
63
+ return this.flagValuesCache;
64
+ }
57
65
  /**
58
66
  * Check if a boolean flag is enabled
59
67
  */
@@ -210,6 +218,7 @@ var RolloutlyClient = class {
210
218
  const cached = localStorage.getItem(CACHE_KEY);
211
219
  if (cached) {
212
220
  this.flags = JSON.parse(cached);
221
+ this.updateFlagValuesCache();
213
222
  this.log("Loaded cached flags");
214
223
  }
215
224
  } catch {
@@ -222,7 +231,14 @@ var RolloutlyClient = class {
222
231
  } catch {
223
232
  }
224
233
  }
234
+ updateFlagValuesCache() {
235
+ this.flagValuesCache = Object.entries(this.flags).reduce((acc, [key, flag]) => {
236
+ acc[key] = flag.value;
237
+ return acc;
238
+ }, {});
239
+ }
225
240
  notifyListeners() {
241
+ this.updateFlagValuesCache();
226
242
  this.listeners.forEach((listener) => listener());
227
243
  }
228
244
  log(...args) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { initializeApp, type FirebaseApp } from 'firebase/app';\nimport {\n getDatabase,\n ref,\n onValue,\n off,\n type Database,\n type Unsubscribe,\n} from 'firebase/database';\n\nimport type {\n ClientStatus,\n Flag,\n FlagChangeListener,\n FlagMap,\n FlagValue,\n ParsedToken,\n RolloutlyConfig,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://rolloutly.com';\nconst CACHE_KEY = 'rolloutly_flags';\n\nexport class RolloutlyClient {\n private config: Required<\n Omit<RolloutlyConfig, 'defaultFlags'> & {\n defaultFlags: Record<string, FlagValue>;\n }\n >;\n private flags: FlagMap = {};\n private status: ClientStatus = 'initializing';\n private error: Error | null = null;\n private listeners: Set<FlagChangeListener> = new Set();\n private parsedToken: ParsedToken;\n private firebaseApp: FirebaseApp | null = null;\n private database: Database | null = null;\n private realtimeUnsubscribe: Unsubscribe | null = null;\n private initPromise: Promise<void>;\n private initResolve!: () => void;\n private initReject!: (error: Error) => void;\n\n constructor(config: RolloutlyConfig) {\n this.config = {\n token: config.token,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n realtimeEnabled: config.realtimeEnabled ?? true,\n defaultFlags: config.defaultFlags ?? {},\n debug: config.debug ?? false,\n };\n\n // Parse the token\n const parsed = this.parseToken(config.token);\n\n if (!parsed) {\n throw new Error('Invalid SDK token format');\n }\n\n this.parsedToken = parsed;\n\n // Create init promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve;\n this.initReject = reject;\n });\n\n // Load cached flags first\n this.loadCachedFlags();\n\n // Start initialization\n void this.initialize();\n }\n\n /**\n * Wait for the client to be initialized\n */\n async waitForInit(): Promise<void> {\n return this.initPromise;\n }\n\n /**\n * Get a single flag value\n */\n getFlag<T extends FlagValue = FlagValue>(key: string): T | undefined {\n const flag = this.flags[key];\n\n if (flag) {\n return (flag.enabled ? flag.value : this.config.defaultFlags[key]) as T;\n }\n\n return this.config.defaultFlags[key] as T;\n }\n\n /**\n * Get all flags\n */\n getFlags(): FlagMap {\n return { ...this.flags };\n }\n\n /**\n * Check if a boolean flag is enabled\n */\n isEnabled(key: string): boolean {\n const flag = this.flags[key];\n\n if (!flag) {\n return Boolean(this.config.defaultFlags[key]);\n }\n\n if (!flag.enabled) {\n return Boolean(this.config.defaultFlags[key]);\n }\n\n return flag.type === 'boolean' ? Boolean(flag.value) : true;\n }\n\n /**\n * Get current client status\n */\n getStatus(): ClientStatus {\n return this.status;\n }\n\n /**\n * Get the last error if any\n */\n getError(): Error | null {\n return this.error;\n }\n\n /**\n * Subscribe to flag changes (for React useSyncExternalStore)\n */\n subscribe(listener: FlagChangeListener): () => void {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Cleanup and disconnect\n */\n close(): void {\n if (this.realtimeUnsubscribe) {\n this.realtimeUnsubscribe();\n this.realtimeUnsubscribe = null;\n }\n\n if (this.database) {\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n off(flagsRef);\n }\n\n this.listeners.clear();\n this.log('Client closed');\n }\n\n // ==================== Private Methods ====================\n\n private parseToken(token: string): ParsedToken | null {\n const parts = token.split('_');\n\n // Format: rly_{projectId}_{environmentKey}_{randomString}\n if (parts.length < 4 || parts[0] !== 'rly') {\n return null;\n }\n\n return {\n projectId: parts[1],\n environmentKey: parts[2],\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n // Fetch initial flags from API\n await this.fetchFlags();\n\n // Set up real-time updates if enabled\n if (this.config.realtimeEnabled) {\n await this.setupRealtime();\n }\n\n this.status = 'ready';\n this.initResolve();\n this.log('Client initialized');\n } catch (err) {\n this.status = 'error';\n this.error = err instanceof Error ? err : new Error(String(err));\n this.initReject(this.error);\n this.log('Initialization failed:', this.error.message);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n const url = `${this.config.baseUrl}/api/sdk/flags`;\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status}`);\n }\n\n const data = (await response.json()) as { flags: FlagMap };\n this.flags = data.flags;\n this.cacheFlags();\n this.notifyListeners();\n this.log('Flags fetched:', Object.keys(this.flags).length);\n }\n\n private async setupRealtime(): Promise<void> {\n // Initialize Firebase with minimal config for Realtime DB\n // The database URL is derived from the API response or configured\n const databaseURL = await this.getDatabaseUrl();\n\n if (!databaseURL) {\n this.log('Realtime DB URL not available, skipping real-time updates');\n\n return;\n }\n\n this.firebaseApp = initializeApp(\n {\n databaseURL,\n },\n `rolloutly-${Date.now()}`,\n );\n\n this.database = getDatabase(this.firebaseApp);\n\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n\n this.realtimeUnsubscribe = onValue(\n flagsRef,\n (snapshot) => {\n const data = snapshot.val() as Record<string, Flag> | null;\n\n if (data) {\n // Convert realtime format to our flag format\n this.flags = Object.entries(data).reduce<FlagMap>(\n (acc, [key, flag]) => {\n acc[key] = { ...flag, key };\n\n return acc;\n },\n {},\n );\n this.cacheFlags();\n this.notifyListeners();\n this.log('Realtime update received');\n }\n },\n (error) => {\n this.log('Realtime error:', error.message);\n },\n );\n }\n\n private async getDatabaseUrl(): Promise<string | null> {\n // Try to get the database URL from the API\n try {\n const url = `${this.config.baseUrl}/api/sdk/config`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (response.ok) {\n const data = (await response.json()) as { databaseUrl?: string };\n\n return data.databaseUrl ?? null;\n }\n } catch {\n // Ignore - we'll try without realtime\n }\n\n return null;\n }\n\n private loadCachedFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n const cached = localStorage.getItem(CACHE_KEY);\n\n if (cached) {\n this.flags = JSON.parse(cached) as FlagMap;\n this.log('Loaded cached flags');\n }\n } catch {\n // Ignore cache errors\n }\n }\n\n private cacheFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify(this.flags));\n } catch {\n // Ignore cache errors\n }\n }\n\n private notifyListeners(): void {\n this.listeners.forEach((listener) => listener());\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Rolloutly]', ...args);\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { initializeApp, type FirebaseApp } from 'firebase/app';\nimport {\n getDatabase,\n off,\n onValue,\n ref,\n type Database,\n type Unsubscribe,\n} from 'firebase/database';\n\nimport type {\n ClientStatus,\n Flag,\n FlagChangeListener,\n FlagMap,\n FlagValue,\n ParsedToken,\n RolloutlyConfig,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://rolloutly.com';\nconst CACHE_KEY = 'rolloutly_flags';\n\nexport class RolloutlyClient {\n private config: Required<\n Omit<RolloutlyConfig, 'defaultFlags'> & {\n defaultFlags: Record<string, FlagValue>;\n }\n >;\n private flags: FlagMap = {};\n private flagValuesCache: Record<string, FlagValue | undefined> = {};\n private status: ClientStatus = 'initializing';\n private error: Error | null = null;\n private listeners: Set<FlagChangeListener> = new Set();\n private parsedToken: ParsedToken;\n private firebaseApp: FirebaseApp | null = null;\n private database: Database | null = null;\n private realtimeUnsubscribe: Unsubscribe | null = null;\n private initPromise: Promise<void>;\n private initResolve!: () => void;\n private initReject!: (error: Error) => void;\n\n constructor(config: RolloutlyConfig) {\n this.config = {\n token: config.token,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n realtimeEnabled: config.realtimeEnabled ?? true,\n defaultFlags: config.defaultFlags ?? {},\n debug: config.debug ?? false,\n };\n\n // Parse the token\n const parsed = this.parseToken(config.token);\n\n if (!parsed) {\n throw new Error('Invalid SDK token format');\n }\n\n this.parsedToken = parsed;\n\n // Create init promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve;\n this.initReject = reject;\n });\n\n // Load cached flags first\n this.loadCachedFlags();\n\n // Start initialization\n void this.initialize();\n }\n\n /**\n * Wait for the client to be initialized\n */\n async waitForInit(): Promise<void> {\n return this.initPromise;\n }\n\n /**\n * Get a single flag value\n */\n getFlag<T extends FlagValue = FlagValue>(key: string): T | undefined {\n const flag = this.flags[key];\n\n if (flag) {\n return (flag.enabled ? flag.value : this.config.defaultFlags[key]) as T;\n }\n\n return this.config.defaultFlags[key] as T;\n }\n\n /**\n * Get all flags\n */\n getFlags(): FlagMap {\n return { ...this.flags };\n }\n\n /**\n * Get all flag values as a stable object (for React hooks)\n * Returns a cached object that only changes when flags change\n */\n getFlagValues(): Record<string, FlagValue | undefined> {\n return this.flagValuesCache;\n }\n\n /**\n * Check if a boolean flag is enabled\n */\n isEnabled(key: string): boolean {\n const flag = this.flags[key];\n\n if (!flag) {\n return Boolean(this.config.defaultFlags[key]);\n }\n\n if (!flag.enabled) {\n return Boolean(this.config.defaultFlags[key]);\n }\n\n return flag.type === 'boolean' ? Boolean(flag.value) : true;\n }\n\n /**\n * Get current client status\n */\n getStatus(): ClientStatus {\n return this.status;\n }\n\n /**\n * Get the last error if any\n */\n getError(): Error | null {\n return this.error;\n }\n\n /**\n * Subscribe to flag changes (for React useSyncExternalStore)\n */\n subscribe(listener: FlagChangeListener): () => void {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Cleanup and disconnect\n */\n close(): void {\n if (this.realtimeUnsubscribe) {\n this.realtimeUnsubscribe();\n this.realtimeUnsubscribe = null;\n }\n\n if (this.database) {\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n off(flagsRef);\n }\n\n this.listeners.clear();\n this.log('Client closed');\n }\n\n // ==================== Private Methods ====================\n\n private parseToken(token: string): ParsedToken | null {\n const parts = token.split('_');\n\n // Format: rly_{projectId}_{environmentKey}_{randomString}\n if (parts.length < 4 || parts[0] !== 'rly') {\n return null;\n }\n\n return {\n projectId: parts[1],\n environmentKey: parts[2],\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n // Fetch initial flags from API\n await this.fetchFlags();\n\n // Set up real-time updates if enabled\n if (this.config.realtimeEnabled) {\n await this.setupRealtime();\n }\n\n this.status = 'ready';\n this.initResolve();\n this.log('Client initialized');\n } catch (err) {\n this.status = 'error';\n this.error = err instanceof Error ? err : new Error(String(err));\n this.initReject(this.error);\n this.log('Initialization failed:', this.error.message);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n const url = `${this.config.baseUrl}/api/sdk/flags`;\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status}`);\n }\n\n const data = (await response.json()) as { flags: FlagMap };\n this.flags = data.flags;\n this.cacheFlags();\n this.notifyListeners();\n this.log('Flags fetched:', Object.keys(this.flags).length);\n }\n\n private async setupRealtime(): Promise<void> {\n // Initialize Firebase with minimal config for Realtime DB\n // The database URL is derived from the API response or configured\n const databaseURL = await this.getDatabaseUrl();\n\n if (!databaseURL) {\n this.log('Realtime DB URL not available, skipping real-time updates');\n\n return;\n }\n\n this.firebaseApp = initializeApp(\n {\n databaseURL,\n },\n `rolloutly-${Date.now()}`,\n );\n\n this.database = getDatabase(this.firebaseApp);\n\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n\n this.realtimeUnsubscribe = onValue(\n flagsRef,\n (snapshot) => {\n const data = snapshot.val() as Record<string, Flag> | null;\n\n if (data) {\n // Convert realtime format to our flag format\n this.flags = Object.entries(data).reduce<FlagMap>(\n (acc, [key, flag]) => {\n acc[key] = { ...flag, key };\n\n return acc;\n },\n {},\n );\n this.cacheFlags();\n this.notifyListeners();\n this.log('Realtime update received');\n }\n },\n (error) => {\n this.log('Realtime error:', error.message);\n },\n );\n }\n\n private async getDatabaseUrl(): Promise<string | null> {\n // Try to get the database URL from the API\n try {\n const url = `${this.config.baseUrl}/api/sdk/config`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (response.ok) {\n const data = (await response.json()) as { databaseUrl?: string };\n\n return data.databaseUrl ?? null;\n }\n } catch {\n // Ignore - we'll try without realtime\n }\n\n return null;\n }\n\n private loadCachedFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n const cached = localStorage.getItem(CACHE_KEY);\n\n if (cached) {\n this.flags = JSON.parse(cached) as FlagMap;\n this.updateFlagValuesCache();\n this.log('Loaded cached flags');\n }\n } catch {\n // Ignore cache errors\n }\n }\n\n private cacheFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify(this.flags));\n } catch {\n // Ignore cache errors\n }\n }\n\n private updateFlagValuesCache(): void {\n // Create a new cache object only when flags change\n this.flagValuesCache = Object.entries(this.flags).reduce<\n Record<string, FlagValue | undefined>\n >((acc, [key, flag]) => {\n acc[key] = flag.value;\n\n return acc;\n }, {});\n }\n\n private notifyListeners(): void {\n this.updateFlagValuesCache();\n this.listeners.forEach((listener) => listener());\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Rolloutly]', ...args);\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rolloutly/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Rolloutly feature flags SDK - Core JavaScript client",
5
5
  "author": "Kevin Beltrão",
6
6
  "license": "MIT",