hadars 0.3.1 → 0.4.0

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 CHANGED
@@ -182,42 +182,46 @@ function getCtx() {
182
182
  var clientServerDataCache = /* @__PURE__ */ new Map();
183
183
  var pendingDataFetch = /* @__PURE__ */ new Map();
184
184
  var fetchedPaths = /* @__PURE__ */ new Set();
185
- var ssrInitialKeys = null;
186
- var unclaimedKeyCheckScheduled = false;
187
- function scheduleUnclaimedKeyCheck() {
188
- if (unclaimedKeyCheckScheduled) return;
189
- unclaimedKeyCheckScheduled = true;
190
- setTimeout(() => {
191
- unclaimedKeyCheckScheduled = false;
192
- if (ssrInitialKeys && ssrInitialKeys.size > 0) {
193
- console.warn(
194
- `[hadars] useServerData: ${ssrInitialKeys.size} server-resolved key(s) were never claimed during client hydration: ${[...ssrInitialKeys].map((k) => JSON.stringify(k)).join(", ")}. This usually means the key passed to useServerData was different on the server than on the client (e.g. it contains Date.now(), Math.random(), or another value that changes between renders). Keys must be stable and deterministic.`
195
- );
196
- }
197
- ssrInitialKeys = null;
198
- }, 0);
199
- }
185
+ var _navValues = [];
186
+ var _navIdx = 0;
200
187
  function initServerDataCache(data) {
201
188
  clientServerDataCache.clear();
202
- ssrInitialKeys = /* @__PURE__ */ new Set();
189
+ _navValues = [];
190
+ _navIdx = 0;
203
191
  for (const [k, v] of Object.entries(data)) {
204
192
  clientServerDataCache.set(k, v);
205
- ssrInitialKeys.add(k);
206
193
  }
207
194
  }
208
- function useServerData(key, fn) {
209
- const cacheKey = Array.isArray(key) ? JSON.stringify(key) : key;
195
+ function useServerData(fn, options) {
196
+ const cacheKey = import_react.default.useId();
197
+ const evictTimerRef = import_react.default.useRef(null);
198
+ import_react.default.useEffect(() => {
199
+ if (options?.cache !== false) return;
200
+ if (evictTimerRef.current !== null) {
201
+ clearTimeout(evictTimerRef.current);
202
+ evictTimerRef.current = null;
203
+ }
204
+ return () => {
205
+ evictTimerRef.current = setTimeout(() => {
206
+ clientServerDataCache.delete(cacheKey);
207
+ evictTimerRef.current = null;
208
+ }, 0);
209
+ };
210
+ }, []);
210
211
  if (typeof window !== "undefined") {
211
212
  if (clientServerDataCache.has(cacheKey)) {
212
- ssrInitialKeys?.delete(cacheKey);
213
213
  return clientServerDataCache.get(cacheKey);
214
214
  }
215
- if (ssrInitialKeys !== null && ssrInitialKeys.size > 0) {
216
- scheduleUnclaimedKeyCheck();
217
- }
218
215
  const pathKey = window.location.pathname + window.location.search;
219
216
  if (fetchedPaths.has(pathKey)) {
220
- return void 0;
217
+ if (_navIdx < _navValues.length) {
218
+ const value = _navValues[_navIdx++];
219
+ clientServerDataCache.set(cacheKey, value);
220
+ return value;
221
+ }
222
+ fetchedPaths.delete(pathKey);
223
+ _navValues = [];
224
+ _navIdx = 0;
221
225
  }
222
226
  if (!pendingDataFetch.has(pathKey)) {
223
227
  let resolve;
@@ -236,9 +240,9 @@ function useServerData(key, fn) {
236
240
  const res = await fetch(pathKey, { headers: { "Accept": "application/json" } });
237
241
  if (res.ok) json = await res.json();
238
242
  }
239
- for (const [k, v] of Object.entries(json?.serverData ?? {})) {
240
- clientServerDataCache.set(k, v);
241
- }
243
+ _navValues = Object.values(json?.serverData ?? {});
244
+ _navIdx = 0;
245
+ fetchedPaths.clear();
242
246
  } finally {
243
247
  fetchedPaths.add(pathKey);
244
248
  pendingDataFetch.delete(pathKey);
@@ -250,12 +254,7 @@ function useServerData(key, fn) {
250
254
  }
251
255
  const unsuspend = globalThis.__hadarsUnsuspend;
252
256
  if (!unsuspend) return void 0;
253
- const _u = unsuspend;
254
- if (!_u.pendingCreated) _u.pendingCreated = 0;
255
257
  const existing = unsuspend.cache.get(cacheKey);
256
- if (existing?.status === "fulfilled" && _u.lastPendingKey === cacheKey) {
257
- _u.lastPendingKeyAccessed = true;
258
- }
259
258
  if (!existing) {
260
259
  const result = fn();
261
260
  const isThenable = result !== null && typeof result === "object" && typeof result.then === "function";
@@ -264,22 +263,6 @@ function useServerData(key, fn) {
264
263
  unsuspend.cache.set(cacheKey, { status: "fulfilled", value });
265
264
  return value;
266
265
  }
267
- if (_u.lastPendingKey != null && !_u.lastPendingKeyAccessed) {
268
- const prev = unsuspend.cache.get(_u.lastPendingKey);
269
- if (prev?.status === "fulfilled") {
270
- throw new Error(
271
- `[hadars] useServerData: key ${JSON.stringify(cacheKey)} is not stable between render passes. The previous pass resolved ${JSON.stringify(_u.lastPendingKey)} but it was not requested in this pass \u2014 the key is changing between renders. Avoid dynamic values in keys (e.g. Date.now() or Math.random()); use stable, deterministic identifiers instead.`
272
- );
273
- }
274
- }
275
- _u.pendingCreated++;
276
- if (_u.pendingCreated > 100) {
277
- throw new Error(
278
- `[hadars] useServerData: more than 100 async keys created in a single render. This usually means a key is not stable between renders (e.g. it contains Date.now() or Math.random()). Currently offending key: ${JSON.stringify(cacheKey)}.`
279
- );
280
- }
281
- _u.lastPendingKey = cacheKey;
282
- _u.lastPendingKeyAccessed = false;
283
266
  const promise = result.then(
284
267
  (value) => {
285
268
  unsuspend.cache.set(cacheKey, { status: "fulfilled", value });
@@ -297,18 +280,8 @@ function useServerData(key, fn) {
297
280
  if (existing.status === "rejected") throw existing.reason;
298
281
  return existing.value;
299
282
  }
300
- function toCacheKey(doc) {
301
- if (typeof doc === "string") return doc.trim();
302
- for (const def of doc?.definitions ?? []) {
303
- if (def.kind === "OperationDefinition" && def.name?.value) {
304
- return `op:${def.name.value}`;
305
- }
306
- }
307
- return JSON.stringify(doc?.definitions ?? doc);
308
- }
309
283
  function useGraphQL(query, variables) {
310
- const key = ["__gql", toCacheKey(query), JSON.stringify(variables ?? {})];
311
- return useServerData(key, async () => {
284
+ return useServerData(async () => {
312
285
  const executor = globalThis.__hadarsGraphQL;
313
286
  if (!executor) {
314
287
  throw new Error(
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as HadarsDocumentNode } from './hadars-CSWWhlQC.cjs';
2
- export { G as GraphQLExecutor, c as HadarsApp, H as HadarsEntryModule, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest, i as HadarsSourceEntry, j as HadarsStaticContext } from './hadars-CSWWhlQC.cjs';
1
+ import { b as HadarsDocumentNode } from './hadars-CPplIz_z.cjs';
2
+ export { G as GraphQLExecutor, c as HadarsApp, H as HadarsEntryModule, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest, i as HadarsSourceEntry, j as HadarsStaticContext } from './hadars-CPplIz_z.cjs';
3
3
  import React from 'react';
4
4
 
5
5
  /** Call this before hydrating to seed the client cache from the server's data.
@@ -14,9 +14,8 @@ declare function initServerDataCache(data: Record<string, unknown>): void;
14
14
  * On the client the pre-resolved value is read from the hydration cache
15
15
  * serialised into the page by the server, so no fetch is issued in the browser.
16
16
  *
17
- * The `key` (string or array of strings) uniquely identifies the cached value
18
- * across all SSR render passes and client hydration it must be stable and
19
- * unique within the page.
17
+ * The cache key is derived automatically from the call-site's position in the
18
+ * component tree via `useId()`no manual key is required.
20
19
  *
21
20
  * `fn` may return a `Promise<T>` (async usage) or return `T` synchronously.
22
21
  * The resolved value is serialised into `__serverData` and returned from cache
@@ -28,11 +27,16 @@ declare function initServerDataCache(data: Record<string, unknown>): void;
28
27
  * React Suspense until the server returns the JSON map of resolved values.
29
28
  *
30
29
  * @example
31
- * const user = useServerData('current_user', () => db.getUser(id));
32
- * const post = useServerData(['post', postId], () => db.getPost(postId));
30
+ * const user = useServerData(() => db.getUser(id));
31
+ * const post = useServerData(() => db.getPost(postId));
33
32
  * if (!user) return null; // undefined while pending on the first SSR pass
33
+ *
34
+ * // cache: false — evicts the entry on unmount so the next mount fetches fresh data
35
+ * const stats = useServerData(() => getServerStats(), { cache: false });
34
36
  */
35
- declare function useServerData<T>(key: string | string[], fn: () => Promise<T> | T): T | undefined;
37
+ declare function useServerData<T>(fn: () => Promise<T> | T, options?: {
38
+ cache?: boolean;
39
+ }): T | undefined;
36
40
  /**
37
41
  * Execute a GraphQL query server-side and return the result.
38
42
  *
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as HadarsDocumentNode } from './hadars-CSWWhlQC.js';
2
- export { G as GraphQLExecutor, c as HadarsApp, H as HadarsEntryModule, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest, i as HadarsSourceEntry, j as HadarsStaticContext } from './hadars-CSWWhlQC.js';
1
+ import { b as HadarsDocumentNode } from './hadars-CPplIz_z.js';
2
+ export { G as GraphQLExecutor, c as HadarsApp, H as HadarsEntryModule, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest, i as HadarsSourceEntry, j as HadarsStaticContext } from './hadars-CPplIz_z.js';
3
3
  import React from 'react';
4
4
 
5
5
  /** Call this before hydrating to seed the client cache from the server's data.
@@ -14,9 +14,8 @@ declare function initServerDataCache(data: Record<string, unknown>): void;
14
14
  * On the client the pre-resolved value is read from the hydration cache
15
15
  * serialised into the page by the server, so no fetch is issued in the browser.
16
16
  *
17
- * The `key` (string or array of strings) uniquely identifies the cached value
18
- * across all SSR render passes and client hydration it must be stable and
19
- * unique within the page.
17
+ * The cache key is derived automatically from the call-site's position in the
18
+ * component tree via `useId()`no manual key is required.
20
19
  *
21
20
  * `fn` may return a `Promise<T>` (async usage) or return `T` synchronously.
22
21
  * The resolved value is serialised into `__serverData` and returned from cache
@@ -28,11 +27,16 @@ declare function initServerDataCache(data: Record<string, unknown>): void;
28
27
  * React Suspense until the server returns the JSON map of resolved values.
29
28
  *
30
29
  * @example
31
- * const user = useServerData('current_user', () => db.getUser(id));
32
- * const post = useServerData(['post', postId], () => db.getPost(postId));
30
+ * const user = useServerData(() => db.getUser(id));
31
+ * const post = useServerData(() => db.getPost(postId));
33
32
  * if (!user) return null; // undefined while pending on the first SSR pass
33
+ *
34
+ * // cache: false — evicts the entry on unmount so the next mount fetches fresh data
35
+ * const stats = useServerData(() => getServerStats(), { cache: false });
34
36
  */
35
- declare function useServerData<T>(key: string | string[], fn: () => Promise<T> | T): T | undefined;
37
+ declare function useServerData<T>(fn: () => Promise<T> | T, options?: {
38
+ cache?: boolean;
39
+ }): T | undefined;
36
40
  /**
37
41
  * Execute a GraphQL query server-side and return the result.
38
42
  *
package/dist/index.js CHANGED
@@ -142,42 +142,46 @@ function getCtx() {
142
142
  var clientServerDataCache = /* @__PURE__ */ new Map();
143
143
  var pendingDataFetch = /* @__PURE__ */ new Map();
144
144
  var fetchedPaths = /* @__PURE__ */ new Set();
145
- var ssrInitialKeys = null;
146
- var unclaimedKeyCheckScheduled = false;
147
- function scheduleUnclaimedKeyCheck() {
148
- if (unclaimedKeyCheckScheduled) return;
149
- unclaimedKeyCheckScheduled = true;
150
- setTimeout(() => {
151
- unclaimedKeyCheckScheduled = false;
152
- if (ssrInitialKeys && ssrInitialKeys.size > 0) {
153
- console.warn(
154
- `[hadars] useServerData: ${ssrInitialKeys.size} server-resolved key(s) were never claimed during client hydration: ${[...ssrInitialKeys].map((k) => JSON.stringify(k)).join(", ")}. This usually means the key passed to useServerData was different on the server than on the client (e.g. it contains Date.now(), Math.random(), or another value that changes between renders). Keys must be stable and deterministic.`
155
- );
156
- }
157
- ssrInitialKeys = null;
158
- }, 0);
159
- }
145
+ var _navValues = [];
146
+ var _navIdx = 0;
160
147
  function initServerDataCache(data) {
161
148
  clientServerDataCache.clear();
162
- ssrInitialKeys = /* @__PURE__ */ new Set();
149
+ _navValues = [];
150
+ _navIdx = 0;
163
151
  for (const [k, v] of Object.entries(data)) {
164
152
  clientServerDataCache.set(k, v);
165
- ssrInitialKeys.add(k);
166
153
  }
167
154
  }
168
- function useServerData(key, fn) {
169
- const cacheKey = Array.isArray(key) ? JSON.stringify(key) : key;
155
+ function useServerData(fn, options) {
156
+ const cacheKey = React.useId();
157
+ const evictTimerRef = React.useRef(null);
158
+ React.useEffect(() => {
159
+ if (options?.cache !== false) return;
160
+ if (evictTimerRef.current !== null) {
161
+ clearTimeout(evictTimerRef.current);
162
+ evictTimerRef.current = null;
163
+ }
164
+ return () => {
165
+ evictTimerRef.current = setTimeout(() => {
166
+ clientServerDataCache.delete(cacheKey);
167
+ evictTimerRef.current = null;
168
+ }, 0);
169
+ };
170
+ }, []);
170
171
  if (typeof window !== "undefined") {
171
172
  if (clientServerDataCache.has(cacheKey)) {
172
- ssrInitialKeys?.delete(cacheKey);
173
173
  return clientServerDataCache.get(cacheKey);
174
174
  }
175
- if (ssrInitialKeys !== null && ssrInitialKeys.size > 0) {
176
- scheduleUnclaimedKeyCheck();
177
- }
178
175
  const pathKey = window.location.pathname + window.location.search;
179
176
  if (fetchedPaths.has(pathKey)) {
180
- return void 0;
177
+ if (_navIdx < _navValues.length) {
178
+ const value = _navValues[_navIdx++];
179
+ clientServerDataCache.set(cacheKey, value);
180
+ return value;
181
+ }
182
+ fetchedPaths.delete(pathKey);
183
+ _navValues = [];
184
+ _navIdx = 0;
181
185
  }
182
186
  if (!pendingDataFetch.has(pathKey)) {
183
187
  let resolve;
@@ -196,9 +200,9 @@ function useServerData(key, fn) {
196
200
  const res = await fetch(pathKey, { headers: { "Accept": "application/json" } });
197
201
  if (res.ok) json = await res.json();
198
202
  }
199
- for (const [k, v] of Object.entries(json?.serverData ?? {})) {
200
- clientServerDataCache.set(k, v);
201
- }
203
+ _navValues = Object.values(json?.serverData ?? {});
204
+ _navIdx = 0;
205
+ fetchedPaths.clear();
202
206
  } finally {
203
207
  fetchedPaths.add(pathKey);
204
208
  pendingDataFetch.delete(pathKey);
@@ -210,12 +214,7 @@ function useServerData(key, fn) {
210
214
  }
211
215
  const unsuspend = globalThis.__hadarsUnsuspend;
212
216
  if (!unsuspend) return void 0;
213
- const _u = unsuspend;
214
- if (!_u.pendingCreated) _u.pendingCreated = 0;
215
217
  const existing = unsuspend.cache.get(cacheKey);
216
- if (existing?.status === "fulfilled" && _u.lastPendingKey === cacheKey) {
217
- _u.lastPendingKeyAccessed = true;
218
- }
219
218
  if (!existing) {
220
219
  const result = fn();
221
220
  const isThenable = result !== null && typeof result === "object" && typeof result.then === "function";
@@ -224,22 +223,6 @@ function useServerData(key, fn) {
224
223
  unsuspend.cache.set(cacheKey, { status: "fulfilled", value });
225
224
  return value;
226
225
  }
227
- if (_u.lastPendingKey != null && !_u.lastPendingKeyAccessed) {
228
- const prev = unsuspend.cache.get(_u.lastPendingKey);
229
- if (prev?.status === "fulfilled") {
230
- throw new Error(
231
- `[hadars] useServerData: key ${JSON.stringify(cacheKey)} is not stable between render passes. The previous pass resolved ${JSON.stringify(_u.lastPendingKey)} but it was not requested in this pass \u2014 the key is changing between renders. Avoid dynamic values in keys (e.g. Date.now() or Math.random()); use stable, deterministic identifiers instead.`
232
- );
233
- }
234
- }
235
- _u.pendingCreated++;
236
- if (_u.pendingCreated > 100) {
237
- throw new Error(
238
- `[hadars] useServerData: more than 100 async keys created in a single render. This usually means a key is not stable between renders (e.g. it contains Date.now() or Math.random()). Currently offending key: ${JSON.stringify(cacheKey)}.`
239
- );
240
- }
241
- _u.lastPendingKey = cacheKey;
242
- _u.lastPendingKeyAccessed = false;
243
226
  const promise = result.then(
244
227
  (value) => {
245
228
  unsuspend.cache.set(cacheKey, { status: "fulfilled", value });
@@ -257,18 +240,8 @@ function useServerData(key, fn) {
257
240
  if (existing.status === "rejected") throw existing.reason;
258
241
  return existing.value;
259
242
  }
260
- function toCacheKey(doc) {
261
- if (typeof doc === "string") return doc.trim();
262
- for (const def of doc?.definitions ?? []) {
263
- if (def.kind === "OperationDefinition" && def.name?.value) {
264
- return `op:${def.name.value}`;
265
- }
266
- }
267
- return JSON.stringify(doc?.definitions ?? doc);
268
- }
269
243
  function useGraphQL(query, variables) {
270
- const key = ["__gql", toCacheKey(query), JSON.stringify(variables ?? {})];
271
- return useServerData(key, async () => {
244
+ return useServerData(async () => {
272
245
  const executor = globalThis.__hadarsGraphQL;
273
246
  if (!executor) {
274
247
  throw new Error(
package/dist/lambda.cjs CHANGED
@@ -323,7 +323,16 @@ function componentCalledUseId() {
323
323
  function snapshotContext() {
324
324
  const st = s();
325
325
  const ctx = st.currentTreeContext;
326
- return { tree: { id: ctx.id, overflow: ctx.overflow }, localId: st.localIdCounter, treeDepth: _treeDepth };
326
+ const depth = _treeDepth;
327
+ return {
328
+ tree: { id: ctx.id, overflow: ctx.overflow },
329
+ localId: st.localIdCounter,
330
+ treeDepth: depth,
331
+ // Snapshot the live stack so that popTreeContext reads correct saved values
332
+ // even if another concurrent render's resetRenderState stomped the arrays.
333
+ idStack: _treeIdStack.slice(0, depth),
334
+ ovStack: _treeOvStack.slice(0, depth)
335
+ };
327
336
  }
328
337
  function restoreContext(snap) {
329
338
  const st = s();
@@ -332,6 +341,10 @@ function restoreContext(snap) {
332
341
  ctx.overflow = snap.tree.overflow;
333
342
  st.localIdCounter = snap.localId;
334
343
  _treeDepth = snap.treeDepth;
344
+ for (let i = 0; i < snap.treeDepth; i++) {
345
+ _treeIdStack[i] = snap.idStack[i];
346
+ _treeOvStack[i] = snap.ovStack[i];
347
+ }
335
348
  }
336
349
  function getTreeId() {
337
350
  const { id, overflow } = s().currentTreeContext;
@@ -445,6 +458,14 @@ function restoreDispatcher(prev) {
445
458
  }
446
459
 
447
460
  // src/slim-react/render.ts
461
+ function captureRenderCtx() {
462
+ return { m: captureMap(), u: captureUnsuspend(), t: snapshotContext() };
463
+ }
464
+ function restoreRenderCtx(ctx) {
465
+ swapContextMap(ctx.m);
466
+ restoreUnsuspend(ctx.u);
467
+ restoreContext(ctx.t);
468
+ }
448
469
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
449
470
  "area",
450
471
  "base",
@@ -888,11 +909,9 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
888
909
  if (e && typeof e.then === "function") {
889
910
  if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
890
911
  patchPromiseStatus(e);
891
- const m = captureMap();
892
- const u = captureUnsuspend();
912
+ const rctx = captureRenderCtx();
893
913
  return e.then(() => {
894
- swapContextMap(m);
895
- restoreUnsuspend(u);
914
+ restoreRenderCtx(rctx);
896
915
  return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
897
916
  });
898
917
  }
@@ -929,17 +948,14 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
929
948
  };
930
949
  const r2 = renderChildren(props.children, writer, isSvg);
931
950
  if (r2 && typeof r2.then === "function") {
932
- const m = captureMap();
933
- const u = captureUnsuspend();
951
+ const rctx = captureRenderCtx();
934
952
  return r2.then(
935
953
  () => {
936
- swapContextMap(m);
937
- restoreUnsuspend(u);
954
+ restoreRenderCtx(rctx);
938
955
  finish();
939
956
  },
940
957
  (e) => {
941
- swapContextMap(m);
942
- restoreUnsuspend(u);
958
+ restoreRenderCtx(rctx);
943
959
  finish();
944
960
  throw e;
945
961
  }
@@ -968,11 +984,9 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
968
984
  if (e && typeof e.then === "function") {
969
985
  if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
970
986
  patchPromiseStatus(e);
971
- const m = captureMap();
972
- const u = captureUnsuspend();
987
+ const rctx = captureRenderCtx();
973
988
  return e.then(() => {
974
- swapContextMap(m);
975
- restoreUnsuspend(u);
989
+ restoreRenderCtx(rctx);
976
990
  return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
977
991
  });
978
992
  }
@@ -984,30 +998,25 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
984
998
  savedIdTree = pushTreeContext(1, 0);
985
999
  }
986
1000
  if (result instanceof Promise) {
987
- const m = captureMap();
988
- const u = captureUnsuspend();
1001
+ const rctx = captureRenderCtx();
989
1002
  return result.then((resolved) => {
990
- swapContextMap(m);
991
- restoreUnsuspend(u);
1003
+ restoreRenderCtx(rctx);
992
1004
  let asyncSavedIdTree;
993
1005
  if (componentCalledUseId()) {
994
1006
  asyncSavedIdTree = pushTreeContext(1, 0);
995
1007
  }
996
1008
  const r2 = renderNode(resolved, writer, isSvg);
997
1009
  if (r2 && typeof r2.then === "function") {
998
- const m2 = captureMap();
999
- const u2 = captureUnsuspend();
1010
+ const rctx2 = captureRenderCtx();
1000
1011
  return r2.then(
1001
1012
  () => {
1002
- swapContextMap(m2);
1003
- restoreUnsuspend(u2);
1013
+ restoreRenderCtx(rctx2);
1004
1014
  if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
1005
1015
  popComponentScope(savedScope);
1006
1016
  if (isProvider) popContextValue(ctx, prevCtxValue);
1007
1017
  },
1008
1018
  (e) => {
1009
- swapContextMap(m2);
1010
- restoreUnsuspend(u2);
1019
+ restoreRenderCtx(rctx2);
1011
1020
  if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
1012
1021
  popComponentScope(savedScope);
1013
1022
  if (isProvider) popContextValue(ctx, prevCtxValue);
@@ -1019,8 +1028,7 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
1019
1028
  popComponentScope(savedScope);
1020
1029
  if (isProvider) popContextValue(ctx, prevCtxValue);
1021
1030
  }, (e) => {
1022
- swapContextMap(m);
1023
- restoreUnsuspend(u);
1031
+ restoreRenderCtx(rctx);
1024
1032
  popComponentScope(savedScope);
1025
1033
  if (isProvider) popContextValue(ctx, prevCtxValue);
1026
1034
  throw e;
@@ -1028,19 +1036,16 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
1028
1036
  }
1029
1037
  const r = renderNode(result, writer, isSvg);
1030
1038
  if (r && typeof r.then === "function") {
1031
- const m = captureMap();
1032
- const u = captureUnsuspend();
1039
+ const rctx = captureRenderCtx();
1033
1040
  return r.then(
1034
1041
  () => {
1035
- swapContextMap(m);
1036
- restoreUnsuspend(u);
1042
+ restoreRenderCtx(rctx);
1037
1043
  if (savedIdTree !== void 0) popTreeContext(savedIdTree);
1038
1044
  popComponentScope(savedScope);
1039
1045
  if (isProvider) popContextValue(ctx, prevCtxValue);
1040
1046
  },
1041
1047
  (e) => {
1042
- swapContextMap(m);
1043
- restoreUnsuspend(u);
1048
+ restoreRenderCtx(rctx);
1044
1049
  if (savedIdTree !== void 0) popTreeContext(savedIdTree);
1045
1050
  popComponentScope(savedScope);
1046
1051
  if (isProvider) popContextValue(ctx, prevCtxValue);
@@ -1065,11 +1070,9 @@ function renderChildArrayFrom(children, startIndex, writer, isSvg) {
1065
1070
  const savedTree = pushTreeContext(totalChildren, i);
1066
1071
  const r = renderNode(child, writer, isSvg);
1067
1072
  if (r && typeof r.then === "function") {
1068
- const m = captureMap();
1069
- const u = captureUnsuspend();
1073
+ const rctx = captureRenderCtx();
1070
1074
  return r.then(() => {
1071
- swapContextMap(m);
1072
- restoreUnsuspend(u);
1075
+ restoreRenderCtx(rctx);
1073
1076
  popTreeContext(savedTree);
1074
1077
  return renderChildArrayFrom(children, i + 1, writer, isSvg);
1075
1078
  });
@@ -1093,11 +1096,9 @@ async function renderSuspense(props, writer, isSvg = false) {
1093
1096
  try {
1094
1097
  const r = renderNode(children, buffer, isSvg);
1095
1098
  if (r && typeof r.then === "function") {
1096
- const m = captureMap();
1097
- const u = captureUnsuspend();
1099
+ const rctx = captureRenderCtx();
1098
1100
  await r;
1099
- swapContextMap(m);
1100
- restoreUnsuspend(u);
1101
+ restoreRenderCtx(rctx);
1101
1102
  }
1102
1103
  writer.write("<!--$-->");
1103
1104
  buffer.flushTo(writer);
@@ -1111,11 +1112,9 @@ async function renderSuspense(props, writer, isSvg = false) {
1111
1112
  if (fallback) {
1112
1113
  const r = renderNode(fallback, writer, isSvg);
1113
1114
  if (r && typeof r.then === "function") {
1114
- const m = captureMap();
1115
- const u = captureUnsuspend();
1115
+ const rctx = captureRenderCtx();
1116
1116
  await r;
1117
- swapContextMap(m);
1118
- restoreUnsuspend(u);
1117
+ restoreRenderCtx(rctx);
1119
1118
  }
1120
1119
  }
1121
1120
  writer.write("<!--/$-->");
@@ -1140,9 +1139,9 @@ async function renderPreflight(element, options) {
1140
1139
  NULL_WRITER.lastWasText = false;
1141
1140
  const r = renderNode(element, NULL_WRITER);
1142
1141
  if (r && typeof r.then === "function") {
1143
- const m = captureMap();
1142
+ const rctx = captureRenderCtx();
1144
1143
  await r;
1145
- swapContextMap(m);
1144
+ restoreRenderCtx(rctx);
1146
1145
  }
1147
1146
  } finally {
1148
1147
  swapContextMap(prev);
@@ -1167,9 +1166,9 @@ async function renderToString(element, options) {
1167
1166
  resetRenderState(idPrefix);
1168
1167
  const r = renderNode(element, writer);
1169
1168
  if (r && typeof r.then === "function") {
1170
- const m = captureMap();
1169
+ const rctx = captureRenderCtx();
1171
1170
  await r;
1172
- swapContextMap(m);
1171
+ restoreRenderCtx(rctx);
1173
1172
  }
1174
1173
  return output;
1175
1174
  } finally {
@@ -1285,18 +1284,18 @@ var makePrecontentHtmlGetter = (htmlFilePromise) => {
1285
1284
  let preHead = null;
1286
1285
  let postHead = null;
1287
1286
  let postContent = null;
1287
+ const primed = htmlFilePromise.then((html) => {
1288
+ const headEnd = html.indexOf(HEAD_MARKER);
1289
+ const contentStart = html.indexOf(BODY_MARKER);
1290
+ preHead = html.slice(0, headEnd);
1291
+ postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
1292
+ postContent = html.slice(contentStart + BODY_MARKER.length);
1293
+ });
1288
1294
  return (headHtml) => {
1289
1295
  if (preHead !== null) {
1290
1296
  return [preHead + headHtml + postHead, postContent];
1291
1297
  }
1292
- return htmlFilePromise.then((html) => {
1293
- const headEnd = html.indexOf(HEAD_MARKER);
1294
- const contentStart = html.indexOf(BODY_MARKER);
1295
- preHead = html.slice(0, headEnd);
1296
- postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
1297
- postContent = html.slice(contentStart + BODY_MARKER.length);
1298
- return [preHead + headHtml + postHead, postContent];
1299
- });
1298
+ return primed.then(() => [preHead + headHtml + postHead, postContent]);
1300
1299
  };
1301
1300
  };
1302
1301
  async function transformStream(data, stream) {
@@ -1456,14 +1455,8 @@ function createLambdaHandler(options, bundled) {
1456
1455
  const fetchHandler = options.fetch;
1457
1456
  const handleProxy = createProxyHandler(options);
1458
1457
  const getPrecontentHtml = bundled ? makePrecontentHtmlGetter(Promise.resolve(bundled.outHtml)) : makePrecontentHtmlGetter(import_promises2.default.readFile(import_node_path.default.join(cwd, StaticPath, "out.html"), "utf-8"));
1459
- let ssrModulePromise = null;
1460
- const getSsrModule = () => {
1461
- if (bundled) return Promise.resolve(bundled.ssrModule);
1462
- if (!ssrModulePromise) {
1463
- ssrModulePromise = import((0, import_node_url.pathToFileURL)(import_node_path.default.resolve(cwd, HadarsFolder, SSR_FILENAME)).href);
1464
- }
1465
- return ssrModulePromise;
1466
- };
1458
+ const ssrModulePromise = bundled ? Promise.resolve(bundled.ssrModule) : import((0, import_node_url.pathToFileURL)(import_node_path.default.resolve(cwd, HadarsFolder, SSR_FILENAME)).href);
1459
+ const getSsrModule = () => ssrModulePromise;
1467
1460
  const runHandler = async (req) => {
1468
1461
  const request = parseRequest(req);
1469
1462
  if (fetchHandler) {
@@ -1517,6 +1510,8 @@ function createLambdaHandler(options, bundled) {
1517
1510
  });
1518
1511
  } catch (err) {
1519
1512
  console.error("[hadars] SSR render error:", err);
1513
+ options.onError?.(err, request)?.catch?.(() => {
1514
+ });
1520
1515
  return new Response("Internal Server Error", { status: 500 });
1521
1516
  }
1522
1517
  };
package/dist/lambda.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-CSWWhlQC.cjs';
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-CPplIz_z.cjs';
2
2
 
3
3
  /**
4
4
  * AWS Lambda adapter for hadars.
package/dist/lambda.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-CSWWhlQC.js';
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-CPplIz_z.js';
2
2
 
3
3
  /**
4
4
  * AWS Lambda adapter for hadars.