hadars 0.2.0 → 0.2.1

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.
@@ -0,0 +1,64 @@
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-DEBSYAQl.cjs';
2
+
3
+ /**
4
+ * Cloudflare Workers adapter for hadars.
5
+ *
6
+ * After running `hadars build`, bundle your app with:
7
+ *
8
+ * hadars export cloudflare
9
+ *
10
+ * This produces a self-contained `cloudflare.mjs` that you deploy with:
11
+ *
12
+ * wrangler deploy
13
+ *
14
+ * Static assets (JS, CSS, fonts) under `.hadars/static/` must be served from
15
+ * R2 or another CDN — the Worker only handles HTML rendering. Route requests
16
+ * for static file extensions to R2 and everything else to the Worker.
17
+ *
18
+ * @example wrangler.toml
19
+ * name = "my-app"
20
+ * main = "cloudflare.mjs"
21
+ * compatibility_date = "2024-09-23"
22
+ * compatibility_flags = ["nodejs_compat"]
23
+ */
24
+
25
+ /**
26
+ * Pre-loaded SSR module and HTML template for single-file Cloudflare bundles
27
+ * produced by `hadars export cloudflare`. All I/O is eliminated at runtime —
28
+ * the Worker is fully self-contained.
29
+ */
30
+ interface CloudflareBundled {
31
+ /** The compiled SSR module — import it statically in your entry shim. */
32
+ ssrModule: HadarsEntryModule<any>;
33
+ /**
34
+ * The contents of `.hadars/static/out.html` — esbuild inlines this as a
35
+ * string when `hadars export cloudflare` runs.
36
+ */
37
+ outHtml: string;
38
+ }
39
+ /**
40
+ * The shape of a Cloudflare Workers export object.
41
+ * Return this as the default export of your Worker entry file.
42
+ */
43
+ interface CloudflareHandler {
44
+ fetch(request: Request, env: unknown, ctx: unknown): Promise<Response>;
45
+ }
46
+ /**
47
+ * Creates a Cloudflare Workers handler from a hadars config and a pre-bundled
48
+ * SSR module. Use this as the default export of your Worker entry.
49
+ *
50
+ * Unlike the Lambda adapter, Cloudflare Workers receive a standard Web
51
+ * `Request` and return a standard `Response` — no event format conversion is
52
+ * required. Static assets must be routed to R2/CDN via wrangler rules; this
53
+ * Worker handles only HTML rendering and API routes.
54
+ *
55
+ * @example — generated entry shim (created by `hadars export cloudflare`)
56
+ * import * as ssrModule from './.hadars/index.ssr.js';
57
+ * import outHtml from './.hadars/static/out.html';
58
+ * import { createCloudflareHandler } from 'hadars/cloudflare';
59
+ * import config from './hadars.config';
60
+ * export default createCloudflareHandler(config, { ssrModule, outHtml });
61
+ */
62
+ declare function createCloudflareHandler(options: HadarsOptions, bundled: CloudflareBundled): CloudflareHandler;
63
+
64
+ export { type CloudflareBundled, type CloudflareHandler, createCloudflareHandler };
@@ -0,0 +1,64 @@
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-DEBSYAQl.js';
2
+
3
+ /**
4
+ * Cloudflare Workers adapter for hadars.
5
+ *
6
+ * After running `hadars build`, bundle your app with:
7
+ *
8
+ * hadars export cloudflare
9
+ *
10
+ * This produces a self-contained `cloudflare.mjs` that you deploy with:
11
+ *
12
+ * wrangler deploy
13
+ *
14
+ * Static assets (JS, CSS, fonts) under `.hadars/static/` must be served from
15
+ * R2 or another CDN — the Worker only handles HTML rendering. Route requests
16
+ * for static file extensions to R2 and everything else to the Worker.
17
+ *
18
+ * @example wrangler.toml
19
+ * name = "my-app"
20
+ * main = "cloudflare.mjs"
21
+ * compatibility_date = "2024-09-23"
22
+ * compatibility_flags = ["nodejs_compat"]
23
+ */
24
+
25
+ /**
26
+ * Pre-loaded SSR module and HTML template for single-file Cloudflare bundles
27
+ * produced by `hadars export cloudflare`. All I/O is eliminated at runtime —
28
+ * the Worker is fully self-contained.
29
+ */
30
+ interface CloudflareBundled {
31
+ /** The compiled SSR module — import it statically in your entry shim. */
32
+ ssrModule: HadarsEntryModule<any>;
33
+ /**
34
+ * The contents of `.hadars/static/out.html` — esbuild inlines this as a
35
+ * string when `hadars export cloudflare` runs.
36
+ */
37
+ outHtml: string;
38
+ }
39
+ /**
40
+ * The shape of a Cloudflare Workers export object.
41
+ * Return this as the default export of your Worker entry file.
42
+ */
43
+ interface CloudflareHandler {
44
+ fetch(request: Request, env: unknown, ctx: unknown): Promise<Response>;
45
+ }
46
+ /**
47
+ * Creates a Cloudflare Workers handler from a hadars config and a pre-bundled
48
+ * SSR module. Use this as the default export of your Worker entry.
49
+ *
50
+ * Unlike the Lambda adapter, Cloudflare Workers receive a standard Web
51
+ * `Request` and return a standard `Response` — no event format conversion is
52
+ * required. Static assets must be routed to R2/CDN via wrangler rules; this
53
+ * Worker handles only HTML rendering and API routes.
54
+ *
55
+ * @example — generated entry shim (created by `hadars export cloudflare`)
56
+ * import * as ssrModule from './.hadars/index.ssr.js';
57
+ * import outHtml from './.hadars/static/out.html';
58
+ * import { createCloudflareHandler } from 'hadars/cloudflare';
59
+ * import config from './hadars.config';
60
+ * export default createCloudflareHandler(config, { ssrModule, outHtml });
61
+ */
62
+ declare function createCloudflareHandler(options: HadarsOptions, bundled: CloudflareBundled): CloudflareHandler;
63
+
64
+ export { type CloudflareBundled, type CloudflareHandler, createCloudflareHandler };
@@ -0,0 +1,68 @@
1
+ import {
2
+ buildHeadHtml,
3
+ buildSsrHtml,
4
+ createProxyHandler,
5
+ createRenderCache,
6
+ getReactResponse,
7
+ makePrecontentHtmlGetter,
8
+ parseRequest
9
+ } from "./chunk-HWOLYLPF.js";
10
+ import "./chunk-LY5MTHFV.js";
11
+ import "./chunk-OS3V4CPN.js";
12
+
13
+ // src/cloudflare.ts
14
+ import "react";
15
+ function createCloudflareHandler(options, bundled) {
16
+ const fetchHandler = options.fetch;
17
+ const handleProxy = createProxyHandler(options);
18
+ const getPrecontentHtml = makePrecontentHtmlGetter(Promise.resolve(bundled.outHtml));
19
+ const { ssrModule } = bundled;
20
+ const runHandler = async (req) => {
21
+ const request = parseRequest(req);
22
+ if (fetchHandler) {
23
+ const res = await fetchHandler(request);
24
+ if (res) return res;
25
+ }
26
+ const proxied = await handleProxy(request);
27
+ if (proxied) return proxied;
28
+ try {
29
+ const { default: Component, getInitProps, getFinalProps } = ssrModule;
30
+ const { head, status, getAppBody, finalize } = await getReactResponse(request, {
31
+ document: {
32
+ body: Component,
33
+ lang: "en",
34
+ getInitProps,
35
+ getFinalProps
36
+ }
37
+ });
38
+ if (request.headers.get("Accept") === "application/json") {
39
+ const { clientProps: clientProps2 } = await finalize();
40
+ const serverData = clientProps2.__serverData ?? {};
41
+ return new Response(JSON.stringify({ serverData }), {
42
+ status,
43
+ headers: { "Content-Type": "application/json; charset=utf-8" }
44
+ });
45
+ }
46
+ const bodyHtml = await getAppBody();
47
+ const { clientProps } = await finalize();
48
+ const headHtml = buildHeadHtml(head);
49
+ const html = await buildSsrHtml(bodyHtml, clientProps, headHtml, getPrecontentHtml);
50
+ return new Response(html, {
51
+ status,
52
+ headers: { "Content-Type": "text/html; charset=utf-8" }
53
+ });
54
+ } catch (err) {
55
+ console.error("[hadars] SSR render error:", err);
56
+ return new Response("Internal Server Error", { status: 500 });
57
+ }
58
+ };
59
+ const finalFetch = options.cache ? createRenderCache(options.cache, (req) => runHandler(req)) : (req) => runHandler(req);
60
+ return {
61
+ fetch: async (request, _env, ctx) => {
62
+ return await finalFetch(request, ctx) ?? new Response("Not Found", { status: 404 });
63
+ }
64
+ };
65
+ }
66
+ export {
67
+ createCloudflareHandler
68
+ };
@@ -1,5 +1,3 @@
1
- import { MetaHTMLAttributes, LinkHTMLAttributes, StyleHTMLAttributes, ScriptHTMLAttributes } from 'react';
2
-
3
1
  type HadarsGetInitialProps<T extends {}> = (req: HadarsRequest) => Promise<T> | T;
4
2
  type HadarsGetClientProps<T extends {}> = (props: T) => Promise<T> | T;
5
3
  type HadarsGetFinalProps<T extends {}> = (props: HadarsProps<T>) => Promise<T> | T;
@@ -10,43 +8,10 @@ type HadarsEntryModule<T extends {}> = {
10
8
  getFinalProps?: HadarsGetFinalProps<T>;
11
9
  getClientProps?: HadarsGetClientProps<T>;
12
10
  };
13
- interface AppHead {
14
- title: string;
15
- status: number;
16
- meta: Record<string, MetaProps>;
17
- link: Record<string, LinkProps>;
18
- style: Record<string, StyleProps>;
19
- script: Record<string, ScriptProps>;
20
- }
21
- type UnsuspendEntry = {
22
- status: 'pending';
23
- promise: Promise<unknown>;
24
- } | {
25
- status: 'fulfilled';
26
- value: unknown;
27
- } | {
28
- status: 'rejected';
29
- reason: unknown;
30
- };
31
- /** @internal Populated by the framework's render loop — use useServerData() instead. */
32
- interface AppUnsuspend {
33
- cache: Map<string, UnsuspendEntry>;
34
- }
35
- interface AppContext {
36
- path?: string;
37
- head: AppHead;
38
- /** @internal Framework use only — use the useServerData() hook instead. */
39
- _unsuspend?: AppUnsuspend;
40
- }
41
11
  type HadarsEntryBase = {
42
12
  location: string;
43
- context: AppContext;
44
13
  };
45
14
  type HadarsProps<T extends {}> = T & HadarsEntryBase;
46
- type MetaProps = MetaHTMLAttributes<HTMLMetaElement>;
47
- type LinkProps = LinkHTMLAttributes<HTMLLinkElement>;
48
- type StyleProps = StyleHTMLAttributes<HTMLStyleElement>;
49
- type ScriptProps = ScriptHTMLAttributes<HTMLScriptElement>;
50
15
  interface HadarsOptions {
51
16
  port?: number;
52
17
  entry: string;
@@ -196,4 +161,4 @@ interface HadarsRequest extends Request {
196
161
  cookies: Record<string, string>;
197
162
  }
198
163
 
199
- export type { AppContext as A, HadarsEntryModule as H, HadarsOptions as a, HadarsApp as b, HadarsGetClientProps as c, HadarsGetFinalProps as d, HadarsGetInitialProps as e, HadarsProps as f, HadarsRequest as g };
164
+ export type { HadarsEntryModule as H, HadarsOptions as a, HadarsApp as b, HadarsGetClientProps as c, HadarsGetFinalProps as d, HadarsGetInitialProps as e, HadarsProps as f, HadarsRequest as g };
@@ -1,5 +1,3 @@
1
- import { MetaHTMLAttributes, LinkHTMLAttributes, StyleHTMLAttributes, ScriptHTMLAttributes } from 'react';
2
-
3
1
  type HadarsGetInitialProps<T extends {}> = (req: HadarsRequest) => Promise<T> | T;
4
2
  type HadarsGetClientProps<T extends {}> = (props: T) => Promise<T> | T;
5
3
  type HadarsGetFinalProps<T extends {}> = (props: HadarsProps<T>) => Promise<T> | T;
@@ -10,43 +8,10 @@ type HadarsEntryModule<T extends {}> = {
10
8
  getFinalProps?: HadarsGetFinalProps<T>;
11
9
  getClientProps?: HadarsGetClientProps<T>;
12
10
  };
13
- interface AppHead {
14
- title: string;
15
- status: number;
16
- meta: Record<string, MetaProps>;
17
- link: Record<string, LinkProps>;
18
- style: Record<string, StyleProps>;
19
- script: Record<string, ScriptProps>;
20
- }
21
- type UnsuspendEntry = {
22
- status: 'pending';
23
- promise: Promise<unknown>;
24
- } | {
25
- status: 'fulfilled';
26
- value: unknown;
27
- } | {
28
- status: 'rejected';
29
- reason: unknown;
30
- };
31
- /** @internal Populated by the framework's render loop — use useServerData() instead. */
32
- interface AppUnsuspend {
33
- cache: Map<string, UnsuspendEntry>;
34
- }
35
- interface AppContext {
36
- path?: string;
37
- head: AppHead;
38
- /** @internal Framework use only — use the useServerData() hook instead. */
39
- _unsuspend?: AppUnsuspend;
40
- }
41
11
  type HadarsEntryBase = {
42
12
  location: string;
43
- context: AppContext;
44
13
  };
45
14
  type HadarsProps<T extends {}> = T & HadarsEntryBase;
46
- type MetaProps = MetaHTMLAttributes<HTMLMetaElement>;
47
- type LinkProps = LinkHTMLAttributes<HTMLLinkElement>;
48
- type StyleProps = StyleHTMLAttributes<HTMLStyleElement>;
49
- type ScriptProps = ScriptHTMLAttributes<HTMLScriptElement>;
50
15
  interface HadarsOptions {
51
16
  port?: number;
52
17
  entry: string;
@@ -196,4 +161,4 @@ interface HadarsRequest extends Request {
196
161
  cookies: Record<string, string>;
197
162
  }
198
163
 
199
- export type { AppContext as A, HadarsEntryModule as H, HadarsOptions as a, HadarsApp as b, HadarsGetClientProps as c, HadarsGetFinalProps as d, HadarsGetInitialProps as e, HadarsProps as f, HadarsRequest as g };
164
+ export type { HadarsEntryModule as H, HadarsOptions as a, HadarsApp as b, HadarsGetClientProps as c, HadarsGetFinalProps as d, HadarsGetInitialProps as e, HadarsProps as f, HadarsRequest as g };
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();
@@ -267,26 +247,12 @@ function useServerData(key, fn) {
267
247
  const unsuspend = globalThis.__hadarsUnsuspend;
268
248
  if (!unsuspend) return void 0;
269
249
  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);
250
+ if (!_u.pendingCreated) _u.pendingCreated = 0;
278
251
  const existing = unsuspend.cache.get(cacheKey);
252
+ if (existing?.status === "fulfilled" && _u.lastPendingKey === cacheKey) {
253
+ _u.lastPendingKeyAccessed = true;
254
+ }
279
255
  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
256
  const result = fn();
291
257
  const isThenable = result !== null && typeof result === "object" && typeof result.then === "function";
292
258
  if (!isThenable) {
@@ -294,6 +260,22 @@ function useServerData(key, fn) {
294
260
  unsuspend.cache.set(cacheKey, { status: "fulfilled", value });
295
261
  return value;
296
262
  }
263
+ if (_u.lastPendingKey != null && !_u.lastPendingKeyAccessed) {
264
+ const prev = unsuspend.cache.get(_u.lastPendingKey);
265
+ if (prev?.status === "fulfilled") {
266
+ throw new Error(
267
+ `[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.`
268
+ );
269
+ }
270
+ }
271
+ _u.pendingCreated++;
272
+ if (_u.pendingCreated > 100) {
273
+ throw new Error(
274
+ `[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)}.`
275
+ );
276
+ }
277
+ _u.lastPendingKey = cacheKey;
278
+ _u.lastPendingKeyAccessed = false;
297
279
  const promise = result.then(
298
280
  (value) => {
299
281
  unsuspend.cache.set(cacheKey, { status: "fulfilled", value });
@@ -303,25 +285,18 @@ function useServerData(key, fn) {
303
285
  }
304
286
  );
305
287
  unsuspend.cache.set(cacheKey, { status: "pending", promise });
306
- _u.newPassStarting = true;
307
288
  throw promise;
308
289
  }
309
290
  if (existing.status === "pending") {
310
- _u.newPassStarting = true;
311
291
  throw existing.promise;
312
292
  }
313
293
  if (existing.status === "rejected") throw existing.reason;
314
294
  return existing.value;
315
295
  }
316
296
  var Head = import_react.default.memo(({ children, status }) => {
317
- const {
318
- setStatus,
319
- setTitle,
320
- addMeta,
321
- addLink,
322
- addStyle,
323
- addScript
324
- } = useApp();
297
+ const ctx = getCtx();
298
+ if (!ctx) return null;
299
+ const { setStatus, setTitle, addMeta, addLink, addStyle, addScript } = ctx;
325
300
  if (status) {
326
301
  setStatus(status);
327
302
  }
@@ -366,7 +341,6 @@ var Head = import_react.default.memo(({ children, status }) => {
366
341
  });
367
342
 
368
343
  // src/index.tsx
369
- var HadarsContext = typeof window === "undefined" ? AppProviderSSR : AppProviderCSR;
370
344
  function loadModule(path) {
371
345
  return import(
372
346
  /* webpackIgnore: true */
@@ -375,7 +349,6 @@ function loadModule(path) {
375
349
  }
376
350
  // Annotate the CommonJS export names for ESM import in node:
377
351
  0 && (module.exports = {
378
- HadarsContext,
379
352
  HadarsHead,
380
353
  initServerDataCache,
381
354
  loadModule,