@replanejs/react 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -35
- package/dist/index.cjs +177 -34
- package/dist/index.d.cts +99 -32
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +99 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +166 -37
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -51,23 +51,26 @@ function MyComponent() {
|
|
|
51
51
|
|
|
52
52
|
### ReplaneProvider
|
|
53
53
|
|
|
54
|
-
Provider component that makes the Replane client available to your component tree. Supports
|
|
54
|
+
Provider component that makes the Replane client available to your component tree. Supports four usage patterns:
|
|
55
55
|
|
|
56
56
|
#### 1. With options (recommended)
|
|
57
57
|
|
|
58
|
-
The provider creates and manages the client internally:
|
|
58
|
+
The provider creates and manages the client internally. Use an Error Boundary to handle initialization errors:
|
|
59
59
|
|
|
60
60
|
```tsx
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
62
|
+
|
|
63
|
+
<ErrorBoundary fallback={<div>Failed to load configuration</div>}>
|
|
64
|
+
<ReplaneProvider
|
|
65
|
+
options={{
|
|
66
|
+
baseUrl: 'https://your-replane-server.com',
|
|
67
|
+
sdkKey: 'your-sdk-key',
|
|
68
|
+
}}
|
|
69
|
+
loader={<LoadingSpinner />}
|
|
70
|
+
>
|
|
71
|
+
<App />
|
|
72
|
+
</ReplaneProvider>
|
|
73
|
+
</ErrorBoundary>
|
|
71
74
|
```
|
|
72
75
|
|
|
73
76
|
#### 2. With pre-created client
|
|
@@ -92,19 +95,48 @@ const client = await createReplaneClient({
|
|
|
92
95
|
Integrates with React Suspense for loading states:
|
|
93
96
|
|
|
94
97
|
```tsx
|
|
95
|
-
<
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
+
<ErrorBoundary fallback={<div>Failed to load configuration</div>}>
|
|
99
|
+
<Suspense fallback={<LoadingSpinner />}>
|
|
100
|
+
<ReplaneProvider
|
|
101
|
+
options={{
|
|
102
|
+
baseUrl: 'https://your-replane-server.com',
|
|
103
|
+
sdkKey: 'your-sdk-key',
|
|
104
|
+
}}
|
|
105
|
+
suspense
|
|
106
|
+
>
|
|
107
|
+
<App />
|
|
108
|
+
</ReplaneProvider>
|
|
109
|
+
</Suspense>
|
|
110
|
+
</ErrorBoundary>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### 4. With snapshot (for SSR/hydration)
|
|
114
|
+
|
|
115
|
+
Restore a client from a snapshot obtained on the server. This is synchronous and useful for SSR scenarios:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// On the server
|
|
119
|
+
const serverClient = await createReplaneClient({ baseUrl: '...', sdkKey: '...' });
|
|
120
|
+
const snapshot = serverClient.getSnapshot();
|
|
121
|
+
// Pass snapshot to client via props, context, or serialized HTML
|
|
122
|
+
|
|
123
|
+
// On the client
|
|
124
|
+
<ReplaneProvider
|
|
125
|
+
restoreOptions={{
|
|
126
|
+
snapshot,
|
|
127
|
+
// Optional: connect for live updates
|
|
128
|
+
connection: {
|
|
98
129
|
baseUrl: 'https://your-replane-server.com',
|
|
99
130
|
sdkKey: 'your-sdk-key',
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
</Suspense>
|
|
131
|
+
},
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<App />
|
|
135
|
+
</ReplaneProvider>
|
|
106
136
|
```
|
|
107
137
|
|
|
138
|
+
The restored client is immediately available with no loading state. If `connection` is provided, it will establish a connection for real-time updates in the background.
|
|
139
|
+
|
|
108
140
|
### useConfig
|
|
109
141
|
|
|
110
142
|
Hook to retrieve a configuration value. Automatically subscribes to updates and re-renders when the value changes.
|
|
@@ -128,15 +160,15 @@ function MyComponent() {
|
|
|
128
160
|
|
|
129
161
|
### useReplane
|
|
130
162
|
|
|
131
|
-
Hook to access the underlying Replane client directly:
|
|
163
|
+
Hook to access the underlying Replane client directly. Returns the client instance:
|
|
132
164
|
|
|
133
165
|
```tsx
|
|
134
166
|
function MyComponent() {
|
|
135
|
-
const
|
|
167
|
+
const replane = useReplane();
|
|
136
168
|
|
|
137
169
|
const handleClick = () => {
|
|
138
|
-
// Access
|
|
139
|
-
const value =
|
|
170
|
+
// Access replane methods directly
|
|
171
|
+
const value = replane.get('some-config');
|
|
140
172
|
console.log(value);
|
|
141
173
|
};
|
|
142
174
|
|
|
@@ -144,6 +176,77 @@ function MyComponent() {
|
|
|
144
176
|
}
|
|
145
177
|
```
|
|
146
178
|
|
|
179
|
+
### createReplaneHook
|
|
180
|
+
|
|
181
|
+
Factory function to create a typed version of `useReplane`. Returns a hook that provides the typed client directly:
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
import { createReplaneHook } from '@replanejs/react';
|
|
185
|
+
|
|
186
|
+
// Define your config types
|
|
187
|
+
interface AppConfigs {
|
|
188
|
+
theme: { darkMode: boolean; primaryColor: string };
|
|
189
|
+
features: { beta: boolean; analytics: boolean };
|
|
190
|
+
maxItems: number;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Create a typed hook
|
|
194
|
+
const useAppReplane = createReplaneHook<AppConfigs>();
|
|
195
|
+
|
|
196
|
+
function MyComponent() {
|
|
197
|
+
const replane = useAppReplane();
|
|
198
|
+
|
|
199
|
+
// replane.get is now typed - autocomplete works!
|
|
200
|
+
const theme = replane.get('theme');
|
|
201
|
+
// ^? { darkMode: boolean; primaryColor: string }
|
|
202
|
+
|
|
203
|
+
return <div>Dark mode: {theme.darkMode ? 'on' : 'off'}</div>;
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### createConfigHook
|
|
208
|
+
|
|
209
|
+
Factory function to create a typed version of `useConfig`. This provides autocomplete for config names and type inference for values:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { createConfigHook } from '@replanejs/react';
|
|
213
|
+
|
|
214
|
+
// Define your config types
|
|
215
|
+
interface AppConfigs {
|
|
216
|
+
theme: { darkMode: boolean; primaryColor: string };
|
|
217
|
+
features: { beta: boolean; analytics: boolean };
|
|
218
|
+
maxItems: number;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Create a typed hook
|
|
222
|
+
const useAppConfig = createConfigHook<AppConfigs>();
|
|
223
|
+
|
|
224
|
+
function MyComponent() {
|
|
225
|
+
// Autocomplete for config names, automatic type inference
|
|
226
|
+
const theme = useAppConfig('theme');
|
|
227
|
+
// ^? { darkMode: boolean; primaryColor: string }
|
|
228
|
+
|
|
229
|
+
const features = useAppConfig('features');
|
|
230
|
+
// ^? { beta: boolean; analytics: boolean }
|
|
231
|
+
|
|
232
|
+
const maxItems = useAppConfig('maxItems');
|
|
233
|
+
// ^? number
|
|
234
|
+
|
|
235
|
+
// With context override
|
|
236
|
+
const premiumFeatures = useAppConfig('features', {
|
|
237
|
+
context: { userId: '123', plan: 'premium' },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div>
|
|
242
|
+
<p>Dark mode: {theme.darkMode ? 'on' : 'off'}</p>
|
|
243
|
+
<p>Beta enabled: {features.beta ? 'yes' : 'no'}</p>
|
|
244
|
+
<p>Max items: {maxItems}</p>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
147
250
|
### clearSuspenseCache
|
|
148
251
|
|
|
149
252
|
Utility function to clear the suspense cache. Useful for testing or forcing re-initialization:
|
|
@@ -163,21 +266,95 @@ clearSuspenseCache();
|
|
|
163
266
|
|
|
164
267
|
## TypeScript
|
|
165
268
|
|
|
166
|
-
The SDK is fully typed.
|
|
269
|
+
The SDK is fully typed. For the best TypeScript experience, use the hook factory functions:
|
|
167
270
|
|
|
168
271
|
```tsx
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
272
|
+
// Define all your config types in one interface
|
|
273
|
+
interface AppConfigs {
|
|
274
|
+
'theme-config': {
|
|
275
|
+
darkMode: boolean;
|
|
276
|
+
primaryColor: string;
|
|
277
|
+
};
|
|
278
|
+
'feature-flags': {
|
|
279
|
+
newUI: boolean;
|
|
280
|
+
beta: boolean;
|
|
175
281
|
};
|
|
282
|
+
'max-items': number;
|
|
283
|
+
'welcome-message': string;
|
|
176
284
|
}
|
|
177
285
|
|
|
178
|
-
//
|
|
179
|
-
const
|
|
180
|
-
const
|
|
286
|
+
// Create typed hooks once
|
|
287
|
+
const useAppReplane = createReplaneHook<AppConfigs>();
|
|
288
|
+
const useAppConfig = createConfigHook<AppConfigs>();
|
|
289
|
+
|
|
290
|
+
// Use throughout your app with full type safety
|
|
291
|
+
function Settings() {
|
|
292
|
+
const theme = useAppConfig('theme-config');
|
|
293
|
+
// ^? { darkMode: boolean; primaryColor: string }
|
|
294
|
+
|
|
295
|
+
const replane = useAppReplane();
|
|
296
|
+
const snapshot = replane.getSnapshot();
|
|
297
|
+
// ^? { configs: ConfigSnapshot<AppConfigs>[] }
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<div style={{ color: theme.primaryColor }}>
|
|
301
|
+
Dark mode: {theme.darkMode ? 'enabled' : 'disabled'}
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Error Handling
|
|
308
|
+
|
|
309
|
+
The provider throws errors during rendering so they can be caught by React Error Boundaries:
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
import { Component, ReactNode } from 'react';
|
|
313
|
+
|
|
314
|
+
class ErrorBoundary extends Component<
|
|
315
|
+
{ children: ReactNode; fallback: ReactNode },
|
|
316
|
+
{ hasError: boolean }
|
|
317
|
+
> {
|
|
318
|
+
state = { hasError: false };
|
|
319
|
+
|
|
320
|
+
static getDerivedStateFromError() {
|
|
321
|
+
return { hasError: true };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
render() {
|
|
325
|
+
if (this.state.hasError) {
|
|
326
|
+
return this.props.fallback;
|
|
327
|
+
}
|
|
328
|
+
return this.props.children;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Usage
|
|
333
|
+
<ErrorBoundary fallback={<div>Configuration failed to load</div>}>
|
|
334
|
+
<ReplaneProvider options={options} loader={<Loading />}>
|
|
335
|
+
<App />
|
|
336
|
+
</ReplaneProvider>
|
|
337
|
+
</ErrorBoundary>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Or use a library like `react-error-boundary`:
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
344
|
+
|
|
345
|
+
<ErrorBoundary
|
|
346
|
+
fallbackRender={({ error, resetErrorBoundary }) => (
|
|
347
|
+
<div>
|
|
348
|
+
<p>Error: {error.message}</p>
|
|
349
|
+
<button onClick={resetErrorBoundary}>Retry</button>
|
|
350
|
+
</div>
|
|
351
|
+
)}
|
|
352
|
+
onReset={() => clearSuspenseCache()}
|
|
353
|
+
>
|
|
354
|
+
<ReplaneProvider options={options} loader={<Loading />}>
|
|
355
|
+
<App />
|
|
356
|
+
</ReplaneProvider>
|
|
357
|
+
</ErrorBoundary>
|
|
181
358
|
```
|
|
182
359
|
|
|
183
360
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,7 @@ function getCacheKey(options) {
|
|
|
38
38
|
* Hook to manage ReplaneClient creation internally.
|
|
39
39
|
* Handles loading state and cleanup.
|
|
40
40
|
*/
|
|
41
|
-
function
|
|
41
|
+
function useReplaneClientInternal(options) {
|
|
42
42
|
const [state, setState] = (0, react.useState)({
|
|
43
43
|
status: "loading",
|
|
44
44
|
client: null,
|
|
@@ -63,13 +63,12 @@ function useReplaneClient(options, onError) {
|
|
|
63
63
|
});
|
|
64
64
|
} catch (err) {
|
|
65
65
|
if (cancelled) return;
|
|
66
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
66
|
+
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
|
|
67
67
|
setState({
|
|
68
68
|
status: "error",
|
|
69
69
|
client: null,
|
|
70
70
|
error
|
|
71
71
|
});
|
|
72
|
-
onError?.(error);
|
|
73
72
|
}
|
|
74
73
|
}
|
|
75
74
|
initClient();
|
|
@@ -116,6 +115,101 @@ function clearSuspenseCache(options) {
|
|
|
116
115
|
else suspenseCache.clear();
|
|
117
116
|
}
|
|
118
117
|
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/hooks.ts
|
|
120
|
+
function useReplane() {
|
|
121
|
+
const context = (0, react.useContext)(ReplaneContext);
|
|
122
|
+
if (!context) throw new Error("useReplane must be used within a ReplaneProvider");
|
|
123
|
+
return context.client;
|
|
124
|
+
}
|
|
125
|
+
function useConfig(name, options) {
|
|
126
|
+
const client = useReplane();
|
|
127
|
+
const subscribe = (0, react.useCallback)((callback) => {
|
|
128
|
+
return client.subscribe(name, callback);
|
|
129
|
+
}, [client, name]);
|
|
130
|
+
const get = (0, react.useCallback)(() => {
|
|
131
|
+
return client.get(name, options);
|
|
132
|
+
}, [
|
|
133
|
+
client,
|
|
134
|
+
name,
|
|
135
|
+
options
|
|
136
|
+
]);
|
|
137
|
+
const value = (0, react.useSyncExternalStore)(subscribe, get, get);
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Creates a typed version of useReplane hook.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```tsx
|
|
145
|
+
* interface AppConfigs {
|
|
146
|
+
* theme: { darkMode: boolean };
|
|
147
|
+
* features: { beta: boolean };
|
|
148
|
+
* }
|
|
149
|
+
*
|
|
150
|
+
* const useAppReplane = createReplaneHook<AppConfigs>();
|
|
151
|
+
*
|
|
152
|
+
* function MyComponent() {
|
|
153
|
+
* const replane = useAppReplane();
|
|
154
|
+
* // replane.get("theme") returns { darkMode: boolean }
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
function createReplaneHook() {
|
|
159
|
+
return function useTypedReplane() {
|
|
160
|
+
return useReplane();
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Creates a typed version of useConfig hook.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```tsx
|
|
168
|
+
* interface AppConfigs {
|
|
169
|
+
* theme: { darkMode: boolean };
|
|
170
|
+
* features: { beta: boolean };
|
|
171
|
+
* }
|
|
172
|
+
*
|
|
173
|
+
* const useAppConfig = createConfigHook<AppConfigs>();
|
|
174
|
+
*
|
|
175
|
+
* function MyComponent() {
|
|
176
|
+
* const theme = useAppConfig("theme");
|
|
177
|
+
* // theme is typed as { darkMode: boolean }
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
function createConfigHook() {
|
|
182
|
+
return function useTypedConfig(name, options) {
|
|
183
|
+
return useConfig(String(name), options);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Hook for creating stateful resources with cleanup support.
|
|
188
|
+
* Unlike useMemo, this guarantees cleanup when dependencies change or on unmount.
|
|
189
|
+
*
|
|
190
|
+
* @param factory - Function that creates the resource
|
|
191
|
+
* @param cleanup - Function that cleans up the resource
|
|
192
|
+
* @param deps - Dependencies array (resource is recreated when these change)
|
|
193
|
+
*/
|
|
194
|
+
function useStateful(factory, cleanup, deps) {
|
|
195
|
+
const valueRef = (0, react.useRef)(null);
|
|
196
|
+
const initializedRef = (0, react.useRef)(false);
|
|
197
|
+
if (!initializedRef.current) {
|
|
198
|
+
valueRef.current = factory();
|
|
199
|
+
initializedRef.current = true;
|
|
200
|
+
}
|
|
201
|
+
(0, react.useEffect)(() => {
|
|
202
|
+
if (valueRef.current === null) valueRef.current = factory();
|
|
203
|
+
return () => {
|
|
204
|
+
if (valueRef.current !== null) {
|
|
205
|
+
cleanup(valueRef.current);
|
|
206
|
+
valueRef.current = null;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}, deps);
|
|
210
|
+
return valueRef.current;
|
|
211
|
+
}
|
|
212
|
+
|
|
119
213
|
//#endregion
|
|
120
214
|
//#region src/types.ts
|
|
121
215
|
/**
|
|
@@ -138,12 +232,37 @@ function ReplaneProviderWithClient({ client, children }) {
|
|
|
138
232
|
});
|
|
139
233
|
}
|
|
140
234
|
/**
|
|
235
|
+
* Internal provider component for restoring client from snapshot.
|
|
236
|
+
* Uses restoreReplaneClient which is synchronous.
|
|
237
|
+
*/
|
|
238
|
+
function ReplaneProviderWithSnapshot({ options, snapshot, children }) {
|
|
239
|
+
const client = useStateful(() => (0, __replanejs_sdk.restoreReplaneClient)({
|
|
240
|
+
snapshot,
|
|
241
|
+
connection: {
|
|
242
|
+
baseUrl: options.baseUrl,
|
|
243
|
+
sdkKey: options.sdkKey,
|
|
244
|
+
fetchFn: options.fetchFn,
|
|
245
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
246
|
+
retryDelayMs: options.retryDelayMs,
|
|
247
|
+
inactivityTimeoutMs: options.inactivityTimeoutMs,
|
|
248
|
+
logger: options.logger
|
|
249
|
+
},
|
|
250
|
+
context: options.context
|
|
251
|
+
}), (c) => c.close(), [snapshot, options]);
|
|
252
|
+
const value = (0, react.useMemo)(() => ({ client }), [client]);
|
|
253
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
|
|
254
|
+
value,
|
|
255
|
+
children
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
141
259
|
* Internal provider component for options-based client creation (non-suspense).
|
|
260
|
+
* Throws errors during rendering so they can be caught by Error Boundaries.
|
|
142
261
|
*/
|
|
143
|
-
function ReplaneProviderWithOptions({ options, children, loader
|
|
144
|
-
const state =
|
|
262
|
+
function ReplaneProviderWithOptions({ options, children, loader }) {
|
|
263
|
+
const state = useReplaneClientInternal(options);
|
|
145
264
|
if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: loader ?? null });
|
|
146
|
-
if (state.status === "error")
|
|
265
|
+
if (state.status === "error") throw state.error;
|
|
147
266
|
const value = { client: state.client };
|
|
148
267
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
|
|
149
268
|
value,
|
|
@@ -164,7 +283,7 @@ function ReplaneProviderWithSuspense({ options, children }) {
|
|
|
164
283
|
/**
|
|
165
284
|
* Provider component that makes a ReplaneClient available to the component tree.
|
|
166
285
|
*
|
|
167
|
-
* Can be used in
|
|
286
|
+
* Can be used in three ways:
|
|
168
287
|
*
|
|
169
288
|
* 1. With a pre-created client:
|
|
170
289
|
* ```tsx
|
|
@@ -176,49 +295,73 @@ function ReplaneProviderWithSuspense({ options, children }) {
|
|
|
176
295
|
*
|
|
177
296
|
* 2. With options (client managed internally):
|
|
178
297
|
* ```tsx
|
|
298
|
+
* <ErrorBoundary fallback={<ErrorMessage />}>
|
|
299
|
+
* <ReplaneProvider
|
|
300
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
301
|
+
* loader={<LoadingSpinner />}
|
|
302
|
+
* >
|
|
303
|
+
* <App />
|
|
304
|
+
* </ReplaneProvider>
|
|
305
|
+
* </ErrorBoundary>
|
|
306
|
+
* ```
|
|
307
|
+
*
|
|
308
|
+
* 3. With Suspense:
|
|
309
|
+
* ```tsx
|
|
310
|
+
* <ErrorBoundary fallback={<ErrorMessage />}>
|
|
311
|
+
* <Suspense fallback={<LoadingSpinner />}>
|
|
312
|
+
* <ReplaneProvider
|
|
313
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
314
|
+
* suspense
|
|
315
|
+
* >
|
|
316
|
+
* <App />
|
|
317
|
+
* </ReplaneProvider>
|
|
318
|
+
* </Suspense>
|
|
319
|
+
* </ErrorBoundary>
|
|
320
|
+
* ```
|
|
321
|
+
*
|
|
322
|
+
* 4. With a snapshot (for SSR/hydration):
|
|
323
|
+
* ```tsx
|
|
324
|
+
* // On the server, get a snapshot from the client
|
|
325
|
+
* const snapshot = serverClient.getSnapshot();
|
|
326
|
+
*
|
|
327
|
+
* // On the client, restore from the snapshot with live updates
|
|
179
328
|
* <ReplaneProvider
|
|
180
329
|
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
181
|
-
*
|
|
330
|
+
* snapshot={snapshot}
|
|
182
331
|
* >
|
|
183
332
|
* <App />
|
|
184
333
|
* </ReplaneProvider>
|
|
185
334
|
* ```
|
|
186
335
|
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* <Suspense fallback={<LoadingSpinner />}>
|
|
190
|
-
* <ReplaneProvider
|
|
191
|
-
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
192
|
-
* suspense
|
|
193
|
-
* >
|
|
194
|
-
* <App />
|
|
195
|
-
* </ReplaneProvider>
|
|
196
|
-
* </Suspense>
|
|
197
|
-
* ```
|
|
336
|
+
* Errors during client initialization are thrown during rendering,
|
|
337
|
+
* allowing them to be caught by React Error Boundaries.
|
|
198
338
|
*/
|
|
199
339
|
function ReplaneProvider(props) {
|
|
200
340
|
if (hasClient(props)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithClient, { ...props });
|
|
341
|
+
if (props.snapshot) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithSnapshot, {
|
|
342
|
+
...props,
|
|
343
|
+
snapshot: props.snapshot
|
|
344
|
+
});
|
|
201
345
|
if (props.suspense) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithSuspense, { ...props });
|
|
202
346
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithOptions, { ...props });
|
|
203
347
|
}
|
|
204
348
|
|
|
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
349
|
//#endregion
|
|
221
350
|
exports.ReplaneProvider = ReplaneProvider;
|
|
351
|
+
Object.defineProperty(exports, 'clearSnapshotCache', {
|
|
352
|
+
enumerable: true,
|
|
353
|
+
get: function () {
|
|
354
|
+
return __replanejs_sdk.clearSnapshotCache;
|
|
355
|
+
}
|
|
356
|
+
});
|
|
222
357
|
exports.clearSuspenseCache = clearSuspenseCache;
|
|
358
|
+
exports.createConfigHook = createConfigHook;
|
|
359
|
+
exports.createReplaneHook = createReplaneHook;
|
|
360
|
+
Object.defineProperty(exports, 'getReplaneSnapshot', {
|
|
361
|
+
enumerable: true,
|
|
362
|
+
get: function () {
|
|
363
|
+
return __replanejs_sdk.getReplaneSnapshot;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
223
366
|
exports.useConfig = useConfig;
|
|
224
367
|
exports.useReplane = useReplane;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
-
import { ReplaneClient, ReplaneClientOptions } from "@replanejs/sdk";
|
|
2
|
+
import { GetConfigOptions, GetReplaneSnapshotOptions, ReplaneClient, ReplaneClientOptions, ReplaneSnapshot, clearSnapshotCache, getReplaneSnapshot } from "@replanejs/sdk";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/types.d.ts
|
|
6
|
-
|
|
7
|
-
client: ReplaneClient<T>;
|
|
8
|
-
}
|
|
6
|
+
type UntypedReplaneConfig = Record<string, unknown>;
|
|
9
7
|
/**
|
|
10
8
|
* Props for ReplaneProvider when using a pre-created client.
|
|
11
9
|
*/
|
|
12
|
-
interface ReplaneProviderWithClientProps<T extends object =
|
|
10
|
+
interface ReplaneProviderWithClientProps<T extends object = UntypedReplaneConfig> {
|
|
13
11
|
/** Pre-created ReplaneClient instance */
|
|
14
12
|
client: ReplaneClient<T>;
|
|
15
13
|
children: ReactNode;
|
|
@@ -17,36 +15,42 @@ interface ReplaneProviderWithClientProps<T extends object = any> {
|
|
|
17
15
|
/**
|
|
18
16
|
* Props for ReplaneProvider when letting it manage the client internally.
|
|
19
17
|
*/
|
|
20
|
-
interface ReplaneProviderWithOptionsProps<T extends object =
|
|
21
|
-
/** Options to create the ReplaneClient */
|
|
18
|
+
interface ReplaneProviderWithOptionsProps<T extends object = UntypedReplaneConfig> {
|
|
19
|
+
/** Options to create or restore the ReplaneClient */
|
|
22
20
|
options: ReplaneClientOptions<T>;
|
|
23
21
|
children: ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Optional snapshot from server-side rendering.
|
|
24
|
+
* When provided, the client will be restored from the snapshot synchronously
|
|
25
|
+
* instead of fetching configs from the server.
|
|
26
|
+
* The `options` will be used for live updates connection if provided.
|
|
27
|
+
*/
|
|
28
|
+
snapshot?: ReplaneSnapshot<T>;
|
|
24
29
|
/**
|
|
25
30
|
* Optional loading component to show while the client is initializing.
|
|
26
31
|
* If not provided and suspense is false/undefined, children will not render until ready.
|
|
32
|
+
* Ignored when snapshot is provided (restoration is synchronous).
|
|
27
33
|
*/
|
|
28
34
|
loader?: ReactNode;
|
|
29
35
|
/**
|
|
30
36
|
* If true, uses React Suspense for loading state.
|
|
31
37
|
* The provider will throw a promise that Suspense can catch.
|
|
38
|
+
* Ignored when snapshot is provided (restoration is synchronous).
|
|
32
39
|
* @default false
|
|
33
40
|
*/
|
|
34
41
|
suspense?: boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Callback when client initialization fails.
|
|
37
|
-
*/
|
|
38
|
-
onError?: (error: Error) => void;
|
|
39
42
|
}
|
|
40
|
-
type ReplaneProviderProps<T extends object =
|
|
43
|
+
type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProviderWithClientProps<T> | ReplaneProviderWithOptionsProps<T>;
|
|
41
44
|
/**
|
|
42
45
|
* Type guard to check if props contain a pre-created client.
|
|
43
46
|
*/
|
|
47
|
+
|
|
44
48
|
//#endregion
|
|
45
49
|
//#region src/provider.d.ts
|
|
46
50
|
/**
|
|
47
51
|
* Provider component that makes a ReplaneClient available to the component tree.
|
|
48
52
|
*
|
|
49
|
-
* Can be used in
|
|
53
|
+
* Can be used in three ways:
|
|
50
54
|
*
|
|
51
55
|
* 1. With a pre-created client:
|
|
52
56
|
* ```tsx
|
|
@@ -58,36 +62,99 @@ type ReplaneProviderProps<T extends object = any> = ReplaneProviderWithClientPro
|
|
|
58
62
|
*
|
|
59
63
|
* 2. With options (client managed internally):
|
|
60
64
|
* ```tsx
|
|
65
|
+
* <ErrorBoundary fallback={<ErrorMessage />}>
|
|
66
|
+
* <ReplaneProvider
|
|
67
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
68
|
+
* loader={<LoadingSpinner />}
|
|
69
|
+
* >
|
|
70
|
+
* <App />
|
|
71
|
+
* </ReplaneProvider>
|
|
72
|
+
* </ErrorBoundary>
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* 3. With Suspense:
|
|
76
|
+
* ```tsx
|
|
77
|
+
* <ErrorBoundary fallback={<ErrorMessage />}>
|
|
78
|
+
* <Suspense fallback={<LoadingSpinner />}>
|
|
79
|
+
* <ReplaneProvider
|
|
80
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
81
|
+
* suspense
|
|
82
|
+
* >
|
|
83
|
+
* <App />
|
|
84
|
+
* </ReplaneProvider>
|
|
85
|
+
* </Suspense>
|
|
86
|
+
* </ErrorBoundary>
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* 4. With a snapshot (for SSR/hydration):
|
|
90
|
+
* ```tsx
|
|
91
|
+
* // On the server, get a snapshot from the client
|
|
92
|
+
* const snapshot = serverClient.getSnapshot();
|
|
93
|
+
*
|
|
94
|
+
* // On the client, restore from the snapshot with live updates
|
|
61
95
|
* <ReplaneProvider
|
|
62
96
|
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
63
|
-
*
|
|
97
|
+
* snapshot={snapshot}
|
|
64
98
|
* >
|
|
65
99
|
* <App />
|
|
66
100
|
* </ReplaneProvider>
|
|
67
101
|
* ```
|
|
68
102
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* <Suspense fallback={<LoadingSpinner />}>
|
|
72
|
-
* <ReplaneProvider
|
|
73
|
-
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
74
|
-
* suspense
|
|
75
|
-
* >
|
|
76
|
-
* <App />
|
|
77
|
-
* </ReplaneProvider>
|
|
78
|
-
* </Suspense>
|
|
79
|
-
* ```
|
|
103
|
+
* Errors during client initialization are thrown during rendering,
|
|
104
|
+
* allowing them to be caught by React Error Boundaries.
|
|
80
105
|
*/
|
|
81
106
|
declare function ReplaneProvider<T extends object>(props: ReplaneProviderProps<T>): react_jsx_runtime0.JSX.Element;
|
|
82
107
|
//# sourceMappingURL=provider.d.ts.map
|
|
83
108
|
//#endregion
|
|
84
109
|
//#region src/hooks.d.ts
|
|
85
|
-
declare function useReplane<T extends object =
|
|
86
|
-
declare function useConfig<T>(name: string, options?:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
110
|
+
declare function useReplane<T extends object = UntypedReplaneConfig>(): ReplaneClient<T>;
|
|
111
|
+
declare function useConfig<T>(name: string, options?: GetConfigOptions): T;
|
|
112
|
+
/**
|
|
113
|
+
* Creates a typed version of useReplane hook.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```tsx
|
|
117
|
+
* interface AppConfigs {
|
|
118
|
+
* theme: { darkMode: boolean };
|
|
119
|
+
* features: { beta: boolean };
|
|
120
|
+
* }
|
|
121
|
+
*
|
|
122
|
+
* const useAppReplane = createReplaneHook<AppConfigs>();
|
|
123
|
+
*
|
|
124
|
+
* function MyComponent() {
|
|
125
|
+
* const replane = useAppReplane();
|
|
126
|
+
* // replane.get("theme") returns { darkMode: boolean }
|
|
127
|
+
* }
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
declare function createReplaneHook<TConfigs extends object>(): () => ReplaneClient<TConfigs>;
|
|
131
|
+
/**
|
|
132
|
+
* Creates a typed version of useConfig hook.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```tsx
|
|
136
|
+
* interface AppConfigs {
|
|
137
|
+
* theme: { darkMode: boolean };
|
|
138
|
+
* features: { beta: boolean };
|
|
139
|
+
* }
|
|
140
|
+
*
|
|
141
|
+
* const useAppConfig = createConfigHook<AppConfigs>();
|
|
142
|
+
*
|
|
143
|
+
* function MyComponent() {
|
|
144
|
+
* const theme = useAppConfig("theme");
|
|
145
|
+
* // theme is typed as { darkMode: boolean }
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
declare function createConfigHook<TConfigs extends object>(): <K extends keyof TConfigs>(name: K, options?: GetConfigOptions) => TConfigs[K];
|
|
150
|
+
/**
|
|
151
|
+
* Hook for creating stateful resources with cleanup support.
|
|
152
|
+
* Unlike useMemo, this guarantees cleanup when dependencies change or on unmount.
|
|
153
|
+
*
|
|
154
|
+
* @param factory - Function that creates the resource
|
|
155
|
+
* @param cleanup - Function that cleans up the resource
|
|
156
|
+
* @param deps - Dependencies array (resource is recreated when these change)
|
|
157
|
+
*/
|
|
91
158
|
//#endregion
|
|
92
159
|
//#region src/useReplaneClient.d.ts
|
|
93
160
|
/**
|
|
@@ -96,5 +163,5 @@ declare function useConfig<T>(name: string, options?: {
|
|
|
96
163
|
*/
|
|
97
164
|
declare function clearSuspenseCache<T extends object>(options?: ReplaneClientOptions<T>): void;
|
|
98
165
|
//#endregion
|
|
99
|
-
export { type
|
|
166
|
+
export { type GetReplaneSnapshotOptions, ReplaneProvider, type ReplaneProviderProps, type ReplaneProviderWithClientProps, type ReplaneProviderWithOptionsProps, clearSnapshotCache, clearSuspenseCache, createConfigHook, createReplaneHook, getReplaneSnapshot, useConfig, useReplane };
|
|
100
167
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/provider.tsx","../src/hooks.ts","../src/useReplaneClient.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/provider.tsx","../src/hooks.ts","../src/useReplaneClient.ts"],"sourcesContent":[],"mappings":";;;;;KAOY,oBAAA,GAAuB;AASnC;;;AAEwB,UAFP,8BAEO,CAAA,UAAA,MAAA,GAF2C,oBAE3C,CAAA,CAAA;EAAC;EAAF,MACX,EADF,aACE,CADY,CACZ,CAAA;EAAS,QAAA,EAAT,SAAS;AAMrB;;;;AAEW,UAFM,+BAEN,CAAA,UAAA,MAAA,GAFyD,oBAEzD,CAAA,CAAA;EAAoB;EACV,OAOQ,EARlB,oBAQkB,CARG,CAQH,CAAA;EAAC,QAAjB,EAPD,SAOC;EAAe;AAMR;AAUpB;;;;EACoC,QAAhC,CAAA,EAjBS,eAiBT,CAjByB,CAiBzB,CAAA;EAA8B;;AACC;;;WAZxB;EC0GK;;;;;AAAgE;;;KDhGpE,wCAAwC,wBAChD,+BAA+B,KAC/B,gCAAgC;AEhDpC;;;;;;;;;;AFEA;AASA;;;;;;AAGqB;AAMrB;;;;;;;;;AAgBoB;AAUpB;;;;;;;AAEmC;;;;AC8FnC;;;;;AAAgF;;;;AC9IhF;;;;;AAAoF;AAQpF;;;;AAAyE;AAqCzE;;;AACqC,iBDgGrB,eChGqB,CAAA,UAAA,MAAA,CAAA,CAAA,KAAA,EDgGoB,oBChGpB,CDgGyC,CChGzC,CAAA,CAAA,EDgG2C,kBAAA,CAAA,GAAA,CAAA,OChG3C;AAAa;;;iBA9ClC,8BAA8B,yBAAyB,cAAc;iBAQrE,qCAAqC,mBAAmB;;AFNxE;AASA;;;;;;AAGqB;AAMrB;;;;;;;;;AAgBoB,iBESJ,iBFTI,CAAA,iBAAA,MAAA,CAAA,CAAA,CAAA,EAAA,GAAA,GEUiB,aFVjB,CEU+B,QFV/B,CAAA;AAUpB;;;;;;;AAEmC;;;;AC8FnC;;;;;AAAgF;;iBCzEhE,8DACiC,gBACvC,aACI,qBACT,SAAS;;AAzEd;;;;;AAAoF;AAQpF;;;;;;;AFwCoC,iBGuEpB,kBHvEoB,CAAA,UAAA,MAAA,CAAA,CAAA,OAAA,CAAA,EGuE2B,oBHvE3B,CGuEgD,CHvEhD,CAAA,CAAA,EAAA,IAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
-
import { ReplaneClient, ReplaneClientOptions } from "@replanejs/sdk";
|
|
2
|
+
import { GetConfigOptions, GetReplaneSnapshotOptions, ReplaneClient, ReplaneClientOptions, ReplaneSnapshot, clearSnapshotCache, getReplaneSnapshot } from "@replanejs/sdk";
|
|
3
3
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/types.d.ts
|
|
6
|
-
|
|
7
|
-
client: ReplaneClient<T>;
|
|
8
|
-
}
|
|
6
|
+
type UntypedReplaneConfig = Record<string, unknown>;
|
|
9
7
|
/**
|
|
10
8
|
* Props for ReplaneProvider when using a pre-created client.
|
|
11
9
|
*/
|
|
12
|
-
interface ReplaneProviderWithClientProps<T extends object =
|
|
10
|
+
interface ReplaneProviderWithClientProps<T extends object = UntypedReplaneConfig> {
|
|
13
11
|
/** Pre-created ReplaneClient instance */
|
|
14
12
|
client: ReplaneClient<T>;
|
|
15
13
|
children: ReactNode;
|
|
@@ -17,36 +15,42 @@ interface ReplaneProviderWithClientProps<T extends object = any> {
|
|
|
17
15
|
/**
|
|
18
16
|
* Props for ReplaneProvider when letting it manage the client internally.
|
|
19
17
|
*/
|
|
20
|
-
interface ReplaneProviderWithOptionsProps<T extends object =
|
|
21
|
-
/** Options to create the ReplaneClient */
|
|
18
|
+
interface ReplaneProviderWithOptionsProps<T extends object = UntypedReplaneConfig> {
|
|
19
|
+
/** Options to create or restore the ReplaneClient */
|
|
22
20
|
options: ReplaneClientOptions<T>;
|
|
23
21
|
children: ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Optional snapshot from server-side rendering.
|
|
24
|
+
* When provided, the client will be restored from the snapshot synchronously
|
|
25
|
+
* instead of fetching configs from the server.
|
|
26
|
+
* The `options` will be used for live updates connection if provided.
|
|
27
|
+
*/
|
|
28
|
+
snapshot?: ReplaneSnapshot<T>;
|
|
24
29
|
/**
|
|
25
30
|
* Optional loading component to show while the client is initializing.
|
|
26
31
|
* If not provided and suspense is false/undefined, children will not render until ready.
|
|
32
|
+
* Ignored when snapshot is provided (restoration is synchronous).
|
|
27
33
|
*/
|
|
28
34
|
loader?: ReactNode;
|
|
29
35
|
/**
|
|
30
36
|
* If true, uses React Suspense for loading state.
|
|
31
37
|
* The provider will throw a promise that Suspense can catch.
|
|
38
|
+
* Ignored when snapshot is provided (restoration is synchronous).
|
|
32
39
|
* @default false
|
|
33
40
|
*/
|
|
34
41
|
suspense?: boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Callback when client initialization fails.
|
|
37
|
-
*/
|
|
38
|
-
onError?: (error: Error) => void;
|
|
39
42
|
}
|
|
40
|
-
type ReplaneProviderProps<T extends object =
|
|
43
|
+
type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProviderWithClientProps<T> | ReplaneProviderWithOptionsProps<T>;
|
|
41
44
|
/**
|
|
42
45
|
* Type guard to check if props contain a pre-created client.
|
|
43
46
|
*/
|
|
47
|
+
|
|
44
48
|
//#endregion
|
|
45
49
|
//#region src/provider.d.ts
|
|
46
50
|
/**
|
|
47
51
|
* Provider component that makes a ReplaneClient available to the component tree.
|
|
48
52
|
*
|
|
49
|
-
* Can be used in
|
|
53
|
+
* Can be used in three ways:
|
|
50
54
|
*
|
|
51
55
|
* 1. With a pre-created client:
|
|
52
56
|
* ```tsx
|
|
@@ -58,36 +62,99 @@ type ReplaneProviderProps<T extends object = any> = ReplaneProviderWithClientPro
|
|
|
58
62
|
*
|
|
59
63
|
* 2. With options (client managed internally):
|
|
60
64
|
* ```tsx
|
|
65
|
+
* <ErrorBoundary fallback={<ErrorMessage />}>
|
|
66
|
+
* <ReplaneProvider
|
|
67
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
68
|
+
* loader={<LoadingSpinner />}
|
|
69
|
+
* >
|
|
70
|
+
* <App />
|
|
71
|
+
* </ReplaneProvider>
|
|
72
|
+
* </ErrorBoundary>
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* 3. With Suspense:
|
|
76
|
+
* ```tsx
|
|
77
|
+
* <ErrorBoundary fallback={<ErrorMessage />}>
|
|
78
|
+
* <Suspense fallback={<LoadingSpinner />}>
|
|
79
|
+
* <ReplaneProvider
|
|
80
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
81
|
+
* suspense
|
|
82
|
+
* >
|
|
83
|
+
* <App />
|
|
84
|
+
* </ReplaneProvider>
|
|
85
|
+
* </Suspense>
|
|
86
|
+
* </ErrorBoundary>
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* 4. With a snapshot (for SSR/hydration):
|
|
90
|
+
* ```tsx
|
|
91
|
+
* // On the server, get a snapshot from the client
|
|
92
|
+
* const snapshot = serverClient.getSnapshot();
|
|
93
|
+
*
|
|
94
|
+
* // On the client, restore from the snapshot with live updates
|
|
61
95
|
* <ReplaneProvider
|
|
62
96
|
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
63
|
-
*
|
|
97
|
+
* snapshot={snapshot}
|
|
64
98
|
* >
|
|
65
99
|
* <App />
|
|
66
100
|
* </ReplaneProvider>
|
|
67
101
|
* ```
|
|
68
102
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* <Suspense fallback={<LoadingSpinner />}>
|
|
72
|
-
* <ReplaneProvider
|
|
73
|
-
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
74
|
-
* suspense
|
|
75
|
-
* >
|
|
76
|
-
* <App />
|
|
77
|
-
* </ReplaneProvider>
|
|
78
|
-
* </Suspense>
|
|
79
|
-
* ```
|
|
103
|
+
* Errors during client initialization are thrown during rendering,
|
|
104
|
+
* allowing them to be caught by React Error Boundaries.
|
|
80
105
|
*/
|
|
81
106
|
declare function ReplaneProvider<T extends object>(props: ReplaneProviderProps<T>): react_jsx_runtime0.JSX.Element;
|
|
82
107
|
//# sourceMappingURL=provider.d.ts.map
|
|
83
108
|
//#endregion
|
|
84
109
|
//#region src/hooks.d.ts
|
|
85
|
-
declare function useReplane<T extends object =
|
|
86
|
-
declare function useConfig<T>(name: string, options?:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
110
|
+
declare function useReplane<T extends object = UntypedReplaneConfig>(): ReplaneClient<T>;
|
|
111
|
+
declare function useConfig<T>(name: string, options?: GetConfigOptions): T;
|
|
112
|
+
/**
|
|
113
|
+
* Creates a typed version of useReplane hook.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```tsx
|
|
117
|
+
* interface AppConfigs {
|
|
118
|
+
* theme: { darkMode: boolean };
|
|
119
|
+
* features: { beta: boolean };
|
|
120
|
+
* }
|
|
121
|
+
*
|
|
122
|
+
* const useAppReplane = createReplaneHook<AppConfigs>();
|
|
123
|
+
*
|
|
124
|
+
* function MyComponent() {
|
|
125
|
+
* const replane = useAppReplane();
|
|
126
|
+
* // replane.get("theme") returns { darkMode: boolean }
|
|
127
|
+
* }
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
declare function createReplaneHook<TConfigs extends object>(): () => ReplaneClient<TConfigs>;
|
|
131
|
+
/**
|
|
132
|
+
* Creates a typed version of useConfig hook.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```tsx
|
|
136
|
+
* interface AppConfigs {
|
|
137
|
+
* theme: { darkMode: boolean };
|
|
138
|
+
* features: { beta: boolean };
|
|
139
|
+
* }
|
|
140
|
+
*
|
|
141
|
+
* const useAppConfig = createConfigHook<AppConfigs>();
|
|
142
|
+
*
|
|
143
|
+
* function MyComponent() {
|
|
144
|
+
* const theme = useAppConfig("theme");
|
|
145
|
+
* // theme is typed as { darkMode: boolean }
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
declare function createConfigHook<TConfigs extends object>(): <K extends keyof TConfigs>(name: K, options?: GetConfigOptions) => TConfigs[K];
|
|
150
|
+
/**
|
|
151
|
+
* Hook for creating stateful resources with cleanup support.
|
|
152
|
+
* Unlike useMemo, this guarantees cleanup when dependencies change or on unmount.
|
|
153
|
+
*
|
|
154
|
+
* @param factory - Function that creates the resource
|
|
155
|
+
* @param cleanup - Function that cleans up the resource
|
|
156
|
+
* @param deps - Dependencies array (resource is recreated when these change)
|
|
157
|
+
*/
|
|
91
158
|
//#endregion
|
|
92
159
|
//#region src/useReplaneClient.d.ts
|
|
93
160
|
/**
|
|
@@ -96,5 +163,5 @@ declare function useConfig<T>(name: string, options?: {
|
|
|
96
163
|
*/
|
|
97
164
|
declare function clearSuspenseCache<T extends object>(options?: ReplaneClientOptions<T>): void;
|
|
98
165
|
//#endregion
|
|
99
|
-
export { type
|
|
166
|
+
export { type GetReplaneSnapshotOptions, ReplaneProvider, type ReplaneProviderProps, type ReplaneProviderWithClientProps, type ReplaneProviderWithOptionsProps, clearSnapshotCache, clearSuspenseCache, createConfigHook, createReplaneHook, getReplaneSnapshot, useConfig, useReplane };
|
|
100
167
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/provider.tsx","../src/hooks.ts","../src/useReplaneClient.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/provider.tsx","../src/hooks.ts","../src/useReplaneClient.ts"],"sourcesContent":[],"mappings":";;;;;KAOY,oBAAA,GAAuB;AASnC;;;AAEwB,UAFP,8BAEO,CAAA,UAAA,MAAA,GAF2C,oBAE3C,CAAA,CAAA;EAAC;EAAF,MACX,EADF,aACE,CADY,CACZ,CAAA;EAAS,QAAA,EAAT,SAAS;AAMrB;;;;AAEW,UAFM,+BAEN,CAAA,UAAA,MAAA,GAFyD,oBAEzD,CAAA,CAAA;EAAoB;EACV,OAOQ,EARlB,oBAQkB,CARG,CAQH,CAAA;EAAC,QAAjB,EAPD,SAOC;EAAe;AAMR;AAUpB;;;;EACoC,QAAhC,CAAA,EAjBS,eAiBT,CAjByB,CAiBzB,CAAA;EAA8B;;AACC;;;WAZxB;EC0GK;;;;;AAAgE;;;KDhGpE,wCAAwC,wBAChD,+BAA+B,KAC/B,gCAAgC;AEhDpC;;;;;;;;;;AFEA;AASA;;;;;;AAGqB;AAMrB;;;;;;;;;AAgBoB;AAUpB;;;;;;;AAEmC;;;;AC8FnC;;;;;AAAgF;;;;AC9IhF;;;;;AAAoF;AAQpF;;;;AAAyE;AAqCzE;;;AACqC,iBDgGrB,eChGqB,CAAA,UAAA,MAAA,CAAA,CAAA,KAAA,EDgGoB,oBChGpB,CDgGyC,CChGzC,CAAA,CAAA,EDgG2C,kBAAA,CAAA,GAAA,CAAA,OChG3C;AAAa;;;iBA9ClC,8BAA8B,yBAAyB,cAAc;iBAQrE,qCAAqC,mBAAmB;;AFNxE;AASA;;;;;;AAGqB;AAMrB;;;;;;;;;AAgBoB,iBESJ,iBFTI,CAAA,iBAAA,MAAA,CAAA,CAAA,CAAA,EAAA,GAAA,GEUiB,aFVjB,CEU+B,QFV/B,CAAA;AAUpB;;;;;;;AAEmC;;;;AC8FnC;;;;;AAAgF;;iBCzEhE,8DACiC,gBACvC,aACI,qBACT,SAAS;;AAzEd;;;;;AAAoF;AAQpF;;;;;;;AFwCoC,iBGuEpB,kBHvEoB,CAAA,UAAA,MAAA,CAAA,CAAA,OAAA,CAAA,EGuE2B,oBHvE3B,CGuEgD,CHvEhD,CAAA,CAAA,EAAA,IAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createContext, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
|
-
import { createReplaneClient } from "@replanejs/sdk";
|
|
1
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
|
+
import { clearSnapshotCache, createReplaneClient, getReplaneSnapshot, restoreReplaneClient } from "@replanejs/sdk";
|
|
3
3
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/context.ts
|
|
@@ -15,7 +15,7 @@ function getCacheKey(options) {
|
|
|
15
15
|
* Hook to manage ReplaneClient creation internally.
|
|
16
16
|
* Handles loading state and cleanup.
|
|
17
17
|
*/
|
|
18
|
-
function
|
|
18
|
+
function useReplaneClientInternal(options) {
|
|
19
19
|
const [state, setState] = useState({
|
|
20
20
|
status: "loading",
|
|
21
21
|
client: null,
|
|
@@ -40,13 +40,12 @@ function useReplaneClient(options, onError) {
|
|
|
40
40
|
});
|
|
41
41
|
} catch (err) {
|
|
42
42
|
if (cancelled) return;
|
|
43
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
43
|
+
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
|
|
44
44
|
setState({
|
|
45
45
|
status: "error",
|
|
46
46
|
client: null,
|
|
47
47
|
error
|
|
48
48
|
});
|
|
49
|
-
onError?.(error);
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
initClient();
|
|
@@ -93,6 +92,101 @@ function clearSuspenseCache(options) {
|
|
|
93
92
|
else suspenseCache.clear();
|
|
94
93
|
}
|
|
95
94
|
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/hooks.ts
|
|
97
|
+
function useReplane() {
|
|
98
|
+
const context = useContext(ReplaneContext);
|
|
99
|
+
if (!context) throw new Error("useReplane must be used within a ReplaneProvider");
|
|
100
|
+
return context.client;
|
|
101
|
+
}
|
|
102
|
+
function useConfig(name, options) {
|
|
103
|
+
const client = useReplane();
|
|
104
|
+
const subscribe = useCallback((callback) => {
|
|
105
|
+
return client.subscribe(name, callback);
|
|
106
|
+
}, [client, name]);
|
|
107
|
+
const get = useCallback(() => {
|
|
108
|
+
return client.get(name, options);
|
|
109
|
+
}, [
|
|
110
|
+
client,
|
|
111
|
+
name,
|
|
112
|
+
options
|
|
113
|
+
]);
|
|
114
|
+
const value = useSyncExternalStore(subscribe, get, get);
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Creates a typed version of useReplane hook.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```tsx
|
|
122
|
+
* interface AppConfigs {
|
|
123
|
+
* theme: { darkMode: boolean };
|
|
124
|
+
* features: { beta: boolean };
|
|
125
|
+
* }
|
|
126
|
+
*
|
|
127
|
+
* const useAppReplane = createReplaneHook<AppConfigs>();
|
|
128
|
+
*
|
|
129
|
+
* function MyComponent() {
|
|
130
|
+
* const replane = useAppReplane();
|
|
131
|
+
* // replane.get("theme") returns { darkMode: boolean }
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
function createReplaneHook() {
|
|
136
|
+
return function useTypedReplane() {
|
|
137
|
+
return useReplane();
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Creates a typed version of useConfig hook.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```tsx
|
|
145
|
+
* interface AppConfigs {
|
|
146
|
+
* theme: { darkMode: boolean };
|
|
147
|
+
* features: { beta: boolean };
|
|
148
|
+
* }
|
|
149
|
+
*
|
|
150
|
+
* const useAppConfig = createConfigHook<AppConfigs>();
|
|
151
|
+
*
|
|
152
|
+
* function MyComponent() {
|
|
153
|
+
* const theme = useAppConfig("theme");
|
|
154
|
+
* // theme is typed as { darkMode: boolean }
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
function createConfigHook() {
|
|
159
|
+
return function useTypedConfig(name, options) {
|
|
160
|
+
return useConfig(String(name), options);
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Hook for creating stateful resources with cleanup support.
|
|
165
|
+
* Unlike useMemo, this guarantees cleanup when dependencies change or on unmount.
|
|
166
|
+
*
|
|
167
|
+
* @param factory - Function that creates the resource
|
|
168
|
+
* @param cleanup - Function that cleans up the resource
|
|
169
|
+
* @param deps - Dependencies array (resource is recreated when these change)
|
|
170
|
+
*/
|
|
171
|
+
function useStateful(factory, cleanup, deps) {
|
|
172
|
+
const valueRef = useRef(null);
|
|
173
|
+
const initializedRef = useRef(false);
|
|
174
|
+
if (!initializedRef.current) {
|
|
175
|
+
valueRef.current = factory();
|
|
176
|
+
initializedRef.current = true;
|
|
177
|
+
}
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (valueRef.current === null) valueRef.current = factory();
|
|
180
|
+
return () => {
|
|
181
|
+
if (valueRef.current !== null) {
|
|
182
|
+
cleanup(valueRef.current);
|
|
183
|
+
valueRef.current = null;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}, deps);
|
|
187
|
+
return valueRef.current;
|
|
188
|
+
}
|
|
189
|
+
|
|
96
190
|
//#endregion
|
|
97
191
|
//#region src/types.ts
|
|
98
192
|
/**
|
|
@@ -115,12 +209,37 @@ function ReplaneProviderWithClient({ client, children }) {
|
|
|
115
209
|
});
|
|
116
210
|
}
|
|
117
211
|
/**
|
|
212
|
+
* Internal provider component for restoring client from snapshot.
|
|
213
|
+
* Uses restoreReplaneClient which is synchronous.
|
|
214
|
+
*/
|
|
215
|
+
function ReplaneProviderWithSnapshot({ options, snapshot, children }) {
|
|
216
|
+
const client = useStateful(() => restoreReplaneClient({
|
|
217
|
+
snapshot,
|
|
218
|
+
connection: {
|
|
219
|
+
baseUrl: options.baseUrl,
|
|
220
|
+
sdkKey: options.sdkKey,
|
|
221
|
+
fetchFn: options.fetchFn,
|
|
222
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
223
|
+
retryDelayMs: options.retryDelayMs,
|
|
224
|
+
inactivityTimeoutMs: options.inactivityTimeoutMs,
|
|
225
|
+
logger: options.logger
|
|
226
|
+
},
|
|
227
|
+
context: options.context
|
|
228
|
+
}), (c) => c.close(), [snapshot, options]);
|
|
229
|
+
const value = useMemo(() => ({ client }), [client]);
|
|
230
|
+
return /* @__PURE__ */ jsx(ReplaneContext.Provider, {
|
|
231
|
+
value,
|
|
232
|
+
children
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
118
236
|
* Internal provider component for options-based client creation (non-suspense).
|
|
237
|
+
* Throws errors during rendering so they can be caught by Error Boundaries.
|
|
119
238
|
*/
|
|
120
|
-
function ReplaneProviderWithOptions({ options, children, loader
|
|
121
|
-
const state =
|
|
239
|
+
function ReplaneProviderWithOptions({ options, children, loader }) {
|
|
240
|
+
const state = useReplaneClientInternal(options);
|
|
122
241
|
if (state.status === "loading") return /* @__PURE__ */ jsx(Fragment, { children: loader ?? null });
|
|
123
|
-
if (state.status === "error")
|
|
242
|
+
if (state.status === "error") throw state.error;
|
|
124
243
|
const value = { client: state.client };
|
|
125
244
|
return /* @__PURE__ */ jsx(ReplaneContext.Provider, {
|
|
126
245
|
value,
|
|
@@ -141,7 +260,7 @@ function ReplaneProviderWithSuspense({ options, children }) {
|
|
|
141
260
|
/**
|
|
142
261
|
* Provider component that makes a ReplaneClient available to the component tree.
|
|
143
262
|
*
|
|
144
|
-
* Can be used in
|
|
263
|
+
* Can be used in three ways:
|
|
145
264
|
*
|
|
146
265
|
* 1. With a pre-created client:
|
|
147
266
|
* ```tsx
|
|
@@ -153,47 +272,57 @@ function ReplaneProviderWithSuspense({ options, children }) {
|
|
|
153
272
|
*
|
|
154
273
|
* 2. With options (client managed internally):
|
|
155
274
|
* ```tsx
|
|
275
|
+
* <ErrorBoundary fallback={<ErrorMessage />}>
|
|
276
|
+
* <ReplaneProvider
|
|
277
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
278
|
+
* loader={<LoadingSpinner />}
|
|
279
|
+
* >
|
|
280
|
+
* <App />
|
|
281
|
+
* </ReplaneProvider>
|
|
282
|
+
* </ErrorBoundary>
|
|
283
|
+
* ```
|
|
284
|
+
*
|
|
285
|
+
* 3. With Suspense:
|
|
286
|
+
* ```tsx
|
|
287
|
+
* <ErrorBoundary fallback={<ErrorMessage />}>
|
|
288
|
+
* <Suspense fallback={<LoadingSpinner />}>
|
|
289
|
+
* <ReplaneProvider
|
|
290
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
291
|
+
* suspense
|
|
292
|
+
* >
|
|
293
|
+
* <App />
|
|
294
|
+
* </ReplaneProvider>
|
|
295
|
+
* </Suspense>
|
|
296
|
+
* </ErrorBoundary>
|
|
297
|
+
* ```
|
|
298
|
+
*
|
|
299
|
+
* 4. With a snapshot (for SSR/hydration):
|
|
300
|
+
* ```tsx
|
|
301
|
+
* // On the server, get a snapshot from the client
|
|
302
|
+
* const snapshot = serverClient.getSnapshot();
|
|
303
|
+
*
|
|
304
|
+
* // On the client, restore from the snapshot with live updates
|
|
156
305
|
* <ReplaneProvider
|
|
157
306
|
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
158
|
-
*
|
|
307
|
+
* snapshot={snapshot}
|
|
159
308
|
* >
|
|
160
309
|
* <App />
|
|
161
310
|
* </ReplaneProvider>
|
|
162
311
|
* ```
|
|
163
312
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* <Suspense fallback={<LoadingSpinner />}>
|
|
167
|
-
* <ReplaneProvider
|
|
168
|
-
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
169
|
-
* suspense
|
|
170
|
-
* >
|
|
171
|
-
* <App />
|
|
172
|
-
* </ReplaneProvider>
|
|
173
|
-
* </Suspense>
|
|
174
|
-
* ```
|
|
313
|
+
* Errors during client initialization are thrown during rendering,
|
|
314
|
+
* allowing them to be caught by React Error Boundaries.
|
|
175
315
|
*/
|
|
176
316
|
function ReplaneProvider(props) {
|
|
177
317
|
if (hasClient(props)) return /* @__PURE__ */ jsx(ReplaneProviderWithClient, { ...props });
|
|
318
|
+
if (props.snapshot) return /* @__PURE__ */ jsx(ReplaneProviderWithSnapshot, {
|
|
319
|
+
...props,
|
|
320
|
+
snapshot: props.snapshot
|
|
321
|
+
});
|
|
178
322
|
if (props.suspense) return /* @__PURE__ */ jsx(ReplaneProviderWithSuspense, { ...props });
|
|
179
323
|
return /* @__PURE__ */ jsx(ReplaneProviderWithOptions, { ...props });
|
|
180
324
|
}
|
|
181
325
|
|
|
182
326
|
//#endregion
|
|
183
|
-
|
|
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 };
|
|
327
|
+
export { ReplaneProvider, clearSnapshotCache, clearSuspenseCache, createConfigHook, createReplaneHook, getReplaneSnapshot, useConfig, useReplane };
|
|
199
328
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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\nexport function useReplane<T extends object = Record<string, unknown>>(): 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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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["options: ReplaneClientOptions<T>","options?: ReplaneClientOptions<T>","name: string","options?: GetConfigOptions","callback: () => void","name: K","factory: () => T","cleanup: (value: T) => void","deps: React.DependencyList","props: ReplaneProviderProps<T>","value: ReplaneContextValue<T>","props: ReplaneProviderProps<T>"],"sources":["../src/context.ts","../src/useReplaneClient.ts","../src/hooks.ts","../src/types.ts","../src/provider.tsx"],"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\ntype ErrorConstructor = new (message: string, options?: { cause?: unknown }) => Error;\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 useReplaneClientInternal<T extends object = any>(\n options: ReplaneClientOptions<T>\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 =\n err instanceof Error ? err : new (Error as ErrorConstructor)(String(err), { cause: err });\n setState({ status: \"error\", client: null, 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 }, []);\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 { useCallback, useContext, useEffect, useRef, useSyncExternalStore } from \"react\";\nimport { ReplaneContext } from \"./context\";\nimport type { UntypedReplaneConfig } from \"./types\";\nimport type { ReplaneClient, GetConfigOptions } from \"@replanejs/sdk\";\n\nexport function useReplane<T extends object = UntypedReplaneConfig>(): ReplaneClient<T> {\n const context = useContext(ReplaneContext);\n if (!context) {\n throw new Error(\"useReplane must be used within a ReplaneProvider\");\n }\n return context.client as ReplaneClient<T>;\n}\n\nexport function useConfig<T>(name: string, options?: GetConfigOptions): T {\n const client = useReplane();\n\n const subscribe = useCallback(\n (callback: () => void) => {\n return client.subscribe(name, callback);\n },\n [client, name]\n );\n\n const get = useCallback(() => {\n return client.get(name, options) as T;\n }, [client, name, options]);\n\n const value = useSyncExternalStore(subscribe, get, get);\n\n return value;\n}\n\n/**\n * Creates a typed version of useReplane hook.\n *\n * @example\n * ```tsx\n * interface AppConfigs {\n * theme: { darkMode: boolean };\n * features: { beta: boolean };\n * }\n *\n * const useAppReplane = createReplaneHook<AppConfigs>();\n *\n * function MyComponent() {\n * const replane = useAppReplane();\n * // replane.get(\"theme\") returns { darkMode: boolean }\n * }\n * ```\n */\nexport function createReplaneHook<TConfigs extends object>() {\n return function useTypedReplane(): ReplaneClient<TConfigs> {\n return useReplane<TConfigs>();\n };\n}\n\n/**\n * Creates a typed version of useConfig hook.\n *\n * @example\n * ```tsx\n * interface AppConfigs {\n * theme: { darkMode: boolean };\n * features: { beta: boolean };\n * }\n *\n * const useAppConfig = createConfigHook<AppConfigs>();\n *\n * function MyComponent() {\n * const theme = useAppConfig(\"theme\");\n * // theme is typed as { darkMode: boolean }\n * }\n * ```\n */\nexport function createConfigHook<TConfigs extends object>() {\n return function useTypedConfig<K extends keyof TConfigs>(\n name: K,\n options?: GetConfigOptions\n ): TConfigs[K] {\n return useConfig<TConfigs[K]>(String(name), options);\n };\n}\n\n/**\n * Hook for creating stateful resources with cleanup support.\n * Unlike useMemo, this guarantees cleanup when dependencies change or on unmount.\n *\n * @param factory - Function that creates the resource\n * @param cleanup - Function that cleans up the resource\n * @param deps - Dependencies array (resource is recreated when these change)\n */\nexport function useStateful<T>(\n factory: () => T,\n cleanup: (value: T) => void,\n deps: React.DependencyList\n): T {\n const valueRef = useRef<T | null>(null);\n const initializedRef = useRef(false);\n\n // Create initial value synchronously on first render\n if (!initializedRef.current) {\n valueRef.current = factory();\n initializedRef.current = true;\n }\n\n useEffect(() => {\n // On mount or deps change, we may need to recreate\n // If this is not the initial mount, recreate the value\n if (valueRef.current === null) {\n valueRef.current = factory();\n }\n\n return () => {\n if (valueRef.current !== null) {\n cleanup(valueRef.current);\n valueRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n return valueRef.current as T;\n}\n","import type {\n ReplaneClient,\n ReplaneClientOptions,\n ReplaneSnapshot,\n} from \"@replanejs/sdk\";\nimport type { ReactNode } from \"react\";\n\nexport type UntypedReplaneConfig = Record<string, unknown>;\n\nexport interface ReplaneContextValue<T extends object = UntypedReplaneConfig> {\n client: ReplaneClient<T>;\n}\n\n/**\n * Props for ReplaneProvider when using a pre-created client.\n */\nexport interface ReplaneProviderWithClientProps<T extends object = UntypedReplaneConfig> {\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 */\nexport interface ReplaneProviderWithOptionsProps<T extends object = UntypedReplaneConfig> {\n /** Options to create or restore the ReplaneClient */\n options: ReplaneClientOptions<T>;\n children: ReactNode;\n /**\n * Optional snapshot from server-side rendering.\n * When provided, the client will be restored from the snapshot synchronously\n * instead of fetching configs from the server.\n * The `options` will be used for live updates connection if provided.\n */\n snapshot?: ReplaneSnapshot<T>;\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 * Ignored when snapshot is provided (restoration is synchronous).\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 * Ignored when snapshot is provided (restoration is synchronous).\n * @default false\n */\n suspense?: boolean;\n}\n\nexport type ReplaneProviderProps<T extends object = UntypedReplaneConfig> =\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\n/**\n * Type guard to check if props contain options (with or without snapshot).\n */\nexport function hasOptions<T extends object>(\n props: ReplaneProviderProps<T>\n): props is ReplaneProviderWithOptionsProps<T> {\n return \"options\" in props && props.options !== undefined;\n}\n","import { useMemo } from \"react\";\nimport { restoreReplaneClient } from \"@replanejs/sdk\";\nimport { ReplaneContext } from \"./context\";\nimport { useReplaneClientInternal, useReplaneClientSuspense } from \"./useReplaneClient\";\nimport { useStateful } from \"./hooks\";\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 restoring client from snapshot.\n * Uses restoreReplaneClient which is synchronous.\n */\nfunction ReplaneProviderWithSnapshot<T extends object>({\n options,\n snapshot,\n children,\n}: ReplaneProviderWithOptionsProps<T> & { snapshot: NonNullable<ReplaneProviderWithOptionsProps<T>[\"snapshot\"]> }) {\n const client = useStateful(\n () =>\n restoreReplaneClient<T>({\n snapshot,\n connection: {\n baseUrl: options.baseUrl,\n sdkKey: options.sdkKey,\n fetchFn: options.fetchFn,\n requestTimeoutMs: options.requestTimeoutMs,\n retryDelayMs: options.retryDelayMs,\n inactivityTimeoutMs: options.inactivityTimeoutMs,\n logger: options.logger,\n },\n context: options.context,\n }),\n (c) => c.close(),\n [snapshot, options]\n );\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 * Throws errors during rendering so they can be caught by Error Boundaries.\n */\nfunction ReplaneProviderWithOptions<T extends object>({\n options,\n children,\n loader,\n}: ReplaneProviderWithOptionsProps<T>) {\n const state = useReplaneClientInternal<T>(options);\n\n if (state.status === \"loading\") {\n return <>{loader ?? null}</>;\n }\n\n if (state.status === \"error\") {\n // Throw error during render so it can be caught by Error Boundary\n throw state.error;\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 three 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 * <ErrorBoundary fallback={<ErrorMessage />}>\n * <ReplaneProvider\n * options={{ baseUrl: '...', sdkKey: '...' }}\n * loader={<LoadingSpinner />}\n * >\n * <App />\n * </ReplaneProvider>\n * </ErrorBoundary>\n * ```\n *\n * 3. With Suspense:\n * ```tsx\n * <ErrorBoundary fallback={<ErrorMessage />}>\n * <Suspense fallback={<LoadingSpinner />}>\n * <ReplaneProvider\n * options={{ baseUrl: '...', sdkKey: '...' }}\n * suspense\n * >\n * <App />\n * </ReplaneProvider>\n * </Suspense>\n * </ErrorBoundary>\n * ```\n *\n * 4. With a snapshot (for SSR/hydration):\n * ```tsx\n * // On the server, get a snapshot from the client\n * const snapshot = serverClient.getSnapshot();\n *\n * // On the client, restore from the snapshot with live updates\n * <ReplaneProvider\n * options={{ baseUrl: '...', sdkKey: '...' }}\n * snapshot={snapshot}\n * >\n * <App />\n * </ReplaneProvider>\n * ```\n *\n * Errors during client initialization are thrown during rendering,\n * allowing them to be caught by React Error Boundaries.\n */\nexport function ReplaneProvider<T extends object>(props: ReplaneProviderProps<T>) {\n if (hasClient(props)) {\n return <ReplaneProviderWithClient {...props} />;\n }\n\n // Has options - check if snapshot is provided\n if (props.snapshot) {\n return <ReplaneProviderWithSnapshot {...props} snapshot={props.snapshot} />;\n }\n\n if (props.suspense) {\n return <ReplaneProviderWithSuspense {...props} />;\n }\n\n return <ReplaneProviderWithOptions {...props} />;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replanejs/react",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "React SDK for Replane - feature flags and remote configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"react": ">=18.0.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@replanejs/sdk": "^0.7.
|
|
43
|
+
"@replanejs/sdk": "^0.7.5"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@testing-library/jest-dom": "^6.9.1",
|