hadars 0.1.16 → 0.1.18

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
@@ -30,8 +30,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.tsx
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ CacheSegment: () => CacheSegment,
33
34
  HadarsContext: () => HadarsContext,
34
35
  HadarsHead: () => Head,
36
+ clearSegments: () => clearSegments,
37
+ deleteSegment: () => deleteSegment,
35
38
  initServerDataCache: () => initServerDataCache,
36
39
  loadModule: () => loadModule,
37
40
  useServerData: () => useServerData
@@ -216,8 +219,7 @@ function useServerData(key, fn) {
216
219
  }
217
220
  );
218
221
  unsuspend.cache.set(cacheKey, { status: "pending", promise: suspensePromise });
219
- unsuspend.hasPending = true;
220
- return void 0;
222
+ throw suspensePromise;
221
223
  }
222
224
  throw thrown;
223
225
  }
@@ -236,12 +238,10 @@ function useServerData(key, fn) {
236
238
  }
237
239
  );
238
240
  unsuspend.cache.set(cacheKey, { status: "pending", promise });
239
- unsuspend.hasPending = true;
240
- return void 0;
241
+ throw promise;
241
242
  }
242
243
  if (existing.status === "pending") {
243
- unsuspend.hasPending = true;
244
- return void 0;
244
+ throw existing.promise;
245
245
  }
246
246
  if (existing.status === "rejected")
247
247
  throw existing.reason;
@@ -298,6 +298,58 @@ var Head = import_react.default.memo(({ children, status }) => {
298
298
  return null;
299
299
  });
300
300
 
301
+ // src/components/CacheSegment.tsx
302
+ var import_react2 = __toESM(require("react"), 1);
303
+
304
+ // src/utils/segmentCache.ts
305
+ function getStore() {
306
+ const g = globalThis;
307
+ if (!g.__hadarsSegmentStore) {
308
+ g.__hadarsSegmentStore = /* @__PURE__ */ new Map();
309
+ }
310
+ return g.__hadarsSegmentStore;
311
+ }
312
+ function getSegment(key) {
313
+ const entry = getStore().get(key);
314
+ if (!entry)
315
+ return null;
316
+ if (entry.expiresAt !== null && Date.now() >= entry.expiresAt) {
317
+ getStore().delete(key);
318
+ return null;
319
+ }
320
+ return entry.html;
321
+ }
322
+ function deleteSegment(key) {
323
+ getStore().delete(key);
324
+ }
325
+ function clearSegments() {
326
+ getStore().clear();
327
+ }
328
+ var CACHE_TAG = "hadars-c";
329
+
330
+ // src/components/CacheSegment.tsx
331
+ var import_jsx_runtime2 = require("react/jsx-runtime");
332
+ function CacheSegment({ cacheKey, ttl, children }) {
333
+ if (typeof window !== "undefined") {
334
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
335
+ }
336
+ const cached = getSegment(cacheKey);
337
+ if (cached !== null) {
338
+ return import_react2.default.createElement(CACHE_TAG, {
339
+ "data-key": cacheKey,
340
+ "data-cache": "hit",
341
+ dangerouslySetInnerHTML: { __html: cached }
342
+ });
343
+ }
344
+ const props = {
345
+ "data-key": cacheKey,
346
+ "data-cache": "miss"
347
+ };
348
+ if (ttl != null)
349
+ props["data-ttl"] = ttl;
350
+ return import_react2.default.createElement(CACHE_TAG, props, children);
351
+ }
352
+
301
353
  // src/index.tsx
302
354
  var HadarsContext = typeof window === "undefined" ? AppProviderSSR : AppProviderCSR;
303
355
  function loadModule(path) {
@@ -308,8 +360,11 @@ function loadModule(path) {
308
360
  }
309
361
  // Annotate the CommonJS export names for ESM import in node:
310
362
  0 && (module.exports = {
363
+ CacheSegment,
311
364
  HadarsContext,
312
365
  HadarsHead,
366
+ clearSegments,
367
+ deleteSegment,
313
368
  initServerDataCache,
314
369
  loadModule,
315
370
  useServerData
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as React$1 from 'react';
2
2
  import React__default, { MetaHTMLAttributes, LinkHTMLAttributes, StyleHTMLAttributes, ScriptHTMLAttributes } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
4
 
4
5
  type HadarsGetInitialProps<T extends {}> = (req: HadarsRequest) => Promise<T> | T;
5
6
  type HadarsGetClientProps<T extends {}> = (props: T) => Promise<T> | T;
@@ -181,6 +182,44 @@ declare const Head: React__default.FC<{
181
182
  status?: number;
182
183
  }>;
183
184
 
185
+ interface CacheSegmentProps {
186
+ /**
187
+ * Unique cache key for this segment. Use a key that encodes all values
188
+ * the output depends on, e.g. `"product-" + product.id`.
189
+ */
190
+ cacheKey: string;
191
+ /**
192
+ * Time-to-live in milliseconds. Omit for entries that never expire.
193
+ */
194
+ ttl?: number;
195
+ children: React__default.ReactNode;
196
+ }
197
+ /**
198
+ * Caches the server-rendered HTML of its children across requests.
199
+ *
200
+ * **Server (SSR):**
201
+ * - Cache miss — children are rendered normally as part of the main
202
+ * `renderToString` call, so React context propagates correctly. The output
203
+ * is wrapped in a `<hadars-c>` marker that `processSegmentCache` uses to
204
+ * extract and store the HTML. The marker is stripped before the response
205
+ * is sent; the browser never sees it.
206
+ * - Cache hit — children are **not** rendered at all. The cached HTML is
207
+ * injected directly, saving the entire subtree render cost.
208
+ *
209
+ * **Client:** renders children normally (no caching). Because the server
210
+ * strips the marker wrapper, the client output matches the server HTML and
211
+ * React hydration succeeds without warnings for deterministic components.
212
+ *
213
+ * **Note:** components that rely on request-specific data (cookies, auth,
214
+ * personalisation) must not be wrapped in `CacheSegment` unless the cache
215
+ * key encodes that data — otherwise a cached response for one user could be
216
+ * served to another.
217
+ */
218
+ declare function CacheSegment({ cacheKey, ttl, children }: CacheSegmentProps): react_jsx_runtime.JSX.Element;
219
+
220
+ declare function deleteSegment(key: string): void;
221
+ declare function clearSegments(): void;
222
+
184
223
  declare const HadarsContext: React$1.FC<{
185
224
  children: React.ReactNode;
186
225
  context: AppContext;
@@ -205,4 +244,4 @@ declare const HadarsContext: React$1.FC<{
205
244
  */
206
245
  declare function loadModule<T = any>(path: string): Promise<T>;
207
246
 
208
- export { HadarsApp, HadarsContext, HadarsEntryModule, HadarsGetAfterRenderProps, HadarsGetClientProps, HadarsGetFinalProps, HadarsGetInitialProps, Head as HadarsHead, HadarsOptions, HadarsProps, HadarsRequest, initServerDataCache, loadModule, useServerData };
247
+ export { CacheSegment, HadarsApp, HadarsContext, HadarsEntryModule, HadarsGetAfterRenderProps, HadarsGetClientProps, HadarsGetFinalProps, HadarsGetInitialProps, Head as HadarsHead, HadarsOptions, HadarsProps, HadarsRequest, clearSegments, deleteSegment, initServerDataCache, loadModule, useServerData };
package/dist/index.js CHANGED
@@ -176,8 +176,7 @@ function useServerData(key, fn) {
176
176
  }
177
177
  );
178
178
  unsuspend.cache.set(cacheKey, { status: "pending", promise: suspensePromise });
179
- unsuspend.hasPending = true;
180
- return void 0;
179
+ throw suspensePromise;
181
180
  }
182
181
  throw thrown;
183
182
  }
@@ -196,12 +195,10 @@ function useServerData(key, fn) {
196
195
  }
197
196
  );
198
197
  unsuspend.cache.set(cacheKey, { status: "pending", promise });
199
- unsuspend.hasPending = true;
200
- return void 0;
198
+ throw promise;
201
199
  }
202
200
  if (existing.status === "pending") {
203
- unsuspend.hasPending = true;
204
- return void 0;
201
+ throw existing.promise;
205
202
  }
206
203
  if (existing.status === "rejected")
207
204
  throw existing.reason;
@@ -258,6 +255,58 @@ var Head = React.memo(({ children, status }) => {
258
255
  return null;
259
256
  });
260
257
 
258
+ // src/components/CacheSegment.tsx
259
+ import React2 from "react";
260
+
261
+ // src/utils/segmentCache.ts
262
+ function getStore() {
263
+ const g = globalThis;
264
+ if (!g.__hadarsSegmentStore) {
265
+ g.__hadarsSegmentStore = /* @__PURE__ */ new Map();
266
+ }
267
+ return g.__hadarsSegmentStore;
268
+ }
269
+ function getSegment(key) {
270
+ const entry = getStore().get(key);
271
+ if (!entry)
272
+ return null;
273
+ if (entry.expiresAt !== null && Date.now() >= entry.expiresAt) {
274
+ getStore().delete(key);
275
+ return null;
276
+ }
277
+ return entry.html;
278
+ }
279
+ function deleteSegment(key) {
280
+ getStore().delete(key);
281
+ }
282
+ function clearSegments() {
283
+ getStore().clear();
284
+ }
285
+ var CACHE_TAG = "hadars-c";
286
+
287
+ // src/components/CacheSegment.tsx
288
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
289
+ function CacheSegment({ cacheKey, ttl, children }) {
290
+ if (typeof window !== "undefined") {
291
+ return /* @__PURE__ */ jsx2(Fragment, { children });
292
+ }
293
+ const cached = getSegment(cacheKey);
294
+ if (cached !== null) {
295
+ return React2.createElement(CACHE_TAG, {
296
+ "data-key": cacheKey,
297
+ "data-cache": "hit",
298
+ dangerouslySetInnerHTML: { __html: cached }
299
+ });
300
+ }
301
+ const props = {
302
+ "data-key": cacheKey,
303
+ "data-cache": "miss"
304
+ };
305
+ if (ttl != null)
306
+ props["data-ttl"] = ttl;
307
+ return React2.createElement(CACHE_TAG, props, children);
308
+ }
309
+
261
310
  // src/index.tsx
262
311
  var HadarsContext = typeof window === "undefined" ? AppProviderSSR : AppProviderCSR;
263
312
  function loadModule(path) {
@@ -267,8 +316,11 @@ function loadModule(path) {
267
316
  );
268
317
  }
269
318
  export {
319
+ CacheSegment,
270
320
  HadarsContext,
271
321
  Head as HadarsHead,
322
+ clearSegments,
323
+ deleteSegment,
272
324
  initServerDataCache,
273
325
  loadModule,
274
326
  useServerData
@@ -0,0 +1,18 @@
1
+ declare const SLIM_ELEMENT: unique symbol;
2
+ declare const FRAGMENT_TYPE: unique symbol;
3
+ declare const SUSPENSE_TYPE: unique symbol;
4
+ type ComponentFunction = (props: any) => SlimNode;
5
+ type SlimElement = {
6
+ $$typeof: typeof SLIM_ELEMENT;
7
+ type: string | ComponentFunction | symbol;
8
+ props: Record<string, any>;
9
+ key: string | number | null;
10
+ };
11
+ type SlimNode = SlimElement | string | number | boolean | null | undefined | SlimNode[];
12
+
13
+ declare const Fragment: symbol;
14
+ declare function jsx(type: string | ComponentFunction | symbol, props: Record<string, any>, key?: string | number | null): SlimElement;
15
+
16
+ declare function createElement(type: string | ComponentFunction | symbol, props?: Record<string, any> | null, ...children: SlimNode[]): SlimElement;
17
+
18
+ export { ComponentFunction as C, Fragment as F, SlimNode as S, SlimElement as a, SLIM_ELEMENT as b, createElement as c, FRAGMENT_TYPE as d, SUSPENSE_TYPE as e, jsx as j };