@ops-ai/astro-feature-flags-toggly 1.0.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.
Files changed (48) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +462 -0
  3. package/dist/chunk-354E3C57.js +173 -0
  4. package/dist/chunk-354E3C57.js.map +1 -0
  5. package/dist/chunk-4UKIT2NP.js +44 -0
  6. package/dist/chunk-4UKIT2NP.js.map +1 -0
  7. package/dist/chunk-FK633ULF.js +200 -0
  8. package/dist/chunk-FK633ULF.js.map +1 -0
  9. package/dist/chunk-XCJSQJHR.js +74 -0
  10. package/dist/chunk-XCJSQJHR.js.map +1 -0
  11. package/dist/chunk-XQGKGTBK.js +161 -0
  12. package/dist/chunk-XQGKGTBK.js.map +1 -0
  13. package/dist/client/setup.d.ts +3 -0
  14. package/dist/client/setup.js +21 -0
  15. package/dist/client/setup.js.map +1 -0
  16. package/dist/client/store.d.ts +59 -0
  17. package/dist/client/store.js +25 -0
  18. package/dist/client/store.js.map +1 -0
  19. package/dist/components/Feature.astro +79 -0
  20. package/dist/components/FeatureClient.astro +144 -0
  21. package/dist/frameworks/react/Feature.d.ts +86 -0
  22. package/dist/frameworks/react/Feature.js +14 -0
  23. package/dist/frameworks/react/Feature.js.map +1 -0
  24. package/dist/frameworks/react/index.d.ts +3 -0
  25. package/dist/frameworks/react/index.js +12 -0
  26. package/dist/frameworks/react/index.js.map +1 -0
  27. package/dist/frameworks/svelte/Feature.svelte +75 -0
  28. package/dist/frameworks/svelte/stores.d.ts +54 -0
  29. package/dist/frameworks/svelte/stores.js +34 -0
  30. package/dist/frameworks/svelte/stores.js.map +1 -0
  31. package/dist/frameworks/vue/Feature.vue +93 -0
  32. package/dist/frameworks/vue/composables.d.ts +56 -0
  33. package/dist/frameworks/vue/composables.js +39 -0
  34. package/dist/frameworks/vue/composables.js.map +1 -0
  35. package/dist/index-S3g0i0FH.d.ts +102 -0
  36. package/dist/index.d.ts +7 -0
  37. package/dist/index.js +47 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/integration/index.d.ts +24 -0
  40. package/dist/integration/index.js +10 -0
  41. package/dist/integration/index.js.map +1 -0
  42. package/dist/server/toggly-server.d.ts +53 -0
  43. package/dist/server/toggly-server.js +9 -0
  44. package/dist/server/toggly-server.js.map +1 -0
  45. package/dist/server/utils.d.ts +43 -0
  46. package/dist/server/utils.js +13 -0
  47. package/dist/server/utils.js.map +1 -0
  48. package/package.json +105 -0
@@ -0,0 +1,44 @@
1
+ // src/server/utils.ts
2
+ function getTogglyFromAstroGlobal(Astro) {
3
+ if (!Astro.locals.toggly) {
4
+ throw new Error(
5
+ "[Toggly] Client not initialized. Make sure the Toggly integration is added to astro.config.mjs"
6
+ );
7
+ }
8
+ return Astro.locals.toggly;
9
+ }
10
+ async function withFeatureFlag(featureKey, Astro) {
11
+ try {
12
+ const toggly = getTogglyFromAstroGlobal(Astro);
13
+ return await toggly.getFlag(featureKey, false);
14
+ } catch (error) {
15
+ console.error(`[Toggly] Error evaluating feature flag "${featureKey}":`, error);
16
+ return false;
17
+ }
18
+ }
19
+ async function anyFeatureEnabled(featureKeys, Astro) {
20
+ try {
21
+ const toggly = getTogglyFromAstroGlobal(Astro);
22
+ return await toggly.evaluateGate(featureKeys, "any", false);
23
+ } catch (error) {
24
+ console.error("[Toggly] Error evaluating feature gates:", error);
25
+ return false;
26
+ }
27
+ }
28
+ async function allFeaturesEnabled(featureKeys, Astro) {
29
+ try {
30
+ const toggly = getTogglyFromAstroGlobal(Astro);
31
+ return await toggly.evaluateGate(featureKeys, "all", false);
32
+ } catch (error) {
33
+ console.error("[Toggly] Error evaluating feature gates:", error);
34
+ return false;
35
+ }
36
+ }
37
+
38
+ export {
39
+ getTogglyFromAstroGlobal,
40
+ withFeatureFlag,
41
+ anyFeatureEnabled,
42
+ allFeaturesEnabled
43
+ };
44
+ //# sourceMappingURL=chunk-4UKIT2NP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/utils.ts"],"sourcesContent":["/**\n * Toggly Server Utilities for Astro\n * \n * Helper functions for server-side feature flag evaluation\n */\n\nimport type { AstroGlobal } from 'astro';\nimport type { TogglyClient } from '../types/index.js';\n\n/**\n * Get Toggly client from Astro global\n * \n * @param Astro - Astro global object\n * @returns TogglyClient instance\n * @throws Error if Toggly client is not initialized\n */\nexport function getTogglyFromAstroGlobal(Astro: AstroGlobal): TogglyClient {\n if (!Astro.locals.toggly) {\n throw new Error(\n '[Toggly] Client not initialized. Make sure the Toggly integration is added to astro.config.mjs'\n );\n }\n return Astro.locals.toggly;\n}\n\n/**\n * Higher-order function for page-level feature gating\n * \n * @param featureKey - Feature flag key to check\n * @param Astro - Astro global object\n * @returns Boolean indicating if feature is enabled\n */\nexport async function withFeatureFlag(\n featureKey: string,\n Astro: AstroGlobal\n): Promise<boolean> {\n try {\n const toggly = getTogglyFromAstroGlobal(Astro);\n return await toggly.getFlag(featureKey, false);\n } catch (error) {\n console.error(`[Toggly] Error evaluating feature flag \"${featureKey}\":`, error);\n return false;\n }\n}\n\n/**\n * Check if any of the provided feature flags are enabled\n * \n * @param featureKeys - Array of feature flag keys to check\n * @param Astro - Astro global object\n * @returns Boolean indicating if any feature is enabled\n */\nexport async function anyFeatureEnabled(\n featureKeys: string[],\n Astro: AstroGlobal\n): Promise<boolean> {\n try {\n const toggly = getTogglyFromAstroGlobal(Astro);\n return await toggly.evaluateGate(featureKeys, 'any', false);\n } catch (error) {\n console.error('[Toggly] Error evaluating feature gates:', error);\n return false;\n }\n}\n\n/**\n * Check if all of the provided feature flags are enabled\n * \n * @param featureKeys - Array of feature flag keys to check\n * @param Astro - Astro global object\n * @returns Boolean indicating if all features are enabled\n */\nexport async function allFeaturesEnabled(\n featureKeys: string[],\n Astro: AstroGlobal\n): Promise<boolean> {\n try {\n const toggly = getTogglyFromAstroGlobal(Astro);\n return await toggly.evaluateGate(featureKeys, 'all', false);\n } catch (error) {\n console.error('[Toggly] Error evaluating feature gates:', error);\n return false;\n }\n}\n\n\n"],"mappings":";AAgBO,SAAS,yBAAyB,OAAkC;AACzE,MAAI,CAAC,MAAM,OAAO,QAAQ;AACxB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,MAAM,OAAO;AACtB;AASA,eAAsB,gBACpB,YACA,OACkB;AAClB,MAAI;AACF,UAAM,SAAS,yBAAyB,KAAK;AAC7C,WAAO,MAAM,OAAO,QAAQ,YAAY,KAAK;AAAA,EAC/C,SAAS,OAAO;AACd,YAAQ,MAAM,2CAA2C,UAAU,MAAM,KAAK;AAC9E,WAAO;AAAA,EACT;AACF;AASA,eAAsB,kBACpB,aACA,OACkB;AAClB,MAAI;AACF,UAAM,SAAS,yBAAyB,KAAK;AAC7C,WAAO,MAAM,OAAO,aAAa,aAAa,OAAO,KAAK;AAAA,EAC5D,SAAS,OAAO;AACd,YAAQ,MAAM,4CAA4C,KAAK;AAC/D,WAAO;AAAA,EACT;AACF;AASA,eAAsB,mBACpB,aACA,OACkB;AAClB,MAAI;AACF,UAAM,SAAS,yBAAyB,KAAK;AAC7C,WAAO,MAAM,OAAO,aAAa,aAAa,OAAO,KAAK;AAAA,EAC5D,SAAS,OAAO;AACd,YAAQ,MAAM,4CAA4C,KAAK;AAC/D,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,200 @@
1
+ // src/client/store.ts
2
+ import { atom, computed } from "nanostores";
3
+ var $flags = atom({});
4
+ var $isReady = atom(false);
5
+ var $error = atom(null);
6
+ var clientInstance = null;
7
+ var TogglyClientInstance = class {
8
+ config;
9
+ cache = null;
10
+ refreshInterval = null;
11
+ constructor(config) {
12
+ this.config = {
13
+ baseURI: "https://client.toggly.io",
14
+ environment: "Production",
15
+ flagDefaults: {},
16
+ featureFlagsRefreshInterval: 3 * 60 * 1e3,
17
+ isDebug: false,
18
+ connectTimeout: 5 * 1e3,
19
+ ...config
20
+ };
21
+ }
22
+ getApiUrl() {
23
+ const { baseURI, appKey, environment, identity } = this.config;
24
+ if (!appKey) {
25
+ return "";
26
+ }
27
+ const baseUrl = baseURI.replace(/\/$/, "");
28
+ let url = `${baseUrl}/${appKey}-${environment}/defs`;
29
+ if (identity) {
30
+ url += `?u=${encodeURIComponent(identity)}`;
31
+ }
32
+ return url;
33
+ }
34
+ async fetchFlags() {
35
+ const url = this.getApiUrl();
36
+ if (!url || !this.config.appKey) {
37
+ if (this.config.isDebug) {
38
+ console.log("[Toggly Client] Using flag defaults (no appKey):", this.config.flagDefaults);
39
+ }
40
+ return { ...this.config.flagDefaults };
41
+ }
42
+ try {
43
+ const controller = new AbortController();
44
+ const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);
45
+ const response = await fetch(url, {
46
+ method: "GET",
47
+ headers: {
48
+ Accept: "application/json"
49
+ },
50
+ signal: controller.signal
51
+ });
52
+ clearTimeout(timeoutId);
53
+ if (!response.ok) {
54
+ throw new Error(`Failed to fetch flags: ${response.status} ${response.statusText}`);
55
+ }
56
+ const flags = await response.json();
57
+ if (this.config.isDebug) {
58
+ console.log("[Toggly Client] Fetched flags:", flags);
59
+ }
60
+ return flags;
61
+ } catch (error) {
62
+ if (this.config.isDebug) {
63
+ console.error("[Toggly Client] Error fetching flags:", error);
64
+ }
65
+ if (this.cache) {
66
+ if (this.config.isDebug) {
67
+ console.log("[Toggly Client] Using cached flags");
68
+ }
69
+ return { ...this.cache };
70
+ }
71
+ if (this.config.isDebug) {
72
+ console.log("[Toggly Client] Using flag defaults");
73
+ }
74
+ return { ...this.config.flagDefaults };
75
+ }
76
+ }
77
+ async init() {
78
+ try {
79
+ const flags = await this.fetchFlags();
80
+ this.cache = flags;
81
+ $flags.set(flags);
82
+ $isReady.set(true);
83
+ $error.set(null);
84
+ if (this.config.featureFlagsRefreshInterval && this.config.featureFlagsRefreshInterval > 0) {
85
+ this.startRefreshInterval();
86
+ }
87
+ } catch (error) {
88
+ $error.set(error);
89
+ $isReady.set(true);
90
+ console.error("[Toggly Client] Initialization error:", error);
91
+ }
92
+ }
93
+ async refresh() {
94
+ try {
95
+ const flags = await this.fetchFlags();
96
+ this.cache = flags;
97
+ $flags.set(flags);
98
+ if (this.config.isDebug) {
99
+ console.log("[Toggly Client] Flags refreshed");
100
+ }
101
+ } catch (error) {
102
+ console.error("[Toggly Client] Refresh error:", error);
103
+ }
104
+ }
105
+ startRefreshInterval() {
106
+ if (this.refreshInterval) {
107
+ return;
108
+ }
109
+ this.refreshInterval = setInterval(() => {
110
+ this.refresh();
111
+ }, this.config.featureFlagsRefreshInterval);
112
+ if (this.config.isDebug) {
113
+ console.log(
114
+ `[Toggly Client] Started refresh interval: ${this.config.featureFlagsRefreshInterval}ms`
115
+ );
116
+ }
117
+ }
118
+ stopRefreshInterval() {
119
+ if (this.refreshInterval) {
120
+ clearInterval(this.refreshInterval);
121
+ this.refreshInterval = null;
122
+ if (this.config.isDebug) {
123
+ console.log("[Toggly Client] Stopped refresh interval");
124
+ }
125
+ }
126
+ }
127
+ setIdentity(identity) {
128
+ this.config.identity = identity;
129
+ this.refresh();
130
+ }
131
+ clearIdentity() {
132
+ this.config.identity = void 0;
133
+ this.refresh();
134
+ }
135
+ };
136
+ async function initTogglyClient(config) {
137
+ if (clientInstance) {
138
+ console.warn("[Toggly Client] Client already initialized");
139
+ return;
140
+ }
141
+ clientInstance = new TogglyClientInstance(config);
142
+ await clientInstance.init();
143
+ }
144
+ async function refreshFlags() {
145
+ if (!clientInstance) {
146
+ console.error("[Toggly Client] Client not initialized");
147
+ return;
148
+ }
149
+ await clientInstance.refresh();
150
+ }
151
+ function setIdentity(identity) {
152
+ if (!clientInstance) {
153
+ console.error("[Toggly Client] Client not initialized");
154
+ return;
155
+ }
156
+ clientInstance.setIdentity(identity);
157
+ }
158
+ function clearIdentity() {
159
+ if (!clientInstance) {
160
+ console.error("[Toggly Client] Client not initialized");
161
+ return;
162
+ }
163
+ clientInstance.clearIdentity();
164
+ }
165
+ function stopRefreshInterval() {
166
+ if (clientInstance) {
167
+ clientInstance.stopRefreshInterval();
168
+ }
169
+ }
170
+ function $flag(key, defaultValue = false) {
171
+ return computed($flags, (flags) => flags[key] ?? defaultValue);
172
+ }
173
+ function $gate(keys, requirement = "all", negate = false) {
174
+ return computed($flags, (flags) => {
175
+ if (keys.length === 0) {
176
+ return !negate;
177
+ }
178
+ let isEnabled;
179
+ if (requirement === "any") {
180
+ isEnabled = keys.some((key) => flags[key] === true);
181
+ } else {
182
+ isEnabled = keys.every((key) => flags[key] === true);
183
+ }
184
+ return negate ? !isEnabled : isEnabled;
185
+ });
186
+ }
187
+
188
+ export {
189
+ $flags,
190
+ $isReady,
191
+ $error,
192
+ initTogglyClient,
193
+ refreshFlags,
194
+ setIdentity,
195
+ clearIdentity,
196
+ stopRefreshInterval,
197
+ $flag,
198
+ $gate
199
+ };
200
+ //# sourceMappingURL=chunk-FK633ULF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/store.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';\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\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 ...config,\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 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 // 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 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"],"mappings":"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","names":[]}
@@ -0,0 +1,74 @@
1
+ import {
2
+ $flags,
3
+ $isReady
4
+ } from "./chunk-FK633ULF.js";
5
+
6
+ // src/frameworks/react/Feature.tsx
7
+ import { useStore } from "@nanostores/react";
8
+ import { Fragment, jsx } from "react/jsx-runtime";
9
+ function Feature({
10
+ flag,
11
+ flags,
12
+ requirement = "all",
13
+ negate = false,
14
+ children,
15
+ fallback = null
16
+ }) {
17
+ const allFlags = useStore($flags);
18
+ const isReady = useStore($isReady);
19
+ const flagKeys = [];
20
+ if (flag) {
21
+ flagKeys.push(flag);
22
+ }
23
+ if (flags && Array.isArray(flags)) {
24
+ flagKeys.push(...flags);
25
+ }
26
+ if (!isReady) {
27
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback });
28
+ }
29
+ if (flagKeys.length === 0) {
30
+ return /* @__PURE__ */ jsx(Fragment, { children: negate ? fallback : children });
31
+ }
32
+ let isEnabled;
33
+ if (requirement === "any") {
34
+ isEnabled = flagKeys.some((key) => allFlags[key] === true);
35
+ } else {
36
+ isEnabled = flagKeys.every((key) => allFlags[key] === true);
37
+ }
38
+ if (negate) {
39
+ isEnabled = !isEnabled;
40
+ }
41
+ return /* @__PURE__ */ jsx(Fragment, { children: isEnabled ? children : fallback });
42
+ }
43
+ function useFeatureFlag(flagKey, defaultValue = false) {
44
+ const flags = useStore($flags);
45
+ const isReady = useStore($isReady);
46
+ const enabled = flags[flagKey] ?? defaultValue;
47
+ return { enabled, isReady };
48
+ }
49
+ function useFeatureGate(flagKeys, requirement = "all", negate = false) {
50
+ const flags = useStore($flags);
51
+ const isReady = useStore($isReady);
52
+ if (flagKeys.length === 0) {
53
+ return { enabled: !negate, isReady };
54
+ }
55
+ let isEnabled;
56
+ if (requirement === "any") {
57
+ isEnabled = flagKeys.some((key) => flags[key] === true);
58
+ } else {
59
+ isEnabled = flagKeys.every((key) => flags[key] === true);
60
+ }
61
+ if (negate) {
62
+ isEnabled = !isEnabled;
63
+ }
64
+ return { enabled: isEnabled, isReady };
65
+ }
66
+ var Feature_default = Feature;
67
+
68
+ export {
69
+ Feature,
70
+ useFeatureFlag,
71
+ useFeatureGate,
72
+ Feature_default
73
+ };
74
+ //# sourceMappingURL=chunk-XCJSQJHR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/frameworks/react/Feature.tsx"],"sourcesContent":["/**\n * React Feature Component for Astro Islands\n * \n * Use this component in React islands within Astro for client-side feature flagging.\n * Integrates with nanostores for reactive state management.\n */\n\nimport { useStore } from '@nanostores/react';\nimport { $flags, $isReady } from '../../client/store.js';\nimport type { ReactNode } from 'react';\n\nexport interface FeatureProps {\n /** Single feature flag key to check */\n flag?: string;\n /** Multiple feature flag keys to check */\n flags?: string[];\n /** Requirement for multiple flags: 'all' or 'any' (default: 'all') */\n requirement?: 'all' | 'any';\n /** If true, negates the result (default: false) */\n negate?: boolean;\n /** Content to render when flag is enabled */\n children: ReactNode;\n /** Content to render when flag is disabled (optional) */\n fallback?: ReactNode;\n}\n\n/**\n * Feature - React component for conditional rendering based on feature flags\n * \n * @example\n * ```tsx\n * <Feature flag=\"new-dashboard\">\n * <Dashboard />\n * </Feature>\n * ```\n * \n * @example Multiple flags with 'any' requirement\n * ```tsx\n * <Feature flags={['feature1', 'feature2']} requirement=\"any\">\n * <Content />\n * </Feature>\n * ```\n * \n * @example With fallback\n * ```tsx\n * <Feature flag=\"premium-feature\" fallback={<UpgradePrompt />}>\n * <PremiumContent />\n * </Feature>\n * ```\n */\nexport function Feature({\n flag,\n flags,\n requirement = 'all',\n negate = false,\n children,\n fallback = null,\n}: FeatureProps) {\n const allFlags = useStore($flags);\n const isReady = useStore($isReady);\n\n // Build flag keys array\n const flagKeys: string[] = [];\n if (flag) {\n flagKeys.push(flag);\n }\n if (flags && Array.isArray(flags)) {\n flagKeys.push(...flags);\n }\n\n // Wait for flags to be ready\n if (!isReady) {\n return <>{fallback}</>;\n }\n\n // No flags specified\n if (flagKeys.length === 0) {\n return <>{negate ? fallback : children}</>;\n }\n\n // Evaluate flags\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n isEnabled = flagKeys.some((key) => allFlags[key] === true);\n } else {\n isEnabled = flagKeys.every((key) => allFlags[key] === true);\n }\n\n if (negate) {\n isEnabled = !isEnabled;\n }\n\n return <>{isEnabled ? children : fallback}</>;\n}\n\n/**\n * Hook to check if a feature flag is enabled\n * \n * @param flagKey - Feature flag key to check\n * @param defaultValue - Default value if flag not found (default: false)\n * @returns Object with enabled state and ready state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { enabled, isReady } = useFeatureFlag('new-dashboard');\n * \n * if (!isReady) return <Loading />;\n * if (!enabled) return <OldDashboard />;\n * return <NewDashboard />;\n * }\n * ```\n */\nexport function useFeatureFlag(\n flagKey: string,\n defaultValue: boolean = false\n): { enabled: boolean; isReady: boolean } {\n const flags = useStore($flags);\n const isReady = useStore($isReady);\n\n const enabled = flags[flagKey] ?? defaultValue;\n\n return { enabled, isReady };\n}\n\n/**\n * Hook to check if multiple feature flags are enabled\n * \n * @param flagKeys - Array of feature flag keys to check\n * @param requirement - 'all' or 'any' (default: 'all')\n * @param negate - If true, negates the result (default: false)\n * @returns Object with enabled state and ready state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { enabled } = useFeatureGate(['feature1', 'feature2'], 'any');\n * return enabled ? <NewFeatures /> : <OldFeatures />;\n * }\n * ```\n */\nexport function useFeatureGate(\n flagKeys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n): { enabled: boolean; isReady: boolean } {\n const flags = useStore($flags);\n const isReady = useStore($isReady);\n\n if (flagKeys.length === 0) {\n return { enabled: !negate, isReady };\n }\n\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n isEnabled = flagKeys.some((key) => flags[key] === true);\n } else {\n isEnabled = flagKeys.every((key) => flags[key] === true);\n }\n\n if (negate) {\n isEnabled = !isEnabled;\n }\n\n return { enabled: isEnabled, isReady };\n}\n\nexport default Feature;\n\n\n"],"mappings":";;;;;;AAOA,SAAS,gBAAgB;AAiEd;AAtBJ,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,SAAS;AAAA,EACT;AAAA,EACA,WAAW;AACb,GAAiB;AACf,QAAM,WAAW,SAAS,MAAM;AAChC,QAAM,UAAU,SAAS,QAAQ;AAGjC,QAAM,WAAqB,CAAC;AAC5B,MAAI,MAAM;AACR,aAAS,KAAK,IAAI;AAAA,EACpB;AACA,MAAI,SAAS,MAAM,QAAQ,KAAK,GAAG;AACjC,aAAS,KAAK,GAAG,KAAK;AAAA,EACxB;AAGA,MAAI,CAAC,SAAS;AACZ,WAAO,gCAAG,oBAAS;AAAA,EACrB;AAGA,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,gCAAG,mBAAS,WAAW,UAAS;AAAA,EACzC;AAGA,MAAI;AAEJ,MAAI,gBAAgB,OAAO;AACzB,gBAAY,SAAS,KAAK,CAAC,QAAQ,SAAS,GAAG,MAAM,IAAI;AAAA,EAC3D,OAAO;AACL,gBAAY,SAAS,MAAM,CAAC,QAAQ,SAAS,GAAG,MAAM,IAAI;AAAA,EAC5D;AAEA,MAAI,QAAQ;AACV,gBAAY,CAAC;AAAA,EACf;AAEA,SAAO,gCAAG,sBAAY,WAAW,UAAS;AAC5C;AAoBO,SAAS,eACd,SACA,eAAwB,OACgB;AACxC,QAAM,QAAQ,SAAS,MAAM;AAC7B,QAAM,UAAU,SAAS,QAAQ;AAEjC,QAAM,UAAU,MAAM,OAAO,KAAK;AAElC,SAAO,EAAE,SAAS,QAAQ;AAC5B;AAkBO,SAAS,eACd,UACA,cAA6B,OAC7B,SAAkB,OACsB;AACxC,QAAM,QAAQ,SAAS,MAAM;AAC7B,QAAM,UAAU,SAAS,QAAQ;AAEjC,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,EAAE,SAAS,CAAC,QAAQ,QAAQ;AAAA,EACrC;AAEA,MAAI;AAEJ,MAAI,gBAAgB,OAAO;AACzB,gBAAY,SAAS,KAAK,CAAC,QAAQ,MAAM,GAAG,MAAM,IAAI;AAAA,EACxD,OAAO;AACL,gBAAY,SAAS,MAAM,CAAC,QAAQ,MAAM,GAAG,MAAM,IAAI;AAAA,EACzD;AAEA,MAAI,QAAQ;AACV,gBAAY,CAAC;AAAA,EACf;AAEA,SAAO,EAAE,SAAS,WAAW,QAAQ;AACvC;AAEA,IAAO,kBAAQ;","names":[]}
@@ -0,0 +1,161 @@
1
+ // src/server/toggly-server.ts
2
+ var TogglyServer = class {
3
+ config;
4
+ cache = null;
5
+ fetchPromise = null;
6
+ constructor(config) {
7
+ this.config = {
8
+ baseURI: "https://client.toggly.io",
9
+ environment: "Production",
10
+ flagDefaults: {},
11
+ featureFlagsRefreshInterval: 3 * 60 * 1e3,
12
+ // 3 minutes
13
+ isDebug: false,
14
+ connectTimeout: 5 * 1e3,
15
+ // 5 seconds
16
+ ...config
17
+ };
18
+ }
19
+ /**
20
+ * Get API URL for fetching flags
21
+ */
22
+ getApiUrl() {
23
+ const { baseURI, appKey, environment, identity } = this.config;
24
+ if (!appKey) {
25
+ return "";
26
+ }
27
+ const baseUrl = baseURI.replace(/\/$/, "");
28
+ let url = `${baseUrl}/${appKey}-${environment}/defs`;
29
+ if (identity) {
30
+ url += `?u=${encodeURIComponent(identity)}`;
31
+ }
32
+ return url;
33
+ }
34
+ /**
35
+ * Check if cache is valid
36
+ */
37
+ isCacheValid() {
38
+ if (!this.cache) return false;
39
+ const age = Date.now() - this.cache.timestamp;
40
+ return age < this.config.featureFlagsRefreshInterval;
41
+ }
42
+ /**
43
+ * Fetch flags from Toggly API
44
+ */
45
+ async fetchFlags() {
46
+ const url = this.getApiUrl();
47
+ if (!url || !this.config.appKey) {
48
+ if (this.config.isDebug) {
49
+ console.log("[Toggly Server] Using flag defaults (no appKey):", this.config.flagDefaults);
50
+ }
51
+ return { ...this.config.flagDefaults };
52
+ }
53
+ try {
54
+ const controller = new AbortController();
55
+ const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);
56
+ const response = await fetch(url, {
57
+ method: "GET",
58
+ headers: {
59
+ Accept: "application/json"
60
+ },
61
+ signal: controller.signal
62
+ });
63
+ clearTimeout(timeoutId);
64
+ if (!response.ok) {
65
+ throw new Error(
66
+ `Failed to fetch flags from Toggly API: ${response.status} ${response.statusText}`
67
+ );
68
+ }
69
+ const flags = await response.json();
70
+ if (this.config.isDebug) {
71
+ console.log("[Toggly Server] Fetched flags:", flags);
72
+ }
73
+ return flags;
74
+ } catch (error) {
75
+ if (this.config.isDebug) {
76
+ console.error("[Toggly Server] Error fetching flags:", error);
77
+ }
78
+ if (this.cache) {
79
+ if (this.config.isDebug) {
80
+ console.log("[Toggly Server] Using cached flags:", this.cache.flags);
81
+ }
82
+ return { ...this.cache.flags };
83
+ }
84
+ if (this.config.isDebug) {
85
+ console.log("[Toggly Server] Using flag defaults:", this.config.flagDefaults);
86
+ }
87
+ return { ...this.config.flagDefaults };
88
+ }
89
+ }
90
+ /**
91
+ * Refresh flags cache
92
+ */
93
+ async refreshFlags() {
94
+ if (this.config.isDebug) {
95
+ console.log("[Toggly Server] Refreshing flags...");
96
+ }
97
+ if (this.fetchPromise) {
98
+ await this.fetchPromise;
99
+ return;
100
+ }
101
+ this.fetchPromise = this.fetchFlags();
102
+ try {
103
+ const flags = await this.fetchPromise;
104
+ this.cache = {
105
+ flags,
106
+ timestamp: Date.now()
107
+ };
108
+ } finally {
109
+ this.fetchPromise = null;
110
+ }
111
+ }
112
+ /**
113
+ * Get all feature flags
114
+ */
115
+ async getFlags() {
116
+ if (!this.config.appKey) {
117
+ return { ...this.config.flagDefaults };
118
+ }
119
+ if (this.isCacheValid() && this.cache) {
120
+ return { ...this.cache.flags };
121
+ }
122
+ await this.refreshFlags();
123
+ return this.cache ? { ...this.cache.flags } : { ...this.config.flagDefaults };
124
+ }
125
+ /**
126
+ * Get a single feature flag value
127
+ */
128
+ async getFlag(key, defaultValue = false) {
129
+ const flags = await this.getFlags();
130
+ const value = flags[key];
131
+ if (value !== void 0) {
132
+ return value;
133
+ }
134
+ return this.config.flagDefaults?.[key] ?? defaultValue;
135
+ }
136
+ /**
137
+ * Evaluate a feature gate with multiple flags
138
+ */
139
+ async evaluateGate(keys, requirement = "all", negate = false) {
140
+ if (keys.length === 0) {
141
+ return !negate;
142
+ }
143
+ const flags = await this.getFlags();
144
+ let isEnabled;
145
+ if (requirement === "any") {
146
+ isEnabled = keys.some((key) => flags[key] === true);
147
+ } else {
148
+ isEnabled = keys.every((key) => flags[key] === true);
149
+ }
150
+ return negate ? !isEnabled : isEnabled;
151
+ }
152
+ };
153
+ function createTogglyServerClient(config) {
154
+ return new TogglyServer(config);
155
+ }
156
+
157
+ export {
158
+ TogglyServer,
159
+ createTogglyServerClient
160
+ };
161
+ //# sourceMappingURL=chunk-XQGKGTBK.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\n constructor(config: TogglyConfig) {\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 ...config,\n };\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 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 flags = (await response.json()) as Flags;\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): TogglyServer {\n return new TogglyServer(config);\n}\n\n\n"],"mappings":"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","names":[]}
@@ -0,0 +1,3 @@
1
+ export { initTogglyClient } from './store.js';
2
+ import 'nanostores';
3
+ import '../index-S3g0i0FH.js';
@@ -0,0 +1,21 @@
1
+ import {
2
+ initTogglyClient
3
+ } from "../chunk-FK633ULF.js";
4
+
5
+ // src/client/setup.ts
6
+ if (typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined") {
7
+ const config = globalThis.window.__TOGGLY_CONFIG__;
8
+ if (config) {
9
+ initTogglyClient(config).catch((error) => {
10
+ console.error("[Toggly] Auto-initialization failed:", error);
11
+ });
12
+ } else {
13
+ console.warn(
14
+ "[Toggly] No configuration found on window.__TOGGLY_CONFIG__. Make sure the Toggly integration is properly configured in astro.config.mjs"
15
+ );
16
+ }
17
+ }
18
+ export {
19
+ initTogglyClient
20
+ };
21
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/setup.ts"],"sourcesContent":["/**\n * Toggly Client Setup\n * \n * Auto-initialization script for client-side Toggly\n */\n\nimport { initTogglyClient } from './store.js';\nimport type { TogglyConfig } from '../types/index.js';\n\n/**\n * Auto-initialize Toggly if configuration is available on window\n * Only runs in browser environment\n */\nif (typeof globalThis !== 'undefined' && typeof (globalThis as any).window !== 'undefined') {\n const config = (globalThis as any).window.__TOGGLY_CONFIG__ as TogglyConfig | undefined;\n\n if (config) {\n // Initialize client with config from integration\n initTogglyClient(config).catch((error: Error) => {\n console.error('[Toggly] Auto-initialization failed:', error);\n });\n } else {\n console.warn(\n '[Toggly] No configuration found on window.__TOGGLY_CONFIG__. ' +\n 'Make sure the Toggly integration is properly configured in astro.config.mjs'\n );\n }\n}\n\n// Export for manual initialization\nexport { initTogglyClient };\n\n\n"],"mappings":";;;;;AAaA,IAAI,OAAO,eAAe,eAAe,OAAQ,WAAmB,WAAW,aAAa;AAC1F,QAAM,SAAU,WAAmB,OAAO;AAE1C,MAAI,QAAQ;AAEV,qBAAiB,MAAM,EAAE,MAAM,CAAC,UAAiB;AAC/C,cAAQ,MAAM,wCAAwC,KAAK;AAAA,IAC7D,CAAC;AAAA,EACH,OAAO;AACL,YAAQ;AAAA,MACN;AAAA,IAEF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,59 @@
1
+ import * as nanostores from 'nanostores';
2
+ import { ReadableAtom } from 'nanostores';
3
+ import { T as TogglyConfig, F as Flags } from '../index-S3g0i0FH.js';
4
+
5
+ /**
6
+ * Atom containing all feature flags
7
+ */
8
+ declare const $flags: nanostores.PreinitializedWritableAtom<Flags> & object;
9
+ /**
10
+ * Atom indicating if flags are loaded and ready
11
+ */
12
+ declare const $isReady: nanostores.PreinitializedWritableAtom<boolean> & object;
13
+ /**
14
+ * Atom containing any error that occurred during initialization
15
+ */
16
+ declare const $error: nanostores.PreinitializedWritableAtom<Error | null> & object;
17
+ /**
18
+ * Initialize Toggly client with configuration
19
+ *
20
+ * @param config - Toggly configuration
21
+ */
22
+ declare function initTogglyClient(config: TogglyConfig): Promise<void>;
23
+ /**
24
+ * Manually refresh feature flags
25
+ */
26
+ declare function refreshFlags(): Promise<void>;
27
+ /**
28
+ * Set user identity for targeting
29
+ *
30
+ * @param identity - User identifier
31
+ */
32
+ declare function setIdentity(identity: string): void;
33
+ /**
34
+ * Clear user identity
35
+ */
36
+ declare function clearIdentity(): void;
37
+ /**
38
+ * Stop automatic refresh interval
39
+ */
40
+ declare function stopRefreshInterval(): void;
41
+ /**
42
+ * Create a computed atom for a specific feature flag
43
+ *
44
+ * @param key - Feature flag key
45
+ * @param defaultValue - Default value if flag not found
46
+ * @returns Readable atom with the flag value
47
+ */
48
+ declare function $flag(key: string, defaultValue?: boolean): ReadableAtom<boolean>;
49
+ /**
50
+ * Create a computed atom that evaluates multiple feature flags
51
+ *
52
+ * @param keys - Array of feature flag keys
53
+ * @param requirement - 'all' or 'any'
54
+ * @param negate - Whether to negate the result
55
+ * @returns Readable atom with the evaluation result
56
+ */
57
+ declare function $gate(keys: string[], requirement?: 'all' | 'any', negate?: boolean): ReadableAtom<boolean>;
58
+
59
+ export { $error, $flag, $flags, $gate, $isReady, clearIdentity, initTogglyClient, refreshFlags, setIdentity, stopRefreshInterval };
@@ -0,0 +1,25 @@
1
+ import {
2
+ $error,
3
+ $flag,
4
+ $flags,
5
+ $gate,
6
+ $isReady,
7
+ clearIdentity,
8
+ initTogglyClient,
9
+ refreshFlags,
10
+ setIdentity,
11
+ stopRefreshInterval
12
+ } from "../chunk-FK633ULF.js";
13
+ export {
14
+ $error,
15
+ $flag,
16
+ $flags,
17
+ $gate,
18
+ $isReady,
19
+ clearIdentity,
20
+ initTogglyClient,
21
+ refreshFlags,
22
+ setIdentity,
23
+ stopRefreshInterval
24
+ };
25
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}