@replanejs/next 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/dist/index.d.ts CHANGED
@@ -1,216 +1,56 @@
1
- import { ReactNode } from "react";
2
- import { ReplaneContextValue, useConfig, useReplane } from "@replanejs/react";
3
- import { ReplaneClient, ReplaneContext, ReplaneContext as ReplaneContext$1, ReplaneError, ReplaneSnapshot, ReplaneSnapshot as ReplaneSnapshot$1, restoreReplaneClient } from "@replanejs/sdk";
4
- import * as react_jsx_runtime0$1 from "react/jsx-runtime";
1
+ import { GetReplaneSnapshotOptions, ReplaneProvider, ReplaneProviderProps, ReplaneProviderWithClientProps, ReplaneProviderWithOptionsProps, clearSnapshotCache, clearSuspenseCache, createConfigHook, createReplaneHook, getReplaneSnapshot, useConfig, useReplane } from "@replanejs/react";
2
+ import { GetConfigOptions, ReplaneClient, ReplaneClientOptions, ReplaneClientOptions as ReplaneClientOptions$1, ReplaneContext, ReplaneError, ReplaneErrorCode, ReplaneLogger, ReplaneSnapshot, RestoreReplaneClientOptions, createInMemoryReplaneClient, createReplaneClient, restoreReplaneClient } from "@replanejs/sdk";
5
3
  import * as react_jsx_runtime0 from "react/jsx-runtime";
4
+ import { ReactNode } from "react";
5
+
6
+ //#region src/root.d.ts
6
7
 
7
- //#region src/types.d.ts
8
- /**
9
- * Connection options for real-time updates.
10
- */
11
- interface ReplaneConnectionOptions {
12
- /**
13
- * Base URL of the Replane instance (no trailing slash).
14
- * Use a NEXT_PUBLIC_ prefixed env var for client-side access.
15
- */
16
- baseUrl: string;
17
- /**
18
- * Project SDK key for authorization.
19
- * Use a NEXT_PUBLIC_ prefixed env var for client-side access.
20
- */
21
- sdkKey: string;
22
- /**
23
- * Optional timeout in ms for requests.
24
- * @default 2000
25
- */
26
- requestTimeoutMs?: number;
27
- /**
28
- * Delay between retries in ms.
29
- * @default 200
30
- */
31
- retryDelayMs?: number;
32
- /**
33
- * Timeout in ms for SSE connection inactivity.
34
- * @default 30000
35
- */
36
- inactivityTimeoutMs?: number;
37
- }
38
8
  /**
39
- * Props for ReplaneNextProvider.
9
+ * Props for ReplaneRoot server component
40
10
  */
41
- interface ReplaneNextProviderProps<T extends object = object> {
11
+ interface ReplaneRootProps<T extends object> {
42
12
  /**
43
- * Serializable snapshot from the server.
44
- * Obtained from `getReplaneSnapshot()` in a Server Component or getServerSideProps.
13
+ * Options for Replane client.
14
+ * Used for both server-side fetching and client-side live updates.
45
15
  */
46
- snapshot: ReplaneSnapshot$1<T>;
16
+ options: ReplaneClientOptions$1<T>;
47
17
  /**
48
- * Connection options for real-time updates.
49
- * If not provided, the client will only use the snapshot data (no live updates).
50
- *
51
- * For SSR apps that need real-time updates, provide connection options:
52
- * ```tsx
53
- * <ReplaneNextProvider
54
- * snapshot={snapshot}
55
- * connection={{
56
- * baseUrl: process.env.NEXT_PUBLIC_REPLANE_BASE_URL!,
57
- * sdkKey: process.env.NEXT_PUBLIC_REPLANE_SDK_KEY!,
58
- * }}
59
- * >
60
- * {children}
61
- * </ReplaneNextProvider>
62
- * ```
18
+ * React children to render inside the provider
63
19
  */
64
- connection?: ReplaneConnectionOptions;
65
- /**
66
- * Override the context from the snapshot on the client.
67
- * Useful for client-specific context like browser info.
68
- */
69
- context?: ReplaneContext$1;
70
20
  children: ReactNode;
71
21
  }
72
- //# sourceMappingURL=types.d.ts.map
73
- //#endregion
74
- //#region src/provider.d.ts
75
22
  /**
76
- * Next.js-optimized Replane provider with SSR hydration support.
77
- *
78
- * This component:
79
- * 1. Restores the Replane client from a server-side snapshot instantly (no loading state)
80
- * 2. Optionally connects to Replane for real-time updates
81
- * 3. Preserves the client across re-renders for minimal latency
23
+ * Server component that fetches Replane configs and provides them to the app.
24
+ * This is the simplest way to set up Replane in Next.js App Router.
82
25
  *
83
- * @example
26
+ * @example Basic usage in layout.tsx
84
27
  * ```tsx
85
28
  * // app/layout.tsx
86
- * import { getReplaneSnapshot } from "@replanejs/next/server";
87
- * import { ReplaneNextProvider } from "@replanejs/next";
88
- *
89
- * export default async function RootLayout({ children }) {
90
- * const snapshot = await getReplaneSnapshot({
91
- * baseUrl: process.env.REPLANE_BASE_URL!,
92
- * sdkKey: process.env.REPLANE_SDK_KEY!,
93
- * });
29
+ * import { ReplaneRoot } from "@replanejs/next";
94
30
  *
31
+ * export default function RootLayout({ children }) {
95
32
  * return (
96
33
  * <html>
97
34
  * <body>
98
- * <ReplaneNextProvider
99
- * snapshot={snapshot}
100
- * connection={{
35
+ * <ReplaneRoot
36
+ * options={{
101
37
  * baseUrl: process.env.NEXT_PUBLIC_REPLANE_BASE_URL!,
102
38
  * sdkKey: process.env.NEXT_PUBLIC_REPLANE_SDK_KEY!,
103
39
  * }}
104
40
  * >
105
41
  * {children}
106
- * </ReplaneNextProvider>
42
+ * </ReplaneRoot>
107
43
  * </body>
108
44
  * </html>
109
45
  * );
110
46
  * }
111
47
  * ```
112
- *
113
- * @example
114
- * ```tsx
115
- * // Without real-time updates (static snapshot only)
116
- * <ReplaneNextProvider snapshot={snapshot}>
117
- * {children}
118
- * </ReplaneNextProvider>
119
- * ```
120
- */
121
- declare function ReplaneNextProvider<T extends object = Record<string, unknown>>({
122
- snapshot,
123
- connection,
124
- context,
125
- children
126
- }: ReplaneNextProviderProps<T>): react_jsx_runtime0$1.JSX.Element;
127
- //# sourceMappingURL=provider.d.ts.map
128
- //#endregion
129
- //#region src/script.d.ts
130
- declare const REPLANE_SNAPSHOT_KEY = "__REPLANE_SNAPSHOT__";
131
- type AnyConfig = Record<string, unknown>;
132
- declare global {
133
- interface Window {
134
- [REPLANE_SNAPSHOT_KEY]?: ReplaneSnapshot$1<AnyConfig>;
135
- }
136
- }
137
- /**
138
- * Generate the script content for embedding the snapshot.
139
- *
140
- * Use this in a Server Component to embed the snapshot in the page:
141
- *
142
- * @example
143
- * ```tsx
144
- * // app/layout.tsx
145
- * import { getReplaneSnapshot } from "@replanejs/next/server";
146
- * import { getReplaneSnapshotScript, ReplaneScriptProvider } from "@replanejs/next";
147
- *
148
- * export default async function RootLayout({ children }) {
149
- * const snapshot = await getReplaneSnapshot({ ... });
150
- *
151
- * return (
152
- * <html>
153
- * <head>
154
- * <script
155
- * dangerouslySetInnerHTML={{
156
- * __html: getReplaneSnapshotScript(snapshot),
157
- * }}
158
- * />
159
- * </head>
160
- * <body>
161
- * <ReplaneScriptProvider connection={{ ... }}>
162
- * {children}
163
- * </ReplaneScriptProvider>
164
- * </body>
165
- * </html>
166
- * );
167
- * }
168
- * ```
169
- */
170
- declare function getReplaneSnapshotScript<T extends object = AnyConfig>(snapshot: ReplaneSnapshot$1<T>): string;
171
- /**
172
- * Props for ReplaneScriptProvider.
173
- */
174
- interface ReplaneScriptProviderProps {
175
- /**
176
- * Connection options for real-time updates.
177
- * If not provided, the client will only use the snapshot data (no live updates).
178
- */
179
- connection?: ReplaneConnectionOptions;
180
- /**
181
- * Fallback to render while waiting for the snapshot.
182
- * This should rarely be needed since the script is in the head.
183
- */
184
- fallback?: ReactNode;
185
- children: ReactNode;
186
- }
187
- /**
188
- * Provider that reads the snapshot from a script tag.
189
- *
190
- * Use this with `getReplaneSnapshotScript()` for an alternative hydration pattern
191
- * where the snapshot is embedded in a script tag instead of passed as a prop.
192
- *
193
- * This pattern can be useful for:
194
- * - Pages with heavy component trees where prop drilling is inconvenient
195
- * - Partial hydration scenarios
196
- * - When you want the snapshot to be available before React hydrates
197
- *
198
- * @example
199
- * ```tsx
200
- * // In app/layout.tsx (Server Component)
201
- * <script dangerouslySetInnerHTML={{ __html: getReplaneSnapshotScript(snapshot) }} />
202
- *
203
- * // In a client component
204
- * <ReplaneScriptProvider connection={{ baseUrl, sdkKey }}>
205
- * <App />
206
- * </ReplaneScriptProvider>
207
- * ```
208
48
  */
209
- declare function ReplaneScriptProvider({
210
- connection,
211
- fallback,
49
+ declare function ReplaneRoot<T extends object>({
50
+ options,
212
51
  children
213
- }: ReplaneScriptProviderProps): react_jsx_runtime0.JSX.Element;
52
+ }: ReplaneRootProps<T>): Promise<react_jsx_runtime0.JSX.Element>;
53
+ //# sourceMappingURL=root.d.ts.map
214
54
  //#endregion
215
- export { type ReplaneClient, type ReplaneConnectionOptions, type ReplaneContext, type ReplaneContextValue, type ReplaneError, ReplaneNextProvider, type ReplaneNextProviderProps, ReplaneScriptProvider, type ReplaneScriptProviderProps, type ReplaneSnapshot, getReplaneSnapshotScript, restoreReplaneClient, useConfig, useReplane };
55
+ export { type GetConfigOptions, type GetReplaneSnapshotOptions, type ReplaneClient, type ReplaneClientOptions, type ReplaneContext, ReplaneError, ReplaneErrorCode, type ReplaneLogger, ReplaneProvider, type ReplaneProviderProps, type ReplaneProviderWithClientProps, type ReplaneProviderWithOptionsProps, ReplaneRoot, type ReplaneRootProps, type ReplaneSnapshot, type RestoreReplaneClientOptions, clearSnapshotCache, clearSuspenseCache, createConfigHook, createInMemoryReplaneClient, createReplaneClient, createReplaneHook, getReplaneSnapshot, restoreReplaneClient, useConfig, useReplane };
216
56
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/provider.tsx","../src/script.tsx"],"sourcesContent":[],"mappings":";;;;;;;;;;UAMiB,wBAAA;;AAAjB;AAmCA;;EAAyC,OAKb,EAAA,MAAA;EAAC;;;;EA2BR,MAAA,EAAA,MAAA;;;;ACpBrB;EAAmC,gBAAA,CAAA,EAAA,MAAA;EAAA;;;;EAG1B,YACP,CAAA,EAAA,MAAA;EAAQ;;;AACoB;;;;ACrD0B;AAG9B;AAEH,UF+BN,wBE/BM,CAAA,UAAA,MAAA,GAAA,MAAA,CAAA,CAAA;EAAA;;;;EAIE,QAAA,EFgCb,iBEhCa,CFgCG,CEhCH,CAAA;EAAA;AAqCzB;;;;;AAC2B;AAU3B;;;;;AAaqB;AAyBrB;;;;EAEU,UACR,CAAA,EFtCa,wBEsCb;EAAQ;;AACmB;;YFjCjB;YAEA;;;;;;;;;;;AAnEZ;AAmCA;;;;;;;AAgCqB;;;;ACpBrB;;;;;;;;;;AAK8B;;;;ACrD0B;AAG9B;AAEH;;;;;;AAIE;AAqCzB;;;;;AAC2B,iBDCX,mBCDW,CAAA,UAAA,MAAA,GDC4B,MCD5B,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA;EAAA,QAAA;EAAA,UAAA;EAAA,OAAA;EAAA;AAAA,CAAA,EDMxB,wBCNwB,CDMC,CCND,CAAA,CAAA,EDMG,oBAAA,CAAA,GAAA,CAAA,OCNH;AAU3B;;;cAtDM,oBAAA;KAED,SAAA,GAAY;;;IFJA,CEQZ,oBAAA,EFRoC,EEQZ,iBFRY,CEQI,SFRJ,CAAA;EAmCxB;;;;;;;AAgCI;;;;ACpBrB;;;;;;;;;;AAK8B;;;;ACrD0B;AAG9B;AAEH;;;;;;AAIE;AAqCzB;AAAwC,iBAAxB,wBAAwB,CAAA,UAAA,MAAA,GAAoB,SAApB,CAAA,CAAA,QAAA,EAC5B,iBAD4B,CACZ,CADY,CAAA,CAAA,EAAA,MAAA;;;;AACb,UAUV,0BAAA,CAVU;EAUV;;;;EAWK,UAEV,CAAA,EARG,wBAQH;EAAS;AAyBrB;;;EACY,QACV,CAAA,EA7BW,SA6BX;EAAQ,QACR,EA5BU,SA4BV;;;AAC2B;;;;;;;;;;;;;;;;;;;;;iBAJb,qBAAA;;;;GAIb,6BAA0B,kBAAA,CAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/root.tsx"],"sourcesContent":[],"mappings":";;;;;;;AAaA;;;AAKW,UALM,gBAKN,CAAA,UAAA,MAAA,CAAA,CAAA;EAAoB;AAIV;AA8BrB;;EAAiC,OAAqB,EAlC3C,sBAkC2C,CAlCtB,CAkCsB,CAAA;EAAO;;;EAA8B,QAAG,EA9BlF,SA8BkF;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAxE;;;GAAqD,iBAAiB,KAAE,QAAA,kBAAA,CAAA,GAAA,CAAA,OAAA"}
package/dist/index.js CHANGED
@@ -1,198 +1,44 @@
1
- import { useEffect, useMemo, useRef, useState } from "react";
2
- import { ReplaneProvider, useConfig, useReplane } from "@replanejs/react";
3
- import { restoreReplaneClient, restoreReplaneClient as restoreReplaneClient$1 } from "@replanejs/sdk";
4
- import { Fragment, jsx } from "react/jsx-runtime";
1
+ import { ReplaneProvider, ReplaneProvider as ReplaneProvider$1, clearSnapshotCache, clearSuspenseCache, createConfigHook, createReplaneHook, getReplaneSnapshot, getReplaneSnapshot as getReplaneSnapshot$1, useConfig, useReplane } from "@replanejs/react";
2
+ import { ReplaneError, ReplaneErrorCode, createInMemoryReplaneClient, createReplaneClient, restoreReplaneClient } from "@replanejs/sdk";
3
+ import { jsx } from "react/jsx-runtime";
5
4
 
6
- //#region src/provider.tsx
5
+ //#region src/root.tsx
7
6
  /**
8
- * Next.js-optimized Replane provider with SSR hydration support.
7
+ * Server component that fetches Replane configs and provides them to the app.
8
+ * This is the simplest way to set up Replane in Next.js App Router.
9
9
  *
10
- * This component:
11
- * 1. Restores the Replane client from a server-side snapshot instantly (no loading state)
12
- * 2. Optionally connects to Replane for real-time updates
13
- * 3. Preserves the client across re-renders for minimal latency
14
- *
15
- * @example
10
+ * @example Basic usage in layout.tsx
16
11
  * ```tsx
17
12
  * // app/layout.tsx
18
- * import { getReplaneSnapshot } from "@replanejs/next/server";
19
- * import { ReplaneNextProvider } from "@replanejs/next";
20
- *
21
- * export default async function RootLayout({ children }) {
22
- * const snapshot = await getReplaneSnapshot({
23
- * baseUrl: process.env.REPLANE_BASE_URL!,
24
- * sdkKey: process.env.REPLANE_SDK_KEY!,
25
- * });
13
+ * import { ReplaneRoot } from "@replanejs/next";
26
14
  *
15
+ * export default function RootLayout({ children }) {
27
16
  * return (
28
17
  * <html>
29
18
  * <body>
30
- * <ReplaneNextProvider
31
- * snapshot={snapshot}
32
- * connection={{
19
+ * <ReplaneRoot
20
+ * options={{
33
21
  * baseUrl: process.env.NEXT_PUBLIC_REPLANE_BASE_URL!,
34
22
  * sdkKey: process.env.NEXT_PUBLIC_REPLANE_SDK_KEY!,
35
23
  * }}
36
24
  * >
37
25
  * {children}
38
- * </ReplaneNextProvider>
26
+ * </ReplaneRoot>
39
27
  * </body>
40
28
  * </html>
41
29
  * );
42
30
  * }
43
31
  * ```
44
- *
45
- * @example
46
- * ```tsx
47
- * // Without real-time updates (static snapshot only)
48
- * <ReplaneNextProvider snapshot={snapshot}>
49
- * {children}
50
- * </ReplaneNextProvider>
51
- * ```
52
32
  */
53
- function ReplaneNextProvider({ snapshot, connection, context, children }) {
54
- const clientRef = useRef(null);
55
- const clientKey = useMemo(() => {
56
- const snapshotKey = JSON.stringify(snapshot.configs.map((c) => c.name).sort());
57
- const connectionKey = connection ? `${connection.baseUrl}:${connection.sdkKey}` : "no-connection";
58
- const contextKey = context ? JSON.stringify(context) : "no-context";
59
- return `${snapshotKey}:${connectionKey}:${contextKey}`;
60
- }, [
33
+ async function ReplaneRoot({ options, children }) {
34
+ const snapshot = await getReplaneSnapshot$1(options);
35
+ return /* @__PURE__ */ jsx(ReplaneProvider$1, {
36
+ options,
61
37
  snapshot,
62
- connection,
63
- context
64
- ]);
65
- const client = useMemo(() => {
66
- if (clientRef.current) {}
67
- const newClient = restoreReplaneClient$1({
68
- snapshot,
69
- connection: connection ? {
70
- baseUrl: connection.baseUrl,
71
- sdkKey: connection.sdkKey,
72
- requestTimeoutMs: connection.requestTimeoutMs,
73
- retryDelayMs: connection.retryDelayMs,
74
- inactivityTimeoutMs: connection.inactivityTimeoutMs
75
- } : void 0,
76
- context
77
- });
78
- clientRef.current = newClient;
79
- return newClient;
80
- }, [clientKey]);
81
- useEffect(() => {
82
- return () => {
83
- if (clientRef.current) {
84
- clientRef.current.close();
85
- clientRef.current = null;
86
- }
87
- };
88
- }, []);
89
- return /* @__PURE__ */ jsx(ReplaneProvider, {
90
- client,
91
- children
92
- });
93
- }
94
-
95
- //#endregion
96
- //#region src/script.tsx
97
- const REPLANE_SNAPSHOT_KEY = "__REPLANE_SNAPSHOT__";
98
- /**
99
- * Generate the script content for embedding the snapshot.
100
- *
101
- * Use this in a Server Component to embed the snapshot in the page:
102
- *
103
- * @example
104
- * ```tsx
105
- * // app/layout.tsx
106
- * import { getReplaneSnapshot } from "@replanejs/next/server";
107
- * import { getReplaneSnapshotScript, ReplaneScriptProvider } from "@replanejs/next";
108
- *
109
- * export default async function RootLayout({ children }) {
110
- * const snapshot = await getReplaneSnapshot({ ... });
111
- *
112
- * return (
113
- * <html>
114
- * <head>
115
- * <script
116
- * dangerouslySetInnerHTML={{
117
- * __html: getReplaneSnapshotScript(snapshot),
118
- * }}
119
- * />
120
- * </head>
121
- * <body>
122
- * <ReplaneScriptProvider connection={{ ... }}>
123
- * {children}
124
- * </ReplaneScriptProvider>
125
- * </body>
126
- * </html>
127
- * );
128
- * }
129
- * ```
130
- */
131
- function getReplaneSnapshotScript(snapshot) {
132
- const json = JSON.stringify(snapshot).replace(/<\/script>/gi, "<\\/script>");
133
- return `window.${REPLANE_SNAPSHOT_KEY}=${json};`;
134
- }
135
- /**
136
- * Provider that reads the snapshot from a script tag.
137
- *
138
- * Use this with `getReplaneSnapshotScript()` for an alternative hydration pattern
139
- * where the snapshot is embedded in a script tag instead of passed as a prop.
140
- *
141
- * This pattern can be useful for:
142
- * - Pages with heavy component trees where prop drilling is inconvenient
143
- * - Partial hydration scenarios
144
- * - When you want the snapshot to be available before React hydrates
145
- *
146
- * @example
147
- * ```tsx
148
- * // In app/layout.tsx (Server Component)
149
- * <script dangerouslySetInnerHTML={{ __html: getReplaneSnapshotScript(snapshot) }} />
150
- *
151
- * // In a client component
152
- * <ReplaneScriptProvider connection={{ baseUrl, sdkKey }}>
153
- * <App />
154
- * </ReplaneScriptProvider>
155
- * ```
156
- */
157
- function ReplaneScriptProvider({ connection, fallback, children }) {
158
- const [snapshot, setSnapshot] = useState(() => {
159
- if (typeof window !== "undefined" && window[REPLANE_SNAPSHOT_KEY]) return window[REPLANE_SNAPSHOT_KEY];
160
- return null;
161
- });
162
- const clientRef = useRef(null);
163
- useEffect(() => {
164
- if (!snapshot && typeof window !== "undefined" && window[REPLANE_SNAPSHOT_KEY]) setSnapshot(window[REPLANE_SNAPSHOT_KEY]);
165
- }, [snapshot]);
166
- const client = useMemo(() => {
167
- if (!snapshot) return null;
168
- const newClient = restoreReplaneClient$1({
169
- snapshot,
170
- connection: connection ? {
171
- baseUrl: connection.baseUrl,
172
- sdkKey: connection.sdkKey,
173
- requestTimeoutMs: connection.requestTimeoutMs,
174
- retryDelayMs: connection.retryDelayMs,
175
- inactivityTimeoutMs: connection.inactivityTimeoutMs
176
- } : void 0
177
- });
178
- clientRef.current = newClient;
179
- return newClient;
180
- }, [snapshot, connection]);
181
- useEffect(() => {
182
- return () => {
183
- if (clientRef.current) {
184
- clientRef.current.close();
185
- clientRef.current = null;
186
- }
187
- };
188
- }, []);
189
- if (!client) return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? null });
190
- return /* @__PURE__ */ jsx(ReplaneProvider, {
191
- client,
192
38
  children
193
39
  });
194
40
  }
195
41
 
196
42
  //#endregion
197
- export { ReplaneNextProvider, ReplaneScriptProvider, getReplaneSnapshotScript, restoreReplaneClient, useConfig, useReplane };
43
+ export { ReplaneError, ReplaneErrorCode, ReplaneProvider, ReplaneRoot, clearSnapshotCache, clearSuspenseCache, createConfigHook, createInMemoryReplaneClient, createReplaneClient, createReplaneHook, getReplaneSnapshot, restoreReplaneClient, useConfig, useReplane };
198
44
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["snapshot: ReplaneSnapshot<T>"],"sources":["../src/provider.tsx","../src/script.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useRef, useEffect } from \"react\";\nimport { ReplaneProvider } from \"@replanejs/react\";\nimport { restoreReplaneClient, type ReplaneClient } from \"@replanejs/sdk\";\nimport type { ReplaneNextProviderProps } from \"./types\";\n\n/**\n * Next.js-optimized Replane provider with SSR hydration support.\n *\n * This component:\n * 1. Restores the Replane client from a server-side snapshot instantly (no loading state)\n * 2. Optionally connects to Replane for real-time updates\n * 3. Preserves the client across re-renders for minimal latency\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { getReplaneSnapshot } from \"@replanejs/next/server\";\n * import { ReplaneNextProvider } from \"@replanejs/next\";\n *\n * export default async function RootLayout({ children }) {\n * const snapshot = await getReplaneSnapshot({\n * baseUrl: process.env.REPLANE_BASE_URL!,\n * sdkKey: process.env.REPLANE_SDK_KEY!,\n * });\n *\n * return (\n * <html>\n * <body>\n * <ReplaneNextProvider\n * snapshot={snapshot}\n * connection={{\n * baseUrl: process.env.NEXT_PUBLIC_REPLANE_BASE_URL!,\n * sdkKey: process.env.NEXT_PUBLIC_REPLANE_SDK_KEY!,\n * }}\n * >\n * {children}\n * </ReplaneNextProvider>\n * </body>\n * </html>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Without real-time updates (static snapshot only)\n * <ReplaneNextProvider snapshot={snapshot}>\n * {children}\n * </ReplaneNextProvider>\n * ```\n */\nexport function ReplaneNextProvider<T extends object = Record<string, unknown>>({\n snapshot,\n connection,\n context,\n children,\n}: ReplaneNextProviderProps<T>) {\n // Use a ref to store the client to preserve it across re-renders\n // This is important for minimizing latency - we don't want to recreate\n // the client on every render\n const clientRef = useRef<ReplaneClient<T> | null>(null);\n\n // Create a stable key for the client based on snapshot and connection\n // We only recreate the client if these change\n const clientKey = useMemo(() => {\n const snapshotKey = JSON.stringify(snapshot.configs.map((c) => c.name).sort());\n const connectionKey = connection ? `${connection.baseUrl}:${connection.sdkKey}` : \"no-connection\";\n const contextKey = context ? JSON.stringify(context) : \"no-context\";\n return `${snapshotKey}:${connectionKey}:${contextKey}`;\n }, [snapshot, connection, context]);\n\n // Memoize client creation\n const client = useMemo(() => {\n // If we have a cached client with the same key, reuse it\n if (clientRef.current) {\n // Check if we need to create a new client\n // For simplicity, we always create a new client if the key changes\n // This happens when snapshot or connection changes\n }\n\n const newClient = restoreReplaneClient<T>({\n snapshot,\n connection: connection\n ? {\n baseUrl: connection.baseUrl,\n sdkKey: connection.sdkKey,\n requestTimeoutMs: connection.requestTimeoutMs,\n retryDelayMs: connection.retryDelayMs,\n inactivityTimeoutMs: connection.inactivityTimeoutMs,\n }\n : undefined,\n context,\n });\n\n clientRef.current = newClient;\n return newClient;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [clientKey]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (clientRef.current) {\n clientRef.current.close();\n clientRef.current = null;\n }\n };\n }, []);\n\n return <ReplaneProvider client={client}>{children}</ReplaneProvider>;\n}\n","\"use client\";\n\nimport { useEffect, useState, useMemo, useRef, type ReactNode } from \"react\";\nimport { ReplaneProvider } from \"@replanejs/react\";\nimport { restoreReplaneClient, type ReplaneSnapshot, type ReplaneClient } from \"@replanejs/sdk\";\nimport type { ReplaneConnectionOptions } from \"./types\";\n\n// Global variable name for the snapshot\nconst REPLANE_SNAPSHOT_KEY = \"__REPLANE_SNAPSHOT__\";\n\ntype AnyConfig = Record<string, unknown>;\n\ndeclare global {\n interface Window {\n [REPLANE_SNAPSHOT_KEY]?: ReplaneSnapshot<AnyConfig>;\n }\n}\n\n/**\n * Generate the script content for embedding the snapshot.\n *\n * Use this in a Server Component to embed the snapshot in the page:\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { getReplaneSnapshot } from \"@replanejs/next/server\";\n * import { getReplaneSnapshotScript, ReplaneScriptProvider } from \"@replanejs/next\";\n *\n * export default async function RootLayout({ children }) {\n * const snapshot = await getReplaneSnapshot({ ... });\n *\n * return (\n * <html>\n * <head>\n * <script\n * dangerouslySetInnerHTML={{\n * __html: getReplaneSnapshotScript(snapshot),\n * }}\n * />\n * </head>\n * <body>\n * <ReplaneScriptProvider connection={{ ... }}>\n * {children}\n * </ReplaneScriptProvider>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\nexport function getReplaneSnapshotScript<T extends object = AnyConfig>(\n snapshot: ReplaneSnapshot<T>\n): string {\n // Escape script closing tags in JSON to prevent XSS\n const json = JSON.stringify(snapshot).replace(/<\\/script>/gi, \"<\\\\/script>\");\n return `window.${REPLANE_SNAPSHOT_KEY}=${json};`;\n}\n\n/**\n * Props for ReplaneScriptProvider.\n */\nexport interface ReplaneScriptProviderProps {\n /**\n * Connection options for real-time updates.\n * If not provided, the client will only use the snapshot data (no live updates).\n */\n connection?: ReplaneConnectionOptions;\n\n /**\n * Fallback to render while waiting for the snapshot.\n * This should rarely be needed since the script is in the head.\n */\n fallback?: ReactNode;\n\n children: ReactNode;\n}\n\n/**\n * Provider that reads the snapshot from a script tag.\n *\n * Use this with `getReplaneSnapshotScript()` for an alternative hydration pattern\n * where the snapshot is embedded in a script tag instead of passed as a prop.\n *\n * This pattern can be useful for:\n * - Pages with heavy component trees where prop drilling is inconvenient\n * - Partial hydration scenarios\n * - When you want the snapshot to be available before React hydrates\n *\n * @example\n * ```tsx\n * // In app/layout.tsx (Server Component)\n * <script dangerouslySetInnerHTML={{ __html: getReplaneSnapshotScript(snapshot) }} />\n *\n * // In a client component\n * <ReplaneScriptProvider connection={{ baseUrl, sdkKey }}>\n * <App />\n * </ReplaneScriptProvider>\n * ```\n */\nexport function ReplaneScriptProvider({\n connection,\n fallback,\n children,\n}: ReplaneScriptProviderProps) {\n const [snapshot, setSnapshot] = useState<ReplaneSnapshot<AnyConfig> | null>(() => {\n // Try to get snapshot from window on initial render (SSR-safe)\n if (typeof window !== \"undefined\" && window[REPLANE_SNAPSHOT_KEY]) {\n return window[REPLANE_SNAPSHOT_KEY];\n }\n return null;\n });\n\n const clientRef = useRef<ReplaneClient<AnyConfig> | null>(null);\n\n // Check for snapshot on mount (in case the script runs after initial render)\n useEffect(() => {\n if (!snapshot && typeof window !== \"undefined\" && window[REPLANE_SNAPSHOT_KEY]) {\n setSnapshot(window[REPLANE_SNAPSHOT_KEY]);\n }\n }, [snapshot]);\n\n // Create client from snapshot\n const client = useMemo(() => {\n if (!snapshot) return null;\n\n const newClient = restoreReplaneClient({\n snapshot,\n connection: connection\n ? {\n baseUrl: connection.baseUrl,\n sdkKey: connection.sdkKey,\n requestTimeoutMs: connection.requestTimeoutMs,\n retryDelayMs: connection.retryDelayMs,\n inactivityTimeoutMs: connection.inactivityTimeoutMs,\n }\n : undefined,\n });\n\n clientRef.current = newClient;\n return newClient;\n }, [snapshot, connection]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (clientRef.current) {\n clientRef.current.close();\n clientRef.current = null;\n }\n };\n }, []);\n\n if (!client) {\n return <>{fallback ?? null}</>;\n }\n\n return <ReplaneProvider client={client}>{children}</ReplaneProvider>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAgB,oBAAgE,EAC9E,UACA,YACA,SACA,UAC4B,EAAE;CAI9B,MAAM,YAAY,OAAgC,KAAK;CAIvD,MAAM,YAAY,QAAQ,MAAM;EAC9B,MAAM,cAAc,KAAK,UAAU,SAAS,QAAQ,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;EAC9E,MAAM,gBAAgB,cAAc,EAAE,WAAW,QAAQ,GAAG,WAAW,OAAO,IAAI;EAClF,MAAM,aAAa,UAAU,KAAK,UAAU,QAAQ,GAAG;AACvD,UAAQ,EAAE,YAAY,GAAG,cAAc,GAAG,WAAW;CACtD,GAAE;EAAC;EAAU;EAAY;CAAQ,EAAC;CAGnC,MAAM,SAAS,QAAQ,MAAM;AAE3B,MAAI,UAAU,SAAS,CAItB;EAED,MAAM,YAAY,uBAAwB;GACxC;GACA,YAAY,aACR;IACE,SAAS,WAAW;IACpB,QAAQ,WAAW;IACnB,kBAAkB,WAAW;IAC7B,cAAc,WAAW;IACzB,qBAAqB,WAAW;GACjC;GAEL;EACD,EAAC;AAEF,YAAU,UAAU;AACpB,SAAO;CAER,GAAE,CAAC,SAAU,EAAC;AAGf,WAAU,MAAM;AACd,SAAO,MAAM;AACX,OAAI,UAAU,SAAS;AACrB,cAAU,QAAQ,OAAO;AACzB,cAAU,UAAU;GACrB;EACF;CACF,GAAE,CAAE,EAAC;AAEN,wBAAO,IAAC;EAAwB;EAAS;GAA2B;AACrE;;;;ACxGD,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2C7B,SAAgB,yBACdA,UACQ;CAER,MAAM,OAAO,KAAK,UAAU,SAAS,CAAC,QAAQ,gBAAgB,cAAc;AAC5E,SAAQ,SAAS,qBAAqB,GAAG,KAAK;AAC/C;;;;;;;;;;;;;;;;;;;;;;;AA2CD,SAAgB,sBAAsB,EACpC,YACA,UACA,UAC2B,EAAE;CAC7B,MAAM,CAAC,UAAU,YAAY,GAAG,SAA4C,MAAM;AAEhF,aAAW,WAAW,eAAe,OAAO,sBAC1C,QAAO,OAAO;AAEhB,SAAO;CACR,EAAC;CAEF,MAAM,YAAY,OAAwC,KAAK;AAG/D,WAAU,MAAM;AACd,OAAK,mBAAmB,WAAW,eAAe,OAAO,sBACvD,aAAY,OAAO,sBAAsB;CAE5C,GAAE,CAAC,QAAS,EAAC;CAGd,MAAM,SAAS,QAAQ,MAAM;AAC3B,OAAK,SAAU,QAAO;EAEtB,MAAM,YAAY,uBAAqB;GACrC;GACA,YAAY,aACR;IACE,SAAS,WAAW;IACpB,QAAQ,WAAW;IACnB,kBAAkB,WAAW;IAC7B,cAAc,WAAW;IACzB,qBAAqB,WAAW;GACjC;EAEN,EAAC;AAEF,YAAU,UAAU;AACpB,SAAO;CACR,GAAE,CAAC,UAAU,UAAW,EAAC;AAG1B,WAAU,MAAM;AACd,SAAO,MAAM;AACX,OAAI,UAAU,SAAS;AACrB,cAAU,QAAQ,OAAO;AACzB,cAAU,UAAU;GACrB;EACF;CACF,GAAE,CAAE,EAAC;AAEN,MAAK,OACH,wBAAO,0BAAG,YAAY,OAAQ;AAGhC,wBAAO,IAAC;EAAwB;EAAS;GAA2B;AACrE"}
1
+ {"version":3,"file":"index.js","names":["ReplaneProvider"],"sources":["../src/root.tsx"],"sourcesContent":["/**\n * Server-side utilities for Next.js\n * Use this module in Server Components, getServerSideProps, or getStaticProps\n */\n\nimport type { ReactNode } from \"react\";\nimport { getReplaneSnapshot } from \"@replanejs/react\";\nimport { type ReplaneClientOptions } from \"@replanejs/sdk\";\nimport { ReplaneProvider } from \"@replanejs/react\";\n\n/**\n * Props for ReplaneRoot server component\n */\nexport interface ReplaneRootProps<T extends object> {\n /**\n * Options for Replane client.\n * Used for both server-side fetching and client-side live updates.\n */\n options: ReplaneClientOptions<T>;\n /**\n * React children to render inside the provider\n */\n children: ReactNode;\n}\n\n/**\n * Server component that fetches Replane configs and provides them to the app.\n * This is the simplest way to set up Replane in Next.js App Router.\n *\n * @example Basic usage in layout.tsx\n * ```tsx\n * // app/layout.tsx\n * import { ReplaneRoot } from \"@replanejs/next\";\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <body>\n * <ReplaneRoot\n * options={{\n * baseUrl: process.env.NEXT_PUBLIC_REPLANE_BASE_URL!,\n * sdkKey: process.env.NEXT_PUBLIC_REPLANE_SDK_KEY!,\n * }}\n * >\n * {children}\n * </ReplaneRoot>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\nexport async function ReplaneRoot<T extends object>({ options, children }: ReplaneRootProps<T>) {\n const snapshot = await getReplaneSnapshot(options);\n\n return (\n <ReplaneProvider options={options} snapshot={snapshot}>\n {children}\n </ReplaneProvider>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,eAAsB,YAA8B,EAAE,SAAS,UAA+B,EAAE;CAC9F,MAAM,WAAW,MAAM,qBAAmB,QAAQ;AAElD,wBACE,IAACA;EAAyB;EAAmB;EAC1C;GACe;AAErB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replanejs/next",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Next.js SDK for Replane - feature flags and remote configuration with SSR support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -11,11 +11,6 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
- },
15
- "./server": {
16
- "types": "./dist/server.d.ts",
17
- "import": "./dist/server.js",
18
- "require": "./dist/server.cjs"
19
14
  }
20
15
  },
21
16
  "files": [
@@ -26,13 +21,13 @@
26
21
  "replane",
27
22
  "next",
28
23
  "nextjs",
29
- "react",
30
24
  "feature-flags",
31
25
  "remote-config",
32
26
  "configuration",
33
27
  "sdk",
34
28
  "ssr",
35
- "server-side-rendering"
29
+ "app-router",
30
+ "pages-router"
36
31
  ],
37
32
  "author": "",
38
33
  "license": "MIT",
@@ -50,16 +45,13 @@
50
45
  "react": ">=18.0.0"
51
46
  },
52
47
  "dependencies": {
53
- "@replanejs/react": "^0.7.3",
54
- "@replanejs/sdk": "^0.7.3"
48
+ "@replanejs/react": "^0.7.5",
49
+ "@replanejs/sdk": "^0.7.5"
55
50
  },
56
51
  "devDependencies": {
57
- "@testing-library/jest-dom": "^6.9.1",
58
- "@testing-library/react": "^16.3.1",
59
52
  "@types/node": "^22.19.3",
60
53
  "@types/react": "^18.2.0",
61
54
  "@types/react-dom": "^18.3.7",
62
- "jsdom": "^27.3.0",
63
55
  "next": "^15.0.0",
64
56
  "react": "^18.2.0",
65
57
  "react-dom": "^18.3.1",
@@ -77,7 +69,7 @@
77
69
  "build": "tsdown",
78
70
  "dev": "tsdown --watch",
79
71
  "typecheck": "tsc --noEmit",
80
- "test": "vitest run",
81
- "test:watch": "vitest"
72
+ "test": "echo 'No tests yet'",
73
+ "test:watch": "echo 'No tests yet'"
82
74
  }
83
75
  }
@@ -1,30 +0,0 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
-
23
- //#endregion
24
-
25
- Object.defineProperty(exports, '__toESM', {
26
- enumerable: true,
27
- get: function () {
28
- return __toESM;
29
- }
30
- });