@flagward/react 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brian Suárez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # @flagward/react
2
+
3
+ React SDK for Flagward - Feature Flags as a Service
4
+
5
+ > Published as `flagward-sdk-react` up to 0.2.0. Same package, same API — it
6
+ > moved into the `@flagward` scope so every SDK in this family shares one
7
+ > namespace the project actually owns. Change the name in your `package.json`
8
+ > and the import; nothing else.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @flagward/react
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ```tsx
19
+ import { FlagwardProvider, useFlag } from "@flagward/react";
20
+
21
+ function App() {
22
+ return (
23
+ <FlagwardProvider apiKey="your-api-key">
24
+ <Dashboard />
25
+ </FlagwardProvider>
26
+ );
27
+ }
28
+
29
+ function Dashboard() {
30
+ const { value: showNewUI } = useFlag("show-new-ui");
31
+
32
+ return showNewUI ? <NewDashboard /> : <OldDashboard />;
33
+ }
34
+ ```
35
+
36
+ ## Module format
37
+
38
+ This package ships as **ESM only** (`"type": "module"`, with an `exports` map
39
+ pointing at `dist/index.js`). It has no CommonJS build: `require("@flagward/react")`
40
+ fails with a clear `ERR_PACKAGE_PATH_NOT_EXPORTED`/`ERR_REQUIRE_ESM` error
41
+ rather than silently loading broken code. Next.js, Vite, and any other
42
+ bundler-based toolchain resolve ESM packages natively, which covers the
43
+ realistic ways a React SDK gets consumed. If you have a pure CommonJS
44
+ build pipeline with no ESM support, `import()` the package dynamically or
45
+ open an issue.
46
+
47
+ ## How flags are evaluated
48
+
49
+ This SDK evaluates **locally, in the browser**. On startup it downloads the
50
+ full set of flags and their targeting rules for the environment your API key
51
+ belongs to, then evaluates each flag against the rules on the client. A
52
+ Server-Sent Events (SSE) stream keeps that local copy fresh as flags change on
53
+ the server, without a page reload (see "Losing the network" below for what
54
+ happens when that stream drops).
55
+
56
+ This has a direct consequence you must know before writing a targeting rule:
57
+ **the rules are visible in the browser.** Anything you put in a rule
58
+ (condition values, user attribute names, percentages, etc.) is downloaded as
59
+ plain JSON to every client and can be read by opening devtools. Do not encode
60
+ secrets, internal identifiers you don't want exposed, or anything
61
+ security-sensitive in a flag's targeting rules — treat them the same way you'd
62
+ treat any other client-side configuration.
63
+
64
+ ## Hooks
65
+
66
+ ### `useFlag(key)`
67
+
68
+ Evaluates a single flag by key.
69
+
70
+ ```tsx
71
+ const { value, isLoading, error } = useFlag("new-dashboard");
72
+ ```
73
+
74
+ Returns:
75
+ - `value`: `boolean | string | undefined`
76
+ - `isLoading`: `boolean`
77
+ - `error`: `Error | null`
78
+
79
+ ### `useFlags()`
80
+
81
+ Returns all flags and helper functions.
82
+
83
+ ```tsx
84
+ const { flags, getFlag, isLoading } = useFlags();
85
+
86
+ const showBanner = getFlag("show-banner");
87
+ ```
88
+
89
+ ## Provider
90
+
91
+ Wrap your app with `FlagwardProvider`:
92
+
93
+ ```tsx
94
+ <FlagwardProvider
95
+ apiKey="your-api-key"
96
+ host="http://localhost:8000" // optional, defaults to localhost:8000
97
+ context={{ userId: "123" }} // optional, used to evaluate targeting rules
98
+ logLevel="warn" // optional, see Error reporting below
99
+ >
100
+ {children}
101
+ </FlagwardProvider>
102
+ ```
103
+
104
+ ## Losing the network
105
+
106
+ Flags are read once and evaluated locally, so a client that loses its
107
+ connection keeps working: every flag answers from its last known value.
108
+
109
+ What it cannot do while disconnected is notice a change. When the browser
110
+ reports the network is back, the SDK re-reads the flags and reopens the update
111
+ stream if the browser had given up on it, so a change made during the outage is
112
+ picked up rather than waiting for the next reload.
113
+
114
+ A stream that is still open is left alone: replacing a working connection would
115
+ drop events for no reason.
116
+
117
+ Returning to a backgrounded tab does the same. A machine waking from sleep
118
+ drops its connection without the browser ever reporting the network as gone, so
119
+ `online` never fires and the tab comes back holding whatever it knew before it
120
+ slept. Both signals share one in-flight refresh, so arriving together does not
121
+ produce two.
122
+
123
+ ## Error reporting
124
+
125
+ The SDK never throws into your application. A rejected API key, an unreachable
126
+ server or an unknown flag all resolve to `undefined`, so your own fallback
127
+ decides what the user sees and the page keeps rendering.
128
+
129
+ Because none of that surfaces on its own, the SDK reports it to the console
130
+ instead:
131
+
132
+ ```
133
+ [Flagward] The API key was rejected. Check that it matches an environment in
134
+ your Flagward dashboard.
135
+ ```
136
+
137
+ Each distinct problem is reported once per page load, so a component that
138
+ re-renders a hundred times does not produce a hundred lines.
139
+
140
+ `logLevel` controls how much is reported:
141
+
142
+ | Value | Reports |
143
+ |-------|---------|
144
+ | `"warn"` (default) | everything: unknown flags, dropped streams, network failures |
145
+ | `"error"` | only what cannot work at all: a missing or rejected API key, a missing provider |
146
+ | `"silent"` | nothing |
147
+
148
+ ## The core
149
+
150
+ Everything that is not React lives in
151
+ [`@flagward/core`](https://www.npmjs.com/package/@flagward/core) — the client,
152
+ the rule evaluator and the reporting — and is re-exported from this package, so
153
+ installing this one is enough. Reach for the core directly only outside React.
154
+
155
+ ## Standalone Client
156
+
157
+ Use the client directly without React:
158
+
159
+ ```ts
160
+ import { FlagwardClient } from "@flagward/react";
161
+
162
+ const client = new FlagwardClient({
163
+ apiKey: "your-api-key",
164
+ host: "http://localhost:8000",
165
+ });
166
+
167
+ await client.init();
168
+
169
+ // Every flag's configured on/off state, from the local snapshot.
170
+ // Targeting rules are NOT applied here -- there is no user context to
171
+ // apply them against.
172
+ const flags = client.cachedFlags;
173
+
174
+ // One flag with targeting rules applied against the context you pass.
175
+ // Throws if the key does not exist in this environment.
176
+ const showBanner = client.evaluate("show-banner", { plan: "premium" });
177
+
178
+ // The same, without context and without throwing: an unknown key reads
179
+ // as undefined and your own fallback decides what happens.
180
+ const maintenance = client.getFlag("maintenance-mode");
181
+
182
+ // Optional: keep the snapshot fresh via the SSE stream.
183
+ client.connect();
184
+
185
+ // Close it when you are done -- an open stream holds a connection.
186
+ client.disconnect();
187
+ ```
188
+
189
+ `cachedFlags` and `evaluate` answer different questions. The first says how a
190
+ flag is configured; the second says what it resolves to for a particular user.
191
+ Reaching for `cachedFlags` when you meant `evaluate` silently ignores every
192
+ targeting rule on the flag.
193
+
194
+ ## License
195
+
196
+ MIT
@@ -0,0 +1,12 @@
1
+ import { FlagwardClient } from "@flagward/core";
2
+ import type { FlagDataMap, UserContext } from "@flagward/core";
3
+ export interface FlagwardContextValue {
4
+ client: FlagwardClient | null;
5
+ /** The flag data this render was produced from. */
6
+ flagsData: FlagDataMap;
7
+ isLoading: boolean;
8
+ error: Error | null;
9
+ context: UserContext;
10
+ }
11
+ export declare const FlagwardContext: import("react").Context<FlagwardContextValue>;
12
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE/D,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,mDAAmD;IACnD,SAAS,EAAE,WAAW,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,eAAO,MAAM,eAAe,+CAM1B,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { createContext } from "react";
2
+ export const FlagwardContext = createContext({
3
+ client: null,
4
+ flagsData: {},
5
+ isLoading: false,
6
+ error: null,
7
+ context: {},
8
+ });
9
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAatC,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAuB;IACjE,MAAM,EAAE,IAAI;IACZ,SAAS,EAAE,EAAE;IACb,SAAS,EAAE,KAAK;IAChB,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,EAAE;CACZ,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export { FlagwardClient, evaluateFlag, toFlagMap, } from "@flagward/core";
2
+ export type { Condition, Flag, FlagData, FlagDataMap, FlagMap, FlagwardClientOptions, LogLevel, Logger, Rule, UserContext, } from "@flagward/core";
3
+ export { FlagwardProvider } from "./provider.js";
4
+ export type { FlagwardProviderProps } from "./provider.js";
5
+ export { useFlag } from "./useFlag.js";
6
+ export type { UseFlagResult } from "./useFlag.js";
7
+ export { useFlags } from "./useFlags.js";
8
+ export type { UseFlagsResult } from "./useFlags.js";
9
+ export { SDK_VERSION } from "./version.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,cAAc,EACd,YAAY,EACZ,SAAS,GACV,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,WAAW,EACX,OAAO,EACP,qBAAqB,EACrB,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,WAAW,GACZ,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,YAAY,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAG3D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // The framework-agnostic core, re-exported so a consumer of this package never
2
+ // has to install @flagward/core to reach the client, the evaluator or the types.
3
+ export { FlagwardClient, evaluateFlag, toFlagMap, } from "@flagward/core";
4
+ // Provider
5
+ export { FlagwardProvider } from "./provider.js";
6
+ // Hooks
7
+ export { useFlag } from "./useFlag.js";
8
+ export { useFlags } from "./useFlags.js";
9
+ // The version this adapter registers with
10
+ export { SDK_VERSION } from "./version.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,iFAAiF;AACjF,OAAO,EACL,cAAc,EACd,YAAY,EACZ,SAAS,GACV,MAAM,gBAAgB,CAAC;AAcxB,WAAW;AACX,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,QAAQ;AACR,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,0CAA0C;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { LogLevel, UserContext } from "@flagward/core";
2
+ export interface FlagwardProviderProps {
3
+ apiKey: string;
4
+ host?: string;
5
+ context?: UserContext;
6
+ /** How much the SDK reports to the console. Defaults to "warn". */
7
+ logLevel?: LogLevel;
8
+ children: React.ReactNode;
9
+ }
10
+ export declare function FlagwardProvider({ apiKey, host, context: userContext, logLevel, children, }: FlagwardProviderProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,QAAQ,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAczE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,IAAI,EACJ,OAAO,EAAE,WAAgB,EACzB,QAAQ,EACR,QAAQ,GACT,EAAE,qBAAqB,+BAsEvB"}
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { FlagwardClient } from "@flagward/core";
4
+ import { FlagwardContext } from "./context.js";
5
+ import { SDK_VERSION } from "./version.js";
6
+ /**
7
+ * How this adapter names itself at registration.
8
+ *
9
+ * Not yet one of the server's stored choices, which is why the dashboard shows
10
+ * it as its own row rather than folding it into JavaScript. It is named here
11
+ * so that, once the server records adapters, a React application is already
12
+ * distinguishable from a Vue one without changing what anybody has installed.
13
+ */
14
+ const SDK_TYPE = "REACT";
15
+ export function FlagwardProvider({ apiKey, host, context: userContext = {}, logLevel, children, }) {
16
+ const [client] = useState(() => new FlagwardClient({
17
+ apiKey,
18
+ host,
19
+ logLevel,
20
+ sdkType: SDK_TYPE,
21
+ sdkVersion: SDK_VERSION,
22
+ }));
23
+ // The flag data lives in React state, not in the client's mutable field,
24
+ // so what a hook renders is what triggered the render.
25
+ const [flagsData, setFlagsData] = useState({});
26
+ const [isLoading, setIsLoading] = useState(true);
27
+ const [error, setError] = useState(null);
28
+ useEffect(() => {
29
+ const initClient = async () => {
30
+ try {
31
+ await client.init();
32
+ setFlagsData(client.snapshot);
33
+ }
34
+ catch (err) {
35
+ // Startup failing is never fatal to the host application: the tree
36
+ // still renders and every flag reads as undefined, so each caller's
37
+ // own fallback decides what the user sees.
38
+ client.logger.warn("init-failed", "Could not load flags, so every flag falls back to undefined and " +
39
+ "your own defaults apply. The cause is reported above.");
40
+ setError(err instanceof Error ? err : new Error(String(err)));
41
+ }
42
+ finally {
43
+ // Outside the try on purpose: a startup that failed is exactly when
44
+ // the stream and the connectivity watcher matter most, and leaving
45
+ // them behind the success path meant a client that started offline
46
+ // could never recover.
47
+ client.connect();
48
+ setIsLoading(false);
49
+ }
50
+ };
51
+ initClient();
52
+ const unsubscribe = client.subscribe((snapshot) => {
53
+ setFlagsData(snapshot);
54
+ // Flags arriving means the client is talking to the server again. Leaving
55
+ // a past failure in state would keep every consumer that checks `error`
56
+ // showing a problem that is over.
57
+ setError(null);
58
+ });
59
+ return () => {
60
+ unsubscribe();
61
+ client.destroy();
62
+ };
63
+ }, [client]);
64
+ const value = {
65
+ client,
66
+ flagsData,
67
+ isLoading,
68
+ error,
69
+ context: userContext,
70
+ };
71
+ return (_jsx(FlagwardContext.Provider, { value: value, children: children }));
72
+ }
73
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,eAAe,EAA6B,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,QAAQ,GAAG,OAAO,CAAC;AAWzB,MAAM,UAAU,gBAAgB,CAAC,EAC/B,MAAM,EACN,IAAI,EACJ,OAAO,EAAE,WAAW,GAAG,EAAE,EACzB,QAAQ,EACR,QAAQ,GACc;IACtB,MAAM,CAAC,MAAM,CAAC,GAAG,QAAQ,CACvB,GAAG,EAAE,CAAC,IAAI,cAAc,CAAC;QACvB,MAAM;QACN,IAAI;QACJ,QAAQ;QACR,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,WAAW;KACxB,CAAC,CACH,CAAC;IACF,yEAAyE;IACzE,uDAAuD;IACvD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAc,EAAE,CAAC,CAAC;IAC5D,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;YAC5B,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBACpB,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,mEAAmE;gBACnE,oEAAoE;gBACpE,2CAA2C;gBAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,CAChB,aAAa,EACb,kEAAkE;oBAChE,uDAAuD,CAC1D,CAAC;gBACF,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAChE,CAAC;oBAAS,CAAC;gBACT,oEAAoE;gBACpE,mEAAmE;gBACnE,mEAAmE;gBACnE,uBAAuB;gBACvB,MAAM,CAAC,OAAO,EAAE,CAAC;gBACjB,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC,CAAC;QAEF,UAAU,EAAE,CAAC;QAEb,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE;YAChD,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvB,0EAA0E;YAC1E,wEAAwE;YACxE,kCAAkC;YAClC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,WAAW,EAAE,CAAC;YACd,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,MAAM,KAAK,GAAyB;QAClC,MAAM;QACN,SAAS;QACT,SAAS;QACT,KAAK;QACL,OAAO,EAAE,WAAW;KACrB,CAAC;IAEF,OAAO,CACL,KAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACnC,QAAQ,GACgB,CAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { UserContext } from "@flagward/core";
2
+ export interface UseFlagResult {
3
+ /** The flag's value, or undefined while loading or if it does not exist. */
4
+ value: boolean | undefined;
5
+ isLoading: boolean;
6
+ error: Error | null;
7
+ }
8
+ export declare function useFlag(key: string, flagContext?: UserContext): UseFlagResult;
9
+ //# sourceMappingURL=useFlag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFlag.d.ts","sourceRoot":"","sources":["../src/useFlag.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,aAAa,CAmC7E"}
@@ -0,0 +1,30 @@
1
+ import { useContext } from "react";
2
+ import { FlagwardContext } from "./context.js";
3
+ import { createLogger, evaluateFlag } from "@flagward/core";
4
+ export function useFlag(key, flagContext) {
5
+ const context = useContext(FlagwardContext);
6
+ const logger = context.client?.logger ?? createLogger();
7
+ // Merge provider context with flag-specific context
8
+ const mergedContext = { ...context.context, ...flagContext };
9
+ let value;
10
+ if (!context.client) {
11
+ logger.error("no-provider", `useFlag("${key}") was called outside FlagwardProvider, so it can only ` +
12
+ "return undefined. Wrap the tree in <FlagwardProvider>.");
13
+ }
14
+ else if (!context.isLoading) {
15
+ // Evaluated against the data this render was produced from, so the value
16
+ // shown and the update that caused it can never disagree.
17
+ value = evaluateFlag(context.flagsData[key], mergedContext);
18
+ if (value === undefined) {
19
+ logger.warn(`unknown-flag:${key}`, `Flag "${key}" is not in this environment, so it reads as undefined. ` +
20
+ "Check the key, and that the flag exists in the environment this " +
21
+ "API key belongs to.");
22
+ }
23
+ }
24
+ return {
25
+ value,
26
+ isLoading: context.isLoading,
27
+ error: context.error,
28
+ };
29
+ }
30
+ //# sourceMappingURL=useFlag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFlag.js","sourceRoot":"","sources":["../src/useFlag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAU5D,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,WAAyB;IAC5D,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,IAAI,YAAY,EAAE,CAAC;IAExD,oDAAoD;IACpD,MAAM,aAAa,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,WAAW,EAAE,CAAC;IAE7D,IAAI,KAA0B,CAAC;IAE/B,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,CAAC,KAAK,CACV,aAAa,EACb,YAAY,GAAG,yDAAyD;YACtE,wDAAwD,CAC3D,CAAC;IACJ,CAAC;SAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QAC9B,yEAAyE;QACzE,0DAA0D;QAC1D,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC;QAE5D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,CAAC,IAAI,CACT,gBAAgB,GAAG,EAAE,EACrB,SAAS,GAAG,0DAA0D;gBACpE,kEAAkE;gBAClE,qBAAqB,CACxB,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK;QACL,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { FlagMap, UserContext } from "@flagward/core";
2
+ export interface UseFlagsResult {
3
+ flags: FlagMap;
4
+ isLoading: boolean;
5
+ error: Error | null;
6
+ getFlag: (key: string, flagContext?: UserContext) => boolean | undefined;
7
+ }
8
+ export declare function useFlags(): UseFlagsResult;
9
+ //# sourceMappingURL=useFlags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFlags.d.ts","sourceRoot":"","sources":["../src/useFlags.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,WAAW,KAAK,OAAO,GAAG,SAAS,CAAC;CAC1E;AAED,wBAAgB,QAAQ,IAAI,cAAc,CAkCzC"}
@@ -0,0 +1,28 @@
1
+ import { useContext } from "react";
2
+ import { FlagwardContext } from "./context.js";
3
+ import { createLogger, evaluateFlag, toFlagMap } from "@flagward/core";
4
+ export function useFlags() {
5
+ const context = useContext(FlagwardContext);
6
+ const logger = context.client?.logger ?? createLogger();
7
+ if (!context.client) {
8
+ logger.error("no-provider", "useFlags() was called outside FlagwardProvider, so it can only return " +
9
+ "an empty set. Wrap the tree in <FlagwardProvider>.");
10
+ }
11
+ const getFlag = (key, flagContext) => {
12
+ const mergedContext = { ...context.context, ...flagContext };
13
+ const value = evaluateFlag(context.flagsData[key], mergedContext);
14
+ if (value === undefined) {
15
+ logger.warn(`unknown-flag:${key}`, `Flag "${key}" is not in this environment, so it reads as undefined. ` +
16
+ "Check the key, and that the flag exists in the environment this " +
17
+ "API key belongs to.");
18
+ }
19
+ return value;
20
+ };
21
+ return {
22
+ flags: toFlagMap(context.flagsData, context.context),
23
+ isLoading: context.isLoading,
24
+ error: context.error,
25
+ getFlag,
26
+ };
27
+ }
28
+ //# sourceMappingURL=useFlags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFlags.js","sourceRoot":"","sources":["../src/useFlags.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAUvE,MAAM,UAAU,QAAQ;IACtB,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,IAAI,YAAY,EAAE,CAAC;IAExD,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,CAAC,KAAK,CACV,aAAa,EACb,wEAAwE;YACtE,oDAAoD,CACvD,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,WAAyB,EAAuB,EAAE;QAC9E,MAAM,aAAa,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,WAAW,EAAE,CAAC;QAC7D,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC;QAElE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,CAAC,IAAI,CACT,gBAAgB,GAAG,EAAE,EACrB,SAAS,GAAG,0DAA0D;gBACpE,kEAAkE;gBAClE,qBAAqB,CACxB,CAAC;QACJ,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC;QACpD,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,OAAO;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The version this package reports when it registers with the server.
3
+ *
4
+ * GENERATED by scripts/sync-version.mjs from package.json. Do not edit: the
5
+ * next build overwrites it, and a test fails if the two disagree.
6
+ */
7
+ export declare const SDK_VERSION = "0.2.0";
8
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,WAAW,UAAU,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The version this package reports when it registers with the server.
3
+ *
4
+ * GENERATED by scripts/sync-version.mjs from package.json. Do not edit: the
5
+ * next build overwrites it, and a test fails if the two disagree.
6
+ */
7
+ export const SDK_VERSION = "0.2.0";
8
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@flagward/react",
3
+ "version": "0.2.0",
4
+ "description": "React SDK for Flagward - Feature Flags as a Service",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "sideEffects": false,
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "prebuild": "node ../../scripts/sync-version.mjs",
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "dev": "tsc --watch",
22
+ "lint": "tsc --noEmit",
23
+ "test": "vitest",
24
+ "test:run": "vitest run",
25
+ "prepublishOnly": "npm run build --prefix ../core && npm run lint && npm run test:run && npm run build"
26
+ },
27
+ "keywords": [
28
+ "feature-flags",
29
+ "flags",
30
+ "react",
31
+ "sdk",
32
+ "flagward"
33
+ ],
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/basb7/flagward-sdk-js.git",
38
+ "directory": "packages/react"
39
+ },
40
+ "homepage": "https://github.com/basb7/flagward-sdk-js/tree/main/packages/react#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/basb7/flagward-sdk-js/issues"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "dependencies": {
48
+ "@flagward/core": "^0.1.0"
49
+ },
50
+ "peerDependencies": {
51
+ "react": ">=18.0.0",
52
+ "react-dom": ">=18.0.0"
53
+ },
54
+ "devDependencies": {
55
+ "@testing-library/jest-dom": "^6.0.0",
56
+ "@testing-library/react": "^16.0.0",
57
+ "@types/node": "^22.20.1",
58
+ "@types/react": "^19.0.0",
59
+ "@types/react-dom": "^19.0.0",
60
+ "@vitejs/plugin-react": "^6.1.0",
61
+ "jsdom": "^25.0.0",
62
+ "react": "^19.0.0",
63
+ "react-dom": "^19.0.0",
64
+ "typescript": "^5.7.0",
65
+ "vitest": "^3.0.0"
66
+ }
67
+ }