@rolloutly/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -0
- package/dist/index.cjs +153 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +146 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# @rolloutly/react
|
|
2
|
+
|
|
3
|
+
React SDK for [Rolloutly](https://rolloutly.com) feature flags.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rolloutly/react
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @rolloutly/react
|
|
11
|
+
# or
|
|
12
|
+
yarn add @rolloutly/react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { RolloutlyProvider, useFlag, useFlagEnabled } from '@rolloutly/react';
|
|
19
|
+
|
|
20
|
+
// 1. Wrap your app with the provider
|
|
21
|
+
function App() {
|
|
22
|
+
return (
|
|
23
|
+
<RolloutlyProvider token="rly_your_project_production_xxx">
|
|
24
|
+
<MyApp />
|
|
25
|
+
</RolloutlyProvider>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 2. Use hooks in your components
|
|
30
|
+
function MyFeature() {
|
|
31
|
+
const isNewCheckout = useFlagEnabled('new-checkout');
|
|
32
|
+
const rateLimit = useFlag<number>('api-rate-limit');
|
|
33
|
+
|
|
34
|
+
if (isNewCheckout) {
|
|
35
|
+
return <NewCheckout rateLimit={rateLimit} />;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return <OldCheckout />;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Provider Configuration
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<RolloutlyProvider
|
|
46
|
+
// Required: Your SDK token
|
|
47
|
+
token="rly_xxx"
|
|
48
|
+
|
|
49
|
+
// Optional: Custom API URL
|
|
50
|
+
baseUrl="https://your-instance.com"
|
|
51
|
+
|
|
52
|
+
// Optional: Enable real-time updates (default: true)
|
|
53
|
+
realtimeEnabled={true}
|
|
54
|
+
|
|
55
|
+
// Optional: Default values before flags load
|
|
56
|
+
defaultFlags={{
|
|
57
|
+
'new-feature': false,
|
|
58
|
+
'api-rate-limit': 100,
|
|
59
|
+
}}
|
|
60
|
+
|
|
61
|
+
// Optional: Enable debug logging
|
|
62
|
+
debug={false}
|
|
63
|
+
|
|
64
|
+
// Optional: Component to show while loading
|
|
65
|
+
loadingComponent={<Spinner />}
|
|
66
|
+
>
|
|
67
|
+
<App />
|
|
68
|
+
</RolloutlyProvider>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Hooks
|
|
72
|
+
|
|
73
|
+
### `useFlag<T>(key: string): T | undefined`
|
|
74
|
+
|
|
75
|
+
Get a single flag value with real-time updates.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
const rateLimit = useFlag<number>('api-rate-limit');
|
|
79
|
+
const config = useFlag<{ maxUsers: number }>('app-config');
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `useFlagEnabled(key: string): boolean`
|
|
83
|
+
|
|
84
|
+
Check if a boolean flag is enabled. Returns `false` if the flag doesn't exist or is disabled.
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
const showBanner = useFlagEnabled('show-banner');
|
|
88
|
+
|
|
89
|
+
if (showBanner) {
|
|
90
|
+
return <PromoBanner />;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `useFlags(): Record<string, FlagValue>`
|
|
95
|
+
|
|
96
|
+
Get all flags as a key-value object.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
const flags = useFlags();
|
|
100
|
+
console.log(flags['my-feature']); // true
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `useRolloutly(): RolloutlyContextValue`
|
|
104
|
+
|
|
105
|
+
Get the Rolloutly context including loading and error states.
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
const { isLoading, isError, error, getFlag, isEnabled } = useRolloutly();
|
|
109
|
+
|
|
110
|
+
if (isLoading) return <Spinner />;
|
|
111
|
+
if (isError) return <Error message={error?.message} />;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `useRolloutlyClient(): RolloutlyClient`
|
|
115
|
+
|
|
116
|
+
Get direct access to the Rolloutly client instance.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
const client = useRolloutlyClient();
|
|
120
|
+
const allFlags = client.getFlags();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## TypeScript
|
|
124
|
+
|
|
125
|
+
All hooks are fully typed. Use generics for type-safe flag values:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
// Boolean flag
|
|
129
|
+
const isEnabled = useFlagEnabled('feature');
|
|
130
|
+
|
|
131
|
+
// Typed flag values
|
|
132
|
+
const limit = useFlag<number>('rate-limit');
|
|
133
|
+
const config = useFlag<{ theme: string; maxItems: number }>('app-config');
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Server-Side Rendering (Next.js)
|
|
137
|
+
|
|
138
|
+
The provider includes `'use client'` directive and works with Next.js App Router. For server components, fetch flags server-side using `@rolloutly/core`:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
// app/layout.tsx
|
|
142
|
+
import { RolloutlyProvider } from '@rolloutly/react';
|
|
143
|
+
|
|
144
|
+
export default function RootLayout({ children }) {
|
|
145
|
+
return (
|
|
146
|
+
<html>
|
|
147
|
+
<body>
|
|
148
|
+
<RolloutlyProvider token={process.env.NEXT_PUBLIC_ROLLOUTLY_TOKEN!}>
|
|
149
|
+
{children}
|
|
150
|
+
</RolloutlyProvider>
|
|
151
|
+
</body>
|
|
152
|
+
</html>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@rolloutly/core');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/provider.tsx
|
|
8
|
+
var RolloutlyContext = react.createContext({
|
|
9
|
+
client: null,
|
|
10
|
+
isLoading: true,
|
|
11
|
+
isError: false,
|
|
12
|
+
error: null
|
|
13
|
+
});
|
|
14
|
+
function RolloutlyProvider({
|
|
15
|
+
token,
|
|
16
|
+
children,
|
|
17
|
+
baseUrl,
|
|
18
|
+
realtimeEnabled = true,
|
|
19
|
+
defaultFlags,
|
|
20
|
+
debug = false,
|
|
21
|
+
loadingComponent
|
|
22
|
+
}) {
|
|
23
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
24
|
+
const [isError, setIsError] = react.useState(false);
|
|
25
|
+
const [error, setError] = react.useState(null);
|
|
26
|
+
const client = react.useMemo(() => {
|
|
27
|
+
return new core.RolloutlyClient({
|
|
28
|
+
token,
|
|
29
|
+
baseUrl,
|
|
30
|
+
realtimeEnabled,
|
|
31
|
+
defaultFlags,
|
|
32
|
+
debug
|
|
33
|
+
});
|
|
34
|
+
}, [token, baseUrl, realtimeEnabled, defaultFlags, debug]);
|
|
35
|
+
react.useEffect(() => {
|
|
36
|
+
let mounted = true;
|
|
37
|
+
client.waitForInit().then(() => {
|
|
38
|
+
if (mounted) {
|
|
39
|
+
setIsLoading(false);
|
|
40
|
+
}
|
|
41
|
+
}).catch((err) => {
|
|
42
|
+
if (mounted) {
|
|
43
|
+
setIsError(true);
|
|
44
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
45
|
+
setIsLoading(false);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
mounted = false;
|
|
50
|
+
client.close();
|
|
51
|
+
};
|
|
52
|
+
}, [client]);
|
|
53
|
+
const contextValue = react.useMemo(
|
|
54
|
+
() => ({
|
|
55
|
+
client,
|
|
56
|
+
isLoading,
|
|
57
|
+
isError,
|
|
58
|
+
error
|
|
59
|
+
}),
|
|
60
|
+
[client, isLoading, isError, error]
|
|
61
|
+
);
|
|
62
|
+
if (isLoading && loadingComponent) {
|
|
63
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent });
|
|
64
|
+
}
|
|
65
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RolloutlyContext.Provider, { value: contextValue, children });
|
|
66
|
+
}
|
|
67
|
+
function useRolloutlyClient() {
|
|
68
|
+
const { client } = react.useContext(RolloutlyContext);
|
|
69
|
+
if (!client) {
|
|
70
|
+
throw new Error("useRolloutlyClient must be used within RolloutlyProvider");
|
|
71
|
+
}
|
|
72
|
+
return client;
|
|
73
|
+
}
|
|
74
|
+
function useRolloutly() {
|
|
75
|
+
const { client, isLoading, isError, error } = react.useContext(RolloutlyContext);
|
|
76
|
+
if (!client) {
|
|
77
|
+
throw new Error("useRolloutly must be used within RolloutlyProvider");
|
|
78
|
+
}
|
|
79
|
+
const getFlag = react.useCallback(
|
|
80
|
+
(key) => {
|
|
81
|
+
return client.getFlag(key);
|
|
82
|
+
},
|
|
83
|
+
[client]
|
|
84
|
+
);
|
|
85
|
+
const isEnabled = react.useCallback(
|
|
86
|
+
(key) => {
|
|
87
|
+
return client.isEnabled(key);
|
|
88
|
+
},
|
|
89
|
+
[client]
|
|
90
|
+
);
|
|
91
|
+
return {
|
|
92
|
+
isLoading,
|
|
93
|
+
isError,
|
|
94
|
+
error,
|
|
95
|
+
getFlag,
|
|
96
|
+
isEnabled
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function useFlag(key) {
|
|
100
|
+
const client = useRolloutlyClient();
|
|
101
|
+
const getSnapshot = react.useCallback(() => {
|
|
102
|
+
return client.getFlag(key);
|
|
103
|
+
}, [client, key]);
|
|
104
|
+
const subscribe = react.useCallback(
|
|
105
|
+
(onStoreChange) => {
|
|
106
|
+
return client.subscribe(onStoreChange);
|
|
107
|
+
},
|
|
108
|
+
[client]
|
|
109
|
+
);
|
|
110
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
111
|
+
}
|
|
112
|
+
function useFlagEnabled(key) {
|
|
113
|
+
const client = useRolloutlyClient();
|
|
114
|
+
const getSnapshot = react.useCallback(() => {
|
|
115
|
+
return client.isEnabled(key);
|
|
116
|
+
}, [client, key]);
|
|
117
|
+
const subscribe = react.useCallback(
|
|
118
|
+
(onStoreChange) => {
|
|
119
|
+
return client.subscribe(onStoreChange);
|
|
120
|
+
},
|
|
121
|
+
[client]
|
|
122
|
+
);
|
|
123
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
124
|
+
}
|
|
125
|
+
function useFlags() {
|
|
126
|
+
const client = useRolloutlyClient();
|
|
127
|
+
const getSnapshot = react.useCallback(() => {
|
|
128
|
+
const flags = client.getFlags();
|
|
129
|
+
return Object.entries(flags).reduce(
|
|
130
|
+
(acc, [key, flag]) => {
|
|
131
|
+
acc[key] = flag.enabled ? flag.value : void 0;
|
|
132
|
+
return acc;
|
|
133
|
+
},
|
|
134
|
+
{}
|
|
135
|
+
);
|
|
136
|
+
}, [client]);
|
|
137
|
+
const subscribe = react.useCallback(
|
|
138
|
+
(onStoreChange) => {
|
|
139
|
+
return client.subscribe(onStoreChange);
|
|
140
|
+
},
|
|
141
|
+
[client]
|
|
142
|
+
);
|
|
143
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
exports.RolloutlyProvider = RolloutlyProvider;
|
|
147
|
+
exports.useFlag = useFlag;
|
|
148
|
+
exports.useFlagEnabled = useFlagEnabled;
|
|
149
|
+
exports.useFlags = useFlags;
|
|
150
|
+
exports.useRolloutly = useRolloutly;
|
|
151
|
+
exports.useRolloutlyClient = useRolloutlyClient;
|
|
152
|
+
//# sourceMappingURL=index.cjs.map
|
|
153
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx"],"names":["createContext","useState","useMemo","RolloutlyClient","useEffect","useContext","useCallback","useSyncExternalStore"],"mappings":"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","file":"index.cjs","sourcesContent":["'use client';\n\nimport { RolloutlyClient, type FlagValue } from '@rolloutly/core';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from 'react';\n\nimport type { RolloutlyContextValue, RolloutlyProviderProps } from './types';\n\nconst RolloutlyContext = createContext<{\n client: RolloutlyClient | null;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n}>({\n client: null,\n isLoading: true,\n isError: false,\n error: null,\n});\n\n/**\n * Provider component that initializes Rolloutly and provides context\n */\nexport function RolloutlyProvider({\n token,\n children,\n baseUrl,\n realtimeEnabled = true,\n defaultFlags,\n debug = false,\n loadingComponent,\n}: RolloutlyProviderProps): JSX.Element {\n const [isLoading, setIsLoading] = useState(true);\n const [isError, setIsError] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const client = useMemo(() => {\n return new RolloutlyClient({\n token,\n baseUrl,\n realtimeEnabled,\n defaultFlags,\n debug,\n });\n }, [token, baseUrl, realtimeEnabled, defaultFlags, debug]);\n\n useEffect(() => {\n let mounted = true;\n\n client\n .waitForInit()\n .then(() => {\n if (mounted) {\n setIsLoading(false);\n }\n })\n .catch((err) => {\n if (mounted) {\n setIsError(true);\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n });\n\n return () => {\n mounted = false;\n client.close();\n };\n }, [client]);\n\n const contextValue = useMemo(\n () => ({\n client,\n isLoading,\n isError,\n error,\n }),\n [client, isLoading, isError, error],\n );\n\n if (isLoading && loadingComponent) {\n return <>{loadingComponent}</>;\n }\n\n return (\n <RolloutlyContext.Provider value={contextValue}>\n {children}\n </RolloutlyContext.Provider>\n );\n}\n\n/**\n * Hook to access the Rolloutly client directly\n */\nexport function useRolloutlyClient(): RolloutlyClient {\n const { client } = useContext(RolloutlyContext);\n\n if (!client) {\n throw new Error('useRolloutlyClient must be used within RolloutlyProvider');\n }\n\n return client;\n}\n\n/**\n * Hook to get Rolloutly status\n */\nexport function useRolloutly(): RolloutlyContextValue {\n const { client, isLoading, isError, error } = useContext(RolloutlyContext);\n\n if (!client) {\n throw new Error('useRolloutly must be used within RolloutlyProvider');\n }\n\n const getFlag = useCallback(\n <T extends FlagValue = FlagValue>(key: string): T | undefined => {\n return client.getFlag<T>(key);\n },\n [client],\n );\n\n const isEnabled = useCallback(\n (key: string): boolean => {\n return client.isEnabled(key);\n },\n [client],\n );\n\n return {\n isLoading,\n isError,\n error,\n getFlag,\n isEnabled,\n };\n}\n\n/**\n * Hook to get a single flag value with real-time updates\n */\nexport function useFlag<T extends FlagValue = FlagValue>(\n key: string,\n): T | undefined {\n const client = useRolloutlyClient();\n\n const getSnapshot = useCallback((): T | undefined => {\n return client.getFlag<T>(key);\n }, [client, key]);\n\n const subscribe = useCallback(\n (onStoreChange: () => void): (() => void) => {\n return client.subscribe(onStoreChange);\n },\n [client],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to check if a boolean flag is enabled with real-time updates\n */\nexport function useFlagEnabled(key: string): boolean {\n const client = useRolloutlyClient();\n\n const getSnapshot = useCallback((): boolean => {\n return client.isEnabled(key);\n }, [client, key]);\n\n const subscribe = useCallback(\n (onStoreChange: () => void): (() => void) => {\n return client.subscribe(onStoreChange);\n },\n [client],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to get all flags with real-time updates\n */\nexport function useFlags(): Record<string, FlagValue> {\n const client = useRolloutlyClient();\n\n const getSnapshot = useCallback((): Record<string, FlagValue> => {\n const flags = client.getFlags();\n\n return Object.entries(flags).reduce<Record<string, FlagValue>>(\n (acc, [key, flag]) => {\n acc[key] = flag.enabled ? flag.value : undefined!;\n\n return acc;\n },\n {},\n );\n }, [client]);\n\n const subscribe = useCallback(\n (onStoreChange: () => void): (() => void) => {\n return client.subscribe(onStoreChange);\n },\n [client],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { FlagValue, RolloutlyClient } from '@rolloutly/core';
|
|
2
|
+
export { Flag, FlagMap, FlagValue } from '@rolloutly/core';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Props for the RolloutlyProvider component
|
|
7
|
+
*/
|
|
8
|
+
type RolloutlyProviderProps = {
|
|
9
|
+
/** SDK token */
|
|
10
|
+
token: string;
|
|
11
|
+
/** Child components */
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
/** Base URL for the API */
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
/** Enable real-time updates */
|
|
16
|
+
realtimeEnabled?: boolean;
|
|
17
|
+
/** Default flag values */
|
|
18
|
+
defaultFlags?: Record<string, FlagValue>;
|
|
19
|
+
/** Enable debug logging */
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
/** Component to show while loading */
|
|
22
|
+
loadingComponent?: ReactNode;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Rolloutly context value
|
|
26
|
+
*/
|
|
27
|
+
type RolloutlyContextValue = {
|
|
28
|
+
/** Whether the client is still loading */
|
|
29
|
+
isLoading: boolean;
|
|
30
|
+
/** Whether there was an error */
|
|
31
|
+
isError: boolean;
|
|
32
|
+
/** The error if any */
|
|
33
|
+
error: Error | null;
|
|
34
|
+
/** Get a flag value */
|
|
35
|
+
getFlag: <T extends FlagValue = FlagValue>(key: string) => T | undefined;
|
|
36
|
+
/** Check if a boolean flag is enabled */
|
|
37
|
+
isEnabled: (key: string) => boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Provider component that initializes Rolloutly and provides context
|
|
42
|
+
*/
|
|
43
|
+
declare function RolloutlyProvider({ token, children, baseUrl, realtimeEnabled, defaultFlags, debug, loadingComponent, }: RolloutlyProviderProps): JSX.Element;
|
|
44
|
+
/**
|
|
45
|
+
* Hook to access the Rolloutly client directly
|
|
46
|
+
*/
|
|
47
|
+
declare function useRolloutlyClient(): RolloutlyClient;
|
|
48
|
+
/**
|
|
49
|
+
* Hook to get Rolloutly status
|
|
50
|
+
*/
|
|
51
|
+
declare function useRolloutly(): RolloutlyContextValue;
|
|
52
|
+
/**
|
|
53
|
+
* Hook to get a single flag value with real-time updates
|
|
54
|
+
*/
|
|
55
|
+
declare function useFlag<T extends FlagValue = FlagValue>(key: string): T | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Hook to check if a boolean flag is enabled with real-time updates
|
|
58
|
+
*/
|
|
59
|
+
declare function useFlagEnabled(key: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Hook to get all flags with real-time updates
|
|
62
|
+
*/
|
|
63
|
+
declare function useFlags(): Record<string, FlagValue>;
|
|
64
|
+
|
|
65
|
+
export { type RolloutlyContextValue, RolloutlyProvider, type RolloutlyProviderProps, useFlag, useFlagEnabled, useFlags, useRolloutly, useRolloutlyClient };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { FlagValue, RolloutlyClient } from '@rolloutly/core';
|
|
2
|
+
export { Flag, FlagMap, FlagValue } from '@rolloutly/core';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Props for the RolloutlyProvider component
|
|
7
|
+
*/
|
|
8
|
+
type RolloutlyProviderProps = {
|
|
9
|
+
/** SDK token */
|
|
10
|
+
token: string;
|
|
11
|
+
/** Child components */
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
/** Base URL for the API */
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
/** Enable real-time updates */
|
|
16
|
+
realtimeEnabled?: boolean;
|
|
17
|
+
/** Default flag values */
|
|
18
|
+
defaultFlags?: Record<string, FlagValue>;
|
|
19
|
+
/** Enable debug logging */
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
/** Component to show while loading */
|
|
22
|
+
loadingComponent?: ReactNode;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Rolloutly context value
|
|
26
|
+
*/
|
|
27
|
+
type RolloutlyContextValue = {
|
|
28
|
+
/** Whether the client is still loading */
|
|
29
|
+
isLoading: boolean;
|
|
30
|
+
/** Whether there was an error */
|
|
31
|
+
isError: boolean;
|
|
32
|
+
/** The error if any */
|
|
33
|
+
error: Error | null;
|
|
34
|
+
/** Get a flag value */
|
|
35
|
+
getFlag: <T extends FlagValue = FlagValue>(key: string) => T | undefined;
|
|
36
|
+
/** Check if a boolean flag is enabled */
|
|
37
|
+
isEnabled: (key: string) => boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Provider component that initializes Rolloutly and provides context
|
|
42
|
+
*/
|
|
43
|
+
declare function RolloutlyProvider({ token, children, baseUrl, realtimeEnabled, defaultFlags, debug, loadingComponent, }: RolloutlyProviderProps): JSX.Element;
|
|
44
|
+
/**
|
|
45
|
+
* Hook to access the Rolloutly client directly
|
|
46
|
+
*/
|
|
47
|
+
declare function useRolloutlyClient(): RolloutlyClient;
|
|
48
|
+
/**
|
|
49
|
+
* Hook to get Rolloutly status
|
|
50
|
+
*/
|
|
51
|
+
declare function useRolloutly(): RolloutlyContextValue;
|
|
52
|
+
/**
|
|
53
|
+
* Hook to get a single flag value with real-time updates
|
|
54
|
+
*/
|
|
55
|
+
declare function useFlag<T extends FlagValue = FlagValue>(key: string): T | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Hook to check if a boolean flag is enabled with real-time updates
|
|
58
|
+
*/
|
|
59
|
+
declare function useFlagEnabled(key: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Hook to get all flags with real-time updates
|
|
62
|
+
*/
|
|
63
|
+
declare function useFlags(): Record<string, FlagValue>;
|
|
64
|
+
|
|
65
|
+
export { type RolloutlyContextValue, RolloutlyProvider, type RolloutlyProviderProps, useFlag, useFlagEnabled, useFlags, useRolloutly, useRolloutlyClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { RolloutlyClient } from '@rolloutly/core';
|
|
2
|
+
import { createContext, useState, useMemo, useEffect, useContext, useCallback, useSyncExternalStore } from 'react';
|
|
3
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/provider.tsx
|
|
6
|
+
var RolloutlyContext = createContext({
|
|
7
|
+
client: null,
|
|
8
|
+
isLoading: true,
|
|
9
|
+
isError: false,
|
|
10
|
+
error: null
|
|
11
|
+
});
|
|
12
|
+
function RolloutlyProvider({
|
|
13
|
+
token,
|
|
14
|
+
children,
|
|
15
|
+
baseUrl,
|
|
16
|
+
realtimeEnabled = true,
|
|
17
|
+
defaultFlags,
|
|
18
|
+
debug = false,
|
|
19
|
+
loadingComponent
|
|
20
|
+
}) {
|
|
21
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
22
|
+
const [isError, setIsError] = useState(false);
|
|
23
|
+
const [error, setError] = useState(null);
|
|
24
|
+
const client = useMemo(() => {
|
|
25
|
+
return new RolloutlyClient({
|
|
26
|
+
token,
|
|
27
|
+
baseUrl,
|
|
28
|
+
realtimeEnabled,
|
|
29
|
+
defaultFlags,
|
|
30
|
+
debug
|
|
31
|
+
});
|
|
32
|
+
}, [token, baseUrl, realtimeEnabled, defaultFlags, debug]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
let mounted = true;
|
|
35
|
+
client.waitForInit().then(() => {
|
|
36
|
+
if (mounted) {
|
|
37
|
+
setIsLoading(false);
|
|
38
|
+
}
|
|
39
|
+
}).catch((err) => {
|
|
40
|
+
if (mounted) {
|
|
41
|
+
setIsError(true);
|
|
42
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
43
|
+
setIsLoading(false);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return () => {
|
|
47
|
+
mounted = false;
|
|
48
|
+
client.close();
|
|
49
|
+
};
|
|
50
|
+
}, [client]);
|
|
51
|
+
const contextValue = useMemo(
|
|
52
|
+
() => ({
|
|
53
|
+
client,
|
|
54
|
+
isLoading,
|
|
55
|
+
isError,
|
|
56
|
+
error
|
|
57
|
+
}),
|
|
58
|
+
[client, isLoading, isError, error]
|
|
59
|
+
);
|
|
60
|
+
if (isLoading && loadingComponent) {
|
|
61
|
+
return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent });
|
|
62
|
+
}
|
|
63
|
+
return /* @__PURE__ */ jsx(RolloutlyContext.Provider, { value: contextValue, children });
|
|
64
|
+
}
|
|
65
|
+
function useRolloutlyClient() {
|
|
66
|
+
const { client } = useContext(RolloutlyContext);
|
|
67
|
+
if (!client) {
|
|
68
|
+
throw new Error("useRolloutlyClient must be used within RolloutlyProvider");
|
|
69
|
+
}
|
|
70
|
+
return client;
|
|
71
|
+
}
|
|
72
|
+
function useRolloutly() {
|
|
73
|
+
const { client, isLoading, isError, error } = useContext(RolloutlyContext);
|
|
74
|
+
if (!client) {
|
|
75
|
+
throw new Error("useRolloutly must be used within RolloutlyProvider");
|
|
76
|
+
}
|
|
77
|
+
const getFlag = useCallback(
|
|
78
|
+
(key) => {
|
|
79
|
+
return client.getFlag(key);
|
|
80
|
+
},
|
|
81
|
+
[client]
|
|
82
|
+
);
|
|
83
|
+
const isEnabled = useCallback(
|
|
84
|
+
(key) => {
|
|
85
|
+
return client.isEnabled(key);
|
|
86
|
+
},
|
|
87
|
+
[client]
|
|
88
|
+
);
|
|
89
|
+
return {
|
|
90
|
+
isLoading,
|
|
91
|
+
isError,
|
|
92
|
+
error,
|
|
93
|
+
getFlag,
|
|
94
|
+
isEnabled
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function useFlag(key) {
|
|
98
|
+
const client = useRolloutlyClient();
|
|
99
|
+
const getSnapshot = useCallback(() => {
|
|
100
|
+
return client.getFlag(key);
|
|
101
|
+
}, [client, key]);
|
|
102
|
+
const subscribe = useCallback(
|
|
103
|
+
(onStoreChange) => {
|
|
104
|
+
return client.subscribe(onStoreChange);
|
|
105
|
+
},
|
|
106
|
+
[client]
|
|
107
|
+
);
|
|
108
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
109
|
+
}
|
|
110
|
+
function useFlagEnabled(key) {
|
|
111
|
+
const client = useRolloutlyClient();
|
|
112
|
+
const getSnapshot = useCallback(() => {
|
|
113
|
+
return client.isEnabled(key);
|
|
114
|
+
}, [client, key]);
|
|
115
|
+
const subscribe = useCallback(
|
|
116
|
+
(onStoreChange) => {
|
|
117
|
+
return client.subscribe(onStoreChange);
|
|
118
|
+
},
|
|
119
|
+
[client]
|
|
120
|
+
);
|
|
121
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
122
|
+
}
|
|
123
|
+
function useFlags() {
|
|
124
|
+
const client = useRolloutlyClient();
|
|
125
|
+
const getSnapshot = useCallback(() => {
|
|
126
|
+
const flags = client.getFlags();
|
|
127
|
+
return Object.entries(flags).reduce(
|
|
128
|
+
(acc, [key, flag]) => {
|
|
129
|
+
acc[key] = flag.enabled ? flag.value : void 0;
|
|
130
|
+
return acc;
|
|
131
|
+
},
|
|
132
|
+
{}
|
|
133
|
+
);
|
|
134
|
+
}, [client]);
|
|
135
|
+
const subscribe = useCallback(
|
|
136
|
+
(onStoreChange) => {
|
|
137
|
+
return client.subscribe(onStoreChange);
|
|
138
|
+
},
|
|
139
|
+
[client]
|
|
140
|
+
);
|
|
141
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { RolloutlyProvider, useFlag, useFlagEnabled, useFlags, useRolloutly, useRolloutlyClient };
|
|
145
|
+
//# sourceMappingURL=index.js.map
|
|
146
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\nimport { RolloutlyClient, type FlagValue } from '@rolloutly/core';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from 'react';\n\nimport type { RolloutlyContextValue, RolloutlyProviderProps } from './types';\n\nconst RolloutlyContext = createContext<{\n client: RolloutlyClient | null;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n}>({\n client: null,\n isLoading: true,\n isError: false,\n error: null,\n});\n\n/**\n * Provider component that initializes Rolloutly and provides context\n */\nexport function RolloutlyProvider({\n token,\n children,\n baseUrl,\n realtimeEnabled = true,\n defaultFlags,\n debug = false,\n loadingComponent,\n}: RolloutlyProviderProps): JSX.Element {\n const [isLoading, setIsLoading] = useState(true);\n const [isError, setIsError] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const client = useMemo(() => {\n return new RolloutlyClient({\n token,\n baseUrl,\n realtimeEnabled,\n defaultFlags,\n debug,\n });\n }, [token, baseUrl, realtimeEnabled, defaultFlags, debug]);\n\n useEffect(() => {\n let mounted = true;\n\n client\n .waitForInit()\n .then(() => {\n if (mounted) {\n setIsLoading(false);\n }\n })\n .catch((err) => {\n if (mounted) {\n setIsError(true);\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n });\n\n return () => {\n mounted = false;\n client.close();\n };\n }, [client]);\n\n const contextValue = useMemo(\n () => ({\n client,\n isLoading,\n isError,\n error,\n }),\n [client, isLoading, isError, error],\n );\n\n if (isLoading && loadingComponent) {\n return <>{loadingComponent}</>;\n }\n\n return (\n <RolloutlyContext.Provider value={contextValue}>\n {children}\n </RolloutlyContext.Provider>\n );\n}\n\n/**\n * Hook to access the Rolloutly client directly\n */\nexport function useRolloutlyClient(): RolloutlyClient {\n const { client } = useContext(RolloutlyContext);\n\n if (!client) {\n throw new Error('useRolloutlyClient must be used within RolloutlyProvider');\n }\n\n return client;\n}\n\n/**\n * Hook to get Rolloutly status\n */\nexport function useRolloutly(): RolloutlyContextValue {\n const { client, isLoading, isError, error } = useContext(RolloutlyContext);\n\n if (!client) {\n throw new Error('useRolloutly must be used within RolloutlyProvider');\n }\n\n const getFlag = useCallback(\n <T extends FlagValue = FlagValue>(key: string): T | undefined => {\n return client.getFlag<T>(key);\n },\n [client],\n );\n\n const isEnabled = useCallback(\n (key: string): boolean => {\n return client.isEnabled(key);\n },\n [client],\n );\n\n return {\n isLoading,\n isError,\n error,\n getFlag,\n isEnabled,\n };\n}\n\n/**\n * Hook to get a single flag value with real-time updates\n */\nexport function useFlag<T extends FlagValue = FlagValue>(\n key: string,\n): T | undefined {\n const client = useRolloutlyClient();\n\n const getSnapshot = useCallback((): T | undefined => {\n return client.getFlag<T>(key);\n }, [client, key]);\n\n const subscribe = useCallback(\n (onStoreChange: () => void): (() => void) => {\n return client.subscribe(onStoreChange);\n },\n [client],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to check if a boolean flag is enabled with real-time updates\n */\nexport function useFlagEnabled(key: string): boolean {\n const client = useRolloutlyClient();\n\n const getSnapshot = useCallback((): boolean => {\n return client.isEnabled(key);\n }, [client, key]);\n\n const subscribe = useCallback(\n (onStoreChange: () => void): (() => void) => {\n return client.subscribe(onStoreChange);\n },\n [client],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to get all flags with real-time updates\n */\nexport function useFlags(): Record<string, FlagValue> {\n const client = useRolloutlyClient();\n\n const getSnapshot = useCallback((): Record<string, FlagValue> => {\n const flags = client.getFlags();\n\n return Object.entries(flags).reduce<Record<string, FlagValue>>(\n (acc, [key, flag]) => {\n acc[key] = flag.enabled ? flag.value : undefined!;\n\n return acc;\n },\n {},\n );\n }, [client]);\n\n const subscribe = useCallback(\n (onStoreChange: () => void): (() => void) => {\n return client.subscribe(onStoreChange);\n },\n [client],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rolloutly/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Rolloutly feature flags SDK - React hooks and provider",
|
|
5
|
+
"author": "Kevin Beltrão",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/KevBeltrao/rolloutly-sdk.git",
|
|
10
|
+
"directory": "packages/react"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"feature-flags",
|
|
14
|
+
"feature-toggles",
|
|
15
|
+
"rolloutly",
|
|
16
|
+
"sdk",
|
|
17
|
+
"react",
|
|
18
|
+
"hooks"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.cjs",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"import": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"require": {
|
|
31
|
+
"types": "./dist/index.d.cts",
|
|
32
|
+
"default": "./dist/index.cjs"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"dev": "tsup --watch",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"clean": "rm -rf dist"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@rolloutly/core": "^0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": ">=18.0.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/react": "^18.2.0",
|
|
54
|
+
"react": "^18.2.0",
|
|
55
|
+
"tsup": "^8.0.1",
|
|
56
|
+
"typescript": "^5.3.3"
|
|
57
|
+
}
|
|
58
|
+
}
|