@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.
- package/LICENSE +23 -0
- package/README.md +462 -0
- package/dist/chunk-354E3C57.js +173 -0
- package/dist/chunk-354E3C57.js.map +1 -0
- package/dist/chunk-4UKIT2NP.js +44 -0
- package/dist/chunk-4UKIT2NP.js.map +1 -0
- package/dist/chunk-FK633ULF.js +200 -0
- package/dist/chunk-FK633ULF.js.map +1 -0
- package/dist/chunk-XCJSQJHR.js +74 -0
- package/dist/chunk-XCJSQJHR.js.map +1 -0
- package/dist/chunk-XQGKGTBK.js +161 -0
- package/dist/chunk-XQGKGTBK.js.map +1 -0
- package/dist/client/setup.d.ts +3 -0
- package/dist/client/setup.js +21 -0
- package/dist/client/setup.js.map +1 -0
- package/dist/client/store.d.ts +59 -0
- package/dist/client/store.js +25 -0
- package/dist/client/store.js.map +1 -0
- package/dist/components/Feature.astro +79 -0
- package/dist/components/FeatureClient.astro +144 -0
- package/dist/frameworks/react/Feature.d.ts +86 -0
- package/dist/frameworks/react/Feature.js +14 -0
- package/dist/frameworks/react/Feature.js.map +1 -0
- package/dist/frameworks/react/index.d.ts +3 -0
- package/dist/frameworks/react/index.js +12 -0
- package/dist/frameworks/react/index.js.map +1 -0
- package/dist/frameworks/svelte/Feature.svelte +75 -0
- package/dist/frameworks/svelte/stores.d.ts +54 -0
- package/dist/frameworks/svelte/stores.js +34 -0
- package/dist/frameworks/svelte/stores.js.map +1 -0
- package/dist/frameworks/vue/Feature.vue +93 -0
- package/dist/frameworks/vue/composables.d.ts +56 -0
- package/dist/frameworks/vue/composables.js +39 -0
- package/dist/frameworks/vue/composables.js.map +1 -0
- package/dist/index-S3g0i0FH.d.ts +102 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/index.d.ts +24 -0
- package/dist/integration/index.js +10 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/server/toggly-server.d.ts +53 -0
- package/dist/server/toggly-server.js +9 -0
- package/dist/server/toggly-server.js.map +1 -0
- package/dist/server/utils.d.ts +43 -0
- package/dist/server/utils.js +13 -0
- package/dist/server/utils.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Feature - Server-rendered feature flag component for Astro
|
|
4
|
+
*
|
|
5
|
+
* This component evaluates feature flags on the server side during SSR/SSG.
|
|
6
|
+
* Use this for content that should be evaluated at build time or on each request.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```astro
|
|
10
|
+
* <Feature flag="new-dashboard">
|
|
11
|
+
* <p>This content is only shown when the feature is enabled</p>
|
|
12
|
+
* </Feature>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @example Multiple flags with 'any' requirement
|
|
16
|
+
* ```astro
|
|
17
|
+
* <Feature flags={['feature1', 'feature2']} requirement="any">
|
|
18
|
+
* <p>Shown if at least one feature is enabled</p>
|
|
19
|
+
* </Feature>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example With fallback content
|
|
23
|
+
* ```astro
|
|
24
|
+
* <Feature flag="beta-feature">
|
|
25
|
+
* <p>Beta content</p>
|
|
26
|
+
* <div slot="fallback">
|
|
27
|
+
* <p>Beta feature not available yet</p>
|
|
28
|
+
* </div>
|
|
29
|
+
* </Feature>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
interface Props {
|
|
34
|
+
/** Single feature flag key to check */
|
|
35
|
+
flag?: string;
|
|
36
|
+
/** Multiple feature flag keys to check */
|
|
37
|
+
flags?: string[];
|
|
38
|
+
/** Requirement for multiple flags: 'all' or 'any' (default: 'all') */
|
|
39
|
+
requirement?: 'all' | 'any';
|
|
40
|
+
/** If true, negates the result (default: false) */
|
|
41
|
+
negate?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { flag, flags, requirement = 'all', negate = false } = Astro.props;
|
|
45
|
+
|
|
46
|
+
// Get Toggly client from Astro.locals
|
|
47
|
+
const toggly = Astro.locals.toggly;
|
|
48
|
+
|
|
49
|
+
if (!toggly) {
|
|
50
|
+
console.warn(
|
|
51
|
+
'[Toggly Feature] Client not found in Astro.locals. ' +
|
|
52
|
+
'Make sure the Toggly integration is properly configured.'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Build feature keys array
|
|
57
|
+
const flagKeys: string[] = [];
|
|
58
|
+
if (flag) {
|
|
59
|
+
flagKeys.push(flag);
|
|
60
|
+
}
|
|
61
|
+
if (flags && Array.isArray(flags)) {
|
|
62
|
+
flagKeys.push(...flags);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Evaluate feature gate
|
|
66
|
+
let isEnabled = false;
|
|
67
|
+
if (toggly && flagKeys.length > 0) {
|
|
68
|
+
try {
|
|
69
|
+
isEnabled = await toggly.evaluateGate(flagKeys, requirement, negate);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('[Toggly Feature] Error evaluating feature gate:', error);
|
|
72
|
+
isEnabled = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
{isEnabled ? <slot /> : <slot name="fallback" />}
|
|
78
|
+
|
|
79
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* FeatureClient - Client-side hydrated feature flag component
|
|
4
|
+
*
|
|
5
|
+
* This component evaluates feature flags on the client side using Astro islands.
|
|
6
|
+
* Use this when you need dynamic, client-side feature flag evaluation or when
|
|
7
|
+
* you want to update the UI based on flag changes without a page reload.
|
|
8
|
+
*
|
|
9
|
+
* @example Basic usage with client:load
|
|
10
|
+
* ```astro
|
|
11
|
+
* <FeatureClient flag="new-feature" client="load">
|
|
12
|
+
* <p>This content hydrates and checks the flag on the client</p>
|
|
13
|
+
* </FeatureClient>
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @example With client:visible for lazy loading
|
|
17
|
+
* ```astro
|
|
18
|
+
* <FeatureClient flag="beta-widget" client="visible">
|
|
19
|
+
* <BetaWidget />
|
|
20
|
+
* </FeatureClient>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example With fallback
|
|
24
|
+
* ```astro
|
|
25
|
+
* <FeatureClient flag="premium-feature" client="load">
|
|
26
|
+
* <PremiumContent />
|
|
27
|
+
* <div slot="fallback">
|
|
28
|
+
* <p>Upgrade to access premium features</p>
|
|
29
|
+
* </div>
|
|
30
|
+
* </FeatureClient>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
/** Feature flag key to check */
|
|
36
|
+
flag: string;
|
|
37
|
+
/** Hydration strategy: 'load', 'idle', or 'visible' (default: 'load') */
|
|
38
|
+
client?: 'load' | 'idle' | 'visible';
|
|
39
|
+
/** Requirement for multiple flags: 'all' or 'any' (default: 'all') */
|
|
40
|
+
requirement?: 'all' | 'any';
|
|
41
|
+
/** If true, negates the result (default: false) */
|
|
42
|
+
negate?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { flag, client = 'load', requirement = 'all', negate = false } = Astro.props;
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
<div
|
|
49
|
+
data-toggly-feature={flag}
|
|
50
|
+
data-toggly-requirement={requirement}
|
|
51
|
+
data-toggly-negate={negate}
|
|
52
|
+
data-toggly-hydrate={client}
|
|
53
|
+
class="toggly-feature-client"
|
|
54
|
+
>
|
|
55
|
+
<div data-toggly-content>
|
|
56
|
+
<slot />
|
|
57
|
+
</div>
|
|
58
|
+
<div data-toggly-fallback style="display: none;">
|
|
59
|
+
<slot name="fallback" />
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<script>
|
|
64
|
+
import { $flags, $isReady } from '@ops-ai/astro-feature-flags-toggly/client/store';
|
|
65
|
+
|
|
66
|
+
// Hydrate all FeatureClient components
|
|
67
|
+
function hydrateFeatureClients() {
|
|
68
|
+
const elements = document.querySelectorAll<HTMLElement>('.toggly-feature-client');
|
|
69
|
+
|
|
70
|
+
elements.forEach((element) => {
|
|
71
|
+
const flag = element.dataset.togglyFeature;
|
|
72
|
+
const requirement = (element.dataset.togglyRequirement as 'all' | 'any') || 'all';
|
|
73
|
+
const negate = element.dataset.togglyNegate === 'true';
|
|
74
|
+
|
|
75
|
+
if (!flag) return;
|
|
76
|
+
|
|
77
|
+
const content = element.querySelector<HTMLElement>('[data-toggly-content]');
|
|
78
|
+
const fallback = element.querySelector<HTMLElement>('[data-toggly-fallback]');
|
|
79
|
+
|
|
80
|
+
if (!content || !fallback) return;
|
|
81
|
+
|
|
82
|
+
// Subscribe to flags changes
|
|
83
|
+
const unsubscribe = $flags.subscribe((flags) => {
|
|
84
|
+
// Wait for flags to be ready
|
|
85
|
+
const ready = $isReady.get();
|
|
86
|
+
if (!ready) return;
|
|
87
|
+
|
|
88
|
+
// Evaluate flag
|
|
89
|
+
const flagKeys = flag.split(',').map((k) => k.trim());
|
|
90
|
+
let isEnabled: boolean;
|
|
91
|
+
|
|
92
|
+
if (requirement === 'any') {
|
|
93
|
+
isEnabled = flagKeys.some((key) => flags[key] === true);
|
|
94
|
+
} else {
|
|
95
|
+
isEnabled = flagKeys.every((key) => flags[key] === true);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (negate) {
|
|
99
|
+
isEnabled = !isEnabled;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Show/hide content based on flag
|
|
103
|
+
if (isEnabled) {
|
|
104
|
+
content.style.display = '';
|
|
105
|
+
fallback.style.display = 'none';
|
|
106
|
+
} else {
|
|
107
|
+
content.style.display = 'none';
|
|
108
|
+
fallback.style.display = '';
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Store unsubscribe function for cleanup
|
|
113
|
+
(element as any).__togglyUnsubscribe = unsubscribe;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Hydrate on page load
|
|
118
|
+
if (document.readyState === 'loading') {
|
|
119
|
+
document.addEventListener('DOMContentLoaded', hydrateFeatureClients);
|
|
120
|
+
} else {
|
|
121
|
+
hydrateFeatureClients();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Re-hydrate on navigation (for view transitions)
|
|
125
|
+
document.addEventListener('astro:page-load', hydrateFeatureClients);
|
|
126
|
+
|
|
127
|
+
// Cleanup on navigation
|
|
128
|
+
document.addEventListener('astro:before-swap', () => {
|
|
129
|
+
document.querySelectorAll('.toggly-feature-client').forEach((element) => {
|
|
130
|
+
const unsubscribe = (element as any).__togglyUnsubscribe;
|
|
131
|
+
if (unsubscribe) {
|
|
132
|
+
unsubscribe();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<style>
|
|
139
|
+
.toggly-feature-client {
|
|
140
|
+
display: contents;
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
143
|
+
|
|
144
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface FeatureProps {
|
|
5
|
+
/** Single feature flag key to check */
|
|
6
|
+
flag?: string;
|
|
7
|
+
/** Multiple feature flag keys to check */
|
|
8
|
+
flags?: string[];
|
|
9
|
+
/** Requirement for multiple flags: 'all' or 'any' (default: 'all') */
|
|
10
|
+
requirement?: 'all' | 'any';
|
|
11
|
+
/** If true, negates the result (default: false) */
|
|
12
|
+
negate?: boolean;
|
|
13
|
+
/** Content to render when flag is enabled */
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
/** Content to render when flag is disabled (optional) */
|
|
16
|
+
fallback?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Feature - React component for conditional rendering based on feature flags
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <Feature flag="new-dashboard">
|
|
24
|
+
* <Dashboard />
|
|
25
|
+
* </Feature>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example Multiple flags with 'any' requirement
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <Feature flags={['feature1', 'feature2']} requirement="any">
|
|
31
|
+
* <Content />
|
|
32
|
+
* </Feature>
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example With fallback
|
|
36
|
+
* ```tsx
|
|
37
|
+
* <Feature flag="premium-feature" fallback={<UpgradePrompt />}>
|
|
38
|
+
* <PremiumContent />
|
|
39
|
+
* </Feature>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function Feature({ flag, flags, requirement, negate, children, fallback, }: FeatureProps): react_jsx_runtime.JSX.Element;
|
|
43
|
+
/**
|
|
44
|
+
* Hook to check if a feature flag is enabled
|
|
45
|
+
*
|
|
46
|
+
* @param flagKey - Feature flag key to check
|
|
47
|
+
* @param defaultValue - Default value if flag not found (default: false)
|
|
48
|
+
* @returns Object with enabled state and ready state
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* function MyComponent() {
|
|
53
|
+
* const { enabled, isReady } = useFeatureFlag('new-dashboard');
|
|
54
|
+
*
|
|
55
|
+
* if (!isReady) return <Loading />;
|
|
56
|
+
* if (!enabled) return <OldDashboard />;
|
|
57
|
+
* return <NewDashboard />;
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare function useFeatureFlag(flagKey: string, defaultValue?: boolean): {
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
isReady: boolean;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Hook to check if multiple feature flags are enabled
|
|
67
|
+
*
|
|
68
|
+
* @param flagKeys - Array of feature flag keys to check
|
|
69
|
+
* @param requirement - 'all' or 'any' (default: 'all')
|
|
70
|
+
* @param negate - If true, negates the result (default: false)
|
|
71
|
+
* @returns Object with enabled state and ready state
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* function MyComponent() {
|
|
76
|
+
* const { enabled } = useFeatureGate(['feature1', 'feature2'], 'any');
|
|
77
|
+
* return enabled ? <NewFeatures /> : <OldFeatures />;
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function useFeatureGate(flagKeys: string[], requirement?: 'all' | 'any', negate?: boolean): {
|
|
82
|
+
enabled: boolean;
|
|
83
|
+
isReady: boolean;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export { Feature, type FeatureProps, Feature as default, useFeatureFlag, useFeatureGate };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Feature,
|
|
3
|
+
Feature_default,
|
|
4
|
+
useFeatureFlag,
|
|
5
|
+
useFeatureGate
|
|
6
|
+
} from "../../chunk-XCJSQJHR.js";
|
|
7
|
+
import "../../chunk-FK633ULF.js";
|
|
8
|
+
export {
|
|
9
|
+
Feature,
|
|
10
|
+
Feature_default as default,
|
|
11
|
+
useFeatureFlag,
|
|
12
|
+
useFeatureGate
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=Feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Svelte Feature Component for Astro Islands
|
|
4
|
+
*
|
|
5
|
+
* Use this component in Svelte islands within Astro for client-side feature flagging.
|
|
6
|
+
* Integrates with nanostores for reactive state management.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <Feature flag="new-dashboard">
|
|
11
|
+
* <Dashboard />
|
|
12
|
+
* </Feature>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @example Multiple flags with 'any' requirement
|
|
16
|
+
* ```svelte
|
|
17
|
+
* <Feature flags={['feature1', 'feature2']} requirement="any">
|
|
18
|
+
* <Content />
|
|
19
|
+
* </Feature>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example With fallback
|
|
23
|
+
* ```svelte
|
|
24
|
+
* <Feature flag="premium-feature">
|
|
25
|
+
* <PremiumContent />
|
|
26
|
+
* <svelte:fragment slot="fallback">
|
|
27
|
+
* <UpgradePrompt />
|
|
28
|
+
* </svelte:fragment>
|
|
29
|
+
* </Feature>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { $flags, $isReady } from '../../client/store.js';
|
|
34
|
+
|
|
35
|
+
export let flag: string | undefined = undefined;
|
|
36
|
+
export let flags: string[] | undefined = undefined;
|
|
37
|
+
export let requirement: 'all' | 'any' = 'all';
|
|
38
|
+
export let negate: boolean = false;
|
|
39
|
+
|
|
40
|
+
// Reactively compute whether the feature is enabled
|
|
41
|
+
$: flagKeys = (() => {
|
|
42
|
+
const keys: string[] = [];
|
|
43
|
+
if (flag) keys.push(flag);
|
|
44
|
+
if (flags && Array.isArray(flags)) keys.push(...flags);
|
|
45
|
+
return keys;
|
|
46
|
+
})();
|
|
47
|
+
|
|
48
|
+
$: isEnabled = (() => {
|
|
49
|
+
if (flagKeys.length === 0) {
|
|
50
|
+
return !negate;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let enabled: boolean;
|
|
54
|
+
|
|
55
|
+
if (requirement === 'any') {
|
|
56
|
+
enabled = flagKeys.some((key) => $flags[key] === true);
|
|
57
|
+
} else {
|
|
58
|
+
enabled = flagKeys.every((key) => $flags[key] === true);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (negate) {
|
|
62
|
+
enabled = !enabled;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return enabled;
|
|
66
|
+
})();
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
{#if $isReady && isEnabled}
|
|
70
|
+
<slot />
|
|
71
|
+
{:else}
|
|
72
|
+
<slot name="fallback" />
|
|
73
|
+
{/if}
|
|
74
|
+
|
|
75
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as svelte_store from 'svelte/store';
|
|
2
|
+
export { $flags as flags, $isReady as isReady } from '../../client/store.js';
|
|
3
|
+
import 'nanostores';
|
|
4
|
+
import '../../index-S3g0i0FH.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a derived store for a specific feature flag
|
|
8
|
+
*
|
|
9
|
+
* @param flagKey - Feature flag key to check
|
|
10
|
+
* @param defaultValue - Default value if flag not found (default: false)
|
|
11
|
+
* @returns Svelte-compatible derived store
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```svelte
|
|
15
|
+
* <script>
|
|
16
|
+
* import { featureFlag } from '@ops-ai/astro-feature-flags-toggly/svelte';
|
|
17
|
+
*
|
|
18
|
+
* const newDashboard = featureFlag('new-dashboard');
|
|
19
|
+
* </script>
|
|
20
|
+
*
|
|
21
|
+
* {#if $newDashboard}
|
|
22
|
+
* <NewDashboard />
|
|
23
|
+
* {:else}
|
|
24
|
+
* <OldDashboard />
|
|
25
|
+
* {/if}
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function featureFlag(flagKey: string, defaultValue?: boolean): svelte_store.Readable<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* Create a derived store that evaluates multiple feature flags
|
|
31
|
+
*
|
|
32
|
+
* @param flagKeys - Array of feature flag keys to check
|
|
33
|
+
* @param requirement - 'all' or 'any' (default: 'all')
|
|
34
|
+
* @param negate - If true, negates the result (default: false)
|
|
35
|
+
* @returns Svelte-compatible derived store
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```svelte
|
|
39
|
+
* <script>
|
|
40
|
+
* import { featureGate } from '@ops-ai/astro-feature-flags-toggly/svelte';
|
|
41
|
+
*
|
|
42
|
+
* const hasAnyFeature = featureGate(['feature1', 'feature2'], 'any');
|
|
43
|
+
* </script>
|
|
44
|
+
*
|
|
45
|
+
* {#if $hasAnyFeature}
|
|
46
|
+
* <NewFeatures />
|
|
47
|
+
* {:else}
|
|
48
|
+
* <OldFeatures />
|
|
49
|
+
* {/if}
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare function featureGate(flagKeys: string[], requirement?: 'all' | 'any', negate?: boolean): svelte_store.Readable<boolean>;
|
|
53
|
+
|
|
54
|
+
export { featureFlag, featureGate };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
$flags,
|
|
3
|
+
$isReady
|
|
4
|
+
} from "../../chunk-FK633ULF.js";
|
|
5
|
+
|
|
6
|
+
// src/frameworks/svelte/stores.ts
|
|
7
|
+
import { derived } from "svelte/store";
|
|
8
|
+
function featureFlag(flagKey, defaultValue = false) {
|
|
9
|
+
return derived($flags, ($flags2) => $flags2[flagKey] ?? defaultValue);
|
|
10
|
+
}
|
|
11
|
+
function featureGate(flagKeys, requirement = "all", negate = false) {
|
|
12
|
+
return derived($flags, ($flags2) => {
|
|
13
|
+
if (flagKeys.length === 0) {
|
|
14
|
+
return !negate;
|
|
15
|
+
}
|
|
16
|
+
let isEnabled;
|
|
17
|
+
if (requirement === "any") {
|
|
18
|
+
isEnabled = flagKeys.some((key) => $flags2[key] === true);
|
|
19
|
+
} else {
|
|
20
|
+
isEnabled = flagKeys.every((key) => $flags2[key] === true);
|
|
21
|
+
}
|
|
22
|
+
if (negate) {
|
|
23
|
+
isEnabled = !isEnabled;
|
|
24
|
+
}
|
|
25
|
+
return isEnabled;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
featureFlag,
|
|
30
|
+
featureGate,
|
|
31
|
+
$flags as flags,
|
|
32
|
+
$isReady as isReady
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=stores.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/frameworks/svelte/stores.ts"],"sourcesContent":["/**\n * Svelte-specific store utilities for Toggly\n * \n * Re-exports nanostores for easy use in Svelte components\n */\n\nimport { derived } from 'svelte/store';\nimport { $flags, $isReady } from '../../client/store.js';\n\n/**\n * Create a derived store for a specific feature flag\n * \n * @param flagKey - Feature flag key to check\n * @param defaultValue - Default value if flag not found (default: false)\n * @returns Svelte-compatible derived store\n * \n * @example\n * ```svelte\n * <script>\n * import { featureFlag } from '@ops-ai/astro-feature-flags-toggly/svelte';\n * \n * const newDashboard = featureFlag('new-dashboard');\n * </script>\n * \n * {#if $newDashboard}\n * <NewDashboard />\n * {:else}\n * <OldDashboard />\n * {/if}\n * ```\n */\nexport function featureFlag(flagKey: string, defaultValue: boolean = false) {\n return derived($flags, ($flags) => $flags[flagKey] ?? defaultValue);\n}\n\n/**\n * Create a derived store that evaluates multiple feature flags\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 Svelte-compatible derived store\n * \n * @example\n * ```svelte\n * <script>\n * import { featureGate } from '@ops-ai/astro-feature-flags-toggly/svelte';\n * \n * const hasAnyFeature = featureGate(['feature1', 'feature2'], 'any');\n * </script>\n * \n * {#if $hasAnyFeature}\n * <NewFeatures />\n * {:else}\n * <OldFeatures />\n * {/if}\n * ```\n */\nexport function featureGate(\n flagKeys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n) {\n return derived($flags, ($flags) => {\n if (flagKeys.length === 0) {\n return !negate;\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 isEnabled;\n });\n}\n\n// Re-export base stores for direct use\nexport { $flags as flags, $isReady as isReady };\n\n\n"],"mappings":";;;;;;AAMA,SAAS,eAAe;AAyBjB,SAAS,YAAY,SAAiB,eAAwB,OAAO;AAC1E,SAAO,QAAQ,QAAQ,CAACA,YAAWA,QAAO,OAAO,KAAK,YAAY;AACpE;AAyBO,SAAS,YACd,UACA,cAA6B,OAC7B,SAAkB,OAClB;AACA,SAAO,QAAQ,QAAQ,CAACA,YAAW;AACjC,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO,CAAC;AAAA,IACV;AAEA,QAAI;AAEJ,QAAI,gBAAgB,OAAO;AACzB,kBAAY,SAAS,KAAK,CAAC,QAAQA,QAAO,GAAG,MAAM,IAAI;AAAA,IACzD,OAAO;AACL,kBAAY,SAAS,MAAM,CAAC,QAAQA,QAAO,GAAG,MAAM,IAAI;AAAA,IAC1D;AAEA,QAAI,QAAQ;AACV,kBAAY,CAAC;AAAA,IACf;AAEA,WAAO;AAAA,EACT,CAAC;AACH;","names":["$flags"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<slot v-if="isReady && isEnabled" />
|
|
3
|
+
<slot v-else name="fallback" />
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script setup lang="ts">
|
|
7
|
+
/**
|
|
8
|
+
* Vue Feature Component for Astro Islands
|
|
9
|
+
*
|
|
10
|
+
* Use this component in Vue islands within Astro for client-side feature flagging.
|
|
11
|
+
* Integrates with nanostores for reactive state management.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```vue
|
|
15
|
+
* <Feature flag="new-dashboard">
|
|
16
|
+
* <Dashboard />
|
|
17
|
+
* </Feature>
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example Multiple flags with 'any' requirement
|
|
21
|
+
* ```vue
|
|
22
|
+
* <Feature :flags="['feature1', 'feature2']" requirement="any">
|
|
23
|
+
* <Content />
|
|
24
|
+
* </Feature>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example With fallback
|
|
28
|
+
* ```vue
|
|
29
|
+
* <Feature flag="premium-feature">
|
|
30
|
+
* <PremiumContent />
|
|
31
|
+
* <template #fallback>
|
|
32
|
+
* <UpgradePrompt />
|
|
33
|
+
* </template>
|
|
34
|
+
* </Feature>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { computed } from 'vue';
|
|
39
|
+
import { useStore } from '@nanostores/vue';
|
|
40
|
+
import { $flags, $isReady } from '../../client/store.js';
|
|
41
|
+
|
|
42
|
+
export interface FeatureProps {
|
|
43
|
+
/** Single feature flag key to check */
|
|
44
|
+
flag?: string;
|
|
45
|
+
/** Multiple feature flag keys to check */
|
|
46
|
+
flags?: string[];
|
|
47
|
+
/** Requirement for multiple flags: 'all' or 'any' (default: 'all') */
|
|
48
|
+
requirement?: 'all' | 'any';
|
|
49
|
+
/** If true, negates the result (default: false) */
|
|
50
|
+
negate?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const props = withDefaults(defineProps<FeatureProps>(), {
|
|
54
|
+
requirement: 'all',
|
|
55
|
+
negate: false,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const allFlags = useStore($flags);
|
|
59
|
+
const isReady = useStore($isReady);
|
|
60
|
+
|
|
61
|
+
const isEnabled = computed(() => {
|
|
62
|
+
// Build flag keys array
|
|
63
|
+
const flagKeys: string[] = [];
|
|
64
|
+
if (props.flag) {
|
|
65
|
+
flagKeys.push(props.flag);
|
|
66
|
+
}
|
|
67
|
+
if (props.flags && Array.isArray(props.flags)) {
|
|
68
|
+
flagKeys.push(...props.flags);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// No flags specified
|
|
72
|
+
if (flagKeys.length === 0) {
|
|
73
|
+
return !props.negate;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Evaluate flags
|
|
77
|
+
let enabled: boolean;
|
|
78
|
+
|
|
79
|
+
if (props.requirement === 'any') {
|
|
80
|
+
enabled = flagKeys.some((key) => allFlags.value[key] === true);
|
|
81
|
+
} else {
|
|
82
|
+
enabled = flagKeys.every((key) => allFlags.value[key] === true);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (props.negate) {
|
|
86
|
+
enabled = !enabled;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return enabled;
|
|
90
|
+
});
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vue Composables for Toggly
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook to check if a feature flag is enabled
|
|
9
|
+
*
|
|
10
|
+
* @param flagKey - Feature flag key to check
|
|
11
|
+
* @param defaultValue - Default value if flag not found (default: false)
|
|
12
|
+
* @returns Object with enabled state and ready state
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```vue
|
|
16
|
+
* <script setup>
|
|
17
|
+
* const { enabled, isReady } = useFeatureFlag('new-dashboard');
|
|
18
|
+
* </script>
|
|
19
|
+
*
|
|
20
|
+
* <template>
|
|
21
|
+
* <Loading v-if="!isReady" />
|
|
22
|
+
* <NewDashboard v-else-if="enabled" />
|
|
23
|
+
* <OldDashboard v-else />
|
|
24
|
+
* </template>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare function useFeatureFlag(flagKey: string, defaultValue?: boolean): {
|
|
28
|
+
enabled: Readonly<Ref<boolean>>;
|
|
29
|
+
isReady: Readonly<Ref<boolean>>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Hook to check if multiple feature flags are enabled
|
|
33
|
+
*
|
|
34
|
+
* @param flagKeys - Array of feature flag keys to check
|
|
35
|
+
* @param requirement - 'all' or 'any' (default: 'all')
|
|
36
|
+
* @param negate - If true, negates the result (default: false)
|
|
37
|
+
* @returns Object with enabled state and ready state
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```vue
|
|
41
|
+
* <script setup>
|
|
42
|
+
* const { enabled } = useFeatureGate(['feature1', 'feature2'], 'any');
|
|
43
|
+
* </script>
|
|
44
|
+
*
|
|
45
|
+
* <template>
|
|
46
|
+
* <NewFeatures v-if="enabled" />
|
|
47
|
+
* <OldFeatures v-else />
|
|
48
|
+
* </template>
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare function useFeatureGate(flagKeys: string[], requirement?: 'all' | 'any', negate?: boolean): {
|
|
52
|
+
enabled: Readonly<Ref<boolean>>;
|
|
53
|
+
isReady: Readonly<Ref<boolean>>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { useFeatureFlag, useFeatureGate };
|