hadars 0.1.28 → 0.1.30

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/cli.js CHANGED
@@ -141,7 +141,6 @@ var FRAGMENT_TYPE = Symbol.for("react.fragment");
141
141
  var SUSPENSE_TYPE = Symbol.for("react.suspense");
142
142
 
143
143
  // src/slim-react/jsx.ts
144
- var Fragment = FRAGMENT_TYPE;
145
144
  function createElement(type, props, ...children) {
146
145
  const normalizedProps = { ...props || {} };
147
146
  if (children.length === 1) {
@@ -198,10 +197,11 @@ function s() {
198
197
  }
199
198
  return g[GLOBAL_KEY];
200
199
  }
201
- function resetRenderState() {
200
+ function resetRenderState(idPrefix = "") {
202
201
  const st = s();
203
202
  st.currentTreeContext = { ...EMPTY };
204
203
  st.localIdCounter = 0;
204
+ st.idPrefix = idPrefix;
205
205
  }
206
206
  function pushTreeContext(totalChildren, index) {
207
207
  const st = s();
@@ -242,6 +242,9 @@ function pushComponentScope() {
242
242
  function popComponentScope(saved) {
243
243
  s().localIdCounter = saved;
244
244
  }
245
+ function componentCalledUseId() {
246
+ return s().localIdCounter > 0;
247
+ }
245
248
  function snapshotContext() {
246
249
  const st = s();
247
250
  return { tree: { ...st.currentTreeContext }, localId: st.localIdCounter };
@@ -251,6 +254,121 @@ function restoreContext(snap) {
251
254
  st.currentTreeContext = { ...snap.tree };
252
255
  st.localIdCounter = snap.localId;
253
256
  }
257
+ function getTreeId() {
258
+ const { id, overflow } = s().currentTreeContext;
259
+ if (id === 1)
260
+ return overflow;
261
+ const stripped = (id & ~(1 << 31 - Math.clz32(id))).toString(32);
262
+ return stripped + overflow;
263
+ }
264
+ function makeId() {
265
+ const st = s();
266
+ const treeId = getTreeId();
267
+ const n = st.localIdCounter++;
268
+ let id = "_R_" + st.idPrefix + treeId;
269
+ if (n > 0)
270
+ id += "H" + n.toString(32);
271
+ return id + "_";
272
+ }
273
+
274
+ // src/slim-react/hooks.ts
275
+ function useState(initialState) {
276
+ const value = typeof initialState === "function" ? initialState() : initialState;
277
+ return [value, () => {
278
+ }];
279
+ }
280
+ function useReducer(_reducer, initialState) {
281
+ return [initialState, () => {
282
+ }];
283
+ }
284
+ function useEffect(_effect, _deps) {
285
+ }
286
+ function useLayoutEffect(_effect, _deps) {
287
+ }
288
+ function useInsertionEffect(_effect, _deps) {
289
+ }
290
+ function useRef(initialValue) {
291
+ return { current: initialValue };
292
+ }
293
+ function useMemo(factory, _deps) {
294
+ return factory();
295
+ }
296
+ function useCallback(callback, _deps) {
297
+ return callback;
298
+ }
299
+ function useDebugValue(_value, _format) {
300
+ }
301
+ function useImperativeHandle(_ref, _createHandle, _deps) {
302
+ }
303
+ function useSyncExternalStore(_subscribe, getSnapshot, getServerSnapshot) {
304
+ return (getServerSnapshot || getSnapshot)();
305
+ }
306
+ function useTransition() {
307
+ return [false, (cb) => cb()];
308
+ }
309
+ function useDeferredValue(value) {
310
+ return value;
311
+ }
312
+ function useOptimistic(passthrough) {
313
+ return [passthrough, () => {
314
+ }];
315
+ }
316
+ function useActionState(_action, initialState, _permalink) {
317
+ return [initialState, () => {
318
+ }, false];
319
+ }
320
+ function use(usable) {
321
+ if (typeof usable === "object" && usable !== null && ("_currentValue" in usable || "_defaultValue" in usable)) {
322
+ return getContextValue(usable);
323
+ }
324
+ const promise = usable;
325
+ if (promise.status === "fulfilled")
326
+ return promise.value;
327
+ if (promise.status === "rejected")
328
+ throw promise.reason;
329
+ throw promise;
330
+ }
331
+
332
+ // src/slim-react/dispatcher.ts
333
+ import * as ReactNS from "react";
334
+ var _internals = ReactNS.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
335
+ var slimDispatcher = {
336
+ useId: makeId,
337
+ readContext: (ctx) => getContextValue(ctx),
338
+ useContext: (ctx) => getContextValue(ctx),
339
+ useState,
340
+ useReducer,
341
+ useEffect,
342
+ useLayoutEffect,
343
+ useInsertionEffect,
344
+ useRef,
345
+ useMemo,
346
+ useCallback,
347
+ useDebugValue,
348
+ useImperativeHandle,
349
+ useSyncExternalStore,
350
+ useTransition,
351
+ useDeferredValue,
352
+ useOptimistic,
353
+ useActionState,
354
+ use,
355
+ // React internals that compiled output may call
356
+ useMemoCache: (size) => new Array(size).fill(void 0),
357
+ useCacheRefresh: () => () => {
358
+ },
359
+ useHostTransitionStatus: () => false
360
+ };
361
+ function installDispatcher() {
362
+ if (!_internals)
363
+ return null;
364
+ const prev = _internals.H;
365
+ _internals.H = slimDispatcher;
366
+ return prev;
367
+ }
368
+ function restoreDispatcher(prev) {
369
+ if (_internals)
370
+ _internals.H = prev;
371
+ }
254
372
 
255
373
  // src/slim-react/render.ts
256
374
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
@@ -655,6 +773,7 @@ function renderComponent(type, props, writer, isSvg) {
655
773
  return;
656
774
  }
657
775
  let result;
776
+ const prevDispatcher = installDispatcher();
658
777
  try {
659
778
  if (type.prototype && typeof type.prototype.render === "function") {
660
779
  const instance = new type(props);
@@ -668,12 +787,20 @@ function renderComponent(type, props, writer, isSvg) {
668
787
  result = type(props);
669
788
  }
670
789
  } catch (e) {
790
+ restoreDispatcher(prevDispatcher);
671
791
  popComponentScope(savedScope);
672
792
  if (isProvider)
673
793
  popContextValue(ctx, prevCtxValue);
674
794
  throw e;
675
795
  }
796
+ restoreDispatcher(prevDispatcher);
797
+ let savedIdTree;
798
+ if (!(result instanceof Promise) && componentCalledUseId()) {
799
+ savedIdTree = pushTreeContext(1, 0);
800
+ }
676
801
  const finish = () => {
802
+ if (savedIdTree !== void 0)
803
+ popTreeContext(savedIdTree);
677
804
  popComponentScope(savedScope);
678
805
  if (isProvider)
679
806
  popContextValue(ctx, prevCtxValue);
@@ -682,22 +809,33 @@ function renderComponent(type, props, writer, isSvg) {
682
809
  const m = captureMap();
683
810
  return result.then((resolved) => {
684
811
  swapContextMap(m);
812
+ let asyncSavedIdTree;
813
+ if (componentCalledUseId()) {
814
+ asyncSavedIdTree = pushTreeContext(1, 0);
815
+ }
816
+ const asyncFinish = () => {
817
+ if (asyncSavedIdTree !== void 0)
818
+ popTreeContext(asyncSavedIdTree);
819
+ popComponentScope(savedScope);
820
+ if (isProvider)
821
+ popContextValue(ctx, prevCtxValue);
822
+ };
685
823
  const r2 = renderNode(resolved, writer, isSvg);
686
824
  if (r2 && typeof r2.then === "function") {
687
825
  const m2 = captureMap();
688
826
  return r2.then(
689
827
  () => {
690
828
  swapContextMap(m2);
691
- finish();
829
+ asyncFinish();
692
830
  },
693
831
  (e) => {
694
832
  swapContextMap(m2);
695
- finish();
833
+ asyncFinish();
696
834
  throw e;
697
835
  }
698
836
  );
699
837
  }
700
- finish();
838
+ asyncFinish();
701
839
  }, (e) => {
702
840
  swapContextMap(m);
703
841
  finish();
@@ -777,8 +915,8 @@ async function renderSuspense(props, writer, isSvg = false) {
777
915
  const snap = snapshotContext();
778
916
  while (attempts < MAX_SUSPENSE_RETRIES) {
779
917
  restoreContext(snap);
918
+ let buffer = new BufferWriter();
780
919
  try {
781
- const buffer = new BufferWriter();
782
920
  const r = renderNode(children, buffer, isSvg);
783
921
  if (r && typeof r.then === "function") {
784
922
  const m = captureMap();
@@ -812,12 +950,13 @@ async function renderSuspense(props, writer, isSvg = false) {
812
950
  }
813
951
  writer.write("<!--/$-->");
814
952
  }
815
- async function renderToString(element) {
953
+ async function renderToString(element, options) {
954
+ const idPrefix = options?.identifierPrefix ?? "";
816
955
  const contextMap = /* @__PURE__ */ new Map();
817
956
  const prev = swapContextMap(contextMap);
818
957
  try {
819
958
  for (let attempt = 0; attempt < MAX_SUSPENSE_RETRIES; attempt++) {
820
- resetRenderState();
959
+ resetRenderState(idPrefix);
821
960
  swapContextMap(contextMap);
822
961
  const chunks = [];
823
962
  const writer = {
@@ -934,31 +1073,14 @@ var getReactResponse = async (req, opts) => {
934
1073
  location: req.location,
935
1074
  ...Object.keys(serverData).length > 0 ? { __serverData: serverData } : {}
936
1075
  };
937
- const ReactPage = createElement(
938
- Fragment,
939
- null,
940
- createElement(
941
- "div",
942
- { id: "app" },
943
- createElement(App, { ...props, location: req.location, context })
944
- ),
945
- createElement("script", {
946
- id: "hadars",
947
- type: "application/json",
948
- dangerouslySetInnerHTML: {
949
- __html: JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c")
950
- }
951
- })
952
- );
1076
+ const appProps = { ...props, location: req.location, context };
953
1077
  return {
954
- ReactPage,
1078
+ App,
1079
+ appProps,
1080
+ clientProps,
955
1081
  unsuspend,
956
1082
  status: context.head.status,
957
- headHtml: getHeadHtml(context.head),
958
- renderPayload: {
959
- appProps: { ...props, location: req.location, context },
960
- clientProps
961
- }
1083
+ headHtml: getHeadHtml(context.head)
962
1084
  };
963
1085
  };
964
1086
 
@@ -1692,7 +1814,7 @@ var RenderWorkerPool = class {
1692
1814
  await Promise.all(this.workers.map((w) => w.terminate()));
1693
1815
  }
1694
1816
  };
1695
- async function buildSsrResponse(ReactPage, headHtml, status, getPrecontentHtml, unsuspendForRender) {
1817
+ async function buildSsrResponse(App, appProps, clientProps, headHtml, status, getPrecontentHtml, unsuspendForRender) {
1696
1818
  const responseStream = new ReadableStream({
1697
1819
  async start(controller) {
1698
1820
  const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
@@ -1700,12 +1822,15 @@ async function buildSsrResponse(ReactPage, headHtml, status, getPrecontentHtml,
1700
1822
  let bodyHtml;
1701
1823
  try {
1702
1824
  globalThis.__hadarsUnsuspend = unsuspendForRender;
1703
- bodyHtml = await renderToString(ReactPage);
1825
+ bodyHtml = await renderToString(createElement(App, appProps));
1704
1826
  } finally {
1705
1827
  globalThis.__hadarsUnsuspend = null;
1706
1828
  }
1707
1829
  bodyHtml = processSegmentCache(bodyHtml);
1708
- controller.enqueue(encoder.encode(bodyHtml + postContent));
1830
+ const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c");
1831
+ controller.enqueue(encoder.encode(
1832
+ `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` + postContent
1833
+ ));
1709
1834
  controller.close();
1710
1835
  }
1711
1836
  });
@@ -2075,7 +2200,7 @@ var dev = async (options) => {
2075
2200
  getAfterRenderProps,
2076
2201
  getFinalProps
2077
2202
  } = await import(importPath);
2078
- const { ReactPage, unsuspend, status, headHtml } = await getReactResponse(request, {
2203
+ const { App, appProps, clientProps, unsuspend, status, headHtml } = await getReactResponse(request, {
2079
2204
  document: {
2080
2205
  body: Component,
2081
2206
  lang: "en",
@@ -2084,7 +2209,7 @@ var dev = async (options) => {
2084
2209
  getFinalProps
2085
2210
  }
2086
2211
  });
2087
- return buildSsrResponse(ReactPage, headHtml, status, getPrecontentHtml, unsuspend);
2212
+ return buildSsrResponse(App, appProps, clientProps, headHtml, status, getPrecontentHtml, unsuspend);
2088
2213
  } catch (err) {
2089
2214
  console.error("[hadars] SSR render error:", err);
2090
2215
  const msg = (err?.stack ?? err?.message ?? String(err)).replace(/</g, "&lt;");
@@ -2229,7 +2354,7 @@ var run = async (options) => {
2229
2354
  status: wStatus
2230
2355
  });
2231
2356
  }
2232
- const { ReactPage, unsuspend, status, headHtml } = await getReactResponse(request, {
2357
+ const { App, appProps, clientProps, unsuspend, status, headHtml } = await getReactResponse(request, {
2233
2358
  document: {
2234
2359
  body: Component,
2235
2360
  lang: "en",
@@ -2238,7 +2363,7 @@ var run = async (options) => {
2238
2363
  getFinalProps
2239
2364
  }
2240
2365
  });
2241
- return buildSsrResponse(ReactPage, headHtml, status, getPrecontentHtml, unsuspend);
2366
+ return buildSsrResponse(App, appProps, clientProps, headHtml, status, getPrecontentHtml, unsuspend);
2242
2367
  } catch (err) {
2243
2368
  console.error("[hadars] SSR render error:", err);
2244
2369
  return new Response("Internal Server Error", { status: 500 });
package/dist/index.cjs CHANGED
@@ -183,10 +183,7 @@ function initServerDataCache(data) {
183
183
  function useServerData(key, fn) {
184
184
  const cacheKey = Array.isArray(key) ? JSON.stringify(key) : key;
185
185
  if (typeof window !== "undefined") {
186
- if (clientServerDataCache.has(cacheKey)) {
187
- return clientServerDataCache.get(cacheKey);
188
- }
189
- return fn();
186
+ return clientServerDataCache.get(cacheKey);
190
187
  }
191
188
  const unsuspend = globalThis.__hadarsUnsuspend;
192
189
  if (!unsuspend)
package/dist/index.d.ts CHANGED
@@ -205,6 +205,9 @@ declare function initServerDataCache(data: Record<string, unknown>): void;
205
205
  * awaited, the cache entry is then cleared so that the next render re-calls
206
206
  * `fn()` — at that point the Suspense hook returns synchronously.
207
207
  *
208
+ * `fn` is **server-only**: it is never called in the browser. The resolved value
209
+ * is serialised into `__serverData` and returned from cache during hydration.
210
+ *
208
211
  * @example
209
212
  * const user = useServerData('current_user', () => db.getUser(id));
210
213
  * const post = useServerData(['post', postId], () => db.getPost(postId));
package/dist/index.js CHANGED
@@ -140,10 +140,7 @@ function initServerDataCache(data) {
140
140
  function useServerData(key, fn) {
141
141
  const cacheKey = Array.isArray(key) ? JSON.stringify(key) : key;
142
142
  if (typeof window !== "undefined") {
143
- if (clientServerDataCache.has(cacheKey)) {
144
- return clientServerDataCache.get(cacheKey);
145
- }
146
- return fn();
143
+ return clientServerDataCache.get(cacheKey);
147
144
  }
148
145
  const unsuspend = globalThis.__hadarsUnsuspend;
149
146
  if (!unsuspend)
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/slim-react/index.ts
@@ -138,10 +148,11 @@ function s() {
138
148
  }
139
149
  return g[GLOBAL_KEY];
140
150
  }
141
- function resetRenderState() {
151
+ function resetRenderState(idPrefix = "") {
142
152
  const st = s();
143
153
  st.currentTreeContext = { ...EMPTY };
144
154
  st.localIdCounter = 0;
155
+ st.idPrefix = idPrefix;
145
156
  }
146
157
  function pushTreeContext(totalChildren, index) {
147
158
  const st = s();
@@ -182,6 +193,9 @@ function pushComponentScope() {
182
193
  function popComponentScope(saved) {
183
194
  s().localIdCounter = saved;
184
195
  }
196
+ function componentCalledUseId() {
197
+ return s().localIdCounter > 0;
198
+ }
185
199
  function snapshotContext() {
186
200
  const st = s();
187
201
  return { tree: { ...st.currentTreeContext }, localId: st.localIdCounter };
@@ -202,7 +216,7 @@ function makeId() {
202
216
  const st = s();
203
217
  const treeId = getTreeId();
204
218
  const n = st.localIdCounter++;
205
- let id = "_" + st.idPrefix + "R_" + treeId;
219
+ let id = "_R_" + st.idPrefix + treeId;
206
220
  if (n > 0)
207
221
  id += "H" + n.toString(32);
208
222
  return id + "_";
@@ -298,6 +312,47 @@ function createContext(defaultValue) {
298
312
  return context;
299
313
  }
300
314
 
315
+ // src/slim-react/dispatcher.ts
316
+ var ReactNS = __toESM(require("react"), 1);
317
+ var _internals = ReactNS.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
318
+ var slimDispatcher = {
319
+ useId: makeId,
320
+ readContext: (ctx) => getContextValue(ctx),
321
+ useContext: (ctx) => getContextValue(ctx),
322
+ useState,
323
+ useReducer,
324
+ useEffect,
325
+ useLayoutEffect,
326
+ useInsertionEffect,
327
+ useRef,
328
+ useMemo,
329
+ useCallback,
330
+ useDebugValue,
331
+ useImperativeHandle,
332
+ useSyncExternalStore,
333
+ useTransition,
334
+ useDeferredValue,
335
+ useOptimistic,
336
+ useActionState,
337
+ use,
338
+ // React internals that compiled output may call
339
+ useMemoCache: (size) => new Array(size).fill(void 0),
340
+ useCacheRefresh: () => () => {
341
+ },
342
+ useHostTransitionStatus: () => false
343
+ };
344
+ function installDispatcher() {
345
+ if (!_internals)
346
+ return null;
347
+ const prev = _internals.H;
348
+ _internals.H = slimDispatcher;
349
+ return prev;
350
+ }
351
+ function restoreDispatcher(prev) {
352
+ if (_internals)
353
+ _internals.H = prev;
354
+ }
355
+
301
356
  // src/slim-react/render.ts
302
357
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
303
358
  "area",
@@ -701,6 +756,7 @@ function renderComponent(type, props, writer, isSvg) {
701
756
  return;
702
757
  }
703
758
  let result;
759
+ const prevDispatcher = installDispatcher();
704
760
  try {
705
761
  if (type.prototype && typeof type.prototype.render === "function") {
706
762
  const instance = new type(props);
@@ -714,12 +770,20 @@ function renderComponent(type, props, writer, isSvg) {
714
770
  result = type(props);
715
771
  }
716
772
  } catch (e) {
773
+ restoreDispatcher(prevDispatcher);
717
774
  popComponentScope(savedScope);
718
775
  if (isProvider)
719
776
  popContextValue(ctx, prevCtxValue);
720
777
  throw e;
721
778
  }
779
+ restoreDispatcher(prevDispatcher);
780
+ let savedIdTree;
781
+ if (!(result instanceof Promise) && componentCalledUseId()) {
782
+ savedIdTree = pushTreeContext(1, 0);
783
+ }
722
784
  const finish = () => {
785
+ if (savedIdTree !== void 0)
786
+ popTreeContext(savedIdTree);
723
787
  popComponentScope(savedScope);
724
788
  if (isProvider)
725
789
  popContextValue(ctx, prevCtxValue);
@@ -728,22 +792,33 @@ function renderComponent(type, props, writer, isSvg) {
728
792
  const m = captureMap();
729
793
  return result.then((resolved) => {
730
794
  swapContextMap(m);
795
+ let asyncSavedIdTree;
796
+ if (componentCalledUseId()) {
797
+ asyncSavedIdTree = pushTreeContext(1, 0);
798
+ }
799
+ const asyncFinish = () => {
800
+ if (asyncSavedIdTree !== void 0)
801
+ popTreeContext(asyncSavedIdTree);
802
+ popComponentScope(savedScope);
803
+ if (isProvider)
804
+ popContextValue(ctx, prevCtxValue);
805
+ };
731
806
  const r2 = renderNode(resolved, writer, isSvg);
732
807
  if (r2 && typeof r2.then === "function") {
733
808
  const m2 = captureMap();
734
809
  return r2.then(
735
810
  () => {
736
811
  swapContextMap(m2);
737
- finish();
812
+ asyncFinish();
738
813
  },
739
814
  (e) => {
740
815
  swapContextMap(m2);
741
- finish();
816
+ asyncFinish();
742
817
  throw e;
743
818
  }
744
819
  );
745
820
  }
746
- finish();
821
+ asyncFinish();
747
822
  }, (e) => {
748
823
  swapContextMap(m);
749
824
  finish();
@@ -823,8 +898,8 @@ async function renderSuspense(props, writer, isSvg = false) {
823
898
  const snap = snapshotContext();
824
899
  while (attempts < MAX_SUSPENSE_RETRIES) {
825
900
  restoreContext(snap);
901
+ let buffer = new BufferWriter();
826
902
  try {
827
- const buffer = new BufferWriter();
828
903
  const r = renderNode(children, buffer, isSvg);
829
904
  if (r && typeof r.then === "function") {
830
905
  const m = captureMap();
@@ -858,12 +933,13 @@ async function renderSuspense(props, writer, isSvg = false) {
858
933
  }
859
934
  writer.write("<!--/$-->");
860
935
  }
861
- function renderToStream(element) {
936
+ function renderToStream(element, options) {
862
937
  const encoder = new TextEncoder();
938
+ const idPrefix = options?.identifierPrefix ?? "";
863
939
  const contextMap = /* @__PURE__ */ new Map();
864
940
  return new ReadableStream({
865
941
  async start(controller) {
866
- resetRenderState();
942
+ resetRenderState(idPrefix);
867
943
  const prev = swapContextMap(contextMap);
868
944
  const writer = {
869
945
  lastWasText: false,
@@ -892,12 +968,13 @@ function renderToStream(element) {
892
968
  }
893
969
  });
894
970
  }
895
- async function renderToString(element) {
971
+ async function renderToString(element, options) {
972
+ const idPrefix = options?.identifierPrefix ?? "";
896
973
  const contextMap = /* @__PURE__ */ new Map();
897
974
  const prev = swapContextMap(contextMap);
898
975
  try {
899
976
  for (let attempt = 0; attempt < MAX_SUSPENSE_RETRIES; attempt++) {
900
- resetRenderState();
977
+ resetRenderState(idPrefix);
901
978
  swapContextMap(contextMap);
902
979
  const chunks = [];
903
980
  const writer = {
@@ -80,19 +80,27 @@ declare function createContext<T>(defaultValue: T): Context<T>;
80
80
  * until the async data is ready, then continues – exactly as requested.
81
81
  */
82
82
 
83
+ interface RenderOptions {
84
+ /**
85
+ * Must match the `identifierPrefix` option passed to `hydrateRoot` on the
86
+ * client so that `useId()` generates identical IDs on server and client.
87
+ * Defaults to `""` (React's default).
88
+ */
89
+ identifierPrefix?: string;
90
+ }
83
91
  /**
84
92
  * Render a component tree to a `ReadableStream<Uint8Array>`.
85
93
  *
86
94
  * The stream pauses at `<Suspense>` boundaries until the suspended
87
95
  * promise resolves, then continues writing HTML.
88
96
  */
89
- declare function renderToStream(element: SlimNode): ReadableStream<Uint8Array>;
97
+ declare function renderToStream(element: SlimNode, options?: RenderOptions): ReadableStream<Uint8Array>;
90
98
  /**
91
99
  * Convenience: render to a complete HTML string.
92
100
  * Retries the full tree when a component throws a Promise (Suspense protocol),
93
101
  * so useServerData and similar hooks work without requiring explicit <Suspense>.
94
102
  */
95
- declare function renderToString(element: SlimNode): Promise<string>;
103
+ declare function renderToString(element: SlimNode, options?: RenderOptions): Promise<string>;
96
104
 
97
105
  /**
98
106
  * slim-react – a lightweight, SSR-only React-compatible runtime.
@@ -179,4 +187,4 @@ declare const React: {
179
187
  version: string;
180
188
  };
181
189
 
182
- export { Children, Component, Context, PureComponent, SlimElement, SlimNode, Suspense, cloneElement, createContext, createElement, React as default, forwardRef, isValidElement, lazy, memo, renderToStream as renderToReadableStream, renderToStream, renderToString, startTransition, use, useActionState, useCallback, useContext, useDebugValue, useDeferredValue, useEffect, useFormStatus, useId, useImperativeHandle, useInsertionEffect, useLayoutEffect, useMemo, useOptimistic, useReducer, useRef, useState, useSyncExternalStore, useTransition, version };
190
+ export { Children, Component, Context, PureComponent, RenderOptions, SlimElement, SlimNode, Suspense, cloneElement, createContext, createElement, React as default, forwardRef, isValidElement, lazy, memo, renderToStream as renderToReadableStream, renderToStream, renderToString, startTransition, use, useActionState, useCallback, useContext, useDebugValue, useDeferredValue, useEffect, useFormStatus, useId, useImperativeHandle, useInsertionEffect, useLayoutEffect, useMemo, useOptimistic, useReducer, useRef, useState, useSyncExternalStore, useTransition, version };