@ops-ai/astro-feature-flags-toggly 1.3.0 → 1.4.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/{chunk-JAYPBSS4.js → chunk-A6UWWUNJ.js} +10 -4
- package/dist/chunk-A6UWWUNJ.js.map +1 -0
- package/dist/{chunk-ILEHRLEW.js → chunk-E2VPLWUD.js} +28 -16
- package/dist/chunk-E2VPLWUD.js.map +1 -0
- package/dist/{chunk-32IEMDUZ.js → chunk-OIZJ54T2.js} +3 -3
- package/dist/chunk-OIZJ54T2.js.map +1 -0
- package/dist/{chunk-GW5NHZA2.js → chunk-PZXGYCWT.js} +2 -2
- package/dist/client/setup.d.ts +1 -1
- package/dist/client/setup.js +1 -1
- package/dist/client/store.d.ts +7 -2
- package/dist/client/store.js +3 -1
- package/dist/frameworks/react/Feature.js +2 -2
- package/dist/frameworks/react/index.js +2 -2
- package/dist/frameworks/svelte/stores.d.ts +1 -1
- package/dist/frameworks/svelte/stores.js +1 -1
- package/dist/frameworks/vue/composables.js +1 -1
- package/dist/{index-Cjy13d0b.d.ts → index-CThzG-jN.d.ts} +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/integration/index.d.ts +1 -1
- package/dist/integration/index.js +2 -2
- package/dist/server/toggly-server.d.ts +1 -1
- package/dist/server/toggly-server.js +1 -1
- package/dist/server/utils.d.ts +1 -1
- package/package.json +7 -5
- package/dist/chunk-32IEMDUZ.js.map +0 -1
- package/dist/chunk-ILEHRLEW.js.map +0 -1
- package/dist/chunk-JAYPBSS4.js.map +0 -1
- /package/dist/{chunk-GW5NHZA2.js.map → chunk-PZXGYCWT.js.map} +0 -0
|
@@ -6,7 +6,7 @@ var TogglyServer = class {
|
|
|
6
6
|
isBuildTime = false;
|
|
7
7
|
constructor(config, isBuildTime = false) {
|
|
8
8
|
this.config = {
|
|
9
|
-
baseURI: "https://
|
|
9
|
+
baseURI: "https://definitions.toggly.io",
|
|
10
10
|
environment: "Production",
|
|
11
11
|
flagDefaults: {},
|
|
12
12
|
featureFlagsRefreshInterval: 3 * 60 * 1e3,
|
|
@@ -28,7 +28,7 @@ var TogglyServer = class {
|
|
|
28
28
|
return "";
|
|
29
29
|
}
|
|
30
30
|
const baseUrl = baseURI.replace(/\/$/, "");
|
|
31
|
-
let url = `${baseUrl}/${appKey}
|
|
31
|
+
let url = `${baseUrl}/evaluated-signed/${appKey}/${environment}`;
|
|
32
32
|
if (identity) {
|
|
33
33
|
url += `?u=${encodeURIComponent(identity)}`;
|
|
34
34
|
}
|
|
@@ -70,7 +70,13 @@ var TogglyServer = class {
|
|
|
70
70
|
`Failed to fetch flags from Toggly API: ${response.status} ${response.statusText}`
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
const payload = await response.json();
|
|
74
|
+
let flags;
|
|
75
|
+
if (typeof payload === "object" && payload !== null && "defs" in payload && typeof payload.defs === "object") {
|
|
76
|
+
flags = payload.defs;
|
|
77
|
+
} else {
|
|
78
|
+
flags = payload;
|
|
79
|
+
}
|
|
74
80
|
if (this.config.allFeaturesEnabledDuringBuild && this.isBuildTime) {
|
|
75
81
|
if (this.config.isDebug) {
|
|
76
82
|
console.log("[Toggly Server] Build mode: Enabling all features");
|
|
@@ -171,4 +177,4 @@ export {
|
|
|
171
177
|
TogglyServer,
|
|
172
178
|
createTogglyServerClient
|
|
173
179
|
};
|
|
174
|
-
//# sourceMappingURL=chunk-
|
|
180
|
+
//# sourceMappingURL=chunk-A6UWWUNJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/toggly-server.ts"],"sourcesContent":["/**\n * Toggly Server-Side Client for Astro SSR/SSG\n * \n * This module provides server-side feature flag evaluation for Astro applications.\n * It caches flags for SSG builds and fetches fresh flags for SSR requests.\n * This is a complete, embedded Toggly client implementation.\n */\n\nimport type { TogglyConfig, Flags, TogglyClient } from '../types/index.js';\n\ninterface CachedFlags {\n flags: Flags;\n timestamp: number;\n}\n\n/**\n * Server-side Toggly client implementation\n */\nexport class TogglyServer implements TogglyClient {\n private config: TogglyConfig;\n private cache: CachedFlags | null = null;\n private fetchPromise: Promise<Flags> | null = null;\n private isBuildTime: boolean = false;\n\n constructor(config: TogglyConfig, isBuildTime: boolean = false) {\n this.config = {\n baseURI: 'https://definitions.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000, // 3 minutes\n isDebug: false,\n connectTimeout: 5 * 1000, // 5 seconds\n allFeaturesEnabledDuringBuild: false,\n ...config,\n };\n this.isBuildTime = isBuildTime;\n }\n\n /**\n * Get API URL for fetching flags\n */\n private getApiUrl(): string {\n const { baseURI, appKey, environment, identity } = this.config;\n\n if (!appKey) {\n return '';\n }\n\n const baseUrl = baseURI!.replace(/\\/$/, '');\n let url = `${baseUrl}/evaluated-signed/${appKey}/${environment}`;\n\n if (identity) {\n url += `?u=${encodeURIComponent(identity)}`;\n }\n\n return url;\n }\n\n /**\n * Check if cache is valid\n */\n private isCacheValid(): boolean {\n if (!this.cache) return false;\n const age = Date.now() - this.cache.timestamp;\n return age < this.config.featureFlagsRefreshInterval!;\n }\n\n /**\n * Fetch flags from Toggly API\n */\n private async fetchFlags(): Promise<Flags> {\n const url = this.getApiUrl();\n\n // If no appKey, return flagDefaults\n if (!url || !this.config.appKey) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using flag defaults (no appKey):', this.config.flagDefaults);\n }\n return { ...this.config.flagDefaults! };\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n cache: 'no-store',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch flags from Toggly API: ${response.status} ${response.statusText}`\n );\n }\n\n const payload = await response.json();\n let flags: Flags;\n if (typeof payload === 'object' && payload !== null && 'defs' in payload && typeof payload.defs === 'object') {\n flags = payload.defs as Flags;\n } else {\n flags = payload as Flags;\n }\n\n // If allFeaturesEnabledDuringBuild is true and we're in build time,\n // override all flags to true\n if (this.config.allFeaturesEnabledDuringBuild && this.isBuildTime) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Build mode: Enabling all features');\n }\n flags = Object.keys(flags).reduce((acc, key) => {\n acc[key] = true;\n return acc;\n }, {} as Flags);\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Server] Fetched flags:', flags);\n }\n\n return flags;\n } catch (error) {\n if (this.config.isDebug) {\n console.error('[Toggly Server] Error fetching flags:', error);\n }\n\n // On error, try to use cached flags, otherwise use flagDefaults\n if (this.cache) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using cached flags:', this.cache.flags);\n }\n return { ...this.cache.flags };\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using flag defaults:', this.config.flagDefaults);\n }\n\n return { ...this.config.flagDefaults! };\n }\n }\n\n /**\n * Refresh flags cache\n */\n async refreshFlags(): Promise<void> {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Refreshing flags...');\n }\n\n // Prevent multiple concurrent fetches\n if (this.fetchPromise) {\n await this.fetchPromise;\n return;\n }\n\n this.fetchPromise = this.fetchFlags();\n\n try {\n const flags = await this.fetchPromise;\n this.cache = {\n flags,\n timestamp: Date.now(),\n };\n } finally {\n this.fetchPromise = null;\n }\n }\n\n /**\n * Get all feature flags\n */\n async getFlags(): Promise<Flags> {\n // If no appKey, return flagDefaults immediately\n if (!this.config.appKey) {\n return { ...this.config.flagDefaults! };\n }\n\n // If cache is valid, return it\n if (this.isCacheValid() && this.cache) {\n return { ...this.cache.flags };\n }\n\n // Otherwise, refresh and return\n await this.refreshFlags();\n return this.cache ? { ...this.cache.flags } : { ...this.config.flagDefaults! };\n }\n\n /**\n * Get a single feature flag value\n */\n async getFlag(key: string, defaultValue: boolean = false): Promise<boolean> {\n const flags = await this.getFlags();\n const value = flags[key];\n\n if (value !== undefined) {\n return value;\n }\n\n // Check flagDefaults first, then use provided defaultValue\n return this.config.flagDefaults?.[key] ?? defaultValue;\n }\n\n /**\n * Evaluate a feature gate with multiple flags\n */\n async evaluateGate(\n keys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n ): Promise<boolean> {\n if (keys.length === 0) {\n return !negate;\n }\n\n const flags = await this.getFlags();\n\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n // At least one flag must be true\n isEnabled = keys.some((key) => flags[key] === true);\n } else {\n // All flags must be true\n isEnabled = keys.every((key) => flags[key] === true);\n }\n\n return negate ? !isEnabled : isEnabled;\n }\n}\n\n/**\n * Create a new Toggly server-side client instance\n */\nexport function createTogglyServerClient(config: TogglyConfig, isBuildTime: boolean = false): TogglyServer {\n return new TogglyServer(config, isBuildTime);\n}\n\n\n"],"mappings":"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","names":[]}
|
|
@@ -32,12 +32,12 @@ var HookExecutor = class {
|
|
|
32
32
|
* Execute beforeEvaluation hooks in registration order (FIFO)
|
|
33
33
|
* Collects data from each hook to pass to afterEvaluation
|
|
34
34
|
*/
|
|
35
|
-
executeBeforeEvaluation(flagKey, defaultValue) {
|
|
35
|
+
async executeBeforeEvaluation(flagKey, defaultValue) {
|
|
36
36
|
const dataMap = /* @__PURE__ */ new Map();
|
|
37
37
|
for (const hook of this.hooks) {
|
|
38
38
|
if (hook.beforeEvaluation) {
|
|
39
39
|
try {
|
|
40
|
-
const data = hook.beforeEvaluation(flagKey, defaultValue);
|
|
40
|
+
const data = await hook.beforeEvaluation(flagKey, defaultValue);
|
|
41
41
|
dataMap.set(hook.getMetadata().name, data);
|
|
42
42
|
} catch (error) {
|
|
43
43
|
console.error(
|
|
@@ -53,13 +53,13 @@ var HookExecutor = class {
|
|
|
53
53
|
* Execute afterEvaluation hooks in reverse order (LIFO)
|
|
54
54
|
* Passes data from corresponding beforeEvaluation
|
|
55
55
|
*/
|
|
56
|
-
executeAfterEvaluation(flagKey, dataMap, result) {
|
|
56
|
+
async executeAfterEvaluation(flagKey, dataMap, result) {
|
|
57
57
|
for (let i = this.hooks.length - 1; i >= 0; i--) {
|
|
58
58
|
const hook = this.hooks[i];
|
|
59
59
|
if (hook.afterEvaluation) {
|
|
60
60
|
try {
|
|
61
61
|
const data = dataMap.get(hook.getMetadata().name);
|
|
62
|
-
hook.afterEvaluation(flagKey, data, result);
|
|
62
|
+
await hook.afterEvaluation(flagKey, data, result);
|
|
63
63
|
} catch (error) {
|
|
64
64
|
console.error(
|
|
65
65
|
`[Toggly] Error in hook "${hook.getMetadata().name}.afterEvaluation":`,
|
|
@@ -72,12 +72,12 @@ var HookExecutor = class {
|
|
|
72
72
|
/**
|
|
73
73
|
* Execute beforeIdentify hooks in registration order (FIFO)
|
|
74
74
|
*/
|
|
75
|
-
executeBeforeIdentify(identity) {
|
|
75
|
+
async executeBeforeIdentify(identity) {
|
|
76
76
|
const dataMap = /* @__PURE__ */ new Map();
|
|
77
77
|
for (const hook of this.hooks) {
|
|
78
78
|
if (hook.beforeIdentify) {
|
|
79
79
|
try {
|
|
80
|
-
const data = hook.beforeIdentify(identity);
|
|
80
|
+
const data = await hook.beforeIdentify(identity);
|
|
81
81
|
dataMap.set(hook.getMetadata().name, data);
|
|
82
82
|
} catch (error) {
|
|
83
83
|
console.error(
|
|
@@ -92,13 +92,13 @@ var HookExecutor = class {
|
|
|
92
92
|
/**
|
|
93
93
|
* Execute afterIdentify hooks in reverse order (LIFO)
|
|
94
94
|
*/
|
|
95
|
-
executeAfterIdentify(identity, dataMap) {
|
|
95
|
+
async executeAfterIdentify(identity, dataMap) {
|
|
96
96
|
for (let i = this.hooks.length - 1; i >= 0; i--) {
|
|
97
97
|
const hook = this.hooks[i];
|
|
98
98
|
if (hook.afterIdentify) {
|
|
99
99
|
try {
|
|
100
100
|
const data = dataMap.get(hook.getMetadata().name);
|
|
101
|
-
hook.afterIdentify(identity, data);
|
|
101
|
+
await hook.afterIdentify(identity, data);
|
|
102
102
|
} catch (error) {
|
|
103
103
|
console.error(
|
|
104
104
|
`[Toggly] Error in hook "${hook.getMetadata().name}.afterIdentify":`,
|
|
@@ -111,11 +111,11 @@ var HookExecutor = class {
|
|
|
111
111
|
/**
|
|
112
112
|
* Execute afterRefresh hooks in registration order (FIFO)
|
|
113
113
|
*/
|
|
114
|
-
executeAfterRefresh(flags) {
|
|
114
|
+
async executeAfterRefresh(flags) {
|
|
115
115
|
for (const hook of this.hooks) {
|
|
116
116
|
if (hook.afterRefresh) {
|
|
117
117
|
try {
|
|
118
|
-
hook.afterRefresh(flags);
|
|
118
|
+
await hook.afterRefresh(flags);
|
|
119
119
|
} catch (error) {
|
|
120
120
|
console.error(
|
|
121
121
|
`[Toggly] Error in hook "${hook.getMetadata().name}.afterRefresh":`,
|
|
@@ -139,7 +139,8 @@ var TogglyClientInstance = class {
|
|
|
139
139
|
hookExecutor = new HookExecutor();
|
|
140
140
|
constructor(config) {
|
|
141
141
|
this.config = {
|
|
142
|
-
baseURI: "https://
|
|
142
|
+
baseURI: "https://definitions.toggly.io",
|
|
143
|
+
verifySignatures: false,
|
|
143
144
|
environment: "Production",
|
|
144
145
|
flagDefaults: {},
|
|
145
146
|
featureFlagsRefreshInterval: 3 * 60 * 1e3,
|
|
@@ -158,7 +159,7 @@ var TogglyClientInstance = class {
|
|
|
158
159
|
return "";
|
|
159
160
|
}
|
|
160
161
|
const baseUrl = baseURI.replace(/\/$/, "");
|
|
161
|
-
let url = `${baseUrl}/${appKey}
|
|
162
|
+
let url = `${baseUrl}/evaluated-signed/${appKey}/${environment}`;
|
|
162
163
|
if (identity) {
|
|
163
164
|
url += `?u=${encodeURIComponent(identity)}`;
|
|
164
165
|
}
|
|
@@ -187,7 +188,8 @@ var TogglyClientInstance = class {
|
|
|
187
188
|
if (!response.ok) {
|
|
188
189
|
throw new Error(`Failed to fetch flags: ${response.status} ${response.statusText}`);
|
|
189
190
|
}
|
|
190
|
-
const
|
|
191
|
+
const payload = await response.json();
|
|
192
|
+
const flags = "defs" in payload ? payload.defs : payload;
|
|
191
193
|
if (this.config.isDebug) {
|
|
192
194
|
console.log("[Toggly Client] Fetched flags:", flags);
|
|
193
195
|
}
|
|
@@ -215,7 +217,7 @@ var TogglyClientInstance = class {
|
|
|
215
217
|
$flags.set(flags);
|
|
216
218
|
$isReady.set(true);
|
|
217
219
|
$error.set(null);
|
|
218
|
-
this.hookExecutor.executeAfterRefresh(flags);
|
|
220
|
+
await this.hookExecutor.executeAfterRefresh(flags);
|
|
219
221
|
if (this.config.featureFlagsRefreshInterval && this.config.featureFlagsRefreshInterval > 0) {
|
|
220
222
|
this.startRefreshInterval();
|
|
221
223
|
}
|
|
@@ -230,7 +232,7 @@ var TogglyClientInstance = class {
|
|
|
230
232
|
const flags = await this.fetchFlags();
|
|
231
233
|
this.cache = flags;
|
|
232
234
|
$flags.set(flags);
|
|
233
|
-
this.hookExecutor.executeAfterRefresh(flags);
|
|
235
|
+
await this.hookExecutor.executeAfterRefresh(flags);
|
|
234
236
|
if (this.config.isDebug) {
|
|
235
237
|
console.log("[Toggly Client] Flags refreshed");
|
|
236
238
|
}
|
|
@@ -303,6 +305,15 @@ function stopRefreshInterval() {
|
|
|
303
305
|
clientInstance.stopRefreshInterval();
|
|
304
306
|
}
|
|
305
307
|
}
|
|
308
|
+
function __resetClient() {
|
|
309
|
+
if (clientInstance) {
|
|
310
|
+
clientInstance.stopRefreshInterval();
|
|
311
|
+
}
|
|
312
|
+
clientInstance = null;
|
|
313
|
+
$flags.set({});
|
|
314
|
+
$isReady.set(false);
|
|
315
|
+
$error.set(null);
|
|
316
|
+
}
|
|
306
317
|
function $flag(key, defaultValue = false) {
|
|
307
318
|
return computed($flags, (flags) => flags[key] ?? defaultValue);
|
|
308
319
|
}
|
|
@@ -344,9 +355,10 @@ export {
|
|
|
344
355
|
setIdentity,
|
|
345
356
|
clearIdentity,
|
|
346
357
|
stopRefreshInterval,
|
|
358
|
+
__resetClient,
|
|
347
359
|
$flag,
|
|
348
360
|
$gate,
|
|
349
361
|
addHook,
|
|
350
362
|
removeHook
|
|
351
363
|
};
|
|
352
|
-
//# sourceMappingURL=chunk-
|
|
364
|
+
//# sourceMappingURL=chunk-E2VPLWUD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/store.ts","../src/client/hooks.ts"],"sourcesContent":["/**\n * Toggly Client-Side Store using Nanostores\n * \n * Provides reactive state management for feature flags on the client side.\n * This module includes its own embedded Toggly client implementation.\n */\n\nimport { atom, computed, type ReadableAtom } from 'nanostores';\nimport type { TogglyConfig, Flags } from '../types/index.js';\nimport type { Hook } from '@ops-ai/toggly-hooks-types';\nimport { HookExecutor } from './hooks.js';\n\n/**\n * Atom containing all feature flags\n */\nexport const $flags = atom<Flags>({});\n\n/**\n * Atom indicating if flags are loaded and ready\n */\nexport const $isReady = atom<boolean>(false);\n\n/**\n * Atom containing any error that occurred during initialization\n */\nexport const $error = atom<Error | null>(null);\n\n/**\n * Internal client instance storage\n */\nlet clientInstance: TogglyClientInstance | null = null;\n\n/**\n * Internal client implementation\n */\nclass TogglyClientInstance {\n private config: TogglyConfig;\n private cache: Flags | null = null;\n private refreshInterval: NodeJS.Timeout | null = null;\n public hookExecutor = new HookExecutor();\n\n constructor(config: TogglyConfig) {\n this.config = {\n baseURI: 'https://definitions.toggly.io',\n verifySignatures: false,\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000,\n isDebug: false,\n connectTimeout: 5 * 1000,\n hooks: [],\n ...config,\n };\n \n // Register initial hooks\n if (this.config.hooks) {\n this.config.hooks.forEach(hook => this.hookExecutor.addHook(hook));\n }\n }\n\n private getApiUrl(): string {\n const { baseURI, appKey, environment, identity } = this.config;\n\n if (!appKey) {\n return '';\n }\n\n const baseUrl = baseURI!.replace(/\\/$/, '');\n let url = `${baseUrl}/evaluated-signed/${appKey}/${environment}`;\n\n if (identity) {\n url += `?u=${encodeURIComponent(identity)}`;\n }\n\n return url;\n }\n\n async fetchFlags(): Promise<Flags> {\n const url = this.getApiUrl();\n\n if (!url || !this.config.appKey) {\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using flag defaults (no appKey):', this.config.flagDefaults);\n }\n return { ...this.config.flagDefaults! };\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n cache: 'no-store',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status} ${response.statusText}`);\n }\n\n const payload = (await response.json()) as { defs?: Flags } | Flags;\n const flags = ('defs' in (payload as Record<string, unknown>) ? (payload as { defs: Flags }).defs : payload) as Flags;\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Fetched flags:', flags);\n }\n\n return flags;\n } catch (error) {\n if (this.config.isDebug) {\n console.error('[Toggly Client] Error fetching flags:', error);\n }\n\n // Fall back to cached flags or defaults\n if (this.cache) {\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using cached flags');\n }\n return { ...this.cache };\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using flag defaults');\n }\n\n return { ...this.config.flagDefaults! };\n }\n }\n\n async init(): Promise<void> {\n try {\n const flags = await this.fetchFlags();\n this.cache = flags;\n $flags.set(flags);\n $isReady.set(true);\n $error.set(null);\n \n // Trigger afterRefresh hooks\n await this.hookExecutor.executeAfterRefresh(flags);\n\n // Start refresh interval if configured\n if (\n this.config.featureFlagsRefreshInterval &&\n this.config.featureFlagsRefreshInterval > 0\n ) {\n this.startRefreshInterval();\n }\n } catch (error) {\n $error.set(error as Error);\n $isReady.set(true); // Still mark as ready even on error\n console.error('[Toggly Client] Initialization error:', error);\n }\n }\n\n async refresh(): Promise<void> {\n try {\n const flags = await this.fetchFlags();\n this.cache = flags;\n $flags.set(flags);\n \n // Trigger afterRefresh hooks\n await this.hookExecutor.executeAfterRefresh(flags);\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Flags refreshed');\n }\n } catch (error) {\n console.error('[Toggly Client] Refresh error:', error);\n }\n }\n\n private startRefreshInterval(): void {\n if (this.refreshInterval) {\n return;\n }\n\n this.refreshInterval = setInterval(() => {\n this.refresh();\n }, this.config.featureFlagsRefreshInterval!);\n\n if (this.config.isDebug) {\n console.log(\n `[Toggly Client] Started refresh interval: ${this.config.featureFlagsRefreshInterval}ms`\n );\n }\n }\n\n stopRefreshInterval(): void {\n if (this.refreshInterval) {\n clearInterval(this.refreshInterval);\n this.refreshInterval = null;\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Stopped refresh interval');\n }\n }\n }\n\n setIdentity(identity: string): void {\n this.config.identity = identity;\n this.refresh(); // Refresh with new identity\n }\n\n clearIdentity(): void {\n this.config.identity = undefined;\n this.refresh(); // Refresh without identity\n }\n}\n\n/**\n * Initialize Toggly client with configuration\n * \n * @param config - Toggly configuration\n */\nexport async function initTogglyClient(config: TogglyConfig): Promise<void> {\n if (clientInstance) {\n console.warn('[Toggly Client] Client already initialized');\n return;\n }\n\n clientInstance = new TogglyClientInstance(config);\n await clientInstance.init();\n}\n\n/**\n * Manually refresh feature flags\n */\nexport async function refreshFlags(): Promise<void> {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n await clientInstance.refresh();\n}\n\n/**\n * Set user identity for targeting\n * \n * @param identity - User identifier\n */\nexport function setIdentity(identity: string): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n clientInstance.setIdentity(identity);\n}\n\n/**\n * Clear user identity\n */\nexport function clearIdentity(): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n clientInstance.clearIdentity();\n}\n\n/**\n * Stop automatic refresh interval\n */\nexport function stopRefreshInterval(): void {\n if (clientInstance) {\n clientInstance.stopRefreshInterval();\n }\n}\n\n/**\n * Reset the client instance (for testing purposes)\n * @internal\n */\nexport function __resetClient(): void {\n if (clientInstance) {\n clientInstance.stopRefreshInterval();\n }\n clientInstance = null;\n $flags.set({});\n $isReady.set(false);\n $error.set(null);\n}\n\n/**\n * Create a computed atom for a specific feature flag\n * \n * @param key - Feature flag key\n * @param defaultValue - Default value if flag not found\n * @returns Readable atom with the flag value\n */\nexport function $flag(key: string, defaultValue: boolean = false): ReadableAtom<boolean> {\n return computed($flags, (flags) => flags[key] ?? defaultValue);\n}\n\n/**\n * Create a computed atom that evaluates multiple feature flags\n * \n * @param keys - Array of feature flag keys\n * @param requirement - 'all' or 'any'\n * @param negate - Whether to negate the result\n * @returns Readable atom with the evaluation result\n */\nexport function $gate(\n keys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n): ReadableAtom<boolean> {\n return computed($flags, (flags) => {\n if (keys.length === 0) {\n return !negate;\n }\n\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n isEnabled = keys.some((key) => flags[key] === true);\n } else {\n isEnabled = keys.every((key) => flags[key] === true);\n }\n\n return negate ? !isEnabled : isEnabled;\n });\n}\n\n/**\n * Add a hook dynamically\n */\nexport function addHook(hook: Hook): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n clientInstance.hookExecutor.addHook(hook);\n}\n\n/**\n * Remove a hook by name\n * @returns true if hook was found and removed, false otherwise\n */\nexport function removeHook(name: string): boolean {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return false;\n }\n return clientInstance.hookExecutor.removeHook(name);\n}\n\n","import type { Hook, EvaluationSeriesData, IdentitySeriesData } from '@ops-ai/toggly-hooks-types';\n\n/**\n * Internal class that manages hook registration and execution\n */\nexport class HookExecutor {\n private hooks: Hook[] = [];\n\n /**\n * Register a new hook\n */\n addHook(hook: Hook): void {\n const metadata = hook.getMetadata();\n \n // Check for duplicate hook names\n const existingHook = this.hooks.find(h => h.getMetadata().name === metadata.name);\n if (existingHook) {\n console.warn(`[Toggly] Hook with name \"${metadata.name}\" already registered. Skipping.`);\n return;\n }\n \n this.hooks.push(hook);\n }\n\n /**\n * Remove a hook by name\n * @returns true if hook was found and removed, false otherwise\n */\n removeHook(name: string): boolean {\n const index = this.hooks.findIndex(h => h.getMetadata().name === name);\n if (index > -1) {\n this.hooks.splice(index, 1);\n return true;\n }\n return false;\n }\n\n /**\n * Execute beforeEvaluation hooks in registration order (FIFO)\n * Collects data from each hook to pass to afterEvaluation\n */\n async executeBeforeEvaluation(\n flagKey: string,\n defaultValue?: boolean\n ): Promise<Map<string, EvaluationSeriesData | void>> {\n const dataMap = new Map<string, EvaluationSeriesData | void>();\n \n for (const hook of this.hooks) {\n if (hook.beforeEvaluation) {\n try {\n const data = await hook.beforeEvaluation(flagKey, defaultValue);\n dataMap.set(hook.getMetadata().name, data);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.beforeEvaluation\":`,\n error\n );\n }\n }\n }\n \n return dataMap;\n }\n\n /**\n * Execute afterEvaluation hooks in reverse order (LIFO)\n * Passes data from corresponding beforeEvaluation\n */\n async executeAfterEvaluation(\n flagKey: string,\n dataMap: Map<string, EvaluationSeriesData | void>,\n result: boolean\n ): Promise<void> {\n // Execute in reverse order\n for (let i = this.hooks.length - 1; i >= 0; i--) {\n const hook = this.hooks[i];\n if (hook.afterEvaluation) {\n try {\n const data = dataMap.get(hook.getMetadata().name);\n await hook.afterEvaluation(flagKey, data, result);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.afterEvaluation\":`,\n error\n );\n }\n }\n }\n }\n\n /**\n * Execute beforeIdentify hooks in registration order (FIFO)\n */\n async executeBeforeIdentify(identity: string): Promise<Map<string, IdentitySeriesData | void>> {\n const dataMap = new Map<string, IdentitySeriesData | void>();\n \n for (const hook of this.hooks) {\n if (hook.beforeIdentify) {\n try {\n const data = await hook.beforeIdentify(identity);\n dataMap.set(hook.getMetadata().name, data);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.beforeIdentify\":`,\n error\n );\n }\n }\n }\n \n return dataMap;\n }\n\n /**\n * Execute afterIdentify hooks in reverse order (LIFO)\n */\n async executeAfterIdentify(\n identity: string,\n dataMap: Map<string, IdentitySeriesData | void>\n ): Promise<void> {\n for (let i = this.hooks.length - 1; i >= 0; i--) {\n const hook = this.hooks[i];\n if (hook.afterIdentify) {\n try {\n const data = dataMap.get(hook.getMetadata().name);\n await hook.afterIdentify(identity, data);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.afterIdentify\":`,\n error\n );\n }\n }\n }\n }\n\n /**\n * Execute afterRefresh hooks in registration order (FIFO)\n */\n async executeAfterRefresh(flags: { [key: string]: boolean }): Promise<void> {\n for (const hook of this.hooks) {\n if (hook.afterRefresh) {\n try {\n await hook.afterRefresh(flags);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.afterRefresh\":`,\n error\n );\n }\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createTogglyServerClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-A6UWWUNJ.js";
|
|
4
4
|
|
|
5
5
|
// src/integration/index.ts
|
|
6
6
|
import * as fs from "fs";
|
|
@@ -8,7 +8,7 @@ import * as path from "path";
|
|
|
8
8
|
import { glob } from "glob";
|
|
9
9
|
function togglyIntegration(options = {}) {
|
|
10
10
|
const config = {
|
|
11
|
-
baseURI: "https://
|
|
11
|
+
baseURI: "https://definitions.toggly.io",
|
|
12
12
|
environment: "Production",
|
|
13
13
|
flagDefaults: {},
|
|
14
14
|
featureFlagsRefreshInterval: 3 * 60 * 1e3,
|
|
@@ -203,4 +203,4 @@ export {
|
|
|
203
203
|
togglyIntegration,
|
|
204
204
|
createTogglyMiddleware
|
|
205
205
|
};
|
|
206
|
-
//# sourceMappingURL=chunk-
|
|
206
|
+
//# sourceMappingURL=chunk-OIZJ54T2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/integration/index.ts"],"sourcesContent":["/**\n * Toggly Astro Integration\n * \n * Provides build-time configuration, frontmatter extraction, and runtime injection\n */\n\nimport type { AstroIntegration, AstroConfig } from 'astro';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { glob } from 'glob';\nimport type { TogglyConfig, PageFeatureMapping } from '../types/index.js';\nimport { createTogglyServerClient } from '../server/toggly-server.js';\n\nexport interface TogglyIntegrationOptions extends TogglyConfig {}\n\n/**\n * Toggly Astro Integration\n */\nexport default function togglyIntegration(\n options: TogglyIntegrationOptions = {}\n): AstroIntegration {\n const config: TogglyConfig = {\n baseURI: 'https://definitions.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000,\n isDebug: false,\n connectTimeout: 5 * 1000,\n allFeaturesEnabledDuringBuild: false,\n ...options,\n };\n\n let pageFeatureMapping: PageFeatureMapping = {};\n let astroConfig: AstroConfig;\n let buildTimeClient: any = null;\n\n return {\n name: '@ops-ai/astro-feature-flags-toggly',\n hooks: {\n 'astro:config:setup': async ({ config: cfg, injectScript, updateConfig }) => {\n astroConfig = cfg;\n\n if (config.isDebug) {\n console.log('[Toggly Integration] Setting up integration...');\n }\n\n // Inject client setup script\n // For client-side, we never want allFeaturesEnabledDuringBuild since that's only for SSG\n const clientConfig = { ...config, allFeaturesEnabledDuringBuild: false };\n injectScript(\n 'page',\n `\n window.__TOGGLY_CONFIG__ = ${JSON.stringify(clientConfig)};\n import('@ops-ai/astro-feature-flags-toggly/client/setup');\n `\n );\n\n // Add Vite plugin to strip x-feature directives before Astro's compiler\n updateConfig({\n vite: {\n ssr: {\n noExternal: ['@ops-ai/astro-feature-flags-toggly'],\n },\n plugins: [\n {\n name: 'toggly-x-feature-transform',\n enforce: 'pre' as const,\n load(id: string) {\n // Only process .astro files\n if (!id.endsWith('.astro')) return null;\n\n const code = fs.readFileSync(id, 'utf-8');\n\n // Check if frontmatter contains x-feature:\n const frontmatterMatch = code.match(/^(---\\s*\\n)([\\s\\S]*?)(\\n---)/);\n if (!frontmatterMatch) return null;\n\n const frontmatter = frontmatterMatch[2];\n if (!/^x-feature:\\s*.+$/m.test(frontmatter)) return null;\n\n // Strip the x-feature line entirely so esbuild doesn't choke on it\n const updatedFrontmatter = frontmatter.replace(\n /^x-feature:\\s*.+\\n?/m,\n ''\n );\n\n return code.replace(\n frontmatterMatch[0],\n frontmatterMatch[1] + updatedFrontmatter + frontmatterMatch[3]\n );\n },\n },\n ],\n },\n });\n },\n\n 'astro:server:setup': async ({ server }) => {\n if (config.isDebug) {\n console.log('[Toggly Integration] Server setup...');\n }\n\n // Create server client for SSR/dev server\n // In dev mode, we don't enable all features - we use actual flags\n const togglyClient = createTogglyServerClient(config, false);\n\n // Inject into server context (this will be available in SSR)\n server.middlewares.use((req, res, next) => {\n // @ts-ignore - Adding toggly to request\n req.togglyClient = togglyClient;\n next();\n });\n },\n\n 'astro:build:start': async () => {\n if (config.isDebug) {\n console.log('[Toggly Integration] Build started, extracting frontmatter...');\n }\n\n // If allFeaturesEnabledDuringBuild is true, create a build-time client\n // that will override all flags to true\n if (config.allFeaturesEnabledDuringBuild) {\n if (config.isDebug) {\n console.log('[Toggly Integration] Build mode: All features will be enabled');\n }\n // Create a build-time client that enables all features\n buildTimeClient = createTogglyServerClient(config, true);\n }\n\n // Extract page feature mapping from frontmatter\n pageFeatureMapping = await extractPageFeatures(astroConfig, config.isDebug);\n\n if (config.isDebug) {\n console.log(\n `[Toggly Integration] Found ${Object.keys(pageFeatureMapping).length} pages with x-feature`\n );\n Object.entries(pageFeatureMapping).forEach(([route, feature]) => {\n console.log(` ${route} -> ${feature}`);\n });\n }\n },\n\n 'astro:build:done': async ({ dir }) => {\n if (config.isDebug) {\n console.log('[Toggly Integration] Build done, writing manifest...');\n }\n\n // Write page feature manifest for edge workers\n const manifestPath = path.join(dir.pathname, 'toggly-page-features.json');\n fs.writeFileSync(manifestPath, JSON.stringify(pageFeatureMapping, null, 2), 'utf-8');\n\n if (config.isDebug) {\n console.log(`[Toggly Integration] Manifest written to: ${manifestPath}`);\n }\n\n // Also write config for reference\n const configPath = path.join(dir.pathname, 'toggly-config.json');\n fs.writeFileSync(\n configPath,\n JSON.stringify(\n {\n ...config,\n // Don't expose appKey in public build output\n appKey: config.appKey ? '***' : undefined,\n },\n null,\n 2\n ),\n 'utf-8'\n );\n },\n\n 'astro:config:done': ({ config: cfg, setAdapter }) => {\n // Store final config\n astroConfig = cfg;\n\n if (config.isDebug) {\n console.log('[Toggly Integration] Configuration finalized');\n }\n },\n },\n };\n}\n\n/**\n * Extract x-feature frontmatter from pages\n */\nasync function extractPageFeatures(\n astroConfig: AstroConfig,\n isDebug?: boolean\n): Promise<PageFeatureMapping> {\n const mapping: PageFeatureMapping = {};\n\n // Determine source directory\n const srcDir = astroConfig.srcDir?.pathname || path.join(process.cwd(), 'src');\n const pagesDir = path.join(srcDir, 'pages');\n const contentDir = path.join(srcDir, 'content');\n\n // Check if directories exist\n const dirsToScan: string[] = [];\n if (fs.existsSync(pagesDir)) {\n dirsToScan.push(pagesDir);\n }\n if (fs.existsSync(contentDir)) {\n dirsToScan.push(contentDir);\n }\n\n if (dirsToScan.length === 0) {\n if (isDebug) {\n console.warn('[Toggly Integration] No pages or content directories found');\n }\n return mapping;\n }\n\n for (const dir of dirsToScan) {\n // Find all .astro, .md, .mdx files\n const files = await glob('**/*.{astro,md,mdx}', {\n cwd: dir,\n absolute: false,\n ignore: ['node_modules/**', '**/node_modules/**'],\n });\n\n for (const file of files) {\n const filePath = path.join(dir, file);\n const content = fs.readFileSync(filePath, 'utf-8');\n\n // Extract frontmatter\n const frontmatterMatch = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---/);\n if (!frontmatterMatch) {\n continue;\n }\n\n const frontmatter = frontmatterMatch[1];\n\n // Look for x-feature in frontmatter\n const xFeatureMatch = frontmatter.match(/^x-feature:\\s*(.+)$/m);\n if (!xFeatureMatch) {\n continue;\n }\n\n let featureKey = xFeatureMatch[1].trim();\n // Remove quotes if present\n featureKey = featureKey.replace(/^[\"']|[\"']$/g, '');\n\n // Convert file path to route\n let route = convertFilePathToRoute(file, dir === pagesDir);\n\n // Prepend base if configured\n const base = astroConfig.base || '/';\n if (base !== '/') {\n route = path.join(base, route).replace(/\\\\/g, '/');\n }\n\n mapping[route] = featureKey;\n }\n }\n\n return mapping;\n}\n\n/**\n * Convert file path to Astro route\n */\nfunction convertFilePathToRoute(filePath: string, isPages: boolean): string {\n // Remove file extension\n let route = filePath.replace(/\\.(astro|md|mdx)$/, '');\n\n // Remove numeric prefixes (e.g., 01-intro.md -> intro.md)\n route = route\n .split('/')\n .map((segment) => segment.replace(/^\\d+-/, ''))\n .join('/');\n\n // Handle index files\n if (route.endsWith('/index') || route === 'index') {\n route = route.replace(/\\/index$/, '') || '/';\n }\n\n // Ensure leading slash\n if (!route.startsWith('/')) {\n route = '/' + route;\n }\n\n // For content collections, prepend with collection name if not pages\n // This is a simplification - Astro content collections have more complex routing\n\n return route;\n}\n\n/**\n * Astro middleware to inject Toggly into locals\n * This should be added to src/middleware.ts in the user's project\n */\nexport function createTogglyMiddleware(config: TogglyConfig) {\n return async function togglyMiddleware(\n { locals }: { locals: Record<string, any> },\n next: () => Promise<Response>\n ): Promise<Response> {\n // Create or reuse Toggly client\n // In middleware (runtime), we never enable all features - we use actual flags\n if (!locals.toggly) {\n const client = createTogglyServerClient(config, false);\n // Pre-fetch flags before page rendering starts so Feature components\n // have cached flags available immediately\n await client.refreshFlags();\n locals.toggly = client;\n }\n\n return next();\n };\n}\n\n\n"],"mappings":";;;;;AAOA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,SAAS,YAAY;AASN,SAAR,kBACL,UAAoC,CAAC,GACnB;AAClB,QAAM,SAAuB;AAAA,IAC3B,SAAS;AAAA,IACT,aAAa;AAAA,IACb,cAAc,CAAC;AAAA,IACf,6BAA6B,IAAI,KAAK;AAAA,IACtC,SAAS;AAAA,IACT,gBAAgB,IAAI;AAAA,IACpB,+BAA+B;AAAA,IAC/B,GAAG;AAAA,EACL;AAEA,MAAI,qBAAyC,CAAC;AAC9C,MAAI;AACJ,MAAI,kBAAuB;AAE3B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,MACL,sBAAsB,OAAO,EAAE,QAAQ,KAAK,cAAc,aAAa,MAAM;AAC3E,sBAAc;AAEd,YAAI,OAAO,SAAS;AAClB,kBAAQ,IAAI,gDAAgD;AAAA,QAC9D;AAIA,cAAM,eAAe,EAAE,GAAG,QAAQ,+BAA+B,MAAM;AACvE;AAAA,UACE;AAAA,UACA;AAAA,uCAC6B,KAAK,UAAU,YAAY,CAAC;AAAA;AAAA;AAAA,QAG3D;AAGA,qBAAa;AAAA,UACX,MAAM;AAAA,YACJ,KAAK;AAAA,cACH,YAAY,CAAC,oCAAoC;AAAA,YACnD;AAAA,YACA,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,SAAS;AAAA,gBACT,KAAK,IAAY;AAEf,sBAAI,CAAC,GAAG,SAAS,QAAQ,EAAG,QAAO;AAEnC,wBAAM,OAAU,gBAAa,IAAI,OAAO;AAGxC,wBAAM,mBAAmB,KAAK,MAAM,8BAA8B;AAClE,sBAAI,CAAC,iBAAkB,QAAO;AAE9B,wBAAM,cAAc,iBAAiB,CAAC;AACtC,sBAAI,CAAC,qBAAqB,KAAK,WAAW,EAAG,QAAO;AAGpD,wBAAM,qBAAqB,YAAY;AAAA,oBACrC;AAAA,oBACA;AAAA,kBACF;AAEA,yBAAO,KAAK;AAAA,oBACV,iBAAiB,CAAC;AAAA,oBAClB,iBAAiB,CAAC,IAAI,qBAAqB,iBAAiB,CAAC;AAAA,kBAC/D;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MAEA,sBAAsB,OAAO,EAAE,OAAO,MAAM;AAC1C,YAAI,OAAO,SAAS;AAClB,kBAAQ,IAAI,sCAAsC;AAAA,QACpD;AAIA,cAAM,eAAe,yBAAyB,QAAQ,KAAK;AAG3D,eAAO,YAAY,IAAI,CAAC,KAAK,KAAK,SAAS;AAEzC,cAAI,eAAe;AACnB,eAAK;AAAA,QACP,CAAC;AAAA,MACH;AAAA,MAEA,qBAAqB,YAAY;AAC/B,YAAI,OAAO,SAAS;AAClB,kBAAQ,IAAI,+DAA+D;AAAA,QAC7E;AAIA,YAAI,OAAO,+BAA+B;AACxC,cAAI,OAAO,SAAS;AAClB,oBAAQ,IAAI,+DAA+D;AAAA,UAC7E;AAEA,4BAAkB,yBAAyB,QAAQ,IAAI;AAAA,QACzD;AAGA,6BAAqB,MAAM,oBAAoB,aAAa,OAAO,OAAO;AAE1E,YAAI,OAAO,SAAS;AAClB,kBAAQ;AAAA,YACN,8BAA8B,OAAO,KAAK,kBAAkB,EAAE,MAAM;AAAA,UACtE;AACA,iBAAO,QAAQ,kBAAkB,EAAE,QAAQ,CAAC,CAAC,OAAO,OAAO,MAAM;AAC/D,oBAAQ,IAAI,KAAK,KAAK,OAAO,OAAO,EAAE;AAAA,UACxC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MAEA,oBAAoB,OAAO,EAAE,IAAI,MAAM;AACrC,YAAI,OAAO,SAAS;AAClB,kBAAQ,IAAI,sDAAsD;AAAA,QACpE;AAGA,cAAM,eAAoB,UAAK,IAAI,UAAU,2BAA2B;AACxE,QAAG,iBAAc,cAAc,KAAK,UAAU,oBAAoB,MAAM,CAAC,GAAG,OAAO;AAEnF,YAAI,OAAO,SAAS;AAClB,kBAAQ,IAAI,6CAA6C,YAAY,EAAE;AAAA,QACzE;AAGA,cAAM,aAAkB,UAAK,IAAI,UAAU,oBAAoB;AAC/D,QAAG;AAAA,UACD;AAAA,UACA,KAAK;AAAA,YACH;AAAA,cACE,GAAG;AAAA;AAAA,cAEH,QAAQ,OAAO,SAAS,QAAQ;AAAA,YAClC;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,MAEA,qBAAqB,CAAC,EAAE,QAAQ,KAAK,WAAW,MAAM;AAEpD,sBAAc;AAEd,YAAI,OAAO,SAAS;AAClB,kBAAQ,IAAI,8CAA8C;AAAA,QAC5D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAKA,eAAe,oBACb,aACA,SAC6B;AAC7B,QAAM,UAA8B,CAAC;AAGrC,QAAM,SAAS,YAAY,QAAQ,YAAiB,UAAK,QAAQ,IAAI,GAAG,KAAK;AAC7E,QAAM,WAAgB,UAAK,QAAQ,OAAO;AAC1C,QAAM,aAAkB,UAAK,QAAQ,SAAS;AAG9C,QAAM,aAAuB,CAAC;AAC9B,MAAO,cAAW,QAAQ,GAAG;AAC3B,eAAW,KAAK,QAAQ;AAAA,EAC1B;AACA,MAAO,cAAW,UAAU,GAAG;AAC7B,eAAW,KAAK,UAAU;AAAA,EAC5B;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,QAAI,SAAS;AACX,cAAQ,KAAK,4DAA4D;AAAA,IAC3E;AACA,WAAO;AAAA,EACT;AAEA,aAAW,OAAO,YAAY;AAE5B,UAAM,QAAQ,MAAM,KAAK,uBAAuB;AAAA,MAC9C,KAAK;AAAA,MACL,UAAU;AAAA,MACV,QAAQ,CAAC,mBAAmB,oBAAoB;AAAA,IAClD,CAAC;AAED,eAAW,QAAQ,OAAO;AACxB,YAAM,WAAgB,UAAK,KAAK,IAAI;AACpC,YAAM,UAAa,gBAAa,UAAU,OAAO;AAGjD,YAAM,mBAAmB,QAAQ,MAAM,0BAA0B;AACjE,UAAI,CAAC,kBAAkB;AACrB;AAAA,MACF;AAEA,YAAM,cAAc,iBAAiB,CAAC;AAGtC,YAAM,gBAAgB,YAAY,MAAM,sBAAsB;AAC9D,UAAI,CAAC,eAAe;AAClB;AAAA,MACF;AAEA,UAAI,aAAa,cAAc,CAAC,EAAE,KAAK;AAEvC,mBAAa,WAAW,QAAQ,gBAAgB,EAAE;AAGlD,UAAI,QAAQ,uBAAuB,MAAM,QAAQ,QAAQ;AAGzD,YAAM,OAAO,YAAY,QAAQ;AACjC,UAAI,SAAS,KAAK;AAChB,gBAAa,UAAK,MAAM,KAAK,EAAE,QAAQ,OAAO,GAAG;AAAA,MACnD;AAEA,cAAQ,KAAK,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,uBAAuB,UAAkB,SAA0B;AAE1E,MAAI,QAAQ,SAAS,QAAQ,qBAAqB,EAAE;AAGpD,UAAQ,MACL,MAAM,GAAG,EACT,IAAI,CAAC,YAAY,QAAQ,QAAQ,SAAS,EAAE,CAAC,EAC7C,KAAK,GAAG;AAGX,MAAI,MAAM,SAAS,QAAQ,KAAK,UAAU,SAAS;AACjD,YAAQ,MAAM,QAAQ,YAAY,EAAE,KAAK;AAAA,EAC3C;AAGA,MAAI,CAAC,MAAM,WAAW,GAAG,GAAG;AAC1B,YAAQ,MAAM;AAAA,EAChB;AAKA,SAAO;AACT;AAMO,SAAS,uBAAuB,QAAsB;AAC3D,SAAO,eAAe,iBACpB,EAAE,OAAO,GACT,MACmB;AAGnB,QAAI,CAAC,OAAO,QAAQ;AAClB,YAAM,SAAS,yBAAyB,QAAQ,KAAK;AAGrD,YAAM,OAAO,aAAa;AAC1B,aAAO,SAAS;AAAA,IAClB;AAEA,WAAO,KAAK;AAAA,EACd;AACF;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
$flags,
|
|
3
3
|
$isReady
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-E2VPLWUD.js";
|
|
5
5
|
|
|
6
6
|
// src/frameworks/react/Feature.tsx
|
|
7
7
|
import { useStore } from "@nanostores/react";
|
|
@@ -71,4 +71,4 @@ export {
|
|
|
71
71
|
useFeatureGate,
|
|
72
72
|
Feature_default
|
|
73
73
|
};
|
|
74
|
-
//# sourceMappingURL=chunk-
|
|
74
|
+
//# sourceMappingURL=chunk-PZXGYCWT.js.map
|
package/dist/client/setup.d.ts
CHANGED
package/dist/client/setup.js
CHANGED
package/dist/client/store.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as nanostores from 'nanostores';
|
|
2
2
|
import { ReadableAtom } from 'nanostores';
|
|
3
|
-
import { T as TogglyConfig, F as Flags } from '../index-
|
|
3
|
+
import { T as TogglyConfig, F as Flags } from '../index-CThzG-jN.js';
|
|
4
4
|
import { Hook } from '@ops-ai/toggly-hooks-types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -39,6 +39,11 @@ declare function clearIdentity(): void;
|
|
|
39
39
|
* Stop automatic refresh interval
|
|
40
40
|
*/
|
|
41
41
|
declare function stopRefreshInterval(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Reset the client instance (for testing purposes)
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
declare function __resetClient(): void;
|
|
42
47
|
/**
|
|
43
48
|
* Create a computed atom for a specific feature flag
|
|
44
49
|
*
|
|
@@ -66,4 +71,4 @@ declare function addHook(hook: Hook): void;
|
|
|
66
71
|
*/
|
|
67
72
|
declare function removeHook(name: string): boolean;
|
|
68
73
|
|
|
69
|
-
export { $error, $flag, $flags, $gate, $isReady, addHook, clearIdentity, initTogglyClient, refreshFlags, removeHook, setIdentity, stopRefreshInterval };
|
|
74
|
+
export { $error, $flag, $flags, $gate, $isReady, __resetClient, addHook, clearIdentity, initTogglyClient, refreshFlags, removeHook, setIdentity, stopRefreshInterval };
|
package/dist/client/store.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
$flags,
|
|
5
5
|
$gate,
|
|
6
6
|
$isReady,
|
|
7
|
+
__resetClient,
|
|
7
8
|
addHook,
|
|
8
9
|
clearIdentity,
|
|
9
10
|
initTogglyClient,
|
|
@@ -11,13 +12,14 @@ import {
|
|
|
11
12
|
removeHook,
|
|
12
13
|
setIdentity,
|
|
13
14
|
stopRefreshInterval
|
|
14
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-E2VPLWUD.js";
|
|
15
16
|
export {
|
|
16
17
|
$error,
|
|
17
18
|
$flag,
|
|
18
19
|
$flags,
|
|
19
20
|
$gate,
|
|
20
21
|
$isReady,
|
|
22
|
+
__resetClient,
|
|
21
23
|
addHook,
|
|
22
24
|
clearIdentity,
|
|
23
25
|
initTogglyClient,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as svelte_store from 'svelte/store';
|
|
2
2
|
export { $flags as flags, $isReady as isReady } from '../../client/store.js';
|
|
3
3
|
import 'nanostores';
|
|
4
|
-
import '../../index-
|
|
4
|
+
import '../../index-CThzG-jN.js';
|
|
5
5
|
import '@ops-ai/toggly-hooks-types';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -8,8 +8,10 @@ import { Hook } from '@ops-ai/toggly-hooks-types';
|
|
|
8
8
|
* Configuration options for Toggly integration
|
|
9
9
|
*/
|
|
10
10
|
interface TogglyConfig {
|
|
11
|
-
/** Base URI for the Toggly API (default: 'https://
|
|
11
|
+
/** Base URI for the Toggly definitions API (default: 'https://definitions.toggly.io') */
|
|
12
12
|
baseURI?: string;
|
|
13
|
+
/** Whether signatures should be verified on signed responses */
|
|
14
|
+
verifySignatures?: boolean;
|
|
13
15
|
/** Application key from Toggly */
|
|
14
16
|
appKey?: string;
|
|
15
17
|
/** Environment name (e.g., 'Production', 'Staging') (default: 'Production') */
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { TogglyIntegrationOptions, createTogglyMiddleware, default as togglyInte
|
|
|
2
2
|
export { TogglyServer, createTogglyServerClient } from './server/toggly-server.js';
|
|
3
3
|
export { allFeaturesEnabled, anyFeatureEnabled, getTogglyFromAstroGlobal, withFeatureFlag } from './server/utils.js';
|
|
4
4
|
export { $error, $flag, $flags, $gate, $isReady, clearIdentity, initTogglyClient, refreshFlags, setIdentity, stopRefreshInterval } from './client/store.js';
|
|
5
|
-
export { c as FeatureClientProps, b as FeatureProps, F as Flags, P as PageFeatureMapping, a as TogglyClient, T as TogglyConfig } from './index-
|
|
5
|
+
export { c as FeatureClientProps, b as FeatureProps, F as Flags, P as PageFeatureMapping, a as TogglyClient, T as TogglyConfig } from './index-CThzG-jN.js';
|
|
6
6
|
import 'astro';
|
|
7
7
|
import 'nanostores';
|
|
8
8
|
import '@ops-ai/toggly-hooks-types';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createTogglyMiddleware,
|
|
3
3
|
togglyIntegration
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OIZJ54T2.js";
|
|
5
5
|
import {
|
|
6
6
|
TogglyServer,
|
|
7
7
|
createTogglyServerClient
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-A6UWWUNJ.js";
|
|
9
9
|
import {
|
|
10
10
|
allFeaturesEnabled,
|
|
11
11
|
anyFeatureEnabled,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
refreshFlags,
|
|
24
24
|
setIdentity,
|
|
25
25
|
stopRefreshInterval
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-E2VPLWUD.js";
|
|
27
27
|
export {
|
|
28
28
|
$error,
|
|
29
29
|
$flag,
|
package/dist/server/utils.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ops-ai/astro-feature-flags-toggly",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Toggly feature flags SDK for Astro with SSR, SSG, and framework support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"url": "https://github.com/ops-ai/Toggly.FeatureManagement"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@ops-ai/toggly-hooks-types": "^1.
|
|
71
|
+
"@ops-ai/toggly-hooks-types": "^1.1.0",
|
|
72
72
|
"glob": "^10.3.10"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
@@ -77,15 +77,17 @@
|
|
|
77
77
|
"@nanostores/vue": "^1.0.1",
|
|
78
78
|
"@types/node": "^20.11.0",
|
|
79
79
|
"@types/react": "^19.2.7",
|
|
80
|
-
"@
|
|
80
|
+
"@types/ws": "^8.0.0",
|
|
81
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
81
82
|
"astro": "^5.16.0",
|
|
82
83
|
"jsdom": "^23.0.0",
|
|
83
84
|
"react": "^19.2.3",
|
|
84
85
|
"svelte": "^5.46.1",
|
|
85
86
|
"tsup": "^8.0.0",
|
|
86
87
|
"typescript": "^5.3.3",
|
|
87
|
-
"vitest": "^
|
|
88
|
-
"vue": "^3.5.26"
|
|
88
|
+
"vitest": "^4.0.18",
|
|
89
|
+
"vue": "^3.5.26",
|
|
90
|
+
"ws": "^8.0.0"
|
|
89
91
|
},
|
|
90
92
|
"peerDependencies": {
|
|
91
93
|
"@nanostores/react": "^1.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/integration/index.ts"],"sourcesContent":["/**\n * Toggly Astro Integration\n * \n * Provides build-time configuration, frontmatter extraction, and runtime injection\n */\n\nimport type { AstroIntegration, AstroConfig } from 'astro';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { glob } from 'glob';\nimport type { TogglyConfig, PageFeatureMapping } from '../types/index.js';\nimport { createTogglyServerClient } from '../server/toggly-server.js';\n\nexport interface TogglyIntegrationOptions extends TogglyConfig {}\n\n/**\n * Toggly Astro Integration\n */\nexport default function togglyIntegration(\n options: TogglyIntegrationOptions = {}\n): AstroIntegration {\n const config: TogglyConfig = {\n baseURI: 'https://client.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000,\n isDebug: false,\n connectTimeout: 5 * 1000,\n allFeaturesEnabledDuringBuild: false,\n ...options,\n };\n\n let pageFeatureMapping: PageFeatureMapping = {};\n let astroConfig: AstroConfig;\n let buildTimeClient: any = null;\n\n return {\n name: '@ops-ai/astro-feature-flags-toggly',\n hooks: {\n 'astro:config:setup': async ({ config: cfg, injectScript, updateConfig }) => {\n astroConfig = cfg;\n\n if (config.isDebug) {\n console.log('[Toggly Integration] Setting up integration...');\n }\n\n // Inject client setup script\n // For client-side, we never want allFeaturesEnabledDuringBuild since that's only for SSG\n const clientConfig = { ...config, allFeaturesEnabledDuringBuild: false };\n injectScript(\n 'page',\n `\n window.__TOGGLY_CONFIG__ = ${JSON.stringify(clientConfig)};\n import('@ops-ai/astro-feature-flags-toggly/client/setup');\n `\n );\n\n // Add Vite plugin to strip x-feature directives before Astro's compiler\n updateConfig({\n vite: {\n ssr: {\n noExternal: ['@ops-ai/astro-feature-flags-toggly'],\n },\n plugins: [\n {\n name: 'toggly-x-feature-transform',\n enforce: 'pre' as const,\n load(id: string) {\n // Only process .astro files\n if (!id.endsWith('.astro')) return null;\n\n const code = fs.readFileSync(id, 'utf-8');\n\n // Check if frontmatter contains x-feature:\n const frontmatterMatch = code.match(/^(---\\s*\\n)([\\s\\S]*?)(\\n---)/);\n if (!frontmatterMatch) return null;\n\n const frontmatter = frontmatterMatch[2];\n if (!/^x-feature:\\s*.+$/m.test(frontmatter)) return null;\n\n // Strip the x-feature line entirely so esbuild doesn't choke on it\n const updatedFrontmatter = frontmatter.replace(\n /^x-feature:\\s*.+\\n?/m,\n ''\n );\n\n return code.replace(\n frontmatterMatch[0],\n frontmatterMatch[1] + updatedFrontmatter + frontmatterMatch[3]\n );\n },\n },\n ],\n },\n });\n },\n\n 'astro:server:setup': async ({ server }) => {\n if (config.isDebug) {\n console.log('[Toggly Integration] Server setup...');\n }\n\n // Create server client for SSR/dev server\n // In dev mode, we don't enable all features - we use actual flags\n const togglyClient = createTogglyServerClient(config, false);\n\n // Inject into server context (this will be available in SSR)\n server.middlewares.use((req, res, next) => {\n // @ts-ignore - Adding toggly to request\n req.togglyClient = togglyClient;\n next();\n });\n },\n\n 'astro:build:start': async () => {\n if (config.isDebug) {\n console.log('[Toggly Integration] Build started, extracting frontmatter...');\n }\n\n // If allFeaturesEnabledDuringBuild is true, create a build-time client\n // that will override all flags to true\n if (config.allFeaturesEnabledDuringBuild) {\n if (config.isDebug) {\n console.log('[Toggly Integration] Build mode: All features will be enabled');\n }\n // Create a build-time client that enables all features\n buildTimeClient = createTogglyServerClient(config, true);\n }\n\n // Extract page feature mapping from frontmatter\n pageFeatureMapping = await extractPageFeatures(astroConfig, config.isDebug);\n\n if (config.isDebug) {\n console.log(\n `[Toggly Integration] Found ${Object.keys(pageFeatureMapping).length} pages with x-feature`\n );\n Object.entries(pageFeatureMapping).forEach(([route, feature]) => {\n console.log(` ${route} -> ${feature}`);\n });\n }\n },\n\n 'astro:build:done': async ({ dir }) => {\n if (config.isDebug) {\n console.log('[Toggly Integration] Build done, writing manifest...');\n }\n\n // Write page feature manifest for edge workers\n const manifestPath = path.join(dir.pathname, 'toggly-page-features.json');\n fs.writeFileSync(manifestPath, JSON.stringify(pageFeatureMapping, null, 2), 'utf-8');\n\n if (config.isDebug) {\n console.log(`[Toggly Integration] Manifest written to: ${manifestPath}`);\n }\n\n // Also write config for reference\n const configPath = path.join(dir.pathname, 'toggly-config.json');\n fs.writeFileSync(\n configPath,\n JSON.stringify(\n {\n ...config,\n // Don't expose appKey in public build output\n appKey: config.appKey ? '***' : undefined,\n },\n null,\n 2\n ),\n 'utf-8'\n );\n },\n\n 'astro:config:done': ({ config: cfg, setAdapter }) => {\n // Store final config\n astroConfig = cfg;\n\n if (config.isDebug) {\n console.log('[Toggly Integration] Configuration finalized');\n }\n },\n },\n };\n}\n\n/**\n * Extract x-feature frontmatter from pages\n */\nasync function extractPageFeatures(\n astroConfig: AstroConfig,\n isDebug?: boolean\n): Promise<PageFeatureMapping> {\n const mapping: PageFeatureMapping = {};\n\n // Determine source directory\n const srcDir = astroConfig.srcDir?.pathname || path.join(process.cwd(), 'src');\n const pagesDir = path.join(srcDir, 'pages');\n const contentDir = path.join(srcDir, 'content');\n\n // Check if directories exist\n const dirsToScan: string[] = [];\n if (fs.existsSync(pagesDir)) {\n dirsToScan.push(pagesDir);\n }\n if (fs.existsSync(contentDir)) {\n dirsToScan.push(contentDir);\n }\n\n if (dirsToScan.length === 0) {\n if (isDebug) {\n console.warn('[Toggly Integration] No pages or content directories found');\n }\n return mapping;\n }\n\n for (const dir of dirsToScan) {\n // Find all .astro, .md, .mdx files\n const files = await glob('**/*.{astro,md,mdx}', {\n cwd: dir,\n absolute: false,\n ignore: ['node_modules/**', '**/node_modules/**'],\n });\n\n for (const file of files) {\n const filePath = path.join(dir, file);\n const content = fs.readFileSync(filePath, 'utf-8');\n\n // Extract frontmatter\n const frontmatterMatch = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---/);\n if (!frontmatterMatch) {\n continue;\n }\n\n const frontmatter = frontmatterMatch[1];\n\n // Look for x-feature in frontmatter\n const xFeatureMatch = frontmatter.match(/^x-feature:\\s*(.+)$/m);\n if (!xFeatureMatch) {\n continue;\n }\n\n let featureKey = xFeatureMatch[1].trim();\n // Remove quotes if present\n featureKey = featureKey.replace(/^[\"']|[\"']$/g, '');\n\n // Convert file path to route\n let route = convertFilePathToRoute(file, dir === pagesDir);\n\n // Prepend base if configured\n const base = astroConfig.base || '/';\n if (base !== '/') {\n route = path.join(base, route).replace(/\\\\/g, '/');\n }\n\n mapping[route] = featureKey;\n }\n }\n\n return mapping;\n}\n\n/**\n * Convert file path to Astro route\n */\nfunction convertFilePathToRoute(filePath: string, isPages: boolean): string {\n // Remove file extension\n let route = filePath.replace(/\\.(astro|md|mdx)$/, '');\n\n // Remove numeric prefixes (e.g., 01-intro.md -> intro.md)\n route = route\n .split('/')\n .map((segment) => segment.replace(/^\\d+-/, ''))\n .join('/');\n\n // Handle index files\n if (route.endsWith('/index') || route === 'index') {\n route = route.replace(/\\/index$/, '') || '/';\n }\n\n // Ensure leading slash\n if (!route.startsWith('/')) {\n route = '/' + route;\n }\n\n // For content collections, prepend with collection name if not pages\n // This is a simplification - Astro content collections have more complex routing\n\n return route;\n}\n\n/**\n * Astro middleware to inject Toggly into locals\n * This should be added to src/middleware.ts in the user's project\n */\nexport function createTogglyMiddleware(config: TogglyConfig) {\n return async function togglyMiddleware(\n { locals }: { locals: Record<string, any> },\n next: () => Promise<Response>\n ): Promise<Response> {\n // Create or reuse Toggly client\n // In middleware (runtime), we never enable all features - we use actual flags\n if (!locals.toggly) {\n const client = createTogglyServerClient(config, false);\n // Pre-fetch flags before page rendering starts so Feature components\n // have cached flags available immediately\n await client.refreshFlags();\n locals.toggly = client;\n }\n\n return next();\n };\n}\n\n\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/store.ts","../src/client/hooks.ts"],"sourcesContent":["/**\n * Toggly Client-Side Store using Nanostores\n * \n * Provides reactive state management for feature flags on the client side.\n * This module includes its own embedded Toggly client implementation.\n */\n\nimport { atom, computed, type ReadableAtom } from 'nanostores';\nimport type { TogglyConfig, Flags } from '../types/index.js';\nimport type { Hook } from '@ops-ai/toggly-hooks-types';\nimport { HookExecutor } from './hooks.js';\n\n/**\n * Atom containing all feature flags\n */\nexport const $flags = atom<Flags>({});\n\n/**\n * Atom indicating if flags are loaded and ready\n */\nexport const $isReady = atom<boolean>(false);\n\n/**\n * Atom containing any error that occurred during initialization\n */\nexport const $error = atom<Error | null>(null);\n\n/**\n * Internal client instance storage\n */\nlet clientInstance: TogglyClientInstance | null = null;\n\n/**\n * Internal client implementation\n */\nclass TogglyClientInstance {\n private config: TogglyConfig;\n private cache: Flags | null = null;\n private refreshInterval: NodeJS.Timeout | null = null;\n public hookExecutor = new HookExecutor();\n\n constructor(config: TogglyConfig) {\n this.config = {\n baseURI: 'https://client.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000,\n isDebug: false,\n connectTimeout: 5 * 1000,\n hooks: [],\n ...config,\n };\n \n // Register initial hooks\n if (this.config.hooks) {\n this.config.hooks.forEach(hook => this.hookExecutor.addHook(hook));\n }\n }\n\n private getApiUrl(): string {\n const { baseURI, appKey, environment, identity } = this.config;\n\n if (!appKey) {\n return '';\n }\n\n const baseUrl = baseURI!.replace(/\\/$/, '');\n let url = `${baseUrl}/${appKey}-${environment}/defs`;\n\n if (identity) {\n url += `?u=${encodeURIComponent(identity)}`;\n }\n\n return url;\n }\n\n async fetchFlags(): Promise<Flags> {\n const url = this.getApiUrl();\n\n if (!url || !this.config.appKey) {\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using flag defaults (no appKey):', this.config.flagDefaults);\n }\n return { ...this.config.flagDefaults! };\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n cache: 'no-store',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status} ${response.statusText}`);\n }\n\n const flags = (await response.json()) as Flags;\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Fetched flags:', flags);\n }\n\n return flags;\n } catch (error) {\n if (this.config.isDebug) {\n console.error('[Toggly Client] Error fetching flags:', error);\n }\n\n // Fall back to cached flags or defaults\n if (this.cache) {\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using cached flags');\n }\n return { ...this.cache };\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using flag defaults');\n }\n\n return { ...this.config.flagDefaults! };\n }\n }\n\n async init(): Promise<void> {\n try {\n const flags = await this.fetchFlags();\n this.cache = flags;\n $flags.set(flags);\n $isReady.set(true);\n $error.set(null);\n \n // Trigger afterRefresh hooks\n this.hookExecutor.executeAfterRefresh(flags);\n\n // Start refresh interval if configured\n if (\n this.config.featureFlagsRefreshInterval &&\n this.config.featureFlagsRefreshInterval > 0\n ) {\n this.startRefreshInterval();\n }\n } catch (error) {\n $error.set(error as Error);\n $isReady.set(true); // Still mark as ready even on error\n console.error('[Toggly Client] Initialization error:', error);\n }\n }\n\n async refresh(): Promise<void> {\n try {\n const flags = await this.fetchFlags();\n this.cache = flags;\n $flags.set(flags);\n \n // Trigger afterRefresh hooks\n this.hookExecutor.executeAfterRefresh(flags);\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Flags refreshed');\n }\n } catch (error) {\n console.error('[Toggly Client] Refresh error:', error);\n }\n }\n\n private startRefreshInterval(): void {\n if (this.refreshInterval) {\n return;\n }\n\n this.refreshInterval = setInterval(() => {\n this.refresh();\n }, this.config.featureFlagsRefreshInterval!);\n\n if (this.config.isDebug) {\n console.log(\n `[Toggly Client] Started refresh interval: ${this.config.featureFlagsRefreshInterval}ms`\n );\n }\n }\n\n stopRefreshInterval(): void {\n if (this.refreshInterval) {\n clearInterval(this.refreshInterval);\n this.refreshInterval = null;\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Stopped refresh interval');\n }\n }\n }\n\n setIdentity(identity: string): void {\n this.config.identity = identity;\n this.refresh(); // Refresh with new identity\n }\n\n clearIdentity(): void {\n this.config.identity = undefined;\n this.refresh(); // Refresh without identity\n }\n}\n\n/**\n * Initialize Toggly client with configuration\n * \n * @param config - Toggly configuration\n */\nexport async function initTogglyClient(config: TogglyConfig): Promise<void> {\n if (clientInstance) {\n console.warn('[Toggly Client] Client already initialized');\n return;\n }\n\n clientInstance = new TogglyClientInstance(config);\n await clientInstance.init();\n}\n\n/**\n * Manually refresh feature flags\n */\nexport async function refreshFlags(): Promise<void> {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n await clientInstance.refresh();\n}\n\n/**\n * Set user identity for targeting\n * \n * @param identity - User identifier\n */\nexport function setIdentity(identity: string): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n clientInstance.setIdentity(identity);\n}\n\n/**\n * Clear user identity\n */\nexport function clearIdentity(): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n clientInstance.clearIdentity();\n}\n\n/**\n * Stop automatic refresh interval\n */\nexport function stopRefreshInterval(): void {\n if (clientInstance) {\n clientInstance.stopRefreshInterval();\n }\n}\n\n/**\n * Create a computed atom for a specific feature flag\n * \n * @param key - Feature flag key\n * @param defaultValue - Default value if flag not found\n * @returns Readable atom with the flag value\n */\nexport function $flag(key: string, defaultValue: boolean = false): ReadableAtom<boolean> {\n return computed($flags, (flags) => flags[key] ?? defaultValue);\n}\n\n/**\n * Create a computed atom that evaluates multiple feature flags\n * \n * @param keys - Array of feature flag keys\n * @param requirement - 'all' or 'any'\n * @param negate - Whether to negate the result\n * @returns Readable atom with the evaluation result\n */\nexport function $gate(\n keys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n): ReadableAtom<boolean> {\n return computed($flags, (flags) => {\n if (keys.length === 0) {\n return !negate;\n }\n\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n isEnabled = keys.some((key) => flags[key] === true);\n } else {\n isEnabled = keys.every((key) => flags[key] === true);\n }\n\n return negate ? !isEnabled : isEnabled;\n });\n}\n\n/**\n * Add a hook dynamically\n */\nexport function addHook(hook: Hook): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n clientInstance.hookExecutor.addHook(hook);\n}\n\n/**\n * Remove a hook by name\n * @returns true if hook was found and removed, false otherwise\n */\nexport function removeHook(name: string): boolean {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return false;\n }\n return clientInstance.hookExecutor.removeHook(name);\n}\n\n","import type { Hook, EvaluationSeriesData, IdentitySeriesData } from '@ops-ai/toggly-hooks-types';\n\n/**\n * Internal class that manages hook registration and execution\n */\nexport class HookExecutor {\n private hooks: Hook[] = [];\n\n /**\n * Register a new hook\n */\n addHook(hook: Hook): void {\n const metadata = hook.getMetadata();\n \n // Check for duplicate hook names\n const existingHook = this.hooks.find(h => h.getMetadata().name === metadata.name);\n if (existingHook) {\n console.warn(`[Toggly] Hook with name \"${metadata.name}\" already registered. Skipping.`);\n return;\n }\n \n this.hooks.push(hook);\n }\n\n /**\n * Remove a hook by name\n * @returns true if hook was found and removed, false otherwise\n */\n removeHook(name: string): boolean {\n const index = this.hooks.findIndex(h => h.getMetadata().name === name);\n if (index > -1) {\n this.hooks.splice(index, 1);\n return true;\n }\n return false;\n }\n\n /**\n * Execute beforeEvaluation hooks in registration order (FIFO)\n * Collects data from each hook to pass to afterEvaluation\n */\n executeBeforeEvaluation(\n flagKey: string,\n defaultValue?: boolean\n ): Map<string, EvaluationSeriesData | void> {\n const dataMap = new Map<string, EvaluationSeriesData | void>();\n \n for (const hook of this.hooks) {\n if (hook.beforeEvaluation) {\n try {\n const data = hook.beforeEvaluation(flagKey, defaultValue);\n dataMap.set(hook.getMetadata().name, data);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.beforeEvaluation\":`,\n error\n );\n }\n }\n }\n \n return dataMap;\n }\n\n /**\n * Execute afterEvaluation hooks in reverse order (LIFO)\n * Passes data from corresponding beforeEvaluation\n */\n executeAfterEvaluation(\n flagKey: string,\n dataMap: Map<string, EvaluationSeriesData | void>,\n result: boolean\n ): void {\n // Execute in reverse order\n for (let i = this.hooks.length - 1; i >= 0; i--) {\n const hook = this.hooks[i];\n if (hook.afterEvaluation) {\n try {\n const data = dataMap.get(hook.getMetadata().name);\n hook.afterEvaluation(flagKey, data, result);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.afterEvaluation\":`,\n error\n );\n }\n }\n }\n }\n\n /**\n * Execute beforeIdentify hooks in registration order (FIFO)\n */\n executeBeforeIdentify(identity: string): Map<string, IdentitySeriesData | void> {\n const dataMap = new Map<string, IdentitySeriesData | void>();\n \n for (const hook of this.hooks) {\n if (hook.beforeIdentify) {\n try {\n const data = hook.beforeIdentify(identity);\n dataMap.set(hook.getMetadata().name, data);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.beforeIdentify\":`,\n error\n );\n }\n }\n }\n \n return dataMap;\n }\n\n /**\n * Execute afterIdentify hooks in reverse order (LIFO)\n */\n executeAfterIdentify(\n identity: string,\n dataMap: Map<string, IdentitySeriesData | void>\n ): void {\n for (let i = this.hooks.length - 1; i >= 0; i--) {\n const hook = this.hooks[i];\n if (hook.afterIdentify) {\n try {\n const data = dataMap.get(hook.getMetadata().name);\n hook.afterIdentify(identity, data);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.afterIdentify\":`,\n error\n );\n }\n }\n }\n }\n\n /**\n * Execute afterRefresh hooks in registration order (FIFO)\n */\n executeAfterRefresh(flags: { [key: string]: boolean }): void {\n for (const hook of this.hooks) {\n if (hook.afterRefresh) {\n try {\n hook.afterRefresh(flags);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.afterRefresh\":`,\n error\n );\n }\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/toggly-server.ts"],"sourcesContent":["/**\n * Toggly Server-Side Client for Astro SSR/SSG\n * \n * This module provides server-side feature flag evaluation for Astro applications.\n * It caches flags for SSG builds and fetches fresh flags for SSR requests.\n * This is a complete, embedded Toggly client implementation.\n */\n\nimport type { TogglyConfig, Flags, TogglyClient } from '../types/index.js';\n\ninterface CachedFlags {\n flags: Flags;\n timestamp: number;\n}\n\n/**\n * Server-side Toggly client implementation\n */\nexport class TogglyServer implements TogglyClient {\n private config: TogglyConfig;\n private cache: CachedFlags | null = null;\n private fetchPromise: Promise<Flags> | null = null;\n private isBuildTime: boolean = false;\n\n constructor(config: TogglyConfig, isBuildTime: boolean = false) {\n this.config = {\n baseURI: 'https://client.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000, // 3 minutes\n isDebug: false,\n connectTimeout: 5 * 1000, // 5 seconds\n allFeaturesEnabledDuringBuild: false,\n ...config,\n };\n this.isBuildTime = isBuildTime;\n }\n\n /**\n * Get API URL for fetching flags\n */\n private getApiUrl(): string {\n const { baseURI, appKey, environment, identity } = this.config;\n\n if (!appKey) {\n return '';\n }\n\n const baseUrl = baseURI!.replace(/\\/$/, '');\n let url = `${baseUrl}/${appKey}-${environment}/defs`;\n\n if (identity) {\n url += `?u=${encodeURIComponent(identity)}`;\n }\n\n return url;\n }\n\n /**\n * Check if cache is valid\n */\n private isCacheValid(): boolean {\n if (!this.cache) return false;\n const age = Date.now() - this.cache.timestamp;\n return age < this.config.featureFlagsRefreshInterval!;\n }\n\n /**\n * Fetch flags from Toggly API\n */\n private async fetchFlags(): Promise<Flags> {\n const url = this.getApiUrl();\n\n // If no appKey, return flagDefaults\n if (!url || !this.config.appKey) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using flag defaults (no appKey):', this.config.flagDefaults);\n }\n return { ...this.config.flagDefaults! };\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n cache: 'no-store',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch flags from Toggly API: ${response.status} ${response.statusText}`\n );\n }\n\n let flags = (await response.json()) as Flags;\n\n // If allFeaturesEnabledDuringBuild is true and we're in build time,\n // override all flags to true\n if (this.config.allFeaturesEnabledDuringBuild && this.isBuildTime) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Build mode: Enabling all features');\n }\n flags = Object.keys(flags).reduce((acc, key) => {\n acc[key] = true;\n return acc;\n }, {} as Flags);\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Server] Fetched flags:', flags);\n }\n\n return flags;\n } catch (error) {\n if (this.config.isDebug) {\n console.error('[Toggly Server] Error fetching flags:', error);\n }\n\n // On error, try to use cached flags, otherwise use flagDefaults\n if (this.cache) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using cached flags:', this.cache.flags);\n }\n return { ...this.cache.flags };\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using flag defaults:', this.config.flagDefaults);\n }\n\n return { ...this.config.flagDefaults! };\n }\n }\n\n /**\n * Refresh flags cache\n */\n async refreshFlags(): Promise<void> {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Refreshing flags...');\n }\n\n // Prevent multiple concurrent fetches\n if (this.fetchPromise) {\n await this.fetchPromise;\n return;\n }\n\n this.fetchPromise = this.fetchFlags();\n\n try {\n const flags = await this.fetchPromise;\n this.cache = {\n flags,\n timestamp: Date.now(),\n };\n } finally {\n this.fetchPromise = null;\n }\n }\n\n /**\n * Get all feature flags\n */\n async getFlags(): Promise<Flags> {\n // If no appKey, return flagDefaults immediately\n if (!this.config.appKey) {\n return { ...this.config.flagDefaults! };\n }\n\n // If cache is valid, return it\n if (this.isCacheValid() && this.cache) {\n return { ...this.cache.flags };\n }\n\n // Otherwise, refresh and return\n await this.refreshFlags();\n return this.cache ? { ...this.cache.flags } : { ...this.config.flagDefaults! };\n }\n\n /**\n * Get a single feature flag value\n */\n async getFlag(key: string, defaultValue: boolean = false): Promise<boolean> {\n const flags = await this.getFlags();\n const value = flags[key];\n\n if (value !== undefined) {\n return value;\n }\n\n // Check flagDefaults first, then use provided defaultValue\n return this.config.flagDefaults?.[key] ?? defaultValue;\n }\n\n /**\n * Evaluate a feature gate with multiple flags\n */\n async evaluateGate(\n keys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n ): Promise<boolean> {\n if (keys.length === 0) {\n return !negate;\n }\n\n const flags = await this.getFlags();\n\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n // At least one flag must be true\n isEnabled = keys.some((key) => flags[key] === true);\n } else {\n // All flags must be true\n isEnabled = keys.every((key) => flags[key] === true);\n }\n\n return negate ? !isEnabled : isEnabled;\n }\n}\n\n/**\n * Create a new Toggly server-side client instance\n */\nexport function createTogglyServerClient(config: TogglyConfig, isBuildTime: boolean = false): TogglyServer {\n return new TogglyServer(config, isBuildTime);\n}\n\n\n"],"mappings":"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","names":[]}
|
|
File without changes
|