@replanejs/react 0.1.1

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) 2025 Dmitry Tilyupo
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/dist/index.cjs ADDED
@@ -0,0 +1,224 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ const react = __toESM(require("react"));
25
+ const __replanejs_sdk = __toESM(require("@replanejs/sdk"));
26
+ const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
27
+
28
+ //#region src/context.ts
29
+ const ReplaneContext = (0, react.createContext)(null);
30
+
31
+ //#endregion
32
+ //#region src/useReplaneClient.ts
33
+ const suspenseCache = new Map();
34
+ function getCacheKey(options) {
35
+ return `${options.baseUrl}:${options.sdkKey}`;
36
+ }
37
+ /**
38
+ * Hook to manage ReplaneClient creation internally.
39
+ * Handles loading state and cleanup.
40
+ */
41
+ function useReplaneClient(options, onError) {
42
+ const [state, setState] = (0, react.useState)({
43
+ status: "loading",
44
+ client: null,
45
+ error: null
46
+ });
47
+ const clientRef = (0, react.useRef)(null);
48
+ const optionsRef = (0, react.useRef)(options);
49
+ (0, react.useEffect)(() => {
50
+ let cancelled = false;
51
+ async function initClient() {
52
+ try {
53
+ const client = await (0, __replanejs_sdk.createReplaneClient)(optionsRef.current);
54
+ if (cancelled) {
55
+ client.close();
56
+ return;
57
+ }
58
+ clientRef.current = client;
59
+ setState({
60
+ status: "ready",
61
+ client,
62
+ error: null
63
+ });
64
+ } catch (err) {
65
+ if (cancelled) return;
66
+ const error = err instanceof Error ? err : new Error(String(err));
67
+ setState({
68
+ status: "error",
69
+ client: null,
70
+ error
71
+ });
72
+ onError?.(error);
73
+ }
74
+ }
75
+ initClient();
76
+ return () => {
77
+ cancelled = true;
78
+ if (clientRef.current) {
79
+ clientRef.current.close();
80
+ clientRef.current = null;
81
+ }
82
+ };
83
+ }, []);
84
+ return state;
85
+ }
86
+ /**
87
+ * Hook for Suspense-based client creation.
88
+ * Throws a promise while loading, throws error on failure.
89
+ */
90
+ function useReplaneClientSuspense(options) {
91
+ const cacheKey = getCacheKey(options);
92
+ const cached = suspenseCache.get(cacheKey);
93
+ if (cached) {
94
+ if (cached.error) throw cached.error;
95
+ if (cached.result) return cached.result;
96
+ throw cached.promise;
97
+ }
98
+ const promise = (0, __replanejs_sdk.createReplaneClient)(options).then((client) => {
99
+ const entry = suspenseCache.get(cacheKey);
100
+ if (entry) entry.result = client;
101
+ return client;
102
+ }).catch((err) => {
103
+ const entry = suspenseCache.get(cacheKey);
104
+ if (entry) entry.error = err instanceof Error ? err : new Error(String(err));
105
+ throw err;
106
+ });
107
+ suspenseCache.set(cacheKey, { promise });
108
+ throw promise;
109
+ }
110
+ /**
111
+ * Clear the suspense cache for a specific options configuration.
112
+ * Useful for testing or when you need to force re-initialization.
113
+ */
114
+ function clearSuspenseCache(options) {
115
+ if (options) suspenseCache.delete(getCacheKey(options));
116
+ else suspenseCache.clear();
117
+ }
118
+
119
+ //#endregion
120
+ //#region src/types.ts
121
+ /**
122
+ * Type guard to check if props contain a pre-created client.
123
+ */
124
+ function hasClient(props) {
125
+ return "client" in props && props.client !== void 0;
126
+ }
127
+
128
+ //#endregion
129
+ //#region src/provider.tsx
130
+ /**
131
+ * Internal provider component for pre-created client.
132
+ */
133
+ function ReplaneProviderWithClient({ client, children }) {
134
+ const value = (0, react.useMemo)(() => ({ client }), [client]);
135
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
136
+ value,
137
+ children
138
+ });
139
+ }
140
+ /**
141
+ * Internal provider component for options-based client creation (non-suspense).
142
+ */
143
+ function ReplaneProviderWithOptions({ options, children, loader, onError }) {
144
+ const state = useReplaneClient(options, onError);
145
+ if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: loader ?? null });
146
+ if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: loader ?? null });
147
+ const value = { client: state.client };
148
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
149
+ value,
150
+ children
151
+ });
152
+ }
153
+ /**
154
+ * Internal provider component for options-based client creation with Suspense.
155
+ */
156
+ function ReplaneProviderWithSuspense({ options, children }) {
157
+ const client = useReplaneClientSuspense(options);
158
+ const value = (0, react.useMemo)(() => ({ client }), [client]);
159
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
160
+ value,
161
+ children
162
+ });
163
+ }
164
+ /**
165
+ * Provider component that makes a ReplaneClient available to the component tree.
166
+ *
167
+ * Can be used in two ways:
168
+ *
169
+ * 1. With a pre-created client:
170
+ * ```tsx
171
+ * const client = await createReplaneClient({ ... });
172
+ * <ReplaneProvider client={client}>
173
+ * <App />
174
+ * </ReplaneProvider>
175
+ * ```
176
+ *
177
+ * 2. With options (client managed internally):
178
+ * ```tsx
179
+ * <ReplaneProvider
180
+ * options={{ baseUrl: '...', sdkKey: '...' }}
181
+ * loader={<LoadingSpinner />}
182
+ * >
183
+ * <App />
184
+ * </ReplaneProvider>
185
+ * ```
186
+ *
187
+ * 3. With Suspense:
188
+ * ```tsx
189
+ * <Suspense fallback={<LoadingSpinner />}>
190
+ * <ReplaneProvider
191
+ * options={{ baseUrl: '...', sdkKey: '...' }}
192
+ * suspense
193
+ * >
194
+ * <App />
195
+ * </ReplaneProvider>
196
+ * </Suspense>
197
+ * ```
198
+ */
199
+ function ReplaneProvider(props) {
200
+ if (hasClient(props)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithClient, { ...props });
201
+ if (props.suspense) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithSuspense, { ...props });
202
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithOptions, { ...props });
203
+ }
204
+
205
+ //#endregion
206
+ //#region src/hooks.ts
207
+ function useReplane() {
208
+ const context = (0, react.useContext)(ReplaneContext);
209
+ if (!context) throw new Error("useReplane must be used within a ReplaneProvider");
210
+ return context;
211
+ }
212
+ function useConfig(name, options) {
213
+ const { client } = useReplane();
214
+ const value = (0, react.useSyncExternalStore)((onStoreChange) => {
215
+ return client.subscribe(name, onStoreChange);
216
+ }, () => client.get(name, options), () => client.get(name, options));
217
+ return value;
218
+ }
219
+
220
+ //#endregion
221
+ exports.ReplaneProvider = ReplaneProvider;
222
+ exports.clearSuspenseCache = clearSuspenseCache;
223
+ exports.useConfig = useConfig;
224
+ exports.useReplane = useReplane;
@@ -0,0 +1,100 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+ import { ReplaneClient, ReplaneClientOptions } from "@replanejs/sdk";
3
+ import { ReactNode } from "react";
4
+
5
+ //#region src/types.d.ts
6
+ interface ReplaneContextValue<T extends object = any> {
7
+ client: ReplaneClient<T>;
8
+ }
9
+ /**
10
+ * Props for ReplaneProvider when using a pre-created client.
11
+ */
12
+ interface ReplaneProviderWithClientProps<T extends object = any> {
13
+ /** Pre-created ReplaneClient instance */
14
+ client: ReplaneClient<T>;
15
+ children: ReactNode;
16
+ }
17
+ /**
18
+ * Props for ReplaneProvider when letting it manage the client internally.
19
+ */
20
+ interface ReplaneProviderWithOptionsProps<T extends object = any> {
21
+ /** Options to create the ReplaneClient */
22
+ options: ReplaneClientOptions<T>;
23
+ children: ReactNode;
24
+ /**
25
+ * Optional loading component to show while the client is initializing.
26
+ * If not provided and suspense is false/undefined, children will not render until ready.
27
+ */
28
+ loader?: ReactNode;
29
+ /**
30
+ * If true, uses React Suspense for loading state.
31
+ * The provider will throw a promise that Suspense can catch.
32
+ * @default false
33
+ */
34
+ suspense?: boolean;
35
+ /**
36
+ * Callback when client initialization fails.
37
+ */
38
+ onError?: (error: Error) => void;
39
+ }
40
+ type ReplaneProviderProps<T extends object = any> = ReplaneProviderWithClientProps<T> | ReplaneProviderWithOptionsProps<T>;
41
+ /**
42
+ * Type guard to check if props contain a pre-created client.
43
+ */
44
+ //#endregion
45
+ //#region src/provider.d.ts
46
+ /**
47
+ * Provider component that makes a ReplaneClient available to the component tree.
48
+ *
49
+ * Can be used in two ways:
50
+ *
51
+ * 1. With a pre-created client:
52
+ * ```tsx
53
+ * const client = await createReplaneClient({ ... });
54
+ * <ReplaneProvider client={client}>
55
+ * <App />
56
+ * </ReplaneProvider>
57
+ * ```
58
+ *
59
+ * 2. With options (client managed internally):
60
+ * ```tsx
61
+ * <ReplaneProvider
62
+ * options={{ baseUrl: '...', sdkKey: '...' }}
63
+ * loader={<LoadingSpinner />}
64
+ * >
65
+ * <App />
66
+ * </ReplaneProvider>
67
+ * ```
68
+ *
69
+ * 3. With Suspense:
70
+ * ```tsx
71
+ * <Suspense fallback={<LoadingSpinner />}>
72
+ * <ReplaneProvider
73
+ * options={{ baseUrl: '...', sdkKey: '...' }}
74
+ * suspense
75
+ * >
76
+ * <App />
77
+ * </ReplaneProvider>
78
+ * </Suspense>
79
+ * ```
80
+ */
81
+ declare function ReplaneProvider<T extends object>(props: ReplaneProviderProps<T>): react_jsx_runtime0.JSX.Element;
82
+ //# sourceMappingURL=provider.d.ts.map
83
+ //#endregion
84
+ //#region src/hooks.d.ts
85
+ declare function useReplane<T extends object = any>(): ReplaneContextValue<T>;
86
+ declare function useConfig<T>(name: string, options?: {
87
+ context?: Record<string, string | number | boolean | null>;
88
+ }): T;
89
+ //# sourceMappingURL=hooks.d.ts.map
90
+
91
+ //#endregion
92
+ //#region src/useReplaneClient.d.ts
93
+ /**
94
+ * Clear the suspense cache for a specific options configuration.
95
+ * Useful for testing or when you need to force re-initialization.
96
+ */
97
+ declare function clearSuspenseCache<T extends object>(options?: ReplaneClientOptions<T>): void;
98
+ //#endregion
99
+ export { type ReplaneContextValue, ReplaneProvider, type ReplaneProviderProps, type ReplaneProviderWithClientProps, type ReplaneProviderWithOptionsProps, clearSuspenseCache, useConfig, useReplane };
100
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/provider.tsx","../src/hooks.ts","../src/useReplaneClient.ts"],"sourcesContent":[],"mappings":";;;;;UAIiB;UACP,cAAc;;AADxB;;;AACU,UAOO,8BAPP,CAAA,UAAA,MAAA,GAAA,GAAA,CAAA,CAAA;EAAa;EAON,MAAA,EAEP,aAFO,CAEO,CAFP,CAAA;EAA8B,QAAA,EAGnC,SAHmC;;;;AAG1B;AAOJ,UAAA,+BAA+B,CAAA,UAAA,MAAA,GAAA,GAAA,CAAA,CAAA;EAAA;EAAA,OAEhB,EAArB,oBAAqB,CAAA,CAAA,CAAA;EAAC,QAAtB,EACC,SADD;EAAoB;;;AAgBN;EAIb,MAAA,CAAA,EAdD,SAcC;EAAoB;;;;;EAEG,QAAA,CAAA,EAAA,OAAA;;;;ECgDnB,OAAA,CAAA,EAAA,CAAA,KAAA,EDtDI,KCsDW,EAAA,GAAA,IAAA;;AAA+C,KDlDlE,oBCkDkE,CAAA,UAAA,MAAA,GAAA,GAAA,CAAA,GDjD1E,8BCiD0E,CDjD3C,CCiD2C,CAAA,GDhD1E,+BCgD0E,CDhD1C,CCgD0C,CAAA;;;AAAE;;;;;;;AD1FhF;;;;AACuB;AAOvB;;;;;AAGqB;AAOrB;;;;;;;AAkByB;AAIzB;;;;;;AAEmC;;;;ACgDnC;;AAA8E,iBAA9D,eAA8D,CAAA,UAAA,MAAA,CAAA,CAAA,KAAA,EAArB,oBAAqB,CAAA,CAAA,CAAA,CAAA,EAAE,kBAAA,CAAA,GAAA,CAAA,OAAF;;;;iBCzF9D,sCAAsC,oBAAoB;iBAQ1D;YAEQ;IACrB;AFZH;;;;AAwCA;;;;AAEoC,iBGgFpB,kBHhFoB,CAAA,UAAA,MAAA,CAAA,CAAA,OAAA,CAAA,EGgF2B,oBHhF3B,CGgFgD,CHhFhD,CAAA,CAAA,EAAA,IAAA"}
@@ -0,0 +1,100 @@
1
+ import { ReactNode } from "react";
2
+ import { ReplaneClient, ReplaneClientOptions } from "@replanejs/sdk";
3
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
4
+
5
+ //#region src/types.d.ts
6
+ interface ReplaneContextValue<T extends object = any> {
7
+ client: ReplaneClient<T>;
8
+ }
9
+ /**
10
+ * Props for ReplaneProvider when using a pre-created client.
11
+ */
12
+ interface ReplaneProviderWithClientProps<T extends object = any> {
13
+ /** Pre-created ReplaneClient instance */
14
+ client: ReplaneClient<T>;
15
+ children: ReactNode;
16
+ }
17
+ /**
18
+ * Props for ReplaneProvider when letting it manage the client internally.
19
+ */
20
+ interface ReplaneProviderWithOptionsProps<T extends object = any> {
21
+ /** Options to create the ReplaneClient */
22
+ options: ReplaneClientOptions<T>;
23
+ children: ReactNode;
24
+ /**
25
+ * Optional loading component to show while the client is initializing.
26
+ * If not provided and suspense is false/undefined, children will not render until ready.
27
+ */
28
+ loader?: ReactNode;
29
+ /**
30
+ * If true, uses React Suspense for loading state.
31
+ * The provider will throw a promise that Suspense can catch.
32
+ * @default false
33
+ */
34
+ suspense?: boolean;
35
+ /**
36
+ * Callback when client initialization fails.
37
+ */
38
+ onError?: (error: Error) => void;
39
+ }
40
+ type ReplaneProviderProps<T extends object = any> = ReplaneProviderWithClientProps<T> | ReplaneProviderWithOptionsProps<T>;
41
+ /**
42
+ * Type guard to check if props contain a pre-created client.
43
+ */
44
+ //#endregion
45
+ //#region src/provider.d.ts
46
+ /**
47
+ * Provider component that makes a ReplaneClient available to the component tree.
48
+ *
49
+ * Can be used in two ways:
50
+ *
51
+ * 1. With a pre-created client:
52
+ * ```tsx
53
+ * const client = await createReplaneClient({ ... });
54
+ * <ReplaneProvider client={client}>
55
+ * <App />
56
+ * </ReplaneProvider>
57
+ * ```
58
+ *
59
+ * 2. With options (client managed internally):
60
+ * ```tsx
61
+ * <ReplaneProvider
62
+ * options={{ baseUrl: '...', sdkKey: '...' }}
63
+ * loader={<LoadingSpinner />}
64
+ * >
65
+ * <App />
66
+ * </ReplaneProvider>
67
+ * ```
68
+ *
69
+ * 3. With Suspense:
70
+ * ```tsx
71
+ * <Suspense fallback={<LoadingSpinner />}>
72
+ * <ReplaneProvider
73
+ * options={{ baseUrl: '...', sdkKey: '...' }}
74
+ * suspense
75
+ * >
76
+ * <App />
77
+ * </ReplaneProvider>
78
+ * </Suspense>
79
+ * ```
80
+ */
81
+ declare function ReplaneProvider<T extends object>(props: ReplaneProviderProps<T>): react_jsx_runtime0.JSX.Element;
82
+ //# sourceMappingURL=provider.d.ts.map
83
+ //#endregion
84
+ //#region src/hooks.d.ts
85
+ declare function useReplane<T extends object = any>(): ReplaneContextValue<T>;
86
+ declare function useConfig<T>(name: string, options?: {
87
+ context?: Record<string, string | number | boolean | null>;
88
+ }): T;
89
+ //# sourceMappingURL=hooks.d.ts.map
90
+
91
+ //#endregion
92
+ //#region src/useReplaneClient.d.ts
93
+ /**
94
+ * Clear the suspense cache for a specific options configuration.
95
+ * Useful for testing or when you need to force re-initialization.
96
+ */
97
+ declare function clearSuspenseCache<T extends object>(options?: ReplaneClientOptions<T>): void;
98
+ //#endregion
99
+ export { type ReplaneContextValue, ReplaneProvider, type ReplaneProviderProps, type ReplaneProviderWithClientProps, type ReplaneProviderWithOptionsProps, clearSuspenseCache, useConfig, useReplane };
100
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/provider.tsx","../src/hooks.ts","../src/useReplaneClient.ts"],"sourcesContent":[],"mappings":";;;;;UAIiB;UACP,cAAc;;AADxB;;;AACU,UAOO,8BAPP,CAAA,UAAA,MAAA,GAAA,GAAA,CAAA,CAAA;EAAa;EAON,MAAA,EAEP,aAFO,CAEO,CAFP,CAAA;EAA8B,QAAA,EAGnC,SAHmC;;;;AAG1B;AAOJ,UAAA,+BAA+B,CAAA,UAAA,MAAA,GAAA,GAAA,CAAA,CAAA;EAAA;EAAA,OAEhB,EAArB,oBAAqB,CAAA,CAAA,CAAA;EAAC,QAAtB,EACC,SADD;EAAoB;;;AAgBN;EAIb,MAAA,CAAA,EAdD,SAcC;EAAoB;;;;;EAEG,QAAA,CAAA,EAAA,OAAA;;;;ECgDnB,OAAA,CAAA,EAAA,CAAA,KAAA,EDtDI,KCsDW,EAAA,GAAA,IAAA;;AAA+C,KDlDlE,oBCkDkE,CAAA,UAAA,MAAA,GAAA,GAAA,CAAA,GDjD1E,8BCiD0E,CDjD3C,CCiD2C,CAAA,GDhD1E,+BCgD0E,CDhD1C,CCgD0C,CAAA;;;AAAE;;;;;;;AD1FhF;;;;AACuB;AAOvB;;;;;AAGqB;AAOrB;;;;;;;AAkByB;AAIzB;;;;;;AAEmC;;;;ACgDnC;;AAA8E,iBAA9D,eAA8D,CAAA,UAAA,MAAA,CAAA,CAAA,KAAA,EAArB,oBAAqB,CAAA,CAAA,CAAA,CAAA,EAAE,kBAAA,CAAA,GAAA,CAAA,OAAF;;;;iBCzF9D,sCAAsC,oBAAoB;iBAQ1D;YAEQ;IACrB;AFZH;;;;AAwCA;;;;AAEoC,iBGgFpB,kBHhFoB,CAAA,UAAA,MAAA,CAAA,CAAA,OAAA,CAAA,EGgF2B,oBHhF3B,CGgFgD,CHhFhD,CAAA,CAAA,EAAA,IAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,199 @@
1
+ import { createContext, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
+ import { createReplaneClient } from "@replanejs/sdk";
3
+ import { Fragment, jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/context.ts
6
+ const ReplaneContext = createContext(null);
7
+
8
+ //#endregion
9
+ //#region src/useReplaneClient.ts
10
+ const suspenseCache = new Map();
11
+ function getCacheKey(options) {
12
+ return `${options.baseUrl}:${options.sdkKey}`;
13
+ }
14
+ /**
15
+ * Hook to manage ReplaneClient creation internally.
16
+ * Handles loading state and cleanup.
17
+ */
18
+ function useReplaneClient(options, onError) {
19
+ const [state, setState] = useState({
20
+ status: "loading",
21
+ client: null,
22
+ error: null
23
+ });
24
+ const clientRef = useRef(null);
25
+ const optionsRef = useRef(options);
26
+ useEffect(() => {
27
+ let cancelled = false;
28
+ async function initClient() {
29
+ try {
30
+ const client = await createReplaneClient(optionsRef.current);
31
+ if (cancelled) {
32
+ client.close();
33
+ return;
34
+ }
35
+ clientRef.current = client;
36
+ setState({
37
+ status: "ready",
38
+ client,
39
+ error: null
40
+ });
41
+ } catch (err) {
42
+ if (cancelled) return;
43
+ const error = err instanceof Error ? err : new Error(String(err));
44
+ setState({
45
+ status: "error",
46
+ client: null,
47
+ error
48
+ });
49
+ onError?.(error);
50
+ }
51
+ }
52
+ initClient();
53
+ return () => {
54
+ cancelled = true;
55
+ if (clientRef.current) {
56
+ clientRef.current.close();
57
+ clientRef.current = null;
58
+ }
59
+ };
60
+ }, []);
61
+ return state;
62
+ }
63
+ /**
64
+ * Hook for Suspense-based client creation.
65
+ * Throws a promise while loading, throws error on failure.
66
+ */
67
+ function useReplaneClientSuspense(options) {
68
+ const cacheKey = getCacheKey(options);
69
+ const cached = suspenseCache.get(cacheKey);
70
+ if (cached) {
71
+ if (cached.error) throw cached.error;
72
+ if (cached.result) return cached.result;
73
+ throw cached.promise;
74
+ }
75
+ const promise = createReplaneClient(options).then((client) => {
76
+ const entry = suspenseCache.get(cacheKey);
77
+ if (entry) entry.result = client;
78
+ return client;
79
+ }).catch((err) => {
80
+ const entry = suspenseCache.get(cacheKey);
81
+ if (entry) entry.error = err instanceof Error ? err : new Error(String(err));
82
+ throw err;
83
+ });
84
+ suspenseCache.set(cacheKey, { promise });
85
+ throw promise;
86
+ }
87
+ /**
88
+ * Clear the suspense cache for a specific options configuration.
89
+ * Useful for testing or when you need to force re-initialization.
90
+ */
91
+ function clearSuspenseCache(options) {
92
+ if (options) suspenseCache.delete(getCacheKey(options));
93
+ else suspenseCache.clear();
94
+ }
95
+
96
+ //#endregion
97
+ //#region src/types.ts
98
+ /**
99
+ * Type guard to check if props contain a pre-created client.
100
+ */
101
+ function hasClient(props) {
102
+ return "client" in props && props.client !== void 0;
103
+ }
104
+
105
+ //#endregion
106
+ //#region src/provider.tsx
107
+ /**
108
+ * Internal provider component for pre-created client.
109
+ */
110
+ function ReplaneProviderWithClient({ client, children }) {
111
+ const value = useMemo(() => ({ client }), [client]);
112
+ return /* @__PURE__ */ jsx(ReplaneContext.Provider, {
113
+ value,
114
+ children
115
+ });
116
+ }
117
+ /**
118
+ * Internal provider component for options-based client creation (non-suspense).
119
+ */
120
+ function ReplaneProviderWithOptions({ options, children, loader, onError }) {
121
+ const state = useReplaneClient(options, onError);
122
+ if (state.status === "loading") return /* @__PURE__ */ jsx(Fragment, { children: loader ?? null });
123
+ if (state.status === "error") return /* @__PURE__ */ jsx(Fragment, { children: loader ?? null });
124
+ const value = { client: state.client };
125
+ return /* @__PURE__ */ jsx(ReplaneContext.Provider, {
126
+ value,
127
+ children
128
+ });
129
+ }
130
+ /**
131
+ * Internal provider component for options-based client creation with Suspense.
132
+ */
133
+ function ReplaneProviderWithSuspense({ options, children }) {
134
+ const client = useReplaneClientSuspense(options);
135
+ const value = useMemo(() => ({ client }), [client]);
136
+ return /* @__PURE__ */ jsx(ReplaneContext.Provider, {
137
+ value,
138
+ children
139
+ });
140
+ }
141
+ /**
142
+ * Provider component that makes a ReplaneClient available to the component tree.
143
+ *
144
+ * Can be used in two ways:
145
+ *
146
+ * 1. With a pre-created client:
147
+ * ```tsx
148
+ * const client = await createReplaneClient({ ... });
149
+ * <ReplaneProvider client={client}>
150
+ * <App />
151
+ * </ReplaneProvider>
152
+ * ```
153
+ *
154
+ * 2. With options (client managed internally):
155
+ * ```tsx
156
+ * <ReplaneProvider
157
+ * options={{ baseUrl: '...', sdkKey: '...' }}
158
+ * loader={<LoadingSpinner />}
159
+ * >
160
+ * <App />
161
+ * </ReplaneProvider>
162
+ * ```
163
+ *
164
+ * 3. With Suspense:
165
+ * ```tsx
166
+ * <Suspense fallback={<LoadingSpinner />}>
167
+ * <ReplaneProvider
168
+ * options={{ baseUrl: '...', sdkKey: '...' }}
169
+ * suspense
170
+ * >
171
+ * <App />
172
+ * </ReplaneProvider>
173
+ * </Suspense>
174
+ * ```
175
+ */
176
+ function ReplaneProvider(props) {
177
+ if (hasClient(props)) return /* @__PURE__ */ jsx(ReplaneProviderWithClient, { ...props });
178
+ if (props.suspense) return /* @__PURE__ */ jsx(ReplaneProviderWithSuspense, { ...props });
179
+ return /* @__PURE__ */ jsx(ReplaneProviderWithOptions, { ...props });
180
+ }
181
+
182
+ //#endregion
183
+ //#region src/hooks.ts
184
+ function useReplane() {
185
+ const context = useContext(ReplaneContext);
186
+ if (!context) throw new Error("useReplane must be used within a ReplaneProvider");
187
+ return context;
188
+ }
189
+ function useConfig(name, options) {
190
+ const { client } = useReplane();
191
+ const value = useSyncExternalStore((onStoreChange) => {
192
+ return client.subscribe(name, onStoreChange);
193
+ }, () => client.get(name, options), () => client.get(name, options));
194
+ return value;
195
+ }
196
+
197
+ //#endregion
198
+ export { ReplaneProvider, clearSuspenseCache, useConfig, useReplane };
199
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["options: ReplaneClientOptions<T>","onError?: (error: Error) => void","options?: ReplaneClientOptions<T>","props: ReplaneProviderProps<T>","value: ReplaneContextValue<T>","props: ReplaneProviderProps<T>","name: string","options?: { context?: Record<string, string | number | boolean | null> }"],"sources":["../src/context.ts","../src/useReplaneClient.ts","../src/types.ts","../src/provider.tsx","../src/hooks.ts"],"sourcesContent":["import { createContext } from \"react\";\nimport type { ReplaneContextValue } from \"./types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const ReplaneContext = createContext<ReplaneContextValue<any> | null>(null);\n","import { useEffect, useRef, useState } from \"react\";\nimport { createReplaneClient } from \"@replanejs/sdk\";\nimport type { ReplaneClient, ReplaneClientOptions } from \"@replanejs/sdk\";\n\ntype ClientState<T extends object> =\n | { status: \"loading\"; client: null; error: null }\n | { status: \"ready\"; client: ReplaneClient<T>; error: null }\n | { status: \"error\"; client: null; error: Error };\n\n// Cache for suspense promise tracking\nconst suspenseCache = new Map<\n string,\n {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n promise: Promise<ReplaneClient<any>>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n result?: ReplaneClient<any>;\n error?: Error;\n }\n>();\n\nfunction getCacheKey<T extends object>(options: ReplaneClientOptions<T>): string {\n return `${options.baseUrl}:${options.sdkKey}`;\n}\n\n/**\n * Hook to manage ReplaneClient creation internally.\n * Handles loading state and cleanup.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useReplaneClient<T extends object = any>(\n options: ReplaneClientOptions<T>,\n onError?: (error: Error) => void\n): ClientState<T> {\n const [state, setState] = useState<ClientState<T>>({\n status: \"loading\",\n client: null,\n error: null,\n });\n const clientRef = useRef<ReplaneClient<T> | null>(null);\n const optionsRef = useRef(options);\n\n useEffect(() => {\n let cancelled = false;\n\n async function initClient() {\n try {\n const client = await createReplaneClient<T>(optionsRef.current);\n if (cancelled) {\n client.close();\n return;\n }\n clientRef.current = client;\n setState({ status: \"ready\", client, error: null });\n } catch (err) {\n if (cancelled) return;\n const error = err instanceof Error ? err : new Error(String(err));\n setState({ status: \"error\", client: null, error });\n onError?.(error);\n }\n }\n\n initClient();\n\n return () => {\n cancelled = true;\n if (clientRef.current) {\n clientRef.current.close();\n clientRef.current = null;\n }\n };\n // We intentionally only run this effect once on mount\n // Options changes would require remounting the provider\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return state;\n}\n\n/**\n * Hook for Suspense-based client creation.\n * Throws a promise while loading, throws error on failure.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useReplaneClientSuspense<T extends object = any>(\n options: ReplaneClientOptions<T>\n): ReplaneClient<T> {\n const cacheKey = getCacheKey(options);\n const cached = suspenseCache.get(cacheKey);\n\n if (cached) {\n if (cached.error) {\n throw cached.error;\n }\n if (cached.result) {\n return cached.result as ReplaneClient<T>;\n }\n // Still loading, throw the promise\n throw cached.promise;\n }\n\n // First time - create the promise\n const promise = createReplaneClient<T>(options)\n .then((client) => {\n const entry = suspenseCache.get(cacheKey);\n if (entry) {\n entry.result = client;\n }\n return client;\n })\n .catch((err) => {\n const entry = suspenseCache.get(cacheKey);\n if (entry) {\n entry.error = err instanceof Error ? err : new Error(String(err));\n }\n throw err;\n });\n\n suspenseCache.set(cacheKey, { promise });\n throw promise;\n}\n\n/**\n * Clear the suspense cache for a specific options configuration.\n * Useful for testing or when you need to force re-initialization.\n */\nexport function clearSuspenseCache<T extends object>(options?: ReplaneClientOptions<T>): void {\n if (options) {\n suspenseCache.delete(getCacheKey(options));\n } else {\n suspenseCache.clear();\n }\n}\n","import type { ReplaneClient, ReplaneClientOptions } from \"@replanejs/sdk\";\nimport type { ReactNode } from \"react\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface ReplaneContextValue<T extends object = any> {\n client: ReplaneClient<T>;\n}\n\n/**\n * Props for ReplaneProvider when using a pre-created client.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface ReplaneProviderWithClientProps<T extends object = any> {\n /** Pre-created ReplaneClient instance */\n client: ReplaneClient<T>;\n children: ReactNode;\n}\n\n/**\n * Props for ReplaneProvider when letting it manage the client internally.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface ReplaneProviderWithOptionsProps<T extends object = any> {\n /** Options to create the ReplaneClient */\n options: ReplaneClientOptions<T>;\n children: ReactNode;\n /**\n * Optional loading component to show while the client is initializing.\n * If not provided and suspense is false/undefined, children will not render until ready.\n */\n loader?: ReactNode;\n /**\n * If true, uses React Suspense for loading state.\n * The provider will throw a promise that Suspense can catch.\n * @default false\n */\n suspense?: boolean;\n /**\n * Callback when client initialization fails.\n */\n onError?: (error: Error) => void;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ReplaneProviderProps<T extends object = any> =\n | ReplaneProviderWithClientProps<T>\n | ReplaneProviderWithOptionsProps<T>;\n\n/**\n * Type guard to check if props contain a pre-created client.\n */\nexport function hasClient<T extends object>(\n props: ReplaneProviderProps<T>\n): props is ReplaneProviderWithClientProps<T> {\n return \"client\" in props && props.client !== undefined;\n}\n","import { useMemo } from \"react\";\nimport { ReplaneContext } from \"./context\";\nimport { useReplaneClient, useReplaneClientSuspense } from \"./useReplaneClient\";\nimport type {\n ReplaneProviderProps,\n ReplaneProviderWithClientProps,\n ReplaneProviderWithOptionsProps,\n ReplaneContextValue,\n} from \"./types\";\nimport { hasClient } from \"./types\";\n\n/**\n * Internal provider component for pre-created client.\n */\nfunction ReplaneProviderWithClient<T extends object>({\n client,\n children,\n}: ReplaneProviderWithClientProps<T>) {\n const value = useMemo<ReplaneContextValue<T>>(() => ({ client }), [client]);\n return <ReplaneContext.Provider value={value}>{children}</ReplaneContext.Provider>;\n}\n\n/**\n * Internal provider component for options-based client creation (non-suspense).\n */\nfunction ReplaneProviderWithOptions<T extends object>({\n options,\n children,\n loader,\n onError,\n}: ReplaneProviderWithOptionsProps<T>) {\n const state = useReplaneClient<T>(options, onError);\n\n if (state.status === \"loading\") {\n return <>{loader ?? null}</>;\n }\n\n if (state.status === \"error\") {\n // Error was already reported via onError callback\n // Return loader or null to prevent rendering children without a client\n return <>{loader ?? null}</>;\n }\n\n const value: ReplaneContextValue<T> = { client: state.client };\n return <ReplaneContext.Provider value={value}>{children}</ReplaneContext.Provider>;\n}\n\n/**\n * Internal provider component for options-based client creation with Suspense.\n */\nfunction ReplaneProviderWithSuspense<T extends object>({\n options,\n children,\n}: ReplaneProviderWithOptionsProps<T>) {\n const client = useReplaneClientSuspense<T>(options);\n const value = useMemo<ReplaneContextValue<T>>(() => ({ client }), [client]);\n return <ReplaneContext.Provider value={value}>{children}</ReplaneContext.Provider>;\n}\n\n/**\n * Provider component that makes a ReplaneClient available to the component tree.\n *\n * Can be used in two ways:\n *\n * 1. With a pre-created client:\n * ```tsx\n * const client = await createReplaneClient({ ... });\n * <ReplaneProvider client={client}>\n * <App />\n * </ReplaneProvider>\n * ```\n *\n * 2. With options (client managed internally):\n * ```tsx\n * <ReplaneProvider\n * options={{ baseUrl: '...', sdkKey: '...' }}\n * loader={<LoadingSpinner />}\n * >\n * <App />\n * </ReplaneProvider>\n * ```\n *\n * 3. With Suspense:\n * ```tsx\n * <Suspense fallback={<LoadingSpinner />}>\n * <ReplaneProvider\n * options={{ baseUrl: '...', sdkKey: '...' }}\n * suspense\n * >\n * <App />\n * </ReplaneProvider>\n * </Suspense>\n * ```\n */\nexport function ReplaneProvider<T extends object>(props: ReplaneProviderProps<T>) {\n if (hasClient(props)) {\n return <ReplaneProviderWithClient {...props} />;\n }\n\n if (props.suspense) {\n return <ReplaneProviderWithSuspense {...props} />;\n }\n\n return <ReplaneProviderWithOptions {...props} />;\n}\n","import { useContext, useSyncExternalStore } from \"react\";\nimport { ReplaneContext } from \"./context\";\nimport type { ReplaneContextValue } from \"./types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useReplane<T extends object = any>(): ReplaneContextValue<T> {\n const context = useContext(ReplaneContext);\n if (!context) {\n throw new Error(\"useReplane must be used within a ReplaneProvider\");\n }\n return context as ReplaneContextValue<T>;\n}\n\nexport function useConfig<T>(\n name: string,\n options?: { context?: Record<string, string | number | boolean | null> }\n): T {\n const { client } = useReplane();\n\n const value = useSyncExternalStore(\n (onStoreChange) => {\n return client.subscribe(name, onStoreChange);\n },\n () => client.get(name, options) as T,\n () => client.get(name, options) as T\n );\n\n return value;\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@replanejs/react",
3
+ "version": "0.1.1",
4
+ "description": "React SDK for Replane - feature flags and remote configuration",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "keywords": [
21
+ "replane",
22
+ "react",
23
+ "feature-flags",
24
+ "remote-config",
25
+ "configuration",
26
+ "sdk"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/replane-dev/replane-javascript",
33
+ "directory": "packages/react"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/replane-dev/replane-javascript/issues"
37
+ },
38
+ "homepage": "https://github.com/replane-dev/replane-javascript#readme",
39
+ "peerDependencies": {
40
+ "react": ">=18.0.0"
41
+ },
42
+ "dependencies": {
43
+ "@replanejs/sdk": "0.6.2"
44
+ },
45
+ "devDependencies": {
46
+ "@testing-library/jest-dom": "^6.9.1",
47
+ "bumpp": "^10.1.0",
48
+ "@testing-library/react": "^16.3.1",
49
+ "@types/node": "^22.19.3",
50
+ "@types/react": "^18.2.0",
51
+ "@types/react-dom": "^18.3.7",
52
+ "jsdom": "^27.3.0",
53
+ "react": "^18.2.0",
54
+ "react-dom": "^18.3.1",
55
+ "tsdown": "^0.11.9",
56
+ "typescript": "^5.4.0",
57
+ "vitest": "^3.2.4"
58
+ },
59
+ "engines": {
60
+ "node": ">=18.0.0"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "scripts": {
66
+ "build": "tsdown",
67
+ "dev": "tsdown --watch",
68
+ "typecheck": "tsc --noEmit",
69
+ "test": "vitest run",
70
+ "test:watch": "vitest",
71
+ "release": "pnpm run build && bumpp && npm publish"
72
+ }
73
+ }