hadars 0.2.0 → 0.2.2-rc.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
@@ -30,7 +30,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.tsx
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- HadarsContext: () => HadarsContext,
34
33
  HadarsHead: () => Head,
35
34
  initServerDataCache: () => initServerDataCache,
36
35
  loadModule: () => loadModule,
@@ -40,7 +39,6 @@ module.exports = __toCommonJS(index_exports);
40
39
 
41
40
  // src/utils/Head.tsx
42
41
  var import_react = __toESM(require("react"), 1);
43
- var import_jsx_runtime = require("react/jsx-runtime");
44
42
  function deriveKey(tag, props) {
45
43
  switch (tag) {
46
44
  case "meta": {
@@ -70,134 +68,116 @@ function deriveKey(tag, props) {
70
68
  return `${tag}:${JSON.stringify(props)}`;
71
69
  }
72
70
  }
73
- var AppContext = import_react.default.createContext({
74
- setTitle: () => {
75
- console.warn("AppContext: setTitle called outside of provider");
76
- },
77
- addMeta: () => {
78
- console.warn("AppContext: addMeta called outside of provider");
79
- },
80
- addLink: () => {
81
- console.warn("AppContext: addLink called outside of provider");
82
- },
83
- addStyle: () => {
84
- console.warn("AppContext: addStyle called outside of provider");
85
- },
86
- addScript: () => {
87
- console.warn("AppContext: addScript called outside of provider");
88
- },
89
- setStatus: () => {
90
- }
91
- });
92
- var AppProviderSSR = import_react.default.memo(({ children, context }) => {
93
- const { head } = context;
94
- const setTitle = import_react.default.useCallback((title) => {
95
- head.title = title;
96
- }, [head]);
97
- const addMeta = import_react.default.useCallback((props) => {
98
- head.meta[deriveKey("meta", props)] = props;
99
- }, [head]);
100
- const addLink = import_react.default.useCallback((props) => {
101
- head.link[deriveKey("link", props)] = props;
102
- }, [head]);
103
- const addStyle = import_react.default.useCallback((props) => {
104
- head.style[deriveKey("style", props)] = props;
105
- }, [head]);
106
- const addScript = import_react.default.useCallback((props) => {
107
- head.script[deriveKey("script", props)] = props;
108
- }, [head]);
109
- const setStatus = import_react.default.useCallback((status) => {
110
- head.status = status;
111
- }, [head]);
112
- const contextValue = import_react.default.useMemo(() => ({
113
- setTitle,
114
- addMeta,
115
- addLink,
116
- addStyle,
117
- addScript,
118
- setStatus
119
- }), [setTitle, addMeta, addLink, addStyle, addScript, setStatus]);
120
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContext.Provider, { value: contextValue, children });
121
- });
122
- var AppProviderCSR = import_react.default.memo(({ children }) => {
123
- const setTitle = import_react.default.useCallback((title) => {
124
- document.title = title;
125
- }, []);
126
- const addMeta = import_react.default.useCallback((props) => {
127
- const p = props;
128
- let meta = null;
129
- if (p.name) meta = document.querySelector(`meta[name="${CSS.escape(p.name)}"]`);
130
- else if (p.property) meta = document.querySelector(`meta[property="${CSS.escape(p.property)}"]`);
131
- else if (p.httpEquiv ?? p["http-equiv"]) meta = document.querySelector(`meta[http-equiv="${CSS.escape(p.httpEquiv ?? p["http-equiv"])}"]`);
132
- else if ("charSet" in p || "charset" in p) meta = document.querySelector("meta[charset]");
133
- if (!meta) {
134
- meta = document.createElement("meta");
135
- document.head.appendChild(meta);
136
- }
137
- for (const [k, v] of Object.entries(p)) {
138
- if (v != null && v !== false) meta.setAttribute(k === "charSet" ? "charset" : k === "httpEquiv" ? "http-equiv" : k, String(v));
139
- }
140
- }, []);
141
- const addLink = import_react.default.useCallback((props) => {
142
- const p = props;
143
- let link = null;
144
- const asSel = p.as ? `[as="${CSS.escape(p.as)}"]` : "";
145
- if (p.rel && p.href) link = document.querySelector(`link[rel="${CSS.escape(p.rel)}"][href="${CSS.escape(p.href)}"]${asSel}`);
146
- else if (p.rel) link = document.querySelector(`link[rel="${CSS.escape(p.rel)}"]${asSel}`);
147
- if (!link) {
148
- link = document.createElement("link");
149
- document.head.appendChild(link);
150
- }
151
- const LINK_ATTR = { crossOrigin: "crossorigin", referrerPolicy: "referrerpolicy", fetchPriority: "fetchpriority", hrefLang: "hreflang" };
152
- for (const [k, v] of Object.entries(p)) {
153
- if (v != null && v !== false) link.setAttribute(LINK_ATTR[k] ?? k, String(v));
154
- }
155
- }, []);
156
- const addStyle = import_react.default.useCallback((props) => {
157
- const p = props;
158
- let style = null;
159
- if (p["data-id"]) style = document.querySelector(`style[data-id="${CSS.escape(p["data-id"])}"]`);
160
- if (!style) {
161
- style = document.createElement("style");
162
- document.head.appendChild(style);
71
+ var LINK_ATTR = {
72
+ crossOrigin: "crossorigin",
73
+ referrerPolicy: "referrerpolicy",
74
+ fetchPriority: "fetchpriority",
75
+ hrefLang: "hreflang"
76
+ };
77
+ function makeServerCtx(head) {
78
+ return {
79
+ setTitle: (t) => {
80
+ head.title = t;
81
+ },
82
+ addMeta: (p) => {
83
+ head.meta[deriveKey("meta", p)] = p;
84
+ },
85
+ addLink: (p) => {
86
+ head.link[deriveKey("link", p)] = p;
87
+ },
88
+ addStyle: (p) => {
89
+ head.style[deriveKey("style", p)] = p;
90
+ },
91
+ addScript: (p) => {
92
+ head.script[deriveKey("script", p)] = p;
93
+ },
94
+ setStatus: (s) => {
95
+ head.status = s;
163
96
  }
164
- for (const [k, v] of Object.entries(p)) {
165
- if (k === "dangerouslySetInnerHTML") {
166
- style.innerHTML = v.__html ?? "";
167
- continue;
97
+ };
98
+ }
99
+ var _cliCtx = null;
100
+ function makeClientCtx() {
101
+ if (_cliCtx) return _cliCtx;
102
+ _cliCtx = {
103
+ setTitle: (title) => {
104
+ document.title = title;
105
+ },
106
+ setStatus: () => {
107
+ },
108
+ addMeta: (props) => {
109
+ const p = props;
110
+ let meta = null;
111
+ if (p.name) meta = document.querySelector(`meta[name="${CSS.escape(p.name)}"]`);
112
+ else if (p.property) meta = document.querySelector(`meta[property="${CSS.escape(p.property)}"]`);
113
+ else if (p.httpEquiv ?? p["http-equiv"]) meta = document.querySelector(`meta[http-equiv="${CSS.escape(p.httpEquiv ?? p["http-equiv"])}"]`);
114
+ else if ("charSet" in p || "charset" in p) meta = document.querySelector("meta[charset]");
115
+ if (!meta) {
116
+ meta = document.createElement("meta");
117
+ document.head.appendChild(meta);
168
118
  }
169
- if (v != null && v !== false) style.setAttribute(k, String(v));
170
- }
171
- }, []);
172
- const addScript = import_react.default.useCallback((props) => {
173
- const p = props;
174
- let script = null;
175
- if (p.src) script = document.querySelector(`script[src="${CSS.escape(p.src)}"]`);
176
- else if (p["data-id"]) script = document.querySelector(`script[data-id="${CSS.escape(p["data-id"])}"]`);
177
- if (!script) {
178
- script = document.createElement("script");
179
- document.body.appendChild(script);
180
- }
181
- for (const [k, v] of Object.entries(p)) {
182
- if (k === "dangerouslySetInnerHTML") {
183
- script.innerHTML = v.__html ?? "";
184
- continue;
119
+ for (const [k, v] of Object.entries(p)) {
120
+ if (v != null && v !== false) meta.setAttribute(k === "charSet" ? "charset" : k === "httpEquiv" ? "http-equiv" : k, String(v));
121
+ }
122
+ },
123
+ addLink: (props) => {
124
+ const p = props;
125
+ let link = null;
126
+ const asSel = p.as ? `[as="${CSS.escape(p.as)}"]` : "";
127
+ if (p.rel && p.href) link = document.querySelector(`link[rel="${CSS.escape(p.rel)}"][href="${CSS.escape(p.href)}"]${asSel}`);
128
+ else if (p.rel) link = document.querySelector(`link[rel="${CSS.escape(p.rel)}"]${asSel}`);
129
+ if (!link) {
130
+ link = document.createElement("link");
131
+ document.head.appendChild(link);
132
+ }
133
+ for (const [k, v] of Object.entries(p)) {
134
+ if (v != null && v !== false) link.setAttribute(LINK_ATTR[k] ?? k, String(v));
135
+ }
136
+ },
137
+ addStyle: (props) => {
138
+ const p = props;
139
+ let style = null;
140
+ if (p["data-id"]) style = document.querySelector(`style[data-id="${CSS.escape(p["data-id"])}"]`);
141
+ if (!style) {
142
+ style = document.createElement("style");
143
+ document.head.appendChild(style);
144
+ }
145
+ for (const [k, v] of Object.entries(p)) {
146
+ if (k === "dangerouslySetInnerHTML") {
147
+ style.innerHTML = v.__html ?? "";
148
+ continue;
149
+ }
150
+ if (v != null && v !== false) style.setAttribute(k, String(v));
151
+ }
152
+ },
153
+ addScript: (props) => {
154
+ const p = props;
155
+ let script = null;
156
+ if (p.src) script = document.querySelector(`script[src="${CSS.escape(p.src)}"]`);
157
+ else if (p["data-id"]) script = document.querySelector(`script[data-id="${CSS.escape(p["data-id"])}"]`);
158
+ if (!script) {
159
+ script = document.createElement("script");
160
+ document.body.appendChild(script);
161
+ }
162
+ for (const [k, v] of Object.entries(p)) {
163
+ if (k === "dangerouslySetInnerHTML") {
164
+ script.innerHTML = v.__html ?? "";
165
+ continue;
166
+ }
167
+ if (v != null && v !== false) script.setAttribute(k, String(v));
185
168
  }
186
- if (v != null && v !== false) script.setAttribute(k, String(v));
187
- }
188
- }, []);
189
- const contextValue = import_react.default.useMemo(() => ({
190
- setTitle,
191
- addMeta,
192
- addLink,
193
- addStyle,
194
- addScript,
195
- setStatus: () => {
196
169
  }
197
- }), [setTitle, addMeta, addLink, addStyle, addScript]);
198
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContext.Provider, { value: contextValue, children });
199
- });
200
- var useApp = () => import_react.default.useContext(AppContext);
170
+ };
171
+ return _cliCtx;
172
+ }
173
+ function getCtx() {
174
+ if (typeof window === "undefined") {
175
+ const head = globalThis.__hadarsContext?.head;
176
+ if (!head) return null;
177
+ return makeServerCtx(head);
178
+ }
179
+ return makeClientCtx();
180
+ }
201
181
  var clientServerDataCache = /* @__PURE__ */ new Map();
202
182
  var pendingDataFetch = /* @__PURE__ */ new Map();
203
183
  var fetchedPaths = /* @__PURE__ */ new Set();
@@ -246,14 +226,17 @@ function useServerData(key, fn) {
246
226
  pendingDataFetch.set(pathKey, p);
247
227
  queueMicrotask(async () => {
248
228
  try {
249
- const res = await fetch(pathKey, {
250
- headers: { "Accept": "application/json" }
251
- });
252
- if (res.ok) {
253
- const json = await res.json();
254
- for (const [k, v] of Object.entries(json.serverData ?? {})) {
255
- clientServerDataCache.set(k, v);
256
- }
229
+ let json = null;
230
+ if (globalThis.__hadarsStatic) {
231
+ const sidecarUrl = pathKey.replace(/\/$/, "") + "/index.json";
232
+ const res = await fetch(sidecarUrl).catch(() => null);
233
+ if (res?.ok) json = await res.json().catch(() => null);
234
+ } else {
235
+ const res = await fetch(pathKey, { headers: { "Accept": "application/json" } });
236
+ if (res.ok) json = await res.json();
237
+ }
238
+ for (const [k, v] of Object.entries(json?.serverData ?? {})) {
239
+ clientServerDataCache.set(k, v);
257
240
  }
258
241
  } finally {
259
242
  fetchedPaths.add(pathKey);
@@ -267,26 +250,12 @@ function useServerData(key, fn) {
267
250
  const unsuspend = globalThis.__hadarsUnsuspend;
268
251
  if (!unsuspend) return void 0;
269
252
  const _u = unsuspend;
270
- if (!_u.seenThisPass) _u.seenThisPass = /* @__PURE__ */ new Set();
271
- if (!_u.seenLastPass) _u.seenLastPass = /* @__PURE__ */ new Set();
272
- if (_u.newPassStarting) {
273
- _u.seenLastPass = new Set(_u.seenThisPass);
274
- _u.seenThisPass.clear();
275
- _u.newPassStarting = false;
276
- }
277
- _u.seenThisPass.add(cacheKey);
253
+ if (!_u.pendingCreated) _u.pendingCreated = 0;
278
254
  const existing = unsuspend.cache.get(cacheKey);
255
+ if (existing?.status === "fulfilled" && _u.lastPendingKey === cacheKey) {
256
+ _u.lastPendingKeyAccessed = true;
257
+ }
279
258
  if (!existing) {
280
- if (_u.seenLastPass.size > 0) {
281
- const hasVanishedKey = [..._u.seenLastPass].some(
282
- (k) => !_u.seenThisPass.has(k)
283
- );
284
- if (hasVanishedKey) {
285
- throw new Error(
286
- `[hadars] useServerData: key ${JSON.stringify(cacheKey)} appeared in this pass but a key that was present in the previous pass is now missing. This means the key is not stable across render passes (e.g. it contains Date.now(), Math.random(), or a value that changes on every render). Keys must be deterministic.`
287
- );
288
- }
289
- }
290
259
  const result = fn();
291
260
  const isThenable = result !== null && typeof result === "object" && typeof result.then === "function";
292
261
  if (!isThenable) {
@@ -294,6 +263,22 @@ function useServerData(key, fn) {
294
263
  unsuspend.cache.set(cacheKey, { status: "fulfilled", value });
295
264
  return value;
296
265
  }
266
+ if (_u.lastPendingKey != null && !_u.lastPendingKeyAccessed) {
267
+ const prev = unsuspend.cache.get(_u.lastPendingKey);
268
+ if (prev?.status === "fulfilled") {
269
+ throw new Error(
270
+ `[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.`
271
+ );
272
+ }
273
+ }
274
+ _u.pendingCreated++;
275
+ if (_u.pendingCreated > 100) {
276
+ throw new Error(
277
+ `[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)}.`
278
+ );
279
+ }
280
+ _u.lastPendingKey = cacheKey;
281
+ _u.lastPendingKeyAccessed = false;
297
282
  const promise = result.then(
298
283
  (value) => {
299
284
  unsuspend.cache.set(cacheKey, { status: "fulfilled", value });
@@ -303,25 +288,18 @@ function useServerData(key, fn) {
303
288
  }
304
289
  );
305
290
  unsuspend.cache.set(cacheKey, { status: "pending", promise });
306
- _u.newPassStarting = true;
307
291
  throw promise;
308
292
  }
309
293
  if (existing.status === "pending") {
310
- _u.newPassStarting = true;
311
294
  throw existing.promise;
312
295
  }
313
296
  if (existing.status === "rejected") throw existing.reason;
314
297
  return existing.value;
315
298
  }
316
299
  var Head = import_react.default.memo(({ children, status }) => {
317
- const {
318
- setStatus,
319
- setTitle,
320
- addMeta,
321
- addLink,
322
- addStyle,
323
- addScript
324
- } = useApp();
300
+ const ctx = getCtx();
301
+ if (!ctx) return null;
302
+ const { setStatus, setTitle, addMeta, addLink, addStyle, addScript } = ctx;
325
303
  if (status) {
326
304
  setStatus(status);
327
305
  }
@@ -366,7 +344,6 @@ var Head = import_react.default.memo(({ children, status }) => {
366
344
  });
367
345
 
368
346
  // src/index.tsx
369
- var HadarsContext = typeof window === "undefined" ? AppProviderSSR : AppProviderCSR;
370
347
  function loadModule(path) {
371
348
  return import(
372
349
  /* webpackIgnore: true */
@@ -375,7 +352,6 @@ function loadModule(path) {
375
352
  }
376
353
  // Annotate the CommonJS export names for ESM import in node:
377
354
  0 && (module.exports = {
378
- HadarsContext,
379
355
  HadarsHead,
380
356
  initServerDataCache,
381
357
  loadModule,
package/dist/index.d.cts CHANGED
@@ -1,7 +1,5 @@
1
- import * as React$1 from 'react';
2
- import React__default from 'react';
3
- import { A as AppContext } from './hadars-Bh-V5YXg.cjs';
4
- export { b as HadarsApp, H as HadarsEntryModule, c as HadarsGetClientProps, d as HadarsGetFinalProps, e as HadarsGetInitialProps, a as HadarsOptions, f as HadarsProps, g as HadarsRequest } from './hadars-Bh-V5YXg.cjs';
1
+ export { G as GraphQLExecutor, b as HadarsApp, H as HadarsEntryModule, c as HadarsGetClientProps, d as HadarsGetFinalProps, e as HadarsGetInitialProps, a as HadarsOptions, f as HadarsProps, g as HadarsRequest, h as HadarsSourceEntry, i as HadarsStaticContext } from './hadars-mKu5txjW.cjs';
2
+ import React from 'react';
5
3
 
6
4
  /** Call this before hydrating to seed the client cache from the server's data.
7
5
  * Invoked automatically by the hadars client bootstrap.
@@ -34,15 +32,11 @@ declare function initServerDataCache(data: Record<string, unknown>): void;
34
32
  * if (!user) return null; // undefined while pending on the first SSR pass
35
33
  */
36
34
  declare function useServerData<T>(key: string | string[], fn: () => Promise<T> | T): T | undefined;
37
- declare const Head: React__default.FC<{
38
- children?: React__default.ReactNode;
35
+ declare const Head: React.FC<{
36
+ children?: React.ReactNode;
39
37
  status?: number;
40
38
  }>;
41
39
 
42
- declare const HadarsContext: React$1.FC<{
43
- children: React.ReactNode;
44
- context: AppContext;
45
- }>;
46
40
  /**
47
41
  * Dynamically loads a module with target-aware behaviour:
48
42
  *
@@ -63,4 +57,4 @@ declare const HadarsContext: React$1.FC<{
63
57
  */
64
58
  declare function loadModule<T = any>(path: string): Promise<T>;
65
59
 
66
- export { HadarsContext, Head as HadarsHead, initServerDataCache, loadModule, useServerData };
60
+ export { Head as HadarsHead, initServerDataCache, loadModule, useServerData };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,5 @@
1
- import * as React$1 from 'react';
2
- import React__default from 'react';
3
- import { A as AppContext } from './hadars-Bh-V5YXg.js';
4
- export { b as HadarsApp, H as HadarsEntryModule, c as HadarsGetClientProps, d as HadarsGetFinalProps, e as HadarsGetInitialProps, a as HadarsOptions, f as HadarsProps, g as HadarsRequest } from './hadars-Bh-V5YXg.js';
1
+ export { G as GraphQLExecutor, b as HadarsApp, H as HadarsEntryModule, c as HadarsGetClientProps, d as HadarsGetFinalProps, e as HadarsGetInitialProps, a as HadarsOptions, f as HadarsProps, g as HadarsRequest, h as HadarsSourceEntry, i as HadarsStaticContext } from './hadars-mKu5txjW.js';
2
+ import React from 'react';
5
3
 
6
4
  /** Call this before hydrating to seed the client cache from the server's data.
7
5
  * Invoked automatically by the hadars client bootstrap.
@@ -34,15 +32,11 @@ declare function initServerDataCache(data: Record<string, unknown>): void;
34
32
  * if (!user) return null; // undefined while pending on the first SSR pass
35
33
  */
36
34
  declare function useServerData<T>(key: string | string[], fn: () => Promise<T> | T): T | undefined;
37
- declare const Head: React__default.FC<{
38
- children?: React__default.ReactNode;
35
+ declare const Head: React.FC<{
36
+ children?: React.ReactNode;
39
37
  status?: number;
40
38
  }>;
41
39
 
42
- declare const HadarsContext: React$1.FC<{
43
- children: React.ReactNode;
44
- context: AppContext;
45
- }>;
46
40
  /**
47
41
  * Dynamically loads a module with target-aware behaviour:
48
42
  *
@@ -63,4 +57,4 @@ declare const HadarsContext: React$1.FC<{
63
57
  */
64
58
  declare function loadModule<T = any>(path: string): Promise<T>;
65
59
 
66
- export { HadarsContext, Head as HadarsHead, initServerDataCache, loadModule, useServerData };
60
+ export { Head as HadarsHead, initServerDataCache, loadModule, useServerData };