@replanejs/react 0.8.20 → 0.9.2
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 +60 -30
- package/dist/index.cjs +162 -151
- package/dist/index.d.cts +24 -29
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +24 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -153
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ import { ReplaneProvider, useConfig } from "@replanejs/react";
|
|
|
29
29
|
function App() {
|
|
30
30
|
return (
|
|
31
31
|
<ReplaneProvider
|
|
32
|
-
|
|
32
|
+
connection={{
|
|
33
33
|
baseUrl: "https://your-replane-server.com",
|
|
34
34
|
sdkKey: "your-sdk-key",
|
|
35
35
|
}}
|
|
@@ -51,9 +51,9 @@ 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 multiple usage patterns:
|
|
55
55
|
|
|
56
|
-
#### 1. With
|
|
56
|
+
#### 1. With connection (recommended)
|
|
57
57
|
|
|
58
58
|
The provider creates and manages the client internally. Use an Error Boundary to handle initialization errors:
|
|
59
59
|
|
|
@@ -62,7 +62,7 @@ import { ErrorBoundary } from "react-error-boundary";
|
|
|
62
62
|
|
|
63
63
|
<ErrorBoundary fallback={<div>Failed to load configuration</div>}>
|
|
64
64
|
<ReplaneProvider
|
|
65
|
-
|
|
65
|
+
connection={{
|
|
66
66
|
baseUrl: "https://your-replane-server.com",
|
|
67
67
|
sdkKey: "your-sdk-key",
|
|
68
68
|
}}
|
|
@@ -73,29 +73,44 @@ import { ErrorBoundary } from "react-error-boundary";
|
|
|
73
73
|
</ErrorBoundary>;
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
####
|
|
76
|
+
#### Provider Props
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
| Prop | Type | Required | Description |
|
|
79
|
+
| ------------ | --------------------------- | -------- | ------------------------------------------------------- |
|
|
80
|
+
| `connection` | `ConnectOptions \| null` | Yes | Connection options (see below), or `null` to skip connection |
|
|
81
|
+
| `defaults` | `Record<string, unknown>` | No | Default values if server is unavailable |
|
|
82
|
+
| `context` | `Record<string, unknown>` | No | Default context for override evaluations |
|
|
83
|
+
| `snapshot` | `ReplaneSnapshot` | No | Snapshot for SSR hydration |
|
|
84
|
+
| `logger` | `ReplaneLogger` | No | Custom logger (default: console) |
|
|
85
|
+
| `loader` | `ReactNode` | No | Component to show while loading |
|
|
86
|
+
| `suspense` | `boolean` | No | Use React Suspense for loading state |
|
|
87
|
+
| `async` | `boolean` | No | Connect asynchronously (renders immediately with defaults) |
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
| ------------------------- | ---------------------- | -------- | ------------------------------------------ |
|
|
82
|
-
| `baseUrl` | `string` | Yes | Replane server URL |
|
|
83
|
-
| `sdkKey` | `string` | Yes | SDK key for authentication |
|
|
84
|
-
| `context` | `Record<string, any>` | No | Default context for override evaluations |
|
|
85
|
-
| `defaults` | `Record<string, any>` | No | Default values if server is unavailable |
|
|
86
|
-
| `required` | `string[]` or `object` | No | Configs that must exist for initialization |
|
|
87
|
-
| `initializationTimeoutMs` | `number` | No | SDK initialization timeout (default: 5000) |
|
|
89
|
+
#### Connection Options
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
The `connection` prop accepts the following options:
|
|
92
|
+
|
|
93
|
+
| Option | Type | Required | Description |
|
|
94
|
+
| -------------------- | --------------------- | -------- | -------------------------------------------- |
|
|
95
|
+
| `baseUrl` | `string` | Yes | Replane server URL |
|
|
96
|
+
| `sdkKey` | `string` | Yes | SDK key for authentication |
|
|
97
|
+
| `connectTimeoutMs` | `number` | No | SDK connection timeout (default: 5000) |
|
|
98
|
+
| `requestTimeoutMs` | `number` | No | Timeout for SSE requests (default: 2000) |
|
|
99
|
+
| `retryDelayMs` | `number` | No | Base delay between retries (default: 200) |
|
|
100
|
+
| `inactivityTimeoutMs`| `number` | No | SSE inactivity timeout (default: 30000) |
|
|
101
|
+
| `fetchFn` | `typeof fetch` | No | Custom fetch implementation |
|
|
102
|
+
|
|
103
|
+
See [`@replanejs/sdk` documentation](https://github.com/replane-dev/replane-javascript/tree/main/packages/sdk#api) for more details.
|
|
90
104
|
|
|
91
105
|
#### 2. With pre-created client
|
|
92
106
|
|
|
93
107
|
Use this when you need more control over client lifecycle:
|
|
94
108
|
|
|
95
109
|
```tsx
|
|
96
|
-
import {
|
|
110
|
+
import { Replane } from "@replanejs/sdk";
|
|
97
111
|
|
|
98
|
-
const client =
|
|
112
|
+
const client = new Replane();
|
|
113
|
+
await client.connect({
|
|
99
114
|
baseUrl: "https://your-replane-server.com",
|
|
100
115
|
sdkKey: "your-sdk-key",
|
|
101
116
|
});
|
|
@@ -113,7 +128,7 @@ Integrates with React Suspense for loading states:
|
|
|
113
128
|
<ErrorBoundary fallback={<div>Failed to load configuration</div>}>
|
|
114
129
|
<Suspense fallback={<LoadingSpinner />}>
|
|
115
130
|
<ReplaneProvider
|
|
116
|
-
|
|
131
|
+
connection={{
|
|
117
132
|
baseUrl: "https://your-replane-server.com",
|
|
118
133
|
sdkKey: "your-sdk-key",
|
|
119
134
|
}}
|
|
@@ -125,32 +140,47 @@ Integrates with React Suspense for loading states:
|
|
|
125
140
|
</ErrorBoundary>
|
|
126
141
|
```
|
|
127
142
|
|
|
128
|
-
#### 4. With
|
|
143
|
+
#### 4. With async mode
|
|
144
|
+
|
|
145
|
+
Connect in the background while rendering immediately with defaults:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
<ReplaneProvider
|
|
149
|
+
connection={{
|
|
150
|
+
baseUrl: "https://your-replane-server.com",
|
|
151
|
+
sdkKey: "your-sdk-key",
|
|
152
|
+
}}
|
|
153
|
+
defaults={{ featureEnabled: false }}
|
|
154
|
+
async
|
|
155
|
+
>
|
|
156
|
+
<App />
|
|
157
|
+
</ReplaneProvider>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### 5. With snapshot (for SSR/hydration)
|
|
129
161
|
|
|
130
162
|
Restore a client from a snapshot obtained on the server. This is synchronous and useful for SSR scenarios:
|
|
131
163
|
|
|
132
164
|
```tsx
|
|
133
165
|
// On the server
|
|
134
|
-
const serverClient =
|
|
166
|
+
const serverClient = new Replane();
|
|
167
|
+
await serverClient.connect({ baseUrl: "...", sdkKey: "..." });
|
|
135
168
|
const snapshot = serverClient.getSnapshot();
|
|
136
169
|
// Pass snapshot to client via props, context, or serialized HTML
|
|
137
170
|
|
|
138
171
|
// On the client
|
|
139
172
|
<ReplaneProvider
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
connection: {
|
|
144
|
-
baseUrl: "https://your-replane-server.com",
|
|
145
|
-
sdkKey: "your-sdk-key",
|
|
146
|
-
},
|
|
173
|
+
connection={{
|
|
174
|
+
baseUrl: "https://your-replane-server.com",
|
|
175
|
+
sdkKey: "your-sdk-key",
|
|
147
176
|
}}
|
|
177
|
+
snapshot={snapshot}
|
|
148
178
|
>
|
|
149
179
|
<App />
|
|
150
180
|
</ReplaneProvider>;
|
|
151
181
|
```
|
|
152
182
|
|
|
153
|
-
The restored client is immediately available with no loading state.
|
|
183
|
+
The restored client is immediately available with no loading state. The provider will establish a connection for real-time updates in the background.
|
|
154
184
|
|
|
155
185
|
### useConfig
|
|
156
186
|
|
|
@@ -350,7 +380,7 @@ class ErrorBoundary extends Component<
|
|
|
350
380
|
|
|
351
381
|
// Usage
|
|
352
382
|
<ErrorBoundary fallback={<div>Configuration failed to load</div>}>
|
|
353
|
-
<ReplaneProvider
|
|
383
|
+
<ReplaneProvider connection={connection} loader={<Loading />}>
|
|
354
384
|
<App />
|
|
355
385
|
</ReplaneProvider>
|
|
356
386
|
</ErrorBoundary>;
|
|
@@ -370,7 +400,7 @@ import { ErrorBoundary } from "react-error-boundary";
|
|
|
370
400
|
)}
|
|
371
401
|
onReset={() => clearSuspenseCache()}
|
|
372
402
|
>
|
|
373
|
-
<ReplaneProvider
|
|
403
|
+
<ReplaneProvider connection={connection} loader={<Loading />}>
|
|
374
404
|
<App />
|
|
375
405
|
</ReplaneProvider>
|
|
376
406
|
</ErrorBoundary>;
|
package/dist/index.cjs
CHANGED
|
@@ -31,11 +31,6 @@ const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
|
31
31
|
//#region src/context.ts
|
|
32
32
|
const ReplaneContext = (0, react.createContext)(null);
|
|
33
33
|
|
|
34
|
-
//#endregion
|
|
35
|
-
//#region src/version.ts
|
|
36
|
-
const VERSION = "0.8.20";
|
|
37
|
-
const DEFAULT_AGENT = `replane-js-react/${VERSION}`;
|
|
38
|
-
|
|
39
34
|
//#endregion
|
|
40
35
|
//#region src/useReplaneClient.ts
|
|
41
36
|
const suspenseCache = new Map();
|
|
@@ -43,10 +38,23 @@ function getCacheKey(options) {
|
|
|
43
38
|
return `${options.baseUrl}:${options.sdkKey}`;
|
|
44
39
|
}
|
|
45
40
|
/**
|
|
46
|
-
*
|
|
41
|
+
* Creates a Replane client and connects it.
|
|
42
|
+
*/
|
|
43
|
+
async function createAndConnectClient(options, connection) {
|
|
44
|
+
const client = new __replanejs_sdk.Replane({
|
|
45
|
+
logger: options.logger,
|
|
46
|
+
context: options.context,
|
|
47
|
+
defaults: options.defaults
|
|
48
|
+
});
|
|
49
|
+
if (!connection) return client;
|
|
50
|
+
await client.connect(connection);
|
|
51
|
+
return client;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Hook to manage Replane client creation internally.
|
|
47
55
|
* Handles loading state and cleanup.
|
|
48
56
|
*/
|
|
49
|
-
function useReplaneClientInternal(options) {
|
|
57
|
+
function useReplaneClientInternal(options, originalConnection) {
|
|
50
58
|
const [state, setState] = (0, react.useState)({
|
|
51
59
|
status: "loading",
|
|
52
60
|
client: null,
|
|
@@ -54,16 +62,15 @@ function useReplaneClientInternal(options) {
|
|
|
54
62
|
});
|
|
55
63
|
const clientRef = (0, react.useRef)(null);
|
|
56
64
|
const optionsRef = (0, react.useRef)(options);
|
|
65
|
+
const connectionJson = JSON.stringify(originalConnection);
|
|
57
66
|
(0, react.useEffect)(() => {
|
|
67
|
+
const connection = JSON.parse(connectionJson);
|
|
58
68
|
let cancelled = false;
|
|
59
69
|
async function initClient() {
|
|
60
70
|
try {
|
|
61
|
-
const client = await (
|
|
62
|
-
...optionsRef.current,
|
|
63
|
-
agent: optionsRef.current.agent ?? DEFAULT_AGENT
|
|
64
|
-
});
|
|
71
|
+
const client = await createAndConnectClient(optionsRef.current, connection);
|
|
65
72
|
if (cancelled) {
|
|
66
|
-
client.
|
|
73
|
+
client.disconnect();
|
|
67
74
|
return;
|
|
68
75
|
}
|
|
69
76
|
clientRef.current = client;
|
|
@@ -86,29 +93,26 @@ function useReplaneClientInternal(options) {
|
|
|
86
93
|
return () => {
|
|
87
94
|
cancelled = true;
|
|
88
95
|
if (clientRef.current) {
|
|
89
|
-
clientRef.current.
|
|
96
|
+
clientRef.current.disconnect();
|
|
90
97
|
clientRef.current = null;
|
|
91
98
|
}
|
|
92
99
|
};
|
|
93
|
-
}, []);
|
|
100
|
+
}, [connectionJson]);
|
|
94
101
|
return state;
|
|
95
102
|
}
|
|
96
103
|
/**
|
|
97
104
|
* Hook for Suspense-based client creation.
|
|
98
105
|
* Throws a promise while loading, throws error on failure.
|
|
99
106
|
*/
|
|
100
|
-
function useReplaneClientSuspense(options) {
|
|
101
|
-
const cacheKey = getCacheKey(
|
|
107
|
+
function useReplaneClientSuspense(options, connection) {
|
|
108
|
+
const cacheKey = getCacheKey(connection);
|
|
102
109
|
const cached = suspenseCache.get(cacheKey);
|
|
103
110
|
if (cached) {
|
|
104
111
|
if (cached.error) throw cached.error;
|
|
105
112
|
if (cached.result) return cached.result;
|
|
106
113
|
throw cached.promise;
|
|
107
114
|
}
|
|
108
|
-
const promise = (
|
|
109
|
-
...options,
|
|
110
|
-
agent: options.agent ?? DEFAULT_AGENT
|
|
111
|
-
}).then((client) => {
|
|
115
|
+
const promise = createAndConnectClient(options, connection).then((client) => {
|
|
112
116
|
const entry = suspenseCache.get(cacheKey);
|
|
113
117
|
if (entry) entry.result = client;
|
|
114
118
|
return client;
|
|
@@ -121,150 +125,59 @@ function useReplaneClientSuspense(options) {
|
|
|
121
125
|
throw promise;
|
|
122
126
|
}
|
|
123
127
|
/**
|
|
124
|
-
* Clear the suspense cache for a specific
|
|
128
|
+
* Clear the suspense cache for a specific connection configuration.
|
|
125
129
|
* Useful for testing or when you need to force re-initialization.
|
|
126
130
|
*/
|
|
127
|
-
function clearSuspenseCache(
|
|
128
|
-
if (
|
|
131
|
+
function clearSuspenseCache(connection) {
|
|
132
|
+
if (connection) suspenseCache.delete(getCacheKey(connection));
|
|
129
133
|
else suspenseCache.clear();
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
//#endregion
|
|
133
|
-
//#region src/hooks.ts
|
|
134
|
-
function useReplane() {
|
|
135
|
-
const context = (0, react.useContext)(ReplaneContext);
|
|
136
|
-
if (!context) throw new Error("useReplane must be used within a ReplaneProvider");
|
|
137
|
-
return context.client;
|
|
138
|
-
}
|
|
139
|
-
function useConfig(name, options) {
|
|
140
|
-
const client = useReplane();
|
|
141
|
-
const subscribe = (0, react.useCallback)((callback) => {
|
|
142
|
-
return client.subscribe(name, callback);
|
|
143
|
-
}, [client, name]);
|
|
144
|
-
const get = (0, react.useCallback)(() => {
|
|
145
|
-
return client.get(name, options);
|
|
146
|
-
}, [
|
|
147
|
-
client,
|
|
148
|
-
name,
|
|
149
|
-
options
|
|
150
|
-
]);
|
|
151
|
-
const value = (0, react.useSyncExternalStore)(subscribe, get, get);
|
|
152
|
-
return value;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Creates a typed version of useReplane hook.
|
|
156
|
-
*
|
|
157
|
-
* @example
|
|
158
|
-
* ```tsx
|
|
159
|
-
* interface AppConfigs {
|
|
160
|
-
* theme: { darkMode: boolean };
|
|
161
|
-
* features: { beta: boolean };
|
|
162
|
-
* }
|
|
163
|
-
*
|
|
164
|
-
* const useAppReplane = createReplaneHook<AppConfigs>();
|
|
165
|
-
*
|
|
166
|
-
* function MyComponent() {
|
|
167
|
-
* const replane = useAppReplane();
|
|
168
|
-
* // replane.get("theme") returns { darkMode: boolean }
|
|
169
|
-
* }
|
|
170
|
-
* ```
|
|
171
|
-
*/
|
|
172
|
-
function createReplaneHook() {
|
|
173
|
-
return function useTypedReplane() {
|
|
174
|
-
return useReplane();
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Creates a typed version of useConfig hook.
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* ```tsx
|
|
182
|
-
* interface AppConfigs {
|
|
183
|
-
* theme: { darkMode: boolean };
|
|
184
|
-
* features: { beta: boolean };
|
|
185
|
-
* }
|
|
186
|
-
*
|
|
187
|
-
* const useAppConfig = createConfigHook<AppConfigs>();
|
|
188
|
-
*
|
|
189
|
-
* function MyComponent() {
|
|
190
|
-
* const theme = useAppConfig("theme");
|
|
191
|
-
* // theme is typed as { darkMode: boolean }
|
|
192
|
-
* }
|
|
193
|
-
* ```
|
|
194
|
-
*/
|
|
195
|
-
function createConfigHook() {
|
|
196
|
-
return function useTypedConfig(name, options) {
|
|
197
|
-
return useConfig(String(name), options);
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Hook for creating stateful resources with cleanup support.
|
|
202
|
-
* Unlike useMemo, this guarantees cleanup when dependencies change or on unmount.
|
|
203
|
-
*
|
|
204
|
-
* @param factory - Function that creates the resource
|
|
205
|
-
* @param cleanup - Function that cleans up the resource
|
|
206
|
-
* @param deps - Dependencies array (resource is recreated when these change)
|
|
207
|
-
*/
|
|
208
|
-
function useStateful(factory, cleanup, deps) {
|
|
209
|
-
const valueRef = (0, react.useRef)(null);
|
|
210
|
-
const initializedRef = (0, react.useRef)(false);
|
|
211
|
-
if (!initializedRef.current) {
|
|
212
|
-
valueRef.current = factory();
|
|
213
|
-
initializedRef.current = true;
|
|
214
|
-
}
|
|
215
|
-
(0, react.useEffect)(() => {
|
|
216
|
-
if (valueRef.current === null) valueRef.current = factory();
|
|
217
|
-
return () => {
|
|
218
|
-
if (valueRef.current !== null) {
|
|
219
|
-
cleanup(valueRef.current);
|
|
220
|
-
valueRef.current = null;
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
}, deps);
|
|
224
|
-
return valueRef.current;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
136
|
//#endregion
|
|
228
137
|
//#region src/types.ts
|
|
229
138
|
/**
|
|
230
139
|
* Type guard to check if props contain a pre-created client.
|
|
231
140
|
*/
|
|
232
141
|
function hasClient(props) {
|
|
233
|
-
return "client" in props && props.client
|
|
142
|
+
return "client" in props && !!props.client;
|
|
234
143
|
}
|
|
235
144
|
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/version.ts
|
|
147
|
+
const VERSION = "0.9.2";
|
|
148
|
+
const DEFAULT_AGENT = `replane-js-react/${VERSION}`;
|
|
149
|
+
|
|
236
150
|
//#endregion
|
|
237
151
|
//#region src/provider.tsx
|
|
238
152
|
/**
|
|
239
153
|
* Internal provider component for pre-created client.
|
|
240
154
|
*/
|
|
241
155
|
function ReplaneProviderWithClient({ client, children }) {
|
|
242
|
-
const value = (0, react.useMemo)(() => ({ client }), [client]);
|
|
156
|
+
const value = (0, react.useMemo)(() => ({ replane: client }), [client]);
|
|
243
157
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
|
|
244
158
|
value,
|
|
245
159
|
children
|
|
246
160
|
});
|
|
247
161
|
}
|
|
248
162
|
/**
|
|
249
|
-
* Internal provider component for
|
|
250
|
-
*
|
|
163
|
+
* Internal provider component for creating a Replane client asynchronously.
|
|
164
|
+
* Creates a Replane client synchronously and connects in background.
|
|
251
165
|
*/
|
|
252
|
-
function
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const value = (0, react.useMemo)(() => ({ client }), [client]);
|
|
166
|
+
function AsyncReplaneProvider({ children, connection,...options }) {
|
|
167
|
+
const replaneRef = (0, react.useRef)(void 0);
|
|
168
|
+
if (!replaneRef.current) replaneRef.current = new __replanejs_sdk.Replane(options);
|
|
169
|
+
const connectionJson = connection ? JSON.stringify(connection) : void 0;
|
|
170
|
+
(0, react.useEffect)(() => {
|
|
171
|
+
const parsedConnection = connectionJson ? JSON.parse(connectionJson) : void 0;
|
|
172
|
+
if (!parsedConnection) return;
|
|
173
|
+
replaneRef.current.connect(parsedConnection).catch((err) => {
|
|
174
|
+
(options.logger ?? console)?.error("Failed to connect Replane client", err);
|
|
175
|
+
});
|
|
176
|
+
return () => {
|
|
177
|
+
replaneRef.current.disconnect();
|
|
178
|
+
};
|
|
179
|
+
}, [connectionJson, options.logger]);
|
|
180
|
+
const value = (0, react.useMemo)(() => ({ replane: replaneRef.current }), []);
|
|
268
181
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
|
|
269
182
|
value,
|
|
270
183
|
children
|
|
@@ -274,11 +187,12 @@ function ReplaneProviderWithSnapshot({ options, snapshot, children }) {
|
|
|
274
187
|
* Internal provider component for options-based client creation (non-suspense).
|
|
275
188
|
* Throws errors during rendering so they can be caught by Error Boundaries.
|
|
276
189
|
*/
|
|
277
|
-
function
|
|
278
|
-
|
|
190
|
+
function LoaderReplaneProvider({ children, loader, connection,...options }) {
|
|
191
|
+
if (!connection) throw new Error("Connection is required when using Loader");
|
|
192
|
+
const state = useReplaneClientInternal(options, connection);
|
|
279
193
|
if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: loader ?? null });
|
|
280
194
|
if (state.status === "error") throw state.error;
|
|
281
|
-
const value = {
|
|
195
|
+
const value = { replane: state.client };
|
|
282
196
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
|
|
283
197
|
value,
|
|
284
198
|
children
|
|
@@ -287,22 +201,24 @@ function ReplaneProviderWithOptions({ options, children, loader }) {
|
|
|
287
201
|
/**
|
|
288
202
|
* Internal provider component for options-based client creation with Suspense.
|
|
289
203
|
*/
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
const
|
|
204
|
+
function SuspenseReplaneProvider({ connection, children,...options }) {
|
|
205
|
+
if (!connection) throw new Error("Connection is required when using Suspense");
|
|
206
|
+
const client = useReplaneClientSuspense(options, connection);
|
|
207
|
+
const value = (0, react.useMemo)(() => ({ replane: client }), [client]);
|
|
293
208
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
|
|
294
209
|
value,
|
|
295
210
|
children
|
|
296
211
|
});
|
|
297
212
|
}
|
|
298
213
|
/**
|
|
299
|
-
* Provider component that makes a
|
|
214
|
+
* Provider component that makes a Replane client available to the component tree.
|
|
300
215
|
*
|
|
301
|
-
* Can be used in
|
|
216
|
+
* Can be used in several ways:
|
|
302
217
|
*
|
|
303
218
|
* 1. With a pre-created client:
|
|
304
219
|
* ```tsx
|
|
305
|
-
* const client =
|
|
220
|
+
* const client = new Replane({ defaults: { ... } });
|
|
221
|
+
* await client.connect({ baseUrl: '...', sdkKey: '...' });
|
|
306
222
|
* <ReplaneProvider client={client}>
|
|
307
223
|
* <App />
|
|
308
224
|
* </ReplaneProvider>
|
|
@@ -352,16 +268,111 @@ function ReplaneProviderWithSuspense({ options, children }) {
|
|
|
352
268
|
* allowing them to be caught by React Error Boundaries.
|
|
353
269
|
*/
|
|
354
270
|
function ReplaneProvider(props) {
|
|
271
|
+
const originalConnection = props.connection;
|
|
272
|
+
const connection = (0, react.useMemo)(() => originalConnection ? {
|
|
273
|
+
...originalConnection,
|
|
274
|
+
agent: originalConnection.agent ?? DEFAULT_AGENT
|
|
275
|
+
} : void 0, [originalConnection]);
|
|
355
276
|
if (hasClient(props)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithClient, { ...props });
|
|
356
|
-
if (props.snapshot) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
277
|
+
if (props.snapshot || !connection || props.async) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AsyncReplaneProvider, { ...props });
|
|
278
|
+
if (props.suspense) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuspenseReplaneProvider, {
|
|
357
279
|
...props,
|
|
358
|
-
|
|
280
|
+
connection
|
|
359
281
|
});
|
|
360
|
-
|
|
361
|
-
|
|
282
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderReplaneProvider, {
|
|
283
|
+
...props,
|
|
284
|
+
connection
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/hooks.ts
|
|
290
|
+
function useReplane() {
|
|
291
|
+
const context = (0, react.useContext)(ReplaneContext);
|
|
292
|
+
if (!context) throw new Error("useReplane must be used within a ReplaneProvider");
|
|
293
|
+
return context.replane;
|
|
294
|
+
}
|
|
295
|
+
function useConfig(name, options) {
|
|
296
|
+
const client = useReplane();
|
|
297
|
+
const subscribe = (0, react.useCallback)((callback) => {
|
|
298
|
+
return client.subscribe(name, callback);
|
|
299
|
+
}, [client, name]);
|
|
300
|
+
const get = (0, react.useCallback)(() => {
|
|
301
|
+
return client.get(name, options);
|
|
302
|
+
}, [
|
|
303
|
+
client,
|
|
304
|
+
name,
|
|
305
|
+
options
|
|
306
|
+
]);
|
|
307
|
+
const value = (0, react.useSyncExternalStore)(subscribe, get, get);
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Creates a typed version of useReplane hook.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```tsx
|
|
315
|
+
* interface AppConfigs {
|
|
316
|
+
* theme: { darkMode: boolean };
|
|
317
|
+
* features: { beta: boolean };
|
|
318
|
+
* }
|
|
319
|
+
*
|
|
320
|
+
* const useAppReplane = createReplaneHook<AppConfigs>();
|
|
321
|
+
*
|
|
322
|
+
* function MyComponent() {
|
|
323
|
+
* const replane = useAppReplane();
|
|
324
|
+
* // replane.get("theme") returns { darkMode: boolean }
|
|
325
|
+
* }
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
function createReplaneHook() {
|
|
329
|
+
return function useTypedReplane() {
|
|
330
|
+
return useReplane();
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Creates a typed version of useConfig hook.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```tsx
|
|
338
|
+
* interface AppConfigs {
|
|
339
|
+
* theme: { darkMode: boolean };
|
|
340
|
+
* features: { beta: boolean };
|
|
341
|
+
* }
|
|
342
|
+
*
|
|
343
|
+
* const useAppConfig = createConfigHook<AppConfigs>();
|
|
344
|
+
*
|
|
345
|
+
* function MyComponent() {
|
|
346
|
+
* const theme = useAppConfig("theme");
|
|
347
|
+
* // theme is typed as { darkMode: boolean }
|
|
348
|
+
* }
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
function createConfigHook() {
|
|
352
|
+
return function useTypedConfig(name, options) {
|
|
353
|
+
return useConfig(String(name), options);
|
|
354
|
+
};
|
|
362
355
|
}
|
|
363
356
|
|
|
364
357
|
//#endregion
|
|
358
|
+
Object.defineProperty(exports, 'Replane', {
|
|
359
|
+
enumerable: true,
|
|
360
|
+
get: function () {
|
|
361
|
+
return __replanejs_sdk.Replane;
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
Object.defineProperty(exports, 'ReplaneError', {
|
|
365
|
+
enumerable: true,
|
|
366
|
+
get: function () {
|
|
367
|
+
return __replanejs_sdk.ReplaneError;
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
Object.defineProperty(exports, 'ReplaneErrorCode', {
|
|
371
|
+
enumerable: true,
|
|
372
|
+
get: function () {
|
|
373
|
+
return __replanejs_sdk.ReplaneErrorCode;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
365
376
|
exports.ReplaneProvider = ReplaneProvider;
|
|
366
377
|
exports.clearSuspenseCache = clearSuspenseCache;
|
|
367
378
|
exports.createConfigHook = createConfigHook;
|