@replanejs/react 0.7.4 → 0.7.6
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/dist/index.cjs +139 -86
- package/dist/index.d.cts +25 -20
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +25 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -89
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -115,6 +115,101 @@ function clearSuspenseCache(options) {
|
|
|
115
115
|
else suspenseCache.clear();
|
|
116
116
|
}
|
|
117
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
|
+
|
|
118
213
|
//#endregion
|
|
119
214
|
//#region src/types.ts
|
|
120
215
|
/**
|
|
@@ -123,12 +218,6 @@ function clearSuspenseCache(options) {
|
|
|
123
218
|
function hasClient(props) {
|
|
124
219
|
return "client" in props && props.client !== void 0;
|
|
125
220
|
}
|
|
126
|
-
/**
|
|
127
|
-
* Type guard to check if props contain restore options.
|
|
128
|
-
*/
|
|
129
|
-
function hasRestoreOptions(props) {
|
|
130
|
-
return "restoreOptions" in props && props.restoreOptions !== void 0;
|
|
131
|
-
}
|
|
132
221
|
|
|
133
222
|
//#endregion
|
|
134
223
|
//#region src/provider.tsx
|
|
@@ -143,6 +232,30 @@ function ReplaneProviderWithClient({ client, children }) {
|
|
|
143
232
|
});
|
|
144
233
|
}
|
|
145
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
|
+
/**
|
|
146
259
|
* Internal provider component for options-based client creation (non-suspense).
|
|
147
260
|
* Throws errors during rendering so they can be caught by Error Boundaries.
|
|
148
261
|
*/
|
|
@@ -168,21 +281,9 @@ function ReplaneProviderWithSuspense({ options, children }) {
|
|
|
168
281
|
});
|
|
169
282
|
}
|
|
170
283
|
/**
|
|
171
|
-
* Internal provider component for restoring client from snapshot.
|
|
172
|
-
* Uses restoreReplaneClient which is synchronous.
|
|
173
|
-
*/
|
|
174
|
-
function ReplaneProviderWithSnapshot({ restoreOptions, children }) {
|
|
175
|
-
const client = (0, react.useMemo)(() => (0, __replanejs_sdk.restoreReplaneClient)(restoreOptions), [restoreOptions]);
|
|
176
|
-
const value = (0, react.useMemo)(() => ({ client }), [client]);
|
|
177
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneContext.Provider, {
|
|
178
|
-
value,
|
|
179
|
-
children
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
284
|
* Provider component that makes a ReplaneClient available to the component tree.
|
|
184
285
|
*
|
|
185
|
-
* Can be used in
|
|
286
|
+
* Can be used in three ways:
|
|
186
287
|
*
|
|
187
288
|
* 1. With a pre-created client:
|
|
188
289
|
* ```tsx
|
|
@@ -223,12 +324,10 @@ function ReplaneProviderWithSnapshot({ restoreOptions, children }) {
|
|
|
223
324
|
* // On the server, get a snapshot from the client
|
|
224
325
|
* const snapshot = serverClient.getSnapshot();
|
|
225
326
|
*
|
|
226
|
-
* // On the client, restore from the snapshot
|
|
327
|
+
* // On the client, restore from the snapshot with live updates
|
|
227
328
|
* <ReplaneProvider
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* connection: { baseUrl: '...', sdkKey: '...' } // optional, for live updates
|
|
231
|
-
* }}
|
|
329
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
330
|
+
* snapshot={snapshot}
|
|
232
331
|
* >
|
|
233
332
|
* <App />
|
|
234
333
|
* </ReplaneProvider>
|
|
@@ -239,76 +338,30 @@ function ReplaneProviderWithSnapshot({ restoreOptions, children }) {
|
|
|
239
338
|
*/
|
|
240
339
|
function ReplaneProvider(props) {
|
|
241
340
|
if (hasClient(props)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithClient, { ...props });
|
|
242
|
-
if (
|
|
341
|
+
if (props.snapshot) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithSnapshot, {
|
|
342
|
+
...props,
|
|
343
|
+
snapshot: props.snapshot
|
|
344
|
+
});
|
|
243
345
|
if (props.suspense) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithSuspense, { ...props });
|
|
244
346
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplaneProviderWithOptions, { ...props });
|
|
245
347
|
}
|
|
246
348
|
|
|
247
|
-
//#endregion
|
|
248
|
-
//#region src/hooks.ts
|
|
249
|
-
function useReplane() {
|
|
250
|
-
const context = (0, react.useContext)(ReplaneContext);
|
|
251
|
-
if (!context) throw new Error("useReplane must be used within a ReplaneProvider");
|
|
252
|
-
return context.client;
|
|
253
|
-
}
|
|
254
|
-
function useConfig(name, options) {
|
|
255
|
-
const client = useReplane();
|
|
256
|
-
const value = (0, react.useSyncExternalStore)((onStoreChange) => {
|
|
257
|
-
return client.subscribe(name, onStoreChange);
|
|
258
|
-
}, () => client.get(name, options), () => client.get(name, options));
|
|
259
|
-
return value;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Creates a typed version of useReplane hook.
|
|
263
|
-
*
|
|
264
|
-
* @example
|
|
265
|
-
* ```tsx
|
|
266
|
-
* interface AppConfigs {
|
|
267
|
-
* theme: { darkMode: boolean };
|
|
268
|
-
* features: { beta: boolean };
|
|
269
|
-
* }
|
|
270
|
-
*
|
|
271
|
-
* const useAppReplane = createReplaneHook<AppConfigs>();
|
|
272
|
-
*
|
|
273
|
-
* function MyComponent() {
|
|
274
|
-
* const replane = useAppReplane();
|
|
275
|
-
* // replane.get("theme") returns { darkMode: boolean }
|
|
276
|
-
* }
|
|
277
|
-
* ```
|
|
278
|
-
*/
|
|
279
|
-
function createReplaneHook() {
|
|
280
|
-
return function useTypedReplane() {
|
|
281
|
-
return useReplane();
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Creates a typed version of useConfig hook.
|
|
286
|
-
*
|
|
287
|
-
* @example
|
|
288
|
-
* ```tsx
|
|
289
|
-
* interface AppConfigs {
|
|
290
|
-
* theme: { darkMode: boolean };
|
|
291
|
-
* features: { beta: boolean };
|
|
292
|
-
* }
|
|
293
|
-
*
|
|
294
|
-
* const useAppConfig = createConfigHook<AppConfigs>();
|
|
295
|
-
*
|
|
296
|
-
* function MyComponent() {
|
|
297
|
-
* const theme = useAppConfig("theme");
|
|
298
|
-
* // theme is typed as { darkMode: boolean }
|
|
299
|
-
* }
|
|
300
|
-
* ```
|
|
301
|
-
*/
|
|
302
|
-
function createConfigHook() {
|
|
303
|
-
return function useTypedConfig(name, options) {
|
|
304
|
-
return useConfig(String(name), options);
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
349
|
//#endregion
|
|
309
350
|
exports.ReplaneProvider = ReplaneProvider;
|
|
351
|
+
Object.defineProperty(exports, 'clearSnapshotCache', {
|
|
352
|
+
enumerable: true,
|
|
353
|
+
get: function () {
|
|
354
|
+
return __replanejs_sdk.clearSnapshotCache;
|
|
355
|
+
}
|
|
356
|
+
});
|
|
310
357
|
exports.clearSuspenseCache = clearSuspenseCache;
|
|
311
358
|
exports.createConfigHook = createConfigHook;
|
|
312
359
|
exports.createReplaneHook = createReplaneHook;
|
|
360
|
+
Object.defineProperty(exports, 'getReplaneSnapshot', {
|
|
361
|
+
enumerable: true,
|
|
362
|
+
get: function () {
|
|
363
|
+
return __replanejs_sdk.getReplaneSnapshot;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
313
366
|
exports.useConfig = useConfig;
|
|
314
367
|
exports.useReplane = useReplane;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
-
import { GetConfigOptions, ReplaneClient, ReplaneClientOptions,
|
|
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
|
|
@@ -16,31 +16,31 @@ interface ReplaneProviderWithClientProps<T extends object = UntypedReplaneConfig
|
|
|
16
16
|
* Props for ReplaneProvider when letting it manage the client internally.
|
|
17
17
|
*/
|
|
18
18
|
interface ReplaneProviderWithOptionsProps<T extends object = UntypedReplaneConfig> {
|
|
19
|
-
/** Options to create the ReplaneClient */
|
|
19
|
+
/** Options to create or restore the ReplaneClient */
|
|
20
20
|
options: ReplaneClientOptions<T>;
|
|
21
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>;
|
|
22
29
|
/**
|
|
23
30
|
* Optional loading component to show while the client is initializing.
|
|
24
31
|
* If not provided and suspense is false/undefined, children will not render until ready.
|
|
32
|
+
* Ignored when snapshot is provided (restoration is synchronous).
|
|
25
33
|
*/
|
|
26
34
|
loader?: ReactNode;
|
|
27
35
|
/**
|
|
28
36
|
* If true, uses React Suspense for loading state.
|
|
29
37
|
* The provider will throw a promise that Suspense can catch.
|
|
38
|
+
* Ignored when snapshot is provided (restoration is synchronous).
|
|
30
39
|
* @default false
|
|
31
40
|
*/
|
|
32
41
|
suspense?: boolean;
|
|
33
42
|
}
|
|
34
|
-
|
|
35
|
-
* Props for ReplaneProvider when restoring from a snapshot.
|
|
36
|
-
* Uses restoreReplaneClient from the SDK for synchronous client creation.
|
|
37
|
-
*/
|
|
38
|
-
interface ReplaneProviderWithSnapshotProps<T extends object = UntypedReplaneConfig> {
|
|
39
|
-
/** Options to restore the ReplaneClient from a snapshot */
|
|
40
|
-
restoreOptions: RestoreReplaneClientOptions<T>;
|
|
41
|
-
children: ReactNode;
|
|
42
|
-
}
|
|
43
|
-
type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProviderWithClientProps<T> | ReplaneProviderWithOptionsProps<T> | ReplaneProviderWithSnapshotProps<T>;
|
|
43
|
+
type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProviderWithClientProps<T> | ReplaneProviderWithOptionsProps<T>;
|
|
44
44
|
/**
|
|
45
45
|
* Type guard to check if props contain a pre-created client.
|
|
46
46
|
*/
|
|
@@ -50,7 +50,7 @@ type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProv
|
|
|
50
50
|
/**
|
|
51
51
|
* Provider component that makes a ReplaneClient available to the component tree.
|
|
52
52
|
*
|
|
53
|
-
* Can be used in
|
|
53
|
+
* Can be used in three ways:
|
|
54
54
|
*
|
|
55
55
|
* 1. With a pre-created client:
|
|
56
56
|
* ```tsx
|
|
@@ -91,12 +91,10 @@ type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProv
|
|
|
91
91
|
* // On the server, get a snapshot from the client
|
|
92
92
|
* const snapshot = serverClient.getSnapshot();
|
|
93
93
|
*
|
|
94
|
-
* // On the client, restore from the snapshot
|
|
94
|
+
* // On the client, restore from the snapshot with live updates
|
|
95
95
|
* <ReplaneProvider
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* connection: { baseUrl: '...', sdkKey: '...' } // optional, for live updates
|
|
99
|
-
* }}
|
|
96
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
97
|
+
* snapshot={snapshot}
|
|
100
98
|
* >
|
|
101
99
|
* <App />
|
|
102
100
|
* </ReplaneProvider>
|
|
@@ -149,7 +147,14 @@ declare function createReplaneHook<TConfigs extends object>(): () => ReplaneClie
|
|
|
149
147
|
* ```
|
|
150
148
|
*/
|
|
151
149
|
declare function createConfigHook<TConfigs extends object>(): <K extends keyof TConfigs>(name: K, options?: GetConfigOptions) => TConfigs[K];
|
|
152
|
-
|
|
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
|
+
*/
|
|
153
158
|
//#endregion
|
|
154
159
|
//#region src/useReplaneClient.d.ts
|
|
155
160
|
/**
|
|
@@ -158,5 +163,5 @@ declare function createConfigHook<TConfigs extends object>(): <K extends keyof T
|
|
|
158
163
|
*/
|
|
159
164
|
declare function clearSuspenseCache<T extends object>(options?: ReplaneClientOptions<T>): void;
|
|
160
165
|
//#endregion
|
|
161
|
-
export { ReplaneProvider, type ReplaneProviderProps, type ReplaneProviderWithClientProps, type ReplaneProviderWithOptionsProps,
|
|
166
|
+
export { type GetReplaneSnapshotOptions, ReplaneProvider, type ReplaneProviderProps, type ReplaneProviderWithClientProps, type ReplaneProviderWithOptionsProps, clearSnapshotCache, clearSuspenseCache, createConfigHook, createReplaneHook, getReplaneSnapshot, useConfig, useReplane };
|
|
162
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":";;;;;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,
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
-
import { GetConfigOptions, ReplaneClient, ReplaneClientOptions,
|
|
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
|
|
@@ -16,31 +16,31 @@ interface ReplaneProviderWithClientProps<T extends object = UntypedReplaneConfig
|
|
|
16
16
|
* Props for ReplaneProvider when letting it manage the client internally.
|
|
17
17
|
*/
|
|
18
18
|
interface ReplaneProviderWithOptionsProps<T extends object = UntypedReplaneConfig> {
|
|
19
|
-
/** Options to create the ReplaneClient */
|
|
19
|
+
/** Options to create or restore the ReplaneClient */
|
|
20
20
|
options: ReplaneClientOptions<T>;
|
|
21
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>;
|
|
22
29
|
/**
|
|
23
30
|
* Optional loading component to show while the client is initializing.
|
|
24
31
|
* If not provided and suspense is false/undefined, children will not render until ready.
|
|
32
|
+
* Ignored when snapshot is provided (restoration is synchronous).
|
|
25
33
|
*/
|
|
26
34
|
loader?: ReactNode;
|
|
27
35
|
/**
|
|
28
36
|
* If true, uses React Suspense for loading state.
|
|
29
37
|
* The provider will throw a promise that Suspense can catch.
|
|
38
|
+
* Ignored when snapshot is provided (restoration is synchronous).
|
|
30
39
|
* @default false
|
|
31
40
|
*/
|
|
32
41
|
suspense?: boolean;
|
|
33
42
|
}
|
|
34
|
-
|
|
35
|
-
* Props for ReplaneProvider when restoring from a snapshot.
|
|
36
|
-
* Uses restoreReplaneClient from the SDK for synchronous client creation.
|
|
37
|
-
*/
|
|
38
|
-
interface ReplaneProviderWithSnapshotProps<T extends object = UntypedReplaneConfig> {
|
|
39
|
-
/** Options to restore the ReplaneClient from a snapshot */
|
|
40
|
-
restoreOptions: RestoreReplaneClientOptions<T>;
|
|
41
|
-
children: ReactNode;
|
|
42
|
-
}
|
|
43
|
-
type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProviderWithClientProps<T> | ReplaneProviderWithOptionsProps<T> | ReplaneProviderWithSnapshotProps<T>;
|
|
43
|
+
type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProviderWithClientProps<T> | ReplaneProviderWithOptionsProps<T>;
|
|
44
44
|
/**
|
|
45
45
|
* Type guard to check if props contain a pre-created client.
|
|
46
46
|
*/
|
|
@@ -50,7 +50,7 @@ type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProv
|
|
|
50
50
|
/**
|
|
51
51
|
* Provider component that makes a ReplaneClient available to the component tree.
|
|
52
52
|
*
|
|
53
|
-
* Can be used in
|
|
53
|
+
* Can be used in three ways:
|
|
54
54
|
*
|
|
55
55
|
* 1. With a pre-created client:
|
|
56
56
|
* ```tsx
|
|
@@ -91,12 +91,10 @@ type ReplaneProviderProps<T extends object = UntypedReplaneConfig> = ReplaneProv
|
|
|
91
91
|
* // On the server, get a snapshot from the client
|
|
92
92
|
* const snapshot = serverClient.getSnapshot();
|
|
93
93
|
*
|
|
94
|
-
* // On the client, restore from the snapshot
|
|
94
|
+
* // On the client, restore from the snapshot with live updates
|
|
95
95
|
* <ReplaneProvider
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* connection: { baseUrl: '...', sdkKey: '...' } // optional, for live updates
|
|
99
|
-
* }}
|
|
96
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
97
|
+
* snapshot={snapshot}
|
|
100
98
|
* >
|
|
101
99
|
* <App />
|
|
102
100
|
* </ReplaneProvider>
|
|
@@ -149,7 +147,14 @@ declare function createReplaneHook<TConfigs extends object>(): () => ReplaneClie
|
|
|
149
147
|
* ```
|
|
150
148
|
*/
|
|
151
149
|
declare function createConfigHook<TConfigs extends object>(): <K extends keyof TConfigs>(name: K, options?: GetConfigOptions) => TConfigs[K];
|
|
152
|
-
|
|
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
|
+
*/
|
|
153
158
|
//#endregion
|
|
154
159
|
//#region src/useReplaneClient.d.ts
|
|
155
160
|
/**
|
|
@@ -158,5 +163,5 @@ declare function createConfigHook<TConfigs extends object>(): <K extends keyof T
|
|
|
158
163
|
*/
|
|
159
164
|
declare function clearSuspenseCache<T extends object>(options?: ReplaneClientOptions<T>): void;
|
|
160
165
|
//#endregion
|
|
161
|
-
export { ReplaneProvider, type ReplaneProviderProps, type ReplaneProviderWithClientProps, type ReplaneProviderWithOptionsProps,
|
|
166
|
+
export { type GetReplaneSnapshotOptions, ReplaneProvider, type ReplaneProviderProps, type ReplaneProviderWithClientProps, type ReplaneProviderWithOptionsProps, clearSnapshotCache, clearSuspenseCache, createConfigHook, createReplaneHook, getReplaneSnapshot, useConfig, useReplane };
|
|
162
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":";;;;;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,
|
|
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, restoreReplaneClient } 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
|
|
@@ -92,6 +92,101 @@ function clearSuspenseCache(options) {
|
|
|
92
92
|
else suspenseCache.clear();
|
|
93
93
|
}
|
|
94
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
|
+
|
|
95
190
|
//#endregion
|
|
96
191
|
//#region src/types.ts
|
|
97
192
|
/**
|
|
@@ -100,12 +195,6 @@ function clearSuspenseCache(options) {
|
|
|
100
195
|
function hasClient(props) {
|
|
101
196
|
return "client" in props && props.client !== void 0;
|
|
102
197
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Type guard to check if props contain restore options.
|
|
105
|
-
*/
|
|
106
|
-
function hasRestoreOptions(props) {
|
|
107
|
-
return "restoreOptions" in props && props.restoreOptions !== void 0;
|
|
108
|
-
}
|
|
109
198
|
|
|
110
199
|
//#endregion
|
|
111
200
|
//#region src/provider.tsx
|
|
@@ -120,6 +209,30 @@ function ReplaneProviderWithClient({ client, children }) {
|
|
|
120
209
|
});
|
|
121
210
|
}
|
|
122
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
|
+
/**
|
|
123
236
|
* Internal provider component for options-based client creation (non-suspense).
|
|
124
237
|
* Throws errors during rendering so they can be caught by Error Boundaries.
|
|
125
238
|
*/
|
|
@@ -145,21 +258,9 @@ function ReplaneProviderWithSuspense({ options, children }) {
|
|
|
145
258
|
});
|
|
146
259
|
}
|
|
147
260
|
/**
|
|
148
|
-
* Internal provider component for restoring client from snapshot.
|
|
149
|
-
* Uses restoreReplaneClient which is synchronous.
|
|
150
|
-
*/
|
|
151
|
-
function ReplaneProviderWithSnapshot({ restoreOptions, children }) {
|
|
152
|
-
const client = useMemo(() => restoreReplaneClient(restoreOptions), [restoreOptions]);
|
|
153
|
-
const value = useMemo(() => ({ client }), [client]);
|
|
154
|
-
return /* @__PURE__ */ jsx(ReplaneContext.Provider, {
|
|
155
|
-
value,
|
|
156
|
-
children
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
261
|
* Provider component that makes a ReplaneClient available to the component tree.
|
|
161
262
|
*
|
|
162
|
-
* Can be used in
|
|
263
|
+
* Can be used in three ways:
|
|
163
264
|
*
|
|
164
265
|
* 1. With a pre-created client:
|
|
165
266
|
* ```tsx
|
|
@@ -200,12 +301,10 @@ function ReplaneProviderWithSnapshot({ restoreOptions, children }) {
|
|
|
200
301
|
* // On the server, get a snapshot from the client
|
|
201
302
|
* const snapshot = serverClient.getSnapshot();
|
|
202
303
|
*
|
|
203
|
-
* // On the client, restore from the snapshot
|
|
304
|
+
* // On the client, restore from the snapshot with live updates
|
|
204
305
|
* <ReplaneProvider
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* connection: { baseUrl: '...', sdkKey: '...' } // optional, for live updates
|
|
208
|
-
* }}
|
|
306
|
+
* options={{ baseUrl: '...', sdkKey: '...' }}
|
|
307
|
+
* snapshot={snapshot}
|
|
209
308
|
* >
|
|
210
309
|
* <App />
|
|
211
310
|
* </ReplaneProvider>
|
|
@@ -216,72 +315,14 @@ function ReplaneProviderWithSnapshot({ restoreOptions, children }) {
|
|
|
216
315
|
*/
|
|
217
316
|
function ReplaneProvider(props) {
|
|
218
317
|
if (hasClient(props)) return /* @__PURE__ */ jsx(ReplaneProviderWithClient, { ...props });
|
|
219
|
-
if (
|
|
318
|
+
if (props.snapshot) return /* @__PURE__ */ jsx(ReplaneProviderWithSnapshot, {
|
|
319
|
+
...props,
|
|
320
|
+
snapshot: props.snapshot
|
|
321
|
+
});
|
|
220
322
|
if (props.suspense) return /* @__PURE__ */ jsx(ReplaneProviderWithSuspense, { ...props });
|
|
221
323
|
return /* @__PURE__ */ jsx(ReplaneProviderWithOptions, { ...props });
|
|
222
324
|
}
|
|
223
325
|
|
|
224
326
|
//#endregion
|
|
225
|
-
|
|
226
|
-
function useReplane() {
|
|
227
|
-
const context = useContext(ReplaneContext);
|
|
228
|
-
if (!context) throw new Error("useReplane must be used within a ReplaneProvider");
|
|
229
|
-
return context.client;
|
|
230
|
-
}
|
|
231
|
-
function useConfig(name, options) {
|
|
232
|
-
const client = useReplane();
|
|
233
|
-
const value = useSyncExternalStore((onStoreChange) => {
|
|
234
|
-
return client.subscribe(name, onStoreChange);
|
|
235
|
-
}, () => client.get(name, options), () => client.get(name, options));
|
|
236
|
-
return value;
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Creates a typed version of useReplane hook.
|
|
240
|
-
*
|
|
241
|
-
* @example
|
|
242
|
-
* ```tsx
|
|
243
|
-
* interface AppConfigs {
|
|
244
|
-
* theme: { darkMode: boolean };
|
|
245
|
-
* features: { beta: boolean };
|
|
246
|
-
* }
|
|
247
|
-
*
|
|
248
|
-
* const useAppReplane = createReplaneHook<AppConfigs>();
|
|
249
|
-
*
|
|
250
|
-
* function MyComponent() {
|
|
251
|
-
* const replane = useAppReplane();
|
|
252
|
-
* // replane.get("theme") returns { darkMode: boolean }
|
|
253
|
-
* }
|
|
254
|
-
* ```
|
|
255
|
-
*/
|
|
256
|
-
function createReplaneHook() {
|
|
257
|
-
return function useTypedReplane() {
|
|
258
|
-
return useReplane();
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Creates a typed version of useConfig hook.
|
|
263
|
-
*
|
|
264
|
-
* @example
|
|
265
|
-
* ```tsx
|
|
266
|
-
* interface AppConfigs {
|
|
267
|
-
* theme: { darkMode: boolean };
|
|
268
|
-
* features: { beta: boolean };
|
|
269
|
-
* }
|
|
270
|
-
*
|
|
271
|
-
* const useAppConfig = createConfigHook<AppConfigs>();
|
|
272
|
-
*
|
|
273
|
-
* function MyComponent() {
|
|
274
|
-
* const theme = useAppConfig("theme");
|
|
275
|
-
* // theme is typed as { darkMode: boolean }
|
|
276
|
-
* }
|
|
277
|
-
* ```
|
|
278
|
-
*/
|
|
279
|
-
function createConfigHook() {
|
|
280
|
-
return function useTypedConfig(name, options) {
|
|
281
|
-
return useConfig(String(name), options);
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
//#endregion
|
|
286
|
-
export { ReplaneProvider, clearSuspenseCache, createConfigHook, createReplaneHook, useConfig, useReplane };
|
|
327
|
+
export { ReplaneProvider, clearSnapshotCache, clearSuspenseCache, createConfigHook, createReplaneHook, getReplaneSnapshot, useConfig, useReplane };
|
|
287
328
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["options: ReplaneClientOptions<T>","options?: ReplaneClientOptions<T>","props: ReplaneProviderProps<T>","value: ReplaneContextValue<T>","props: ReplaneProviderProps<T>","name: string","options?: GetConfigOptions","name: K"],"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\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 type {\n ReplaneClient,\n ReplaneClientOptions,\n RestoreReplaneClientOptions,\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 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\n/**\n * Props for ReplaneProvider when restoring from a snapshot.\n * Uses restoreReplaneClient from the SDK for synchronous client creation.\n */\nexport interface ReplaneProviderWithSnapshotProps<T extends object = UntypedReplaneConfig> {\n /** Options to restore the ReplaneClient from a snapshot */\n restoreOptions: RestoreReplaneClientOptions<T>;\n children: ReactNode;\n}\n\nexport type ReplaneProviderProps<T extends object = UntypedReplaneConfig> =\n | ReplaneProviderWithClientProps<T>\n | ReplaneProviderWithOptionsProps<T>\n | ReplaneProviderWithSnapshotProps<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 restore options.\n */\nexport function hasRestoreOptions<T extends object>(\n props: ReplaneProviderProps<T>\n): props is ReplaneProviderWithSnapshotProps<T> {\n return \"restoreOptions\" in props && props.restoreOptions !== undefined;\n}\n","import { useMemo } from \"react\";\nimport { restoreReplaneClient } from \"@replanejs/sdk\";\nimport { ReplaneContext } from \"./context\";\nimport { useReplaneClientInternal, useReplaneClientSuspense } from \"./useReplaneClient\";\nimport type {\n ReplaneProviderProps,\n ReplaneProviderWithClientProps,\n ReplaneProviderWithOptionsProps,\n ReplaneProviderWithSnapshotProps,\n ReplaneContextValue,\n} from \"./types\";\nimport { hasClient, hasRestoreOptions } 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 * 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 * Internal provider component for restoring client from snapshot.\n * Uses restoreReplaneClient which is synchronous.\n */\nfunction ReplaneProviderWithSnapshot<T extends object>({\n restoreOptions,\n children,\n}: ReplaneProviderWithSnapshotProps<T>) {\n const client = useMemo(() => restoreReplaneClient<T>(restoreOptions), [restoreOptions]);\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 four 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\n * <ReplaneProvider\n * restoreOptions={{\n * snapshot,\n * connection: { baseUrl: '...', sdkKey: '...' } // optional, for live updates\n * }}\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 if (hasRestoreOptions(props)) {\n return <ReplaneProviderWithSnapshot {...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 { 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 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\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"],"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.6",
|
|
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.6"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@testing-library/jest-dom": "^6.9.1",
|