@liteguard/liteguard-react 0.2.20260314

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 ADDED
@@ -0,0 +1,141 @@
1
+ # @liteguard/liteguard-react
2
+
3
+ [![JavaScript SDK](https://github.com/liteguard/liteguard/actions/workflows/test-js.yml/badge.svg)](https://github.com/liteguard/liteguard/actions/workflows/test-js.yml)
4
+ [![npm](https://img.shields.io/npm/v/@liteguard/liteguard-react)](https://www.npmjs.com/package/@liteguard/liteguard-react)
5
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/liteguard/liteguard/blob/main/LICENSE)
6
+
7
+ > Feature guards, observability, and security response in a single import — evaluated locally, zero network overhead per check.
8
+
9
+ React bindings for Liteguard. Drop in `<LiteguardProvider>` and use the `useIsOpen` hook to gate any component tree — guards are evaluated locally with no network round-trips.
10
+
11
+ Powered by [`@liteguard/liteguard-browser`](https://www.npmjs.com/package/@liteguard/liteguard-browser) under the hood, so runtime behavior, signal telemetry, and CVE auto-disable all work out of the box.
12
+
13
+ ---
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @liteguard/liteguard-react
19
+ ```
20
+
21
+ React 18+ and react-dom 18+ are required as peer dependencies.
22
+
23
+ ---
24
+
25
+ ## Quick Start
26
+
27
+ ```tsx
28
+ import { LiteguardProvider, useIsOpen } from '@liteguard/liteguard-react';
29
+
30
+ function CheckoutButton() {
31
+ const enabled = useIsOpen('payments.checkout');
32
+ if (!enabled) return null;
33
+ return <button onClick={checkout}>Checkout</button>;
34
+ }
35
+
36
+ export function App() {
37
+ return (
38
+ <LiteguardProvider projectClientKeyId="pckid-..." properties={{ userId: 'user-123', plan: 'pro' }}>
39
+ <CheckoutButton />
40
+ </LiteguardProvider>
41
+ );
42
+ }
43
+ ```
44
+
45
+ ---
46
+
47
+ ## API Reference
48
+
49
+ ### `<LiteguardProvider>`
50
+
51
+ Context provider that initializes the Liteguard browser client and makes it available to all descendant components. Place it near the top of your component tree, typically alongside your other providers.
52
+
53
+ ```tsx
54
+ <LiteguardProvider
55
+ projectClientKeyId="pckid-..."
56
+ environment="production" // optional — uses workspace default if omitted
57
+ properties={{ userId: 'user-123', plan: 'pro' }} // optional initial properties
58
+ fallback={false} // default result while guards are loading
59
+ >
60
+ {children}
61
+ </LiteguardProvider>
62
+ ```
63
+
64
+ All `init` options are accepted as props with the same `camelCase` names:
65
+
66
+ | Prop | Default | Description |
67
+ |---|---|---|
68
+ | `projectClientKeyId` | required | Your Liteguard project client key |
69
+ | `environment` | workspace default | Environment slug |
70
+ | `fallback` | `false` | Guard result before rules load |
71
+ | `refreshRateSeconds` | `30` | Guard rule refresh interval |
72
+ | `flushRateSeconds` | `10` | Signal flush interval |
73
+ | `flushSize` | `500` | Max buffered signals before forced flush |
74
+ | `httpTimeoutSeconds` | `4` | HTTP request timeout |
75
+ | `backendUrl` | `https://api.liteguard.io` | API base URL override |
76
+
77
+ ### `useIsOpen(name, options?) → boolean`
78
+
79
+ Hook that returns `true` if the named guard is open for the current context. Re-renders when guard rules are refreshed.
80
+
81
+ ```tsx
82
+ const enabled = useIsOpen('payments.checkout');
83
+
84
+ // With per-call property override
85
+ const enabled = useIsOpen('payments.checkout', { properties: { region: 'us-east-1' } });
86
+ ```
87
+
88
+ > `useIsOpen` is intentionally render-safe and does not emit `guard_execution` measurement signals. For guarded execution with telemetry, use `useLiteguardClient().executeIfOpen(...)`.
89
+
90
+ ### `useLiteguardClient() → LiteguardClient`
91
+
92
+ Returns the underlying `LiteguardClient` instance. Use this for guarded execution with measurement signals, programmatic property management, or direct guard checks outside of React rendering.
93
+
94
+ ```tsx
95
+ function CheckoutButton() {
96
+ const client = useLiteguardClient();
97
+
98
+ const handleClick = async () => {
99
+ const session = await client.executeIfOpenAsync('payments.checkout', async () => {
100
+ return createCheckoutSession();
101
+ });
102
+
103
+ if (session === undefined) {
104
+ // guard was closed
105
+ }
106
+ };
107
+
108
+ return <button onClick={handleClick}>Checkout</button>;
109
+ }
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Guard Evaluation
115
+
116
+ Guards are evaluated **client-side** against rules fetched once at initialization and refreshed periodically. Rules are evaluated in order — **first matching rule wins**. If no rule matches the guard's configured default applies.
117
+
118
+ ```
119
+ guard "payments.checkout"
120
+ rule: plan == "pro" → open
121
+ rule: userId in [canary-list] → open
122
+ default → closed
123
+ ```
124
+
125
+ ### Operators
126
+
127
+ | Operator | Description |
128
+ |---|---|
129
+ | `EQUALS` | Exact match |
130
+ | `NOT_EQUALS` | Negated exact match |
131
+ | `IN` | Value in a set |
132
+ | `NOT_IN` | Value not in a set |
133
+ | `REGEX` | Regular expression match |
134
+ | `GT` / `GTE` | Greater than / greater or equal |
135
+ | `LT` / `LTE` | Less than / less or equal |
136
+
137
+ ---
138
+
139
+ ## License
140
+
141
+ Apache 2.0 — see [LICENSE](https://github.com/liteguard/liteguard/blob/main/LICENSE).
@@ -0,0 +1,120 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { LiteguardClient, ClientOptions, Properties, Options } from '@liteguard/liteguard-browser';
4
+
5
+ /** Props for {@link LiteguardProvider}. */
6
+ type LiteguardProviderProps = {
7
+ /**
8
+ * An externally created {@link LiteguardClient} instance. When provided,
9
+ * the provider uses this client directly and does **not** manage its
10
+ * lifecycle (you are responsible for calling `start()` / `shutdown()`).
11
+ */
12
+ client?: LiteguardClient;
13
+ /**
14
+ * Your project client key ID. When provided (without `client`), the
15
+ * provider automatically creates, starts, and shuts down a managed client.
16
+ */
17
+ projectClientKeyId?: string;
18
+ /** Optional SDK configuration overrides for the managed client. */
19
+ options?: ClientOptions;
20
+ /** Optional default properties applied to the active client scope. */
21
+ properties?: Properties;
22
+ environment?: ClientOptions['environment'];
23
+ fallback?: ClientOptions['fallback'];
24
+ refreshRateSeconds?: ClientOptions['refreshRateSeconds'];
25
+ flushRateSeconds?: ClientOptions['flushRateSeconds'];
26
+ flushSize?: ClientOptions['flushSize'];
27
+ backendUrl?: ClientOptions['backendUrl'];
28
+ quiet?: ClientOptions['quiet'];
29
+ httpTimeoutSeconds?: ClientOptions['httpTimeoutSeconds'];
30
+ flushBufferMultiplier?: ClientOptions['flushBufferMultiplier'];
31
+ disableMeasurement?: ClientOptions['disableMeasurement'];
32
+ /**
33
+ * React node rendered while the managed client is fetching its initial
34
+ * guard bundle. Defaults to `null` (render nothing).
35
+ */
36
+ loading?: ReactNode;
37
+ /** Application subtree that gains access to the Liteguard client via context. */
38
+ children: ReactNode;
39
+ };
40
+ /**
41
+ * React context provider that makes a Liteguard client available to the
42
+ * component tree via React context.
43
+ *
44
+ * **Managed mode** — pass `projectClientKeyId` (and optional `options`) to
45
+ * have the provider create, start, and shut down a client automatically.
46
+ * While the initial guard bundle is loading, the optional `loading` subtree
47
+ * is rendered instead of `children`.
48
+ *
49
+ * **External mode** — pass a pre-configured `client` instance and the
50
+ * provider simply makes it available through context without managing its
51
+ * lifecycle.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * // Managed mode
56
+ * <LiteguardProvider projectClientKeyId="pk_live_..." loading={<Spinner />}>
57
+ * <App />
58
+ * </LiteguardProvider>
59
+ *
60
+ * // External mode
61
+ * const client = new LiteguardClient('pk_live_...');
62
+ * await client.start();
63
+ * <LiteguardProvider client={client}>
64
+ * <App />
65
+ * </LiteguardProvider>
66
+ * ```
67
+ */
68
+ declare function LiteguardProvider({ client, projectClientKeyId, options, properties, environment, fallback, refreshRateSeconds, flushRateSeconds, flushSize, backendUrl, quiet, httpTimeoutSeconds, flushBufferMultiplier, disableMeasurement, loading, children, }: LiteguardProviderProps): react_jsx_runtime.JSX.Element;
69
+ /**
70
+ * Return the nearest Liteguard client from React context, or `null` if no
71
+ * provider is present.
72
+ *
73
+ * Prefer {@link useLiteguardClient} when you need to assert that a client
74
+ * is available.
75
+ *
76
+ * @returns The {@link LiteguardClient}, or `null`.
77
+ */
78
+ declare function useLiteguard(): LiteguardClient | null;
79
+ /**
80
+ * Return the nearest Liteguard client from React context. Throws if no
81
+ * client is available — ensures the component is rendered inside a
82
+ * {@link LiteguardProvider}.
83
+ *
84
+ * @returns The {@link LiteguardClient}.
85
+ * @throws {Error} When no Liteguard client is reachable.
86
+ */
87
+ declare function useLiteguardClient(): LiteguardClient;
88
+ /**
89
+ * Returns `true` once the Liteguard client has fetched its initial guard
90
+ * bundle. Re-renders the component automatically when the ready state
91
+ * changes.
92
+ *
93
+ * @returns `true` when the client is ready, `false` otherwise.
94
+ */
95
+ declare function useLiteguardReady(): boolean;
96
+ /**
97
+ * Return `true` if the named guard is open for the current context.
98
+ * Re-renders automatically whenever the guard bundle is refreshed.
99
+ *
100
+ * Internally uses `peekIsOpen` so guard checks during renders do **not**
101
+ * emit telemetry signals or consume rate-limit slots. For guarded
102
+ * side-effects that need telemetry, call
103
+ * `useLiteguardClient().executeIfOpen()` in an event handler or effect.
104
+ *
105
+ * @param name - Guard name to evaluate (e.g. `"feature.beta"`).
106
+ * @param options - Optional per-call overrides (extra properties, fallback).
107
+ * @returns `true` if the guard is open, `false` otherwise.
108
+ *
109
+ * @example
110
+ * ```tsx
111
+ * function Banner() {
112
+ * const showBeta = useIsOpen('promo.banner');
113
+ * if (!showBeta) return null;
114
+ * return <div>Try our new beta!</div>;
115
+ * }
116
+ * ```
117
+ */
118
+ declare function useIsOpen(name: string, options?: Partial<Options>): boolean;
119
+
120
+ export { LiteguardProvider, type LiteguardProviderProps, useIsOpen, useLiteguard, useLiteguardClient, useLiteguardReady };
@@ -0,0 +1,120 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { LiteguardClient, ClientOptions, Properties, Options } from '@liteguard/liteguard-browser';
4
+
5
+ /** Props for {@link LiteguardProvider}. */
6
+ type LiteguardProviderProps = {
7
+ /**
8
+ * An externally created {@link LiteguardClient} instance. When provided,
9
+ * the provider uses this client directly and does **not** manage its
10
+ * lifecycle (you are responsible for calling `start()` / `shutdown()`).
11
+ */
12
+ client?: LiteguardClient;
13
+ /**
14
+ * Your project client key ID. When provided (without `client`), the
15
+ * provider automatically creates, starts, and shuts down a managed client.
16
+ */
17
+ projectClientKeyId?: string;
18
+ /** Optional SDK configuration overrides for the managed client. */
19
+ options?: ClientOptions;
20
+ /** Optional default properties applied to the active client scope. */
21
+ properties?: Properties;
22
+ environment?: ClientOptions['environment'];
23
+ fallback?: ClientOptions['fallback'];
24
+ refreshRateSeconds?: ClientOptions['refreshRateSeconds'];
25
+ flushRateSeconds?: ClientOptions['flushRateSeconds'];
26
+ flushSize?: ClientOptions['flushSize'];
27
+ backendUrl?: ClientOptions['backendUrl'];
28
+ quiet?: ClientOptions['quiet'];
29
+ httpTimeoutSeconds?: ClientOptions['httpTimeoutSeconds'];
30
+ flushBufferMultiplier?: ClientOptions['flushBufferMultiplier'];
31
+ disableMeasurement?: ClientOptions['disableMeasurement'];
32
+ /**
33
+ * React node rendered while the managed client is fetching its initial
34
+ * guard bundle. Defaults to `null` (render nothing).
35
+ */
36
+ loading?: ReactNode;
37
+ /** Application subtree that gains access to the Liteguard client via context. */
38
+ children: ReactNode;
39
+ };
40
+ /**
41
+ * React context provider that makes a Liteguard client available to the
42
+ * component tree via React context.
43
+ *
44
+ * **Managed mode** — pass `projectClientKeyId` (and optional `options`) to
45
+ * have the provider create, start, and shut down a client automatically.
46
+ * While the initial guard bundle is loading, the optional `loading` subtree
47
+ * is rendered instead of `children`.
48
+ *
49
+ * **External mode** — pass a pre-configured `client` instance and the
50
+ * provider simply makes it available through context without managing its
51
+ * lifecycle.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * // Managed mode
56
+ * <LiteguardProvider projectClientKeyId="pk_live_..." loading={<Spinner />}>
57
+ * <App />
58
+ * </LiteguardProvider>
59
+ *
60
+ * // External mode
61
+ * const client = new LiteguardClient('pk_live_...');
62
+ * await client.start();
63
+ * <LiteguardProvider client={client}>
64
+ * <App />
65
+ * </LiteguardProvider>
66
+ * ```
67
+ */
68
+ declare function LiteguardProvider({ client, projectClientKeyId, options, properties, environment, fallback, refreshRateSeconds, flushRateSeconds, flushSize, backendUrl, quiet, httpTimeoutSeconds, flushBufferMultiplier, disableMeasurement, loading, children, }: LiteguardProviderProps): react_jsx_runtime.JSX.Element;
69
+ /**
70
+ * Return the nearest Liteguard client from React context, or `null` if no
71
+ * provider is present.
72
+ *
73
+ * Prefer {@link useLiteguardClient} when you need to assert that a client
74
+ * is available.
75
+ *
76
+ * @returns The {@link LiteguardClient}, or `null`.
77
+ */
78
+ declare function useLiteguard(): LiteguardClient | null;
79
+ /**
80
+ * Return the nearest Liteguard client from React context. Throws if no
81
+ * client is available — ensures the component is rendered inside a
82
+ * {@link LiteguardProvider}.
83
+ *
84
+ * @returns The {@link LiteguardClient}.
85
+ * @throws {Error} When no Liteguard client is reachable.
86
+ */
87
+ declare function useLiteguardClient(): LiteguardClient;
88
+ /**
89
+ * Returns `true` once the Liteguard client has fetched its initial guard
90
+ * bundle. Re-renders the component automatically when the ready state
91
+ * changes.
92
+ *
93
+ * @returns `true` when the client is ready, `false` otherwise.
94
+ */
95
+ declare function useLiteguardReady(): boolean;
96
+ /**
97
+ * Return `true` if the named guard is open for the current context.
98
+ * Re-renders automatically whenever the guard bundle is refreshed.
99
+ *
100
+ * Internally uses `peekIsOpen` so guard checks during renders do **not**
101
+ * emit telemetry signals or consume rate-limit slots. For guarded
102
+ * side-effects that need telemetry, call
103
+ * `useLiteguardClient().executeIfOpen()` in an event handler or effect.
104
+ *
105
+ * @param name - Guard name to evaluate (e.g. `"feature.beta"`).
106
+ * @param options - Optional per-call overrides (extra properties, fallback).
107
+ * @returns `true` if the guard is open, `false` otherwise.
108
+ *
109
+ * @example
110
+ * ```tsx
111
+ * function Banner() {
112
+ * const showBeta = useIsOpen('promo.banner');
113
+ * if (!showBeta) return null;
114
+ * return <div>Try our new beta!</div>;
115
+ * }
116
+ * ```
117
+ */
118
+ declare function useIsOpen(name: string, options?: Partial<Options>): boolean;
119
+
120
+ export { LiteguardProvider, type LiteguardProviderProps, useIsOpen, useLiteguard, useLiteguardClient, useLiteguardReady };
package/dist/index.js ADDED
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ LiteguardProvider: () => LiteguardProvider,
24
+ useIsOpen: () => useIsOpen,
25
+ useLiteguard: () => useLiteguard,
26
+ useLiteguardClient: () => useLiteguardClient,
27
+ useLiteguardReady: () => useLiteguardReady
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/provider.tsx
32
+ var import_react = require("react");
33
+ var import_liteguard_browser = require("@liteguard/liteguard-browser");
34
+ var import_jsx_runtime = require("react/jsx-runtime");
35
+ var LiteguardContext = (0, import_react.createContext)(null);
36
+ function subscribeNoop() {
37
+ return () => {
38
+ };
39
+ }
40
+ function useOptionalClient() {
41
+ return (0, import_react.useContext)(LiteguardContext);
42
+ }
43
+ function useClientValue(client, getSnapshot, fallback) {
44
+ return (0, import_react.useSyncExternalStore)(
45
+ client ? client.subscribe.bind(client) : subscribeNoop,
46
+ client ? getSnapshot : () => fallback,
47
+ () => fallback
48
+ );
49
+ }
50
+ function LiteguardProvider({
51
+ client,
52
+ projectClientKeyId,
53
+ options,
54
+ properties,
55
+ environment,
56
+ fallback,
57
+ refreshRateSeconds,
58
+ flushRateSeconds,
59
+ flushSize,
60
+ backendUrl,
61
+ quiet,
62
+ httpTimeoutSeconds,
63
+ flushBufferMultiplier,
64
+ disableMeasurement,
65
+ loading = null,
66
+ children
67
+ }) {
68
+ const resolvedOptions = (0, import_react.useMemo)(
69
+ () => ({
70
+ ...options ?? {},
71
+ ...environment !== void 0 ? { environment } : {},
72
+ ...fallback !== void 0 ? { fallback } : {},
73
+ ...refreshRateSeconds !== void 0 ? { refreshRateSeconds } : {},
74
+ ...flushRateSeconds !== void 0 ? { flushRateSeconds } : {},
75
+ ...flushSize !== void 0 ? { flushSize } : {},
76
+ ...backendUrl !== void 0 ? { backendUrl } : {},
77
+ ...quiet !== void 0 ? { quiet } : {},
78
+ ...httpTimeoutSeconds !== void 0 ? { httpTimeoutSeconds } : {},
79
+ ...flushBufferMultiplier !== void 0 ? { flushBufferMultiplier } : {},
80
+ ...disableMeasurement !== void 0 ? { disableMeasurement } : {}
81
+ }),
82
+ [
83
+ options,
84
+ environment,
85
+ fallback,
86
+ refreshRateSeconds,
87
+ flushRateSeconds,
88
+ flushSize,
89
+ backendUrl,
90
+ quiet,
91
+ httpTimeoutSeconds,
92
+ flushBufferMultiplier,
93
+ disableMeasurement
94
+ ]
95
+ );
96
+ const optionsKey = (0, import_react.useMemo)(() => JSON.stringify(resolvedOptions), [resolvedOptions]);
97
+ const propertiesKey = (0, import_react.useMemo)(() => JSON.stringify(properties ?? {}), [properties]);
98
+ const [managedClient, setManagedClient] = (0, import_react.useState)(() => client ?? null);
99
+ const [error, setError] = (0, import_react.useState)(null);
100
+ (0, import_react.useEffect)(() => {
101
+ if (client) {
102
+ setManagedClient(client);
103
+ return;
104
+ }
105
+ if (!projectClientKeyId) {
106
+ setManagedClient(null);
107
+ return;
108
+ }
109
+ let cancelled = false;
110
+ const nextClient = new import_liteguard_browser.LiteguardClient(projectClientKeyId, resolvedOptions);
111
+ setManagedClient(nextClient);
112
+ setError(null);
113
+ const unsubscribe = nextClient.subscribe(() => {
114
+ if (!cancelled) {
115
+ setManagedClient(nextClient);
116
+ }
117
+ });
118
+ void nextClient.start().catch((startError) => {
119
+ if (!cancelled) {
120
+ setError(startError);
121
+ }
122
+ });
123
+ return () => {
124
+ cancelled = true;
125
+ unsubscribe();
126
+ void nextClient.shutdown();
127
+ };
128
+ }, [client, projectClientKeyId, optionsKey, resolvedOptions]);
129
+ const activeClient = client ?? managedClient;
130
+ const ready = useClientValue(activeClient, () => activeClient?.isReady() ?? false, false);
131
+ (0, import_react.useEffect)(() => {
132
+ if (!activeClient) {
133
+ return;
134
+ }
135
+ activeClient.resetProperties();
136
+ if (properties && Object.keys(properties).length > 0) {
137
+ activeClient.addProperties(properties);
138
+ }
139
+ }, [activeClient, propertiesKey, properties]);
140
+ if (error) {
141
+ throw error;
142
+ }
143
+ if (projectClientKeyId && !ready) {
144
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: loading });
145
+ }
146
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LiteguardContext.Provider, { value: activeClient, children });
147
+ }
148
+ function useLiteguard() {
149
+ return useOptionalClient();
150
+ }
151
+ function useLiteguardClient() {
152
+ const client = useOptionalClient();
153
+ if (!client) {
154
+ throw new Error(
155
+ "[liteguard] No Liteguard React client available. Wrap your app in LiteguardProvider."
156
+ );
157
+ }
158
+ return client;
159
+ }
160
+ function useLiteguardReady() {
161
+ const client = useOptionalClient();
162
+ return useClientValue(client, () => client?.isReady() ?? false, false);
163
+ }
164
+ function useIsOpen(name, options) {
165
+ const client = useOptionalClient();
166
+ const fallback = options?.fallback ?? false;
167
+ return useClientValue(client, () => client?.peekIsOpen(name, options) ?? fallback, fallback);
168
+ }
169
+ // Annotate the CommonJS export names for ESM import in node:
170
+ 0 && (module.exports = {
171
+ LiteguardProvider,
172
+ useIsOpen,
173
+ useLiteguard,
174
+ useLiteguardClient,
175
+ useLiteguardReady
176
+ });
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/provider.tsx"],"sourcesContent":["export {\n LiteguardProvider,\n useIsOpen,\n useLiteguard,\n useLiteguardClient,\n useLiteguardReady,\n} from './provider.js';\nexport type { LiteguardProviderProps } from './provider.js';\n","import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react';\nimport { LiteguardClient } from '@liteguard/liteguard-browser';\nimport type { ClientOptions, Options, Properties } from '@liteguard/liteguard-browser';\n\nconst LiteguardContext = createContext<LiteguardClient | null>(null);\n\n/** Props for {@link LiteguardProvider}. */\nexport type LiteguardProviderProps = {\n /**\n * An externally created {@link LiteguardClient} instance. When provided,\n * the provider uses this client directly and does **not** manage its\n * lifecycle (you are responsible for calling `start()` / `shutdown()`).\n */\n client?: LiteguardClient;\n /**\n * Your project client key ID. When provided (without `client`), the\n * provider automatically creates, starts, and shuts down a managed client.\n */\n projectClientKeyId?: string;\n /** Optional SDK configuration overrides for the managed client. */\n options?: ClientOptions;\n /** Optional default properties applied to the active client scope. */\n properties?: Properties;\n environment?: ClientOptions['environment'];\n fallback?: ClientOptions['fallback'];\n refreshRateSeconds?: ClientOptions['refreshRateSeconds'];\n flushRateSeconds?: ClientOptions['flushRateSeconds'];\n flushSize?: ClientOptions['flushSize'];\n backendUrl?: ClientOptions['backendUrl'];\n quiet?: ClientOptions['quiet'];\n httpTimeoutSeconds?: ClientOptions['httpTimeoutSeconds'];\n flushBufferMultiplier?: ClientOptions['flushBufferMultiplier'];\n disableMeasurement?: ClientOptions['disableMeasurement'];\n /**\n * React node rendered while the managed client is fetching its initial\n * guard bundle. Defaults to `null` (render nothing).\n */\n loading?: ReactNode;\n /** Application subtree that gains access to the Liteguard client via context. */\n children: ReactNode;\n};\n\n/** Provide a stable no-op subscription for components rendered without a client. */\nfunction subscribeNoop(): () => void {\n return () => {};\n}\n\n/** Resolve the nearest Liteguard client from React context. */\nfunction useOptionalClient(): LiteguardClient | null {\n return useContext(LiteguardContext);\n}\n\n/** Subscribe a component to client refreshes and fall back cleanly when absent. */\nfunction useClientValue<T>(client: LiteguardClient | null, getSnapshot: () => T, fallback: T): T {\n return useSyncExternalStore(\n client ? client.subscribe.bind(client) : subscribeNoop,\n client ? getSnapshot : () => fallback,\n () => fallback,\n );\n}\n\n/**\n * React context provider that makes a Liteguard client available to the\n * component tree via React context.\n *\n * **Managed mode** — pass `projectClientKeyId` (and optional `options`) to\n * have the provider create, start, and shut down a client automatically.\n * While the initial guard bundle is loading, the optional `loading` subtree\n * is rendered instead of `children`.\n *\n * **External mode** — pass a pre-configured `client` instance and the\n * provider simply makes it available through context without managing its\n * lifecycle.\n *\n * @example\n * ```tsx\n * // Managed mode\n * <LiteguardProvider projectClientKeyId=\"pk_live_...\" loading={<Spinner />}>\n * <App />\n * </LiteguardProvider>\n *\n * // External mode\n * const client = new LiteguardClient('pk_live_...');\n * await client.start();\n * <LiteguardProvider client={client}>\n * <App />\n * </LiteguardProvider>\n * ```\n */\nexport function LiteguardProvider({\n client,\n projectClientKeyId,\n options,\n properties,\n environment,\n fallback,\n refreshRateSeconds,\n flushRateSeconds,\n flushSize,\n backendUrl,\n quiet,\n httpTimeoutSeconds,\n flushBufferMultiplier,\n disableMeasurement,\n loading = null,\n children,\n}: LiteguardProviderProps) {\n const resolvedOptions = useMemo<ClientOptions>(\n () => ({\n ...(options ?? {}),\n ...(environment !== undefined ? { environment } : {}),\n ...(fallback !== undefined ? { fallback } : {}),\n ...(refreshRateSeconds !== undefined ? { refreshRateSeconds } : {}),\n ...(flushRateSeconds !== undefined ? { flushRateSeconds } : {}),\n ...(flushSize !== undefined ? { flushSize } : {}),\n ...(backendUrl !== undefined ? { backendUrl } : {}),\n ...(quiet !== undefined ? { quiet } : {}),\n ...(httpTimeoutSeconds !== undefined ? { httpTimeoutSeconds } : {}),\n ...(flushBufferMultiplier !== undefined ? { flushBufferMultiplier } : {}),\n ...(disableMeasurement !== undefined ? { disableMeasurement } : {}),\n }),\n [\n options,\n environment,\n fallback,\n refreshRateSeconds,\n flushRateSeconds,\n flushSize,\n backendUrl,\n quiet,\n httpTimeoutSeconds,\n flushBufferMultiplier,\n disableMeasurement,\n ],\n );\n const optionsKey = useMemo(() => JSON.stringify(resolvedOptions), [resolvedOptions]);\n const propertiesKey = useMemo(() => JSON.stringify(properties ?? {}), [properties]);\n const [managedClient, setManagedClient] = useState<LiteguardClient | null>(() => client ?? null);\n const [error, setError] = useState<unknown>(null);\n\n useEffect(() => {\n if (client) {\n setManagedClient(client);\n return;\n }\n\n if (!projectClientKeyId) {\n setManagedClient(null);\n return;\n }\n\n let cancelled = false;\n const nextClient = new LiteguardClient(projectClientKeyId, resolvedOptions);\n setManagedClient(nextClient);\n setError(null);\n\n const unsubscribe = nextClient.subscribe(() => {\n if (!cancelled) {\n setManagedClient(nextClient);\n }\n });\n\n void nextClient.start().catch((startError) => {\n if (!cancelled) {\n setError(startError);\n }\n });\n\n return () => {\n cancelled = true;\n unsubscribe();\n void nextClient.shutdown();\n };\n }, [client, projectClientKeyId, optionsKey, resolvedOptions]);\n\n const activeClient = client ?? managedClient;\n const ready = useClientValue(activeClient, () => activeClient?.isReady() ?? false, false);\n\n useEffect(() => {\n if (!activeClient) {\n return;\n }\n\n activeClient.resetProperties();\n if (properties && Object.keys(properties).length > 0) {\n activeClient.addProperties(properties);\n }\n }, [activeClient, propertiesKey, properties]);\n\n if (error) {\n throw error;\n }\n if (projectClientKeyId && !ready) {\n return <>{loading}</>;\n }\n\n return <LiteguardContext.Provider value={activeClient}>{children}</LiteguardContext.Provider>;\n}\n\n/**\n * Return the nearest Liteguard client from React context, or `null` if no\n * provider is present.\n *\n * Prefer {@link useLiteguardClient} when you need to assert that a client\n * is available.\n *\n * @returns The {@link LiteguardClient}, or `null`.\n */\nexport function useLiteguard(): LiteguardClient | null {\n return useOptionalClient();\n}\n\n/**\n * Return the nearest Liteguard client from React context. Throws if no\n * client is available — ensures the component is rendered inside a\n * {@link LiteguardProvider}.\n *\n * @returns The {@link LiteguardClient}.\n * @throws {Error} When no Liteguard client is reachable.\n */\nexport function useLiteguardClient(): LiteguardClient {\n const client = useOptionalClient();\n if (!client) {\n throw new Error(\n '[liteguard] No Liteguard React client available. Wrap your app in LiteguardProvider.',\n );\n }\n return client;\n}\n\n/**\n * Returns `true` once the Liteguard client has fetched its initial guard\n * bundle. Re-renders the component automatically when the ready state\n * changes.\n *\n * @returns `true` when the client is ready, `false` otherwise.\n */\nexport function useLiteguardReady(): boolean {\n const client = useOptionalClient();\n return useClientValue(client, () => client?.isReady() ?? false, false);\n}\n\n/**\n * Return `true` if the named guard is open for the current context.\n * Re-renders automatically whenever the guard bundle is refreshed.\n *\n * Internally uses `peekIsOpen` so guard checks during renders do **not**\n * emit telemetry signals or consume rate-limit slots. For guarded\n * side-effects that need telemetry, call\n * `useLiteguardClient().executeIfOpen()` in an event handler or effect.\n *\n * @param name - Guard name to evaluate (e.g. `\"feature.beta\"`).\n * @param options - Optional per-call overrides (extra properties, fallback).\n * @returns `true` if the guard is open, `false` otherwise.\n *\n * @example\n * ```tsx\n * function Banner() {\n * const showBeta = useIsOpen('promo.banner');\n * if (!showBeta) return null;\n * return <div>Try our new beta!</div>;\n * }\n * ```\n */\nexport function useIsOpen(name: string, options?: Partial<Options>): boolean {\n const client = useOptionalClient();\n const fallback = options?.fallback ?? false;\n return useClientValue(client, () => client?.peekIsOpen(name, options) ?? fallback, fallback);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAQO;AACP,+BAAgC;AAgMrB;AA7LX,IAAM,uBAAmB,4BAAsC,IAAI;AAuCnE,SAAS,gBAA4B;AACnC,SAAO,MAAM;AAAA,EAAC;AAChB;AAGA,SAAS,oBAA4C;AACnD,aAAO,yBAAW,gBAAgB;AACpC;AAGA,SAAS,eAAkB,QAAgC,aAAsB,UAAgB;AAC/F,aAAO;AAAA,IACL,SAAS,OAAO,UAAU,KAAK,MAAM,IAAI;AAAA,IACzC,SAAS,cAAc,MAAM;AAAA,IAC7B,MAAM;AAAA,EACR;AACF;AA8BO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AACF,GAA2B;AACzB,QAAM,sBAAkB;AAAA,IACtB,OAAO;AAAA,MACL,GAAI,WAAW,CAAC;AAAA,MAChB,GAAI,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;AAAA,MACnD,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,uBAAuB,SAAY,EAAE,mBAAmB,IAAI,CAAC;AAAA,MACjE,GAAI,qBAAqB,SAAY,EAAE,iBAAiB,IAAI,CAAC;AAAA,MAC7D,GAAI,cAAc,SAAY,EAAE,UAAU,IAAI,CAAC;AAAA,MAC/C,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,MACvC,GAAI,uBAAuB,SAAY,EAAE,mBAAmB,IAAI,CAAC;AAAA,MACjE,GAAI,0BAA0B,SAAY,EAAE,sBAAsB,IAAI,CAAC;AAAA,MACvE,GAAI,uBAAuB,SAAY,EAAE,mBAAmB,IAAI,CAAC;AAAA,IACnE;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,iBAAa,sBAAQ,MAAM,KAAK,UAAU,eAAe,GAAG,CAAC,eAAe,CAAC;AACnF,QAAM,oBAAgB,sBAAQ,MAAM,KAAK,UAAU,cAAc,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC;AAClF,QAAM,CAAC,eAAe,gBAAgB,QAAI,uBAAiC,MAAM,UAAU,IAAI;AAC/F,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAkB,IAAI;AAEhD,8BAAU,MAAM;AACd,QAAI,QAAQ;AACV,uBAAiB,MAAM;AACvB;AAAA,IACF;AAEA,QAAI,CAAC,oBAAoB;AACvB,uBAAiB,IAAI;AACrB;AAAA,IACF;AAEA,QAAI,YAAY;AAChB,UAAM,aAAa,IAAI,yCAAgB,oBAAoB,eAAe;AAC1E,qBAAiB,UAAU;AAC3B,aAAS,IAAI;AAEb,UAAM,cAAc,WAAW,UAAU,MAAM;AAC7C,UAAI,CAAC,WAAW;AACd,yBAAiB,UAAU;AAAA,MAC7B;AAAA,IACF,CAAC;AAED,SAAK,WAAW,MAAM,EAAE,MAAM,CAAC,eAAe;AAC5C,UAAI,CAAC,WAAW;AACd,iBAAS,UAAU;AAAA,MACrB;AAAA,IACF,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AACZ,kBAAY;AACZ,WAAK,WAAW,SAAS;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,QAAQ,oBAAoB,YAAY,eAAe,CAAC;AAE5D,QAAM,eAAe,UAAU;AAC/B,QAAM,QAAQ,eAAe,cAAc,MAAM,cAAc,QAAQ,KAAK,OAAO,KAAK;AAExF,8BAAU,MAAM;AACd,QAAI,CAAC,cAAc;AACjB;AAAA,IACF;AAEA,iBAAa,gBAAgB;AAC7B,QAAI,cAAc,OAAO,KAAK,UAAU,EAAE,SAAS,GAAG;AACpD,mBAAa,cAAc,UAAU;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,cAAc,eAAe,UAAU,CAAC;AAE5C,MAAI,OAAO;AACT,UAAM;AAAA,EACR;AACA,MAAI,sBAAsB,CAAC,OAAO;AAChC,WAAO,2EAAG,mBAAQ;AAAA,EACpB;AAEA,SAAO,4CAAC,iBAAiB,UAAjB,EAA0B,OAAO,cAAe,UAAS;AACnE;AAWO,SAAS,eAAuC;AACrD,SAAO,kBAAkB;AAC3B;AAUO,SAAS,qBAAsC;AACpD,QAAM,SAAS,kBAAkB;AACjC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AASO,SAAS,oBAA6B;AAC3C,QAAM,SAAS,kBAAkB;AACjC,SAAO,eAAe,QAAQ,MAAM,QAAQ,QAAQ,KAAK,OAAO,KAAK;AACvE;AAwBO,SAAS,UAAU,MAAc,SAAqC;AAC3E,QAAM,SAAS,kBAAkB;AACjC,QAAM,WAAW,SAAS,YAAY;AACtC,SAAO,eAAe,QAAQ,MAAM,QAAQ,WAAW,MAAM,OAAO,KAAK,UAAU,QAAQ;AAC7F;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,153 @@
1
+ // src/provider.tsx
2
+ import {
3
+ createContext,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ useSyncExternalStore
9
+ } from "react";
10
+ import { LiteguardClient } from "@liteguard/liteguard-browser";
11
+ import { Fragment, jsx } from "react/jsx-runtime";
12
+ var LiteguardContext = createContext(null);
13
+ function subscribeNoop() {
14
+ return () => {
15
+ };
16
+ }
17
+ function useOptionalClient() {
18
+ return useContext(LiteguardContext);
19
+ }
20
+ function useClientValue(client, getSnapshot, fallback) {
21
+ return useSyncExternalStore(
22
+ client ? client.subscribe.bind(client) : subscribeNoop,
23
+ client ? getSnapshot : () => fallback,
24
+ () => fallback
25
+ );
26
+ }
27
+ function LiteguardProvider({
28
+ client,
29
+ projectClientKeyId,
30
+ options,
31
+ properties,
32
+ environment,
33
+ fallback,
34
+ refreshRateSeconds,
35
+ flushRateSeconds,
36
+ flushSize,
37
+ backendUrl,
38
+ quiet,
39
+ httpTimeoutSeconds,
40
+ flushBufferMultiplier,
41
+ disableMeasurement,
42
+ loading = null,
43
+ children
44
+ }) {
45
+ const resolvedOptions = useMemo(
46
+ () => ({
47
+ ...options ?? {},
48
+ ...environment !== void 0 ? { environment } : {},
49
+ ...fallback !== void 0 ? { fallback } : {},
50
+ ...refreshRateSeconds !== void 0 ? { refreshRateSeconds } : {},
51
+ ...flushRateSeconds !== void 0 ? { flushRateSeconds } : {},
52
+ ...flushSize !== void 0 ? { flushSize } : {},
53
+ ...backendUrl !== void 0 ? { backendUrl } : {},
54
+ ...quiet !== void 0 ? { quiet } : {},
55
+ ...httpTimeoutSeconds !== void 0 ? { httpTimeoutSeconds } : {},
56
+ ...flushBufferMultiplier !== void 0 ? { flushBufferMultiplier } : {},
57
+ ...disableMeasurement !== void 0 ? { disableMeasurement } : {}
58
+ }),
59
+ [
60
+ options,
61
+ environment,
62
+ fallback,
63
+ refreshRateSeconds,
64
+ flushRateSeconds,
65
+ flushSize,
66
+ backendUrl,
67
+ quiet,
68
+ httpTimeoutSeconds,
69
+ flushBufferMultiplier,
70
+ disableMeasurement
71
+ ]
72
+ );
73
+ const optionsKey = useMemo(() => JSON.stringify(resolvedOptions), [resolvedOptions]);
74
+ const propertiesKey = useMemo(() => JSON.stringify(properties ?? {}), [properties]);
75
+ const [managedClient, setManagedClient] = useState(() => client ?? null);
76
+ const [error, setError] = useState(null);
77
+ useEffect(() => {
78
+ if (client) {
79
+ setManagedClient(client);
80
+ return;
81
+ }
82
+ if (!projectClientKeyId) {
83
+ setManagedClient(null);
84
+ return;
85
+ }
86
+ let cancelled = false;
87
+ const nextClient = new LiteguardClient(projectClientKeyId, resolvedOptions);
88
+ setManagedClient(nextClient);
89
+ setError(null);
90
+ const unsubscribe = nextClient.subscribe(() => {
91
+ if (!cancelled) {
92
+ setManagedClient(nextClient);
93
+ }
94
+ });
95
+ void nextClient.start().catch((startError) => {
96
+ if (!cancelled) {
97
+ setError(startError);
98
+ }
99
+ });
100
+ return () => {
101
+ cancelled = true;
102
+ unsubscribe();
103
+ void nextClient.shutdown();
104
+ };
105
+ }, [client, projectClientKeyId, optionsKey, resolvedOptions]);
106
+ const activeClient = client ?? managedClient;
107
+ const ready = useClientValue(activeClient, () => activeClient?.isReady() ?? false, false);
108
+ useEffect(() => {
109
+ if (!activeClient) {
110
+ return;
111
+ }
112
+ activeClient.resetProperties();
113
+ if (properties && Object.keys(properties).length > 0) {
114
+ activeClient.addProperties(properties);
115
+ }
116
+ }, [activeClient, propertiesKey, properties]);
117
+ if (error) {
118
+ throw error;
119
+ }
120
+ if (projectClientKeyId && !ready) {
121
+ return /* @__PURE__ */ jsx(Fragment, { children: loading });
122
+ }
123
+ return /* @__PURE__ */ jsx(LiteguardContext.Provider, { value: activeClient, children });
124
+ }
125
+ function useLiteguard() {
126
+ return useOptionalClient();
127
+ }
128
+ function useLiteguardClient() {
129
+ const client = useOptionalClient();
130
+ if (!client) {
131
+ throw new Error(
132
+ "[liteguard] No Liteguard React client available. Wrap your app in LiteguardProvider."
133
+ );
134
+ }
135
+ return client;
136
+ }
137
+ function useLiteguardReady() {
138
+ const client = useOptionalClient();
139
+ return useClientValue(client, () => client?.isReady() ?? false, false);
140
+ }
141
+ function useIsOpen(name, options) {
142
+ const client = useOptionalClient();
143
+ const fallback = options?.fallback ?? false;
144
+ return useClientValue(client, () => client?.peekIsOpen(name, options) ?? fallback, fallback);
145
+ }
146
+ export {
147
+ LiteguardProvider,
148
+ useIsOpen,
149
+ useLiteguard,
150
+ useLiteguardClient,
151
+ useLiteguardReady
152
+ };
153
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react';\nimport { LiteguardClient } from '@liteguard/liteguard-browser';\nimport type { ClientOptions, Options, Properties } from '@liteguard/liteguard-browser';\n\nconst LiteguardContext = createContext<LiteguardClient | null>(null);\n\n/** Props for {@link LiteguardProvider}. */\nexport type LiteguardProviderProps = {\n /**\n * An externally created {@link LiteguardClient} instance. When provided,\n * the provider uses this client directly and does **not** manage its\n * lifecycle (you are responsible for calling `start()` / `shutdown()`).\n */\n client?: LiteguardClient;\n /**\n * Your project client key ID. When provided (without `client`), the\n * provider automatically creates, starts, and shuts down a managed client.\n */\n projectClientKeyId?: string;\n /** Optional SDK configuration overrides for the managed client. */\n options?: ClientOptions;\n /** Optional default properties applied to the active client scope. */\n properties?: Properties;\n environment?: ClientOptions['environment'];\n fallback?: ClientOptions['fallback'];\n refreshRateSeconds?: ClientOptions['refreshRateSeconds'];\n flushRateSeconds?: ClientOptions['flushRateSeconds'];\n flushSize?: ClientOptions['flushSize'];\n backendUrl?: ClientOptions['backendUrl'];\n quiet?: ClientOptions['quiet'];\n httpTimeoutSeconds?: ClientOptions['httpTimeoutSeconds'];\n flushBufferMultiplier?: ClientOptions['flushBufferMultiplier'];\n disableMeasurement?: ClientOptions['disableMeasurement'];\n /**\n * React node rendered while the managed client is fetching its initial\n * guard bundle. Defaults to `null` (render nothing).\n */\n loading?: ReactNode;\n /** Application subtree that gains access to the Liteguard client via context. */\n children: ReactNode;\n};\n\n/** Provide a stable no-op subscription for components rendered without a client. */\nfunction subscribeNoop(): () => void {\n return () => {};\n}\n\n/** Resolve the nearest Liteguard client from React context. */\nfunction useOptionalClient(): LiteguardClient | null {\n return useContext(LiteguardContext);\n}\n\n/** Subscribe a component to client refreshes and fall back cleanly when absent. */\nfunction useClientValue<T>(client: LiteguardClient | null, getSnapshot: () => T, fallback: T): T {\n return useSyncExternalStore(\n client ? client.subscribe.bind(client) : subscribeNoop,\n client ? getSnapshot : () => fallback,\n () => fallback,\n );\n}\n\n/**\n * React context provider that makes a Liteguard client available to the\n * component tree via React context.\n *\n * **Managed mode** — pass `projectClientKeyId` (and optional `options`) to\n * have the provider create, start, and shut down a client automatically.\n * While the initial guard bundle is loading, the optional `loading` subtree\n * is rendered instead of `children`.\n *\n * **External mode** — pass a pre-configured `client` instance and the\n * provider simply makes it available through context without managing its\n * lifecycle.\n *\n * @example\n * ```tsx\n * // Managed mode\n * <LiteguardProvider projectClientKeyId=\"pk_live_...\" loading={<Spinner />}>\n * <App />\n * </LiteguardProvider>\n *\n * // External mode\n * const client = new LiteguardClient('pk_live_...');\n * await client.start();\n * <LiteguardProvider client={client}>\n * <App />\n * </LiteguardProvider>\n * ```\n */\nexport function LiteguardProvider({\n client,\n projectClientKeyId,\n options,\n properties,\n environment,\n fallback,\n refreshRateSeconds,\n flushRateSeconds,\n flushSize,\n backendUrl,\n quiet,\n httpTimeoutSeconds,\n flushBufferMultiplier,\n disableMeasurement,\n loading = null,\n children,\n}: LiteguardProviderProps) {\n const resolvedOptions = useMemo<ClientOptions>(\n () => ({\n ...(options ?? {}),\n ...(environment !== undefined ? { environment } : {}),\n ...(fallback !== undefined ? { fallback } : {}),\n ...(refreshRateSeconds !== undefined ? { refreshRateSeconds } : {}),\n ...(flushRateSeconds !== undefined ? { flushRateSeconds } : {}),\n ...(flushSize !== undefined ? { flushSize } : {}),\n ...(backendUrl !== undefined ? { backendUrl } : {}),\n ...(quiet !== undefined ? { quiet } : {}),\n ...(httpTimeoutSeconds !== undefined ? { httpTimeoutSeconds } : {}),\n ...(flushBufferMultiplier !== undefined ? { flushBufferMultiplier } : {}),\n ...(disableMeasurement !== undefined ? { disableMeasurement } : {}),\n }),\n [\n options,\n environment,\n fallback,\n refreshRateSeconds,\n flushRateSeconds,\n flushSize,\n backendUrl,\n quiet,\n httpTimeoutSeconds,\n flushBufferMultiplier,\n disableMeasurement,\n ],\n );\n const optionsKey = useMemo(() => JSON.stringify(resolvedOptions), [resolvedOptions]);\n const propertiesKey = useMemo(() => JSON.stringify(properties ?? {}), [properties]);\n const [managedClient, setManagedClient] = useState<LiteguardClient | null>(() => client ?? null);\n const [error, setError] = useState<unknown>(null);\n\n useEffect(() => {\n if (client) {\n setManagedClient(client);\n return;\n }\n\n if (!projectClientKeyId) {\n setManagedClient(null);\n return;\n }\n\n let cancelled = false;\n const nextClient = new LiteguardClient(projectClientKeyId, resolvedOptions);\n setManagedClient(nextClient);\n setError(null);\n\n const unsubscribe = nextClient.subscribe(() => {\n if (!cancelled) {\n setManagedClient(nextClient);\n }\n });\n\n void nextClient.start().catch((startError) => {\n if (!cancelled) {\n setError(startError);\n }\n });\n\n return () => {\n cancelled = true;\n unsubscribe();\n void nextClient.shutdown();\n };\n }, [client, projectClientKeyId, optionsKey, resolvedOptions]);\n\n const activeClient = client ?? managedClient;\n const ready = useClientValue(activeClient, () => activeClient?.isReady() ?? false, false);\n\n useEffect(() => {\n if (!activeClient) {\n return;\n }\n\n activeClient.resetProperties();\n if (properties && Object.keys(properties).length > 0) {\n activeClient.addProperties(properties);\n }\n }, [activeClient, propertiesKey, properties]);\n\n if (error) {\n throw error;\n }\n if (projectClientKeyId && !ready) {\n return <>{loading}</>;\n }\n\n return <LiteguardContext.Provider value={activeClient}>{children}</LiteguardContext.Provider>;\n}\n\n/**\n * Return the nearest Liteguard client from React context, or `null` if no\n * provider is present.\n *\n * Prefer {@link useLiteguardClient} when you need to assert that a client\n * is available.\n *\n * @returns The {@link LiteguardClient}, or `null`.\n */\nexport function useLiteguard(): LiteguardClient | null {\n return useOptionalClient();\n}\n\n/**\n * Return the nearest Liteguard client from React context. Throws if no\n * client is available — ensures the component is rendered inside a\n * {@link LiteguardProvider}.\n *\n * @returns The {@link LiteguardClient}.\n * @throws {Error} When no Liteguard client is reachable.\n */\nexport function useLiteguardClient(): LiteguardClient {\n const client = useOptionalClient();\n if (!client) {\n throw new Error(\n '[liteguard] No Liteguard React client available. Wrap your app in LiteguardProvider.',\n );\n }\n return client;\n}\n\n/**\n * Returns `true` once the Liteguard client has fetched its initial guard\n * bundle. Re-renders the component automatically when the ready state\n * changes.\n *\n * @returns `true` when the client is ready, `false` otherwise.\n */\nexport function useLiteguardReady(): boolean {\n const client = useOptionalClient();\n return useClientValue(client, () => client?.isReady() ?? false, false);\n}\n\n/**\n * Return `true` if the named guard is open for the current context.\n * Re-renders automatically whenever the guard bundle is refreshed.\n *\n * Internally uses `peekIsOpen` so guard checks during renders do **not**\n * emit telemetry signals or consume rate-limit slots. For guarded\n * side-effects that need telemetry, call\n * `useLiteguardClient().executeIfOpen()` in an event handler or effect.\n *\n * @param name - Guard name to evaluate (e.g. `\"feature.beta\"`).\n * @param options - Optional per-call overrides (extra properties, fallback).\n * @returns `true` if the guard is open, `false` otherwise.\n *\n * @example\n * ```tsx\n * function Banner() {\n * const showBeta = useIsOpen('promo.banner');\n * if (!showBeta) return null;\n * return <div>Try our new beta!</div>;\n * }\n * ```\n */\nexport function useIsOpen(name: string, options?: Partial<Options>): boolean {\n const client = useOptionalClient();\n const fallback = options?.fallback ?? false;\n return useClientValue(client, () => client?.peekIsOpen(name, options) ?? fallback, fallback);\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@liteguard/liteguard-react",
3
+ "version": "0.2.20260314",
4
+ "description": "React bindings for Liteguard — feature guards, observability, and CVE auto-disable via LiteguardProvider and useIsOpen hook",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "license": "Apache-2.0",
9
+ "homepage": "https://liteguard.io",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/liteguard/liteguard.git",
13
+ "directory": "sdk/js/packages/liteguard-react"
14
+ },
15
+ "keywords": [
16
+ "feature-flags",
17
+ "feature-guards",
18
+ "feature-toggles",
19
+ "liteguard",
20
+ "react",
21
+ "observability",
22
+ "security"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.mjs",
28
+ "require": "./dist/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md"
34
+ ],
35
+ "sideEffects": false,
36
+ "scripts": {
37
+ "build": "tsup src/index.ts --format cjs,esm --dts --sourcemap --clean"
38
+ },
39
+ "dependencies": {
40
+ "@liteguard/liteguard-browser": "*"
41
+ },
42
+ "peerDependencies": {
43
+ "react": ">=18",
44
+ "react-dom": ">=18"
45
+ }
46
+ }