@flagdown/react 0.0.3 → 0.0.5
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 +43 -22
- package/dist/index.d.ts +10 -17
- package/dist/index.js +33 -21
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,32 +1,50 @@
|
|
|
1
1
|
# @flagdown/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use the `Feature` component to switch between new UI and a fallback in React 18+.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @flagdown/react@0.0.5
|
|
7
|
+
```
|
|
4
8
|
|
|
5
9
|
```tsx
|
|
6
|
-
import {
|
|
10
|
+
import { Feature } from '@flagdown/react';
|
|
7
11
|
|
|
8
|
-
function Checkout() {
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
export function Checkout() {
|
|
13
|
+
return (
|
|
14
|
+
<Feature
|
|
15
|
+
flag="new-checkout"
|
|
16
|
+
fallback={<OldCheckout />}
|
|
17
|
+
>
|
|
18
|
+
<NewCheckout />
|
|
19
|
+
</Feature>
|
|
20
|
+
);
|
|
11
21
|
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`Feature` renders its children when the named flag is on. When it is off, unknown,
|
|
25
|
+
still loading, or unavailable, it renders `fallback`. Omit `fallback` to render
|
|
26
|
+
nothing in those cases. Only the selected branch mounts; `Feature` adds no DOM
|
|
27
|
+
wrapper.
|
|
28
|
+
|
|
29
|
+
Configure the project once at your app root:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { FlagdownProvider } from '@flagdown/react';
|
|
12
33
|
|
|
13
34
|
<FlagdownProvider
|
|
14
|
-
apiUrl="https://
|
|
35
|
+
apiUrl="https://api.flagdown.dev"
|
|
15
36
|
clientToken="COPY_PUBLIC_CLIENT_TOKEN_FROM_DASHBOARD"
|
|
16
37
|
>
|
|
17
|
-
<
|
|
38
|
+
<App />
|
|
18
39
|
</FlagdownProvider>
|
|
19
40
|
```
|
|
20
41
|
|
|
21
|
-
Create a project and flag in the dashboard, then copy
|
|
22
|
-
|
|
23
|
-
includes the remote adapter; the previously published 0.0.2 build only provides
|
|
24
|
-
an injectable evaluator. This source must be released before registry installs
|
|
25
|
-
can use remote reads.
|
|
42
|
+
Create a project and flag in the dashboard, then copy its integration snippet.
|
|
43
|
+
`apiUrl` is the API base URL without `/v1`. `Feature` must be inside the provider.
|
|
26
44
|
|
|
27
45
|
Flags refresh every 10 seconds, without reloading the page. Set
|
|
28
46
|
`refreshIntervalMs` to change this (minimum 1000ms). Requests time out after 10
|
|
29
|
-
seconds. Initial values, unknown flags and refresh failures evaluate
|
|
47
|
+
seconds. Initial values, unknown flags and refresh failures evaluate off;
|
|
30
48
|
a successful later refresh restores values. An optional `onError(error)` callback
|
|
31
49
|
can report failures. Changing project configuration immediately discards the
|
|
32
50
|
previous project's values. Unmounting cancels in-flight work and future refreshes.
|
|
@@ -35,14 +53,17 @@ Client tokens are public, project-scoped read configuration. They cannot manage
|
|
|
35
53
|
flags. Replacing a token in the dashboard invalidates the previous token; update
|
|
36
54
|
apps using it. Client-side flags are not an authorization boundary.
|
|
37
55
|
|
|
38
|
-
|
|
56
|
+
`Feature` is the SDK's flag consumption API. The evaluation context and evaluator
|
|
57
|
+
are internal; the package exposes no flag hooks or direct evaluation functions.
|
|
58
|
+
Use `FlagdownProvider` to configure your project. A provider without project
|
|
59
|
+
configuration renders each feature's fallback.
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
```
|
|
61
|
+
API transport types are generated from `openapi/flagdown-api.json` with
|
|
62
|
+
`openapi-typescript` before SDK typechecking and building. They stay internal;
|
|
63
|
+
the public exports remain `Feature`, `FlagdownProvider` and their prop types.
|
|
64
|
+
The runtime still rejects malformed or non-boolean flag values.
|
|
45
65
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
For SDK development, run `npm run --workspace @flagdown/react generate:types`
|
|
67
|
+
to regenerate `src/generated/api.d.ts`. This file is ignored by git and is never
|
|
68
|
+
the source of truth. Backend CI checks candidate contracts against the current
|
|
69
|
+
SDK before publishing the generated specification to this repository.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { PropsWithChildren, ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* It deliberately keeps every flag disabled so applications can install the
|
|
12
|
-
* provider and hook safely before remote evaluation is available.
|
|
13
|
-
*/
|
|
14
|
-
declare const disabledFlagEvaluator: FlagEvaluator;
|
|
4
|
+
type FeatureProps = PropsWithChildren<{
|
|
5
|
+
/** Key of the flag controlling this feature. */
|
|
6
|
+
flag: string;
|
|
7
|
+
/** Rendered while the flag is off, unknown, loading, or unavailable. */
|
|
8
|
+
fallback?: ReactNode;
|
|
9
|
+
}>;
|
|
10
|
+
declare function Feature({ flag, fallback, children }: FeatureProps): react.JSX.Element;
|
|
15
11
|
|
|
16
12
|
interface FlagdownProviderProps extends PropsWithChildren {
|
|
17
|
-
evaluator?: FlagEvaluator;
|
|
18
13
|
apiUrl?: string;
|
|
19
14
|
/** Public, read-only project token copied from the dashboard. */
|
|
20
15
|
clientToken?: string;
|
|
@@ -22,8 +17,6 @@ interface FlagdownProviderProps extends PropsWithChildren {
|
|
|
22
17
|
refreshIntervalMs?: number;
|
|
23
18
|
onError?: (error: Error) => void;
|
|
24
19
|
}
|
|
25
|
-
declare function FlagdownProvider({ children,
|
|
26
|
-
|
|
27
|
-
declare function useFlag(flagKey: string): boolean;
|
|
20
|
+
declare function FlagdownProvider({ children, apiUrl, clientToken, refreshIntervalMs, onError }: FlagdownProviderProps): react.JSX.Element;
|
|
28
21
|
|
|
29
|
-
export { type
|
|
22
|
+
export { Feature, type FeatureProps, FlagdownProvider, type FlagdownProviderProps };
|
package/dist/index.js
CHANGED
|
@@ -1,47 +1,60 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
1
|
+
// src/Feature.tsx
|
|
2
|
+
import { useContext } from "react";
|
|
3
3
|
|
|
4
4
|
// src/FlagdownContext.tsx
|
|
5
|
-
import { createContext
|
|
5
|
+
import { createContext } from "react";
|
|
6
6
|
var FlagdownContext = createContext(void 0);
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
// src/Feature.tsx
|
|
9
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
10
|
+
function Feature({ flag, fallback = null, children }) {
|
|
8
11
|
const evaluator = useContext(FlagdownContext);
|
|
9
|
-
if (!evaluator)
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
return evaluator.isEnabled(flagKey);
|
|
12
|
+
if (!evaluator) throw new Error("Feature must be used within a FlagdownProvider");
|
|
13
|
+
return /* @__PURE__ */ jsx(Fragment, { children: evaluator.isEnabled(flag) ? children : fallback });
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
// src/FlagdownProvider.tsx
|
|
17
|
+
import { useEffect, useMemo, useState, useRef } from "react";
|
|
18
|
+
|
|
15
19
|
// src/flags.ts
|
|
16
20
|
var disabledFlagEvaluator = {
|
|
17
21
|
isEnabled: () => false
|
|
18
22
|
};
|
|
19
23
|
|
|
24
|
+
// src/api-types.ts
|
|
25
|
+
var clientFlagsPath = "/v1/client/flags";
|
|
26
|
+
|
|
20
27
|
// src/remote.ts
|
|
21
28
|
async function fetchFlagState(apiUrl, clientToken, signal) {
|
|
22
|
-
const
|
|
23
|
-
|
|
29
|
+
const headers = { "x-flagdown-client-token": clientToken };
|
|
30
|
+
const response = await fetch(apiUrl.replace(/\/$/, "") + clientFlagsPath, {
|
|
31
|
+
headers,
|
|
24
32
|
cache: "no-store",
|
|
25
33
|
signal
|
|
26
34
|
});
|
|
27
35
|
if (!response.ok) throw new Error(`Flagdown request failed (${response.status})`);
|
|
28
36
|
const data = await response.json();
|
|
29
|
-
if (!data || typeof data !== "object" || !("flags" in data) || !data.flags || typeof data.flags !== "object" || Array.isArray(data.flags)
|
|
37
|
+
if (!data || typeof data !== "object" || !("flags" in data) || !data.flags || typeof data.flags !== "object" || Array.isArray(data.flags)) {
|
|
30
38
|
throw new Error("Invalid Flagdown response");
|
|
31
39
|
}
|
|
32
|
-
|
|
40
|
+
const flags = Object.fromEntries(Object.entries(data.flags).map(([key, enabled]) => {
|
|
41
|
+
if (typeof enabled !== "boolean") throw new Error("Invalid Flagdown response");
|
|
42
|
+
return [key, enabled];
|
|
43
|
+
}));
|
|
44
|
+
const result = { flags };
|
|
45
|
+
return result.flags;
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
// src/FlagdownProvider.tsx
|
|
36
|
-
import { jsx } from "react/jsx-runtime";
|
|
37
|
-
function FlagdownProvider({ children,
|
|
49
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
50
|
+
function FlagdownProvider({ children, apiUrl, clientToken, refreshIntervalMs = 1e4, onError }) {
|
|
38
51
|
const errorHandler = useRef(onError);
|
|
39
52
|
useEffect(() => {
|
|
40
53
|
errorHandler.current = onError;
|
|
41
54
|
}, [onError]);
|
|
42
55
|
const [snapshot, setSnapshot] = useState();
|
|
43
56
|
useEffect(() => {
|
|
44
|
-
if (
|
|
57
|
+
if (!apiUrl || !clientToken) return;
|
|
45
58
|
let active = true;
|
|
46
59
|
let timer;
|
|
47
60
|
let controller;
|
|
@@ -70,12 +83,11 @@ function FlagdownProvider({ children, evaluator, apiUrl, clientToken, refreshInt
|
|
|
70
83
|
clearTimeout(timer);
|
|
71
84
|
controller?.abort();
|
|
72
85
|
};
|
|
73
|
-
}, [apiUrl, clientToken,
|
|
74
|
-
const value = useMemo(() =>
|
|
75
|
-
return /* @__PURE__ */
|
|
86
|
+
}, [apiUrl, clientToken, refreshIntervalMs]);
|
|
87
|
+
const value = useMemo(() => snapshot && snapshot.apiUrl === apiUrl && snapshot.clientToken === clientToken ? { isEnabled: (key) => Object.hasOwn(snapshot.flags, key) && snapshot.flags[key] === true } : disabledFlagEvaluator, [snapshot, apiUrl, clientToken]);
|
|
88
|
+
return /* @__PURE__ */ jsx2(FlagdownContext.Provider, { value, children });
|
|
76
89
|
}
|
|
77
90
|
export {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
useFlag
|
|
91
|
+
Feature,
|
|
92
|
+
FlagdownProvider
|
|
81
93
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flagdown/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "React SDK for evaluating Flagdown feature flags.",
|
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
"LICENSE"
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
|
-
"
|
|
31
|
+
"generate:types": "node scripts/generate-api-types.mjs",
|
|
32
|
+
"build": "node scripts/generate-api-types.mjs && tsup src/index.ts --format esm --dts --clean",
|
|
32
33
|
"test": "bun test",
|
|
33
|
-
"typecheck": "tsc --noEmit"
|
|
34
|
+
"typecheck": "node scripts/generate-api-types.mjs && tsc --noEmit"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"react": ">=18",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@types/react": "^18",
|
|
42
|
+
"openapi-typescript": "7.13.0",
|
|
41
43
|
"react": "^18",
|
|
42
44
|
"react-dom": "^18",
|
|
43
45
|
"tsup": "^8",
|